From 50e18aecac2ee233cc6f2af12cf4cc760a31c75d Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 14:52:12 +0000 Subject: [PATCH 1/2] fix(gate): a mutation declaration that cannot discriminate is not coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `mise run mutant` on a tree carrying CLOUD-944's stdin fix reports 223 declarations, not the 204 CLOUD-480's acceptance was recorded against — 19 had never been applied, because the row loop is fed by `done <<<"$rows"` and the `bats` calls inside it were reading those rows off stdin. Three of the 19 were defective, so the published "every one caught" was green over a set that excluded exactly the broken rows. `ci-tools-check/spawned-tool-need-not-be-installed` named no case at all. Its filter was written from memory as "a tool a batten.toml row spawns must be installed in CI"; the case is "a tool a policy row spawns must be in the install list". Repointed at the case that actually discriminates — the one asserting `status -eq 1` on the defect — since the similarly-named case above it asserts `-eq 0` in both arms and would pass under the mutation either way. `filed-here-check`'s two overlap rows are removed rather than repaired, because the property they name is over-determined and no one-line mutation can falsify it. Measured by disarming each guard in turn: `named=${overlap#*,}` returns the whole token when there is no comma, so `named` is the literal `0` or `-`, no changed path equals either, and `paths` comes out empty — the emptiness guard stops the row, and with that disarmed too the report loop has nothing to iterate. Three independent structures protect one property. The measurement is recorded beside the arms so the rows are not re-added; a declaration there reports as a SURVIVED defect in the SUITE, which is the wrong subject. Refs: CLOUD-480, CLOUD-944, CLOUD-941 --- mise-tasks/ci-tools-check.sh | 2 +- mise-tasks/filed-here-check.sh | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/mise-tasks/ci-tools-check.sh b/mise-tasks/ci-tools-check.sh index b3afac2f9..4fea0b3ad 100755 --- a/mise-tasks/ci-tools-check.sh +++ b/mise-tasks/ci-tools-check.sh @@ -150,7 +150,7 @@ done <<<"$requested" # one is invisible to it — and `shfmt` reindents any comment inside a block, so # a column-0 comment beside those arms cannot survive the formatter either. The # slugs name which arm each one reverts. -#MUTANT spawned-tool-need-not-be-installed|s@^ if ! grep -qxF "\$tool" <<<"\$installed"; then$@ if false; then@|a tool a batten.toml row spawns must be installed in CI +#MUTANT spawned-tool-need-not-be-installed|s@^ if ! grep -qxF "\$tool" <<<"\$installed"; then$@ if false; then@|no list installs is refused #MUTANT pr-workflow-may-omit-install-args|s@^\t\tif \[\[ "\$lists" -ne "\$uses" \]\]; then$@\t\tif false; then@|with no install_args fails #MUTANT pr-workflow-list-may-be-nonbinding|s@^\t\tif ! grep -qE "\$binding_task".*@\t\tif false; then@|without the auto-install variables fails if [[ -d "$WORKFLOW_DIR" ]]; then diff --git a/mise-tasks/filed-here-check.sh b/mise-tasks/filed-here-check.sh index a491b65fd..6c2e14a01 100755 --- a/mise-tasks/filed-here-check.sh +++ b/mise-tasks/filed-here-check.sh @@ -111,14 +111,29 @@ # the entire second refusal: spinning a defect out of code you are holding open # goes back to being free. #MUTANT overlap-passes|s/^\t\treport "\$id filed-over-own-diff.*$/\t\t:/|a row naming a file this branch is changing stops the lap -# The mutation reads a zero count as an overlap, so a row that names only files -# this branch never touched is refused — the false positive that would make the -# gate unusable and get it switched off. -#MUTANT zero-overlap-refused|s/^\t0) continue ;;$/\t0) ;;/|a row naming only untouched files passes -# The mutation reads "could not look" as a refusal, so a branch with no -# `origin/main` to diff against — a fresh clone, a detached recorder — is stopped -# over the environment rather than over the row. -#MUTANT overlap-unanswered-refused|s/^\t-) continue ;;$/\t-) ;;/|a row the recorder could not measure passes +# THE `0` AND `-` ARMS CARRY NO `#MUTANT` ROW, AND THAT IS MEASURED RATHER THAN +# AN OMISSION (CLOUD-480). Two rows used to sit here — `zero-overlap-refused` and +# `overlap-unanswered-refused`, each disarming one arm's `continue` — and both +# SURVIVED every run while reading as coverage for the pass-side properties they +# named. They cannot do otherwise: the property is over-determined, so no +# one-line mutation can falsify it. +# +# Measured by disarming the arms and then disarming the next guard too: +# +# overlap=0 -> named=0 -> paths='' stopped by the `[[ -n "$paths" ]]` guard +# overlap=0 -> named=0 -> paths='' guard disarmed: `for path in $paths` never runs +# overlap=- -> named=- -> paths='' same both ways +# +# `named=${overlap#*,}` returns the WHOLE token when there is no comma, so `named` +# is the literal `0` or `-`; no changed path equals either, so the intersection is +# empty and the report loop has nothing to iterate. Three independent structures +# protect the same property — this arm, the emptiness guard, and the loop itself. +# +# So the honest state is no declaration, per this row's own posture: a gate whose +# suite cannot be made to discriminate is a finding about that suite, never a +# weakened mutant. Re-adding a row here would restore a claim of coverage that +# the harness reports as a SURVIVED defect in the SUITE, which is the wrong +# subject — CLOUD-941 spent a section misdiagnosing exactly that. # The override must stay OPT-IN and must stay RECORDED. Dropping the record turns # a visible decision into a silence, which is the state the override exists to # avoid. From 23d6ec389400ebf58d3341fcd21ab3a04c85311f Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 14:53:17 +0000 Subject: [PATCH 2/2] chore(fuzz): record the workspace version the release bumped to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `chore: release v0.0.108` moved the workspace version but left `fuzz/Cargo.lock` recording `batten 0.0.106`, so any cargo invocation under `fuzz/` regenerates it and every contributor's tree comes up dirty. This is that regeneration and nothing else — one version line, produced by the toolchain rather than typed. Carried here because it is drift on `main` that this branch tripped over, not because it belongs to the gate work: it rides its own commit so it is reviewable apart from CLOUD-480's. Refs: CLOUD-480 --- fuzz/Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 8080409c3..e2f8db199 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -108,7 +108,7 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "batten" -version = "0.0.106" +version = "0.0.108" dependencies = [ "anyhow", "clap",