Development record
An exclusion control asserts only what it can prove
Context
itd-183 shipped the cold-reading assembler with an exclusion floor and a manifest that states which constructs the floor refused. Ten records stand against that floor. Read one at a time they are ten spellings; read together they are one mechanism with one consequence.
The mechanism is that the floor recognises a construct by the shape of the
container it sits in rather than by what the container holds. A file whose
extension is not .md is not scanned. A region the fence mask wrongly covers
is not scanned. A frontmatter block that does not open at line 0 is not
frontmatter. In each case the floor does not fail; it declines, silently.
The consequence is the part that makes this an architectural decision rather than a bug list. The manifest goes on asserting the exclusion regardless. A third party holding the bundle and the manifest cannot tell the difference between a scan that ran and found nothing and a scan that never ran, because the two produce byte-identical attestations. That is not a leak with a manifest attached; it is an attestation that does not attest. The itd-183 fidelity audit found it live on this repository's own corpus, and the auditor recorded the finding as manifest and bundle disagreeing with the manifest as the side that is wrong (iss-2608301450065320).
Nine rounds of review closed nine spellings of that mistake by adding patterns. The tenth was found not by review but by running the instrument. The records name the pattern themselves: a spelling arms race needs a design, not more patterns.
Decision
An exclusion control asserts only what it can prove.
- A control that declines to run does not report a clean pass. Where a
control cannot examine an input — the type is one it cannot parse, the
construct is one it cannot resolve, the region is one it cannot bound — the
answer is a refusal naming the input, never silent admission. This is the
loud-stagingprinciple applied to a control rather than to a stage, and it is already the shapeunresolvableFrontmatterShapeholds for one class.
- An attestation may not outrun the examination behind it. A manifest, receipt, or verdict that states a construct was excluded is making a claim about a scan that ran over the bytes that shipped. Where the two can diverge, the artefact states the narrower truth, or the divergence is closed before the artefact is emitted. An assertion whose truth conditions are not established by the code that emits it is a false statement waiting for a reader.
- Admission and comprehension describe one set. Where a system admits inputs through one surface and examines them through another, the two surfaces are held to the same set. A gap between them is not an implementation detail: it is the space in which point 2's divergence lives. Whichever surface moves, they meet.
The rule binds the cold-reading assembler first, and every later control whose output is an attestation a third party relies on.
Alternatives Considered
Widen the floor to read whatever the include table admits. The floor gains a real parse applied to any admitted document, so a record-shaped construct is recognised inside a Go fixture, a JSON blob or a doc comment. This closes the class from the comprehension side and keeps the corpus a reading sees at full size. Rejected for now on cost and provability: it grows a parser the floor does not have, and the resulting claim is empirical (the parse caught everything) where the chosen branch's claim is structural (nothing unparseable was admitted). Recorded rather than dismissed — it is the branch to revisit if the narrowing proves to cost more corpus than a reading can spare, which itd-194 leaves as an open question with the measurement unowned.
Keep adding signals. Rejected on evidence rather than on principle: nine rounds did exactly this, closed nine spellings, and left the tenth live on the corpus. The records are unanimous that fixing them one at a time is how the floor spent those rounds.
Restate the manifest's claim honestly and admit the gap. The manifest would say which documents were scanned rather than which constructs were excluded, leaving the reader to judge. Rejected as insufficient alone: it makes the attestation true, which is point 2, but leaves the leak, and a reader who must reconstruct the control's coverage from a document list has been handed the control's job.
Consequences
- itd-194 implements point 3 by moving admission: the include table refuses what the floor cannot parse. Point 2 then holds by construction rather than by scan.
- The corpus a reading sees gets smaller, by an amount nobody has measured. itd-194 carries the measurement as an open question; this ADR is the reason it must be taken before a reading runs rather than after.
- The rule reaches past the assembler. Any later verb emitting a receipt, verdict or manifest inherits points 1 and 2, and the brief carries it as an invariant so a new control cannot be written without meeting it.
- The rejected branch stays live as a revisit path, so a later widening is an amendment to this ADR rather than a rediscovery of the argument.
Refinement (2026-09-02)
The third rule above, that admission and comprehension describe one set, is refined by the maintainer's ruling that source and tests are opt-in to a reading and that an opted-in item travels whole and marked unscanned in the manifest. Where a control admits an input it cannot examine, it may pass the input only if its attestation says, per item, that the examination did not run; the second rule then carries what the third rule carried, and the two surfaces are made to agree by disclosure rather than by refusal. Refusal remains the rule for an input the control claims to have examined and could not. Recorded in the decision log on 2026-09-02; implemented by itd-194.
Frontmatter
| id | adr-56 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-08-30 |
.abcd/development/decisions/adrs/0056-an-exclusion-control-asserts-only-what.md
open on GitHub ↗ · commit history ↗
References2
Referenced by8
- adr-58A reading is commissioned about something, so the invocation takes a scoperelated
- adr-2609021016270132The principles family is a declared record store whose entries carry typed claims, and it is read cold by statement and never by citationrelated
- adr-2609021016272867The comparative reading receives one widening run's candidates, a positional exception to the prior-run exhaust, with the run derived from the record and no operand addedrelated
- adr-2609021016275803No session holds both a reading and the ledger, and a per-run context stamp is how the record shows itrelated
- adr-2609021016286571The invocation is a position and a target state, and the committed preset for the position supplies the restrelated
- adr-2609090717039680The transcript corpus is a sibling store that creates itself, and the per-repo location is an opt-in pullrelated
- adr-2609091248201071The transcript corpus is a sibling store that creates itself through the canonical directory primitiverelated
- itd-2609151516525843A Repository Can Ban A Name On Its First Commit, And A Machine Can Ban One Everywhererelated
mentions7
- dcn-26090200000000112026-09-02 — adr-56's third rule is refined, not contradicted, by the source-and-tests ruling (maintainer): a control that admits an input it cannot examine may pass it only with a per-item unscanned mark in its attestation, so the second rule (an attestation never says more than the examination) carries what the third rule carried; refusal stays the rule for an input the control claims to have examined and could not. Invariant 16 amended to say so. Every ADR is now checked against the framework v4 and the companion v4: adr-58 is superseded by adr-2609021016286571, adr-2609021016272867 is corrected to the derived run, and none of adr-55, adr-57, adr-2609021016275803, adr-2609021016270132, adr-2609021016288378 or adr-2609021016286571 contradicts either document. The two-hundred-thousand-token target is a clarification the documents do not speak to, kept as such.
- dcn-26091500000000032026-09-15 — Banned names get both halves (product thinker, ruling on iss-2609100506269348, choosing "both" once told a home list is invisible to CI): a committed declaration lifts the public visibility fence so a fresh public repository can create its committed banned-names layer on its first commit, and a machine-global private list in the user-level home bans a name in every repository on the machine. CI never reads the home list and nothing from it reaches a committed file. Filed as itd-2609151516525843 (`builds_on` itd-74, `refines` adr-56); whether the home is one macOS user's or shared across users stays the product thinker's separate open question. Rejected: move the banlist config out of the hidden directory; make the public list private-visibility only.
- iss-2608301450065320the assembler's key and heading signals are scoped to markdown so a record-shaped document inside a Go fixture carries its Audit Notes into the bundle while the manifest asserts refusal
- iss-2608301808198621isAbsentValue decides on literal strings rather than the yaml null and empty class so chasing spellings cannot close it
- itd-2609020625400194An admission and a surprise are written by a verb, and the order the design fixes is a refusal
- spc-2609020626039834The comparative channel: one widening run's candidates, two fields each, characterised before anyone admits one
- spc-2609020626045177The scribe verb: an allow-list assembler, an authoring-refusing ingest, and a per-run context stamp the transcript store can check