Repository navigation
Mutation gate: RemoveCheckAsync block-removal mutant is reported surviving in about half of the sweeps #312
Description
What happens
Stryker reports the block-removal mutant of EfBurstRepository.RemoveCheckAsync's body (Anichron.Core/Data/Repository/BurstRepository.cs, { → {}) as Survived in roughly half of the sweeps. In the other runs it reports it Killed by BurstRepositoryTests.RemoveCheckAsync_CheckedPhoto_DeletesItsRowAtTheNextSaveAsync. With the 100% gate, every sweep that loses this kill fails Mutation and therefore CI gate, whatever the PR changed.
The kill is real. With the body removed by hand, RemoveCheckAsync_CheckedPhoto_DeletesItsRowAtTheNextSaveAsync fails, because the row is still there. A "Survived" means the covering test passed, so the mutant was not active while it ran.
Evidence (2026-10-24)
Covering tests in every run: RemoveCheckAsync_UncheckedPhoto_ChangesNothingAsync and RemoveCheckAsync_CheckedPhoto_DeletesItsRowAtTheNextSaveAsync.
| Where | Code | Mutant reported surviving |
|---|---|---|
Local scoped sweeps (mutation-changed.sh), concurrency 4 |
master at bdb9e0d, unmodified |
2 of 3 runs |
| Local scoped sweeps, concurrency 4 | #308 branch | 5 of 9 runs |
CI Mutation (full sweep) |
#308 | 2 of 2 runs |
CI Mutation divergence, run 40077588216 |
#308 | killed at concurrency 4 (1676 detected); survived single-threaded (1675 detected) |
What this rules out:
- Concurrency is not the trigger. The mutant survived the single-threaded sweep, so this does not look like the concurrency under-reporting in ADR 0001, or not only that.
- feat(worker): Similar photos join bursts through OpenCV feature matching (#298) #308 did not introduce it. Master flips the same way. Master's earlier green sweeps (bdb9e0d, e19c04f, 8547e1d) were lucky draws.
- The test class's shape is not the cause. On feat(worker): Similar photos join bursts through OpenCV feature matching (#298) #308,
BurstRepositoryTestsgained two tests. Moving those tests into their own class, which leftBurstRepositoryTestsidentical to master's, still showed the mutant surviving in 2 of 3 runs.
It was first measured on 8547e1d (#297), where RemoveCheckAsync was added, so the flip may be as old as the method.
Not yet known
- Why this mutant's activation is lost. Candidates:
- The mutant is in an
asyncmethod whose only statement awaitsFindAsync. Stryker's MTP runner may read the active mutant on a path an async continuation does not see. - The test seeds through one
AnichronDbContextand asserts through another, built byAnichronDbContexts.InMemory(name)with the cached service provider. CLAUDE.md names EF's cached model as a source of mutants that flip between runs.
- The mutant is in an
- Whether other mutants flip the same way less often. The divergence job compares counts, and this one moves them by 1.
Workaround in use
#308 reran Mutation until the gate passed, with the maintainer's agreement, because the flip predates it.
Activity
bitbiter-dev commentedon Oct 25, 2026
More runs (2026-10-25), same mutant (
BurstRepository.cs:132after #298 moved it), same two covering tests:Mutation, run 40083774678--project Anichron.Core.csproj -m BurstRepository.csMutation — Anichron.Core(master code)CI now mutates per project (#311, ADR 0010), which changes host reuse and mutant order, but not the cause diagnosed on #305 (EF's process-wide query cache in a reused MTP host). Per-project runs can still flip until #305 lands. The weekly divergence check and the local full sweep still run in solution mode.
test: RemoveCheckAsync's killing test counts past the query filter (#312