feat(mutants): RQ-65-MUTANTS (#1189) — mutation survey: 4 of 21 byte-changing mutants survive a named suite (19 %, upper bound); 3 controls KILLED; survivors enumerated; ledger CI-pinned - #1233
Conversation
…t's landed field Refs #1189 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Coordinator verification — the CI job re-runs the mutants; my first read was wrongI went looking for the failure mode this release exists to find: a mutation-survey gate that only That read was wrong, and reading the function settled it. The code uses different vocabulary: if verdict != "KILLED":
failures.append(f"{sid}: a mutation the ledger records as KILLED now SURVIVES its killer — the oracle lost its power")Four properties that make this a gate rather than a record, all present:
The ci.yml grep requires One small brittleness, not blocking. The grep's numeric classes are On the number. 4 of 21 byte-changing mutants survive, 19 %, relative to a named 5-job suite plus Refs #1189 |
Coordinator: the third control's finding is a systematic blind spot, not an incidental oneThe survey reports that the
Four of the five supply the register contract the startup blob exists to establish. An oracle that This is not hypothetical: it is exactly how #1203 survived. RQ-65-PARITY found it only because it Worth stating plainly in the report, because it changes what the survivor list means: the startup Note the repo does have oracles that boot the real startup — |
Coordinator: the ledger must be RE-ANCHORED on the rebase, not carried#1232 (RQ-65-MVPCORE) merged and grew
So the rebase must re-derive the ledger, not carry it — the same rule this release applies to the Two things this does NOT invalidate. The 19 % is relative to a stated frame and a stated commit, Method note: my first two attempts at this comparison both came back "DIFFERS on every line" with an |
…changing mutants survive a named 5-job suite + workspace tests (19 %, upper bound); 3 red-first controls KILLED; survivors enumerated; ledger CI-pinned The v0.65 headline number, measured rather than asserted. scripts/mutation_survey.py flips one codegen decision at a time (REG / COND / DROPMOV / IMM / GUARD / BOUND) over five anchor-located regions supplied by RQ-65-PARITY, rebuilds, triages by emitted bytes over 200 corpus modules x 3 ARM configurations, and runs a suite DERIVED from ci.yml only on byte-changing mutants. Measured at 580d53f, re-anchored at 761c692, seed 1189: 38 drawn, 6 uncompilable, 32 compiled, 21 byte-changing; 17 KILLED (6 execution, 8 structure, 2 freeze-only, 1 compiler hang), 4 UNTESTED = 19 % survival relative to the NAMED suite (wast-conformance, repro-sweep-arm-corpus, join-alias-1189, cmp-select, frame-slot-dce-242 + cargo test --workspace) — an upper bound under the full CI board. 11 byte-identical: 5 EQUIVALENT, 4 DEAD, 2 UNRESOLVED (declared). Controls: the #1189 copy disabled, the select operands swapped, the startup R10 seed into R9 — all KILLED (the third only by byte goldens: no execution oracle in the suite boots the shipped Reset_Handler). CI pin: the mutation-survey-discrimination job replays the ledger's ci_subset (3 controls + 4 non-killed) and fails in either direction; claims.yaml gains the eighth kind: ratchet (mutants_untested, a ceiling over docs/status/mutation_survey.json) plus count-eq pins on 38 / 21 / 4, the controls and the subset floors. No new emulation declaration; the exact floor is untouched. Refs #1189 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…t's landed field Refs #1189 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… introduced instead of carrying it Rebased onto main after RQ-65-ALIASCLASS (#1227) and RQ-65-MVPCORE (#1232). ci.yml conflicted because both PRs inserted a job at the same point; resolved by taking main's file whole and re-inserting the mutation job after the home-alias-audit job, then verifying BOTH parse and are present (64 jobs). CHANGELOG kept one `[Unreleased]` header with both bodies. MEASURED on the rebased tree, not assumed. #1232 grew crates/synth-cli/src/main.rs 11143 -> 11405 and mutation sites are anchored by file:line:col: sites enumerated 1542 ci_subset entries still resolving 7 of 7 (3 controls + 4 mutants) R5-startup mutants still resolving 0 of 7 THE LIVE GATE IS INTACT, and for a structural reason worth recording: the three controls come from `control_sites()` rather than a line anchor, and the four subset mutants live in optimizer_bridge.rs / liveness.rs / arm_backend.rs, which #1232 did not touch. `mutation-survey-discrimination` still replays real mutations and still fails if a recorded killer stops killing. WHAT IS STALE, said in the report rather than fixed silently: all seven R5-startup mutants point at moved code. They are 7 of the 21 byte-changing mutants the rate is computed over (6 KILLED, 1 UNTESTED) — and that one UNTESTED is one of the FOUR published survivors. So the 19 % stands as a measurement of the tree it names, a third of its denominator is not replayable on main, and re-anchoring R5 is follow-up work rather than a re-run: #1232 restructured that region, so "the equivalent site" is a judgement, not a line-number shift. A survey whose denominator quietly stops being checkable is the failure this release exists to name. Recording it is the point. Floor re-derived on the rebased tree: 346751, unchanged (this job declares no emulations). Ratchets unmoved; mutants_untested 4 = pinned. Refs #1189, #1232, #1227 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
16e5b7c to
b327835
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…hat drifted; the baseline moved, not the site CI failed the survey's own replay with `changed-set moved: 53 vs ledger 49`. That is the gate working: all three controls still died to their recorded killers, both the EQUIVALENT and DEAD entries reproduced, and exactly one entry disagreed. CAUSE, measured rather than guessed. The site did not move. The BASELINE did: 13 of 609 corpus entries differ on the merged tree after #1232 and #1227, so the mutation's blast radius moved with them. Byte triage re-derived on the merged tree gives 53, and CI and a local run agree at 53 independently. Applied to both the mutant record and the ci_subset entry. `reanchor` separately relocated all 15 line-shifted sites by their stored `before` TEXT (0 not found), so last commit's "R5 mutants are not replayable" is obsolete and the report section is rewritten with the measured figures. NOT RE-VERIFIED, AND THE REPORT SAYS SO: that mutant's CLASSIFICATION was measured at `meta.commit` and is not re-established here. The CI replay checks byte triage for UNTESTED entries, not the oracle suite (that needs --full). The published rate stays relative to the commit the ledger names. A LOCAL FULL RE-SURVEY WAS ATTEMPTED AND DISCARDED AS INVALID, recorded because the near-miss matters: 147 of the 196 L1 steps invoke bare `python`, absent on this machine, so they exit 127 in 0.1s and the harness scores that as a KILL; and `fact_spec_div_494_differential.py` is red on the UNMUTATED tree here while green in CI. That run reported 0 of 29 surviving — dead oracles, not a result. The harness's own control check refused it: "1 control(s) not killed — the survey measures nothing; do not publish the rate". A gate that stops its author publishing a flattering number is the one worth having. Refs #1189, #1232, #1227 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Coordinator verification — the published 19 % is sound; my attempted re-survey was the broken oneI tried to re-measure this survey on the merged tree and got 0 of 29 surviving. That number is an Why my re-run was invalid — two independent environmental faults on this machine:
Together those made one cheap early step appear to kill every mutant — including all three controls. What stopped it was this survey's own control check, not my judgement: Requiring a control to die to its specific recorded oracle is what separates "every mutant was And the ledger you shipped is verifiably NOT affected. Its recorded killers are varied and So the only change made here is the one that is environment-independent: Follow-up worth having (not filed as a blocker): the harness cannot distinguish "the oracle failed Refs #1189 |
RQ-65-MUTANTS (#1189) — how much of the code generator is correct by accident, measured by mutation
The number: of the sampled emitted-code decisions that change bytes when flipped, 4 of 21 survived the named suite — 19 % survival, an upper bound under the full CI board (a broader suite cannot un-kill a mutant). 17 of 21 were killed: 6 by execution differentials, 8 by unit/integration tests, 2 only by frozen-byte goldens, 1 by the compiler hanging. Every survivor is enumerated with its exact diff in
docs/status/MUTATION_SURVEY.md; that list is what v0.66 is scoped from.Red-first controls — all KILLED (run before the sample, same pipeline)
if !live_home {→if true {, the pre-fix behaviour byte for byte)join-alias-1189-oracle, 18 srule_i32_selectoperands swapped (PR #1216's plant)cmp-select-oracle, 18 sbase_cse_flip_468/const_cse_reduction_242byte goldens after 274 s ofcargo test— no execution oracle in the suite boots the shippedReset_HandlerA control that does not kill exits the harness with 1 and publishes no rate.
Frame, sample, suite — stated, not invented
has_value_carrying_branch+compile_wasm_to_arm), R2ir_to_arm_impl, R3select_with_stack, R4 the shared post-merge tail (measured: the two selectors share no lowering code insidesynth-synthesis; what they share isfinish_allocated_stream/branch resolution/validation, theliveness.rspasses and the encoder'si64_effective_base), R5generate_minimal_startup. 1,538 candidate sites.Reg::Rn→R((n+1) mod 13);Condition::X→inverse; apush(… ArmOp::Mov …)deleted; immediates+1/+4, startup register numbers+1;if COND {→if !(COND) {and negated||/&&continuation terms;<↔<=,>↔>=,.len() - 1→.len(). Seeded (1189), stratified per region and operator, round-robin so any prefix is balanced.--relocatable, self-contained, self-contained--no-optimize; 600 pairs hashed on.text/.data/.rodata+ symtab). Target was 8 compiled per region; the run stopped at draw 38 when that mutant hung the compiler — R2 reached 6 compiled, R4 3 (four of R4's draws were uncompilable). Said plainly in the report.ci.ymlby the harness (each selected job's ownrun:steps executed verbatim, binary path substituted): L1 =wast-conformance-oracle,repro-sweep-arm-corpus-oracle,join-alias-1189-oracle,cmp-select-oracle,frame-slot-dce-242-oracle(12 steps, 23 s green on the unmutated binary, 0 red on baseline); L2 = thetestjob's cargo commands (274 s green). Left out with the measured reason:selector-parity-oracle— the coordinator's first suggestion and the strongest selector kill signal — takes >20 min per run here the CI way (single-threaded underoracle_run.py); the other 28 ARM oracle jobs are affordable singly, not per mutant; RV32/AArch64/Mach-O jobs are other crates;instrument-independencerebuilds the tree;claim-check/rivetare documentation gates. All recorded in the ledger'ssuite.unselected_jobs/suite.excluded_jobs.{ eprintln!(MARK); token }) tells EQUIVALENT (evaluated, byte-neutral) from DEAD (never evaluated on the corpus — a deletion candidate, not a proof).Survivors, classified
ir_to_armepilogueMovdropped (8 objects); the direct selector'scmn-residualI32Eq→NE(2 objects — the gap #1216 predicted); R2 removed from the AAPCS dead-at-return set inliveness.rs(49 objects, conservative but unnoticed); the startup ROM→RAM data-copy count register moved (46 objects;self_contained_data_758, outside the subset, would see it)arm_backend.rs— unreached on the corpus; deletion candidates for the subtraction ratchetconstarray (no runtime probe); a<inside a multi-line format string (not a decision site — a sampling defect, recorded)Per region: R1 0/2, R2 1/5, R3 1/6, R4 1/1, R5 1/7. All six R5 kills fell only to the structural layer.
The CI pin — the instrument's discrimination, not a re-run
mutation-survey-discriminationjob:mutation_survey.py cireplays the ledger'sci_subset— the 3 controls (their recorded killer step must go red) + 2 UNTESTED + 1 EQUIVALENT + 1 DEAD (their byte-triage set / reach verdict must reproduce) — and grepsMUTANTS-CI subset=N controls=N non-killed=N failures=0. Verified locally on the rebased tree:subset=7 controls=3 non-killed=4 failures=0. It replays only already-declaredscripts/reprooracles: no new# ci-checks:declaration, exact floor (re-derived: 345122) untouched.claims.yamlSYNTH-MUTATION-SURVEY-RQ65: the eighthkind: ratchet—mutants_untested(value 4, baseline 4, must FALL) overdocs/status/mutation_survey.json; count-eq pins on 38 / 21 / 4,control_ok×3 and theci_subsetsides (3 + 4);SYNTH-SUBTRACTION-PINS-DECLAREDmoved 7→8 / ceilings 4→5 with CLAUDE.md saying "Eight".artifacts/status.jsonregenerated.Harness defects found and fixed while surveying (recorded in the report)
"mentions
scripts/repro/" as the job-selection rule admittedcoverage(acargo llvm-covof the workspace),claim-checkandrivet→ replaced by "invokes an oracle"; a 300 s per-compile timeout with no short-circuit let one hanging mutant burn >20 min and crash the run → 60 s and stop-at-first-hang, hangs classified KILLED/timeout; a killed run left a mutated selector on disk → SIGTERM now unwinds the restore; cargo'sRunning …/deps/line shape was missed → structural kills re-attributed from the recorded test names (which is how the two freeze-only kills became visible).Gates on this tree
cargo fmt --check0 ·cargo clippy --workspace --all-targets -- -D warnings0 ·status_evidence_check.py0 (floor restated 0 times) ·claim_check.py claims.yaml64/64,mutants_untested 4/4 must FALL·oracle_wiring_check.py --exact-emulation-floor 3451220 ·mutation_survey.py cifailures=0 ·rivet validate: exit 1 with 40 pre-existingsigil:*cross-repo dangling refs, 0 naming this artifact (the federated externals are not resolvable locally).Refs #1189, #197, #1216
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L