Repository navigation
0.1.9 — eight corrections to the report guide, and a gate against absent fields
This release changes what the kit SAYS, not what it does. Every consumer-visible
change is in the check-report JSON guide — the page acc show how-to-read-the-check-report-json --body prints and the page a script author is
pointed at. Eight of its statements were wrong; at least one of them describes a
check a reader can write today that will never fire.
What did not change
Nothing about the tool's behaviour, and this is the more useful half of the note
if you are deciding whether to upgrade.
- No report field was added, removed, renamed or retyped. The same target checked
with the previous release's kit and this one produces documents that differ only
where any two runs differ:capturedAtand the millisecond timings. acc schemais byte-identical across the range.- No rule, checker, verdict, count, exit code, flag or command changed. The only
source file in the range issrc/acc/kit/report.ts, and its changes are two
new exported constants used by the repo's own lint plus corrected comments; no
interface gained, lost or altered a field.
The upgrade itself breaks nothing. The corrections below are a different
matter: several describe checks that were wrong when you wrote them and are
wrong now, so read them as a work list rather than as reference.
Corrections you may have to act on
Each was checked against the code or the tests before being written here.
spawnFailedis alwaysfalsein any report this kit writes. The guide
previously invited a CI check on it. That check can never fire: the first probe
that cannot spawn aborts the run (record.ts) and surfaces as anot_found
error envelope (commands/check.ts), so no report is produced at all. If you
branch onspawnFailedto catch a target that will not execute, you have a
check that passes silently forever — test for thenot_foundenvelope instead.
This is the one correction that turns a working-looking pipeline into a known
blind spot.exitCodeandsignalare not "exactly one of the two". Both are null on
the path where the target'sclosenever arrived, andexitCodeis also
nulled when the kit killed the target itself —timedOutORtruncated, and
the guide never mentioned the second. Code shaped asexitCode === null→
"signalnames the death" breaks on both. Readcrashed,timedOutand
truncatedbefore attributing a death.countsis not a partition; do not derive one count by subtracting others.
Every count is taken over the applicable, unwaived findings, and a passing
diagnostic finding is counted by none of them.evidenceGapsis not scoped to passes. Fails and unverifieds contribute
rows too, opening with the verdict and the finding'sdetail. Reading a row as
a qualified pass is the misreading this field most invites; check the
matching finding'sverdictfirst.sweepreads in one direction only. The hash covers observation ids, exit
codes, signals and the two stream digests, and nothing else. Two different
marks prove the evidence differs; equal marks do NOT prove it identical — a
hang and an outside kill that both died silently collide, and so do two runs
differing only in timing. The old text claimed the equal direction.launchAdjustmentis a replay aid, not a delivery check. It reports the
argv the kit put on the WIRE; what the target receives isargs, which is the
point of the compensation. Presence tells you the compensation fired; absence
tells you nothing, because the ordinary absence is a target with no bun layer to
compensate for. So you cannot read the field, either way, as confirming that the
target got what was recorded — including in the one documented case where
delivery really does differ, a bun layer hidden inside a wrapper script, where
nothing fires and nothing is reported.excusedwas never explained at all — it appeared only as a value in the
specimen, so this is a new statement rather than a corrected one, listed here
because it is equally actionable. It is true only while aknownFailuresentry
is suppressing a livefailorunverified; it goes false the moment the rule
passes, and the config entry then surfaces instaleExpectations.timeToFirstByteMs: nullis not a hang. A silent success produces the same
null;timedOutis what separates them.- A finding's
detailcarries no per-observation clause. The lookup table
promised one clause per observation that decided the verdict, which the body of
the same page denied. Useprobesfor what was sent, never the clause order.
The page also gained entries rather than corrections for fields it never
mentioned at all — capturedAt, declaration, recordedSurfaces,
stdoutDigest and stderrDigest — and for fields that previously appeared only
inside the JSON specimen or in passing, where a reader got a value and no
statement: probeLevel, waived, unresolved, notApplicable, and
the staleExpectations/inertExpectations pair. It also gained an
advertisedVerbs specimen printed by a command the page gives you, run against
the kit's own CLI, so its shape can be checked rather than trusted.
For contributors: the gate behind it
Two report fields shipped undocumented in two consecutive releases, each caught
only because a reviewer happened to look. Two chained gates now make that a build
failure rather than a habit. DOCUMENTED_REPORT_FIELDS in
src/acc/kit/report.ts is a spec bound by a mapped type to the four interfaces
a report is built from — Report, ReportedFinding, ReportedObservation and
EvidenceProbe — so adding a field to an interface fails tsc until the field
is listed; being listed then fails the artifact docs lint
(bun run docs:lint:artifacts) until the field is either defined in the guide or
declared in UNDOCUMENTED_REPORT_FIELDS. The lint prints two numbers on every
run — currently report fields: 64 listed, 0 declared undocumented — because one
number cannot separate a field that got documented from a field that left the
type.
State the limits, because a green check is a claim:
- Top-level keys only. A key whose value is an object —
surface,counts,
configSource,targetIdentity,declaration,recordedSurfaces,
advertisedVerbs, the elements ofwaivers— is a leaf to the gate, and the
fields inside it are covered by neither half. Both misses that motivated the
work were top-level keys; a recursive spec is separate, larger work. - Shape, not truth. The gate certifies that a definition-shaped sentence
exists. It cannot read one. A bullet that restates the field's own name passes,
and that is worse than absence, because absence at least signals undocumented. - Forward only. A field removed from a type but left in the guide is not
caught. - Declared absence is a legal answer. A field may pass by being named in
UNDOCUMENTED_REPORT_FIELDSinstead of being explained. The property is "no
field is absent without someone saying so", not "every field is documented".
The set is empty at this revision, so nothing is currently declared undocumented.
0.1.9 (2026-08-30)
Features
- docs: declared absence, so no field is silently undocumented (a547130)
- docs: the JSON guide cannot omit a report field (1031c1a)
- docs: the JSON guide cannot silently omit a report field (0739e1d)
Bug Fixes
- docs: correct four invented claims in the JSON guide rewrite (1299465)
- docs: drop the overreaching counts clause and anchor the term fallback (0bb7a60)
- docs: repair an inverted field description and six review findings (4f8ca86)
- docs: three more false bullets, and both homes of the sweep claim (a69fb5a)