Mutation Equivalent-In-Current-Architecture
Problem
Mutation testing surfaces surviving mutants. The default disposition is to write a killing test (close the gap). But some surviving mutants are structurally unreachable by any falsifiable test in the current code architecture — usually because they're on a guard or branch that's never the FIRST guard/branch to fire on the relevant input domain.
Naive response: write a test using a disjunction (assert reason in ("A", "B"))
that "passes" against both current code and the mutation. This is double-slop:
- The mutation isn't killed (the test passes under the mutation).
- The test itself is the canonical ai-slop "tautological assertion / disjunction that passes either way" pattern that mutation testing exists to surface.
Honest response: declare the mutant EQUIVALENT-IN-CURRENT-ARCHITECTURE, document the architectural constraint that blocks independent killability, and defer the architectural refactor (if desired) to a v-next punch list. The mutant remains uncovered in the report but the disposition is honest about why.
Context / Trigger Conditions
- A mutation-testing tool reports a surviving mutant.
- Initial reading of the mutant suggests it's a genuine coverage gap.
- On closer inspection, the mutated code path can only be reached via inputs that ALSO trigger an earlier guard which short-circuits before reaching the mutated line.
- The earlier guard ALSO has a mutation on it (or a separate test); the mutant in question can't be made independently observable.
Specific architectural shapes that produce this:
- Sequential guards over a shared computation. E.g.,
if α̂ <= 0: raise; if β̂ <= 0: raisewhere α̂ and β̂ are computed from the same input and share sign —β̂ <= 0 ⟺ α̂ <= 0, so the β̂ guard never fires alone. - Defensive fallbacks for an already-validated invariant. A
try/excepton an operation that the type system already excludes from failing. - Conditional branches over a single-valued enum. A branch that's technically reachable but only via a state that's never written.
- Logging/observability code paths. Mutations on log messages that don't affect behavior (most mutation tools should be configured to skip these).
Solution
Step 1 · Verify the mutant cannot be killed independently
Manually apply the mutation. Run the FULL test suite. Confirm zero tests fail.
Then attempt to construct a killing test:
- Identify the smallest input that reaches the mutated line via the SECOND guard without firing the FIRST guard.
- If such an input is constructable, the mutant IS a genuine gap — write the test. Done.
- If such an input is NOT constructable due to a mathematical / type-system / state-machine constraint, the mutant is EQUIVALENT-IN-CURRENT-ARCHITECTURE.
Step 2 · Document the architectural constraint
In the mutation-testing fix-brief, write the re-classification with:
M<N> · RE-CLASSIFIED · EQUIVALENT-IN-CURRENT-ARCHITECTURE
- Mutation: <verbatim diff>
- Why equivalent: <the mathematical / structural argument that proves
independent killability is impossible>
- Proof: <minimal demonstration — e.g., "in _ebmom: α̂ = m·common,
β̂ = (1-m)·common with m ∈ (0,1) → sign(α̂) = sign(β̂); so the α̂
guard always fires first when the β̂ guard would fire">
- Architectural fix (v-next): <how to make the mutant independently
observable — usually involves splitting the guards into independent
functions>
- Deferred to: <v0.2 punch list / Phase 3.x-bis / etc.>
Step 3 · Do NOT ship a fake killing test
Reject the temptation to write:
def test_beta_guard():
...
with pytest.raises(ConvergenceFailure) as exc:
_ebmom(...)
assert exc.value.reason in ("alpha_le_zero", "beta_le_zero") # SLOP
This passes under both current code (raises alpha_le_zero) and the mutation
(still raises alpha_le_zero, because the α̂ guard is upstream). The
disjunction is a tautology. Per any mutation-testing rubric's own discipline:
tests must be falsifiable against the specific mutation.
Step 4 · Record the deferral in the project's carry-forward list
The architectural refactor (split guards into independent functions) is real v-next work, not a phantom. The fix-brief should add it to the carry-forward punch list with the explicit rationale: "M mutant becomes independently killable after this refactor."
Verification
A correctly-classified EQUIVALENT-IN-CURRENT-ARCHITECTURE mutant satisfies:
- The mutation is applied; ALL tests pass (confirms surviving).
- No test can be written that passes against current code AND fails against the mutation (the falsifiability test for the test itself).
- The architectural argument is provable, not hand-waving. ("They share sign because alpha = m·X, beta = (1-m)·X" is provable. "The β̂ branch isn't really used" is hand-waving.)
- A documented refactor would make the mutant independently killable.
If any of these fail, the mutant is NOT equivalent — it's just a gap you didn't find the right test for. Try harder before reclassifying.
Example
From the Skill Harness Phase 3.3 fix-loop (2026-06-07):
Mutation fit.py:314 beta_hat <= 0.0 → beta_hat < 0.0 survived.
Initial reading: "convergence guard boundary; symmetric to mut_83 (alpha_hat
boundary); write a parametrized test for β̂ ∈ {-0.001, 0.0, 0.001}."
The fix-loop agent's draft test asserted:
assert exc.value.reason in ("alpha_le_zero", "beta_le_zero")
The agent's self-review caught the disjunction:
- Test passes against current code: GREEN (raises with
reason="alpha_le_zero") - Test passes against the mutation: GREEN (still raises with
reason="alpha_le_zero"because α̂ guard at fit.py:306 fires first) - The β̂ guard at fit.py:314 was NEVER the firing guard for this input.
The agent ran the mutation manually with a non-disjunction test (assert reason == "beta_le_zero") to verify. It went RED against current code too — proving
the β̂ guard cannot fire independently.
Mathematical proof of equivalence: in _ebmom,
m = sample_mean ∈ (0, 1)common = m·(1-m)/v − 1α̂ = m·commonβ̂ = (1-m)·common
Since m ∈ (0, 1): m > 0 and (1-m) > 0. Therefore
sign(α̂) = sign(β̂) = sign(common). So β̂ ≤ 0 ⟺ α̂ ≤ 0, and the α̂ guard
at fit.py:306 ALWAYS fires before the β̂ guard at fit.py:314 can be reached.
Disposition: M5 RE-CLASSIFIED as EQUIVALENT-IN-CURRENT-ARCHITECTURE.
Architectural fix deferred: split _validate_alpha(α̂) and _validate_beta(β̂)
into independent module-level functions; tests can then call each directly with
arbitrary inputs. Punch-list entry: Phase 3.3-bis or v0.2.
The fix-loop's commit body documented the re-classification verbatim instead of shipping the disjunction test. Result: 13 GREEN + 1 honest EQUIVALENT vs the alternative 14 GREEN with 1 silent slop test.
Notes
- Equivalent-in-architecture is NOT the same as a tool's built-in "equivalent
mutant" detection. Tool detection catches syntactic equivalents (e.g.,
range(n)vsrange(0, n)). Architectural equivalence is SEMANTIC and requires human/agent reasoning about the surrounding control flow. - The architectural refactor that makes the mutant independently killable is usually beneficial in its own right (it improves separation of concerns). Treat the punch-list entry as a v-next improvement, not a workaround.
- Mutation testing tools should NOT auto-classify these — the architectural argument requires lens-specific reasoning the tool can't do.
- Self-review by the agent producing the fix-loop is the most likely place to
catch the failed-to-kill case. The discipline pairs well with
ai-slop-sentinelinvoked against your OWN diff before commit (look for disjunction assertions that pass either way). - Honesty discipline: shipping
M<N> · GREENwhen the test is a disjunction is misleading. ShippingM<N> · RE-CLASSIFIEDwith the architectural argument is honest. The mutation-test kill-rate number takes a hit (denominator stays, numerator decreases by 1), but the kill-rate is more trustworthy.
See also
mutation-testing:mutation-testing— the tool-running skill this discipline augmentsai-slop-sentinel— the slop-detection skill that catches the disjunction tautology pattern this skill helps avoid in advancebayesian-eval-discipline— context for understanding sequential-guard patterns in statistical code (where this skill most often applies)