Skip to content

corpus damage: freeze, per-class candidate patch, and what one rule does not reach (#2154 follow-up) #2160

Description

@gHashTag

Follow-up to #2154. This is the measurement, not a corpus rewrite. No spec under specs/ is modified by any tool added here.

Frozen before touching anything

docs/corpus/damage_snapshot_2026-08-15.json — 125 lines, 65 files, 15 shapes, corpus_sha256 = 1b5a37b7a89efb782db7efd2ca7af728d4ed48b1c13ac2a35088c7926b16afd9. Per row: class id, shape, file, line, field, verbatim rhs, 2 lines of context either side, and the file digest. tri damage-repair REFUSES to run if any digest has moved since the freeze.

Class ids are DC- + 8 hex of sha256(shape), deliberately not a frequency rank, so adding a class later does not renumber a citation.

The mechanism, established from the corpus

An intact field is name : "TypeText",. Every damaged line has one defect: the opening quote of the type string was replaced by [.

intact   children : "[4]?QuadNode",
damaged  children : [[4]?QuadNode",

That single fact explains both signals tri damage detects (doubled bracket, odd quote count). The candidate patch is therefore one character at a known offset, and inverting it is the same operation reversed.

The split is the load-bearing result

Applying the substitution and asking whether the result is a closed string is a decision procedure, not a guess.

  • 107 lines / 12 classes yield a closed string. Every character of the type text survived; the repair only puts the delimiter back.
  • 18 lines / 3 classes do not: the type text was TRUNCATED as well. [[]Const [, becomes "[]Const [, — the element type is gone. []Const []Const u8 and []Const [N]u8 are both plausible and the file holds no evidence either way. These get NO patch and go to needs-human-language-decision, owner: language owner.

Auto-repairing those would be a guess dressed as a fix, and unfalsifiable afterwards because the original is unrecoverable.

Double validation, because parsing alone proves nothing

Deleting the offending line also makes a file parse. So each applied candidate is checked twice: (1) t27c parse exits 0; (2) the specific field is back with a NON-EMPTY type AND no previously-present field vanished. Effect is one of parse-restored / still-malformed / ambiguous / needs-human-language-decision.

Two measurement units, and a hypothesis of mine that was wrong

Per-class run: 6 parse-restored, 6 still-malformed, 0 ambiguous, 3 needs-human-language-decision.

My first reading of those 6 still-malformed classes was co-located UNRESTORABLE damage. Checked and false — none of those 7 files contains a destroyed line (0 of 0). The real cause was co-located damage from OTHER restorable classes, untouched by a single-class run. Hence a second mode, --combined, which repairs every restorable row per file:

effect files
parse-restored 49
still-malformed 11
ambiguous 2
needs-human-language-decision 3

Neither unit replaces the other: per-class answers "is one rule enough for this shape", per-file answers "what does the rule achieve".

A third defect, found only because the first two were repaired

10 of those 11 still-malformed files fail at pub const Name(T) = struct {Unexpected token in expression: KwStruct. That is a parser gap on generic const struct declarations, not corpus damage, and it was invisible while the files failed earlier for another reason. The 11th (bitset.t27) fails at Expected LParen, got KwTest — separate, unclassified.

Re-run differential on the repaired corpus

tri diffbin /tmp/t27c.base /tmp/t27c.fixed over the repaired corpus: 623 unchanged, 8 field-loss, 1 strict-improvement, 2 malformed-input-tradeoff, 0 unknown (was 616 / 13 / 1 / 4 / 0).

field-loss 13 -> 8, and 8 of 8 remaining field-loss files contain a destroyed line. The correlation is exact.

field-loss is NOT zero, so the gate on #2151 is NOT met and #2151 stays undecided. The residue is not a tool defect; it is the 18 destroyed lines, which cannot be repaired without a language decision.

Fixtures

15 positive (bootstrap/tests/fixtures/damage_classes/DC-*.t27), one per class, each carrying its shape, origin, candidate and expected effect. 6 negative (bootstrap/tests/fixtures/damage_negative/) pinning the false signals that made an earlier count a metric of the regex: latency bounds target : < 5000ns, match arms, multi-line array literals, function signatures, raw strings, and the intact convention.

Evidentiary power checked rather than assumed: the reconstructed first signal set fires on 6 of 6 negative fixtures; the current one fires on 0. On the real corpus the naive set reports 1378 lines against 125.

Not claimed

  • Not that the 125 are all the damage. tri damage checks two textual signals; damage producing a balanced, quote-even, plausible wrong type is invisible to it and nothing here bounds how much of that exists.
  • Not that parser: nesting decides the separator, never the terminator (#2127) #2151 is regression-free.
  • Nothing about performance: 63 unparsed files remain excluded from cost, and the full-run hang is still undiagnosed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions