Skip to content

tools(mutation-teeth): a misnamed tooth says so, and the ledger places the seven rules no row claimed - #73

Merged
wefio merged 2 commits into
mainfrom
chore/mutation-register-tail
Sep 25, 2026
Merged

wefio merged 2 commits into
mainfrom
chore/mutation-register-tail

Conversation

@wefio

@wefio wefio commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Closes the two tails the derived-mutants arc left open, from main (818228ae).

1. tools(mutation-teeth): an expect that names no case says so

A tooth names the case that must fail, and the sweep filters a suite by that name so one tooth costs one case instead of a whole suite. When the name matches no case the filtered run passes with nothing in it, and that pass was reported as a surviving mutant - which is how two teeth were read as broken while they were only misnamed.

  • ranACase (tools/mutation-anchor.ts, pure, no filesystem) reads the run's own report: a marked line that is not the suite file is a case; a suite line alone means the filter matched nothing; an unrecognized reporter answers unknown, so a tooth is never accused on evidence the harness cannot parse. The suite file's own line is what makes the answer possible - it runs whatever the filter did.
  • The runner asks before it believes a pass, reports the tooth as misnamed with the name it wanted, counts it as a problem (non-zero exit), and skips the whole-suite fallback for it: the defect is the name, not the coverage.
  • tests/tools/mutation-anchor.test.ts gained the case covering the file line alone, a matched case, a failed case, two suites where nothing matched, and the unknown reporter (35 cases in the file now).
  • The record's own failure-mode note moves from "open" to answered, in the same commit.

2. docs(ledger): the seven rules no row claimed get one home each

The retirement pass left seven teeth whose rules no row named. Each rule was recovered from the register before the retirement, each replacement case was located and its assertion read, and the answer is not seven new rows:

tooth its rule's home
round-publication-opens-its-own-transaction B3 - the case is in B3's suite and asserts the transition's rollback for the publication half
round-never-releases-its-pin B4 - the other side of retention: the pins go with the values they protected
ordered-mode-becomes-any-topological-order A6, a new row - the design's sentence about publication order had no row (A: 5 -> 6 rows)
the-loop-awaits-each-unit-instead-of-the-batch, a-unit-is-dispatched-twice-in-one-batch F2b-slot - both share one case, now named there
a-unit-nothing-checks-is-still-a-unit, the-parent-check-ignores-its-own-verdict F2c - the two cases now named there

The readings line no longer calls them orphans, and the record no longer leaves the question open: what remains open is only whether "a check but no row" is a defect at all - it was one here, but the defect was in the ledger, not in the register.

Verification

  • whole-register sweep, with the misnamed check live: mutants: 110 of 110 caught by the named test, all 110 by the case each expect names, 22 of 22 targets restored byte-identically, exit 0 in 162 s - i.e. the check raises no false alarm anywhere in the register
  • a deliberately misnamed tooth reads misnamed (no case in this target's suites is named "..."), the file is restored byte-identically, exit non-zero
  • npm run verify:static exit 0; anchors: 110 of 110 resolve, over 22 targets; docs:check 304 files, 0 errors, 0 warnings; lint 0 findings; complexity gate ok
  • npm run test:product 1581 pass, 0 fail, exit 0 (1580 + the new case)
  • no product behaviour changes: a research tool, its tests, and the documents that describe them

…of surviving

A tooth names the case that must fail, and a sweep filters a suite by that name so one
tooth costs one case instead of a whole suite. When the name matches no case, the
filtered run passes with no case in it - and that pass was reported as a surviving
mutant. The two are different defects: one is a rule nothing checks, the other is
evidence whose name went stale. This is how two teeth were read as broken while they
were only misnamed, and the tool had no way to say which had happened.

- `ranACase` (tools/mutation-anchor.ts, pure, no filesystem) reads the run's own
  report: a marked line that is not the suite file is a case, a suite line alone means
  the filter matched nothing, and an unrecognized reporter answers unknown - a tooth is
  not accused on evidence the harness cannot parse. The suite file's own line is what
  makes the answer possible: it runs whatever the filter did.
- The runner asks it before it believes a pass, reports the tooth as `misnamed` with the
  name it wanted, and counts it as a problem (non-zero exit). The whole-suite fallback
  is not run for it: the defect is the name, not the coverage.
- `tests/tools/mutation-anchor.test.ts`: one case covering the file line alone, a matched
  case, a failed case, two suites where nothing matched, and the unknown reporter - 35
  cases in the file now.
- The record's own failure-mode note moves from "open" to answered, in the same commit.

Measured: the whole register 110 of 110 caught by the named test, all 110 by the case
each expect names, 22 of 22 targets restored byte-identically, exit 0 in 162 s - so the
new check raises no false alarm anywhere in the register. A deliberately misnamed tooth
reads `misnamed (no case in this target's suites is named "...")`, the file is restored
byte-identically, and the run exits non-zero.
The retirement pass left seven teeth whose rules no row named. The reading was "a
case but no row"; the rules themselves were recovered from the register before the
retirement, each replacement case was located and its assertion read, and the answer
is not seven new rows - most of these rules were already carried by a row that simply
did not name the case:

- `round-publication-opens-its-own-transaction` is B3's rule. Its replacement case
  lives in B3's own suite and asserts the transition's rollback for the publication
  half, saying in the assertion what the alternative would mean. Named in B3.
- `round-never-releases-its-pin` is the other side of B4's retention rule. The case
  asserts the pins held while a round runs, then cancels and asserts the retention
  list is empty, because both kinds of pin go with the values they protected. Added
  to B4.
- `ordered-mode-becomes-any-topological-order` had no row at all: the design's
  sentence about publication order had never been given one. It is A6 now, evidenced
  by the case that asserts the exact declared order, one ordered plan, and that the
  baseline never gains order freedom. Row counts: A 5 -> 6.
- The four judge-loop rules are F2b-slot's and F2c's: the batch rule and the
  duplicated dispatch share one case, `a unit's check is outstanding while an
  independent unit's worker runs`, and the unit with no checks and the parent check
  are the two cases F2c's row now names.

So the readings line no longer calls them orphans, and the record no longer leaves the
question open - what remains open is only whether "a check but no row" is a defect at
all, and here the defect was in the ledger rather than in the register. Both languages
in this commit.

Readings: `docs:check` 304 files, 0 errors, 0 warnings; `rtm:check` exit 0;
`test:product` 1581 pass, 0 fail, exit 0.
@wefio
wefio merged commit cc6087a into main Sep 25, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant