Repository navigation
State §4.3.1 item 5's mark re-sort as a rule, not a list of causes - #139
Merged
Merged
Conversation
Item 5 enumerated the causes of its post-rewrite mark re-sort — "relabeling an `anchor` id or a `link` `href`" — and the enumeration was short. The same item rewrites an `academic:*-ref` `target`, which sits on a mark, and its walk relabels ids inside a mark's `content`. The reference already re-sorted every text node's marks unconditionally, so it was right while nothing in spec/ required it: an implementer following the written text computed a different document ID. The divergence needs only two relabeled ids whose string order runs against their document order, and the suite has held such a witness since before this change. It survived because every pre-existing case that depends on the re-sort reorders through a `link` `href` — a cause the enumeration names. The suite had no witness for an unrecognized one. Item 5's walk definition now also states that the walk enters a mark's `content`, unlike item 4. That reach decides which ids receive canonical names and was stated nowhere, while item 4 primes the opposite reading one item earlier; over-generalizing that premise is the recorded root cause of the reverted #121 PR 2. Both additions are rule with no explanatory clause. Earlier drafts carried three such clauses and a review panel falsified each by construction; the reasoning lives in the changelog instead, where being wrong costs a correction rather than a false normative claim. Five canonicalize vectors, hand-derived and digested through canonicalize_oracle.py before the reference was consulted. Their kill sets are recorded as measured: no single vector is individually indispensable, the two mark-content vectors jointly reach a position nothing else in the suite does, and two vectors are dominated and published for legibility rather than coverage. A test-canonicalize case pins alpha-equivalence inside a mark's `content`, the one position where this rule is what makes two orderings agree. canonicalize_oracle.py gains a floor: a vector may pin no expectedId only if it is a reject vector, since otherwise every gate counts it as a pass with its id never compared (#137). suiteVersion 0.2.5 -> 0.2.6, 216 -> 221 vectors, 380 -> 385 checks. No published expectation changes and no document ID moves; canonicalize.ts is comments only. Closes #134. Review also produced #136, #137 and #138.
gvonnessi
force-pushed
the
fix/134-mark-resort
branch
from
September 5, 2026 00:35
f6390c1 to
b383e68
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.
Closes #134.
§4.3.1 item 5 enumerated the causes of its post-rewrite mark re-sort — "relabeling an
anchorid or alinkhref" — instead of stating the condition, and the enumeration was short. The same item rewrites anacademic:theorem-ref/equation-ref/algorithm-reftarget, which sits on a mark; and its walk relabels ids inside a mark's owncontent. The reference re-sorts every text node's marks unconditionally and so was already correct — but nothing inspec/required it, so an implementer following the written text computes a different document ID.Why it survived CI
Not because the divergence is hard to reach. It needs only two relabeled ids whose string order runs against their document order, and
relabel-marks-resort-after-rewritehas been such a witness in the suite since before this change.It survived because every pre-existing case that depends on the re-sort reorders through a
linkhref— a cause the enumeration names, and which a selective implementation therefore handles. Measured by deleting the re-sort againstmain: exactly 2 of 380 checks fail, both[link, link]. The suite had no witness for an unrecognized cause.The measured matrix
Six variants of the re-sort, each built as a real patch and run through
check:conformance:*-refrelabel-marks-resort-not-only-anchor-or-hrefrelabel-marks-resort-id-inside-mark-contentrelabel-marks-resort-two-ids-authored-orderrelabel-marks-resort-inside-mark-content-no-relabelrelabel-marks-resort-inside-mark-content-permutedNo single vector is individually indispensable — an earlier draft of the changelog claimed otherwise and the panel disproved it. Three groupings are: the two mark-content vectors jointly reach a position nothing else in the suite touches (verified across 220 vectors and all 164
.cdxarchives — zero other hits),not-only/id-insidejointly catch the digit-aware comparator, andtwo-ids-authored-orderis dominated outright and published for legibility, which the changelog says in those words.The permuted twin carries an identical
expectedCanonicalJcsandexpectedIdfrom differentparts.content. That pair binds alpha-equivalence on a third-party implementation, which a single vector cannot express.Also here
content, unlike item 4. That reach decides which ids receive canonical names and was stated nowhere, while item 4 primes the opposite reading one item earlier. Over-generalizing that premise is the recorded root cause of the reverted §4.3.1 item 4 and §4.3.2 item 2 are keyed on element shape, not position — and the obvious fix has a regression #121 PR 2.test-canonicalizecase pinning alpha-equivalence inside a mark'scontent— the one position where this rule is what makes two orderings agree. The same test in block content passes with the rule deleted, because item 3 already sorted there.canonicalize_oracle.py: a vector may pin noexpectedIdonly if it is a reject vector. Otherwise every gate counted it as a pass with its id never compared (A vector with noexpectedIdis COUNTED AS A PASS, not skipped: "384/384 passed, 0 skipped" with the document ID never compared #137).Review
Two
/review-fixcycles. Cycle 1: six lenses, 11 verified findings. Cycle 2: three bounded lenses against a closed list, plus a dedicated claim sweep that enumerated 74 clauses, decided 69 by a command it ran, and falsified 8. A third panel was refused by the review-cycle guard — correctly, since the delivered code was byte-identical to what cycle 2 reviewed — so the remaining prose was verified in place by measurement.No finding in either cycle was a runtime-behaviour defect in shipped code. The reference was correct throughout. All eight falsifications were in prose written about it, every one an explanatory clause or a count hung off a true statement — two of them introduced by the fix for the previous one. Item 5's addition went from ~120 words to ~45; everything cut was either false or explanation.
Notes for the reviewer
suiteVersion0.2.5 → 0.2.6 (patch position: added cases, pre-1.0). 216 → 221 vectors, 164 fixtures, 380 → 385 checks.canonicalize.jsonis a pure insertion; no published expectation changes and no document ID moves.canonicalize.tsis comments only — verified by diffing the comment-stripped file against8ce0b5d. (test-canonicalize.tsdoes add executable code.)#121PR-2-shaped mutant was applied and all 385 checks stay green, with a live positive control proving the mutant is not a no-op — so this PR publishes no expectation §4.3.1 item 4 and §4.3.2 item 2 are keyed on element shape, not position — and the obvious fix has a regression #121's sequel must break.npm audit: a pre-existingfast-uriadvisory, unrelated to this change, filed as CI:npm audit --audit-level=highred on four fast-uri advisories; the lockfile pins 3.1.5 while ajv permits 3.1.7 #135 with the actual cause (the lockfile pins 3.1.5 whileajvalready permits^3.0.1and 3.1.7 is published). Every other gate is green.expectedIdis COUNTED AS A PASS, not skipped: "384/384 passed, 0 skipped" with the document ID never compared #137 and Inside a mark'scontent, marks are sorted and de-duplicated but an emptymarks: []is not omitted — item 3's three clauses come apart #138.