Phase 7 filed anxious/agent-wip/release-policy-derivation as `measured` at 11.94% of audited spend on a 30-day window. The same candidate over 7 days came back `thin` at 0.00%. Only the date range differed. A human reading either issue body alone cannot know the other exists, which is rubric.md §4a's failure mode reached through the plugin's own headline number. That was caught by a human running a second skeptical review — luck, not a control. It is now mechanical, in the same register as FR-4.2's overrule-case gate: - bin/stability-classify matches each `file` candidate against the most recent prior snapshot recording it (FR-5.2 identity) and downgrades it to a boundary question if `measurement_strength` changed or the share crossed the 2% filing threshold between windows. Movement within a label is a trend, not an instability; no prior window is `unchecked`, not unstable (FR-2.4). - The downgrade is a finding against THIS repo, not the target's — the target did not change, the auditor described it two ways. Carries an `ia-stability` marker so a re-run comments rather than duplicating. - bin/ia_store.py factors the store and identity resolution out of audit-snapshot so both scripts hash signatures identically. A divergence there would make the gate match nothing and fail open. Documented as spec FR-4.5 and rubric §5b (shipped verbatim in references/). Wired into skills/offload-audit as step 4b, before filing-plan; steps 5-6 now consume its output rather than classified.json. S4 is retired in its old form — a human triage catching a bad candidate is not a repeatable test — and re-passed as: the gate catches the real release-policy-derivation case with no human in the loop. Asserted in tests/stability.test.sh case (f) against the verbatim two-window Phase 7 output. Closes kotkan/claude-plugin-inference-arbitrage#11 Suite: 178 assertions, exit 0.
17 KiB
rubric.md — where the boundary between script and intelligence belongs
"the problem here is to correctly understand a points where we need intelligence between script entities correctly positioned"
This is the crux of the whole plugin, so it gets its own document. It ships
verbatim as references/boundary-rubric.md, and every snapshot records the
rubric_version it was classified under.
The rubric has five parts, applied in order:
- Five determinism tests — is this step a program at all?
- Four positions — the answer is not binary, and the interesting cases are the two in the middle.
- The positioning principle — given a pipeline, where exactly do you cut?
- Two failure modes — named, with detection questions for each.
- The falsifiability triple — the gate a candidate's reasoning must pass to be filed. 5b. The evidence-stability gate — the gate its evidence must pass.
1. The five determinism tests
A step may be offloaded to a script only if it passes all five. These are necessary conditions, not a score to be averaged — a step that fails one has failed, however well it does on the others.
T1 — Closed input domain
Can the inputs be enumerated or typed?
Paths, exit codes, JSON documents, git refs, file contents, timestamps, integers, a fixed set of enum values — closed. Free-form natural language, a user's stated intent, an arbitrary error message from an unknown tool, the contents of a web page — open.
The tell: if you can write the function's parameter list without using the type "whatever the human said," it is closed.
T2 — Stable output oracle
Is there one right answer, and can you assert it in a test?
If two competent engineers looking at the same input would produce the same
output, and you can write assert f(x) == y, there is an oracle. If the answer
is "it depends what we're trying to do here," there is not.
This is the strongest single test, because it is constructive. If you cannot write the assertion, you do not have a specification, and code without a specification is just inference with worse error messages. The inability to write the test is the finding.
T3 — Invariance
Same input, same output, every time — across runs, models, and days?
Then a script is strictly better: it is free, instant, and cannot drift.
The diagnostic is subtle and worth stating carefully. Ask: if this step gave a different answer on two identical inputs, would that be a bug or a feature? If it would be a bug, the step is a script and running it on inference is introducing nondeterminism into something that should not have any. If run-to-run variation is acceptable or desirable — a differently-worded explanation, a different but equally valid prioritization — that variance is the signature of judgment, and freezing it into a script destroys something real.
T4 — No semantic gap
Does the answer depend on anything not present in the inputs?
World knowledge, the user's goals, project history, what "good" means here, whether a name is apt, whether a description is honest, whether this counts as a bug — all live outside the inputs. A step that needs them cannot be a total function over its arguments, and any script pretending otherwise is encoding a guess as a fact.
T4 is the test that most often saves a step from being wrongly scripted.
T5 — Bounded branch factor
Is the decision tree enumerable?
A dozen cases, or a hundred, or a table — enumerable. "Any of the open-ended ways this could go wrong" — not. A step with an unbounded tail of special cases can still be partly scripted (handle the enumerable cases, escalate the tail), but that is position 3 below, not position 1.
Reading the results
| Outcome | Position |
|---|---|
| All five pass | pure-script |
| T2 or T4 fails | pure-inference — stop, do not propose an offload |
| T1/T3/T5 fail but T2 and T4 hold | Decomposable — go to §3 and find the cut |
| Fails only because the inputs are unstructured | Often a digest problem, not a judgment problem — a script can structure the input even if it cannot make the decision |
That last row matters more than it looks. A great many steps look like judgment only because nobody normalized the input.
2. The four positions
The binary "script or LLM" is the wrong frame, and forcing it is how both failure modes in §4 happen. There are four positions, and the two in the middle are where almost all the value is.
Position 1 — pure-script
Deterministic end to end. Offload wholly; the model does not participate.
Parse, glob, diff, count, aggregate, sort by a formula, validate against a schema, git plumbing, format a report from structured data.
Real instance: cc-tokens' entire JSONL pipeline. Nothing in the
de-duplication of (message.id, requestId) or the application of a pricing table
benefits from a model.
Position 2 — script-with-compiled-judgment
The judgment is real, but it is made once, at authoring time, by a human or a model, and then frozen into a rule, table, regex, threshold, or config. Runtime is a pure script.
Real instance: price-hunter's require/exclude/broken/confirm rules per
hunt. Deciding that a hunt for a product must match the product noun and not the
flavour word is judgment; applying that rule to ten thousand listings is not.
The characteristic risk is rule rot. The compiled judgment was correct for the
world as it was at authoring time. When the world moves, the script keeps applying
the old judgment, confidently and silently — and this is precisely why the
tune-hunt skill exists. Any position-2 recommendation must therefore carry a
staleness plan: how the rule is re-validated, and what signal says it has rotted.
A position-2 offload without a re-validation story is not a saving, it is a
deferred bug.
Position 3 — llm-over-script-digest — the dominant win
The script gathers, filters, normalizes, joins, and ranks. The model judges only the shortlist. This is the position the user is describing when they say "intelligence between script entities correctly positioned," and it is where most real candidates land.
Real instance: worktree-audit. The script enumerates worktrees and computes the
provably-safe classification (including the remote-upstream subtlety that
inference kept getting wrong); the model looks only at the must-reconcile pile
and decides whether a given dirty tree is someone's in-progress work worth
rescuing.
Prospective instance: anxious:steward. The deterministic axes of the label
taxonomy are computed by script across all repos; the model is handed only the
disagreements, the no-signal cases, and the weak-prior axes.
The whole design question in position 3 is where the cut goes — §3.
Position 4 — pure-inference
Irreducible. Do not touch, and say so explicitly in the report, because "this is correctly done by inference" is a finding.
Intent, naming, prose, trade-off explanation for this particular user, novel synthesis, disambiguation, adversarial or unstructured input, and — always — the authorization call on an irreversible action.
3. The positioning principle — where exactly to cut
Given a pipeline of steps, the boundary belongs at its narrowest waist: the point where, upstream, volume is high and every transformation is mechanical and total; at the point, the representation is small and typed; and downstream, the decisions are few and semantic.
Formally, you are choosing the cut that minimizes
tokens_crossing_the_boundary × judgment_calls_downstream
subject to one hard constraint that dominates the optimization:
Semantic sufficiency. The digest crossing the boundary must contain everything the judgment rests on.
A cut that saves tokens but strips the evidence the decision depends on is a false offload, and it is worse than no offload: it doesn't slow the model down, it makes it wrong, and it hides the reason. The model downstream cannot know what the script filtered away, so it will answer confidently from a mutilated view.
The four positioning questions
Ask these in order about any proposed cut:
P1 — What crosses? Write the schema of the digest. If you cannot write it, you have not found the boundary — you have only found a place where you would like one to be.
P2 — What is lost? For each field the script drops, name the judgment that would have used it. If any judgment downstream would have used a dropped field, the cut is in the wrong place. Move it upstream.
P3 — What happens on surprise? When the script meets an input it was not designed for, does it escalate to the model, or does it silently return a wrong answer? A correct boundary always has an escalation path. A script whose only options are "handle it" and "handle it wrong" is at the wrong altitude. This is the same shape as the taxonomy's own axis-1 rule ending in "no signal — defer to human".
P4 — Who owns the irreversible action? The script may compute, propose, rank, and prepare. It must not authorize. Anything that deletes, deploys, pushes, sends, or spends stays behind a judgment gate — and this holds even when all five determinism tests pass, because determinism is not the same as safety. Blast radius is an independent axis and it overrides the rubric.
The compression heuristic
Good cuts show a large information-compression ratio with near-zero semantic loss.
A script that turns 400 KB of git worktree list + git log + git status
output into a 12-row typed table of (path, branch, dirty, has_remote_upstream, classification) is a good cut: three orders of magnitude of compression, and
nothing a human would have used got dropped.
A script that turns the same input into "7 worktrees, 3 stale" is a bad cut at
the same location — same compression, catastrophic semantic loss. The location was
right; the schema was wrong. Most bad cuts are bad schemas at good locations,
which is why P1 and P2 come before anything else.
4. The two failure modes
4a — Over-scripting (premature crystallization)
Freezing a judgment that legitimately varies. The system becomes fast, confident, and wrong, and — the real damage — nobody is watching anymore, because the step no longer produces output a human reads.
Detection questions:
- Does the correct answer depend on anything not in the inputs? (T4)
- Would the rule need to change when the world changes, and would anyone notice?
- Are you scripting the decision, or scripting the gathering for the decision? The second is almost always available and almost always safe.
- Is there a case where a competent human would look at the script's output and say "no, not this time"? If yes, that case must escalate, not be encoded.
4b — Under-scripting (inference as interpreter)
Using a model as a slow, expensive, nondeterministic interpreter for something a program does correctly. This is the status quo the plugin exists to find, and it is invisible precisely because it works — the answers are right, just paid for at ~one full context re-read per step.
Detection questions:
- Does the same tool sequence recur across invocations? (§5.2.2 of
spec.mdmeasures exactly this) - Has the model ever got this step wrong in a way a script could not? A
recorded error here is strong evidence — the
@{u}bug in worktree classification is the canonical example. - Is the model re-deriving, every invocation, a procedure that is already written down in the skill body as numbered steps?
- Would a competent engineer, shown this step, reach for a shell one-liner?
The asymmetry
These two errors are not equally bad, and the rubric is deliberately biased.
Under-scripting costs money. Over-scripting costs correctness, silently, and compounds. A missed candidate means a plugin stays somewhat expensive and the next audit will find it again. A wrong candidate means a script that gives a confident wrong answer for a year with no one watching. Hence the conservative filing threshold, and hence §5.
5. The falsifiability triple — the gate
Before any candidate may be filed as an issue (FR-4.2), the agent must be able to write, in the issue body itself, all three of:
(a) The function signature. Name, typed parameters, typed return. If it cannot be typed, T1 or T2 failed and this is not a script.
(b) Three input→expected-output pairs, one of which is an edge case. These are the test assertions. If they cannot be written, T2 failed — there is no oracle, and what looked like an algorithm is a judgment wearing an algorithm's clothes.
(c) The case where a human would overrule the script. The input where the script returns its defined answer and a competent human says "no, not here" — plus what the escalation path does about it.
(c) is the load-bearing element, and it is deliberately the hardest.
An agent that cannot name the overrule case has not understood the boundary; it has only noticed that a step looks repetitive. That is exactly the state in which over-scripting happens. So the rule is strict and mechanical:
No overrule case → downgrade to boundary question. Report it to the user as an open question about where the boundary belongs. Never file it.
The inverse is also informative. If the overrule case turns out to be common — if the human would overrule often — then the step is not a position-1 script at all. It is position 3, and the cut belongs earlier in the pipeline, at the gather/normalize stage rather than at the decision.
Boundary questions are a first-class output, not a failure. They are the plugin asking the user for the judgment the user is uniquely able to supply, which is precisely the behavior the rubric is trying to teach.
5b. The evidence-stability gate
The triple in §5 asks whether the reasoning survives scrutiny. This asks whether the evidence does.
A candidate's headline is a number and a word: measured, 12% of audited spend.
Both come from a window — the range of transcripts the dynamic pass happened to
read — and the window is chosen by whoever ran the audit, usually by typing a
default. If the same candidate reads measured, 12% over thirty days and
thin, 0% over seven, then the audit has not measured the candidate. It has
measured the calendar, and reported the result as a property of the plugin.
The damage is specific, and it is §4a's damage arriving by a new route. A human reading the issue sees one window's figure, with no indication that another window exists or disagrees. They cannot discount what they are not shown. So the number reads as a fact, gets acted on as a fact, and the correction — if it ever comes — arrives only because someone independently re-ran the audit on a different range and happened to notice. That is not a control. It is luck with a review attached.
Hence the same shape of rule as §5:
Evidence that changes label across windows → downgrade to boundary question. Compare each filable candidate against the most recent prior window that recorded it. If the evidence-strength label differs, or if the two windows land on opposite sides of the filing threshold, the candidate is not filed.
Two boundaries make this a usable rule rather than a blanket refusal.
Movement is not instability. A candidate at 12% one window and 8% the next is stable: the human is told the same thing, and the difference is a trend, which the snapshot diff already reports as one. The gate fires only when the description a human receives would change — the label, or which side of the threshold the value falls on. Everything else is signal about the target, and suppressing it would throw away the trend the plugin exists to accumulate.
Absence of history is not instability. The first audit of a target has nothing to compare against, and a candidate seen for the first time has no earlier window. Those file normally, marked unchecked. Inventing an instability finding out of missing history would mean no plugin could ever be audited once, which contradicts the principle that a plugin with no measured history stays auditable on its definition alone.
The instability, when it fires, is a finding about the auditor. The target did not change; the tool described it two different ways. So it is reported against this plugin's own repo, not the target's — the same self-application discipline as §FR-8, applied to the plugin's own headline number.
6. Confidence scoring
boundary_confidence |
Requires |
|---|---|
high |
All five determinism tests pass, position 1 or 3, falsifiability triple complete including the overrule case, escalation path defined |
medium |
Tests pass but the triple is partial, or position 2 without a staleness plan, or the digest schema (P1) is not yet writable |
low |
A repetition or cost signal exists, but T2 or T4 is in doubt |
Only high is filed as an issue. medium and low are reported as boundary
questions in the run summary and carried forward in snapshots, so that a later
audit with more measured evidence can promote them.
Carrying them forward matters: a low candidate that recurs across four audits
with growing cost is itself a signal worth surfacing, even if no single run could
confidently classify it.