Proposal (rationale)
From F-018. WI-045 built a runner that executes self-test fixtures and left it unwired, because wiring it produced mismatches that could not be attributed: stale fixture, or harness bug?
This triages them. The value is not the count going down — it is that each mismatch is a question nobody had asked, because until WI-045 no fixture had ever run.
Decision
accepted — 2026-08-16, directed by the user.
Plan
Requirements
- Every mismatch is attributed to a cause: a defect in the gate set, or a defect in the harness.
- A defect in the gate set is fixed, not annotated.
rungs checkstays green throughout — no red merged on unexplained failures.
Impacts
modules/adr/gains a gate;frontmatter-schemaandsectionsengines change. Gate count 23 → 24.
Approach
Take them one at a time and follow each to its cause rather than pattern-matching on the message.
Acceptance criteria / tests
- Each mismatch investigated is attributed and, where it is a real defect, fixed.
rungs checkgreen;npm testpassing.- Whatever remains is recorded with what is now known about it.
Out of scope
- Wiring the runner into the gate. That needs zero unexplained mismatches, and this did not reach zero. F-018 stays open with the reason.
Execution
Branch feature/WI-056-triage-selftest-mismatches, cut from main at 1cb551e.
Four real defects, none of which were the harness. Every one had been invisible because the fixtures asserting them had never run:
-
adrdeclared no gate for its own[sections]table. The table was fully specified — required sections,non_empty,min_words, a message citing the admission rule’s second criterion — and nothing read it. This is F-007’s shape for the third time. Addedadr-sections-present. -
Two fixtures were labelled for the wrong gate. They described sections and were attached to
adr-required-fields, which checks frontmatter and could never have satisfied them. Relabelled to the new gate. -
adr-required-fieldsthen had nopassfixture at all — both of its fixtures had been the mislabelled ones. A gate asserting only that something fails cannot tell “correct” from “matches nothing”, which isgate-self-test’s own argument. Added. -
[frontmatter_schema.reciprocal]was configured and implemented nowhere. Its fail fixture is asupersededrecord with nosuperseded_by— it could never have failed. Implemented, plusrequired_when, which is a different failure from a one-way link and was not expressible before.
And one the new gate found immediately. adr-sections-present fired on
ADR-0002’s ## Decision as empty. It is not — a ### (a) follows it directly, and the
non_empty check split on any heading, so a section made of subsections read as empty. That is the
normal shape for a long decision record. Fixed to split on the same-or-higher level.
A deadlock in my own tooling, fixed on the way past. npm run claims runs rungs check to
record the run, and execSync throws on a non-zero exit — so the moment a gate failed, claims could
not be regenerated, and site-claims-current fails because claims are stale. Regenerating is now
possible exactly when it is needed.
Review
Verified 2026-08-16.
1 · Every mismatch attributed. Of 17 originally: 10 were harness artifacts, fixed in WI-045; 4 were real defects in the gate set, fixed here; 7 remain and are characterised in F-018 rather than left as a number. Met for those investigated.
2 · rungs check green, tests passing. 24 pass · 0 fail · 0 unimplemented · 0 error — up
from 23 with adr-sections-present. npm test 22 pass. The declaration half of
gates-self-tests-both-directions reports 0 findings across 23 gates. Met.
3 · What remains is recorded. F-018 rewritten with the current seven, the four causes found, and a changed recommendation — see below. Met.
The recommendation changed, and that is the useful output
F-018 originally said “triage each by hand”. Having done that for a round, the advice is now stop triaging one at a time. Every round found something real, so the runner is clearly worth having — but every round also moved which fixtures fail, and a gate whose failures shift under it cannot block a merge.
The recurring cause is structural: a fixture describes a fragment, and an engine needs a
scenario. gates-links-resolve’s pass fixture asserts a link that resolves, which is unprovable
without the file it points at. No amount of harness fixes that; the format has to let a fixture say
what it assumes. That is an ADR-0003
change, and it is the next move rather than a seventh round of this one.