Why
Coverage (#105, #106) measures execution, not verification: a line counts as covered even if no test asserts anything about it. Mutation testing measures the thing coverage proxies badly — whether the suite notices when the logic changes. cargo-mutants systematically flips operators (< → <=), deletes negations, replaces function bodies with defaults, and reports every mutant the test suite fails to kill. On a spec-exact parser with documented fail-closed behaviour, surviving mutants are usually real findings: either a missing assertion or (occasionally) code that provably doesn't matter.
This complements the existing strength layers (legacy edtf.js oracle, property tests, fuzz targets, D/N-decision-anchored assertions) rather than replacing them.
Approach
- Run
cargo mutants per crate, starting with the highest-value targets: edtf-core (parser.rs, bounds.rs, relation.rs) and edtf-normalize (engine.rs).
- Exclude
edtf-postgres (pgrx harness; same rationale as the coverage exclusion in ci.yml).
- Triage each surviving mutant: add the missing assertion, or document why the mutant is equivalent (no observable behaviour change). Equivalent mutants go in
.cargo/mutants.toml exclusions with a reason, mirroring the D/N-decision style.
- Runtime is the constraint (mutants × suite runtime). Options, in order of preference:
- Local/one-shot sprint first to burn down the backlog; decide on CI afterwards.
- If CI-worthy: run only on changed files per PR (
cargo mutants --in-diff), as a published-not-gated report like coverage — thresholds rot into gaming here too.
- Pin the tool via mise like everything else.
Exit criteria
- Zero unexplained surviving mutants in edtf-core and edtf-normalize.
- A documented decision on whether an
--in-diff CI job earns its runtime.
Why
Coverage (#105, #106) measures execution, not verification: a line counts as covered even if no test asserts anything about it. Mutation testing measures the thing coverage proxies badly — whether the suite notices when the logic changes.
cargo-mutantssystematically flips operators (<→<=), deletes negations, replaces function bodies with defaults, and reports every mutant the test suite fails to kill. On a spec-exact parser with documented fail-closed behaviour, surviving mutants are usually real findings: either a missing assertion or (occasionally) code that provably doesn't matter.This complements the existing strength layers (legacy edtf.js oracle, property tests, fuzz targets, D/N-decision-anchored assertions) rather than replacing them.
Approach
cargo mutantsper crate, starting with the highest-value targets:edtf-core(parser.rs, bounds.rs, relation.rs) andedtf-normalize(engine.rs).edtf-postgres(pgrx harness; same rationale as the coverage exclusion in ci.yml)..cargo/mutants.tomlexclusions with a reason, mirroring the D/N-decision style.cargo mutants --in-diff), as a published-not-gated report like coverage — thresholds rot into gaming here too.Exit criteria
--in-diffCI job earns its runtime.