Repository navigation
S2.6.4 — The two-surface mutation recorder #321
Description
Parent: #293
Build target 5. Anchors: CLAIM-293.8. Cases: 4, 10.
Blocked by: #318
There are TWO mutation surfaces, and an argv-only recorder covers half the skills
| Surface | Who mutates it | Detected by |
|---|---|---|
| the forge | all four skills — milestone, issues, sentinel comments | mutating-argv classifier |
| the git worktree | story-design writes a Design; story-test-plan writes a plan; both commit |
worktree tree hash |
An argv-only recorder scores a Design rewritten with identical bytes as zero mutations. That is wrong twice: it is an action that can race, and it means the "zero mutations" denominator quietly covers two of four skills.
The verdict definition
(mutating-argv set is empty) AND (tree hash unchanged) AND (no write syscall to a tracked path occurred) — the third clause because writing identical bytes leaves the hash equal while still having been a write.
The semantic model already exists in the tree
GhPlan = readonly Argv[] treats an empty command list as a successful outcome, and planLabelTransition already returns "nothing to do" as success rather than as a failed create. That is the zero-mutation result modelled as a first-class value — mirror it rather than inventing a second vocabulary.
Neither surface may be silently unwired
Mutation N2's shape: "skills" was left in SCOPE_DIRS but never walked, and four of five assertions still passed. Only a run-time equality between the printed denominator and the files on disk caught it.
So each surface needs at least one scenario that trips ONLY it — a comment-only scenario and a file-only scenario.
Done when
- Argv mutations and worktree tree-hash changes recorded and reported as two separate counts (case 4)
- An identical-bytes rewrite is reported as a mutation (case 10)
- Each surface has a scenario tripping only it, so neither can be silently unwired
- At least one scenario per file-writing skill satisfies
run1.tree_hash != pre.tree_hash— proving the worktree surface was armed - Attempted mutating argv is recorded, not only successful — required by S2.6.3 — The stateful fake forge: able to lie, and non-enforcing #320's non-enforcing fake
Refs #293
Activity
dev-pmallapp commentedon Sep 9, 2026
iai-evidence
Verdict: PASS. Artifact:
docs/evidence/321-20260909T125257Z.md, pinned atdc5e8ee4fe60cca36654fed19de4a20b775c7ac5. PR #332.Build target 5 of
docs/design/stories/293.md. Case 4 PASS, case 10 PASS.CLAIM-293.8is met for the recorder; the per-skill half of its threshold closes at #323.Chain at
761bb1d987 tests / 973 pass / 0 fail / 14 skip / 3513
expect()· lint 0 across 109 files · typecheck clean · skill-lint 4 + 12, 0 errors · claim-lint 117 files, 0 errors, 1 warning (the expecteddocs/design/stories/9.mdone). CI on PR #332 is 7/7.The six-scenario matrix
attemptedMutationswritesgh issue commentidentical: falseidentical: truegit commit --allow-emptyidentical: trueThe zeros are asserted, not implied. The non-zeros prove each surface is wired; only the zeros prove the two are independent. One counter fed by both inputs passes every non-zero assertion and reports "two surfaces" while having one — mutation N2's shape.
N is where this task's non-vacuity comes from, not from an exit code: there is no runner, and #322 owns it. E is why there are three worktree observables —
git commit --allow-emptyafter an identical rewrite moves HEAD while leavingHEAD^{tree}byte-identical.Mutation testing — 14 injected, 12 killed, 2 survivors by design
attemptedMutationsforced emptyidenticalcomputed asexistedBeforetoEqualweakened to a length checkaftersampled atbegin()after.headfrozen tobefore.headpackages/harness/src&& exitCode === 0## Re-entrytable reorderedM3 and M4 kill disjoint halves. A suite carrying only the differing-bytes scenarios ships M3 green — which is exactly the defect case 10 exists to prevent.
M5/M6 is #320's M9/M9b one layer up. The weakening survives alone; the paired defect is killed by a different test, over a deliberately mixed argv roster. A weakened assertion is not detectable by the test it weakens — third occurrence of that class, after #319's M3 and #320's M9.
M12's asymmetry. An exit-code filter is killed only by a lie that refuses. The
silent-no-oplie exits 0, so the forge said yes and did nothing, and the filter survives it. The property "records the attempt, not the success" is observable only through a refusal — #320's finding 1, measured from the recorder's side.S2 and M12 are the same property from opposite sides. The recorder must not care what the double decided, and must not be able to:
attemptedMutationsis typedGhPlan, an argv list with no verdict field.Findings raised
git status,git hash-object, a--statdiff and an mtime comparison all sample effect, and the defining property of an identical-bytes rewrite is that it leaves none.packages/harness/src, so S2.6.5 — The anti-vacuity assertion set, asserted by the runner #322's and S2.6.6 — The seam rung and its CI job #323's modules join the population automatically.story-designas zero mutations — the argv-only defect reproduced inside its own fix.What this does NOT establish
CLAIM-293.8. "At least one scenario per file-writing skill" needs a transcription and a runner. S2.6.6 — The seam rung and its CI job #323 must not read this as having closed it.## Re-entrytable changes not one assertion in 987 tests. That isNEVER-293.5working, and it must not be "fixed".bun run skill-harnessstill exists in nopackage.json. The anti-vacuity set asserted by the runner is S2.6.5 — The anti-vacuity assertion set, asserted by the runner #322.ghcall here was answered from S2.6.3 — The stateful fake forge: able to lie, and non-enforcing #320's fake. Noghargv this repository builds has ever been executed, and no build target owns paying that bill #330 still has no owner in build targets 1–10.shouldRetryResponseanswersretry: falsefor the transient connection error the reference tells you to retry #331 is untouched. The transient mode was injected to prove a failed attempt is still recorded; no retry logic was built on it.dev-pmallapp commentedon Sep 10, 2026
Closed on merge. PR #332 merged into
story/293-s2-6-skill-execution-harnessat27ac716c81309e8f7e57042a94f247a4fe22462b, merged by a human — iAI never merges (CONTRIBUTING.md:416).Verdict stands at PASS for cases 4 and 10.
CLAIM-293.8is met for the recorder only — its per-skill threshold, at least one scenario per file-writing skill withrun1.tree_hash != pre.tree_hash, needs a transcription and a runner and closes at #323, which must not read this as having closed it. Evidence:docs/evidence/321-20260909T125257Z.md.Carried forward to #322: there is still no runner (
bun run skill-harnessexists in nopackage.json), the anti-vacuity must be asserted by the runner's exit code and not only by a test file, and the sanctioned-writer list is a flat read ofpackages/harness/src— so #322's new modules join that population automatically and must go throughrecorder.writeFileor add themselves to the list in review. Still unpaid: #330. Still untouched: #331.