fix(dead_candidate_scan): stop one unbalanced backtick in docs/index.md from hiding citations - #4745
Merged
Merged
Conversation
…ty fix Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Markdown code spans are paired positionally, so one unbalanced backtick does not merely lose its own span: it inverts the parity of the rest of its line, and the tokenizer reads prose as citations and citations as prose with nothing to warn about. docs/index.md:1809 (a single 52186-character progress row) spells `ContinuousOn`.continuousAt` with three backticks where two were meant. From that column on, the line's 218 tokens named none of the six occurrences of `magnetizationAlongExhaustion` the raw text carries, and `magnetizationAlongExhaustion_differentiable_beta_gen` came out `safe-to-delete` although the same line cites it. Lines 1200/1201 carry the sibling shape (a span opened on one line and closed on the next), which `[^`\n]+` cannot match in any pairing. Two changes, both additive on the citation side: * `unpaired_backticks` reports every backtick no code span absorbed, and `_markdown_source` re-reads those lines with `pairing_independent_tokens` instead of skipping them. Splitting a line on backticks yields a superset of the bodies any pairing can produce, so the re-read can add citations and never drop one; the defect is still printed, as a Markdown coverage warning, so the documentation gets repaired rather than worked around. * `elided_prefix_matches` treats a suffix citation whose elided prefix is spelled out on the same line (`` `foo_continuous_gen` + `_differentiable_gen` ``, the notation both documentation files use) as a shorthand for the siblings that share that prefix, instead of a family label attributed to nobody. Charging every match of every family label was measured first and rejected: it touches 5253 of the library's 11000 declarations and leaves no reachable `safe-to-delete` verdict, including the fixtures' isolated-declaration row. Measured on this branch against main 075bcd3, whole library, 11000 declarations: 235 verdicts move, and every one of them moves toward keeping (142 safe -> uncertain, 53 safe -> load-bearing, 20 safe -> published, 9 load-bearing -> published, 9 uncertain -> load-bearing, 2 uncertain -> published). None moves toward `safe-to-delete`. The `_ferromagnetic` family calibration goes 223 -> 47 safe / 77 uncertain / 64 load-bearing / 35 published (was 92 / 44 / 52 / 35); the fixtures header carried older numbers still and is refreshed with it. Tests: 11 new cases in `MarkdownBacktickParityTest` and `ElidedFragmentTest`; `python3 scripts/dead_candidate_scan.py --self-test` runs 90 and passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… it swallows
Review of this branch found one fail-open and two false numeric claims. All
three are repaired here; no verdict on the real tree changes.
1. `unpaired_backticks` credited a fenced match with its whole span. The fenced
alternative of `_MD_TOKEN_RE` is `re.DOTALL` and unbounded, so an unbalanced
run of three or more backticks pairs with the next run anywhere in the file
and its body swallows every line in between -- and the same match then
certified the swallowed citations' backticks as read. Measured on
"start ```\ncite `_alpha_gen` here\nand `beta{,_two}_gen`\nend ```\n"
the old reading returned `{}` and both citations were lost (the fenced body
is split on whitespace and `_nameish` rejects pieces that still carry a
backtick; neither shape has a verbatim fallback). A fenced match is now
credited with its two delimiters only, so that text reports `{2: 2, 3: 2}`
and `_markdown_source` recovers both tokens. An odd number of fence runs is
reported on top (`unbalanced_fence_run`), which catches the one shape the
backtick count cannot: a six-backtick run reads as a fence with an empty body.
Both are charge-only and reach neither a verdict nor the exit code.
Not reachable on the current corpus: no fenced body in README.md or
docs/**/*.md contains a backtick, and every file has an even number of fence
runs, so the full 10965-declaration sweep is byte-identical before and after
(safe-to-delete 1458, family labels 512, zero verdicts moved). Against main
075bcd3 the branch still moves 235 verdicts, all keep-direction, with zero
moves into safe-to-delete and safe 1673 -> 1458 a strict subset.
`test_balanced_lines_and_fences_raise_nothing` asserted the old silence on a
fence whose body quotes a backtick; it is rewritten to the fail-closed
expectation and joined by three cases pinning the recovery and the run-parity
check.
2. `elided_prefix_matches` and LIMITATIONS L11 said charging every family-label
match "leaves no reachable safe-to-delete verdict at all". Measured: that
option leaves 232 (published 6731 / uncertain 1467 / load-bearing 2535), a
collapse from 1458 rather than an elimination. The rejection stands on the
84% collapse plus `--expect` FAIL (1 of 18); the docstring now says so, and
the fragment census it quotes is restated as measured (531 of 759 distinct
resolving fragments match 2+ declarations, union 5895 of 11000).
3. The same docstring said a genuine family label "elides no prefix anyone
spelled out and is unaffected". Measured: `_ferromagnetic` is charged 155
times and `_pos` 9 times. Switching the rule off on the 223 `_ferromagnetic`
candidates leaves 92 safe-to-delete and on leaves 47; charging that one label
accounts for 13 of the 45 that move and other elided fragments for the other
32. What rescues nobody is a family label on a line that elides nothing.
Tests: `--self-test` 94 pass (90 before), `--expect` 18 rows pass,
test_audit_gate.py 166 pass (1 skip), test_citation_audit.py 150 pass. Mutants,
each applied alone and reverted: M1 drop the recovery RED (4 failures), M2 blind
`unpaired_backticks` RED (8 failures + 1 error), M3 restore "2+-match = nobody"
RED (3), M4 charge every family-label match RED (5 + fixtures FAIL 1 of 18), and
the new M5 credit a fenced match with its whole span RED (2). No Lean, docs/ or
tex/ file is touched, so `lake build` was not run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… with the corrected measurement The retracted figures survived in the test module after the scanner module was corrected. `ElidedFragmentTest` still described charging every match of every family label as touching "5253 of 11000 declarations, no reachable safe-to-delete"; the measured sweep touches 5895 of 11000 and leaves 232 `safe-to-delete` verdicts (down from 1458). The docstring now states the same figures as `dead_candidate_scan.py` (module docstring and rule L11). Docstring only; no test, fixture or classifier behaviour changes. Measured on this commit: - `python3 scripts/dead_candidate_scan.py --self-test`: 94 tests, OK - `python3 scripts/test_audit_gate.py`: 166 tests, OK (skipped=1) - `python3 scripts/test_citation_audit.py`: 150 tests, OK - `grep -rn "5253\|no reachable"` over the worktree: no matches Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
phasetr
marked this pull request as ready for review
July 27, 2026 00:50
This was referenced Jul 27, 2026
phasetr
added a commit
that referenced
this pull request
Jul 28, 2026
…/Mayer and J = 0 pseudo-mass wrapper modules (safe-to-delete batch 4) (#4754) Part of #4746 (Item A, batch 4). Deletes the nine fully-dead modules of batch 4 — **31 declarations, 712 lines** — and retracts every documentation claim that counted them. The batch is the union of the three cascade-0 groups of the 32 lane-eligible fully-dead modules, so no declaration anywhere else in the library loses its last consumer. Measured on `fd7a2a71` (`git ls-remote origin main` at branch time) in a throw-away worktree with an APFS-cloned warm `.lake`. ## What was deleted | module | decls | lines | |---|---|---| | `Concrete/LatticeGraphCorrelation/PartitionFreeEnergyBoundsAlongExBridges.lean` | 4 | 78 | | `Concrete/LatticeGraphCorrelation/PartitionFreeEnergyMonotonicityFreeEnergyAlongEx.lean` | 3 | 56 | | `Concrete/LatticeGraphCorrelation/PartitionFreeEnergyRegularity.lean` | 4 | 87 | | `Concrete/LatticeGraphCorrelation/PartitionFunctionGeneralAnalyticity.lean` | 5 | 83 | | `Concrete/LatticeGraphCorrelation/PartitionFunctionSymmetryCubicAbsH.lean` | 2 | 42 | | `Concrete/LatticeGraphCorrelation/MagnetizationCorrelationBasic.lean` | 4 | 72 | | `Concrete/LatticeGraphCorrelation/MayerFilterConnected.lean` | 4 | 96 | | `Concrete/LatticeGraphCorrelation/MayerVdBounds.lean` | 3 | 81 | | `PseudoMass/FromParamsHZero/JZeroJointRegularity.lean` | 2 | 117 | | **total** | **31** | **712** | The line total is 712, not the 721 the batch-4 design recorded: the design's per-file figures are each one higher than `wc -l`, and `git diff --stat` charges 712 deletions. Build jobs go 5015 → 5006. **No build-time claim is made.** Own elaboration cost of these modules is at or below the per-module import-only baseline (issue #4724), so the wall-clock effect is not measurable and none is asserted. ## Declaration-level double confirmation The 31 declarations were re-extracted independently of the design, with a line-folding-aware extractor (a same-line `^theorem NAME` regex silently misses every wrapped declaration, and did so on a first pass here). All 31 names **and their line numbers** reproduce the design's appendix exactly. A boundary-aware reference check over **every tracked file** (not only `IsingModel/`, `docs/`, `tex/`) found **0** genuine references. The four raw `git grep` hits are all prefix collisions with surviving siblings, and none is a reference to a deleted declaration: | raw hit | why it is not a reference | |---|---| | `PartitionFreeEnergyBoundsFeAlongExId.lean:46` | `…_nonneg_of_ferromagnetic` — different identifier | | `PartitionFunctionSymmetryLog.lean:63,64`, `PartitionFunctionSymmetryLogCubic.lean:29,39` | `log_…` prefix — different identifiers | | `PolymerFreeEnergyEpsilonSharpening.lean:31` | `…_minus_one_pow_at_zero` — different identifier | ## Requirement (i): `--lean` cross-check `python3 scripts/dead_candidate_scan.py <31 names> --lean`, run against the green pre-deletion build (scanner blob `7f47c6bd`, unmodified), exit 0: ``` --lean cross-check: 31 candidate(s) compared against the elaborated graph; no consumer seen by Lean was missed on a safe-to-delete verdict canary: 1021 declarations carrying 'Λ'x1018, 'β'x1, 'σ'x2 each find themselves: PASS canary: 10162 code citations in tex/proof-guide.tex, none broken across a line: PASS -- published-result: 0 -- -- load-bearing: 0 -- -- uncertain: 0 -- -- safe-to-delete: 31 -- -- coverage warnings: 0 -- ``` Every one of the 31 carries the reason *"no reference outside the delete set, no citation in the scanned documentation"*. ## Requirement (v): the two sweeps Both sweeps run the full-library scan (`--pattern '.'`, 10,868 scanned declarations) on the **pre-deletion library**, changing only the documentation. | sweep | docs/tex | safe-to-delete | delete set | |---|---|---|---| | 1 | pre-deletion | 1127 | 31/31 safe | | 2 | this commit's | 1123 | 31/31 safe | **Condition (v) assertion: 0 violations.** No declaration outside the delete set moves toward `safe-to-delete`. All seven verdict moves are in the protective direction, and they are quoted rather than summarised: ``` safe-to-delete -> published-result freeEnergyAlongExhaustion_latticeGraph_cubicExhaustion_monotone_J safe-to-delete -> published-result freeEnergyAlongExhaustion_latticeGraph_cubicExhaustion_monotone_beta safe-to-delete -> published-result freeEnergyAlongExhaustion_latticeGraph_cubicExhaustion_monotone_h uncertain -> published-result freeEnergyAlongExhaustion_latticeGraph_cubicExhaustion_upper_bound uncertain -> published-result freeEnergyAlongExhaustion_latticeGraph_upper_bound uncertain -> published-result mayerPartialSum_Λ_latticeGraph_analyticOnNhd safe-to-delete -> uncertain mayerPartialSum_Λ_latticeGraph_differentiable ``` The first three are the point of the surgical brace split: replacing the nested brace by two exact labels turns a citation the scanner could not resolve into citations it can. The 29 declarations the design predicted could be stranded by a careless line rewrite are all still `published-result`. An **independent fail-closed re-expansion** of every backticked token in `docs/index.md` and every `\texttt{}`/`\path{}` token in `tex/proof-guide.tex` (recursive brace expansion, `*` as zero-or-more, cross-checked against a folding-aware index of all 10,963 library declarations) confirms the design's result and adds nothing: of every family token that covers at least one deleted name, **exactly one loses all of its members** — `` `BddAbove_freeEnergyAlongExhaustion_latticeGraph*` `` at `docs/index.md`, which this PR drops. Every other covering token keeps survivors (e.g. `` `correlation_*_latticeGraph` `` 19 members → 17, `` `freeEnergyAlongExhaustion_latticeGraph_*` `` 103 → 98). ## Documentation retraction Prose *counts* are invisible to the scanner, so each affected wrapper family was measured layer by layer instead. **Two of the four count retractions below were not in the batch-4 design** and were found by that measurement. | site | before | after | in design? | |---|---|---|---| | `docs/index.md` PR #1595 / `tex` §18.5 | `12 new thin wrappers (3 abstracts × 4 layers)` | `12 … ; the three ℤ^d Λ-layer wrappers had no consumers and were deleted in PR #4754, leaving 9 across three layers` | yes | | `docs/index.md` PR #1610 / `tex` §18.6 | `20 thin wrappers (5 abstracts × 4 layers)` | `20 … leaving 15 across three layers` | yes | | `docs/index.md` PR #1611 / `tex` §18.6 | `24 thin wrappers (8 abstracts × 3 missing layers)` | `24 … leaving 20 across the remaining layers` | **no** | | `docs/index.md` PR #1613 / `tex` §18.5 | `20 thin wrappers (5 abstracts × 4 layers)` | `20 … leaving 16` | **no** | PR #1613 loses 4 and not 5 because the ℤ^d Λ-layer `mayerPartialSum_Λ_latticeGraph_analyticOnNhd` lives in another module and survives; the retraction says so. Family labels and module lists: | site | before | after | |---|---|---| | `docs/index.md` module-directory bullets | 4 bullets naming the deleted concrete modules | removed (8 lines) | | `docs/index.md` §4.6 monotonicity row | `` `freeEnergyAlongExhaustion_latticeGraph{,_cubicExhaustion}_{upper_bound,monotone_{J,h,beta}}` `` (8 members, 3 deleted) | `` `…latticeGraph{,_cubicExhaustion}_upper_bound` `` + `` `…latticeGraph_cubicExhaustion_monotone_{J,h,beta}` `` | | `docs/index.md` §4.6 bounds row | `` , and `BddAbove_freeEnergyAlongExhaustion_latticeGraph*` now live in `PartitionFreeEnergyBounds.lean` `` (2 members, both deleted) | token dropped; location claim widened to `` `PartitionFreeEnergyBounds.lean` and its child modules `` | | `tex` ×4 "now live in …" sentences | claimed the deleted ℤ^d Λ-layer children among the surviving ones | each names the surviving children and records the ℤ^d Λ-layer deletion | `docs/index.md:1411` (`` `correlation_*_latticeGraph` ``, 17 of 19 survive) and `docs/index.md:1983` are deliberately left alone, as the design determined. The retraction sentences describe the removed wrappers **by layer, never by name**. A first draft named them, and sweep 2 caught it immediately: 11 of the 31 flipped to `published-result` because the retraction itself had become a citation of a declaration about to stop existing. **12 in-library module doc comments** that pointed at the deleted modules were corrected in the same commit: four parent-module "Moved:"/"Removed:" sections (`Magnetization.lean:66`, `PartitionFreeEnergyBounds.lean:102`, `PartitionFreeEnergyMonotonicity.lean:46` and `PartitionFunctionSymmetry.lean:75`) and eight "extracted from `X.lean`" provenance notes. Five of those eight were the basename trap: a same-named module survives under `AmbientLattice/SpecialCases/`, so the reference would silently re-resolve to the wrong file instead of dangling. This closure was not enumerated by the design. ## Ratchet and floor recalibration `scripts/test_dead_candidate_scan.py` * `test_ferromagnetic_family_counts`: `SAFE 47 → 46`, `UNCERTAIN 77 → 78`; `LOAD_BEARING` (64), `PUBLISHED` (35), the 223 total and `test_zero_consumer_count` (112) are unchanged. Exactly one declaration moves, and it moves *away* from `safe-to-delete`: `freeEnergyAlongExhaustion_latticeGraph_nonneg_of_ferromagnetic` `safe-to-delete → uncertain`, because the deleted sibling was the other member of the `nonneg*` component of its family label. The docstring records the mechanism. * Three `docs/index.md` line pins (`1200/1201/1809 → 1192/1193/1801`) shift by the 8 removed bullet lines. Same three rows, different numbers. `scripts/test_audit_gate.py` — **the floors are not lowered.** | counter | `fd7a2a71` | this PR | floor | slack | |---|---|---|---|---| | `iter_checked_files()` | 1993 | 1984 | `CHECKED_FILE_FLOOR` 1957 | 27 | | `iter_lib_files()` | 1986 | 1977 | `LIB_FILE_FLOOR` 1950 | 27 | | `iter_v4_files()` | 2023 | 2014 | `V4_FILE_FLOOR` 1977 | 37 | The comment block claimed *"Measured 2026-07-27 … `iter_checked_files()` = 2003 and `iter_lib_files()` = 1996 … The floors sit 46 below the measured counts"*. Both halves were wrong: 2003/1996 were the counts from *before* PR #4751 deleted its ten modules, so on `fd7a2a71` the real gap was 36, not 46. The block is rewritten against the fresh measurement, and it now records the standing decision that **the next batch that would actually trip a floor must land F1 (per-directory assertions) rather than another recalibration.** A follow-up commit fixes one figure the rewritten block had carried over unchanged: the largest library directory is `Concrete` **851**, not 869. 869 predates PR #4751's ten-module deletion (859 at `fd7a2a71`). Re-measured on this branch, the eight largest are `Concrete` 851, `AmbientLattice` 275, `ClusterExpansion` 205, `TransferMatrix` 109, `Peierls` 105, `AmbientComplexAnalyticity` 76, `Inequalities` 71, `Conditioning` 48 — so the block's claim survives the correction: losing the smallest of the eight takes `iter_checked_files()` from 1984 to 1936, below `CHECKED_FILE_FLOOR` 1957. `scripts/audit/citation_baseline.tsv` — 4 rows hand-moved from `AMBIGUOUS` to `BASENAME_ONLY` (`MayerFilterConnected.lean`, `MayerVdBounds.lean` ×2, `PartitionFreeEnergyRegularity.lean`, `PartitionFunctionGeneralAnalyticity.lean`). Deleting one of two files that share a basename makes the tex citation resolve uniquely, which is a class change, not erosion: **gating findings stay at 960**. `--update-baseline` refuses a row that rises and directs the change to a hand edit so the diff shows it, which is what this is. ## Verification, all re-measured here | gate | result | |---|---| | `lake build` | exit 0, **5006 jobs**, build text grepped: 0 `warning`, 0 `error` (the 2 `info:` `ring` notes are present on `fd7a2a71` too) | | `scripts/audit_gate.py` | PASS — V1 no `axiom` (1984 files), V2 no sorry/admit/native_decide (1984 files), V3 13 capstones all `{propext, Classical.choice, Quot.sound}`, V4 no Japanese (2014 files) | | `grep -rn "sorry" IsingModel/` | 2 hits, both prose inside doc comments, same as `fd7a2a71` | | `lake exe GKSTest` | all tests passed | | `scripts/citation_audit.py` | PASS — 960 gating findings (unchanged), coverage OK, ratchet **0 cleared / 0 new**, tex 1210 raw / 1217 citations, docs 2636 raw / 2700 citations | | `dead_candidate_scan.py --self-test` | **100 tests OK** | | `python3 -m unittest scripts.test_audit_gate` | **166 tests, OK (1 skipped)** | | `citation_audit.py --self-test` | 150 tests OK | | Japanese scan | none in `tex/`, `docs/`, `README.md`, `IsingModel/`, `scripts/` | ## Final check on a synthesized tree A separate throw-away worktree at `fd7a2a71` (**pre-deletion library**) carrying **this commit's** `docs/index.md` and `tex/proof-guide.tex`, scanned with the unmodified scanner: ``` -- published-result: 0 -- -- load-bearing: 0 -- -- uncertain: 0 -- -- safe-to-delete: 31 -- -- coverage warnings: 0 -- canary (unicode self-find): PASS canary (tex line-break): PASS ``` and the full-library sweep on that same tree reports **0 condition-(v) violations**. So the documentation in this commit still justifies deleting exactly these 31 declarations and nothing else. On the merged tree, no exact name among the 31 and none of the five uniquely named deleted modules occurs anywhere in `IsingModel/`, `docs/` or `tex/`. The four shared basenames that remain (`MayerVdBounds.lean`, `MayerFilterConnected.lean`, `PartitionFreeEnergyRegularity.lean`, `PartitionFunctionGeneralAnalyticity.lean`) all resolve to the surviving `AmbientLattice/SpecialCases/` modules. ## Follow-up recorded, not fixed here The three scanner fail-open constructs found while validating this batch are recorded as a child item of #4746 and are deliberately out of scope for this PR: nested braces, a bare trailing `*` with no separating underscore, and `*` read as one-or-more. All three are fail-open on the deletion side, the same polarity class as #4745. The affected sites in this batch were handled by hand and verified by the independent expander above. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
9 tasks
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.
What was wrong
Markdown code spans are paired positionally, so one unbalanced backtick does not
merely lose its own span: it inverts the parity of the rest of its line. Prose is
then read as a citation and the citations are read as prose, and nothing warns.
Measured on main
075bcd39:docs/index.md:1809is a single 52186-character progress row that spells`ContinuousOn`.continuousAt`with three backticks where two were meant(they sit at columns 9925/9938/9952). From column 9952 on the parity is
inverted: the line contributes 218 tokens and none of them names
magnetizationAlongExhaustion, though the raw line spells it six times.magnetizationAlongExhaustion_differentiable_beta_gencame out
safe-to-deletewith the reason "no citation in the scanneddocumentation", while
docs/index.md:1809cites it as`magnetizationAlongExhaustion_continuous_beta_gen` + `_differentiable_beta_gen` + ...docs/index.md:1200/:1201carry the sibling shape, a code span opened on oneline and closed on the next, which
`[^`\n]+`cannot match under anypairing; it hid
latticeBallBoundaryEdges_card_le_sphereandlatticeSphere_card_le'as tokens.README.md,docs/**/*.mdandtex/proof-guide.texas of075bcd39.README.md:66/:69are fence delimiters and pair off.
A second, independent hole sat behind the first:
_differentiable_beta_genmatches three declarations (correlation / magnetization / susceptibility), so
even with the tokens restored the family-label rule attributed the citation to
nobody and the verdict stayed
safe-to-delete.A third hole, one level up, was found by review of this branch and is fixed in
17074732: the fenced alternative of_MD_TOKEN_REisre.DOTALLandunbounded, so an unbalanced run of three or more backticks pairs with the next
run anywhere in the file and its body swallows every line in between — and
crediting the whole match then let the very match that hides those citations
certify their backticks as read. Measured counterexample:
Both shapes are the fragment ones (a brace alternation, an elided suffix) with
no verbatim fallback: the fenced body is split on whitespace and
_nameishrejects the pieces that still carry a backtick.
What changed
scripts/dead_candidate_scan.pyplus its tests and fixtures header; no Lean, nodocs/, notex/, no CI wiring.unpaired_backticks(text)returns every backtick no code span absorbed, keyedby line. A well-formed span carries no backtick inside it, so it exonerates
nothing but its own delimiters. A fenced match is credited with its two
delimiters and never with its body, so the text above now reports
{2: 2, 3: 2}and both citations are recovered. A fenced block thatlegitimately quotes a backtick is charged too: the price is one coverage
warning plus one superset re-read, both keep-only.
unbalanced_fence_run(text)reports an odd number of fence runs. It catchesthe one shape the backtick count cannot: a six-backtick run reads as a fenced
match with an empty body, so every backtick pairs off while the file is still
malformed. Charge-only, like the rest of the coverage warnings — it reaches
neither a verdict nor the exit code.
_markdown_sourcedoes not skip a flagged line — skipping drops exactlythe citations with no verbatim fallback. It re-reads the line with
pairing_independent_tokens, which splits on backticks: every code-span bodyis a maximal backtick-free substring of its line, so the split is a superset
of the bodies any pairing can produce. The re-read can add citations and
cannot drop one. The defect is still reported, in a
coverage warnings, Markdown backtick parityblock, so the Markdown gets repaired rather thanworked around.
elided_prefix_matchestreats a suffix citation whose elided prefix is spelledout on the same line as a shorthand for the siblings that share that prefix,
rather than a family label attributed to nobody. A family label on a line that
elides nothing still rescues nobody.
LIMITATIONSgainsL10(backtick parity, fences included) andL11(familylabels); the module docstring gains the corresponding fifth design rule.
Corrections made after review (
389b91f5→17074732)Two numeric claims shipped in
389b91f5were false as written and are replacedby the measurement:
elided_prefix_matchesandL11said charging every family-label match"leaves no reachable
safe-to-deleteverdict at all". Measured by applyingthat mutant to the real file and sweeping all 10965 declarations: it leaves
232 (
published 6731 / uncertain 1467 / load-bearing 2535 / safe 232), acollapse from 1458 (84%), not an elimination. The rejection stands on the 84%
collapse plus
--expectFAIL (1 of 18), and the source now says that. Thefragment census quoted alongside it (
514 of 742 … 5253 of 11000) did notreproduce either and is restated as measured: 531 of the 759 distinct
fragment-shaped tokens that resolve at all match two declarations or more,
union 5895 of 11000.
_ferromagnetic,_pos)"elides no prefix anyone spelled out and is unaffected". Measured on the real
documentation,
_ferromagneticis charged 155 times and_pos9times. Counterfactual on the 223
_ferromagneticcandidates: rule off leaves92
safe-to-delete, rule off for_ferromagneticonly leaves 60,rule on leaves 47 — so that one label accounts for 13 of the 45 that move
and other elided fragments on the same rows for the remaining 32. The
docstring now states this instead of denying it. (The
932 declarationsfigure is likewise replaced: the rule makes 2206 charges over 1077
distinct declarations.)
The 84%/232 correction does not change the decision, and neither correction
changes a line of classifier behaviour.
Measurements
Run on this branch against main
075bcd39.docs/index.mdcoverage warnings: 3 (:1200,:1201,:1809); the runreports 0 recovered tokens on
:1200, 2 on:1201, 277 on:1809.The 10 declarations of
IsingModel/AmbientLattice/BetaDerivativeMagnetization.lean:before, 3 published / 6 load-bearing / 1 safe-to-delete; after, 5 published /
4 load-bearing / 1 uncertain / 0 safe-to-delete. The one that moved is
magnetizationAlongExhaustion_differentiable_beta_gen, now charged byshorthand docs/index.md:1809: `_differentiable_beta_gen`.Whole-library sweep, all 11000 non-anonymous declarations classified (10965
distinct names) on main and on
17074732: 235 verdicts move, and every move istoward keeping —
No move has
safe-to-deleteas its target, and the after-safe set is a strictsubset of the before-safe set. Totals: safe 1673 -> 1458, uncertain 561 -> 692,
load-bearing 2031 -> 2084, published 6700 -> 6731. Documentation family labels
844 -> 512.
The fence repair is not reachable on the current corpus and changes nothing
there: no fenced body in
README.mdordocs/**/*.mdcontains a backtick, andevery scanned Markdown file has an even number of fence runs. The same
10965-declaration sweep on
389b91f5and on17074732is identical —0 verdicts moved, safe stays 1458, family labels stay 512.
README.md:66and
:69already carry fences, so one added or removed delimiter reproducesthe counterexample.
_ferromagneticfamily calibration (--pattern '_ferromagnetic$', 223candidates): 92/44/52/35 -> 47/77/64/35 safe/uncertain/load-bearing/published;
45 candidates leave
safe-to-delete, 33 touncertainand 12 toload-bearingthrough the delete-closure. Zero-consumer count is unchanged at112.
FamilyCalibrationTestand the fixtures header are refreshed together;the header had also drifted, still carrying 263/132/143 from main
0922a812.Tests
15 new cases:
MarkdownBacktickParityTest(12) andElidedFragmentTest(3).They cover the synthetic parity flip, the swallowed fenced block and its
recovered tokens, the run-parity check and the six-backtick shape it alone
catches, the real
docs/index.mdwarnings and recovered tokens, the end-to-endverdict of the declaration the defect offered up for deletion, and both sides
of the elision rule.
test_balanced_lines_and_fences_raise_nothingassertedthe old silence on a fence whose body quotes a backtick and is rewritten to the
fail-closed expectation.
python3 scripts/dead_candidate_scan.py --self-test: 94 tests, pass (90 on389b91f5, 79 on main).python3 scripts/dead_candidate_scan.py --expect scripts/audit/dead_candidate_fixtures.tsv:18 rows, pass.
python3 scripts/test_audit_gate.py: 166 tests, pass (1 skipped).python3 scripts/test_citation_audit.py: 150 tests, pass. Neither moduleimports
dead_candidate_scan; the only coupling istest_audit_gate.py'simport-contract check, and the
from audit_gate import (...)list is unchanged.Weakening mutants, each applied alone and reverted, guard tests re-run:
unpaired_backticksreturn{}M5 is new with this round and turns red exactly the two tests that pin the
fence repair:
test_a_backtick_inside_a_fence_is_charged_not_exoneratedandtest_the_swallowed_citations_are_recovered_and_reported.lake buildwas not run: no Lean source is touched by this branch.Scope notes
docs/index.md:1809is still there. Repairing theMarkdown is editorial and stays out of this branch, which only stops the
scanner from reading the damaged line as evidence of absence.
L10: a codespan opened on one line and closed on the next is unmatchable in either
pairing (both lines still raise the warning), and prose read as a citation by
the re-read can only add
uncertain, never remove it.here.
elided_prefix_matchesuses a lexicalstartswithwith no identifierboundary (58 charges over 47 sites today, and an identifier-boundary variant
changes 0 of 10965 verdicts); the elision rule's locality bound is the
documentation line, which on a 52186-character progress row is wide; and
test_the_real_index_raises_its_three_warningspins line numbers that aneditorial Markdown repair would move. All three are over-keep or editorial, so
none can produce a false
safe-to-delete.ElidedFragmentTestdocstring ofscripts/test_dead_candidate_scan.py("5253 of 11000 declarations, no reachable
safe-to-delete"). It now statesthe measured 5895 of 11000 and the collapse from 1458 verdicts to 232,
matching
dead_candidate_scan.py;grep -rn "5253\|no reachable"over theworktree finds no other copy.
🤖 Generated with Claude Code