Have Harbour's tests taken on a life of their own?
Partly, but not the way the 90% guess supposes. Harbour's unit suite is not a thick blanket in which most tests repeat each other. It is a thin, wide net. Most of the faults it catches are caught by one test file only, usually the file named after the module. The smallest set of files that keeps the suite's protection is therefore large: somewhere between a third and three fifths of the 804 files, depending on the measure, and never fewer than 145. The best tenth of the files, chosen with hindsight, keeps under half of the protection on faults it was not chosen for. A set chosen on mutants also drops half of the real catches on record, because the real catches lean on guards that mutants rarely touch.
So John's 90% does not hold. The surplus is real, but it is concentrated:
- Ticket-named files (
tests/unit/lin-NNNN-*, 180 of them) are 22% of the files and 27% of per-file run time. They are the only catcher of a sampled fault 7% of the time, against 17% for other files. - Census and count pins catch almost none of the sampled faults. The 16 census-named files catch one between them, never alone, and they are the files the record shows being bumped.
- 377 files (47%) caught none of the 599 sampled faults. Most of them guard code the sample did not hit, but 200 of them also execute no production line that another file does not.
Most of the cost is not surplus tests but the habit around them. Product PRs now bring about twice their own lines in tests, two thirds of them must edit existing tests, and the unit job is CI's slowest job and runs the suite twice. On the other side of the ledger:
- In CI: since 1 June the unit tests caught no real product fault, because agents run them before they push.
- Inside sessions: a hand reading of every candidate found nine real catches in six weeks. Four were load errors that any test importing the module would have caught.
- The larger gap runs the other way: about half of the sampled one-line faults survive the whole suite. Thin protection is a bigger problem than surplus.
Findings
The estate at HEAD, against test-estate's baseline
test-estate, 30 Sep (c65b7dd8) |
HEAD, 12 Oct (ebf93bd9) |
|
|---|---|---|
| unit test files | 592 | 803, plus one eval test test:unit runs |
| unit test lines | 230,632 | 286,764 |
| unit tests | 12,451 | 15,322 |
| behavioural share | 93% | 92% (14,148, plus 136 unclassed) |
| pin-class tests (text pin, census, source scan) | 851 | 1,038 (538, 206, 294) |
| ticket-named files | 97 | 175 (180 counting lin without the hyphen) |
| whole unit suite, in parallel, operator's machine | 45 s | 33 s |
Test-estate's classifier (scripts/survey-tests-shape.mjs) was re-run unchanged. The suite
grew by a quarter in two weeks, yet it runs faster in parallel than on 30 September. The
reason is LIN-3158, which cleared the 25-second timer in routes/proxy.js that held four
files open (routes/proxy.js:397). The estate is still overwhelmingly behavioural. Its growth
since 30 September is behavioural tests, many of them LIN-3449's swap tests written into new
ticket-named files. Two commits have landed since ebf93bd9: LIN-3550 (#1887) adds one test file,
scripts/code-shape/seams.test.mjs, and edits periodicals.test.js, lib/periodicals.js and
routes/proxy-compute.js; LIN-3552 (#1888) is docs and evidence only. Neither changes a
finding.
How much of the suite is needed?
Most caught faults are caught by one file.
- Two uniform samples of 300 one-line mutants were run, A and B (Method). The suite catches 159 of A's 299 valid mutants and 145 of B's 300.
- Of the 304 caught, 178 (59%) are caught by exactly one test file. The median is one file, and nine in ten are caught by five files or fewer.
- Going down to single tests does not change this. Counting, in each killing file, only its deepest failing tests (so a describe block's own failure does not count beside the test inside it), the median caught mutant fails three tests, and 30% (91 of 304) fail exactly one.
- The redundancy that exists sits inside files, a few tests each re-checking the same thing. It is not spread across files.
A small set chosen on one sample does not carry over to the next. Greedy set cover, then pruning, finds a set of files that catches everything the whole suite catches in a sample. That set is then checked against the other sample:
| chosen on | files kept | share of 804 | catches of the other sample's caught mutants |
|---|---|---|---|
| A | 99 | 12% | 80 of 145 (55%) |
| B | 88 | 11% | 84 of 159 (53%) |
Keeping files in greedy order (chosen on A), protection on B rises slowly:
| files kept | 10 | 20 | 40 | 80 (10%) | 99 |
|---|---|---|---|---|---|
| catches, sample A (chosen on) | 30% | 45% | 63% | 88% | 100% |
| catches, sample B (held out) | 16% | 28% | 35% | 47% | 55% |
So the best 10% of files, picked with hindsight on 300 faults, keep 47% of the protection on 300 others. That is the direct test of the 90% hypothesis, and it fails. A random set is far worse: 145 randomly chosen files catch a median of 31% of the pooled kills.
The smallest sufficient set is a lower bound, and it keeps growing. On random subsamples of the 304 caught mutants, the cover needs:
| caught mutants in the subsample | 25 | 100 | 200 | 304 |
|---|---|---|---|---|
| files in the cover | 21 | 67 | 113 | 145 |
At 304 mutants it is still adding about one file for every three faults, and there are 21,020 mutant sites. Three estimates bound what the whole population of faults would need:
- Files that are the only catcher of some fault. 68 files are sole killers in A, 66 in B, and 17 in both. Capture–recapture (Chapman) puts the population at about 256 files, with a 95% interval of roughly 170 to 340. Files that guard large modules are hit more often, which biases this estimate downward.
- Files that catch anything: 427 distinct files across the two samples, and still rising.
- Line coverage. Keeping every production line the suite executes needs 472 files (59%),
and 448 files each execute a line no other file does. Coverage overstates what is needed
where a file executes code without checking it. It understates it for browser code loaded
into a
vmsandbox, which coverage cannot see.
Read together, the protective core is between about 260 and 470 files, a third to three fifths of the suite. It is not a tenth.
simple-dispatcher shows the same shape, more sharply.
- Kills: its unit suite kills 44 of the 50 curated mutants that target its own code, and 35 of 42 fresh operator flips.
- Exact minimum: 13 files on the curated set, 15 on the fresh set, and 22 on both (14% of 153 files).
- Transfer: a set chosen on one sample misses 23 of the other's 35 kills, or 27 of 44.
- Growth: the minimum is still growing by about 1.3 files per ten mutants.
- Coverage: line coverage needs 88 files (57%).
- What it misses: the six curated mutants its unit suite misses are the ones its system suite exists to catch.
Test-estate's sample, re-drawn at HEAD, reproduces test-estate. Its raw file is gone, so the same rule was re-applied to today's lines (Method).
- Logic: 24 of 27 flips are caught (test-estate: 28 of 32). Behavioural tests catch 23 (15 alone) and pin-class tests 9 (1 alone); test-estate had 27 (18 alone) and 11.
- Survivors: the same kind as test-estate's. Two
>→>=flips on the length limits indispatch-validation.js, and an&&→||in the kickoff's goal line. - Prose deletions: 5 of 12 are caught (test-estate: 4 of 19), 4 by behavioural tests and 2 by pins.
The set chosen on A keeps 18 of these 29 caught mutants, and the set chosen on B keeps 8.
Checked against the real catches on record
Mutants are a model of faults. Every real unit-test catch on record whose code still exists was therefore re-created at HEAD and run against all 804 files.
| catch on record | how it was caught | caught at HEAD by | kept by a 99/88/145-file set? |
|---|---|---|---|
a :root token missing from the styleguide (PR #745) |
CI | render-styleguide.test.js only |
no |
| the privacy contact left empty (PR #1803) | CI | render-legal.test.js only |
no |
| the runner prompt over its byte ceiling (PR #1870) | CI | prompt-size-budget.test.js only |
no |
wrong import path, module fails to load (routes/flight-companion.js) |
session | 165 files | yes |
backticks inside a template literal (lib/proxy-instructions.js) |
session | 160 files | yes |
an unescaped apostrophe (lib/render-run-evidence.js) |
session | 187 files | yes |
a Date-versus-number slip after a refactor (lib/pipeline-loops.js) |
session | 3 files | yes |
a result budget too small for its rows (lib/chat-tools.js) |
session | chat-tools.test.js only |
yes |
a login subtitle leaking into the Projects picker (lib/github-install-flow.js) |
session | github-install-flow-golden.test.js only |
no |
proxy mechanics leaking into a general prompt (lib/periodicals.js) |
session | periodicals.test.js only |
no |
a raw .credentials read bypassing the accessor (routes/account-merge.js) |
session | nothing: the census test that caught it was deleted on 12 October (LIN-3523) | — |
Every lean set chosen on mutants keeps 5 of the 10 catches still caught at HEAD, and loses
the other 5. Each lost one is caught by a single file that guards a property: a token census,
a merge-blocker, a byte budget, a golden HTML snapshot, a prompt-content rule. Random
one-line mutants seldom touch those properties. So the mutant measure undervalues guards. A
safe lean set is the mutant cover plus every guard that is the only check of its
property, and checking a cut against the real catches is not optional. A twelfth catch could
not be re-created: lazy SSE headers breaking an error path in lib/flight-companion-turn.js.
The CI record shows no real product fault caught by a unit test since 1 June.
- What was read: all 20 unit-job attempts the classifier marked as fixed in product code or prompt text, read by hand.
- Guards or environment, seven events: guards doing their job (the styleguide token
census, the hermetic socket watcher, the merge-blocker, the runner-prompt byte budget) and
the test environment (a 1 ms clock drift, an
unrefclash with the test runner). - Not catches, ten attempts.
- Test-estate's baseline: its 13 unit "faults" hold up as no user-facing fault.
- The browser specs: e2e specs caught seven real faults on pull requests, and the unit job was green on the same commit each time.
- Since 30 September: unit-job reds have been pins, budgets and test updates. Flaky attempts fell from 66 to 2 after the flake work.
Inside sessions: nine faults in six weeks.
- What was surveyed: every retained session from 2 September to 13 October (15,306 test runs). Of the 128 candidate episodes where a unit test failed and a product edit followed, all were read.
- Real catches: 11, which are nine distinct faults:
- four were load or syntax slips that any test importing the module catches;
- three were behavioural;
- one was a prompt-content pin;
- one was a credential census.
- The other 117:
- 54 were tests updated after an intended change;
- 30 were tests written earlier in the same session;
- 16 were probes;
- the rest were noise.
- The rate: about one real catch for every 150 sessions that ran unit tests (1,399).
What the rest is
145 files form the pooled cover. Of the 659 outside it, by kind (a file can fall into more than one row):
| kind | files | lines | outside the cover | catch a sampled fault | only catcher of one | per-file run time |
|---|---|---|---|---|---|---|
| all | 804 | 286,278 | 659 | 427 | 117 | 100% |
| ticket-named | 180 | 44,766 | 161 | 88 | 12 | 27% |
| mostly pin-class tests | 47 | 12,575 | 45 | 11 | 2 | 9% |
| named as census, inventory or count pin | 16 | 4,179 | 16 | 1 | 0 | 6% |
| reads production source as text | 146 | 68,464 | 115 | 66 | 27 | 20% |
loads browser code into a vm sandbox |
70 | 28,506 | 52 | 34 | 18 | 7% |
| tests of test helpers or fakes | 8 | 1,322 | 7 | 2 | 1 | 1% |
| executes no line another file does not | 356 | 85,191 | 324 | 156 | 24 | 44% |
| caught none of the 599 sampled faults | 377 | 85,497 | 377 | 0 | 0 | 44% |
Run time is each file run alone with coverage on, so it measures per-file overhead as well as assertions.
- Tests that repeat another file's check. The commonest case. 324 files outside the cover
execute no line that another file does not; most re-drive a seam another file already
drives. The dashboard router, for example, is written out 46 times inside one file
(
docs/reviews/code-quality-review-2026-10-12-tests.md:39). - Ticket-named regressions.
- Catch rate: they catch a sampled fault about as often as other files (49% against 54%), but are the only catcher less than half as often (7% against 17%). Each one re-drives a seam the home module's own file already covers.
- Where they matter: the code-quality review found 18 production modules tested only by
ticket-named files (
:184), and those carry unique protection. - Staleness: none of 30 sampled is broken, but 28 of the 175 headers describe a moment
that has passed. Six
lin-3124-pr3checkpoint files say "dark until the write flip", though the flip landed the same day. - Deletions: only 15 have ever been deleted, all with a retired feature.
- Pins of text, counts or source shape.
- On mutants: the 16 census-named files catch one sampled fault between them, never alone, and take 6% of per-file run time.
- On real catches: two of the three CI catches re-created above are a token census and a byte budget, and only those files catch them.
- So pins are not surplus as a class. A pin that is the only check of a property (every styleguide token, the prompt byte budget, a merge blocker) earns its place. A count of routes or credential sites that a behavioural test also exercises does not.
- Tests of test helpers and fakes. Eight small files (1,322 lines), such as
network-guard-sockets,test-server-listen-bindandliveness-fixture-integrity. They guard the harness, not the product; the hermetic watcher they support made one of the CI catches. - Tests that check internals rather than behaviour at a seam.
- Count: 146 files read production source as text, 51 of them ticket-named. 66 catch a sampled fault and 27 are the only catcher, often because a source scan is the only thing that notices a deleted wiring line.
- Cost: the cost of this class is rebuild cost, not run time. A scan fails when code moves, and the "fix" is to move the scan; test-estate found 6 of 12 source-scan "fixes" were exactly that.
Why it accumulated
No single rule did it. The habit changed inside the ordinary pull request:
| Jun | Jul | Aug | Sep | Oct 1–12 | |
|---|---|---|---|---|---|
| unit lines added per product line added | 0.59 | 1.15 | 1.68 | 1.98 | 1.71 |
| the same, median per PR | 0.56 | 1.19 | 1.89 | 2.09 | 2.00 |
| share of added test lines in ticket-named files | 0% | 0.3% | 13.9% | 20.7% | 30.9% |
- Spread: 1,204 tickets added unit-test lines since 1 June, and the top 20 account for only 17.6%.
- Tests come with product changes: only 3.5% of test lines came from test-only PRs.
- Deletion is rare: of 859 unit-test files ever created, 49 were deleted. 41 of those went in the two weeks from 28 September, every one with a retired feature.
- No file has ever been deleted because another file already checked the same thing. That is the "life of their own": every change adds tests, and no change removes them.
The candidate rules, with landing dates and what followed:
| rule | landed | what followed |
|---|---|---|
implementation: "Write tests for new/changed behavior" (lib/prompt-template-defs.js:895) |
17 Jan | the baseline; the ratio was 0.25 |
four rules in one week: simple-dispatcher's TDD rule (its CLAUDE.md, line 51), the bug template's acceptance witness (:174), characterization tests (:900), the close-out ledger (:997) |
16–29 Jun | the ratio doubled from 0.45 to 0.95 as the fleet started; the four cannot be told apart |
| none | Jul–Aug | the ratio kept climbing, 1.15 to 1.68, with no new rule: a habit compounding |
"observe it fail" (:898) and the review's mutation check (:977) |
22–24 Aug | flat ratio (1.5–1.7) on doubled volume; "witness", "mutation" and "goes red" went from about 10% to 25–30% of new test text |
| simple-dispatcher's runtime-witness rule (its CLAUDE.md, lines 56–66) | 12 Sep | governs simple-dispatcher sessions only |
the prompt-size freeze (CLAUDE.md:110, a new lesson lands as code or a test first) |
2 Oct | the ratio fell, 2.48 to 1.59; two test files name it |
| no rule: ticket-named files | first on 23 Jul | 0% to 31% of added test lines; the credential and grant migration (LIN-3124, 3125, 3130, 3134, 3136) wrote 57 of them |
The pieces fit together like this:
- Test-first and witness habits explain why each change brings tests.
- The review's mutation check explains some of the wording. It led 107 findings in 100
tickets, 97 of them test or wording changes (
which-rules-pay.md:125-128). - The prompt freeze is not a driver.
- The ticket-named convention, which no rule asks for, explains where the tests go.
Harbour has no TDD rule and no runtime-witness rule of its own; those live in
simple-dispatcher's CLAUDE.md. Harbour sessions practise test-first anyway: 1,723
test-first reds across both repos' retained sessions.
What the tests cost, set against what they caught
CI:
- The unit job: 0.38 minutes in early July, 5.18 in W41. It is now the slowest job, ahead of the slowest e2e shard (4.81).
- The double run: the job runs the suite twice, the hermetic pass and the proxy-env pass. The workflow calls the second pass "~35s"; it now takes about 2.2 minutes.
- The whole run: the median run went from 2.5–3.1 minutes in June and July to 5.3–5.6 now. The step came in W33–W35, not in the last four weeks the stability review saw.
Edits forced on product changes:
- Share of PRs: since 1 September, 66% of product PRs modified existing test lines (49% since June), and the median product PR touched two existing test files.
- The most-edited files are hubs (
dashboard-routes, 26 tickets;flight-companion-client,- and inventories (
proxy-endpoint-inventory-witness, 22;db-indexes, 17;proxy-di-witness, 12).
- and inventories (
- The pin tax:
lin-3124-pr1-count-pinswas edited by four tickets in seven hours on 11 October and by a fifth the next morning, every edit a decrement or a deletion.
Agent time: in the retained window, Harbour sessions spent 41.6 hours waiting on test runs. That is 3% of active time overall and 14% in implementation sessions. Counting tests against product code, 43% of edit calls and 67% of the lines agents wrote went to tests.
Rebuild cost:
- Harness copies: 64 copies of an in-memory Mongo collection in 41 variants, and 156
hand-rolled HTTP helpers, make a harness change a many-file edit
(
code-quality-review-2026-10-12-tests.md:33,:35). - Fakes: hand-written database fakes let tests pass while they are wrong (LIN-3534).
- Source scans: 146 files read source as text, and each moves when the code does.
- Harness copies: 64 copies of an in-memory Mongo collection in 41 variants, and 156
hand-rolled HTTP helpers, make a harness change a many-file edit
(
Against all that:
- nine real faults caught in sessions in six weeks, four of them load errors any importer would catch;
- no real fault caught in CI since June;
- 295 of 599 sampled one-line faults (49%) not caught at all.
The suite's value is concentrated. 105 of the 117 sole catchers are files named after a module, not a ticket, and mostly behavioural, and a few guards are the only check of their property.
What a lean suite would look like
The evidence supports trimming, not a 90% cut, and it supports changing the inflow more than deleting the stock.
- Keep the core. That means every file that is the only catcher of a sampled fault or a real catch, and every module's own behavioural file. It also means the guards that are the only check of their property: the styleguide token census, the prompt byte budget, the merge blocker, the hermetic watcher, the golden snapshots. That is the 260–470-file core.
- Fold ticket-named files into their home module's file, keeping only cases the home file
lacks. 168 of the 180 were never a sole catcher here.
- Safety: fold one file at a time, and re-run the sample after each fold; nothing the suite caught may go uncaught.
- Stopping the inflow: the code-quality review's open question, "tests go in the home
module's own test file, created if missing" (
:184), is the rule that would stop it.
- Retire count pins that duplicate a behavioural check, as LIN-3523 began. Keep a pin
where it is the only check of an invariant. The raw-
.credentialscensus LIN-3523 deleted had made a real catch two weeks earlier, so check each pin against the real catches before removing it. - Run the unit suite once in CI. The second pass exists to exercise the proxy arm of the hermetic watcher. Either a single pass carrying both watcher arms, or a second pass limited to the files that open sockets, would save about two minutes a run.
- Spend on the gap.
- Overall: half of sampled faults survive.
- Browser code is the thinnest: 76% of
public/mutants survive, against 34% inlib/. - Server-side: of the mutants on lines some test executes, a third (119 of 375) survive, executed but never checked.
The kill matrix in test-surplus-mutants.json and the real-catch mutants beside it are the
instrument for any of this. A deletion is safe when every fault the suite catches, and every
real catch, is still caught, re-checked on a fresh sample.
Method
Everything ran at ebf93bd9 in scratch worktrees outside this checkout. No test in the main
tree was edited, deleted or mutated. The scripts are in test-surplus-scripts/, and every
number above comes from the data files named in the front matter.
The protection measure is faults caught, read from mutants.
- Definition: a mutant counts as caught by a test file when the file fails on it and fails again on a confirming re-run.
- Why not coverage: line coverage is reported alongside, but it is not the measure. A
file can execute a line without checking anything about it. Coverage also cannot see the
70 files that load browser code into a
vmsandbox: 99 files cover no production line at all, yet 19 of them catch a mutant. - Validating the measure: mutants measure the outcome that matters, a fault going red. The real catches on record test whether the measure tracks real faults. It tracks the behavioural ones and misses the guards, so both are used.
Coverage.
cover.mjsruns each of the 804 unit-test files alone, six at a time, withNODE_V8_COVERAGE. That is the 803 intests/unitplusscripts/eval/eval-dense.test.mjs, whichtest:unitalso runs. It reduces V8's block ranges to covered lines oflib/,routes/,public/andserver.js. Child processes inherit the variable, so spawned servers count.Mutant sites.
- Edits:
sites.mjslists every line in those directories where one of three edits applies:- test-estate's operator flips:
===↔!==,>↔>=,&&↔||,return true↔falseand??→||; - deleting a single-line statement;
- deleting one line of prompt prose inside a template literal (test-estate's prose rule).
- test-estate's operator flips:
- Population: 21,020 sites in 429 files: 11,595 logic, 7,296 statement and 2,129 prose.
- Samples: two samples of 300 were drawn uniformly with fixed seeds: A (3551) and B
(1013, excluding A's sites). Mutants that fail
node --checkare dropped.
- Edits:
Test-estate's sample, re-drawn.
- Why: test-estate's raw mutant file was git-ignored and is gone.
- How:
replica.mjsre-draws the sample at HEAD by its own rule (scripts/survey-tests-mutate.mjs). That is eight evenly spaced operator flips in each ofdispatch-validation,credential-state,completion-signalsand the autopilot kickoff. It adds evenly spaced prose deletions in the autopilot kickoff, the runner kickoff andprompt-template-defs.js, where the template definitions now live. - Caveat: the lines differ from 30 September's; the rule is the same.
Running a mutant.
- The run:
mutate.mjsapplies one mutant, then runs every test file that could see it, each file alone, eight at a time. Each test has a 30-second timeout and each file a 90-second budget; a file that hangs past its budget counts as a catch, as it would time out in CI. - What "could see it" means: the file covers the mutated line, or its source names the mutated file, or it scans production directories (29 files).
- Checking the shortcut: twenty sample-A mutants were re-run against all 804 files. The confirmed catching files matched exactly, 20 of 20.
- The run:
Confirmation.
- The noise: three unmutated runs of all 804 files, under the same load, showed up to
two spurious file failures each (
http-keepalive,require-grant, alin-3124checkpoint file andplan-review-round-trips-script). - The fix: every catching file is re-run once more with the mutant applied, and only files that fail twice count.
- What it changed: it removed spurious catches only in the 804-file runs, none in the targeted ones.
- The noise: three unmutated runs of all 804 files, under the same load, showed up to
two spurious file failures each (
The smallest set.
analyse.mjstakes the confirmed kill matrix and does five things:- runs greedy set cover on one sample, then prunes any chosen file whose catches the rest still cover;
- reports how much of the other sample that set catches;
- plots the curve of protection against files kept;
- computes the saturation curve: the size of the cover on random subsamples of the pooled catches;
- estimates, with Chapman's form of Lincoln–Petersen, how many files are the only catcher of some fault, from the overlap of sole catchers between the two samples.
It runs on the committed data:
node test-surplus-scripts/analyse.mjs test-surplus-mutants.json#A test-surplus-mutants.json#B(add--level testfor single tests, counting only each file's deepest failing tests). The kill matrix itself is not re-creatable from this page.classify.mjsjoins each file's coverage, classifier output and catches.Real catches.
- CI: the record was re-pulled with test-estate's own CI scripts. Every attempt fixed in product code or prompt text was read by hand.
- Sessions: the record was re-surveyed with
survey-tests-local.mjs, and all 128 candidate unit-test episodes were read by hand. - Re-creation: each real catch whose code still exists at HEAD was re-created as a mutant (the fixing change reversed, or the same fault re-introduced) and run against all 804 files.
History and cost. Each source:
- test growth, file births and deaths, the rules' landing dates and the edit cost per
product PR:
git logat first-parent (PR) level; - session waiting and edit shares: the retained transcripts, 2 September to 13 October;
- CI times: the GitHub Actions API.
Summaries are in
test-surplus-history.json, and catch records intest-surplus-catches.json.- test growth, file births and deaths, the rules' landing dates and the edit cost per
product PR:
simple-dispatcher. The same method, cheaply: its 50 curated mutants that target its own code, plus 42 operator flips across its six largest modules, against its unit suite, with an exact minimum cover.
Limits
- Mutants are a model of faults, not faults. One-line flips and deletions are smaller than most real slips, and they rarely touch the properties guards check. The real-catch check shows both: the behavioural catches carry over, and the guard catches do not.
- Each sample is 1.4% of the sites. The smallest set is a lower bound that grows with the sample. That is why the saturation curve and the capture–recapture estimate are given, not just the cover.
- Real catches are few. Ten re-created catches, from six weeks of sessions and four months of CI, each judged by one reader with a spot check. 551 session episodes where the test never passed again were not read.
- Kills are counted by file. A file that crashes on import counts as a catch, and so does one whose single unrelated test fails. Going down to single tests was checked and does not change the answer.
- The machine was shared and loaded. Timings are shares, not absolute seconds. Spurious failures were removed by confirmation, not prevented.
- "Never caught" is not "never matters." A census that has never failed may guard an invariant nobody has broken yet. This paper sees catches, not deterrence.
- A harness incident, corrected. The first pass over sample A ran beside other mutant runs that wrote to the same report files, and 36 of its 300 outcomes were wrong. That pass was discarded and sample A re-run with per-process report files; every figure comes from the re-run.
Next
These are questions this paper leaves open, not proposed work:
- Which of the 168 non-sole ticket-named files are safe to fold? Doing so needs the kill matrix at file level for those files specifically: a denser sample over the modules they re-drive.
- What would a deliberate guard census look like? It would list the properties only a pin checks (tokens, budgets, blockers, goldens), so that mutant-driven cuts never touch them.
- Is the surviving half of faults a problem in practice? That means following surviving mutant classes, such as browser code and executed-but-unchecked server lines, to real incidents.