Repository navigation
B1b-3c-1a: correctness gaps — Number() range downgrade, and coverage the review proved missing #112
Description
Found by the B1b-3c-1a review panel, all proven by surviving mutation or by construction.
Correctness
DONE. (anchorPosition uses Number()reference-resolver.ts). Two distinct decimal integers above 2^53 round to the same double, so start < end is false and the code takes the invalid arm instead of the bounds arm. #p1/9007199254740992-9007199254740993 on a 5-code-point block reports CDX-E-ANCHOR-POSITION-INVALID (disposition null → IGNORE) instead of OUT-OF-RANGE (WARNING, B3-escalating). Schema-valid under contentAnchorUri's pattern. The Python oracle uses arbitrary-precision int() and already disagrees. Fix: compare digit strings (length then lexicographic) before converting. Fixed by compareDigits, which normalizes leading zeros first — [0-9]+ admits 007, so a bare length-first compare would have called #p1/007-10 inverted and reported a conformant anchor. Pinned by fixture warn-anchor-position-out-of-range-unsafe-integer (RED before the fix: the document reads IGNORE) plus three unit tests covering the inverted-above-2^53 mirror case and the leading-zero regression.
Coverage (each verified non-equivalent)
DONE (with one correction). — 5 independent surviving mutations (offset-alongside-start/end, half range, offset integer guard, start/end guard,contentAnchorPosition's entire structural rule set is untested!== undefinedvsin). Seven unit tests plus fixturewarn-annotation-anchor-position-malformed(the oracle's_object_positionstructural arm had no corpus case either). FOUR of the five mutants die; the half-range guard does not, and it is EQUIVALENT rather than uncovered — a half range falls through to the numeric guard, where the absent half is not a number and the same code is reported. It survives as a diagnostic only, and the line now says so, since no test in this repo can catch its removal.No anchor target anywhere has >1 text node, so §3's concatenation model is untested.DONE. Fixturejoin('')→join(' ')survives in both implementations — a shared misreading passes every gate.warn-anchor-position-multi-text-node-target(abc+def, range0-7) kills the separator mutant in the reference AND in the oracle, verified separately in each. A unit test pins the length from both sides (0-6clean,0-7out of range), ruling out any off-by-one as well as any separator.- All four new oracle
confirm_cleanarms are deletable with every gate green.archive_oracle.pysolved this withselftest()driving clean-case guards from malformed fixtures;document_oracle.pyhas noselftest(). - A wrong content hash is undetected on any non-clean fixture — the extra FILE-HASH-MISMATCH is tolerated by the subset check and cannot raise the verdict. (All 148 fixtures verified correct today.)
Annotation URI-form positions untestedDONE (both the out-of-range and the no-position control);4 of 8 reference fields untested (incl. the onlyCLOSED by B1b-3c-1a: split CDX-E-ANCHOR-POSITION-OUT-OF-RANGE by §5.4.2 row (one-way door before B3) #111 — the row split needed all eight fields asserted, somany:truepath)test-document-verdict.tsnow covers each one's position routing,academic:theoremuses[]included, each asserting its own code and the absence of the other two. The DANGLING arm's per-field coverage is untouched by that and still thin. The Dublin Core arm has no fixture and array-valued terms no test; run-flush has no negative control.Pin the derived vocabularyDONE.assert.deepEqual([...textBearingTypes].sort(), ['definitionTerm','figcaption','heading','paragraph']). Throw-on-empty is implemented but is not sufficient — a partial schema refactor yields a non-empty wrong set. Measured, and worse than stated: droppingfigcaptionleaves the entire corpus green — 360/360 and all 107 oracle confirmations — not merely the clean fixtures, because no fixture anchors into afigcaption. Only the new assertion fails.
Three absence tests a reviewer specified precisely DONE
All three land in test-document-verdict.ts, each asserting the scan AND canonicalContent so the scope claim is checked against what canon actually does. Note the figure.caption case: canon reaches the same reject by a DIFFERENT arm — it merges the two adjacent text nodes and the per-string check fires on the merged value — so the two agree on the verdict without agreeing on the route. Original text: a crdt payload with a split run is NOT reported and canonicalContent accepts it (assert both directions); a crdt inside a text node's marks with a split run IS reported (kills the naive key-name mutant); a figure.caption rich array with a split run IS reported.
Activity
gvonnessi commentedon Aug 13, 2026
Two items done on
conformance/b1b-3c-1a-content-validity; body updated.Number()range downgrade — fixed.compareDigitsorders the bounds on the digit strings, before any conversion. One wrinkle worth recording:[0-9]+admits leading zeros, so a bare length-then-lexicographic comparison calls#p1/007-10inverted and reports a conformant anchor —Number()happened to get that case right, so the fix normalizes leading zeros first or it trades one defect for another. Only the ordering comparison needs the exact form; the bounds check againsttextLengthdoes not, since a target's length is a real array length and any value a double rounds at already exceeds it.Ratchet: reverting to the double comparison makes
warn-anchor-position-out-of-range-unsafe-integerfail withdocumentDisposition IGNORE outside [WARNING, WARNING]— the downgrade is visible at the document level, not just in the code. Three unit tests cover what the fixture cannot: the INVALID code is absent, a genuinely inverted range above 2^53 is still INVALID (so the fix is not "never report inverted up there"), and the leading-zero case stays clean.§3's concatenation model — fixed.
warn-anchor-position-multi-text-node-targetis the first target in the corpus whose text spans more than one text node:abc+def, range0-7. Mutatingjoin('')tojoin(' ')was verified to fail the suite in the reference and in the oracle separately — this was the one gap where both implementations shared the same misreading, so a probe against either alone would have proved nothing. A unit test pins the length from both directions (0-6clean,0-7out of range), which rules out an off-by-one as well as any separator.Neither needed an oracle change:
int()and"".joinwere already correct, so both fixtures are genuine reference-versus-oracle agreements rather than a shared model.Counts now 152 fixtures, 359/359 conformance, oracle 106 injected / 22 clean,
test:document-verdict56. Every gate green.Still open here:
contentAnchorPosition's structural rule set (5 mutants), the absentdocument_oracle.pyselftest(), the undetected wrong content hash on non-clean fixtures, annotation URI-form positions, the Dublin Core arm, run-flush's negative control, the derived-vocabulary pin, and the three absence tests.gvonnessi commentedon Aug 13, 2026
Second pass: the cheap pins are in. Body updated.
contentAnchorPosition's structural rule set — seven unit tests through the annotation object form (the only form that can express these shapes), pluswarn-annotation-anchor-position-malformed, since the oracle's_object_positionstructural arm had no corpus case either. Four of the five mutants die on deletion. The half-range guard does not, and it turns out to be equivalent rather than uncovered: a half range falls through to the numeric guard below it, where the absent half is not a number and the same code is reported. It survives purely as a diagnostic — "start without end" names the rule §2.2 states, where "start=0, end=undefined" reads like an internal error — so I kept it and annotated the line to say no test can catch its removal. Better than leaving a comment implying coverage that does not exist.Derived-vocabulary pin — in, and the original note understated the problem. Dropping
figcaptionfrom the derived set leaves the entire corpus green: 360/360 conformance and all 107 oracle confirmations, not just the clean fixtures. No fixture anchors into afigcaption, and a wrong-but-non-empty set only produces the null-disposition structural code, which is invisible in both of the comparator's dimensions. Only the new assertion fails. The oracle hand-transcribes the same four names under its own SYNC comment, so this is what keeps the two transcriptions honest.Annotation URI-form positions — covered both ways, out-of-range and a no-position control.
The three absence tests — all three in, each asserting the scan and
canonicalContent, because the scan's scope claim is a claim about what canon deletes and asserting only the scan would pass under a wrong model of it. Confirmed empirically before writing them:crdton a block is silent and canon accepts;crdtunder a text node'smarksis reported and canon throws;figure.captionis reported and canon throws. Worth noting for whoever reads the caption case later — canon gets there by a different arm, merging the two adjacent text nodes so the per-string check fires on the merged value, so the two implementations agree on the verdict without agreeing on the route.153 fixtures, 360/360, oracle 107 injected / 22 clean,
test:document-verdict71. Every gate green.Left here:
document_oracle.pyhas noselftest(), the wrong-content-hash hole, the Dublin Core arm's fixture and array-valued terms, and run-flush's negative control.gvonnessi commentedon Aug 14, 2026
Committed as
2c16d7d. Three/review-fixcycles ran on this branch; cycle 3 returned no defect in shipped runtime behaviour, which is the stopping signal. Recording what the panels closed here, and four deferrals that need to outlive the session.Closed by the review cycles (beyond the items already struck above)
codeBlockwas missing from the derived text-bearing set — its schema reachestextNodethrough anallOfthat narrows it (§4: exactly one marks-free text node), and the derivation followed only direct references. §3 namescodeBlocktext-bearing outright, so every anchor into a code block reported "not a text-bearing block" on conformant content, and an out-of-bounds one was downgraded to the null-disposition code. Fixed on both implementations, with a fixture and unit tests; reverting the derivation fails two gates.anchor-rangerule had the same >2^53 defect the document layer's position check had — on the exact URI a new fixture pins.checkAnchorsordered bounds viaNumber(), so a well-ordered range above 2^53 was reported inverted under a message naming the same number twice. The comparison is now shared (scripts/lib/decimal-digits.ts); two published vectors pin it.codeBlockbug it had just been written to catch), then asserteddeepEqualagainst §3's list — which reads "such as", so the spec supports only a superset bound. An equality assertion fails on a correct widening, and the cheapest route back to green is to narrow the derivation again. Now two one-sided bounds: every named type derived, no container derived.offset), each verified to fail on revert. This mattered more than ordinary coverage because the oracle is the independence property.Deferred — please keep these on this issue
A live false positive remains:
semantic:ref. Its schema declareschildren.items.$ref→content.schema.json'stextNode, so §3 makes it text-bearing, butderiveContentVocabularyreads onlycontent.schema.json's$defs. An in-bounds anchor into one reportsCDX-E-ANCHOR-POSITION-INVALIDtoday; out-of-bounds is downgraded. Same class ascodeBlock, and the oracle shares the blind spot, so no fixture disagreement can surface it. Blast radius is exactly one type. Recorded as OQ-012 rather than fixed, because it is a spec call first:refBlock's children are "Display content" while it also carriesformat("Section {number}"), so if the rendered text is generated rather than taken fromchildren, what an offset addresses is ambiguous. Belongs with 3c-1b (extension-block interiors), and whichever way it goes the fix must land on both implementations at once.check:enumeration-coverage's "emitted" claim is satisfiable by a comment. The scan is lexical and its regex accepts backticks, so a code named only in a backticked JSDoc counts as emitted. Demonstrated by rerouting a code and leaving a comment behind: the gate stayed green whilecheck:conformancefailed. Latent — no code is comment-only today — so it needs a gate change, not a fix.No compile-time link in the
rewriteIds→ resolver direction. The resolver's field tables are now guarded by the per-row test (dropping a field fails it), but a field added torewriteIdsalone is silently unresolved. The cost rose with this change: those tables now decide which §5.4.2 row a position defect takes, so a field known to canon and unknown to the resolver has no row at all. Mitigated for now by a note inrewriteIds's JSDoc, where the drift is caused.anchorTargetsdefaults to an empty map indocument-verdict.ts, reintroducing one level out the defaulting its own JSDoc warns against. Safe today — it is assigned besidecontentIdsand the call site gates oncontentIds !== null— but that invariant spans two variables and is stated nowhere. One nullable pair removes it structurally.Still open from the original list:
document_oracle.pyhas noselftest(), the wrong-content-hash hole, the Dublin Core arm's fixture and array-valued terms, and run-flush's negative control.Close the remaining #112 coverage gaps and a config-slot binding escape
gvonnessi commentedon Aug 28, 2026
The four original items are closed by #118. Leaving this issue open — the four deferrals recorded above are untouched and still belong here.
document_oracle.pyhas noselftest()— it does now, and it covers every arm rather than the four B1b-3c-1a added. Measured first: deleting any one of those four left every gate green, and each deletion madeconfirm_cleanreturn "clean" rather than being caught by a later arm, so nothing was masking them. The same held for every other arm, so the table drives all 37 refusal arms from the malformed corpus — 36 from fixtures, and the archive-unreadable guard from synthetic bytes, no document fixture being an unreadable zip.The rows assert the specific refusal message rather than "was refused at all", because
confirm_cleanreturns on its first failing arm and a bare is-not-None assertion is satisfied by any later one. Six rows were confirmed to fail as WRONG-ARM rather than BLESSED under mutation, which is the proof that distinction is load-bearing.The part worth recording for whoever adds the next arm: a table restoring today's coverage adds nothing that keeps tomorrow's arm honest, and inserting a new arm with no row was measured to leave every gate green — the same defect class, one level up. So the arms are enumerated from the function's own AST and the drivers traced, and an unreached arm fails. It also distinguishes "no row" from "row shadowed by an earlier arm", because the remedies are opposite and the naive message tells you to add a row that already exists.
A wrong content hash is undetected on any non-clean fixture — closed, and wider than stated. The claim needed narrowing: it holds for
reject-andwarn-cases but not for the five expecting exactly[IGNORE, IGNORE], where the extra WARNING does move the disposition andcheck:conformancecatches it. And the hole was not limited tocontent.hash: corrupting one non-clean fixture's asset-index hash was invisible tocheck:conformance,check:fixtures,check:document-oracleandcheck:document-idtogether. The gate now covers every{path, hash}pair a manifest declares — 148 hashes across 125 document fixtures.The Dublin Core arm has no fixture and array-valued terms no test — both closed, and the fixture description needed correcting before it shipped: 4.3.1 projects
creator/subject/languageas arrays with a scalar coerced, so "either a string or an array" describes the authored value, not the projection. That text ships incase.json, and an implementer skipping the coercion computes a different document ID. Pinning onlycreatorwas also measured to leavesubjectandlanguageunscanned in both implementations with the corpus green, so the verdict test runs over all three.Run-flush has no negative control — closed. The control is a pair carrying the same four text-node values, differing only by a paragraph between the second and third; each run is NFC-stable alone and only the concatenation across the boundary is not. Deleting the reset leaves all 368 checks and both oracles green and fails only that test. It sits in a
blockquoterather than the rootblocksarray:blockquote,listItem,tableCell,admonitionanddefinitionDescriptionall admit a text node beside a paragraph (validated with the repo's own factory against negative controls), so the control does not depend on the root-admissibility question open in #114.Two coverage residues the panel measured and #118 did not close are now #119.
Close the remaining #112 coverage gaps
Merge pull request #118 from Entrolution/conformance/b1b-3c-1a-remain…
gvonnessi commentedon Aug 30, 2026
Both remaining un-struck items were closed by PR #118. Measured on
mainat18f293f:"All four new oracle
confirm_cleanarms are deletable with every gate green."document_oracle.pynow has theselftest()the issue asks for:The table is enumerated from the function's own AST and the drivers traced, so an arm added later with no driver fails the selftest rather than going silent — it ratchets rather than merely restoring coverage.
"A wrong content hash is undetected on any non-clean fixture."
check:fixturesgained a hash-coherence pass over every{path, hash}pair a manifest declares:The residue that pass deliberately does not cover — asset hashes listed inside an index file, which are pinned only by the oracle's clean-fixture arms — is tracked separately as #119 item 1, with the reasoning for where the boundary sits.
Everything else in this issue was already struck through. Closing.