test(mutants): repair M21, add X3 [mutation-ok], and the round's report

The `if False:` in this diff is a mutation DEFINITION -- a string in
`MUTANTS`, applied only to a throwaway copy of the tree inside the
harness and restored in a `finally`. No branch in this repository is
pinned by it.

The harness found this round's own change: its first run after H1 gave
`killed 34 of 35` and `ERROR: M21 ... pattern occurs 0 times -- NOT
MEASURED`, because H1 rewrote the `clean` property M21 mutates. M21 is
repaired against the new text, and X3 -- "a document refused whole is
clean again" -- is added beside it, because M21 now removes
`unverified`, `invalid` AND `refused` at once and would be killed by
any one of the three. Final run: killed 36 of 36, 0 survived, 0
errors, exit 0.

The round's report is
`docs/2026-09-19-regnskapsgaten-rest-og-normaliseringsdoren.md`, with
the gate's whole output, the exposure census behind the normalisation
door and the limits of the round.

CHANGELOG: this round's entries are folded into the UNTAGGED `[0.10.1]`
section, whose date moves to 2026-09-19, rather than into a new version
number. `v0.10.1` is not tagged and the packaging gate requires the
head to equal the packaged version; which version this ships as is the
operator's and is asked in the closing block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 06:32:35 +02:00
commit 43331fc4d4
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
3 changed files with 253 additions and 34 deletions

View file

@ -179,8 +179,18 @@ MUTANTS: tuple[Mutant, ...] = (
Mutant(
"M21 an unverified booking is clean",
GATE,
"return not (self.unaccounted or self.double or self.unverified or self.invalid)",
"return not (self.unaccounted or self.double)",
" self.unaccounted or self.double or self.unverified "
"or self.invalid or self.refused",
" self.unaccounted or self.double",
),
# H1's own column, mutated on its own: M21 above removes `unverified`,
# `invalid` AND `refused` at once, so it would be killed by any one of the
# three. This one takes only the fifth.
Mutant(
"X3 a document refused whole is clean again",
GATE,
' if status == "rejected" and not persisted and total > 0:',
" if False:",
),
Mutant(
"M22 any asset file at all proves a carry",