test: every spec clause this repo cites still exists - #104
Merged
Conversation
The repo carries about eighty `PART N section M` citations across its tests and comments. They are how a reader checks that an asserted behavior is the behavior the language requires, and nothing validated them, so a wrong PART number read exactly like a correct citation forever. One was wrong. `test/reverse.test.mjs` cited the frontmatter opener rule as PART 9 section 6b; the canonical writer is PART 11, and PART 9 has no section 6b at all. The assertion was right and only the pointer was wrong, which is the worst combination: it sends the next reader to a clause that does not exist and looks authoritative doing it. The spec repo found the same class in its own normativity test, which read PART 9 and PART 12 only and therefore could not see a dangling PART 10 or PART 11 citation. Widening it surfaced eight. This is that gate for this repo's side of the same citations. Resolution is a containment test, not a parser. Extracting section labels needs to parse a heading format that is right-aligned on the period and that PART 8 uses twice with two independent runs of numbers; a parser that under-collects reports live clauses as dangling, and one that over-collects stops failing. The question actually being asked is narrower - does PART N contain a section labelled M - so the grammar is sliced between PART banners and asked exactly that. A nested list item can at worst let a real clause resolve, never a missing one. A citation to a clause the spec withdrew on purpose goes in a ledger with its reason, and the ledger is itself checked: an entry asserts the clause is gone, so it fails if a later pin brings it back. Both halves of the quote-attribution withdrawal are pinned as absent. The file list comes from `git ls-files` rather than a glob, and the scan asserts it read files and found citations, so the gate cannot quietly go vacuous.
dereuromark
force-pushed
the
test/spec-citations-resolve
branch
from
August 18, 2026 03:37
2cf8699 to
ec730e5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This repo carries about eighty
PART N section Mcitations across its tests and comments. They are how a reader checks that an asserted behavior is the behavior the language actually requires, and nothing validated them, so a wrong PART number read exactly like a correct citation forever.One was wrong.
test/reverse.test.mjsattributed the frontmatter opener rule to PART 9, when the canonical writer is PART 11 and PART 9 carries no clause with that label at all. The assertion itself was right; only the pointer was wrong, which is the worst combination, because it sends the next reader to a clause that does not exist and looks authoritative doing it.The spec repo found the same class in its own
tests/normativity.test.mjs, which read PART 9 and PART 12 only and therefore could not see a dangling PART 10 or PART 11 citation (markup-carve/carve#1367). Widening it surfaced eight. This is that gate for this repo's side of the same citations.Why a containment test rather than a parser
The obvious approach is to extract every section label per PART and check membership. That needs a parser for a heading format right-aligned on the period, so
9.and10a.sit at different indents, and which PART 8 uses twice with two independent runs of numbers. A parser that under-collects reports live clauses as dangling; one that over-collects quietly stops failing. Both are worse than the question actually being asked, which is only ever whether PART N contains a section labelled M. So the grammar is sliced between PART banners and asked exactly that. A nested numbered list item can at worst let a real clause resolve, never a missing one, because the label still has to appear inside the right PART.Withdrawn clauses, and why the ledger is keyed by site
A citation can dangle because the clause was retired rather than mistyped, and that means something different: the repo may still describe behavior the language removed. Both halves of the quote-attribution withdrawal (markup-carve/carve#1213) are in that state, and this repo legitimately names one of them where the engine pin is checked against the withdrawal.
So deliberate references go in a ledger, keyed by file and clause, not by clause alone. A ledger keyed by the clause exempts it everywhere, so a brand new citation to a withdrawn clause would pass on the strength of an unrelated historical mention elsewhere. I built it that way first and the ablation caught it: a fresh
PART 11 section 10dreference added tosrc/convert.tsstayed green. Keyed by site, it fails.Each entry also asserts the clause is gone, so it fails if a later spec pin brings it back, and a companion check fails when an exemption no site uses any more. A stale exemption cannot sit here quietly the way a magic number can.
The gate goes red on each thing it claims to catch
Eight perturbations, each restored afterwards, each confirmed to have actually applied before its result was believed:
reverse.test.mjstypotest/reverse.test.mjs:119PART 10 section 10ainsrc/PART 11 section 10dinsrc/src/convert.ts:1PART 9 section 4ainsrc/The file list comes from
git ls-filesrather than a glob, and the scan asserts it read files and found citations, so it cannot go vacuous the way a glob that stops matching does.Full suite on this branch: 362 tests, 0 failing, 71 skipped for the absent pandoc binary on the machine that ran it, which CI installs.
Sibling: #103 replaces the round trip's population floor with a derived count. Different files, no overlap, but the two land together as the same freeze sweep.