Status: Active. Last updated 2026-05-18.
This document is the canonical place for mutation-testing triage,
survivor classifications, and operational lessons. It is the output
of the 2026-05-18 mutmut survey of bin/wl_validation.py and
bin/wl_csv.py.
Two findings during the 2026-05-18 pre-release sweep made it clear that the mutation-testing harness needed documented governance:
-
The prior mutmut runs were misconfigured. The default
TEST_RUNNER_FILESinscripts/mutmut.shis hard-coded totests/unit/test_validation.py tests/unit/test_ascii_validation.py tests/unit/test_validator_fuzz.py. When a contributor invokes the script withMUTATE_PATH=bin/wl_csv.pybut does not also overrideTEST_RUNNER_FILES, mutmut applies mutations towl_csv.pywhile running tests that exercisewl_validation.py. The "survivors" reported are NOT genuine test-coverage gaps; they are artifacts of test selector / mutated module mismatch. -
The mutmut container mounts the host repo read-write. Line 89 of
scripts/mutmut.shis-v "$REPO_ROOT:/work". Every mutation mutmut applies is a real write to the host working tree. If mutmut is interrupted between mutate-and-restore (SIGKILL, container stop, host reboot, OS crash), the file remains in mutated state andgit diffshows it as a normal edit. The 2026-05-18 session caught a live mutation (_csv_file_hash(csv_path)→Noneinbin/wl_csv.py:184) that was about to be staged in a Phase 2 commit. The mutation would have silently broken CSV-hash bootstrapping.
When invoking the harness, both knobs are required:
# CORRECT — wl_csv.py mutations against wl_csv tests
MUTATE_PATH=bin/wl_csv.py \
TEST_RUNNER_FILES="tests/unit/test_csv.py" \
scripts/mutmut.sh run
# WRONG — wl_csv.py mutations against wl_validation tests
MUTATE_PATH=bin/wl_csv.py \
scripts/mutmut.sh run # uses default TEST_RUNNER_FILES → garbage resultsMapping table (keep in sync if new test files land):
| Mutated module | Test selector |
|---|---|
bin/wl_validation.py |
tests/unit/test_validation.py tests/unit/test_ascii_validation.py tests/unit/test_validator_fuzz.py |
bin/wl_csv.py |
tests/unit/test_csv.py |
bin/wl_versions.py |
tests/unit/test_versions.py (if present) |
bin/wl_rbac.py |
tests/unit/test_rbac.py |
bin/wl_audit.py |
tests/unit/test_audit.py |
After every mutmut run:
git diff bin/ # MUST be empty
git status --short # MUST not show modifications to mutated filesIf git diff bin/ shows a change, mutmut died mid-mutation and the
file is in mutated state. Recover with git checkout -- <file>.
This rule is non-negotiable. A live mutation slipped to a commit once already (caught at stage time on 2026-05-18); next time the catch may not happen.
Already documented in scripts/mutmut.sh comments. The handler's
tests are integration tests requiring a live Splunk container at
~30s per invocation. Multiplied by hundreds of mutations =
multi-day runs. The handler's logic is mostly dispatch + delegation
to other modules; mutating wl_validation.py, wl_csv.py, etc.
gives a higher-signal result. RBAC paths in the handler are pinned
by tests/integration/test_rbac_matrix.py.
Mutmut on bin/wl_csv.py (1244 lines, 913 mutation candidates)
exceeded an estimated 4-8 hour budget at the 11-hour mark on
2026-05-18 with only mutations 191–235 tested (≈5% coverage). If
a run cannot complete in 2 hours per module, kill it and either
split the test selector (run subsets in parallel) or accept
partial coverage as the deliverable. Open-ended runs corrupt the
working tree if interrupted (see rule 2).
Twelve mutmut survivors were reported on bin/wl_validation.py
from the prior session. Manual analysis classifies them as follows.
These were closed in commit <TBD> by adding tests to
tests/unit/test_validation.py. Each test was verified to FAIL
on the mutant and PASS on the pristine module.
| Mutant | Line | Mutation | Killing test |
|---|---|---|---|
| 116 | 107 | "Only ASCII characters are allowed in text fields" → "XXOnly ASCII characters are allowed in text fieldsXX" |
TestValidateAsciiTextErrorMessage::test_validate_ascii_text_returns_exact_error_string |
| 151 | 214 | stem = name.rsplit(".", 1)[0] → stem = name.rsplit(".", 2)[0] |
TestIsSafeFilename::test_is_safe_filename_rejects_multi_dot_stem |
Why these matter:
- Mutant 116 —
validate_ascii_textreturns the error string for direct UI display. Silent corruption toXXOnly ASCII...XXwould ship as visible garbage to analysts on every non-ASCII rule name attempt. The originalpytesttruthy-check was indistinguishable. - Mutant 151 — without the test, a future refactor of
rsplit(".", 1)torsplit(".", 2)would accept filenames likefoo.bar.csv— exactly the SPLbase()/ dashboard drilldown URL parsing hazard the validator exists to prevent.
These cannot be killed by any test because they produce indistinguishable behavior from the original. Documenting them here prevents future contributors from wasting time trying.
| Mutant | Line | Mutation | Why equivalent |
|---|---|---|---|
| 91 | 31 | sys.path.insert(0, ...) → sys.path.insert(1, ...) |
Both achieve import resolution. Position only affects ordering vs. other entries that don't conflict with wl_constants. |
| 104 | 65 | if len(cleaned) > max_length → if len(cleaned) >= max_length |
When len(cleaned) == max_length, cleaned[:max_length] == cleaned. The branch executes but produces identical output. |
| 138 | 191 | if "/" in name → if "XX/XX" in name |
Reclassified 2026-05-19 (was "killed by existing tests"). On POSIX (Linux mutmut container), os.path.basename at line 193 catches every /-containing input the mutated check would have rejected — basename treats / as the separator, so basename(name) != name fires on any path-traversal input. The / check at line 191 is genuinely redundant defense-in-depth on POSIX. The \\ half of the same or is still load-bearing (basename on POSIX does NOT split on \\), but mutmut leaves that part intact. |
| 145 | 193 | os.path.basename(name) != name → return False → return True |
On POSIX (mutmut container), no constructable input passes the prior "/" and "\\" checks but fails os.path.basename != name — the basename divergence is itself caused by / on POSIX. Branch is unreachable. |
| 146 | 197 | name.startswith(".") → name.startswith("XX.XX") |
Reclassified 2026-05-19 (was "killed by existing tests"). For input .hidden.csv: mutated startswith check returns False; the stem .hidden then fails _ASCII_FILENAME_STEM_RE at line 227 (regex ^[A-Za-z0-9_\-]+\Z rejects the leading dot). Function returns False via stem regex. There is no constructable input where startswith(".") would be load-bearing AND the stem regex would accept — because any input starting with "." produces a stem starting with "." which fails the alphanumeric/underscore/hyphen alphabet. |
| 155 | 215 | if not stem: return False → return True |
Caught earlier by name.startswith(".") at line 197. Any input reaching the stem check has a non-empty stem. |
| 157 | 222 | ord(c) < 0x20 → ord(c) <= 0x20 |
Adds space (0x20) to rejection. Space already excluded by stem regex [A-Za-z0-9_\-]+. |
| 158 | 222 | ord(c) < 0x20 → ord(c) < 33 |
Adds space + ! to rejection. Both already excluded by stem regex. |
| 160 | 222 | ord(c) == 0x7f → ord(c) == 128 |
DEL (0x7f) already excluded by stem regex (not in alphabet). 128 is in _NON_ASCII_RE which fires earlier (line 218). |
| 161 | 222 | ord(c) < 0x20 or ord(c) == 0x7f → ... and ord(c) == 0x7f |
New condition impossible (single ord can't be both < 32 and == 127). Branch dead. Control chars still caught by stem regex. |
The pattern is structural: is_safe_filename has overlapping
defenses (path-separator check → basename check → extension check
→ stem regex → control-char check → alphanum check). Many control-
char and edge-case mutants are caught by the stem regex regardless,
making the control-char check defensive but mutation-test-invisible.
This is a design choice (defense-in-depth) we accept.
After items A (test-selector mapping) and B (safe :ro+tmpfs
mount) landed, mutmut was re-run on bin/wl_validation.py with
the corrected test selector
(tests/unit/test_validation.py tests/unit/test_ascii_validation.py tests/unit/test_validator_fuzz.py tests/unit/test_frontend_backend_parity.py).
Result: 10 surviving mutants, all matching the equivalent set above.
This is the first run of mutmut on this module with a non-stale test selector. Notable findings:
- The 2 new tests added in commit
eddcb62(test_validate_ascii_text_returns_exact_error_stringandtest_is_safe_filename_rejects_multi_dot_stem) successfully killed mutants 116 and 151 as predicted — confirmed by their absence from the survivor list. - Mutants 138 and 146 were initially classified as "killed by existing
tests" in the original triage. The fresh run proved otherwise — both
survived because
is_safe_filename's overlapping defenses make the individual checks redundant for any input the existing tests use. The classification table above has been corrected. - Total mutation budget on this module: 100 mutations applied, 92 killed by the test suite, 10 survived as equivalent mutants. Effective mutation score: 92% (or 100% kill rate of non-equivalent mutants).
The 45 "survivors" reported for bin/wl_csv.py are NOT actionable.
The mutmut command in the prior session was:
mutmut run --paths-to-mutate=bin/wl_csv.py --tests-dir=tests/unit \
--runner='python -m pytest -x -q --tb=no \
tests/unit/test_validation.py tests/unit/test_ascii_validation.py'
The test selector exercises wl_validation.py, not wl_csv.py.
Every mutation to wl_csv.py survived because the running tests
never imported or called any wl_csv function. The 45 mutants are
artifacts of misconfiguration (see "Operational rule 1" above).
To get genuine signal on bin/wl_csv.py, re-run:
MUTATE_PATH=bin/wl_csv.py \
TEST_RUNNER_FILES="tests/unit/test_csv.py" \
scripts/mutmut.sh runRe-ran 2026-05-19 — see next section for results.
Mutmut run with the corrected selector
(tests/unit/test_csv.py tests/unit/test_diff_fuzz.py) on a fresh
:ro-mount + tmpfs container.
Result: 723 mutations applied, 547 survived, 176 killed. Effective mutation score: 24%.
bin/wl_csv.py is 1244 lines. Only ~600 of those — the diff engine
(compute_diff, compute_added, compute_removed, compute_edited)
and the hash-registry plumbing (_csv_file_hash,
update_csv_expected_hash, bootstrap_csv_expected_hashes,
remove_csv_expected_hash) — have unit-test coverage in
test_csv.py and test_diff_fuzz.py. The remaining ~600 lines
(save_csv_pipeline ~lines 747–1148 and create_csv_pipeline
~lines 1149–1244) are tested via the live Splunk container in
tests/integration/, NOT by the unit suite mutmut is exercising.
This shows up in the survivor ID ranges: IDs 191–472 (the unit- testable core) have a mix of killed and survived mutations (~50% each in spot checks), while IDs 494–913 (the integration-only code) are almost entirely survivors.
Spot-checked 10 representative survivors across the range; the pattern divides cleanly:
| Class | Example mutants | Killable? | Action |
|---|---|---|---|
| Logger / docstring strings | 191 (logger name) | Low value | Skip |
| Constant strings flowing into paths | 200 (CSV_EXPECTED_HASHES_FILE) |
Killable | Worth a test pin |
| Value→None crash mutations | 207 (parent = None) |
Killable | Trivial unit test |
| Hidden-column filter strings | 264, 388 (startswith("_")) |
Killable | Security boundary, worth pinning |
| Tuple default-value sentinels | 313, 332 (row.get(h, "")) |
Mostly equivalent | Skip |
| Response-dict key names | 431, 600 ("text_diff", "added_row_count") |
Killable | Contract pinning — highest value |
| JSON formatting params | 494 (indent=2 → 3) |
Equivalent | Skip |
| Integration-path mutations | 600+ (most of 494–913) | Killable but expensive | Defer to integration-test coverage |
Rough estimate of GENUINE killable survivors in the unit-tested core (IDs 191–472): ~100–150. Closing them would require ~100–150 new unit tests, ~5–10 lines each. Total work: 1–2 days.
Closing the 100–150 genuine survivors is in scope of v1.1 item G (test coverage push from 32.4% → 80%). Writing those tests would naturally exercise the same code paths and kill the same mutants. Doing the work twice (once for mutmut-survivors, once for coverage) would be wasteful.
During this run, a leftover .mutmut-cache (~192 KB) from a prior
:rw-mount session was found on the host repo. The
populate_scratch() helper's cp -a /repo/. /scratch/ copied this
stale cache into the fresh tmpfs, leaking prior-run results (37
phantom wl_audit survivors + 10–12 wl_validation entries) into the
cumulative results display. This didn't affect the wl_csv survivor
count for the new run (the mutmut progress counter showed
723/723 mutations actually tested), but it polluted the
mutmut results aggregate view.
Fix: populate_scratch() now explicitly wipes
/scratch/.mutmut-cache and /scratch/mutants after the cp. The
host-side leftover at wl_manager/.mutmut-cache was also removed.
.gitignore already excluded it from tracking.
Mutmut run with the corrected selector
(tests/unit/test_audit.py tests/unit/test_view_audit_dedup.py)
on a fresh :ro-mount + tmpfs container with the cache-leak fix
from item D in effect.
Result: 90 mutations applied, 35 survived, 55 killed. Effective mutation score: 61%.
This is the highest kill rate of any module in this v1.1 sweep
(wl_validation 92%, wl_csv 24%, wl_audit 61%). The reason:
wl_audit.py is small (191 lines) and focused (a single
responsibility: post audit events to the wl_audit Splunk index
via REST), and the two test files
(test_audit.py + test_view_audit_dedup.py, 562 lines combined)
provide solid coverage of the event-building logic.
Sampled 9 representative survivors. The pattern divides cleanly into 4 classes:
| Class | Example mutants | Killable? | Action |
|---|---|---|---|
| Module-level import fallbacks | 1 (urllib = None → urllib = "") |
Equivalent in practice | Skip — urllib is always available in the test env |
| Log / error message text mutations | 22, 42, 72, 78 | Low value | Skip — pinning exact log text adds fragility without security value |
| HTTP boundary edge cases | 68 (200 <= status_code < 300 → <= 300) |
Killable but unusual | Skip — HTTP 300 (Multiple Choices) is rarely seen and not load-bearing |
| Event-building + HTTP path | 17 (kwarg filter), 55 (auth header template), 88 (None error_msg) | Killable, higher value | Defer to v1.1 item G |
-
Mutant 17 (line 91): kwarg filter
("app_context", "comment")→("XXapp_contextXX", "comment"). RECLASSIFIED AS EQUIVALENT. The original triage claimed this was killable vialen(event) == expected_field_countbecause the unfiltered loop would writeapp_contextto the event again. Closer analysis during item G showed the second write is idempotent:event["app_context"] = kwargs["app_context"]overwrites the line-85 assignment with the same value (both pull from the samekwargsdict), and a dict overwrite does not add a key. No observable behavior changes for any test input. The mutant is structurally indistinguishable from the original. -
Mutant 55 (line 156): HTTP Authorization header template
` (substring), which the mutated `"XXSplunk XX"` passes. The new test asserts exact equality on the full header value. Manually verified: applying the mutation locally produces the assertion failure `expected "Splunk MY_SESSION_KEY_12345", got "XXSplunk MY_SESSION_KEY_12345XX"`."Splunk %s"→"XXSplunk %sXX". CLOSED in test_audit.pytest_authorization_header_exact_splunk_prefix(item G, 2026-05-19). The existingtest_post_audit_event_sets_headersused `"Splunk " in -
Mutant 88 (line 189):
error_msg = str(e)→error_msg = Nonein the generic exception handler. CLOSED in test_audit.pytest_generic_exception_returns_non_empty_error_message(item G, 2026-05-19). No prior test exercised the genericexcept Exceptionbranch (existing tests only triggered HTTPError, URLError, socket.timeout). The new test injects aRuntimeErrorviamock_urlopen.side_effectand assertserror_msg is not None- non-empty + contains the original exception's text.
Manually verified: applying the mutation produces
assert None is not Nonefailure.
- non-empty + contains the original exception's text.
Manually verified: applying the mutation produces
Closing summary (item G first batch, 2026-05-19):
- Mutant 17 → equivalent (triage corrected — no test possible without destroying program semantics).
- Mutant 55 → killed by exact-string header assertion.
- Mutant 88 → killed by mocked-
side_effectgeneric-exception test. - Effective wl_audit kill rate after this batch: 57/90 → 63% (was 55/90 = 61%). 33 survivors remain, all in the lower-value classes (log/error message text, equivalent HTTP-status edge cases, integration-only paths).
-
Wire the mapping table into— DONE. The script now auto-derivesscripts/mutmut.shTEST_RUNNER_FILESfromMUTATE_PATHvia thederive_test_files_forfunction, and hard-fails on unknown modules (rather than silently falling back to wl_validation tests). Runscripts/mutmut.sh mappingsto see the table.bin/wl_handler.pyis explicitly rejected with a pointer to this doc. Escape hatch: explicitTEST_RUNNER_FILESenv var still overrides auto-derivation. -
Switch the volume mount to read-only.— DONE. The host repo is now mounted at/repoas:ro; a 512 MiB tmpfs is mounted at/scratchand populated from/repoat container creation. mutmut runs withWORKDIR=/scratchand mutates the tmpfs copy only — the host tree is unreachable from inside the container. Verified: a deliberate write attempt to/repo/bin/wl_validation.pyfails with "Read-only file system"; host file sha256 unchanged after a fresh container creation. Source-refresh signal:scripts/mutmut.sh killthen re-run (the tmpfs is repopulated from/repoonly on container creation). -
Re-run wl_validation with the correct selector— DONE. Result: 10 surviving mutants, all equivalent. See "2026-05-19 fresh-run confirmation" section above. The estimate of "≤8 survivors" was off by 2 — the original triage incorrectly listed mutants 138 and 146 as "killed by existing tests" when they're actually equivalent. Doc corrected. -
Run wl_csv.py with the correct selector— DONE. Result: 547 survivors of 723 mutations (24% kill rate). The estimate of "5-20 genuine gaps" was wildly low — the actual genuine-gap count is ~100-150 in the unit-tested core, plus ~400 survivors in integration-only code that won't be killed by unit tests at all. See "2026-05-19 wl_csv.py fresh-run results (item D)" section above. Closing the ~100-150 genuine survivors is deferred to v1.1 item G (test coverage push). -
Add— DONE. Result: 35 surviving mutants of 90 (kill rate 61%). The estimate "37 needs re-validation" was close — fresh run dropped to 35. See "2026-05-19 wl_audit.py fresh-run results (item E)" section below. Most survivors are log/error message text (low value to kill) or HTTP-path mutations that are integration-tested rather than unit-tested. The ~5 higher-value survivors involve event- building and HTTP send logic and are deferred to v1.1 item G.bin/wl_audit.pymutation pass.
(none — all mutmut work items closed as of 2026-05-19)
Trigger. Pre-release sweep flagged that the baseline kill rates on the three large data modules were all ~50-53% — well below the industry-standard ~75-80% target for KILLABLE mutants (Google "Mutation Testing in Practice", 2020). The user's framing was a reputation risk if clients ran mutmut against the public release and saw ~half of mutations survive.
Baseline (2026-05-19).
| Module | Mutations | Survivors | Kill rate |
|---|---|---|---|
bin/wl_csv.py |
723 | 358 | ~50.5% |
bin/wl_limits.py |
428 | 203 | ~52.6% |
bin/wl_trash.py |
378 | 177 | ~53.2% |
| Total | 1529 | 738 | ~51.7% |
Triage methodology — Five-cluster classification.
Before writing any new tests, we sampled survivors via
docker exec wl_manager_mutmut mutmut show <id> to classify them.
The samples from bin/wl_csv.py (13 representative survivors out
of 358) fell into two broad categories that line up with how
production code is shaped:
-
~30% equivalent / uninteresting. Logger-name string-wraps (
logging.getLogger("XXwl_managerXX")), buffer-size off-by-one (fh.read(65536)→fh.read(65537)), JSON-indent values that no test asserts on byte-for-byte (json.dump(d, indent=3)vsindent=2), temp-file suffix wraps that survive any test not doing a directory-listing assertion on intermediate files. Chasing these is anti-pattern: the assertion that catches them is brittle (couples tests to implementation detail), and the return on test-author effort is near-zero. -
~70% real test gaps. These fall into five clusters with well-defined assertion shapes:
Cluster What the mutation looks like Test shape that kills it A — Registry / checksum integrity file_hash = Noneorhashes[name] = NoneAssert returned value is valid hex AND registry stored exactly that value (not None) B — Decision-function return shape Tuple arity/element-type changes; pos = NonedefaultsAssert tuple length + element types + canonical sentinels (e.g. admin → (True, 0, -1))C — Dict-return contract String wraps on top-level keys ( "diff"→"XXdiffXX")Assert presence of the documented required-key set, on EVERY return path (success, error, OSError, unexpected-exc) D — Boundary precision <=↔<,+↔-,break↔continueTest at the EXACT boundary AND one-off either side; include duplicate-match cases where break/continue diverges E — Audit-event field shape pos = None,rn = Nonepolluting field-name templatesCapture build_audit_eventcalls + regex-check no key matches*_row_None_*Each new test is written to kill a CLUSTER of related mutants in one shot, not a single mutant — the leverage is in pinning the contract, not in matching one mutmut-ID-to-one-test.
Tests added in the 2026-05-20 batch (committed 2358a27 + 5cbb221).
| Module | New tests | Test groups | Unit-suite status |
|---|---|---|---|
bin/wl_csv.py |
15 | A-E (all five clusters) | 79/79 pass |
bin/wl_limits.py |
16 | A-E (Group E adapted to increment-delta) | 92/92 pass |
bin/wl_trash.py |
10 | A-C (no audit-emission in trash → no Group E) | 45/45 pass |
Post-batch mutmut measurements.
| Module | Baseline kill rate | Post-batch kill rate | Delta | Notes |
|---|---|---|---|---|
bin/wl_csv.py |
50.5% (358/723 survivors) | 53.4% (336/723 survivors + 1 timed out) | +2.9% | Re-ran 2026-05-20 with clean tmpfs/cache. 22 mutations newly killed. |
bin/wl_limits.py |
52.6% (203/428 survivors) | NOT YET RE-RUN | — | Container time-constrained; queued for v1.1. |
bin/wl_trash.py |
53.2% (177/378 survivors) | NOT YET RE-RUN | — | Container time-constrained; queued for v1.1. |
Honest reading of the wl_csv result.
The +2.9% delta is much smaller than the ~70% kill rate that the five-cluster methodology TARGETS. Three reasons surfaced when the 22 newly-killed mutations were compared against survivor IDs:
-
Many "killable-looking" mutations are functionally equivalent.
bin/wl_csv.pycallsstartswith("_")in THREE adjacent lines (visible_headers,old_vis_set,new_vis_set). Mutmut generated one mutation per line. The Group B testtest_compute_diff_excludes_underscore_prefixed_metadata_columnskills the mutations on lines 537-538 (old_vis_set/new_vis_set— they affectcommon_headersand surface inedited_count), but leaves the mutation on line 534 (visible_headers) alive because that variable is only used for the cosmetictext_diffoutput, not for actual row comparison. Reviewers reading the mutmut output as a binary pass/fail would call this a survivor; readers who trace the call graph would call it equivalent. -
Many mutations are in code paths only exercised by integration tests.
save_csv_pipelineandcreate_csv_pipelineorchestrate versioning, audit emission, FIM registry updates, and atomic writes. A large fraction of their internal branches are reached only when the actual Splunk container is present. Unit tests mockwl_versions.snapshot_versionandwl_audit.post_audit_eventto side-step those dependencies — which means any mutation INSIDE the calls that hit those mocked functions is unreachable from a unit test. -
The cluster-leverage assumption was optimistic. The five-cluster methodology aims for one test killing N related mutations. In practice, mutmut's mutation generator produces variants the tests don't pin in the way I projected (e.g.
"diff"→"XXdiffXX"IS killed, but"added"→"XXaddedXX"is killed too only because Group B explicitly enumerated that key, while"removed_count"→"XXremoved_countXX"survives because the Group DREQUIRED_KEYSset in test_csv.py only spelled out the top-level envelope, not every nested key).
What this means for the rc1 release framing.
The user's original concern was reputation risk if customers ran mutmut against the published source and saw ~50% survivors. The honest position to take is:
- 53.4% kill rate on
bin/wl_csv.py(a large data-pipeline file with significant integration-only code) is consistent with mutmut-on-Python projects of this shape. - The five-cluster triage documented above tells reviewers what the survivor categories are, so a customer looking at the numbers sees the discipline, not opaque survivors.
- The remaining work (further test additions to push toward 75%) is scoped as a v1.1 item, NOT a v1.0.0-rc1 blocker.
This is more honest than the earlier framing of this section (now corrected) which conflated the methodology TARGET (75% on killable) with the measured RESULT (53.4% on all).
Open items deferred to v1.1.
- Re-run mutmut on
bin/wl_limits.py+bin/wl_trash.pyagainst the post-batch test files (the 16+10 new tests) so we have a three-module measured set. - Triage the 336 wl_csv survivors into "killable / equivalent / integration-only" buckets and write tests for the killable subset.
- Consider whether a coverage-aware mutator (mutmut's
--use-coverage) would skip the integration-only paths and give a more interpretable kill rate.
These have been added to project memory (MEMORY.md) so they
don't have to be rediscovered:
feedback_mutmut_host_mount_hazard.md—:romount or scratch-dir-only writes; verifygit diff bin/is clean before staging.feedback_mutmut_test_selector_match.md— pair MUTATE_PATH and TEST_RUNNER_FILES every run; default selector is wl_validation only.
(Add these files manually when promoting this lesson into project memory — the policy is per-session capture, not auto-extraction.)