Repository navigation
feat(stryker-js): skip if-condition mutants whose block no test runs - #296
Open
systemfsoftware-maker wants to merge 37 commits into
Open
systemfsoftware-maker wants to merge 37 commits into
systemfsoftware-maker wants to merge 37 commits into
Conversation
…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
…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
This was referenced Oct 11, 2026
…y-l4-uncovered-block
…y-l4-uncovered-block
…y-l4-uncovered-block # Conflicts: # packages/stryker-js/src/plan-request.cell.ts # packages/stryker-js/src/plan-shards.workflow.ts
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. |
Contributor
|
Reopened by the root conductor: closing this was wrong. It stays open and is driven to merge. |
… 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
…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.
…y-l4-uncovered-block
…y-l4-uncovered-block
This was referenced Oct 13, 2026
…y-l4-uncovered-block # Conflicts: # packages/stryker-js-cli-contract/README.md
…y-l4-uncovered-block
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Under
coverageAnalysis: 'perTest', the condition mutants of anifwhose block no test runs are now Ignored asarid-uncovered-blockinstead of running, as long as that block still reports aNoCoveragemutant 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 onmain. #287 (diff-scoped planning) and #290 (arid imports) are separate units of the same plan.When a condition mutant is held
perTest)elsearid-uncovered-block: …naming the block'sNoCoveragemutantelseblock run by a testelseblock both uncoveredcoverageAnalysisoff/all, ormutantSetPolicy: 'full'elseis not a block (else if …,else stmt)else ifchain guards only its lastifHow an agent consumes this
statusReasonstartsarid-uncovered-block:names aNoCoveragemutant id. Act on that id: a test that enters the block brings the condition mutants back on the next run.Mutantcarriesguard: { block, inside, alternate? }on condition mutants (ids of the block's emptying mutant, the mutants inside it, and theelseblock's emptying mutant).arid-uncovered-blockresult; it decides it again.stryker plankeeps each condition mutant in the same shard as its block, throughanchors.mutator: { mutantSetPolicy: 'full' }.Review rulings
Applied: the
elserule above (14); the guard comes from the innermostif, pinned by a property and a callback-in-condition scenario (2); shard anchors are decided inplacement-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 keepsheldByfrom the planner's decision (6); the Transformer's unreachable failure branch is gone (8); one guard per site (12);isStaticis passed, not recomputed (11).Not changed:
settlementsOftags remembered, early and checked results differently.shiftedLocationOfstays.make-body-purityrefuses the importedshiftedLocationinside the decision body.Transformer.service.tssize, out of scope per ruling.ifbody becomes one shard unit that may exceedtargetSeconds.Shard planning cost (ruling 1)
planShardson one project, one guardedifper 10 mutants (best of 3, local Bun):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
ifowner, ignoringelsecoverage (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).maingradesuncovered-block.workflow.ts,placement-anchors.workflow.ts,plan-shards.workflow.tsandplan-mutant-tests.workflow.ts.Benchfails on every PR untilcount-runs.workflow.ts:65onmainreadsreason.code(renamed by #283). This branch carries the same one-line fix as #287 and #290.Need help on this PR? Tag
@codesmith-botwith 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/mainec1769de2. 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.Engine.mutationTestCellwith a hand-builtRunEnvironmentShape. That is exported but is not a documented user entry: the README documents the CLI andstrykerCell. They also assertexit.value.results, in-process result objects, instead of written files.writeWorkspace:package.json,vitest.config.ts,src/lib/work.ts, test file).guarded-block-checkerloaded as a plugin by name where needed.stryker run(ALLOW_LOCAL_MUTATION=1).reports/mutation/mutation.jsonand the progress stream from disk: each mutant's status (Ignored / NoCoverage / run), thearid-uncovered-block:status reason naming the NoCoverage mutant id, and subsumption.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 publishedinstrumentexport with file name and source text and assert the returned mutants' publishedguardfield 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 themutation.ymlandkill-matrix.ymltriggers. Checked against this head: the diff touches no file under.github/, so this PR makes no trigger change. Applies to the rewrite only:mainwill grade.uncovered-blockrewrite above spawns the builtstryker runon a fixture project withALLOW_LOCAL_MUTATION=1. That is a product test of the CLI, the same pattern as 9 test files onmain, 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 rulingssection above records the earlier rulings. It names no reviewer personas, so this receipt is the persona review of the current diff.fe9bc50b3317a65884c5789262beb2bffb4b40a1against merge-base38924e53fwithorigin/mainee7038a5c. Scope local-aligned, full depth (2746 executable lines).correctness-reviewer,adversarial-reviewer,security-reviewer, and fivegeneralist-reviewerruns carrying the project-standards, testing, maintainability, api-contract and learnings lenses. api-contract ran because the published plugin-interfaceMutantgainsguardand the ignore-rule vocabulary grows.Token Plan usage limit reached (2056), omp fell back toopenrouter/deepseek/deepseek-v4.1-flashand 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_changeevents and per-turnmodel). 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.correctness-revieweradversarial-reviewersecurity-reviewergeneralist-reviewergeneralist-reviewergeneralist-reviewergeneralist-reviewergeneralist-reviewercorrectness-reviewerdocs-engineerturbo.json, workflow, CI task scope or cache.elseblock with no BlockStatement mutant still leaves the guard attached (correctness)alternateOfmaps anelsespan with no block mutant toNonethroughonSome, andOption.allthen drops the whole site, so no guard is attached. Only a site with noelsereaches the citedonNoneline. The propertyASiteWhoseElseHasNoBlockStatementMutantGuardsNothingpins this. The suggested edit would drop every site that has noelse.packages/stryker-js-instrumenter/src/guard-relation.workflow.ts:118-125,131-135;guard-relation.workflow.property.test.ts:279-282; validator rejectedarid-uncovered-blockships without a stream version bump (api-contract)RULE_IDS.IgnoreStatusReasonTextisS.String.check(reasonPatternOf(RULE_IDS)), and the cli-contract stream checks every Ignored line'sstatusReasonagainst it.StreamSchemaVersionstays'8.0'and the branch changes nothing underpackages/stryker-js-cli-contract, so a released consumer reading an8.0stream 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: raiseStreamSchemaVersionto'9.0', regenerate the contract documents, and add a cli-contract changeset (minorwhile 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<script>region holdingif (a) { f(1) }tomutant-span-slice.integration.test.tsand assertguard.block.Transformer.service.ts:1050;mutant-span-slice.integration.test.ts:69-103; validator confirmedanchorsandidare drawn independently as 16-hex ids, so no drawn anchor names another mutant.anchorComponentsToFixpointnever 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 intoanchors, aswithHeldMutantsdoes forguard.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 confirmedheldGuardsto all three sums, which is the drift the finding describes. Fix: one derived total besideMutationTestPlan, 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 confirmedsubsumption-checker/serve.mjsalready factors this bootstrap asserveChecker(answerOf). Reuse it instead of a third copy.tests/__fixtures__/guarded-block-checker/worker.mjs:1-38;subsumption-checker/serve.mjs:1-31; validator confirmedguardOfwrapper (maintainability)index.get(id)with one call site. Inline it.Transformer.service.ts:1374-1377,1513; validator confirmedSuppressed at anchor 50 (not validated):
elseblock not anchored to its shard (adversarial, P2):guardBlockOfanchors a condition mutant toguard.blockonly, but the hold decision also readsguard.alternate's coverage (alternateIsUnexecuted). If theelseblock's mutant lands in another shard, a sharded run may decide differently from an unsharded one. I did not confirm this. The candidate fix addsguard.alternatetoguardBlockOf, with a 1-vs-2-shard scenario. Root decision. Source:placement-anchors.workflow.ts:58-62;plan-mutant-tests.workflow.ts:363-374.elseblocks (correctness, P2):blockMutantIndexmay not index anelseblock 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-sizetoRULE_IDSwithout 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)54f35c353f2a3df7fc5495eed22cbad342df8f9e. Since the previous receipt head737f8eb3dthis branch mergedorigin/maintwice:795170b8c(main449e9cd53; the cli-contract README keeps main's two codecs at9.0) andbb52fa852(mainb08379ddd, 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.737f8eb3d: 4-9 below, newest first: full-path runs onbb52fa852andff9284b92, and four correctness lenses over the fix ranges. The last lens is over54f35c353.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 by330acece0inside this PR;54f35c353fixed it with a built-CLI incremental scenario that was red first. The last lens (round 9) found two ADVISORYs, recorded.openrouter/deepseek/deepseek-v4.1-flash); MiniMax and Grok served no turn. Lenses without a dedicated agent ran ongeneralist-reviewerwith the persona file. Merge and report leaves aredocs-engineeron claude-haiku-4-5 and author no findings.54f35c353: Commitlint 38485156610, Changeset Check 38485156948 and Nix 38485156654 passed. CI 38485156614 failed inchecker-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;checkwas 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 on737f8eb3d(paths gate refused) was fixed on main by ci(repo): gate change intents on turbo's typecheck task graph #335 and is green here.checker-parity-reportspeed gate.Round 9: correctness lens over
54f35c353(run20261013-233526-0cd092d0)54f35c353(round 8 finding 2).correctness-reviewer, DeepSeek V4.1 Flash, 60 turns, all turns; no MiniMax or Grok call (X296N.jsonl).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--mutantrerun) no longer holds the guard; no scenario covers that rerun case.Round 8: correctness lens over
ff9284b92..12aa557c2(run20261013-230452-caaabb43)abaa34fba,12aa557c2.correctness-reviewer, DeepSeek V4.1 Flash, 52 turns, all turns; no MiniMax or Grok call (X296M-2.jsonl).onNone => truefrom330acece0), so the rerun settled the condition mutants Ignored while a fresh run ran them. Accept. Fixed by54f35c353:MutantTestPlanCommandcarries 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 (aridReasons2 on the second run) and passes after. MEDIUM, the rewrittenGuardsentence held only for fresh runs: resolved by the same commit, which makes it true for every run.Round 7: full-path run on
ff9284b92(run20261013-223220-e00fef28, baseb08379ddd)adversarial-reviewerR296AdversarialK-2.jsonlgeneralist-reviewer(personaapi-contract-reviewer)R296ApiK-2.jsonlcorrectness-reviewerR296CorrectnessK-2.jsonlgeneralist-reviewer(personalearnings-researcher)R296LearningsK-2.jsonlgeneralist-reviewer(personamaintainability-reviewer)R296MaintainabilityK-2.jsonlgeneralist-reviewer(personaproject-standards-reviewer)R296ProjectK-2.jsonlsecurity-reviewerR296SecurityK-2.jsonlgeneralist-reviewer(personatesting-reviewer)R296TestingK-2.jsonlcorrectness-reviewerV296K.jsonldocs-engineerM296K.jsonl(43 turns)stryker-js/src/run/mutant-settlement.ts:245human. UnderperTest, 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 outsidetotalValid, so the score loses a detection. Two choices: run held mutants against their own covering tests in the second pass, as forGuardKept(no score change, more work), or settle themNoCoverageinstead ofIgnored(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 choicestryker-js-instrumenter/src/Transformer.service.ts:1108stryker-js-instrumenter/tests/instrumenter.integration.test.ts:1643stryker-js/src/__tests__/plan-mutant-tests.workflow.property.test.ts:215Arbitrary.schema(S.Boolean)draws, so no mis-binding exists today; ...stryker-js/src/plan-request.cell.ts:285stryker-js-instrumenter/src/apply-guards.ts:8abaa34fbastryker-js-plugin-interface/src/Mutant.schema.ts:13112aa557c2(with #9)stryker-js-plugin-interface/src/Mutant.schema.ts:13112aa557c2(same sentence as #7; round 8 then found the sentence held only for fresh runs, and54f35c353made it true for incremental runs too)stryker-js/src/plan-mutant-tests.workflow.ts:36854f35c353:guardCommandArbnow draws an absent block witness remembered as NoCoverage or as Ignored, from the production schemasSuppressed by CE below its confidence gate (recorded, not findings): Public
guardfield is stripped before any consumer sees it (P3, correctness, confidence 50).Pack rule matrix (learnings lens, round 7)
pure-decision-workflows.mddecode-never-cast.mdaspipeable-dual-parity.mdfour-channel-contracts.mdsandwich-phase-order.mdservice-and-layer-boundaries.mddata-only-schema-classes.mdstatic readonly [Workflow.InstrumentationBrand]/TypeId (guard-relation.workflow.ts:865-889, uncovered-block.workflow.ts:22-40)tagged-unions-over-state-by-presence.mdguard: S.optional(Guard); Guard'salternate(Mutant.schema.ts:126-128) is genuinely optional data (present iff theifhas an else block mutant), not a mutually-exclusive status encoded ...invariants-as-refinements.mdblock/inside/alternateas the brandedMutantId, not bare stringcross-field-invariants-as-struct-checks.mdguardfield carries no cross-field relation that consuming code comparestests-own-no-schemas.mdrefusals-beside-generated-laws.mdarid-uncovered-blockis a new literal member of IgnoreRuleId (ignore-rule.schema.ts:23)surface-changes-are-versioned.mdpublic-signature-types.mdone-access-path.mdGuard/uncoveredBlockStatusReasonare exported once from Mutant.schema.ts and re-exported via src/Mutant/mod.tsdeclared-entry-points.mdcondition-branch-agreement.mdexpected-values-never-come-from-the-subjects-output.mdno-mocks-on-internal-glue.mdreal-system-oracles.mdport-fakes-pass-the-port-contract.mdpin-dependency-semantics.mdfake-and-real-store-laws.mdstaged-protocol-evidence.mdsuppress-by-shape-in-an-ignorer.md// Stryker disablecomment, an excluded mutator, nor a shape-keyed ignorer; the rule's prohibitions ar...kill-survivors-with-contract-properties.mdunkillable-survivors-are-dead-code.md(all rules)selection-and-caching-go-through-turbo.mdRound 6: correctness lens over
ff9284b92(run20261013-214250-233d6bb2)ff9284b92.correctness-reviewer, DeepSeek V4.1 Flash, 28 turns, all turns; no MiniMax or Grok call (X296L.jsonl).Round 5: correctness lens over
bb52fa852..330acece0(run20261013-212933-b2b8e363)ab17ff32a,b5fae6667,c71708de5,ef6d59b01,c03fcc0b8,6ecbe152d,330acece0.correctness-reviewer, DeepSeek V4.1 Flash, 42 turns, all turns; no MiniMax or Grok call (X296J.jsonl).mutantIsOpenByIdread an absent witness as closed, contradicting330acece0. Accept. Fixed byff9284b92(the law and helper are deleted; round 8 later showed330acece0's own rule was too wide, fixed by54f35c353).Round 4: full-path run on
bb52fa852(run20261013-203109-28ea241e, baseb08379ddd)adversarial-reviewerR296AdversarialJ.jsonlgeneralist-reviewer(personaapi-contract-reviewer)R296ApiJ.jsonlcorrectness-reviewerR296CorrectnessJ.jsonlgeneralist-reviewer(personalearnings-researcher)R296LearningsJ.jsonlgeneralist-reviewer(personamaintainability-reviewer)R296MaintainabilityJ.jsonlgeneralist-reviewer(personaproject-standards-reviewer)R296ProjectJ.jsonlsecurity-reviewerR296SecurityJ.jsonlgeneralist-reviewer(personatesting-reviewer)R296TestingJ.jsonlcorrectness-reviewerV296J.jsonldocs-engineerM296J.jsonl(28 turns),P296J.jsonl(21 turns).changeset/arid-uncovered-block.md:2ab17ff32astryker-js-instrumenter/src/guard-relation.workflow.ts:168c03fcc0b8stryker-js-instrumenter/src/Location.ts:1036ecbe152dstryker-js-instrumenter/tests/instrumenter.integration.test.ts:1644git 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 ...stryker-js/src/__tests__/plan-shards.workflow.property.test.ts:346ef6d59b01stryker-js/src/plan-mutant-tests.workflow.ts:366b5fae6667and330acece0(the second commit covers a reused witness absent from an incremental plan)stryker-js/src/plan-shards.workflow.ts:235absorbAnchorComponent(plan-shards.workflow.ts:200-217) first-match merges andanchorComponentsToFixpoint(:235-240) iterates until the class count stops shrinking, and the reviewer's own simulation (4000 confi ...stryker-js/tests/__fixtures__/uncovered-block-report.schema.ts:3c71708de5(not selected by CE, fixed because the enrolled pack row below is a violate)Pack rule matrix (learnings lens, round 4)
pure-decision-workflows.mddata-only-schema-classes.mddecode-never-cast.mdsingle-namespace-barrel.mdfour-channel-contracts.mdpipeable-dual-parity.mdtests-own-no-schemas.mdtagged-unions-over-state-by-presence.mdinvariants-as-refinements.mdcross-field-invariants-as-struct-checks.mdguardfield adds no two-field rule left to consumersrefusals-beside-generated-laws.mdarbitrary-filter-floors.mdlaw-failure-is-a-codec-defect.mdsurface-changes-are-versioned.mdenumerated-entry-modules.mdone-access-path.mdpublic-signature-types.mdcondition-branch-agreement.mddeclared-entry-points.mdreal-system-oracles.mdexpected-values-never-come-from-the-subjects-output.mdno-mocks-on-internal-glue.mdport-fakes-pass-the-port-contract.mdsuppress-by-shape-in-an-ignorer.mdkill-survivors-with-contract-properties.mdunkillable-survivors-are-dead-code.md(none)selection-and-caching-go-through-turbo.mdViolate rows:
schema-laws/tests-own-no-schemas.md: Fixed byc71708de5(finding docs(global): rewrite root README for stryker-js-effect #8).Previous receipt header (head
737f8eb3d), unchanged737f8eb3ddab015a8ed87aa206d37f14872d43fc. It mergesorigin/main381b3f5b1intocca787584with an empty combined diff (git show --cc 737f8eb3dprints no file). The earlier mergee071bd047(main117bff023) resolved conflicts inTransformer.service.ts,ignore-rule.schema.tsanduncovered-block.integration.test.ts; round 3 reviewed exactly those resolutions.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.paths gate refused: turbo.json makes turbo the source of change evidence — set "gate": { "strategy": "turbo" } in release.jsonc. The reusablepnpm-release-managementworkflow atmain(2b8955578) now refuses main'srelease.jsonc:14("gate": { "strategy": "paths" }). This PR does not touchrelease.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.20261013-035820-4d26adecon2daf9c7de; (2) one correctness lens over its five fix commits; (3) one correctness lens over mergee071bd047's conflict resolutions. An earlier wave (20261013-034329-2ec9dcad, ondaf91aeb5) ran its lenses but never reached merge or validation; round 1 superseded it on a head that containsdaf91aeb5.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.openrouter/deepseek/deepseek-v4.1-flash); no turn in rounds 1-3 reached MiniMax. Grok served no lens. Merge and report leaves aredocs-engineeron claude-haiku-4-5 and author no findings.Round 3: one correctness lens over merge
e071bd047(run20261013-080657-2aeb415b)correctness-reviewer, DeepSeek V4.1 Flash, 42 turns, all DeepSeek; no MiniMax call (X296M.jsonl).Engine.RunEnvironment.stage→layerStagein the uncovered-block suite, matching main's rename (refactor(repo): enforce cell-architecture layer placement and fix the six refused services #321). NoRunEnvironment.stagecall survives ate071bd047or the head. Testing gap recorded: no test pins that every arid rule description keeps main'sARID_CALLEE/KEEP_ALLsuffixes.Round 2: one correctness lens over the fix commits (run
20261013-074248-c2898301)9957fc4d3(build(deps): bump the testing-major group across 1 directory with 3 updates #6, feat(repo): ignorer protocol on ancestors-as-data and the oxc vocabulary #10),c4eb9fb7c(build(deps): bump nixpkgs fromd6524aato8ce4ef6#11),a6ca1dd89(docs(global): rewrite root README for stryker-js-effect #8),413e9af81(build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 #3),b1d5af60b(refactor(repo): drop the starter package and reframe the repo #9).correctness-reviewer, DeepSeek V4.1 Flash, 64 turns, all DeepSeek; no MiniMax call (X296D.jsonl).packages/stryker-js/tests/__fixtures__/checker-worker.mjs:17CHECK_DELAY(5 ms)mutatorName(answerOf, unchanged), and the engine awaits every check answer, so a 5 ms latency changes no status or reason. The suites passed after413e9af81(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(run20261013-035820-4d26adec, base251201755)correctness-reviewerR296Correctness-3.jsonladversarial-reviewerR296Adversarial-3.jsonlsecurity-reviewerR296Security-3.jsonlproject-standards-reviewerR296Project-2.jsonltesting-reviewerR296Testing-3.jsonlmaintainability-reviewerR296Maintainability-3.jsonlapi-contract-reviewerR296Api-2.jsonllearnings-researcherR296Learnings-3.jsonlcorrectness-reviewerV296b.jsonldocs-engineer.changeset/arid-uncovered-block-guard.md:3arid-uncovered-block:, and packages/stryker-js-cli-contract/src/stream-version.schema.tpackages/stryker-js-instrumenter/src/guard-relation.workflow.ts:160ifwhose condition this mutant sits in' and .changeset/arid-uncovered-block-guard.md says 'a mutant in the condition of anif' — and guard-relation.workflow.ts ownerOf (line 154 `Boolpackages/stryker-js/tests/__fixtures__/guarded-block-checker/worker.mjs:1413e9af81The guarded-block checker fixture reuses the sharedserveCheckerbootstrap.serveCheckerand is imported by both subsumption-checker/worker.mjs and compile-error-worker.mjs, and the new fixture imports none of it.packages/stryker-js-instrumenter/src/Transformer.service.ts:13589957fc4d3Guard application moves intoapply-guards.tsbeside the guard-relation workflow.guardIndexOf(Transformer.service.ts:1427-1441) matches on the workflow'sGuardRelationDecision, withguardOf(1443-1446),withGuard(1448-1453) and theguardRelationcall (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 againspackages/stryker-js/src/run/mutation-test-plan.cell.ts:324a6ca1dd89settledBeforeTestsOf/plannedTotalOfbeside the plan type own the bucket counts.plannedTotalfrom the four buckets, mutant-settlement.ts:336-337 re-listsearlyResults/heldSubsumed/heldGuardsfor 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 addheldGuards.packages/stryker-js/tests/uncovered-block.integration.test.ts:188b1d5af60bThe suite now spawns the built CLI and asserts only report rows and stdout.{ exit, results: exit.value.results }fromEngine.mutationTestCelland every assertion readsobservation.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.packages/stryker-js-instrumenter/src/Transformer.service.ts:13749957fc4d3The pass-throughguardOfis gone with that move.guardOf(Transformer.service.ts:1443-1446) is literallyindex.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.packages/stryker-js-instrumenter/src/guard-relation.workflow.ts:124c4eb9fb7cThe premise was corrected by observation: the guard is dropped only when the run plans noBlockStatementmutant for the block (excluded, directive-disabled or outsidemutate); a planned block mutant keeps the guard even when Ignored. The commit documents that precondition onMutant.guard, adds it to the changeset, and pins both outcomes with instrumenter scenarios. No behavior change.blockMutantIdwithblockMutantAtinsideOption.all, so a site whose consequent block has no BlockStatement mutant (excluded mutator, disable directive, or amutaterange that drops it) is dropped entirely andguardsilently 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 whoseapplies_whenmatches 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_whendoes not touch any changed file are omitted; rules with a matchingapplies_whenare listed with a verdict.error: S.Never; no if/else/switch/for/while in decision bodieserror: S.Neveron every new Workflow.makeguard: S.optional; :126-128alternate: S.optional; plan-mutant-tests.workflow.ts:23heldBy: S.optional— genuinely-absent annotations, no correlated discriminant on the same struct; 'held' has its own HeldGuard typeNo pack rule is contradicted by this diff; every matching rule is
complyornot-applicable.