Why
A review round on 2026-08-20 across three open PRs found eleven mutations that delete a claimed fix while leaving the test suite green. Every one was a test that read as coverage and was not.
The distribution matters more than the individual cases:
- Several were in newly added code, i.e. the tests written alongside a fix did not pin that fix.
- Two were worse than absent coverage, because the test documents the behaviour it fails to pin — a future reader treats it as proof, and a reviewer treats it as reviewed.
- One survived removing the exact line the test is named after.
This is not a review-diligence problem. It survived a seven-agent review on the largest of the three PRs until mutation testing was enforced, and the same round produced several confidently-stated findings that were wrong for the same underlying reason: claims about what a test would do, asserted without running it.
Specifics per PR are in CPK-8037, kept there rather than here.
What to build
A CI check that, for source lines a PR touches, applies mechanical mutations and fails if the suite still passes.
Priority order, based on which classes actually caught real gaps in the review round:
- Predicate relaxation — comparison operators widened, a truthiness check substituted for a normalising one, a regex anchor dropped. Cheapest to implement, caught the most.
- Guard deletion — remove a newly added early-return or throw.
- Call-site un-wiring — where a diff extracts a helper and calls it, revert the call site while leaving the helper defined and exported. This is the class a human reviewer is least likely to imagine, and it produced a full-green suite twice in one day.
await removal on newly added awaits.
Scope to changed lines, not the tree, or the runtime is unbounded.
Known limit, worth designing around
Assertions made against strings that are never executed are the structural blind spot this cannot close. Where a query or template is built as text and the dependency is mocked, a substring assertion can be satisfied by an unrelated occurrence while a real semantic mutation passes through. Mutation testing catches "this test cannot fail"; it does not catch "this test asserts the wrong thing about text it never runs." Closing that needs execution against a real dependency, which is separate work.
Acceptance
- Runs on PRs touching
packages/outpost/** and apps/**
- The mutation classes above are detected as survivors when run against the relevant pre-fix commits (recorded in CPK-8037)
- Bounded enough to sit in the required check set rather than a nightly
@stryker-mutator/core supports vitest and is the obvious starting point, but spike the runtime first — packages/outpost alone is ~1090 tests and mutation runs multiply that.
Why
A review round on 2026-08-20 across three open PRs found eleven mutations that delete a claimed fix while leaving the test suite green. Every one was a test that read as coverage and was not.
The distribution matters more than the individual cases:
This is not a review-diligence problem. It survived a seven-agent review on the largest of the three PRs until mutation testing was enforced, and the same round produced several confidently-stated findings that were wrong for the same underlying reason: claims about what a test would do, asserted without running it.
Specifics per PR are in CPK-8037, kept there rather than here.
What to build
A CI check that, for source lines a PR touches, applies mechanical mutations and fails if the suite still passes.
Priority order, based on which classes actually caught real gaps in the review round:
awaitremoval on newly added awaits.Scope to changed lines, not the tree, or the runtime is unbounded.
Known limit, worth designing around
Assertions made against strings that are never executed are the structural blind spot this cannot close. Where a query or template is built as text and the dependency is mocked, a substring assertion can be satisfied by an unrelated occurrence while a real semantic mutation passes through. Mutation testing catches "this test cannot fail"; it does not catch "this test asserts the wrong thing about text it never runs." Closing that needs execution against a real dependency, which is separate work.
Acceptance
packages/outpost/**andapps/**@stryker-mutator/coresupports vitest and is the obvious starting point, but spike the runtime first —packages/outpostalone is ~1090 tests and mutation runs multiply that.