Counting tests is not the same as checking them
Line coverage has the same problem. A line can be run by a test that never looks at what it returned. In most software that is a quality issue. In code that decides whether an agent may move ₹1,50,000, a check that is executed but never really tested is a check you are trusting on faith.The idea: break the code on purpose
Mutation testing turns the question around. A tool makes one small deliberate change to the source, such as turning<= into <, or forcing a condition to true, and runs the package’s own tests against it. If a test fails, the
change was caught. If every test still passes, it survived, and a survivor points at behaviour no test pins down.
The share caught is the mutation score.
What I did
The first run, on 2026-10-05 with Stryker, covered the five packages that guard a decision: the envelope verifier, approval, policy, the execution gateway and crypto. It is slow, because every change reruns the whole package suite, so it runs by hand rather than in CI. The first scores were humbling: 89.7% for the envelope verifier, 77.7% for approval, 50.3% for policy, 67.6% for the gateway and 65.4% for crypto. The survivors pointed at real gaps:- Approval scope. Only two of the comparators that bound what an approval covers, equal and less than or equal, were tested. A policy using any other one would have relied on untested code.
- Rule operators. What each operator does at its boundary, or with a value of the wrong type, was largely untested.
- The gateway. A configured lifetime limit or key revocation could be dropped on its way to the envelope verifier and nothing would fail.
- Session binding. No single field of the session a connector must match was tested on its own.
What it does not do
- A score is not a proof. It says the tests notice small changes of the kinds the tool makes. It says nothing about a design mistake, a missing check, or an attack that needs many changes at once.
- Not every survivor can be killed. Some changes are equivalent: removing a guard that a later check repeats changes nothing anyone can observe. Those are listed as such, not forced with artificial tests. Others are in advisory code, such as the warnings about overlapping rules, or in message wording.
- Only the package’s own tests count. Integration tests elsewhere cannot see the changes, so real protection is somewhat higher than the score, and the score cannot show it.
- It is not continuous. It runs by hand, so a later change can open a new gap until the next run.
- Five packages, not all of them. Storage, the runtime and the API were not part of this run.
- It is not an outside review. It was run as part of Parmana’s own development process. The full results, every gap
and what was and was not changed, are public so anyone can rerun it with
npm run mutation.