Skip to content

corpus: freeze the damage, patch one class at a time, and say where one rule stops - #2161

Closed
gHashTag wants to merge 2 commits into
masterfrom
w699-damage-classes
Closed

corpus: freeze the damage, patch one class at a time, and say where one rule stops#2161
gHashTag wants to merge 2 commits into
masterfrom
w699-damage-classes

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #2160

Stacked on w699-restore-loop-tools (#2159), not on masterdamage_repair.py imports the field extractor from diffbin.py, which lives on that branch. Base it on master and the import breaks. Review #2159 first.

No spec under specs/ is modified by anything here. Repairs are written to a scratch tree; specs/ is read-only to both new tools.

What the tools do

tri damage-freeze — writes the snapshot, 125 rows with class id, shape, file, line, field, verbatim rhs, ±2 lines of context, and the file digest. docs/corpus/damage_snapshot_2026-08-15.json, corpus_sha256 = 1b5a37b7a89efb782db7efd2ca7af728d4ed48b1c13ac2a35088c7926b16afd9.

tri damage-repair — one candidate per class, reversible diff, effect measured. Refuses to run if any file digest has moved since the freeze, because a patch applied to a changed file is applied to a different file than the one surveyed.

The mechanism

Intact: name : "TypeText",. Damaged: the opening quote became [. children : "[4]?QuadNode",children : [[4]?QuadNode",. One fact, both signals, one-character patch, self-inverse.

The split, which is the result

lines classes
substitution yields a closed string → candidate proposed 107 12
type text truncated too, element type gone → no patch 18 3

[[]Const [,"[]Const [, is not closed. []Const []Const u8 and []Const [N]u8 are both plausible; the file has no evidence. Held as needs-human-language-decision, owner: language owner. Criterion: state what an unclosed element type in slice position means and whether a placeholder is permitted.

Double validation

Deleting the line also makes a file parse. So: (1) t27c parse exits 0; (2) the field is back with a non-empty type and nothing previously present vanished.

Two units, because a hypothesis of mine was wrong

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

I read those six as co-located unrestorable damage. Checked: false — 0 destroyed lines across all seven files. Real cause: co-located damage from other restorable classes, untouched by a single-class run. Hence --combined:

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

A third defect, visible only after the first two were fixed

10 of 11 remaining files fail at pub const Name(T) = struct {Unexpected token in expression: KwStruct. Parser gap on generic const struct declarations, not corpus damage. bitset.t27 fails at Expected LParen, got KwTest — separate, unclassified. Both need their own issues; neither is in scope here.

Re-run differential

623 unchanged, 8 field-loss, 1 strict-improvement, 2 malformed-input-tradeoff, 0 unknown (was 616 / 13 / 1 / 4 / 0).

8 of 8 remaining field-loss files contain a destroyed line — exact correlation.

field-loss ≠ 0, so the gate on #2151 is not met. #2151 stays undecided and must not be merged.

Fixtures, tested rather than assumed

15 positive (one per class, carrying shape/origin/candidate/expected effect) + 6 negative pinning the false signals: target : < 5000ns, match arms, multi-line arrays, function signatures, raw strings, intact convention.

Discriminating power: reconstructed first signal set fires on 6 of 6 negatives, current on 0. On the real corpus naive = 1378 lines, current = 125.

loop-tools-tracked extended to both new tools and verified to FAIL (exit 1) while untracked and PASS after commit.

Not claimed

fpga-formal / fpga-synthesis red is the master baseline (#2153). check-first-party-doc-language.sh has 8 pre-existing errors on master; none is a file from this branch.

Closes #2158)

Two measurement tools were lost and every number they had produced became
unreproducible with them. cost.py and diffbin.py were written, quoted in #2151,
and never committed; the working copy was later re-cloned. Six recovery routes
came back empty -- dangling objects held only a git stash WIP with triage.py, the
reflog records the clone rather than the content, shell history is absent, CI
artifacts hold only FPGA outputs, no PR or issue comment carries the source, and
the session snapshot preserved prose about the scripts instead of the scripts.
So these are reimplementations from a written contract. Recalling what the old
ones roughly did would have reproduced the old one's defect.

That defect was the specification for the new one. It reported "0 regressions"
over 634 specs while files were losing declared struct fields, because a per-file
judgement had relabelled the loss as an acceptable trade and the aggregate then
printed the judgement as if it were a measurement.

  No differential result may be called "0 regressions" unless the metric
  actually checks the claimed class of loss.

diffbin now assigns five ordered categories -- unchanged, field-loss,
strict-improvement, malformed-input-tradeoff, unknown -- with field-loss tested
before strict-improvement, so removing a phantom while dropping a declared field
is a loss and not an improvement. Phantom and declared are told apart by a stated
rule: a removed field is a phantom only if its base type text was empty. Only an
ExprIdentifier whose parent is a StructDecl counts, so identifiers in function
bodies stay out of the totals.

Re-measured on the same 634 specs and the same two binaries: 616 unchanged, 13
field-loss, 1 strict-improvement, 4 malformed-input-tradeoff, 0 unknown.
handoff.t27 goes from 35 parsed fields to 12. All 17 files that moved are inside
the damaged set and no well-formed spec changed at all, which is what 0 unknown
is carrying.

cost reports per stratum with n, median, p95, min-max ms/KB and coefficient of
variation, alpha only at n >= 8 with its r2 and KB range, and no cross-family
alpha at all: that number is a metric of corpus composition rather than of the
parser (#2133), and a printed number gets quoted while its caveat does not
travel with it.

damage classifies the corrupt annotations by shape rather than repairing them
(#2154): 125 lines, 65 files, 15 shapes, one fixture each. The first draft
reported 429, of which 230 were the legitimate bound `target : < 5000ns`, so the
fix was deleting two bad signals rather than tuning a threshold.

loop-tools-tracked.sh fails when a loop tool is missing, untracked, or unrouted,
and was verified to fail in exactly the pre-loss state. The dispatcher no longer
looks for a built compiler before running helpers that never use one.
…ne rule stops (Closes #2160)

The mechanism is one character: the opening quote of the type string was replaced
by '['. Substituting it back and asking whether the result is a closed string is a
decision procedure, and it splits the 125 lines exactly on class boundaries --
107 in 12 classes restorable, 18 in 3 classes truncated beyond recovery and held
for a language decision rather than guessed at.

Two validations per candidate, because deleting a line also makes a file parse:
the field must return with a non-empty type and nothing previously present may
vanish. Two measurement units, because our own first reading of six
still-malformed classes (co-located destroyed lines) was checked and found false
-- the cause was co-located damage from other restorable classes.

Repairing those exposed a third defect that had been hidden behind them: ten
files now fail on 'pub const Name(T) = struct', a parser gap on generic const
struct declarations, not corpus damage.

Re-run differential: 623 unchanged, 8 field-loss, 1 strict-improvement,
2 malformed-input-tradeoff, 0 unknown (was 616/13/1/4/0). All eight remaining
field-loss files contain a destroyed line. field-loss is not zero, so #2151
stays undecided.

Negative fixtures were tested for discriminating power, not assumed to have it:
the reconstructed first signal set fires on 6 of 6, the current one on 0.

No spec under specs/ is modified by anything here.
@gHashTag

Copy link
Copy Markdown
Owner Author

Disclosure: this stacked PR is NOT gate-equivalent to a master-based one

gh pr checks on this PR lists nine checks, all green. Four of the checks that guard master did not run at all, and their absence is silent — a green tick sheet here does not mean what a green tick sheet on a master-based PR means.

Cause, read from the workflow files rather than inferred:

workflow trigger
.github/workflows/now-sync-gate.yml (check-now-freshness) pull_request: branches: [master]
.github/workflows/issue-gate.yml (check-linked-issue) pull_request_target: branches: [master]
.github/workflows/seal-staleness-warn.yml pull_request: branches: [master]

Base here is w699-restore-loop-tools, so none of them match. This is a general property of stacked PRs in this repository, not something specific to this branch, and it is worth knowing before any stacked PR is read as verified.

Ran the two content gates by hand instead, so the claim is measured rather than assumed:

  • check-now-freshness: docs/NOW.md carries Last updated: 2026-08-15, inside the accepted [yesterday..tomorrow] UTC window (today UTC is 2026-08-14). New section is at the top of the file.
  • check-linked-issue: PR body line 1 is Closes #2160, and corpus damage: freeze, per-class candidate patch, and what one rule does not reach (#2154 follow-up) #2160 exists and is OPEN. Verified in that order, because the gate passes on syntax alone and would accept a reference to an issue that was never created.
  • seal-staleness-warn is not applicable: bootstrap/src/compiler.rs, bootstrap/stage0/FROZEN_HASH and repro/numerics/nmse_manifest.json are untouched by this branch.

Not verified by hand: whatever check and validate do beyond the above — those run under a different trigger set and their master-base behaviour on this diff is unmeasured. When #2159 lands, this branch will need retargeting to master and a real gate run before it can be called verified.

This was referenced Aug 14, 2026
gHashTag added a commit that referenced this pull request Aug 24, 2026
#2630 established that the boundary column's denominator holds two
populations -- comparisons that decide a verdict, and loop bounds and
display cutoffs that cannot -- and that no line-local filter separates
them, because a kill is the only proof of verdict-reachability and the
filter removed proven kills.

That leaves the column unreadable rather than wrong. `SURVIVED at
boundary lines 45, 91, 214, 223, 226, 409` gives a reader nothing to
act on.

Two changes, neither touching the measurement.

Print the source beside the line number, for this operator only:

  91   `if len(out) > 6:`      a display cutoff
  214  `while len(v) < N:`     a loop bound
  226  `while j < len(src):`   a scan bound
  223  `if b < 0:`             the only one worth reading

Five of six classify themselves at a glance. The reader does the
separation the tool cannot, in seconds rather than by opening the file.

And say what the denominator is, in the summary, only when this
operator ran -- so killed/total is not read as a rate.

The rule: when a metric mixes populations you cannot separate, do not
report a ratio; report the members. A rate over a mixed denominator
invites exactly the conclusion this command exists to prevent, and
invites it most from whoever computed it.

The killed count keeps its meaning: a LOWER BOUND, established after
the fact, on how many comparisons reach a verdict. Smaller than a
percentage, and true.

194 tests pass.

Refs #2161
gHashTag added a commit that referenced this pull request Aug 24, 2026
* gft encoder: pin the smallest normal binade

`if off < 0: return 0` survived the boundary operator. Printing the
source beside the line number (#2634, yesterday) showed it is not a
loop bound.

`off = e + 40` is ZERO in the smallest normal binade, [2^-40, 2^-39).
With `<=`, every value there encodes as 0 -- which is the ZERO
SENTINEL. A non-zero magnitude silently reported as zero, in the
encoder every trained weight passes through.

The exact power 2^-40 is NOT a witness: its mantissa is 0, so it
already encodes as 0 and the mutant changes nothing there. A witness
needs a non-zero mantissa in that binade, so the pin is 1.5*2^-40,
which encodes 256 and would become 0.

Three assertions: the magnitude, the sign, and the value one binade
below which IS legitimately zero. Negative control: with `<=` planted
the tool exits 1 on the new assertion naming it; restored, 0.

Nothing else in this file reaches that binade -- every value the
training self-tests encode sits many binades higher, which is why the
mutant lived.

This also corrects a generalization I made yesterday. Of the four
`if x < 0:` sites across the tools, only the `find()` idiom in
verify_igla_race is equivalent (find returns -1 or an index, and every
caller passes a match start whose first character is a keyword). The
other three -- this one, `d < 0` in _magsub, and `end < 0` after
rfind in diffbin -- are real boundaries where 0 is a valid value.
"Boundary survivors are loop bounds" was too broad.

Refs #2161

* skill: a surviving boundary was real, and the boundary value was not its witness (Refs #2161)
gHashTag added a commit that referenced this pull request Aug 24, 2026
* gft encoder: pin round-half-to-even on exact ties

`if t > hf: mant += 1` in _magadd survived the boundary operator. It is
the round-half-to-even decision:

    if t > hf: mant += 1                  strictly above half
    elif t == hf and (s & 1): mant += 1   exactly half -> to even

With `>=` the pair becomes round-half-UP and the parity test on the
next line is dead code. Measured:

    _magadd(25600, 20480)  clean 25600   mutated 25601
    _magadd(20992, 20481)  clean 21248   mutated 21249
    _magadd(21504, 20482)  clean 21632   mutated 21633

256 such witnesses exist. The self-tests did not notice ANY of them,
because they train a net and check ACCURACY: an optimiser absorbs a
last-bit error in every addition without changing whether XOR reaches
4/4. An outcome test cannot see an arithmetic defect that outcomes
tolerate.

Three assertions pin the decision itself, one per branch: a tie with
even `s` must NOT round up, a tie with odd `s` must, and strictly above
half must. Negative controls: weakening `>` to `>=` fails the first,
disabling the parity test fails the second.

A correction, too. I planned this tick around line 203, `if d < 0` in
_magsub, listed yesterday among four boundary sites -- without checking
it was a survivor. It is killed: with `<=` the XOR self-test reports
1/4. A finding recorded as a line number expires on the next edit, and
that rule is one I wrote.

Refs #2161

* skill: an outcome test cannot see a defect the outcome tolerates (Refs #2161)
gHashTag added a commit that referenced this pull request Aug 24, 2026
… (#2644)

* gft multiplier: pin round-half-to-even on both carry paths

`_magmul` rounds with the identical pair `_magadd` uses, over a product
instead of a sum, and its `if r > half` survived the boundary operator
for the same reason: the self-tests train a net and check ACCURACY, and
an optimiser absorbs a last-bit error in every multiply as readily as
in every add.

Two paths with different `half`, so four cases rather than three. Only
the even-q rows distinguish `>=`; only the odd-q rows distinguish a
dead parity branch:

  case                 clean   r >= half   parity off
  carry=0, q even      20610     20611       20610
  carry=0, q odd       20738     20738       20737
  carry=1, q even      20998     20999       20998
  carry=1, q odd       21016     21016       21015

Measured by planting each mutant, not derived.

AND MY FIRST NEGATIVE CONTROL DID NOT APPLY. It reported the `>=`
mutant as surviving the new assertions. It had never been planted: the
replacement string I built lost the leading `if `, so `str.replace`
matched nothing and rewrote the file unchanged. A silent no-op
substitution reads as the strongest possible evidence for the wrong
conclusion -- "your assertion does not catch this" -- when nothing was
tested at all.

Both controls now assert the anchor exists before replacing. With that,
`>=` is caught by the even-q assertion and disabling parity by the
odd-q one, each by the assertion aimed at it.

Refs #2161

* skill: the same defect one function over, and a control that did not apply (Refs #2161)
gHashTag added a commit that referenced this pull request Aug 24, 2026
…#2161)

The gate asks for docs/now/<date>-<slug>.md, which is where 184 entries
live. Root NOW.md has not changed on master since 2026-08-09; editing it
satisfied nothing and was the wrong file.
gHashTag added a commit that referenced this pull request Aug 24, 2026
… (Refs #2161)

Section 116. The mechanism (512 == 1 << 9 is the low bit of the exponent
field, so a stuck mantissa renormalises for free on even exponents), the
three probe failures that hid it, and the two controls that localised the
fault to the input space rather than the harness.
gHashTag added a commit that referenced this pull request Aug 24, 2026
) (#2648)

* gft interpreter: pin renormalisation carry at all four sites (Refs #2161)

`enc`, `_magmul`, `_magadd` and `_magsub` all end in

    if mant >= 512: mant = 0; <exponent> += 1

and all four survived the `>=` -> `>` boundary mutant, for one shared
reason: the mutant leaves mant == 512, and `(off << 9) | 512` is
IDENTICAL to `((off + 1) << 9)` whenever `off` is EVEN, because
512 == 1 << 9 is the low bit of the exponent field. The stuck mantissa
renormalises the value by accident across half the exponent space.

Two further reasons a sweep misses this, both measured:
  * mant == 512 is reached only by rounding UP from 511, so a sweep of
    exactly-representable inputs never enters the branch;
  * in `_magmul` the carry path cannot reach it in principle -- the
    largest product 1023 * 1023 == 1046529 gives q == 1022, mant == 510.

Four assertions, each checked both ways: holds clean, fails when its own
site is mutated, catches no other site's mutant. Boundary column for this
file, measured after the change: killed 8/31 -> 12/31.

* docs/now: renormalisation carry entry; drop the root NOW.md edit (Refs #2161)

The gate asks for docs/now/<date>-<slug>.md, which is where 184 entries
live. Root NOW.md has not changed on master since 2026-08-09; editing it
satisfied nothing and was the wrong file.

* ci-gates skill: three sweeps, three ways of never reaching the branch (Refs #2161)

Section 116. The mechanism (512 == 1 << 9 is the low bit of the exponent
field, so a stuck mantissa renormalises for free on even exponents), the
three probe failures that hid it, and the two controls that localised the
fault to the input space rather than the harness.
gHashTag added a commit that referenced this pull request Aug 24, 2026
…Refs #2161) (#2653)

`_magsub` resolves a rounding tie three ways and only two arms are live.
Deleting `if sticky: mant += 1` changes 0 of 525_918 outputs; forcing the
sticky DETECTOR to always fire changes 9851. So the arm is wired -- it is
the value `sticky` takes that never reaches the tie. An exhaustive search
over every (hm, lm, d) for ho in {2, 3, 40, 79} finds no input where
`rem == half` and `sticky == 1` hold together.

That is what keeps the function correct. The arm is the ADDITION rule in
the SUBTRACTION path: discarded bits of the subtrahend make the true
difference smaller, so a tie with lost bits sits strictly below half and
must round DOWN. Measured with exact rationals -- the code's diff is
13303794 against a true 13303793.734375.

Reported as #2652, not changed: this mirrors board/bpseq.v and the RTL
has not been measured.

Three assertions, each catching a different operator class -- boundary,
dead-branch, and deletion -- verified by planting all six mutants.
`if d >= 26` -> `> 26` is recorded as a proven equivalent mutant so the
next tick does not re-chase it.
gHashTag added a commit that referenced this pull request Aug 24, 2026
…em (Refs #2161) (#2656)

* tri gates mutate: refute the equivalence claims instead of quoting them (Refs #2161)

`# mutant-equivalent: <why>` marks a survivor as unkillable by
construction, and this command printed it as "claims equivalent: ...".
The word `claims` was carrying real weight: nothing had ever checked one.
Six sit in tools/.

The run best placed to notice a stale claim is this one -- it already
built the mutant and already knows the verdict. A claimed line whose
mutant DIES is now reported as contradicted. Measured against the six
that already existed: none contradicted.

Counts, not membership: a line can hold two mutable sites (`if a < 1 or
b < 1:`), and a claimed line with one of two dead has been contradicted
once. Keying on "did the line leave the survivor list" would call that
claim intact.

Claims name no operator, and every one in the tree argues about a
comparison. A line equivalent under `boundary` can die under `invert`, so
the report names the direction rather than pretending to judge.

`contradicted_claims()` is separate from the run loop and has four tests:
survived, died, half-died, and claimed-but-not-mutated-in-this-direction.
A checker nobody can test is the same failure one level up.

* tri gates mutate: refute the equivalence claims instead of quoting them (Refs #2161)

`# mutant-equivalent: <why>` marks a survivor as unkillable by
construction, and this command printed it as "claims equivalent: ...".
The word `claims` was carrying real weight: nothing had ever checked one.
Six sit in tools/.

The run best placed to notice a stale claim is this one -- it already
built the mutant and already knows the verdict. A claimed line whose
mutant DIES is now reported as contradicted. Measured against all six
that already existed: none contradicted.

Counts, not membership: a line can hold two mutable sites (`if a < 1 or
b < 1:`), and a claimed line with one of two dead has been contradicted
once. Keying on "did the line leave the survivor list" would call that
claim intact.

The marker must now OPEN the comment rather than appear anywhere in it.
Prose describing the mechanism -- "that reasoning sits on the line as a
`# mutant-equivalent:` claim" -- registered as a claim of its own, bound
to whatever code line followed, which the new check would have reported
as contradicted the day that unrelated line's mutant died. Caught because
the count printed 2 where one claim had been written. Every real claim in
the tree already opens its comment this way.

`contradicted_claims()` is separate from the run loop; six tests cover
survived, died, half-died, claimed-but-not-mutated-in-this-direction,
mention-is-not-a-claim, and marker-opens-the-comment.

First claim written under the check: gft_backprop_microcode.py's
`if d >= 26`, proven equivalent over 525_918 points.
gHashTag added a commit that referenced this pull request Aug 24, 2026
…2161) (#2659)

Closing the sticky finding I cited `board/bpseq.v` as the reason not to
change a rounding rule I had just shown to be wrong. `git log --all` for
that path is empty -- it has never existed in this repository. The
pointer came out of the file's own docstring and I repeated it without
checking it resolved.

The counterpart is `specs/ternary/gft_sadd.t27`. Measured: it carries the
rule line for line, 30 specs carry a copy, the arm is dead there too
(exhaustive over every (hm, lm, d) for six ho), and the spec's barrel
shift agrees with this file's 12-step loop bit-for-bit over 2_193_075
points. The generated Verilog, C and Rust all carry the same dormant rule,
and the bit-exactness gates cannot see it: they prove the compiler
faithful to the spec, never the spec correct.

verify_multitarget's operands were `uniform(-4, 4)` -- off 35..41, six of
81 exponents. Widened to 16 of 81 across the full range by APPENDING
pairs, so the existing 600 stay byte-identical: 1744 pairs, still green;
negative control gives 12 mismatches on a mutated model.

New `tri pointers`: prose pointers ("see X", "cf. X") that do not
resolve. Every path-shaped string was measured first -- 873 mentions, 409
unresolved, mostly paths a program creates -- and discarded as noise. The
narrow form gives 193 and 16. Its first run flagged its own docstring;
the docstring was reworded rather than the tool special-cased.
gHashTag added a commit that referenced this pull request Aug 24, 2026
…2161) (#2661)

`tri pointers` shipped, and its first run on master flagged §120 -- the
section documenting it -- because that section quoted the dead path after
a cue word while explaining the bug. 16 -> 17, and the row named the
skill file.

Third instance in one session, after the equivalence marker and the
pointer tool's own docstring. The trigger lives in the vocabulary, so
every document that teaches the vocabulary carries it; the durable
defence is to run the checker after writing about the checker and read
the count.

Fixed the writer, not the tool, as the rule chosen one commit earlier
requires: a special case for skill files would blind the checker to
exactly the documents most likely to discuss paths.
gHashTag added a commit that referenced this pull request Aug 24, 2026
…n (Refs #2161) (#2663)

* tri gates mutate: the boundary scanner never left the control function (Refs #2161)

`--boundary` reported 31 sites for tools/gft_backprop_microcode.py, and an
independent tokeniser counted 31 comparisons before `if __name__`. The
numbers agreed and the story wrote itself: the operator declines to mutate
a file's own self-test.

Both numbers were real and the agreement was a coincidence. Asking for
the site LINES instead of the count showed the scan ending at 371 -- nine
lines before `__main__`, at `def self_check():`. `is_control_fn` sets
`in_control` and `boundary_sites` never reset it, so everything from 380
to end of file had silently never been mutated. 31 -> 62 sites, which is
the tokeniser's count for the whole file.

Three of four site finders already carried this fix (T125). They are
line-oriented; this one is byte-oriented, so it had no line to attach to
when `--boundary` was added.

The motivating file cannot test the other half: `self_check` there holds
zero comparisons. A synthetic helper/control/`__main__` source covers all
three answers, and reverting the fix makes it fail with `[2]`.

Also in this commit:

* `_magsub`: assert `sticky == 0` at every tie. The arm below it is the
  addition rule in the subtraction path and is correct only because it
  never runs; this makes waking up loud without deciding what the rule
  should be (that is 30 specs, #2652). Positive control: waking the
  detector fires it.
* `tri pointers`: `(?![\w])` restored -- without it `.v` matched inside
  `docker/Dockerfile.vivado` and the tool's own first run invented a dead
  pointer. The guard was in the exploratory version and lost in the
  rewrite.
* `tri pointers`: dead pointers are now split by history into NEVER (no
  commit ever -- the pointer was wrong when written) and REMOVED (stale,
  and the history says where it went). 12 and 2. Both REMOVED ones were
  moved by one docs restructure and are fixed here, taking that class to
  zero.

* docs/now: the count was right for the wrong reason (Refs #2161)
gHashTag added a commit that referenced this pull request Aug 24, 2026
…2666)

`if d > 26` on master should be `if d >= 26`. A full `tri gates mutate`
sweep was running in the background when I staged with `git add -A`, and
the mutant it was holding at that instant went into the commit and
through the merge.

Behaviour is unaffected -- this is the mutant proved equivalent over
525_918 points, and the line's own `# mutant-equivalent:` comment says
so. That is what makes it worth fixing rather than shrugging at: the code
stopped matching the comment directly above it while every test stayed
green.

Skill section 122: the dirty-tree guard protects the run, not the
operator. `git add -A` cannot tell a mutant from an edit, and
`target/.tri-mutating` was sitting right there unread.
gHashTag added a commit that referenced this pull request Aug 24, 2026
…rol (Refs #2161) (#2687)

A five-way fan-out audit raised 19 findings; 11 survived adversarial
refutation, 8 were killed.

SCANNER (cli/tri/src/gates.rs), three defects, each with a test that fails
when its fix is removed:

* The three LINE-oriented site finders had no string state, so a
  flush-left line inside a control function's docstring cleared
  `in_control` and handed the operators the control's own `assert` --
  a site nobody can kill, because neutering a control makes it pass. Two
  files differing by four spaces of indentation on ONE docstring line
  scored 3 assert sites versus 0.
* The same blindness scored word-wrapped prose beginning a line with
  `assert` as an assertion to neuter.
* `assert_sites`' message splitter advanced one byte on a backslash
  instead of two, so an escaped quote either produced a mutant that does
  not PARSE (python exits 1, the site scores KILLED -- a false green in
  the column this command exists to make trustworthy) or stranded the
  quote state and silently dropped the message. `boundary_sites` had
  `i += 2` all along.

CONTROL (tools/gft_backprop_microcode.py): the second plant did not test
what it says. `input [31:0] x0i` spelled out occurs exactly once in the
file -- at the assertion that checks for it -- because the emitter builds
ports with an f-string. The plant rewrote the assertion's own expected
string and the emitted Verilog was untouched. It now targets the emitter's
format string.

STALE NUMBERS of mine, corrected:

* boundary denominators of the form k/31 came from the scanner bug; the
  file has 62 sites. Killed count is unchanged at 14. Errata added.
* `tri pointers`' docstring froze "193 mentions and 16 unresolved"; the
  tool printed 191 and 12 within a day. The counts are removed rather
  than updated -- a number in a docstring cannot learn it went stale.
* `--boundary`'s help advertised "the worst kill rate of the five (26 of
  77 across the tree)", whose denominator this same fix doubled.

Also: external/README.md and external/OWNERS.md pointed at a canon path
that moved. Owner issues filed: #2685 (a live PRL draft claims zero
admitted lemmas; proofs/ has 32, and the cited audit trail never existed)
and #2686 (Gate Topology audits 15 of 45 workflows and misses two live
violators; coq-proofs.yml has never been green in 50 runs).
gHashTag added a commit that referenced this pull request Aug 24, 2026
…16/34 (Refs #2161) (#2702)

The column was the number of planted faults, not a verdict on 32
assertions (§123). The model was tested by predicting before measuring:
2 plants -> 2/34, +7 -> 9/34, +7 more -> 16/34. Three for three.

Cost was never the obstacle and nobody had checked. Each new plant fires
in the arithmetic block at the top of `__main__`, before any training:
0.06s apiece. The control went 11.8s -> 12.5s for eight times the
coverage. The old assumption that more plants meant more whole-program
runs was generalised from a sample of two, both of which are full runs.

Within a family the fault has to be surgical -- adjacent assertions test
adjacent cases, so the obvious fault breaks them all and only the first
is seen. `elif t == hf and (s & 1)` -> `... and False` kills the ODD tie
only, leaving the even one true so it passes and the odd one speaks.

One resisted and is left alone: disabling `_magsub`'s `if rem > half`
also breaks the renormalisation-carry case, which is checked earlier, so
the plant fires that instead. Separating them would mean encoding the
exact remainders, and then the control is a second copy of the subject.

Also: `spawned()` now asserts the plant edited the SUBJECT. All subject
code sits above the control and every assertion under test below it, so
the first changed byte must precede `def self_check(`. T124 caught a
plant that edited the control's own source; T211 one that edited the
assertion checking the result. Both went green. Neither was caught by
reading; both would have been caught by this line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant