Date: 2026-08-12 Status: accepted (2026-08-13; ratification record below)
Context
The product prints and documents claims about the depth of its deterministic gate. Measured on the
shipped 0.33.5 (dist/cli.js, every case executed 2026-08-12, none reasoned), each claim is
stronger than the mechanism behind it. The reader this matters to is the one runward exists not to
deceive: an auditor handed a green strict run.
"The gate verifies the shape of the evidence" holds for 1 rule of 64.
rules --jsonlists 64 rules and exactly onesignature:(frontier-deterministic-boundary). Reproduced on a fresh scaffold, one run, two rows side by side:hexa-architecture | applied | file:src/guard.ts#guardFields, pointing at a real file with no relation whatsoever to hexagonal architecture, raises zero violations, while the one signed rule on the same run raises "evidence does not match the rule's signature" with the hint "(cited, not applied?)". The credibility sentence ADR-0020 made true is true exactly once. Everywhere else the pointer layer verifies existence, not shape, and nothing in the run says which regime a given row was judged under.The symbol check passes on any substring, while its message claims presence.
src/lib/evidence.ts:413testscontent.includes(p.symbol). Executed: a file containing onlyguardFieldsLegacy, pointer#guardFields, is green; the control#guardRowsreds with "symbol not found in the file (moved or renamed? update the pointer)". So the exact case the message names, a renamed symbol, stays green whenever the old name is a prefix or fragment of the new one, and a seal can sit on a pointer naming an identifier that no longer exists. This is a declared depth, not a silence: ADR-0019 declared the substring and ADR-0045 preserved it by name ("the declared depth limits, unchanged"). Changing it amends a documented decision, and this ADR is that amendment, stated rather than slipped in.The run never says how thin the signed share is. "What this gate verified" counts typed versus prose rows (
src/lib/evidence.ts:562-595, renderedsrc/commands/check.ts:150-167) but not signed versus merely resolved. An operator reading "36 of 36 pointers the gate opened and checked (100%)" cannot tell that for 35 of them "checked" meant: the file exists, is non-empty, is not circular, and contains the substring. Never: has the rule's shape.
The unfavorable case, plainly: today the gate's strongest printed sentence covers its weakest verification, in silence. A sibling decision from the same product review, ADR-0053 (accepted), bounds a different overclaim (exit 0 read as "mission complete" during construction); the two are independent and neither depends on the other landing.
Decision
Three moves. Each either strengthens the mechanism to match the sentence, or narrows the sentence
to match the mechanism. Nothing else moves: the 0/1/2 exit contract
(ADR-0030), the six phases, GATE_NON_SCOPE, and the
seal's semantics are untouched.
1. Symbols match at identifier boundaries. For a pointer symbol of identifier form
(/^[A-Za-z_$][A-Za-z0-9_$]*$/), the match requires an occurrence not embedded in a larger
identifier: the escaped symbol wrapped in (?<![\w$]) and (?![\w$]), replacing the bare
includes at src/lib/evidence.ts:413. Quoted symbols keep the exact-substring semantics they
already have by construction. Test names (test:PATH::NAME, line 416) are untouched: they are
prose, and an identifier boundary has no meaning in prose. The violation message does not change;
"moved or renamed? update the pointer" simply becomes true.
2. A slice of signatures, adjudicated rule by rule. Coverage grows only where the rule's own
text prescribes its tokens, which is the ADR-0020 line verbatim ("added rule by rule, each
justified by tokens the rule text itself prescribes, never wholesale"); no amendment is needed.
Realistic target: 8 to 12 of the 31 CRITICAL/HIGH rules mapped to the gated phases. Candidates to
adjudicate, not commitments: provider-no-crash-missing-env (its text prescribes the env-guard
idiom), resilience-retry-backoff (names retry and exponential backoff),
config-secrets-boundary, security-prompt-injection. Every accepted signature: is justified in
the rule's frontmatter by the tokens the text prescribes; passes unsafeSignature (ADR-0045
class 9, a signature must never hang the gate); and is validated against both reference missions
before merge. A candidate whose tokens are not genuinely prescribed is not signed; the target is
not a quota. ADR-0020's reevaluation trigger carries over verbatim: one confirmed false red on a
legitimate implementation retires that signature immediately.
3. The run names the signed share of the verdict. On the model of the critical-scope line
(src/lib/verdict.ts:98-103, a count the sentence could not honestly omit): "N of M applied
row(s) rest on a signed rule; for the others, the gate verified that the evidence exists, not its
shape." Printed in "What this gate verified"; counted, never gated; one additive JSON field under
the ADR-0030 additive-only contract. This is the ADR-0045 doctrine applied to the gate's own
headline: where the gate cannot verify, it says so in the run, not only in the abstract.
Three adjacent paper cuts (the missing-row message naming runward manifest --sync, the
in-progress label naming its true cause, an advisory when several applied rows carry an
identical prose cell) change no decision and ship without an ADR; they live on the roadmap.
Alternatives discarded
- Sign the 31 mapped CRITICAL/HIGH rules at once. Rejected by ADR-0020 itself, in its own
alternatives ("Signatures on every CRITICAL rule at once"): most rules have no canonical token,
hexa-architecture"matches no grep", and a forced signature manufactures false reds, which erode the gate faster than any gap. The same ADR's trigger also forbids the drift toward pseudo-semantic checking that a blanket pass would be. - Identifier-boundary match on test names too. Test names are prose, with spaces and punctuation; an identifier boundary has no defined meaning there, and the quoted case is already exact. The hardening covers only symbols shaped like identifiers.
- Make duplicated prose a blocking gap. Would reverse the standing acceptance of prose evidence (the untyped complement of ADR-0019) and the alternative ADR-0045 already discarded ("Require a typed pointer on every
appliedrow"): an absence cannot be pointed at, and prose is legitimate exactly there. The coherent move is a counted advisory, and it is a roadmap item, not a decision.
Consequences
- Green missions will redden, and that is the point, but it is a rupture. Two populations:
missions whose pointer symbol was a prefix or fragment of the real identifier (a seal over a
dead pointer), and missions with prose
appliedrows on newly signed rules, which redden afterrunward updaterefreshes their rule corpus, because a signature makes file-backed evidence mandatory forappliedrows (ADR-0020 semantics, unchanged here). Same class as ADR-0045: carried in the version number and a migration note that names both populations and the gesture for each. - Bounded before merge. The runward mission and the shipped example must be strict-green
before and after each of the three changes, verified by execution; if a reference mission
reddens, its pointers are fixed first, and that reddening is recorded as the mechanism working.
check --stricton this repository exits 0 today (measured 2026-08-12) and must still exit 0 on the branch. - Recurring cost, accepted. Every signature adds the ADR-0020 maintenance consequence: a
renamed guard idiom in the ecosystem means a rule edit. A slice of 8 to 12 multiplies that
charge accordingly.
scaffold-lock.jsonis regenerated by the release as usual (the rule files change, so their hashes change: ADR-0045 class 1 mechanics). - The scope line may be uncomfortable. A mission with 24
appliedrows of which 2 rest on signed rules will read "2 of 24". That number was always true; only its visibility is new (ADR-0040: every gate names what it cannot verify, per run). - Estimated cost: 3 to 3.5 maintainer-days (0.5 to 1 for the boundary match, 2 to 2.5 for the signature slice with its validations, 0.5 for the scope line), reference-mission re-verification included, ADR ratification excluded.
What this does not claim
- Not that the gate judges whether evidence implements a rule:
GATE_NON_SCOPEis unchanged; the semantic judgment stays with the operator and the adversarialverifyworkflow (ADR-0007). - Not that a signature is unforgeable: pasting the tokens into a comment still passes. ADR-0020 verbatim: the gate "raises the cost of the lie, it does not abolish lying".
- Not that the boundary match is sufficient: pointing at a real identifier that exists but has nothing to do with the rule still passes; only the signature layer, where present, sees content.
- Not that coverage becomes broad: the 14 CRITICAL/HIGH rules mapped to no phase remain reported and never gated (existing perimeter decision), and most rules will stay unsigned permanently, because their text prescribes no token and forcing one would manufacture false reds.
- No change to the six phases, the 0/1/2 exit contract, or what the seal certifies (including its declared-date limit).
Ratification
Accepted (header, 2026-08-13); this ADR crosses no phase and edits no manifest. The bar it had to clear — each test red before its change and green after, in CI, on the built binary:
- Boundary match: a case in
test/unit/evidence-report.test.js(orpointer-grammar.test.js): a file containing onlyguardFieldsLegacy, pointer#guardFields, raises "symbol not found"; pointer#guardFieldsLegacyraises nothing; andcheck --stricton this repository and on the shipped example still exit 0. - Signatures:
rules --json | jq '[.rules[] | select(.signature)] | length'equals the shipped slice size (8 to 12, or the adjudicated number with each refusal named in the PR); for each newly signed rule, a replayed case intest/audit-corpus.jswhere anappliedrow points at a real but unrelated file and exits 1 (the bypass in this ADR's Context, reproduced then killed); both reference missions green before and after. - Scope line: the golden output contains "N of M
appliedrow(s) rest on a signed rule" and the additive JSON field exists (goldens /test/unit/no-overclaim.test.js).
Ratification record (2026-08-13). The three decisions landed and their proofs pass on the built
binary, full unit suite green, both reference missions strict-green (the runward mission judged
against the package corpus, the shipped example freshly scaffolded), node dist/cli.js check --strict exit 0 before and after.
- Boundary match —
test/unit/symbol-boundary.test.js:#guardFieldsno longer matches a file containing onlyguardFieldsLegacy,#guardFieldsLegacydoes, non-identifier symbols keep the substring semantics. Red-before proven by neuteringsymbolPresenttoincludes()(the boundary case reds; the substring case, alreadyincludes, stays green).evidence.ts:413. - Signatures — the adjudicated slice is 5 new (6 signed of 64 total), below the 8-to-12
target on purpose and with the refusals named, exactly as this section permits. Signed:
resilience-retry-backoff,resilience-multi-provider-fallback,security-mcp-server-pinning,security-code-execution-sandbox,security-tool-change-reapproval— each a conventional idiom the rule text prescribes, each carrying anonScope, both reference missions answering themn/aso signing reddens neither. Refused with reasons (idiom illustrative not a code token; or a reference mission answersappliedwith prose, so signing would manufacture evidence): named in the decision-2 commit. Per-rule both-direction proof (unrelated file reds, idiom file passes) intest/unit/rule-signature-slice.test.js. - Scope line —
evidenceBreakdowngained asignedcount, rendered as "N of Mappliedrow(s) rest on a signed rule" and carried as an additive JSON field; measured 1 of 23 on the shipped example. Counted, never gated. Test intest/unit/rule-signature-slice.test.js,no-overclaimgreen.
The three adjacent paper cuts (missing-row message names runward manifest --sync; the in-progress
label states its true cause; a counted advisory on duplicated prose) remain roadmap items and do not
gate this acceptance.
Reevaluation trigger (mandatory, dated)
Trigger set on: 2026-11-05.
The decision is wrong and must be revisited if any holds: the identifier-boundary match produces a false red on a legitimate pointer (a language whose identifiers do not fit [A-Za-z0-9_$], a minified or generated file); a signed rule's signature manufactures a false red on an honest mission (ADR-0020's own trigger, carried); or the signed-share line is read as a quality score rather than as a count of rows resting on shape-matched evidence.
Watched via: the conformance-gate incident log and each rule-set release review.
References
- ADR-0019 — the declared substring depth this ADR amends (decision 1)
- ADR-0020 — the signature frame, unamended; its trigger carried verbatim (decision 2)
- ADR-0045 — the doctrine ("where the gate cannot verify, it says so in the run") and the rupture precedent (decision 3, Consequences)
- ADR-0040 — every gate names what it cannot verify, applied per run
- ADR-0030 — the additive-only JSON contract the new field respects
- ADR-0007 — where the semantic judgment lives, unchanged
- ADR-0053 (accepted 2026-08-13, same product review) — the sibling decision on partial/prefix verdicts; independent