Skip to content

feat(stryker-js): skip if-condition mutants whose block no test runs - #296

Open
systemfsoftware-maker wants to merge 37 commits into
mainfrom
stryker/mutant-quality-l4-uncovered-block
Open

systemfsoftware-maker wants to merge 37 commits into
mainfrom
stryker/mutant-quality-l4-uncovered-block

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Under coverageAnalysis: 'perTest', the condition mutants of an if whose block no test runs are now Ignored as arid-uncovered-block instead of running, as long as that block still reports a NoCoverage mutant in the same run. The test gap stays in the score through the block's own mutants, and the run stops spending test time on condition mutants that only restate that gap.

This is U7 of the mutant-quality plan (docs/plans/2026-10-09-1850-feat-mutant-quality-plan.md), based on main. #287 (diff-scoped planning) and #290 (arid imports) are separate units of the same plan.

When a condition mutant is held

Situation (under perTest) Condition mutants
Block uncovered, no else Ignored, arid-uncovered-block: … naming the block's NoCoverage mutant
Block uncovered, else block run by a test Run: that test executes the condition
Block and else block both uncovered Ignored
Every block mutant fails to compile or is ignored Run, in a second pass after the first
coverageAnalysis off/all, or mutantSetPolicy: 'full' Run
else is not a block (else if …, else stmt) No guard, so they run; an else if chain guards only its last if

How an agent consumes this

  • An Ignored mutant whose statusReason starts arid-uncovered-block: names a NoCoverage mutant id. Act on that id: a test that enters the block brings the condition mutants back on the next run.
  • A plugin Mutant carries guard: { block, inside, alternate? } on condition mutants (ids of the block's emptying mutant, the mutants inside it, and the else block's emptying mutant).
  • An incremental run never reuses an arid-uncovered-block result; it decides it again.
  • stryker plan keeps each condition mutant in the same shard as its block, through anchors.
  • To run every condition mutant: mutator: { mutantSetPolicy: 'full' }.

Review rulings

Applied: the else rule above (14); the guard comes from the innermost if, pinned by a property and a callback-in-condition scenario (2); shard anchors are decided in placement-anchors.workflow.ts, so the mutation glob grades them (3); a command that places one mutant twice is refused (4); a mutant both subsumed and guarded settles exactly once (13); the plan cell keeps heldBy from the planner's decision (6); the Transformer's unreachable failure branch is gone (8); one guard per site (12); isStatic is passed, not recomputed (11).

Not changed:

  • 7: the two result lists are not duplicates. settlementsOf tags remembered, early and checked results differently.
  • 10: shiftedLocationOf stays. make-body-purity refuses the imported shiftedLocation inside the decision body.
  • 9: Transformer.service.ts size, out of scope per ruling.
  • 5, known limit: a large if body becomes one shard unit that may exceed targetSeconds.
  • 15, unmeasured: held-then-kept guard mutants run in a second pass, so they add to the end of a run. Main's Bench lane (feat(repo)!: time every PR's mutation phases against its base #274) measures it.

Shard planning cost (ruling 1)

planShards on one project, one guarded if per 10 mutants (best of 3, local Bun):

Mutants 1k 2k 4k 8k
ms 11 42 158 586

Growth is quadratic (×3.7 per doubling). A keyed index alone cannot replace the fixpoint, because a later mutant can bridge two components already formed; that needs union-find, a redesign. So it stays as is, with these numbers on record.

Validation

  • plugin-interface 300, instrumenter 335, stryker-js 1370 unit tests; tsc, lint and the API report pass. The uncovered-block, shard-merge, plan-shard-reuse, readmit-subsumed and plan integration suites pass (19 scenarios).
  • Each new test was run against a deliberately broken implementation and failed: outer-if owner, ignoring else coverage (property and engine scenario: 2 condition mutants wrongly Ignored), dropped holding-block anchors, a decoder without the duplicate check, and a double hold (10 results for 9 ids).
  • No local mutation run. CI mutation on main grades uncovered-block.workflow.ts, placement-anchors.workflow.ts, plan-shards.workflow.ts and plan-mutant-tests.workflow.ts.

Bench fails on every PR until count-runs.workflow.ts:65 on main reads reason.code (renamed by #283). This branch carries the same one-line fix as #287 and #290.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Integration test compliance (house rule, 2026-10-12)

Rule: an integration test drives the real product through its public surface (the built CLI or a published API a user calls) on a real project, and asserts only user-observable outcomes. Internal fields, private state and mocks of the product path are not allowed, and a pure decision gets a property test instead. Audited per added or changed scenario against origin/main ec1769de2. The audit used CE testing-lens reviewers on DeepSeek V4.1 Flash after MiniMax returned 429; I re-checked every verdict against source.

Rewrite needed: packages/stryker-js/tests/uncovered-block.integration.test.ts, all 7 new scenarios.

  • Why: they call Engine.mutationTestCell with a hand-built RunEnvironmentShape. That is exported but is not a documented user entry: the README documents the CLI and strykerCell. They also assert exit.value.results, in-process result objects, instead of written files.
  • Derived rewrite:
    • Keep the real workspace fixture (writeWorkspace: package.json, vitest.config.ts, src/lib/work.ts, test file).
    • Write the options into the project's stryker config, with the guarded-block-checker loaded as a plugin by name where needed.
    • Spawn the built CLI stryker run (ALLOW_LOCAL_MUTATION=1).
    • Assert the exit code, then read reports/mutation/mutation.json and the progress stream from disk: each mutant's status (Ignored / NoCoverage / run), the arid-uncovered-block: status reason naming the NoCoverage mutant id, and subsumption.
  • Where the pure decisions go: they stay in src/__tests__/uncovered-block.workflow.property.test.ts.

Complies, no change. 7 added scenarios in packages/stryker-js-instrumenter/tests/instrumenter.integration.test.ts. They call the published instrument export with file name and source text and assert the returned mutants' published guard field and statuses.

Not judged (Stream B): incremental-reuse.integration.test.ts. Stream B owns rewriting the reuse suites, and this audit does not touch them. This PR adds one scenario to that suite ("A held condition mutant is re-decided on a repeat run…"). The root decides whether it stays there for Stream B or moves out.

Mutation scheduling law (house rule, 2026-10-12)

Rule: mutation testing runs only in CI, on main, nightly on a schedule or by manual dispatch. It never runs on push or on pull requests, and there is no local mutation testing. Mutation evidence comes only from the CI workflow's own reports. Stream K owns the mutation.yml and kill-matrix.yml triggers. Checked against this head: the diff touches no file under .github/, so this PR makes no trigger change. Applies to the rewrite only:

  • Evidence and waits: not applicable. The body records "No local mutation run" and only names the files CI mutation on main will grade.
  • Open question for the root: the uncovered-block rewrite above spawns the built stryker run on a fixture project with ALLOW_LOCAL_MUTATION=1. That is a product test of the CLI, the same pattern as 9 test files on main, and it produces no mutation evidence for this repo. Under "do not run Stryker locally", running it under local vitest still starts Stryker on the fixture. Pending ruling.

Review receipt (ce-code-review, run 20261012-204311-e7f6b495)

The ## Review rulings section above records the earlier rulings. It names no reviewer personas, so this receipt is the persona review of the current diff.

  • Diff reviewed: head fe9bc50b3317a65884c5789262beb2bffb4b40a1 against merge-base 38924e53f with origin/main ee7038a5c. Scope local-aligned, full depth (2746 executable lines).
  • Personas that ran: omp correctness-reviewer, adversarial-reviewer, security-reviewer, and five generalist-reviewer runs carrying the project-standards, testing, maintainability, api-contract and learnings lenses. api-contract ran because the published plugin-interface Mutant gains guard and the ignore-rule vocabulary grows.
  • Models: each reviewer was dispatched to MiniMax-M3.1-Flash-Preview. Where MiniMax returned 429 Token Plan usage limit reached (2056), omp fell back to openrouter/deepseek/deepseek-v4.1-flash and DeepSeek V4.1 Flash wrote that lens's return. The root accepted DeepSeek V4.1 Flash as the non-Claude reviewer family for these receipts. The table names the model that wrote each return, read from that agent's transcript (model_change events and per-turn model). Exception: the correctness lens never hit the 429 and ran on MiniMax throughout. No external cross-model peer job was started; the adversarial lens ran in-process.
Lens Agent Model that wrote the return Transcript
correctness correctness-reviewer MiniMax-M3.1-Flash-Preview no 429; all 36 turns on MiniMax
adversarial adversarial-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (43 turns)
security security-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (20 turns)
project-standards generalist-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (56 turns)
testing generalist-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (54 turns)
maintainability generalist-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (63 turns)
api-contract generalist-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (54 turns)
learnings generalist-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (28 turns)
validator correctness-reviewer DeepSeek V4.1 Flash MiniMax returned 429; DeepSeek V4.1 Flash wrote the return (34 turns)
merge and report leaves docs-engineer claude-haiku-4-5 CE orchestration only; no findings authored
# Sev Finding (lens) Disposition Source
1 P1 An else block with no BlockStatement mutant still leaves the guard attached (correctness) Reject. alternateOf maps an else span with no block mutant to None through onSome, and Option.all then drops the whole site, so no guard is attached. Only a site with no else reaches the cited onNone line. The property ASiteWhoseElseHasNoBlockStatementMutantGuardsNothing pins this. The suggested edit would drop every site that has no else. packages/stryker-js-instrumenter/src/guard-relation.workflow.ts:118-125,131-135; guard-relation.workflow.property.test.ts:279-282; validator rejected
2 P1 New wire rule id arid-uncovered-block ships without a stream version bump (api-contract) Accept. Blocks merge. The id joins RULE_IDS. IgnoreStatusReasonText is S.String.check(reasonPatternOf(RULE_IDS)), and the cli-contract stream checks every Ignored line's statusReason against it. StreamSchemaVersion stays '8.0' and the branch changes nothing under packages/stryker-js-cli-contract, so a released consumer reading an 8.0 stream rejects these lines. #262 bumped 6.0 → 7.0 when it put rule ids on the wire, and #273 bumped 7.0 → 8.0. Fix: raise StreamSchemaVersion to '9.0', regenerate the contract documents, and add a cli-contract changeset (minor while 0.x, per BREAK-1). ignore-rule.schema.ts:22,187; stryker-js-cli-contract/src/run-event.schema.ts:113,117; stream-version.schema.ts:3; git show 4bf59526d add6faa73 -- …/stream-version.schema.ts; validator confirmed
3 P2 No test covers a guard site in an embedded script region (testing) Accept. Every guard assertion instruments a whole file at offset 0, so the region offset shift is never exercised. Fix: add a <script> region holding if (a) { f(1) } to mutant-span-slice.integration.test.ts and assert guard.block. Transformer.service.ts:1050; mutant-span-slice.integration.test.ts:69-103; validator confirmed
4 P2 Shard-anchor properties pass vacuously on drawn ids (testing) Accept. anchors and id are drawn independently as 16-hex ids, so no drawn anchor names another mutant. anchorComponentsToFixpoint never merges, and dropping the merge leaves both laws green. Project-standards notes that this also leaves the merge code unkilled in the mutation lane. Fix: inject real ids into anchors, as withHeldMutants does for guard.inside, and include a three-mutant bridge. src/__tests__/plan-shards.workflow.property.test.ts:99-101; placement-anchors.workflow.property.test.ts:23; validator confirmed
5 P2 Plan bucket counts are re-enumerated in three modules (maintainability) Accept. This PR had to add heldGuards to all three sums, which is the drift the finding describes. Fix: one derived total beside MutationTestPlan, read at all three sites. run/mutation-test-plan.cell.ts:324; mutant-settlement.ts:336-337; deferrable-dry-run.cell.ts:50-52; validator confirmed
6 P2 New fixture worker copies the checker bootstrap verbatim (maintainability) Accept. subsumption-checker/serve.mjs already factors this bootstrap as serveChecker(answerOf). Reuse it instead of a third copy. tests/__fixtures__/guarded-block-checker/worker.mjs:1-38; subsumption-checker/serve.mjs:1-31; validator confirmed
7 P3 Pass-through guardOf wrapper (maintainability) Accept. It is index.get(id) with one call site. Inline it. Transformer.service.ts:1374-1377,1513; validator confirmed

Suppressed at anchor 50 (not validated):

  • else block not anchored to its shard (adversarial, P2): guardBlockOf anchors a condition mutant to guard.block only, but the hold decision also reads guard.alternate's coverage (alternateIsUnexecuted). If the else block's mutant lands in another shard, a sharded run may decide differently from an unsharded one. I did not confirm this. The candidate fix adds guard.alternate to guardBlockOf, with a 1-vs-2-shard scenario. Root decision. Source: placement-anchors.workflow.ts:58-62; plan-mutant-tests.workflow.ts:363-374.
  • Directive-ignored else blocks (correctness, P2): blockMutantIndex may not index an else block whose BlockStatement mutant a directive removed. Not confirmed; it shares Bump vitest from 4.1.11 to 5.0.0 in the testing-major group across 1 directory #1's mechanism, which drops such a site.

Cross-PR note: #304 adds constant-collection-size to RULE_IDS without a stream bump either, so both PRs need the #2 fix. The second of the two to land has to bump past whatever the first one sets.

Review receipt (final head 54f35c353)

  • Final head: 54f35c353f2a3df7fc5495eed22cbad342df8f9e. Since the previous receipt head 737f8eb3d this branch merged origin/main twice: 795170b8c (main 449e9cd53; the cli-contract README keeps main's two codecs at 9.0) and bb52fa852 (main b08379ddd, feat(stryker-js-typescript-checker): skip importer re-checks for edits inside function bodies #286 and perf(stryker-js): enter the instrument phase before instrumenting (T10 U1) #348; empty --cc). Non-merge commits are the fixes named in the tables below: ab17ff32a, b5fae6667, c71708de5, ef6d59b01, c03fcc0b8, 6ecbe152d, 330acece0, ff9284b92, abaa34fba, 12aa557c2, 54f35c353.
  • Rounds since 737f8eb3d: 4-9 below, newest first: full-path runs on bb52fa852 and ff9284b92, and four correctness lenses over the fix ranges. The last lens is over 54f35c353.
  • Verdict on 54f35c353: every validator-confirmed finding has a fixing commit except round 7 Bump vitest from 4.1.11 to 5.0.0 in the testing-major group across 1 directory #1, which is open for root. Round 4's one violate pack row is fixed and round 7's matrix has none. Round 8 found a fresh-vs-incremental divergence introduced by 330acece0 inside this PR; 54f35c353 fixed it with a built-CLI incremental scenario that was red first. The last lens (round 9) found two ADVISORYs, recorded.
  • Models: read from the omp transcripts. Every reviewer, validator and fix-lens turn in rounds 4-9 was served by DeepSeek V4.1 Flash (openrouter/deepseek/deepseek-v4.1-flash); MiniMax and Grok served no turn. Lenses without a dedicated agent ran on generalist-reviewer with the persona file. Merge and report leaves are docs-engineer on claude-haiku-4-5 and author no findings.
  • Cross-model peer: not run. No different-provider peer CLI is installed.
  • CI on 54f35c353: Commitlint 38485156610, Changeset Check 38485156948 and Nix 38485156654 passed. CI 38485156614 failed in checker-parity-report (job 115598325425): slower-than-main branch 269346.24 ms, main 267100.15 ms (+0.8%). Lane, checker-parity-changes and checker-parity 1-6 passed; check was still running when the watch returned. The gate came with feat(stryker-js-typescript-checker): skip importer re-checks for edits inside function bodies #286 and failed 21 of the last 22 PR runs that reached it, unrelated branches included. The earlier Changeset Check red on 737f8eb3d (paths gate refused) was fixed on main by ci(repo): gate change intents on turbo's typecheck task graph #335 and is green here.
  • Open for root: (1) round 7 Bump vitest from 4.1.11 to 5.0.0 in the testing-major group across 1 directory #1 (P1): held condition mutants are Ignored even where their covering test would kill them; (2) the checker-parity-report speed gate.

Round 9: correctness lens over 54f35c353 (run 20261013-233526-0cd092d0)

  • Commit: 54f35c353 (round 8 finding 2).
  • Model: correctness-reviewer, DeepSeek V4.1 Flash, 60 turns, all turns; no MiniMax or Grok call (X296N.jsonl).
  • Result: two ADVISORYs, recorded, no change: the fixture config pins incremental: true, which is already the default; the property model folds the uncovered check into both witness branches (equal to production for every generated input). The lens traced every branch and confirms an Ignored block mutant never witnesses its guard on fresh or incremental runs, a remembered NoCoverage witness still holds it (330acece0's case), and the else witness follows the same helper. Residual: a witness absent from the plan and not remembered (dropped, or outside a --mutant rerun) no longer holds the guard; no scenario covers that rerun case.

Round 8: correctness lens over ff9284b92..12aa557c2 (run 20261013-230452-caaabb43)

  • Commits: abaa34fba, 12aa557c2.
  • Model: correctness-reviewer, DeepSeek V4.1 Flash, 52 turns, all turns; no MiniMax or Grok call (X296M-2.jsonl).
  • Findings: MEDIUM, a block mutant Ignored by an exclusion or directive, once reused on an incremental run, was absent from the plan and counted as a witness (onNone => true from 330acece0), so the rerun settled the condition mutants Ignored while a fresh run ran them. Accept. Fixed by 54f35c353: MutantTestPlanCommand carries each reused mutant's remembered status, and an absent witness holds the guard only when it was remembered NoCoverage. The new built-CLI scenario failed before (aridReasons 2 on the second run) and passes after. MEDIUM, the rewritten Guard sentence held only for fresh runs: resolved by the same commit, which makes it true for every run.

Round 7: full-path run on ff9284b92 (run 20261013-223220-e00fef28, base b08379ddd)

Lens Agent Model that served it Transcript evidence Transcript
adversarial adversarial-reviewer DeepSeek V4.1 Flash DeepSeek served 36 turns, all turns; no MiniMax or Grok call R296AdversarialK-2.jsonl
api-contract generalist-reviewer (persona api-contract-reviewer) DeepSeek V4.1 Flash DeepSeek served 71 turns, all turns; no MiniMax or Grok call R296ApiK-2.jsonl
correctness correctness-reviewer DeepSeek V4.1 Flash DeepSeek served 62 turns, all turns; no MiniMax or Grok call R296CorrectnessK-2.jsonl
learnings generalist-reviewer (persona learnings-researcher) DeepSeek V4.1 Flash DeepSeek served 21 turns, all turns; no MiniMax or Grok call R296LearningsK-2.jsonl
maintainability generalist-reviewer (persona maintainability-reviewer) DeepSeek V4.1 Flash DeepSeek served 35 turns, all turns; no MiniMax or Grok call R296MaintainabilityK-2.jsonl
project-standards generalist-reviewer (persona project-standards-reviewer) DeepSeek V4.1 Flash DeepSeek served 53 turns, all turns; no MiniMax or Grok call R296ProjectK-2.jsonl
security security-reviewer DeepSeek V4.1 Flash DeepSeek served 18 turns, all turns; no MiniMax or Grok call R296SecurityK-2.jsonl
testing generalist-reviewer (persona testing-reviewer) DeepSeek V4.1 Flash DeepSeek served 33 turns, all turns; no MiniMax or Grok call R296TestingK-2.jsonl
validator correctness-reviewer DeepSeek V4.1 Flash DeepSeek served 13 turns, all turns; no MiniMax or Grok call V296K.jsonl
merge, report leaves docs-engineer claude-haiku-4-5 CE orchestration only; author no findings M296K.jsonl (43 turns)
# Sev Location Finding (lens) Disposition Validator
1 P1 stryker-js/src/run/mutant-settlement.ts:245 Arid ruling ignores condition mutants the covering test kills (adversarial) Open for root. Confirmed by the validator; owner human. Under perTest, a held condition mutant is settled Ignored (arid-uncovered-block) without running, although the diff's own fixture shows the covering test kills it when it runs. Ignored sits outside totalValid, so the score loses a detection. Two choices: run held mutants against their own covering tests in the second pass, as for GuardKept (no score change, more work), or settle them NoCoverage instead of Ignored (keeps them in the score, no extra work). The PR's premise (R10, 'duplicate test goals') is to skip them, so this is the unit's design choice confirmed
2 P2 stryker-js-instrumenter/src/Transformer.service.ts:1108 Guard-site AST derivation added to 1589-line Transformer (maintainability) Reject (validator) rejected: guardSiteOf and its helpers do sit at Transformer.service.ts:1090-1135 of a now-1589-line file, but 'move the derivation to a new module' is a file-organization preference with no incorrect behavior, and style/organization is outside the audit's scope.
3 P2 stryker-js-instrumenter/tests/instrumenter.integration.test.ts:1643 Guard scenarios appended to a 2040-line test file (maintainability) Reject (validator) rejected: The guard scenarios are at instrumenter.integration.test.ts:1643+ of a 2040-line file as cited, but 'move ten passing scenarios into their own file' is a test-organization preference, not a defect in behavior.
4 P2 stryker-js/src/__tests__/plan-mutant-tests.workflow.property.test.ts:215 Fourteen positional S.Boolean slots in guardCommandArb (maintainability) Reject (validator) rejected: The 16-name destructure at plan-mutant-tests.workflow.property.test.ts:218-260 binds correctly to its 2 non-boolean plus 14 Arbitrary.schema(S.Boolean) draws, so no mis-binding exists today; ...
5 P2 stryker-js/src/plan-request.cell.ts:285 No test drives a guard through the plan seam's anchors mapping (testing) Reject (validator) rejected: The seam the finding says is untested does carry guards into anchors (plan-request.cell.ts:285-302 feeds placementAnchors output into PlannedMutant.anchors, and plan-shards.workflow.ts:188-249 groups mutants by anchor keys), so the claim reduces to missing tes ...
6 P3 stryker-js-instrumenter/src/apply-guards.ts:8 Unused export guardsByMutantId in apply-guards.ts (maintainability) Accept. Fixed by abaa34fba confirmed
7 P3 stryker-js-plugin-interface/src/Mutant.schema.ts:131 Guard doc claims an Ignored block mutant witnesses the guard (api-contract) Accept. Fixed by 12aa557c2 (with #9) confirmed
9 P3 stryker-js-plugin-interface/src/Mutant.schema.ts:131 Guard schema doc contradicts the uncovered-block witness rule (learnings-researcher) Accept. Fixed by 12aa557c2 (same sentence as #7; round 8 then found the sentence held only for fresh runs, and 54f35c353 made it true for incremental runs too) confirmed
10 P3 stryker-js/src/plan-mutant-tests.workflow.ts:368 Absent-witness branch of the guard hold decision is untested (testing) Not selected (confidence 75). Addressed by 54f35c353: guardCommandArb now draws an absent block witness remembered as NoCoverage or as Ignored, from the production schemas not selected

Suppressed by CE below its confidence gate (recorded, not findings): Public guard field is stripped before any consumer sees it (P3, correctness, confidence 50).

Pack rule matrix (learnings lens, round 7)

Pack Rule Enrolled Verdict Evidence
cell-architecture pure-decision-workflows.md yes comply packages/stryker-js-instrumenter/src/guard-relation.workflow.ts:1023-1028 (Match.value(...).pipe(Match.when(...), Match.exhaustive)); packages/stryker-js/src/uncovered-block.workflow.ts:112-121 (Workflow.make decide o...
cell-architecture decode-never-cast.md yes not-applicable no changed line casts decoded external data; Location.schema.ts:36-49 uses Match/typed helpers, not as
cell-architecture pipeable-dual-parity.md yes not-applicable new duals (packages/stryker-js-instrumenter/src/apply-guards.ts:821-829 withGuards; Location.schema.ts shiftedLocation; Mutant.schema.ts uncoveredBlockStatusReason) are plain module functions, not blueprint/handle ope...
cell-architecture four-channel-contracts.md yes not-applicable no public Cell signature changed in the diff
cell-architecture sandwich-phase-order.md yes not-applicable no sandwich interaction authored in the diff
cell-architecture service-and-layer-boundaries.md yes not-applicable no Context.Service/Layer added or rebound
cell-architecture data-only-schema-classes.md yes comply new TaggedClass commands/decisions carry only the repository-wide static readonly [Workflow.InstrumentationBrand]/TypeId (guard-relation.workflow.ts:865-889, uncovered-block.workflow.ts:22-40)
schema-laws tagged-unions-over-state-by-presence.md yes comply Mutant.schema.ts:164 adds guard: S.optional(Guard); Guard's alternate (Mutant.schema.ts:126-128) is genuinely optional data (present iff the if has an else block mutant), not a mutually-exclusive status encoded ...
schema-laws invariants-as-refinements.md yes comply Mutant.schema.ts:120-133 types block/inside/alternate as the branded MutantId, not bare string
schema-laws cross-field-invariants-as-struct-checks.md yes comply Mutant.schema.ts:165-174 keeps the struct-level filters (statusReason implies status; subsumption implies Ignored); the new guard field carries no cross-field relation that consuming code compares
schema-laws tests-own-no-schemas.md yes comply changed tests decode with production schemas (packages/stryker-js/src/tests/incremental-diff.workflow.property.test.ts:37-38 uses Mutant.IgnoreStatusReason); no test-local schema added
schema-laws refusals-beside-generated-laws.md yes not-applicable no refinement/pattern/brand added to an existing schema; arid-uncovered-block is a new literal member of IgnoreRuleId (ignore-rule.schema.ts:23)
package-topology surface-changes-are-versioned.md yes comply changesets declare the surface change (arid-uncovered-block.md: major stryker-js; arid-uncovered-block-guard.md: minor instrumenter+plugin-interface; stream-9-arid-uncovered-block.md: minor cli-contract); StreamSchema...
package-topology public-signature-types.md yes comply Mutant.Guard and Mutant.uncoveredBlockStatusReason are exported through the plugin-interface surface and mirrored in etc/stryker-js-plugin-interface.api.md
package-topology one-access-path.md yes comply Guard/uncoveredBlockStatusReason are exported once from Mutant.schema.ts and re-exported via src/Mutant/mod.ts
package-topology declared-entry-points.md yes not-applicable no subpath added, removed or renamed
package-topology condition-branch-agreement.md yes not-applicable no resolution-condition/entry restructuring in the diff
boundary-testing expected-values-never-come-from-the-subjects-output.md yes comply contract/stream.schema.json and etc/stryker-js-plugin-interface.api.md change alongside their generators, but both are drift-gate artifacts regenerated from the codec, not an independent oracle the tests compare again...
boundary-testing no-mocks-on-internal-glue.md yes comply new fixtures packages/stryker-js/tests/fixtures/guarded-block-checker/ and subsumption-checker/ are real subprocess checker plugins driven through the CLI entry point in uncovered-block.integration.test.ts, not un...
boundary-testing real-system-oracles.md yes comply uncovered-block.integration.test.ts runs the real instrumenter/planner over on-disk temp projects via the fixture workers
boundary-testing port-fakes-pass-the-port-contract.md yes not-applicable no port fake added
boundary-testing pin-dependency-semantics.md yes not-applicable no third-party dependency adopted/upgraded/wrapped
boundary-testing fake-and-real-store-laws.md yes not-applicable no store port touched
boundary-testing staged-protocol-evidence.md yes not-applicable no protocol lifecycle wrapper touched
mutation-discipline suppress-by-shape-in-an-ignorer.md yes not-applicable the diff adds a coverage-derived Ignored rule id (ignore-rule.schema.ts:23, Mutant.schema.ts:135-143), neither a // Stryker disable comment, an excluded mutator, nor a shape-keyed ignorer; the rule's prohibitions ar...
mutation-discipline kill-survivors-with-contract-properties.md yes not-applicable no survivor-killing test written
mutation-discipline unkillable-survivors-are-dead-code.md yes not-applicable no baseline row or dead-code deletion in the diff
ci-wall-clock (all rules) yes not-applicable no changed file under .github/ or any CI-facing flake/workflow/action; only a test/e2e test file changed (test/e2e/tests/failing-run.e2e.test.ts:39 schemaVersion 8.0->9.0)
turbo-task-graph selection-and-caching-go-through-turbo.md yes not-applicable turbo.json and all CI task-selection code are unchanged in the diff

Round 6: correctness lens over ff9284b92 (run 20261013-214250-233d6bb2)

  • Commit: ff9284b92.
  • Model: correctness-reviewer, DeepSeek V4.1 Flash, 28 turns, all turns; no MiniMax or Grok call (X296L.jsonl).
  • Result: 0 findings.

Round 5: correctness lens over bb52fa852..330acece0 (run 20261013-212933-b2b8e363)

  • Commits: ab17ff32a, b5fae6667, c71708de5, ef6d59b01, c03fcc0b8, 6ecbe152d, 330acece0.
  • Model: correctness-reviewer, DeepSeek V4.1 Flash, 42 turns, all turns; no MiniMax or Grok call (X296J.jsonl).
  • Finding: LOW, the test helper mutantIsOpenById read an absent witness as closed, contradicting 330acece0. Accept. Fixed by ff9284b92 (the law and helper are deleted; round 8 later showed 330acece0's own rule was too wide, fixed by 54f35c353).

Round 4: full-path run on bb52fa852 (run 20261013-203109-28ea241e, base b08379ddd)

Lens Agent Model that served it Transcript evidence Transcript
adversarial adversarial-reviewer DeepSeek V4.1 Flash DeepSeek served 40 turns, all turns; no MiniMax or Grok call R296AdversarialJ.jsonl
api-contract generalist-reviewer (persona api-contract-reviewer) DeepSeek V4.1 Flash DeepSeek served 39 turns, all turns; no MiniMax or Grok call R296ApiJ.jsonl
correctness correctness-reviewer DeepSeek V4.1 Flash DeepSeek served 65 turns, all turns; no MiniMax or Grok call R296CorrectnessJ.jsonl
learnings generalist-reviewer (persona learnings-researcher) DeepSeek V4.1 Flash DeepSeek served 34 turns, all turns; no MiniMax or Grok call R296LearningsJ.jsonl
maintainability generalist-reviewer (persona maintainability-reviewer) DeepSeek V4.1 Flash DeepSeek served 37 turns, all turns; no MiniMax or Grok call R296MaintainabilityJ.jsonl
project-standards generalist-reviewer (persona project-standards-reviewer) DeepSeek V4.1 Flash DeepSeek served 51 turns, all turns; no MiniMax or Grok call R296ProjectJ.jsonl
security security-reviewer DeepSeek V4.1 Flash DeepSeek served 36 turns, all turns; no MiniMax or Grok call R296SecurityJ.jsonl
testing generalist-reviewer (persona testing-reviewer) DeepSeek V4.1 Flash DeepSeek served 38 turns, all turns; no MiniMax or Grok call R296TestingJ.jsonl
validator correctness-reviewer DeepSeek V4.1 Flash DeepSeek served 24 turns, all turns; no MiniMax or Grok call V296J.jsonl
merge, report leaves docs-engineer claude-haiku-4-5 CE orchestration only; author no findings M296J.jsonl (28 turns), P296J.jsonl (21 turns)
# Sev Location Finding (lens) Disposition Validator
1 P1 .changeset/arid-uncovered-block.md:2 stryker-js changeset bumps minor for a breaking stream move (project-standards) Accept. Fixed by ab17ff32a confirmed
2 P2 stryker-js-instrumenter/src/guard-relation.workflow.ts:168 Guards attach to any mutant inside an if test span (correctness) Accept. Fixed by c03fcc0b8 confirmed
3 P2 stryker-js-instrumenter/src/Location.ts:103 Line-shift operation added to unsuffixed Location.ts, not its schema (maintainability) Accept. Fixed by 6ecbe152d confirmed
4 P2 stryker-js-instrumenter/tests/instrumenter.integration.test.ts:1644 Guard scenarios grow the 1642-line instrumenter suite past 1995 lines (maintainability) Reject (validator) rejected: The cited evidence (8 scenarios appended at instrumenter.integration.test.ts:1643-1995, base 1642 lines per git show b08379ddd:...|wc -l) proves a large test file, not a defect or a violated rule: no AGENTS.md/ADR/plan rule sets a line bar for `tests/*.inte ...
5 P2 stryker-js/src/__tests__/plan-shards.workflow.property.test.ts:346 planShards anchor merge never exercised by new properties (testing) Accept. Fixed by ef6d59b01 confirmed
6 P2 stryker-js/src/plan-mutant-tests.workflow.ts:366 Excluded BlockStatement mutant reads uncovered; covered condition mutants get ignored (adversarial) Accept. Fixed by b5fae6667 and 330acece0 (the second commit covers a reused witness absent from an incremental plan) confirmed
7 P2 stryker-js/src/plan-shards.workflow.ts:235 Anchor merge reaches connected components through a recursive fixpoint (maintainability) Reject (validator) rejected: The cited code proves no incorrect behaviour: absorbAnchorComponent (plan-shards.workflow.ts:200-217) first-match merges and anchorComponentsToFixpoint (:235-240) iterates until the class count stops shrinking, and the reviewer's own simulation (4000 confi ...
8 P3 stryker-js/tests/__fixtures__/uncovered-block-report.schema.ts:3 Test fixture declares its own report schema instead of the published one (learnings-researcher) Accept. Fixed by c71708de5 (not selected by CE, fixed because the enrolled pack row below is a violate) not selected

Pack rule matrix (learnings lens, round 4)

Pack Rule Enrolled Verdict Evidence
cell-architecture pure-decision-workflows.md yes comply guard-relation.workflow.ts:170-191, placement-anchors.workflow.ts:96-104, uncovered-block.workflow.ts:73-81 -- Workflow.make with error: S.Never and Match.exhaustive over policy
cell-architecture data-only-schema-classes.md yes comply guard-relation.workflow.ts:17-49 -- command class for Workflow.make plus two decision-union variants; no instance methods/statics added
cell-architecture decode-never-cast.md yes comply mutation-test-plan.cell.ts heldByOf uses S.decodeOption(Mutant.Guard); placement-anchors.workflow.ts:10-14 decodes via S.Struct -- no unchecked cast introduced
cell-architecture single-namespace-barrel.md yes comply packages/stryker-js-plugin-interface/src/Mutant/mod.ts -- named exports Guard and uncoveredBlockStatusReason added, no wildcard
cell-architecture four-channel-contracts.md yes not-applicable no Cell<...> signature or Error class changed; new modules are Workflow values
cell-architecture pipeable-dual-parity.md yes not-applicable the new duals (Location.ts shiftedLocation, apply-guards.ts withGuards, Mutant.schema.ts uncoveredBlockStatusReason, mutation-test-plan.ts plannedTotalOf) are module functions, not blueprint/handle combinators
schema-laws tests-own-no-schemas.md yes violate packages/stryker-js/tests/fixtures/uncovered-block-report.schema.ts:3,8 -- a *.schema.ts under tests/ re-declaring a published report shape (Report.MutationTestResult)
schema-laws tagged-unions-over-state-by-presence.md yes comply Mutant.schema.ts:120-127 -- Guard.alternate is a genuinely optional detail (absent the same way whenever there is no else), not an optional correlated with a discriminant; GuardRelationDecision/PlacementAnchorDecision/UncoveredBlockRulin...
schema-laws invariants-as-refinements.md yes comply Guard.block/inside/alternate use branded Mutant.MutantId; BlockSettlement.status uses Mutant.MutantStatusSchema -- no bare primitive in a domain position
schema-laws cross-field-invariants-as-struct-checks.md yes comply Mutant.schema.ts:161-176 -- the existing statusReason/subsumption makeFilter is unchanged; the new guard field adds no two-field rule left to consumers
schema-laws refusals-beside-generated-laws.md yes not-applicable the new schemas (Guard, GuardSiteSchema, GuardMutantSchema, AnchoredMutantSchema, HeldGuard, BlockSettlement) carry no refinement with a rejection boundary
schema-laws arbitrary-filter-floors.md yes comply guard-relation.workflow.property.test.ts uses Arbitrary.schema over the new structs, which add no rejection-sampling check
schema-laws law-failure-is-a-codec-defect.md yes not-applicable no transformation/custom codec added; guard-relation.workflow.ts and uncovered-block.workflow.ts are plain decisions
package-topology surface-changes-are-versioned.md yes comply stream-version.schema.ts 8.0->9.0 ships with @systemfsoftware/stryker-js-cli-contract minor (0.x), matching the prior 3.0/6.0 precedent recorded in packages/stryker-js-cli-contract/CHANGELOG.md; the plannedMutant.placementKey->anchors re...
package-topology enumerated-entry-modules.md yes comply packages/stryker-js-plugin-interface/src/Mutant/mod.ts -- additions are listed named exports
package-topology one-access-path.md yes comply Guard and uncoveredBlockStatusReason are exported from a single entry
package-topology public-signature-types.md yes comply packages/stryker-js-plugin-interface/etc/stryker-js-plugin-interface.api.md updated with Guard
package-topology condition-branch-agreement.md yes not-applicable no exports-map condition branch changed
package-topology declared-entry-points.md yes not-applicable no subpath/entry added or renamed
boundary-testing real-system-oracles.md yes comply packages/stryker-js/tests/uncovered-block.integration.test.ts runs the built binary in a real Node process and reads the on-disk report
boundary-testing expected-values-never-come-from-the-subjects-output.md yes comply the oracle asserts laws between views of the report (e.g. named ids are NoCoverage) rather than a golden file it also generates
boundary-testing no-mocks-on-internal-glue.md yes comply no mocks; guarded-block-checker/worker.mjs and checker-worker.mjs are real plugin workers driven over RPC
boundary-testing port-fakes-pass-the-port-contract.md yes not-applicable the new checker fixtures are test harness plugins, not production port fakes
mutation-discipline suppress-by-shape-in-an-ignorer.md yes comply arid-uncovered-block is an automatic status reason computed in uncovered-block.workflow.ts, not a // Stryker disable comment or an excluded mutator
mutation-discipline kill-survivors-with-contract-properties.md yes not-applicable the added property suites are invariants over pure workflows, not tests added to kill a reported survivor
mutation-discipline unkillable-survivors-are-dead-code.md yes not-applicable no baseline row added; no survivor written reason
ci-wall-clock (none) yes not-applicable the diff changes no .github/workflows or composite action
turbo-task-graph selection-and-caching-go-through-turbo.md yes not-applicable no turbo.json change; the incremental-diff change is the mutation tool's own finer-grained record, which the rule places on its closed list (carried, never used to decide which turbo tasks run)

Violate rows:

Previous receipt header (head 737f8eb3d), unchanged

  • Final head: 737f8eb3ddab015a8ed87aa206d37f14872d43fc. It merges origin/main 381b3f5b1 into cca787584 with an empty combined diff (git show --cc 737f8eb3d prints no file). The earlier merge e071bd047 (main 117bff023) resolved conflicts in Transformer.service.ts, ignore-rule.schema.ts and uncovered-block.integration.test.ts; round 3 reviewed exactly those resolutions.
  • CI on 737f8eb3d: CI 38364378714 passed every job (check, checker-parity 1-6, checker-parity-report). Changeset Check 38364378942 fails for the inherited reason recorded below; Bench is outside this unit.
  • Inherited red: every PR's Changeset Check since 07:28Z fails with paths gate refused: turbo.json makes turbo the source of change evidence — set "gate": { "strategy": "turbo" } in release.jsonc. The reusable pnpm-release-management workflow at main (2b8955578) now refuses main's release.jsonc:14 ("gate": { "strategy": "paths" }). This PR does not touch release.jsonc. Main fixed it in ci(repo): gate change intents on turbo's typecheck task graph #335 (1c9f94074, "gate": { "strategy": "turbo", "task": "typecheck" }); this head predates ci(repo): gate change intents on turbo's typecheck task graph #335, so the check clears on the PR's next merge of main.
  • Rounds: (1) full-path run 20261013-035820-4d26adec on 2daf9c7de; (2) one correctness lens over its five fix commits; (3) one correctness lens over merge e071bd047's conflict resolutions. An earlier wave (20261013-034329-2ec9dcad, on daf91aeb5) ran its lenses but never reached merge or validation; round 1 superseded it on a head that contains daf91aeb5.
  • Verdict on 737f8eb3d: every confirmed finding of round 1 is fixed on this head, and the two rejected findings keep the validator's reason. Round 1's pack rule matrix has no violate row. Round 2 raised one P3 at anchor 50, not adopted with the evidence below.
  • Models: read from the omp transcripts. Every reviewer and validator turn was served by DeepSeek V4.1 Flash (openrouter/deepseek/deepseek-v4.1-flash); no turn in rounds 1-3 reached MiniMax. Grok served no lens. Merge and report leaves are docs-engineer on claude-haiku-4-5 and author no findings.
  • Cross-model peer: not run. No different-provider peer CLI is installed (codex, claude, grok, cursor-agent and opencode are absent).

Round 3: one correctness lens over merge e071bd047 (run 20261013-080657-2aeb415b)

  • Model: correctness-reviewer, DeepSeek V4.1 Flash, 42 turns, all DeepSeek; no MiniMax call (X296M.jsonl).
  • Result: 0 findings. For each conflicted file the merge equals the exact union of the two parents' contributions (line counts match per side), plus one adaptation: Engine.RunEnvironment.stage → layerStage in the uncovered-block suite, matching main's rename (refactor(repo): enforce cell-architecture layer placement and fix the six refused services #321). No RunEnvironment.stage call survives at e071bd047 or the head. Testing gap recorded: no test pins that every arid rule description keeps main's ARID_CALLEE/KEEP_ALL suffixes.

Round 2: one correctness lens over the fix commits (run 20261013-074248-c2898301)

# Sev Location Finding Disposition
R1 P3 (anchor 50, advisory) packages/stryker-js/tests/__fixtures__/checker-worker.mjs:17 Shared checker fixture delays the guarded-block checker by CHECK_DELAY (5 ms) Not adopted. The guarded-block worker's answers are a pure function of mutatorName (answerOf, unchanged), and the engine awaits every check answer, so a 5 ms latency changes no status or reason. The suites passed after 413e9af81 (uncovered-block, readmit-subsumed, compile-error-reuse, incremental-reuse, run-event-stream and the plan laws: 7 files, 80 tests on the merged head).

Residual risks recorded by the lens: the rewritten suite's fixture no longer sets incremental: false (each scenario runs from a fresh temp dir, so nothing is reused); no scenario drives a condition mutant whose guard names an Ignored (excluded or directive-disabled) block mutant through the engine.

Round 1: full-path run on 2daf9c7de (run 20261013-035820-4d26adec, base 251201755)

Lens Agent Model that served it Transcript evidence Transcript
correctness correctness-reviewer DeepSeek V4.1 Flash 65 turns, all DeepSeek; no MiniMax call R296Correctness-3.jsonl
adversarial adversarial-reviewer DeepSeek V4.1 Flash 31 turns, all DeepSeek; no MiniMax call R296Adversarial-3.jsonl
security security-reviewer DeepSeek V4.1 Flash 27 turns, all DeepSeek; no MiniMax call R296Security-3.jsonl
project-standards project-standards-reviewer DeepSeek V4.1 Flash 47 turns, all DeepSeek; no MiniMax call R296Project-2.jsonl
testing testing-reviewer DeepSeek V4.1 Flash 45 turns, all DeepSeek; no MiniMax call R296Testing-3.jsonl
maintainability maintainability-reviewer DeepSeek V4.1 Flash 36 turns, all DeepSeek; no MiniMax call R296Maintainability-3.jsonl
api-contract api-contract-reviewer DeepSeek V4.1 Flash 86 turns, all DeepSeek; no MiniMax call R296Api-2.jsonl
learnings learnings-researcher DeepSeek V4.1 Flash 26 turns, all DeepSeek; no MiniMax call R296Learnings-3.jsonl
validator correctness-reviewer DeepSeek V4.1 Flash 18 turns, all DeepSeek; no MiniMax call V296b.jsonl
merge, report leaves docs-engineer claude-haiku-4-5 CE orchestration only; author no findings -
# Sev Location Finding (lens) Disposition Validator
1 P1 .changeset/arid-uncovered-block-guard.md:3 Breaking rule-id vocabulary change ships as a minor changeset (project-standards) Reject (validator). rejected: Refuted by the version in force: the wire rule-id vocabulary is versioned by the stream, not the package major — .changeset/stream-9-arid-uncovered-block.md (shipped in this same diff) records the 8.0 -> 9.0 bump and states a consumer pinned to 8.0 must accept 9.0 because the 8.0 codec refuses an Ignored line whose reason starts arid-uncovered-block:, and packages/stryker-js-cli-contract/src/stream-version.schema.t
2 P1 packages/stryker-js-instrumenter/src/guard-relation.workflow.ts:160 Guard attaches to nested-condition mutants, Ignoring covered mutants (correctness) Reject (validator). rejected: Unsupported: the claim rests on an unstated intent (the finding's own 'Assumption'), while the cited contract defines a condition mutant by containment — packages/stryker-js-plugin-interface/src/Mutant.schema.ts Guard annotates 'The if whose condition this mutant sits in' and .changeset/arid-uncovered-block-guard.md says 'a mutant in the condition of an if' — and guard-relation.workflow.ts ownerOf (line 154 `Bool
3 P2 packages/stryker-js/tests/__fixtures__/guarded-block-checker/worker.mjs:1 New checker fixture duplicates an existing serveChecker bootstrap (maintainability) Accept. Fixed by 413e9af81 The guarded-block checker fixture reuses the shared serveChecker bootstrap. confirmed: Confirmed: packages/stryker-js/tests/fixtures/guarded-block-checker/worker.mjs:1-38 re-copies the checker bootstrap verbatim, while packages/stryker-js/tests/fixtures/subsumption-checker/serve.mjs:12 already exports serveChecker and is imported by both subsumption-checker/worker.mjs and compile-error-worker.mjs, and the new fixture imports none of it.
6 P2 packages/stryker-js-instrumenter/src/Transformer.service.ts:1358 Guard-decision plumbing dumped into the 1543-line Transformer (maintainability) Accept. Fixed by 9957fc4d3 Guard application moves into apply-guards.ts beside the guard-relation workflow. confirmed: Confirmed: the new guard-application logic lands in the transformer — guardIndexOf (Transformer.service.ts:1427-1441) matches on the workflow's GuardRelationDecision, with guardOf (1443-1446), withGuard (1448-1453) and the guardRelation call (1581-1583) — rather than beside the guard-relation.workflow.ts that produces the decisions, which the keep-code-that-changes-together doctrine (CONST-N1) argues agains
8 P2 packages/stryker-js/src/run/mutation-test-plan.cell.ts:324 Plan bucket counts re-enumerated across three modules (maintainability) Accept. Fixed by a6ca1dd89 settledBeforeTestsOf/plannedTotalOf beside the plan type own the bucket counts. confirmed: Confirmed: the bucket counts are re-enumerated in three modules — packages/stryker-js/src/run/mutation-test-plan.cell.ts:324-325 builds plannedTotal from the four buckets, mutant-settlement.ts:336-337 re-lists earlyResults/heldSubsumed/heldGuards for the PlanKnown total, and deferrable-dry-run.cell.ts:52 re-lists the same three for the fast path — all three of which this diff had to edit to add heldGuards.
9 P2 packages/stryker-js/tests/uncovered-block.integration.test.ts:188 New arid-block suite asserts engine internals, not the product surface (testing) Accept. Fixed by b1d5af60b The suite now spawns the built CLI and asserts only report rows and stdout. confirmed: Confirmed: packages/stryker-js/tests/uncovered-block.integration.test.ts:188 returns { exit, results: exit.value.results } from Engine.mutationTestCell and every assertion reads observation.results (lines 224-229, 244-252, 291-293, 354-361) with no report file read, exactly the violation the PR body's house integration-test rule records for all 7 scenarios.
10 P3 packages/stryker-js-instrumenter/src/Transformer.service.ts:1374 Pass-through guardOf wraps a single Map.get call (maintainability) Accept. Fixed by 9957fc4d3 The pass-through guardOf is gone with that move. confirmed: Confirmed: guardOf (Transformer.service.ts:1443-1446) is literally index.get(id) and its only call site is line 1583 (withGuard(mutant, guardOf(guardsById, mutant.id))), so it is a pass-through wrapper with a single caller.
11 P3 packages/stryker-js-instrumenter/src/guard-relation.workflow.ts:124 Mutant.guard absence hides a silently disabling state (api-contract) Accept. Fixed by c4eb9fb7c The premise was corrected by observation: the guard is dropped only when the run plans no BlockStatement mutant for the block (excluded, directive-disabled or outside mutate); a planned block mutant keeps the guard even when Ignored. The commit documents that precondition on Mutant.guard, adds it to the changeset, and pins both outcomes with instrumenter scenarios. No behavior change. confirmed: Confirmed: guard-relation.workflow.ts resolvedSites resolves blockMutantId with blockMutantAt inside Option.all, so a site whose consequent block has no BlockStatement mutant (excluded mutator, disable directive, or a mutate range that drops it) is dropped entirely and guard silently disappears (the feature runs its condition mutants instead), a reachable state that neither the Guard annotation in packages/

Pack rule matrix on 2daf9c7de (learnings lens; every rule whose applies_when matches a changed file)

Enrolled packs: boundary-testing (yes), cell-architecture (yes), schema-laws (yes). Available but NOT enrolled: mutation-discipline (no), package-topology (no), storybook-testing (no). Rules whose applies_when does not touch any changed file are omitted; rules with a matching applies_when are listed with a verdict.

pack id rule path enrolled verdict evidence (file:line from the diff)
boundary-testing real-system-oracles.md yes comply tests/fixtures/guarded-block-checker/worker.mjs:27-38 (real worker over real NodeSocketServer); uncovered-block.integration.test.ts:120-130 teardown
boundary-testing no-mocks-on-internal-glue.md yes not-applicable no unit test mocks internal I/O glue
boundary-testing port-fakes-pass-the-port-contract.md yes not-applicable guarded-block-checker/index.mjs:1-3 is a real Checker plugin
boundary-testing fake-and-real-store-laws.md yes not-applicable no store
boundary-testing pin-dependency-semantics.md yes not-applicable no dependency adopted
boundary-testing staged-protocol-evidence.md yes not-applicable worker protocol types unchanged
cell-architecture pure-decision-workflows.md yes comply guard-relation.workflow.ts:186-192; uncovered-block.workflow.ts:75-81; placement-anchors.workflow.ts:98-104; plan-shards.workflow.ts:359-365 — all Workflow.make with error: S.Never; no if/else/switch/for/while in decision bodies
cell-architecture four-channel-contracts.md yes comply error: S.Never on every new Workflow.make
cell-architecture single-namespace-barrel.md yes comply src/Mutant/mod.ts:41,61 — explicit named exports added to the Mutant namespace
cell-architecture decode-never-cast.md yes not-applicable heldBy decoded via S.decodeOption (mutation-test-plan.cell.ts:152-160), no external raw decode
cell-architecture pipeable-dual-parity.md yes not-applicable new duals are module utilities, not blueprint/handle ops
cell-architecture pipeline-composition.md yes not-applicable —
cell-architecture sandwich-phase-order.md yes not-applicable —
cell-architecture service-and-layer-boundaries.md yes not-applicable —
cell-architecture scoped-lifecycle-boundaries.md yes not-applicable —
cell-architecture resource-vs-handle-duality.md yes not-applicable —
cell-architecture handle-state-privacy.md yes not-applicable —
cell-architecture callable-vs-resource-syntax.md yes not-applicable —
cell-architecture staged-lawful-builders.md yes not-applicable —
cell-architecture store-serializable-unit-of-work.md yes not-applicable —
cell-architecture store-unit-of-work-handle.md yes not-applicable —
schema-laws data-only-schema-classes.md yes comply guard-relation.workflow.ts:29-53; uncovered-block.workflow.ts:26-45; placement-anchors.workflow.ts:18-45 — classes only for Workflow.make commands/decision variants, data + family TypeId only
schema-laws tagged-unions-over-state-by-presence.md yes comply Mutant.schema.ts:161 guard: S.optional; :126-128 alternate: S.optional; plan-mutant-tests.workflow.ts:23 heldBy: S.optional — genuinely-absent annotations, no correlated discriminant on the same struct; 'held' has its own HeldGuard type
schema-laws law-failure-is-a-codec-defect.md yes comply Mutant.schema.ts:120-130 exports Guard; vitest.config.ts:2 enables inSourceSchemaLaws; Guard is a plain Struct with no check/transform
schema-laws arbitrary-filter-floors.md yes comply new drawn schemas add no check/filter
schema-laws cross-field-invariants-as-struct-checks.md yes not-applicable Mutant struct checks unchanged; Guard adds no cross-field rule
schema-laws invariants-as-refinements.md yes comply Guard.block/alternate are branded MutantId
schema-laws tests-own-no-schemas.md yes comply new property suites import schemas from src; the only test-dir *.schema.ts is pre-existing
schema-laws refusals-beside-generated-laws.md yes not-applicable no new check/pattern/bound/brand
schema-laws recursive-schema-suspend.md yes not-applicable —
schema-laws rich-type-over-foreign-encoded.md yes not-applicable —
mutation-discipline suppress-by-shape-in-an-ignorer.md no comply ignore-rule.schema.ts:23,42-43 (shared rule id + stable reason, shape-keyed); near-miss refusal tests in uncovered-block.integration.test.ts and guard-relation.workflow.property.test.ts:246-283
mutation-discipline kill-survivors-with-contract-properties.md no not-applicable no survivor-killer test
mutation-discipline unkillable-survivors-are-dead-code.md no not-applicable —
package-topology surface-changes-are-versioned.md no comply .changeset/arid-uncovered-block-guard.md, .changeset/arid-uncovered-block.md, .changeset/stream-9-arid-uncovered-block.md (minor each)
package-topology enumerated-entry-modules.md no comply src/Mutant/mod.ts:41,61 explicit named exports, no wildcard
package-topology one-access-path.md no comply api.md:19 lists Guard / uncoveredBlockStatusReason once, in the Mutant namespace
package-topology public-signature-types.md no comply Mutant.schema.ts:132-140 signature types are published; api report regenerated
package-topology import-time-inertness.md no comply new modules have no top-level effects
package-topology declared-entry-points.md no not-applicable no subpath added
package-topology condition-branch-agreement.md no not-applicable —
storybook-testing assert-through-accessible-queries.md no not-applicable no stories changed
storybook-testing fresh-fakes-per-story.md no not-applicable no stories changed
storybook-testing journeys-are-embedded-gherkin.md no not-applicable no stories changed
storybook-testing pin-the-viewport-a-claim-needs.md no not-applicable no stories changed
storybook-testing real-browser-axe-as-error.md no not-applicable no stories changed
storybook-testing stories-are-the-ui-integration-tests.md no not-applicable no UI changed
storybook-testing stories-compose-at-their-own-root.md no not-applicable no stories changed

No pack rule is contradicted by this diff; every matching rule is comply or not-applicable.

…block keeps a counted NoCoverage mutant

Plan unit U7 (arid-uncovered-block, R10, AE9, KTD8, KTD14). The instrumenter records a guard {block, inside} on each mutant in the test of an if whose consequent is a non-empty block (guard-relation.workflow.ts). Under coverageAnalysis perTest, planning holds a covered condition mutant whose block mutant is uncovered and non-static; after the first pass, uncovered-block.workflow.ts Ignores it as arid-uncovered-block only when a member of its block settled NoCoverage this run, otherwise it runs with the readmitted subsumed mutants. Reuse refuses arid-uncovered-block records as decidedPerRun; shard planning keeps a guard's mutants in one shard (placementKey becomes anchors). mutantSetPolicy full keeps every mutant.
…ment paths

ce-simplify-code pass over U7 with no change in behavior. Location.ts now owns shiftedLocation, which the Transformer used to copy. Guards are looked up once per file, BlockStatement mutants are indexed by location, and each guard site computes its owner in one linear pass and its inside list once. Shard anchors skip the component fixpoint when a project has no anchors. Inside anchors are accumulated without a copy per insert, and the NoCoverage witness set is built once per command. Dead aliases and conjuncts are dropped from plan-mutant-tests. duplicatedValue is linear. The guard-relation law whose model transcribed ownerOf/insideOf is deleted; the nested-if instrumenter scenario covers innermost selection.
…y-l4-uncovered-block

# Conflicts:
#	packages/stryker-js-plugin-interface/etc/stryker-js-plugin-interface.api.md
#	packages/stryker-js/src/run/mutant-settlement.ts
… is absent or unexecuted

Ruling 14 on the L4 review. Guard gains an optional alternate: the BlockStatement mutant that empties the if's else block. A covered else means tests execute the condition, so planMutantTests holds the condition only when the else is absent or per-test uncovered too. An if whose else is not a non-empty block carries no guard. Ruling 2: a property law and a callback-in-condition scenario pin the innermost owning site. Ruling 12: the guard is built once per site instead of per mutant.
…flow

Ruling 3 on the L4 review. The anchor grouping (subsumption dominators, the mutant's own guard block, and the block of every guard that holds it) moved out of plan-request.cell.ts into placement-anchors.workflow.ts, so the mutation run's src/**/*.workflow.ts glob grades it. The workflow decides Anchored or Unanchored per mutant; the cell keys the anchored ones by id and writes the anchors field. Laws: one decision per mutant in order, and a drawn focus mutant's anchors are exactly its justified anchors.
…twice

Ruling 4 on the L4 review. The plannedPlacementsAreDistinct check on PlanShardsCommand.mutants had no refusal test. The law decodes the drawn command's own placements (accepted) and the same payload with a drawn mutant placed twice (refused), so neither an always-accepting nor an always-refusing decoder passes.
…y from the decision

Rulings 6, 8, 13 on the L4 review. heldGuards now excludes subsumed ids, so a mutant cannot be held both ways even if planning stops stripping its guard; a scenario over a relational condition (limit > 3) proves a readmitted condition mutant settles exactly once, and it fails with 10 results for 9 ids when both protections are removed. The plan cell decodes heldBy from the planner's decision instead of re-reading mutant.guard. The Transformer's unreachable guardRelation failure branch is gone. Findings 7 and 10 were not changed: settlementsOf tags each source differently, so the list is not built twice, and make-body-purity refuses the imported shiftedLocation inside the decision body, so the local alias stays.
…y-l4-uncovered-block

# Conflicts:
#	packages/stryker-js/src/plan-request.cell.ts
#	packages/stryker-js/src/plan-shards.workflow.ts
@ryanleecode

Copy link
Copy Markdown
Contributor

Closed by the root conductor: one open PR per stream from now on, and this one is waiting on other work (an open P1 contract-version blocker and a red check; Stream C lands #290 first). Branch kept; the stream reopens it (or a fresh PR off main) when it becomes that stream's active PR.

@ryanleecode

Copy link
Copy Markdown
Contributor

Reopened by the root conductor: closing this was wrong. It stays open and is driven to merge.

@ryanleecode ryanleecode reopened this Oct 13, 2026
… arid-uncovered-block

An Ignored mutant's statusReason may now carry the rule id
arid-uncovered-block, so the run stream's closed IgnoreStatusReasonText
codec grows. Bump StreamSchemaVersion 8.0 -> 9.0, regenerate the published
stream schema, and update the README stream samples and the tests that pin
the version. A consumer pinned to 8.0 must accept 9.0.
…y-l4-uncovered-block

# Conflicts:
#	packages/stryker-js-plugin-interface/src/ignore-rule.schema.ts
…d-relation workflow

Findings #6 and #10 of full-path review run 20261013-035820-4d26adec.

The decision-to-mutant application (the guard index and withGuard) leaves the
1543-line Transformer for apply-guards.ts beside guard-relation.workflow.ts,
which produces the decisions (CONST-N1). The Transformer now makes a single
withGuards call; guardOf is inlined at its one call site (#10).
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
… constant-collection-size

Findings #5 and #6 of full-path review run 20261013-032451-a9153d08 are merged duplicates (semantic_reconciliation group 3): the diff added the ignore-rule id constant-collection-size without moving the run stream schema version, so an 8.0 reader silently drops Ignored lines whose statusReason starts with the new id.

Bump StreamSchemaVersion 8.0 to 9.0 the way PR #296's commit 57d64f2 did: regenerate the published contract/stream.schema.json through the package's generate:contract script, update the README stream samples and every test pinning the current version, and add the cli-contract minor changeset naming the new rule id as the reason.

A consumer pinned to 8.0 must accept 9.0: the 8.0 codec refuses an Ignored line whose reason starts constant-collection-size.
…ock mutant is planned

Finding #11 of full-path review run 20261013-035820-4d26adec.

The finding's premise is corrected by observation through the published
instrument export: excludedMutations and disable directives leave the block's
BlockStatement mutant in the run as Ignored, so the guard stays; the guard
drops only when no BlockStatement mutant for the block is planned (an empty
block, a block outside the mutate range, or the mutator not selected).

The Guard annotation and the changeset now state that precondition, and two
integration scenarios pin the observed outcomes: an Ignored block with guarded
conditions under excludedMutations, and unguarded conditions when only the
condition falls inside the mutate range.
Finding 8 (P2) of full-path review run 20261013-035820-4d26adec: plannedTotal
and the PlanKnown total each re-enumerated earlyResults/heldSubsumed/heldGuards,
and the dry-run fast path re-listed the same three. settledBeforeTestsOf and
plannedTotalOf now own that enumeration in mutation-test-plan.ts and all three
sites read them, so a new bucket is added in one place. Every count is unchanged.
Finding #3 of full-path review run 20261013-035820-4d26adec (P2,
confirmed): the new guarded-block-checker/worker.mjs re-copied the whole
checker bootstrap while subsumption-checker/serve.mjs already exported it.

serveChecker now takes the answer function itself and lives once at
tests/__fixtures__/checker-worker.mjs; the subsumption fixtures keep one
definition of which mutant is the dominator (dominator-answer.mjs) and the
guarded-block fixture answers by mutator name. Net 15 insertions against 36
deletions.
Finding #9 of full-path review run 20261013-035820-4d26adec (P2, confirmed):
the suite ran Engine.mutationTestCell in-process and asserted the in-memory
exit.value.results, so it pinned engine internals rather than the product.

Each scenario now writes the run options into the fixture project's stryker
config (loading the guarded-block checker by plugin path for the case whose
block mutants must fail to compile), spawns the built dist/main.mjs run with
the ALLOW_LOCAL_MUTATION handling the existing built-CLI suites use, and
asserts only the exit code plus reports/mutation/mutation.json read from disk:
each condition mutant's status/statusReason carrying the arid-uncovered-block:
prefix that names a NoCoverage mutant id, block members NoCoverage, CompileError
statuses, and the readmitted condition settling exactly once. Every scenario
keeps its behavioural claim; the pure decisions stay in
uncovered-block.workflow.property.test.ts.

Declared divergence (CONST-W3): the gherkin-spec capability refuses a feature
that spawns a process; this rewrite follows the review finding, the dispatch,
and every existing stryker-js CLI suite (gate, shard-run-verdict, rerun-by-id),
which spawn the built CLI as the product's public surface.
…run stream 9.0

Finding #1 (run 20261013-203109-28ea241e): this PR moves the run stream to schemaVersion 9.0. stryker-js is 19.x, so AGENTS.md BREAK-1 and the #273 precedent require a major bump; the 0.x cli-contract changeset stays minor.
…es can see coverage

Finding #6 (run 20261013-203109-28ea241e): plan-mutant-tests.workflow.ts read a missing testsByMutantId entry as uncovered, so an Ignored BlockStatement (excluded mutator or disable directive) whose else block a test runs held the condition mutants arid. A guard now holds only when its block and alternate mutants are open and per-test-uncovered; an Ignored witness cannot see coverage, so the condition mutants run (GuardKept). Adds the ∀q_IgnoredGuardWitness law (whose arbitrary models Ignored mutants with no coverage entry) and a built-CLI scenario with excludedMutations ['BlockStatement']; both shown red first. Updates the guard changeset sentence and trims that entry under the 900-byte ceiling.
Finding #8 (run 20261013-203109-28ea241e): tests/__fixtures__/uncovered-block-report.schema.ts declared its own report schema, which the pack schema-laws tests-own-no-schemas law forbids. It is deleted; the integration test decodes with the published Report.MutationTestResult instead (that call-site rewrite is in the shared test file, landed with finding #6).
…ard laws

Finding #5 (run 20261013-203109-28ea241e): the anchor laws drew a raw PlanShardsCommand whose independent 16-hex anchors never named a sibling, so everyAnchoredMutantSharesAShardWithItsAnchor skipped at its target-miss guard and the merge was never exercised. The probe now injects same-project cross-references plus a chained A->B->C case; both laws fail when the anchor merge is broken and pass with it.
…condition carries no guard

Finding #2 (run 20261013-203109-28ea241e). GuardSite gains the
nestedFunctionBodies the transformer walks out of the if statement's test; the
pure guardRelation workflow now owns a location only when the site's test
contains it and no function body nested inside that test contains it. A mutant
in a callback nested in a condition no longer inherits the outer block's guard
(it was held as arid-uncovered-block), while the condition's own mutants keep
their guards.

The transformer edit also repoints the shiftedLocation import for finding #3,
which shares this file (see the following commit for finding #3's own paths).

Tests: new instrumenter scenario 'A mutant inside a function nested in a
condition carries no guard' (red before the fix, green after) and the new
guardRelation law 'AMutantInsideANestedFunctionBodyOfATestIsNeverGuardedByThatSite'.
… into Location.schema.ts

Finding #3 (run 20261013-203109-28ea241e). columnOffsetOf, shiftedPosition and
shiftedLocation move from the unsuffixed src/Location.ts beside ScriptOrigin in
src/Location.schema.ts, as ADR-0002 (schema-file operations) requires, with an
in-source law block that checks them against the host position of the same
character in a scanned witness embedding. plan-mutants.workflow.ts imports
shiftedLocation from './Location.schema.js' and drops its shiftedLocationOf
alias; its usage now names shiftedLocation directly. Location.ts keeps the
position codecs the package still exports (lineStartsOf, locationOf, positionAt,
originAt, offsetAt).

Transformer.service.ts's import repoint for this move rides in the finding #2
commit because that file carries both changes and is committed once.
…membership

Finding #6 (run 20261013-203109-28ea241e): an incremental run's plan omits reused mutants, so requiring a guard's block/alternate mutant to be present-and-open refused to hold when the witness was reused with a remembered NoCoverage settlement — the second run stopped re-ignoring the condition mutants. A witness absent from the plan is now judged by coverage; only a present-but-closed (Ignored/unplaced) witness blocks the hold. Verified by tests/incremental-reuse.integration.test.ts.
Finding (run 20261013-212933-b2b8e363): ∀q_IgnoredGuardWitness_≡GuardNotHeld used the helper mutantIsOpenById (onNone => false), contradicting the implementation's mutantCanWitnessCoverage (onNone => true) after 330acec. It passed only because guardCommandArb never draws an absent witness, and for present witnesses it restated ∀h_RunPlan_≡HeldExactlyWhenItsGuardWitnessesAreOpenAndUncovered. Deleted the law and the now-unused helper; no replacement law.
Finding #6 of full-path review run 20261013-223220-e00fef28: guardsByMutantId is referenced only inside apply-guards.ts (its own call site at line 28), so it needs no export.
…tant guard rule

Findings #7/#9 of full-path review run 20261013-223220-e00fef28: the Guard annotation claimed an Ignored block mutant still witnesses the guard. The shipped rule is the opposite (plan-mutant-tests.workflow.ts mutantWitnessesUncovered/mutantCanWitnessCoverage, uncovered-block.workflow.ts noCoverageWitnessOf, .changeset/arid-uncovered-block-guard.md): an Ignored block mutant never witnesses the guard, so the if's condition mutants run unguarded.
…tlement

Finding #2 (run 20261013-230452-caaabb43): plan-mutant-tests.workflow.ts treated any guard witness absent from the plan as able to witness coverage. Incremental reuse omits remembered mutants from the plan, so a BlockStatement mutant left Ignored by mutator.excludedMutations or a disable directive — once remembered — also read as a witness, and an incremental rerun re-ignored the if's condition mutants as arid-uncovered-block while a fresh run of the same source ran them, contradicting the published Guard rule.

The witness rule now reads the remembered settlement through MutantTestPlanCommand.rememberedStatusByMutantId, populated from reuse.rememberedResults in run/mutant-settlement.ts: an absent witness witnesses only a remembered NoCoverage, and a remembered Ignored (or any other status) never witnesses. Present-but-open witnesses are unchanged, so the remembered-NoCoverage reuse case still holds its guard.

Red first: a built-CLI two-run scenario in tests/uncovered-block.integration.test.ts with excludedMutations ['BlockStatement'] where a benign test edit re-decides the covered condition mutants and the block mutant stays reused — the second run left both condition mutants arid before the fix; the guardCommandArb property generator now draws absent witnesses with a remembered status.
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.

2 participants