Skip to content

0.1.7 — the verb set a tool advertises, against the paths it records

Choose a tag to compare

@github-actions github-actions released this 29 Aug 06:13
f26cda0

acc check now prints an ADVERTISED VERBS vs RECORDED PATHS block: the verb
set a target names at its own root, stated against the first token of every
path in a --recorded-surfaces batch, in both directions. recorded but never advertised is the defect direction. advertised but never recorded renders
as not covered by this batch and never as "missing", because for a
deliberately partial batch that is the expected state.

IT IS EVIDENCE, NOT A RULE, and the condition is load-bearing: the recorded
side is the caller's own attestation, so nothing the KIT fails on may rest on
it. It carries no rule id, contributes to no verdict and to no rule tally, and
changes no exit code — the block is not on any exit path, and the two cases
checked behaved accordingly: a fixture that fails core rules still exits 9 and
a conforming one still exits 0, with the block printed either way. (The block
does print counts of its own, and the JSON carries lists; what it does not do
is move any number this kit already reports.)

An adopter who wants a gate builds it themselves, on the JSON, and status is
the test rather than field presence: every run this release writes carries
.data.advertisedVerbs, a report written before it carries none, and the
three comparison arrays appear only when a comparison happened.
.data.advertisedVerbs carries status, one of not-asserted, no-batch or
compared; capturesRead; open, the openness of the union; union, the
set the directions are computed over; hedged, naming captures that were read
and NOT asserted; quoted, the capture whose words are shown, with its own
shape, from, open and originating observation; and, when status is
compared, recordedNotAdvertised, notCoveredByBatch and disagreement.
union is the set the directions are computed over; hedged names captures
that were read and NOT asserted, so a run can say which surfaces it declined.

A gate over that has three failure modes worth closing explicitly, because two
of them fail silently:

  1. Check the exit code first. 1-8 means the invocation failed, so the JSON is
    absent or partial and status is undefined. A gate written purely over fields
    passes a broken run.
  2. Branch on status, do not just test recordedNotAdvertised. compared
    with an empty array is a clean comparison; not-asserted and no-batch mean
    nothing was compared at all. A gate that treats those alike is green because
    nothing ran — on the fleet described further down, that is half the tools.
  3. Read open — the top-level one — before failing. It carries the
    openness of the UNION, which is the set the directions are computed over;
    quoted.open describes only the capture whose words were quoted, and the two
    differ when one capture's list is elided and the other's is not. On an open
    list the verb may live in the elided tail, so a defect there is a question
    rather than a finding.

It has to be TWO checks, not one conjunction, and this is the part most
likely to be got wrong: a defect-only gate answers fire or do-not-fire, and
not-asserted and a clean compared both answer do-not-fire. They are not
the same result and a build cannot tell them apart. Note also that
recordedNotAdvertised is present only when status is compared, so
testing it non-empty already implies the status — adding a status clause to
the defect conjunction changes no outcome and buys nothing.

ran   = exit code NOT in 1-8, and status === "compared"
clean = ran, and recordedNotAdvertised is empty (or top-level `open` is true)

Fail the build on ran && !clean. Treat !ran as its own state — it is not a
pass, and on the fleet described further down it is half the tools. Exit codes
1-8 are excluded because the invocation failed and the JSON is absent or
partial; the band above 8 stays open-ended, and 10 already exists, so a
literal allowlist of 0 and 9 would skip a run whose JSON is complete.
Gating on it means accepting caller-attested evidence as a failure condition —
which is the thing this kit will not do on your behalf.

THE BLOCK PRINTS ON EVERY RUN, including the runs where it did nothing, and
the case where it says it did NOT run is the one to read first:

THE COMPARISON DID NOT RUN — no advertised verb set could be asserted at the
root, so the recorded paths were compared against nothing.
2 root captures read, none asserted a verb set (NOT a tool that advertises
no verbs)
This is not agreement: nothing was compared, and nothing here is a finding
about the tool.

On the fleet this was designed against, half the tools fail on BOTH root
sources: their bare invocation prints a help screen with no usage:-anchored
bracket group, and their unknown-verb rejection enumerates nothing at all.
With neither source asserting, this is the common render there rather than a
safety net — the adopter's measurement, not ours, and it is why a reader who
mistook it for an empty advertised set would turn every recorded path into a
finding against the tool that was really an artifact of our parse. The
measurement behind "half" is the adopter's, reported in
docs/plans/2026-08-28-the-declaration-skeleton.md; it is not ours.

TWO SHAPES ARE READ, AND ONLY FROM TWO PLACES. A choices array in a JSON
error envelope, and a pipe-delimited bracket group on a usage: line — the
legacy shape, which is where the drift this was reported for actually lives.
Both are read only from the bare invocation and the unknown-verb rejection,
never from a --help body, and the existing exclusions (timeout, crash, spawn
failure, truncation) apply to whichever is read.

THE NARROWING STACK REFUSES CHEAPEST-FIRST: a usage:-anchored line, the
first bracket group after the program token (so usage: cli <open|state> <file> never contributes file), at least one pipe — which is what kills a
standalone <file> or <command>, and is not a formatting quirk, and every
member token-shaped or the whole blob is refused. A two-member blob is not
asserted on shape alone — <name|id> is a type union and <get|set> is a
two-verb tool, and no lexical rule separates them; at that size the last
discriminator is whether a majority of the blob's members appear among the
recorded first tokens.

⚠ That discriminator has a consequence worth stating, because it runs against
this block's own purpose: on a TWO-VERB tool the admission test consults the
recorded side, so drift can suppress the finding it would have produced. A
tool advertising <get|set> whose recorded paths are get and deploy
matches one of two, the set is refused, status is not-asserted, and
deploy is never reported — recorded, unadvertised, and silent. That is
not-asserted arising from the drift itself rather than from a parse failure,
and it is a known limit of the two-member rule rather than a defect in it: no
lexical rule separates a two-verb tool from a type union, and the alternative
is asserting <name|id> as a verb set.

… AND ... ARE OPEN-SET MARKERS, dropped from the set and carried as a
flag, and the flag qualifies the finding rather than suppressing it — a defect
line on an open list says the verb may live in the elided tail.

WHERE TWO CAPTURES BOTH ASSERT, precedence and comparison are split on
purpose. The rejection's words are quoted; the UNION of the asserted sets is
what the directions are computed over. Testing recorded paths against the
rejection alone would turn a verb only the bare capture names into an
accusation manufactured by our own choice of source. A disagreement line
names spellings one asserted capture carries and the other does not — it
catches a partial retrofit, where a tool grows a choices array while its
hand-maintained usage string goes stale. That line has NOT been exercised
against a real divergence; it is recorded as untested rather than assumed
passed.

THE JSON CARRIES THE WHOLE SET AND THE TEXT LINE DOES NOT. The text caps at 32
verbs with the true count beside it; the JSON is never sampled. That asymmetry
is the point: the action on a defect line is "go add THESE to your help", and
a sample plus a count would be the tool saying what it did while withholding
what it found — so the work list lives where a script picks it up. The JSON
set is not truncated to fit. A target emitting a pathological list overruns
MAX_STREAM_BYTES, and a capture cut at that ceiling is EXCLUDED rather than
read — the same exclusion that covers a timeout, a crash and a spawn failure.
So an asserted set is a whole set, an overrunning target yields no assertion
at all, and a script never receives a short list that looks complete. The set
lives in a new advertisedVerbs field rather than widening
surface.nonFlagCandidates, whose contract is diagnostic where this one is an
assertion, and it is derived once so nothing parses a stream twice.

A STORED REPORT WRITTEN BEFORE THIS SAYS SO. acc report on a JSON with no
advertisedVerbs prints that the artifact predates the comparison, naming the
kit that wrote it, rather than omitting the block.

KNOWN LIMIT: the comparison reads the FIRST TOKEN of each recorded path, so
state show contributes state and paths deeper than one token are invisible
to it. A usage: line's bracket group names one token and cannot express
depth, so this is a property of what can be advertised rather than an
oversight — recorded in the plan rather than left for a reader to discover.

VALIDATED BY THE ADOPTER WHO ASKED FOR IT, on a develop install against three
targets of their choosing. It found a true positive on its first real run: a
dispatched alias that works, carried in the tool's help prose — which this
census never reads — and absent from the rejection's choices array, which it
does. So the verb was recorded, was not advertised at the surface the census
reads, and the line said so correctly. The fix is tool-side.

That is also the shape the note's own caveat is about, and the two do not
cancel: the line was right, and on a tool whose aliases live only in help it
can read a notch stronger than a reader expects. Written down and deliberately
not built, with its own trigger — a second adopter tripping on it. This was
NOT a disagreement between two root captures; that line remains unexercised.

THE SKILL NOW GIVES THE EXIT CODE ITS MEANING where a reader first meets it.
Section 2 states the bands at the top: 0 is success, 1-8 mean the INVOCATION
failed, and 9 and up are OUTCOMES — the invocation worked and the answer was
negative. What the outcome is ABOUT is whatever that command's subject is: for
acc check it is the target, and for version --check the 10 it can return
is about the installed kit. Three points on that contract were checked rather
than the bands as a whole: a malformed invocation exits 2, a non-conformant
target exits 9, a conformant one exits 0. The skill also now names
node_modules/agent-cli-conformance/ as where to read the guides after
install, because that copy matches the pin; a clone or the web view is a
fallback, and the version mismatch is the reason it is only that.

THE REGISTRY GUIDE NOW TEACHES READING A TABLE, not only building one. It
gains the inspection act — enumerate the paths your dispatch reaches, read
your own usage line, diff the two — which is the technique that produced the
most valuable finding of any adoption trial so far, with no acc feature
involved. It is the cheapest thing on the page and needs no restructure. The
section says what it does not do: it tells you what drifted, it leaves no
artifact that notices the next drift. And the adopter's own warning is quoted
rather than paraphrased — a committed path list is a third parallel document,
and the cure for parallel documents is not another one.

The guide also gains a worked example of the emitter it had only argued for,
in seven excerpts ending in a runnable round trip. It refuses to hide what it
costs: five hand-maintained surfaces are named, the contract test is shown
with what it MISSES, the flat case is marked as flat with five questions
nesting raises that it cannot answer, and aliases are noted as doubling the
evidence a complete census needs.

The rest of the range is a design record and a consumer does not act on it: a
proposal for a declaration-skeleton generator that two independent reviews
blocked on a false premise and a refusal mechanism with no home in the format,
then reopened by a question that found a veto already in the tree, then
costed, specified, and settled into the comparison above. The skeleton itself
is NOT built and is not recommended.

0.1.7 (2026-08-29)

Features

  • kit: acc check compares advertised verbs to recorded paths (18ca6df)
  • kit: compare the advertised verb set against the recorded paths (09123c2)