test: ratchet mutation score over enumerated decision kernels - #1441
test: ratchet mutation score over enumerated decision kernels#1441devin-ai-integration[bot] wants to merge 7 commits into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. |
|
Review: changes requested — three P1 blockers.
All current checks are green, but the PR-side mutation job only ran its self-tests, reported no affected decision kernels, and ran no mutants. That is not readiness evidence for the new lane. |
ff14f27 to
a19412b
Compare
|
All three P1s fixed in a19412b (branch also rebased onto latest 1. 2. Kernel tests now own their module.
3. Envelope. Both lanes write and upload On readiness: agreed that the PR job's "no affected kernels" run proved nothing. This push touches the kernel registry, so the job runs real mutants on it. Locally |
|
Readiness evidence from this push's PR job (all 23 checks green): the affected lane selected That also gives a real per-shard data point for the weekly budget: 183 mutants ≈ 7m16s here, and the largest shard ( |
|
Re-review at head a19412b: the bench:help-conformance script is fixed and checks are green, but two P1 blockers remain. First, affected test ownership is still a hand-picked list rather than derived/comprehensive: for example src/tests/daemon-error.test.ts directly exercises normalizeError from src/kernel/errors.ts but is absent from both the kernel-errors test ownership and workflow paths. Weakening that test skips the affected mutation run, and the current parity tests cannot detect omissions. Second, issue 1412 requires issue 1430 health/freshness monitoring to land with this new scheduled lane. The envelope is only written after report parsing and ratchet evaluation, so setup/Stryker/report failures can still produce no envelope, and there is no derived cadence monitor to detect a dark lane. Fix ownership derivation/completeness and land the shared health path before readiness. |
Adds a Stryker (vitest runner) mutation lane scoped to the decision kernels, a per-module baseline with tool/config provenance, and a ratchet that only lets scores rise. Non-gating until two consecutive stable weekly sweeps. Refs #1415 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…lysis Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ne envelope - restore bench:help-conformance, broken by a formatting-path edit - kernel test files select their module on PRs (registry `tests` + workflow paths), asserted to reach the kernel through the import graph - every mutation run writes the standard scheduled-lane artifact envelope - move src/utils/__tests__/errors.test.ts beside its source per the mirror rule Refs #1415, #1430 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… monitor Ownership of a kernel's tests is now computed from the static import graph (scripts/mutation/ownership.ts) instead of a hand-listed set, so a test that reaches a kernel indirectly -- src/__tests__/daemon-error.test.ts through src/daemon.ts -- selects that kernel on a PR. The PR lane triggers on every src test and shards the derived modules, keeping wall clock at one module. The lane envelope (#1430) is now written on every exit path with the stage it reached, so a crash before any mutant runs is distinguishable from a lane that never ran. Adds the derived cadence monitor (scripts/lane-health, daily workflow): scheduled lanes are enumerated from .github/workflows/ and reported dark, failing, or never-run against their own cron cadence.
a19412b to
8c1dbe7
Compare
|
Both remaining blockers addressed at 1. Ownership is derived, not hand-picked. The registry's and The honest consequence: reachability is a superset of kill-power, so the derived owner sets are large (kernel-errors 469 test files, the others ~320 each). Any src test change therefore selects several kernels. I took that over any narrowing heuristic, since a narrowing is exactly the false negative you flagged. To keep it affordable, the PR lane is now 2. Envelope on every exit path + a cadence monitor. For the dark-lane half I landed the shared path rather than deferring to #1430: Run against this repo just now, it immediately found a real one: Gates green locally: |
The shard artifacts now carry the lane envelope beside mutation.json, and the merge globbed every .json under the download path, so the ratchet job fed the envelope to the report parser and died after the mutants had already run.
thymikee
left a comment
There was a problem hiding this comment.
Re-reviewed at da4bf16. The derived ownership change closes the prior hand-picked-test omission, including daemon-error.test.ts, and the current GitHub checks plus the focused ownership/workflow/envelope/health tests are green.
Two blockers remain:
-
[P1] Reject incomplete shard sets before writing the aggregate envelope. readShardedReports only requires at least one mutation.json, while summarizeReport creates a zero score for every requested module absent from the downloaded reports. While gating is false, that becomes a non-failing regression, so the ratchet can write a complete/pass aggregate envelope even though a matrix shard failed. Validate that every requested module has a real report/nonzero mutant coverage, or otherwise propagate the shard result, and cover a partial-shard failure.
-
[P1] Extend the envelope boundary to genuine early failures. Argument parsing and provenance/baseline reads occur before the try/finally, so malformed or missing configuration still exits without an envelope. The weekly workflow also runs mutation:test before invoking the runner, so a self-test failure produces no aggregate lane envelope. Ensure these pre-state and pre-run failures create and upload a fail envelope, with tests that exercise them.
Residual risk, not a blocker: issue #1415 requires the weekly lane to complete in under 30 minutes, but the first real weekly run has not happened and shard timeouts remain 45 minutes. Please capture the first scheduled or manual weekly timing as follow-up evidence.
…lures Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Both P1s fixed in [P1] Incomplete shard sets no longer pass. The aggregate now asserts, after scoring, that every requested module actually reported mutants — a module scored from no report is a dead shard, not a 0% score: That throws before [P1] The envelope boundary now covers pre-state and pre-run failures. Argument parsing, For steps that run outside the runner — the weekly Gates: On the 30-minute budget — agreed, still unproven. The 45-minute shard timeout is deliberate headroom so a slow runner reports instead of being killed; I'll dispatch the weekly lane after merge and post real per-shard wall clock as follow-up evidence. |
thymikee
left a comment
There was a problem hiding this comment.
Re-review confirms the missing-shard and early setup-envelope blockers are fixed and tested. One lane-result bug remains: the weekly ratchet can write a pass envelope, then cp/git checkout can fail; the if: failure() fallback calls --fail-envelope, but onlyIfAbsent preserves any existing envelope, so the failed scheduled job is published as passing. Preserve an existing failure, but upgrade an existing pass to fail for a later workflow failure, and add a pass-then-post-step-failure regression.
All head checks are green and the focused suite passes; the kernel-errors shard took ~10m38, but no full Mutation Weekly run exists, so the <30m budget remains unproven residual risk.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Good catch on the pass-then-post-step-failure hole — fixed in
The regression test does the real sequence: a passing Agreed on the residual risk: |
Summary
Adds a mutation-score ratchet over an enumerated set of decision kernels, so "are these tests
load-bearing?" gets a mechanical answer instead of a coverage percentage. Closes #1415 (umbrella
#1412, Track A).
Scope is a registry, not a config file:
scripts/mutation/modules.tsowns the five modules(
src/kernel/errors.ts,src/daemon/ref-frame.ts,src/commands/interaction/runtime/settle.ts,src/utils/scroll-edge-state.ts,src/selectors/), and everything else derives from it —stryker.config.json'smutateglobs are asserted equal tomutateGlobs(), PR-affected selectionmaps changed files through
moduleForFile, and the weekly shard matrix is asserted equal toALL_MODULE_IDS. Modules that spawn subprocesses or wait real time stay out by construction.The ratchet, not Stryker, owns pass/fail (
thresholds.break: null), and it is deliberately dormant:Each baseline entry carries the
strykerVersionand a content hash ofstryker.config.json, which iswhat makes drift separable from test strength. Committed starting point (full sweep, Stryker 9.6.1):
daemon-ref-frame100%,selectors70.56%,interaction-settle68.95%,kernel-errors55.19%,scroll-edge-state28.75% — 2,054 mutants total. A failing check prints the surviving mutants(file:line, mutator, replacement) plus the command to reproduce.
Two things the diff won't explain on its own:
--report-dirbefore ratcheting.Measured locally,
selectorsalone is ~1,280 mutants / ~7 min on 8 cores; a single job would sit onthe 30-minute acceptance budget on a 2-core-class runner. Sharding keeps every job far under it and
the ratchet still sees one full sweep.
scripts/mutation/test-scope.tsasks Vitest for the related tests (vitest relatedover themutated files — the same delegation
pnpm check:affecteduses) and drops three groups it cannotrun: the subprocess-stub tests (real time), non-
src/conformance gates, and thread-hostile tests(Stryker's vitest runner hard-codes
pool: 'threads', whereprocess.chdirthrows and anode:worker_threadsworker inside a worker raises uncaughtMessagePorterrors that kill therunner). Both exclusions are derived from imports, so they cannot rot, and neither can hide a
survivor — a mutant only an excluded test could kill is reported as surviving.
A kernel's tests own the module too:
KERNEL_MODULES.testslists them, so a diff that only weakenssrc/kernel/__tests__/errors.test.tsstill runs that kernel's mutants on the PR. The list is verifiedrather than trusted —
modules.test.tswalks the import graph and fails if a listed test never reachesthe kernel, and separately fails if a mirrored test file of a kernel source is not listed.
(
src/utils/__tests__/errors.test.tsmoved tosrc/kernel/__tests__/errors.test.tsso it mirrors itssource per the topology rule.)
Every run also writes the standard scheduled-lane artifact envelope (#1430) via
scripts/lib/lane-envelope.ts—schemaVersion, commit SHA,tool.stryker,configHash,seed(explicit
null: enumerated input, not randomized), duration,result, per-module scores — uploaded byboth workflows and folded into the weekly step summary, so a lane going dark or a tool bump is visible
without reading logs. It sits in
scripts/lib/so the other scheduled lanes can adopt it.Also extracts the duplicated
parseArgs+--helpboilerplate shared by this script andscripts/check-affected/run.tsintoscripts/lib/cli-args.ts.Validation
pnpm format:check,pnpm lint,pnpm typecheck,pnpm fallowclean; unit suite green (516 files,4,554 tests).
pnpm mutation:test(29 cases) covers the acceptance criteria directly: a lowered scorefails once gating is on, the failure lists surviving mutants, the same lowering only reports before
graduation, regressions keep the high-water baseline, graduation needs two consecutive full sweeps,
affected PR runs never advance it, and provenance drift is not a test-strength regression. Real
Stryker runs produced the committed baseline (per-module, merged) — that is the sweep's own evidence.
Not verified here:
pnpm buildand the Node integration smokes fail on this machine on pristineorigin/maintoo (tsdowncannot importunrun; Node 22.14 has no default.tsstripping), sothose are preexisting environment failures rather than something this PR touches. The weekly
workflow's own wall clock is a projection from local timings until its first scheduled (or manually
dispatched) run.
26 files touched; scope stayed inside the mutation lane plus the small shared-arg extraction.
Link to Devin session: https://app.devin.ai/sessions/97c343f089bb49cf820c2569653f3b35
Requested by: @thymikee