Skip to content

build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 - #3

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/effect-4.0.0-rc.113
Closed

dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/effect-4.0.0-rc.113

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Bumps effect from 4.0.0-rc.112 to 4.0.0-rc.115.

Release notes

Sourced from effect's releases.

effect@4.0.0-rc.115

Patch Changes

  • #8196 657254b Thanks @​gcanti! - Optimize schema initialization while preserving custom constructor options.

  • #8190 f9ef0e9 Thanks @​javascript-unsafe! - Omit response bodies for statuses 204, 205, and 304 in HttpServerResponse.toWeb and the Bun/Deno HTTP adapters, preventing invalid Web responses and hung requests. Cancel omitted raw ReadableStream bodies, and finalize request resources without starting omitted Effect streams.

  • #8187 4f73f9e Thanks @​tim-smart! - Parameterize persistence lookup keys in both SQL backing stores' getMany queries.

effect@4.0.0-rc.114

Patch Changes

  • #8177 3ff4952 Thanks @​tim-smart! - Allow Effect.cachedWithTTL to compute the TTL from each completed Exit, so successes and failures can use different cache durations.

  • #8164 6d55555 Thanks @​sam-goodwin! - Keep Node and Bun file stats usable when optional numeric metadata exceeds the safe integer range by returning Option.none() for those fields.

  • #8162 716e0c0 Thanks @​tim-smart! - Fix published declarations referencing symbols stripped as @internal, which broke consumers compiling with skipLibCheck: false. Effectable.d.ts now uses the public Effect.TypeId, Match.d.ts no longer aliases an internal Contextual type, Schema.d.ts ships the AnnotationSchemaConstraint alias it references, and the CLI's toFlagDoc helper is marked internal so it no longer leaks Param.getParamMetadata.

  • #8160 d4e4ad5 Thanks @​gcanti! - Fix SchemaRepresentation.toCodeDocument generating invalid TypeScript for optional tuple elements containing unions or nested readonly tuples. Optional element types are now parenthesized, for example readonly [(string | number)?] instead of readonly [string | number?]. Generated runtime schemas are unchanged.

  • #8158 b1988f4 Thanks @​gcanti! - Fix SchemaRepresentation.toCodeDocument dropping Struct fields named __proto__ from generated schemas. These fields now use computed keys, such as Schema.Struct({ ["__proto__"]: Schema.String }), so the generated schema validates them correctly.

  • #8169 482b7d7 Thanks @​gcanti! - Improve SchemaRepresentation.fromJsonSchemaDocument and fromJsonSchemaMultiDocument:

    • Import { not: {} } as Schema.Never (#8137).

    • Import closed records with one patternProperties entry, additionalProperties: false, and no declared or required properties when patterns: "apply" is enabled. These were previously rejected.

      {
        "type": "object",
        "patternProperties": { "^a": { "type": "number" } },
        "additionalProperties": false
      }
      Schema.Record(
        Schema.String.check(Schema.isPattern(/^a/)),
        Schema.Finite
      )
    • Reject open patterned objects with patterns: "apply" instead of generating incompatible TypeScript index signatures.

      {
        "type": "object",
        "patternProperties": { "^a": { "type": "number" } },
        "additionalProperties": true

... (truncated)

Changelog

Sourced from effect's changelog.

4.0.0-rc.115

Patch Changes

  • #8196 657254b Thanks @​gcanti! - Optimize schema initialization while preserving custom constructor options.

  • #8190 f9ef0e9 Thanks @​javascript-unsafe! - Omit response bodies for statuses 204, 205, and 304 in HttpServerResponse.toWeb and the Bun/Deno HTTP adapters, preventing invalid Web responses and hung requests. Cancel omitted raw ReadableStream bodies, and finalize request resources without starting omitted Effect streams.

  • #8187 4f73f9e Thanks @​tim-smart! - Parameterize persistence lookup keys in both SQL backing stores' getMany queries.

4.0.0-rc.114

Patch Changes

  • #8177 3ff4952 Thanks @​tim-smart! - Allow Effect.cachedWithTTL to compute the TTL from each completed Exit, so successes and failures can use different cache durations.

  • #8164 6d55555 Thanks @​sam-goodwin! - Keep Node and Bun file stats usable when optional numeric metadata exceeds the safe integer range by returning Option.none() for those fields.

  • #8162 716e0c0 Thanks @​tim-smart! - Fix published declarations referencing symbols stripped as @internal, which broke consumers compiling with skipLibCheck: false. Effectable.d.ts now uses the public Effect.TypeId, Match.d.ts no longer aliases an internal Contextual type, Schema.d.ts ships the AnnotationSchemaConstraint alias it references, and the CLI's toFlagDoc helper is marked internal so it no longer leaks Param.getParamMetadata.

  • #8160 d4e4ad5 Thanks @​gcanti! - Fix SchemaRepresentation.toCodeDocument generating invalid TypeScript for optional tuple elements containing unions or nested readonly tuples. Optional element types are now parenthesized, for example readonly [(string | number)?] instead of readonly [string | number?]. Generated runtime schemas are unchanged.

  • #8158 b1988f4 Thanks @​gcanti! - Fix SchemaRepresentation.toCodeDocument dropping Struct fields named __proto__ from generated schemas. These fields now use computed keys, such as Schema.Struct({ ["__proto__"]: Schema.String }), so the generated schema validates them correctly.

  • #8169 482b7d7 Thanks @​gcanti! - Improve SchemaRepresentation.fromJsonSchemaDocument and fromJsonSchemaMultiDocument:

    • Import { not: {} } as Schema.Never (#8137).

    • Import closed records with one patternProperties entry, additionalProperties: false, and no declared or required properties when patterns: "apply" is enabled. These were previously rejected.

      {
        "type": "object",
        "patternProperties": { "^a": { "type": "number" } },
        "additionalProperties": false
      }
      Schema.Record(
        Schema.String.check(Schema.isPattern(/^a/)),
        Schema.Finite
      )
    • Reject open patterned objects with patterns: "apply" instead of generating incompatible TypeScript index signatures.

      {
        "type": "object",

... (truncated)

Commits
  • 4a05d49 Version Packages (rc) (#8192)
  • 657254b Optimize Schema initialization (#8196)
  • f9ef0e9 fix(http): omit body in toWeb for 204, 205, and 304 (#8190)
  • 4f73f9e fix(persistence): parameterize persistence lookup keys (#8187)
  • cdb3ac9 Version Packages (rc) (#8159)
  • 9941e6d feat(cli): make message optional for Prompt.Select and Prompt.MultiSelect (#8...
  • 6d55555 fix(FileSystem): keep stat when optional inode exceeds MAX_SAFE_INTEGER (#8164)
  • 3ff4952 Support TTL callbacks in Effect.cachedWithTTL (#8177)
  • f48fb1c Add Match.valueTags API contract type tests (#8175)
  • 482b7d7 feat(SchemaRepresentation): improve JSON Schema imports (#8169)
  • Additional commits viewable in compare view

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Sep 13, 2026
@dependabot dependabot Bot changed the title Bump effect from 4.0.0-rc.112 to 4.0.0-rc.113 build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.113 Sep 13, 2026
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/effect-4.0.0-rc.113 branch 4 times, most recently from 324278a to 3daea1c Compare September 14, 2026 12:37
@dependabot dependabot Bot changed the title build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.113 build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 Sep 14, 2026
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/effect-4.0.0-rc.113 branch 4 times, most recently from eac2007 to b73733b Compare September 15, 2026 22:44
@dependabot dependabot Bot changed the title build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 chore(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 Sep 15, 2026
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/effect-4.0.0-rc.113 branch 8 times, most recently from 7d80770 to 62d1d6e Compare September 18, 2026 07:32
@dependabot dependabot Bot changed the title chore(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 build(deps): bump effect from 4.0.0-rc.112 to 4.0.0-rc.115 Sep 18, 2026
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/effect-4.0.0-rc.113 branch 6 times, most recently from 58f03e4 to 644f553 Compare September 19, 2026 16:35
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…ption

Fixes c0cf821b #1, #3 and #6 (correctness, project-standards and
maintainability, all confirmed): test/bench/package.json still said each
push is benched against its first parent. It now states the rule the plan
job applies, the same one README.md and the bench.yml header state.

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U20.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…e real refusal gate

Findings #2 and #3 of full-path review run 20261013-061700-57a7f041.

#2: the defaultMutators header now says every stock mutator is default-tier,
the stock registry declares no opt-in entries, opt-in is a tier only a plugin
catalog declares for its own entries, and a stock name in
mutator.optInMutations is refused before instrumentation as a config failure
(NotOptInTier).

#3: the selectMutators doc now names decodeMutatorSelection
(packages/stryker-js/src/decode-mutator-selection.workflow.ts) as the refusal
gatekeeper instead of the instrumenter's instrument, and says a name the loaded
catalogs do not declare opt-in selects nothing here.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
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.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…o root

Supersedes the 0711 plan. Fixes b2fe30db #2 (testing-confirmed: KD13 said
no e2e-core export was added, but the pack fixes added LegTargetMs,
LegTime, RegressedMetric, Regression and PhaseRegressed) and answers #1
(feb5085's subject is scoped e2e-core; rewriting it needs a force push,
and the squash merge carries only the PR title).

KD21 records the round. U27-U29 fix #12 (GreenRunsJson loses its
generated laws), #11 (corpus schemas as data classes) and #6 (two
stderr-tail helpers). #9 (pin Turbo's --packages output) and #3 (test
atRunPath's renames) ask for tests the admission gate refuses: a Turbo
pin spawns git and turbo, and atRunPath is a private executor helper. The
repository's real-Turbo pins live in scripts/ under PR #319, so both go
to root. The Turbo semantics the plan job relies on were measured on
2.10.12 in a scratch repository and are recorded in KD21.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
The scenario closing the plan-since suite repeated the unresolvable ref
case without the richer scenario's assertion that no plan file was
written. The richer scenario stays.

Finding #3 of review run 20261013-061307-68aac119.
ryanleecode pushed a commit that referenced this pull request Oct 13, 2026
* docs(plans): plan cell layer placement against the cell-architecture rule

Inventories every Layer export in packages/** and test/e2e against compound-packs/cell-architecture/service-and-layer-boundaries.md (systemfsoftware#708), records the gate probe on a scratch gritlint.json, and splits the work into a gate-first PR A and a PR B for placements the gate cannot see.

* docs(plans): apply supervisor rulings to the cell layer placement plan

RunEnvironment.stage becomes layerStage; R2 forbids type-only driver imports with F8 as the one named exception; inventory gains rows 33-38 (RpcGroup.toLayer handlers, cell kinds, F6 sites); microsandbox-* sibling names; gate runs use the flake-built gritlint and PR A re-runs G1-G3 on the build it locks; U10 proven by typecheck and main's postsubmit e2e; Objective narrowed, F6-F8 named gaps.

* docs(plans): re-derive cell layer placement decisions from primary sources

Replaces the supervisor Rulings with Decisions H1-H7, each citing the pack at systemfsoftware 02bf9653b1, ADR-0003, gritlint probes or the OpenTelemetry library guidelines. Row 28 (BakedFixtureCache) stays as gap F9, @opentelemetry/api is not a driver, e2e runs on pull requests (G9), gritlint locks main with a84c2cc03 as fallback (H7), and the inventory is refreshed against ddeb67a (G7-G8). Applies the fix-now ce-doc-review findings: gate coverage stated, local acceptance is the flake-built gritlint, F8 widened, F10 added.

* docs(plans): refresh the cell layer placement inventory at ee7038a

U1 and AE1 derive RunEnvironment.stage call sites by grep (22 at ee7038a, adding lockfile-closure-digest from #303); CheckerRuntime.layer is at :110. G10 records the refresh; H5 follows #314 moving e2e to main pushes; H8 and F11 leave the provisional TypeQueryLive entry (#277) to Stream I.

* refactor(stryker-js)!: keep each service Layer on its class under the pack names

Removes the OutputModeProbeLive and RunEventDrainLive aliases, moves WorkerReportsLive onto WorkerReports as its static layer, and renames MachineConsole.captureLayer, RunEventDrain.fileLayer and RunEnvironment.stage to layerCapture, layerFile and layerStage, so no *.service.ts exports a module-level Layer, a *Live binding or a *Layer field (cell-architecture/service-and-layer-boundaries, U1 of docs/plans/2026-10-11-0952-refactor-cell-layer-placement-plan.md).

BREAKING CHANGE: Engine.RunEnvironment.stage is now layerStage, RunEvent.RunEventDrain.fileLayer is now layerFile, and RunEvent.RunEventDrainLive is removed in favour of RunEvent.RunEventDrain.layer.

* refactor(repo): move the trace and vitest adapters out of *.service.ts

trace-context-rpc.service.ts becomes trace-context-rpc.ts and VitestRunner.service.ts becomes vitest-test-runner.ts, which exports make beside layer; RunFilter moves to VitestRunner.schema.ts so MutantRun.cell.ts no longer imports an adapter (U2, U3).

* build(repo): enforce the cell-architecture pack and pin the packs

gritlint.json enrols cell-architecture; the gritlint flake input moves from 1d60be3 to gritlint main 0e2e07b3 (descends from fe886c5); .compound-engineering/config.yaml pins cell-architecture, boundary-testing and schema-laws to systemfsoftware 02bf9653b1 (U4, U5).

* refactor(stryker-js): build WorkerReports through a static make

Review disposition (ce-code-review run 20261012-205932-45be4afa, finding #3, validator-confirmed): the cell-architecture pack fixes a tier-2 static layer as `Layer.effect(this, this.make)` with the constructor effect named `make` (service-and-layer-boundaries.md:38,57). The body is unchanged.

* refactor(repo)!: move the checker and vitest implementations beside their services

U6-U8 of docs/plans/2026-10-11-0952-refactor-cell-layer-placement-plan.md. plugin-runtime: WorkerOptions/WorkerTelemetry layer -> layerConfig (pack: cell-architecture, service-and-layer-boundaries.md:41). typescript-checker: the runtime Layer and checker constructor move from CheckerRuntime.service.ts to typescript-checker-runtime.ts (TypeScriptCheckerRuntime on ./runtime); CheckerWorker leaves the service suffix. vitest-runner: the VitestSession implementation moves to vitest-session.ts, vitest-test-runner.ts requires the session, and main.ts provides it once per worker; TestRunnerWorker leaves the service suffix; drivers/ is flattened.

* refactor(stryker-js-instrumenter): move the oxc parser module out of the service suffix

U9: Parser.service.ts -> Parser.ts and drivers/oxc-program.ts -> oxc-program.ts; Transformer.service.ts and InstrumentHeader.ts take ParseFailed from Parser.schema.ts.

* refactor(e2e): put the microsandbox harness layers beside their services

U10: GuestJobs and StrykerCliRunner implementations move to microsandbox-guest-job.ts and microsandbox-stryker-cli-runner.ts; harness-layers.ts binds them and owns the telemetry layer. guest-job.service.ts keeps import type { MicroVM } (plan F8).

* refactor(stryker-js-instrumenter): drop the ParseFailed re-export no module imports

Review disposition (ce-code-review run 20261012-212250-6e120e97, maintainability finding): U9 moved the last two importers to Parser.schema.ts.

* ci(repo): re-run check after the lockfile-closure-digest flake

Run 38311506056 failed one scenario (lockfile-closure-digest.integration.test.ts:367) that passes locally on 7890913 (12/12) and in #322 CI on a tree containing this branch; main fails a different single test per run. The token cannot re-run jobs (403 on gh run rerun and both REST rerun endpoints), so this empty commit starts a fresh CI run.

* docs(plans): supersede the cell layer plan so PR A leaves the gate to its owner

CONST-E7 (EVIDENCE-NOT-SELF-GRADING) makes gritlint.json, the gritlint lock
and the .compound-engineering pack list read-only to the work they grade.
The 2026-10-13 plan replaces the 2026-10-11 one: U4 and U5 retire, U11
restores those files to main's bytes, and U12 proves the six fixes with the
pinned gate run outside the repository (KTD8) and carries the enrollment
request for Stream K (H9).

* revert(repo): leave gritlint.json, flake.lock and the pack list at main's bytes

CONST-E7 (EVIDENCE-NOT-SELF-GRADING): lint configs, rule lists and review
criteria are read-only to the work they grade, including in a separate commit
by the same hands. The cell-architecture enrolment in gritlint.json and the
gritlint input bump in flake.lock are root-owned and Stream K authors them
later; the .compound-engineering pack pin returns to the root with them.
This restores all three files to origin/main (plan U11, H9). PR A's body
carries the enrollment request and the pinned-gate evidence instead.

* fix(stryker-js-typescript-checker): publish the TSCompiler type make and nodes take

Review disposition (ce-code-review run 20261013-061611-ae14ea80, validator-confirmed): this PR publishes TypeScriptCheckerRuntime.make(compiler: TSCompiler) on ./runtime, and nodes(self: TSCompiler) was already there, but no entry exported TSCompiler (pack: package-topology, public-signature-types). ./runtime now exports the type; dist/runtime.d.mts carries it. The major changeset and README name it.

* chore(changeset): name the packages the typecheck gate moves

After #335 the change-intent gate reads turbo's typecheck graph. On this branch it moves six publishable packages; three had no intent. stryker-js-vitest-runner's own modules moved (U8), so it takes a patch. stryker-js and stryker-js-html-reporter have no file of their own changed and move only through the ^build hashes of the instrumenter and plugin-runtime, so they take none. Derived from turbo run typecheck --dry=json on origin/main 1c9f940 and on this head (turbo 2.10.12).
ryanleecode pushed a commit that referenced this pull request Oct 13, 2026
…ns share tempDirName (#331)

* fix(stryker-js)!: never remove or block another run's sandbox when runs share tempDirName

Release removed the shared tempDirName parent after reading it as empty, with
a recursive remove, so a sandbox another run created in between was deleted
with it; acquisition failed outright when a finishing run removed the parent
between its makeDirectory and makeTempDirectory.

- EmptyDirectory port joins Engine.EnginePorts: removeIfEmpty runs rmdir in
  the Node adapter, and classify-empty-directory-removal.workflow decides the
  errno code (ENOTEMPTY/EEXIST kept, ENOENT absent, anything else a
  PlatformError).
- Sandbox.service releases its own sandbox, then removes the parent only if
  empty; acquisition retries parent+sandbox creation while the parent vanished,
  bounded at 15 rival removals.
- sandbox-cleanup-race.integration.test.ts drives real Engine runs through
  every filesystem step of a held run for the cleanup/acquire, acquire/cleanup
  and cleanup/cleanup pairs; it fails on the old Sandbox.service.

Major per author-changesets CS1: EnginePorts widens, so hand-built port
layers must add EmptyDirectory (same shape as the SourceParser change).

* fix(stryker-js): keep the rmdir rejection as cause and drop the restating errno law

Review dispositions (ce-code-review, report-only):
- #1/#2 (property law drew no ENOTEMPTY/EEXIST/ENOENT): the Kept/Absent
  arms are graded by sandbox-cleanup-race.integration.test.ts through real
  runs; narrowing the Kept codes fails 3 of 4 pairs and renaming ENOENT
  fails 2 of 4 (probed locally, then reverted). The law kept is the one
  with an oracle independent of the code: a failed removal carries the
  errno it failed with. A literal-drawn law would restate the decision.
- RemovalFailed now fails with a PlatformError whose cause is the rmdir
  rejection, as the FileSystem errors it replaces did.
- #5: the exists/readDirectory steps are labelled as the red-proof hooks.

* test(stryker-js): race real runs against a rival on disk instead of scheduling injected ports

The house law of 2026-10-12 rules out integration tests that inject ports or read engine internals. The race feature now runs Engine.strykerCell on a real project while a rival removes the shared directory whenever it is empty, and grades the published Engine.EmptyDirectory on real directories. On main 2512017 the from-the-start row fails with all four runs failed; the after-acquisition row failed in 2 of 5 runs. The superseding plan records that F2/F3 are reproduced, not pinned.

* fix(stryker-js): keep a non-directory removal target and normalize the failure tag

Fixes review findings #3 and #4.

Feed the raw rmdir errno code straight to the classification workflow instead of round-tripping it through PlatformError.reason.description, and build the PlatformError only for the RemovalFailed outcome with a SystemErrorTag normalized like @effect/platform-node-shareds errno table (fallback Unknown), keeping the code in the error.

Classify ENOTDIR as kept: a non-directory at the parent path is left in place rather than turned into a defect of a finished run.

* fix(stryker-js): publish the EmptyDirectory contract and Node layer

Fixes review findings #1 and #2.

State EmptyDirectoryShape.removeIfEmpty contract on the published type and in the changeset migration note: resolving means the directory was removed, was already gone, or was left because it holds entries or is not a directory; only other failures reject.

Export the Node EmptyDirectory layer and re-export it through Engine as Engine.nodeEmptyDirectoryLayer, so a consumer that builds its own EnginePorts can provide a correct EmptyDirectory without re-deriving the rule. Regenerate the api report.

* test(stryker-js): pin the empty-directory removal partition to an errno oracle

Fixes review finding #4.

Replace the failure-only law with one that draws a single errno per case from the production command schema and pins the whole partition against an independent oracle table: ENOTEMPTY/EEXIST/ENOTDIR keep the path, ENOENT is absent, and anything else fails carrying its code. Verified red when ENOTDIR is dropped from kept and when ENOENT is moved to kept.

* test(stryker-js): grade acquisition races and sentinel data loss in the sandbox race feature

- Finding #6: count the rival removals that land before the shared directory first holds an entry (inside a run's create-parent -> make-sandbox window) and assert the from-the-start row saw at least one, so green entails the acquisition retry fired. Tighten the rival to rmdir-only attempts so it reliably wins that window (6/6 green here; red 3/3 with the retry bound set to 0).
- Finding #5: race five real Engine.strykerCell runs (concurrency 2) against a rival keeping sentinel files in the shared directory and assert every run succeeded and no sentinel was lost (red 3/5 on main with lost=1).
- Finding #4 (test half): assert a plain file and a symlink to a directory are reported as a successful removal left in place, per the new ENOTDIR-as-kept adapter behaviour; red until that adapter change integrates.

* docs(stryker-js): fit the sandbox race intent under the changelog ceiling

The intent grew past 900 bytes when the review fixes for findings 1 and 2 added the EmptyDirectory contract and Engine.nodeEmptyDirectoryLayer; the contract now reads as one migration sentence.

* fix(stryker-js): keep the rmdir rejection and drop tags the classifier never reaches

delta P3: removalErrorTagByCode mapped ENOENT, EEXIST and ENOTDIR, but
classifyEmptyDirectoryRemoval routes those codes to DirectoryAbsent or
DirectoryKept and never to RemovalFailed, so the three entries were
unreachable. Drop them.

delta P3: the PlatformError synthesized for a genuine removal failure dropped
the raw rmdir rejection. Carry the rejection through the classifier in
EmptyDirectoryRemovalRejected and set it as the PlatformError cause, keeping
the errno code as the classification input.

* test(stryker-js): assert the acquisition race only where a rival rmdir proves it

delta P2: the after-acquisition outline row asserted racedAcquisition false
under strict per-row equality, but a rival rmdir that lands before its first
holding observation also lands in that row, so the false could false-fail in
CI. Keep the from-the-start row proof that green entails the acquisition
retry fired, and assert nothing about racedAcquisition in the
after-acquisition row.

delta P3: the sentinel scenario Given claimed a real project whose runs share
one temporary directory while returning Effect.void, the project being
created later in the When. The Given now creates that project and makes it
the working directory, and the When runs the runs against it.

* docs(stryker-js): record that a non-directory shared path is deliberately kept

delta P2: ENOTDIR (a symlink or a plain file at the shared path) is now left
in place, where main removed it. R4 and KTD2 now state that a shared path
that is not a directory, including a symlink, is left in place and never
fails a finished run, and record that main removed such a path.

The started plan is superseded by
docs/plans/2026-10-13-0520-fix-sandbox-cleanup-race-plan.md, which carries
the full corrected contract.

* fix(stryker-js): keep the removal rejection in a schema module and assert only provable race outcomes

Second delta review of #331: EmptyDirectoryRemovalRejected is a tagged error, which ADR-0001 places in a *.schema.ts module, so it moves from the classification workflow to empty-directory.schema.ts. The after-acquisition race row no longer asserts that the rival removed the directory, which it can only do inside a run's own cleanup; the from-the-start row's acquisition-window assertion implies a removal. KTD4 and U1 now list the assertions the outline makes.

* docs(plans): name all five remove-if-empty targets and the new schema module

Third delta review of #331: KTD4 listed four remove-if-empty outcomes where the scenario grades five (the symlink row), and U2 did not list empty-directory.schema.ts.

* fix(stryker-js): race the sandbox cleanup without a test-only export

Root review of 67a640c: the race test emulated another run's cleanup through Engine.EmptyDirectory.removeIfEmpty, an export added to the published API for the test. The other run is now represented by what it does on disk: a rival that rmdirs the empty shared directory (the race proof) and several real runs at once (the concurrent-runs evidence). The two scenarios that graded the port directly are removed; a real run over a shared directory holding another run's file replaces the port's holding target. Engine.EmptyDirectory, EmptyDirectoryShape and Engine.nodeEmptyDirectoryLayer are no longer exported: nothing outside the test named them, and api:check passes without them. Plan KTD5 records both routes and the main-red rates.

* refactor(stryker-js): keep the empty-directory Node layer local to its module

Its only use is makeNodePlatformLayer in the same file now that Engine no longer re-exports it.

* docs(plans): scope KTD5's export evidence to source files

Delta review of 86aa6b1 (P3): the unscoped grep also matched the changeset and the plan. At 67a640c, *.ts files name Engine.EmptyDirectory only in the race test and nodeEmptyDirectoryLayer only in node-platform.ts and its Engine re-export.

* test(stryker-js): delete the empty-directory classification law

CI on 6860528 refuted it with the errno code "__proto__" (replay seed 3902418167). The defect was the law's own oracle: an object lookup reached Object.prototype, while the workflow's S.Literals match classified the code correctly as RemovalFailed. The oracle was a known-answer table restating the mapping, which the property-test rule deletes rather than reshapes. Real runs still reach ENOTEMPTY -> kept and ENOENT -> absent; the plan records that EEXIST and ENOTDIR -> kept are graded by no test.

* test(stryker-js): restore the empty-directory classification law with an own-key oracle

aed144d deleted the law after CI on 6860528 refuted it with "__proto__". The refutation was an oracle defect: ERRNO_ARM_ORACLE[code] reached Object.prototype for an inherited key, while the workflow's S.Literals match classified the code correctly as RemovalFailed. The law is restored unchanged except oracleArmOf, which now looks codes up with Object.hasOwn; errnoArb still draws any string, so prototype keys stay in the domain. ADR-0001:59 requires workflows to have properties. Replaying the CI seed (property=902008308;seed=3902418167;runs=100) refutes the old oracle at ["__proto__"] and passes with this one.
ryanleecode pushed a commit that referenced this pull request Oct 13, 2026
* ci(bench)!: bench each main commit against its first parent

The bench leaves the pull-request path. bench.yml runs on push to main
(docs-only pushes excluded) and on workflow_dispatch, with concurrency
bench-${{ github.sha }} and no cancel, so no PR pays for or waits on it.

- Side A is the first parent, side B the pushed commit; both are worktrees
  under RUNNER_TEMP with their own released tarballs and frozen install,
  and the checkout holds only the harness.
- Every in-process deadline is gone: BENCH_DEADLINE_MS, the setup budget,
  the run deadline, BENCH_RUN_TIMEOUT_MS, and the setup-timed-out,
  budget-exceeded, timed-out and timeout shapes. Every timeout-minutes is
  60; that job timeout is the only kill.
- The report carries the leg's wall time (from JOB_STARTED_AT) against a
  10-minute target, never enforced, and schemaVersion moves to 2.0.
- Only a leg's total decides a regression (non-overlapping samples and
  |delta| >= max(3% of A, 10 ms)). The regression block names the commit,
  its parent, total then every regressed phase, and the next action: a
  revert PR or root-fix P0 on main, speed evidence only elsewhere.
- Every run executes from RUNNER_TEMP/bench-run, so both sides record the
  same project root, inputs digest and mutant ids. Bench configs reference
  plugins relative to the config file so they survive the move.

Plan: docs/plans/2026-10-12-1800-ci-bench-off-pr-plan.md (layer 1; the
enterprise install is layer 2).

BREAKING CHANGE: bench-report.json schemaVersion 2.0 drops the
setup-timed-out and budget-exceeded abort codes and the timed-out run exit,
and adds ref, leg and the regression block.

* docs(bench): say an environment-incomplete abort writes no report

Review finding (correctness, P3): the README said every aborted leg uploads
its report, but main.ts publishes environment-incomplete without writing
bench-report.json or the step summary.

* ci(bench): drop the hand-written path filter from the main trigger

Root amendment 2026-10-12: no change classifier where Turbo can do the job.
paths-ignore (**/*.md, docs/**) skipped none of 11 sampled main commits;
turbo query affected skips three of them. Every push now benches every
leg until layer 1b scopes legs with turbo query affected.

Plan: docs/plans/2026-10-12-1854-ci-bench-off-pr-plan.md supersedes the
1800 plan and records the turbo-query evidence and rule-4 reasons.

* docs(plan): bench every main SHA; defer Turbo affected-only leg selection

Sub-conductor ruling 2026-10-12: the acceptance is a comparison per main
SHA, so Turbo's affected-packages answer stays evidence, not a leg filter.
The 1926 plan supersedes 1854: layer 1b is dropped, KD10 keeps Turbo as
the owner of builds and cache and adds no classifier, KD11 defers
affected-only selection to root.

* fix(bench): name phase-only slowdowns instead of claiming no phase changed

A summarized report with no regression said "no project/phase changed past
the signal threshold" even when its table showed a phase that regressed under
an unchanged total. The notice is now titled "Bench phase-only slowdown", says
no leg total crossed the regression threshold, and lists each such cell as a
phase-only slowdown. A new law drives a check-only slowdown through
summarizeBench and renderBenchReport.

The plan moves to docs/plans/2026-10-12-2032-ci-bench-off-pr-plan.md
(supersedes 1926) and adds the CONST-W3 declaration for the bench.yml edits,
the corrected cause of the stream-undecodable PR runs (#298 changes the
stream contract), and that case as a known limit.

* docs(plan): list every bench.yml edit in the CONST-W3 waiver and scope the PR-run evidence

Review of eec5cfa found that the waiver called side B the checkout (it is the bench-b worktree) and left out the checkout ref, Turbo cache and harness install edits. Run 38284821646 also had a budget-exceeded abort on one leg, so the Problem Frame now scopes stream-undecodable to the other three legs.

* test(e2e-core): move the bench decision checks beside their workflows as laws

bench-decisions.integration.test.ts fed hand-written NDJSON lines and
report text to the pure readBenchRun and coveringTestFiles workflows and
asserted their decision fields. Under root's 2026-10-12 testing law an
integration test drives the real product and asserts only observable
outcomes, and a pure decision gets property laws beside it (CONST-T12,
CONST-T14). This layer had to change the file (ReadBenchRunCommand's
exit field), so it is deleted rather than carried.

The behaviours no other suite pinned are now laws over the production
schemas: testsExecuted sums every drawn MutantCost, the first undecodable
line is named by its 1-based number among blank and decodable lines, a
verdict count other than one is stream-invalid, verdict phase durations
are carried as reported or refused when absent, covering test files are
the sorted distinct holders, and a non-JSON report is refused. The
legacy-line decode stays pinned by the cli-contract wire-line suite.

The plan moves to docs/plans/2026-10-12-2154-ci-bench-off-pr-plan.md
(supersedes 2032) with the widened edit scope and test admission rows.

* refactor(bench): keep the bench's new decisions in test/bench

Root's 2026-10-13 scope law puts no new code in test/e2e-core. The
layer's four new modules and their laws move to test/bench/src, which is
their only consumer: run-path.schema, plan-run-path and settle-run-path
(the fixed run path) and measure-leg (the leg wall time). e2e-core's
mod.ts is back to main.

measureLeg now decides a bench-local LegClock (LegElapsed or
LegClockBehind) that main.ts maps onto the report's LegTime:
make-body-purity forbids a decision from constructing a class imported
from another package, and e2e-core cannot import test/bench.

test/bench gains a test lane shaped like test/checker-parity: a test
script, a vitest config with inSourceSchemaLaws, tsconfig.test.json and
lint:tsgo over it. The plan moves to
docs/plans/2026-10-13-0223-ci-bench-off-pr-plan.md (supersedes 2154).

* fix(bench): abort an outside-tree run path as a defect

Review of f1da499 (MiniMax lenses, report-only) found two defects in
the bench's run path:

- planRunPath's identity law drew cwd and CLI tails of length one or
  more, so the enterprise shape (cwd equal to the tree) was never
  generated. Dropping the `path === tree` operand left the law green;
  with empty tails allowed it now fails.
- A cwd or CLI outside its side's tree aborted as run-path-move-failed,
  whose next action says to fix the runner. It is a bench bug, so it
  now aborts as defect. The README lists that case.

The plan moves to docs/plans/2026-10-13-0238-ci-bench-off-pr-plan.md
(supersedes 0223) with an accurate split of the e2e-core law files.

* fix(bench): name both causes in the run-path-move-failed action

63ba7bb sent an outside-tree run path to `defect`. Review of that head
found two problems: it contradicted KTD3, and `defect`'s next action asks
for a stack trace, which a typed failure does not have. So the outside-tree
case aborts as run-path-move-failed again. That code's next action, which
this layer added, now names both causes. An OS error needs a runner fix; a
path outside the tree is a bug in side setup. The README matches.

The plan moves to docs/plans/2026-10-13-0258-ci-bench-off-pr-plan.md
(supersedes 0238), with U4 brought back in line with KTD3.

* docs(plan): plan the pack-violation fixes for the bench lane

Supersedes the 0258 plan. Root (2026-10-13) requires every confirmed pack
violation in the a359a82 full-path receipt to be fixed, not waived.
Adds KD12-KD15 and units U7-U12, the e2e-core boundary (one in-source
refusal block per refined e2e-core file, LegTime as a data union), and
test-admission rows for the refusal properties and the settle law.

* fix(bench): carry run-path settle outcomes as tagged causes

Fixes full-path receipt findings #8 (four-channel-contracts) and #11
(tagged-unions-over-state-by-presence) on a359a82.

The settle command took restoreError and runFailure as nullable
Cause.pretty strings and decided on their presence. It now takes two
tagged unions, RunPathMoveBack (MovedBack | MoveBackFailed { cause }) and
RunPathRunEnd (RunSucceeded | RunFailed { cause }), and matches on the
move-back. RunPathUnrestored carries cause: S.optional(S.Unknown) and the
run's end instead of strings; the service passes each Exit's Cause as is.
Text is rendered once, in the error's message, when the report's reason
is built.

The settle law draws the production SettleRunPathCommand and asserts
unrestored iff the move-back failed, with both causes carried by
identity. Plan: docs/plans/2026-10-13-0416-ci-bench-off-pr-plan.md U7.

* fix(bench): make the report's leg time plain data

Fixes full-path receipt finding #9 (data-only-schema-classes) on a359a82.

LegMeasured and LegNotMeasured were TaggedClass variants carrying a
LegTime type id that no Workflow.make checks; LegTime is only a field of
BenchReport. LegTime is now an S.TaggedUnion of the same two cases, so
bench-report.schema.ts declares two fewer classes and no type id, and the
encoded shape is unchanged. main.ts and the render law construct through
LegTime.cases.

Evidence: tsc, oxlint and lint:tsgo clean in test/e2e-core and
test/bench; vitest 364/364 in test/e2e-core (the 3 fewer tests are the
generated codec laws of the two deleted classes; LegTime's own laws
remain) and 39/39 in test/bench. Plan:
docs/plans/2026-10-13-0416-ci-bench-off-pr-plan.md U8.

* fix(bench): refine the leg's elapsed time and the report's ref

Fixes full-path receipt finding #10 (invariants-as-refinements) on
a359a82, both fields named by root.

- LegElapsed.wallMs was S.Finite, so a negative elapsed time decoded as
  the "clock advanced" variant. It is now finite and >= 0, with the
  invariant stated in the check's message.
- BenchReport.ref was S.String while SummarizeBenchCommand.ref is
  S.NonEmptyString. It is now S.NonEmptyString, and main.ts reads
  BENCH_REF with Config.NonEmptyString, so an empty value takes the
  existing environment-incomplete path (main.ts program, UNKNOWN_REF)
  instead of failing at BenchReport.make.

Evidence: tsc, oxlint and lint:tsgo clean in test/e2e-core and
test/bench; vitest 364/364 and 39/39. The refusal properties for these
refinements land with #12. Plan:
docs/plans/2026-10-13-0416-ci-bench-off-pr-plan.md U9.

* fix(bench): state the refusal boundary of every refinement this PR adds

Fixes full-path receipt finding #12 (refusals-beside-generated-laws) on
a359a82.

The generated schema laws draw only accepted values. Each refined schema
the PR adds or changes now states its refusals in its own file, in one
import.meta.vitest block whose subject is a private predicate. Each
block draws the unrefined base, appends boundary values to every draw,
and compares acceptance with a predicate written from the domain:

- test/bench: run-path.schema.ts (all ten path fields), leg-clock.schema.ts
  (elapsed ms finite and >= 0), and the commands of plan-run-path,
  settle-run-path and measure-leg.
- test/e2e-core, the narrowest arrangement under root's scope law (plan
  KD13: no new file, export, dependency, config or helper):
  bench-report.schema.ts (LegTargetMs, ref, abort codes = the codes with a
  next action, rendered exit 0|1), bench-summary.schema.ts (RegressedMetric
  and PhaseRegression fields), bench-run.schema.ts (failure codes without
  timeout, exitCode a safe integer, never null) and summarize-bench (ref).

Evidence: tsc, oxlint and lint:tsgo clean in both packages; vitest
44/44 in test/bench (+5) and 375/375 in test/e2e-core (+11). The pack's
widen-once check is not run: root forbids hand-applied probes. Plan:
docs/plans/2026-10-13-0416-ci-bench-off-pr-plan.md U10.

* ci(bench): give each dispatch its own concurrency group

Fixes full-path receipt finding #15 (heavy-suites-judge-every-main-commit)
on a359a82.

The group was bench-${{ github.sha }} for both triggers, so a
workflow_dispatch on main's head shared, and could replace, the pending
push run for that commit. A push now groups by github.sha and a
dispatch by github.run_id, with cancel-in-progress false, as the rule's
Concurrency bullet requires. Why side A is HEAD^1 rather than the last
green commit is stated in the lane's cost header (#14). Plan:
docs/plans/2026-10-13-0416-ci-bench-off-pr-plan.md U11.

* ci(bench): state the lane's measured runner-minutes in its header

Fixes full-path receipt finding #14 (heavy-lanes-state-their-daily-minutes)
on a359a82.

bench.yml now opens with the cost header the rule asks for:
- the daily figure, about 267 runner-min/day (241-319), priced on the
  run every push to main starts;
- that run's shape, the full corpus against the first parent, and why
  side A is HEAD^1 rather than the last green commit (the corpus is a
  fixed workload; an older base selects no smaller workload and would
  credit several pushes' change to one commit), which also answers #15;
- the per-run cost, 15.3-20.3 job-min, with the seven run ids;
- the 7-day first-parent merge count, 110 in 2026-10-06T01:54Z to
  2026-10-13T01:54Z (15.71/day);
- whether the lane can starve PR CI, from the same week's job
  timestamps: only once about 40 jobs are already running; the replay
  without the pull_request Bench lowers hours at 40+ jobs from 11.98 to
  11.45.

Every figure is from GitHub Actions job timestamps already reported in
the PR's Bench cost section; nothing was rerun. Plan:
docs/plans/2026-10-13-0416-ci-bench-off-pr-plan.md U12.

* docs(plan): record the landed concurrency, settle law and header shape

Supersedes the 0416 plan. R3, U1 and the CONST-W3 list now carry the
push-sha/dispatch-run-id concurrency group from 7d962fc. U7's test
sentence matches the landed settle law (it draws the production command
and asserts the carried causes, not message text, whose oracle would
copy Cause.pretty). U12 records that the HEAD^1 rationale lives in the
cost header.

* docs(plan): say where the HEAD^1 rationale lives in U11

Supersedes the 0434 plan. U11 no longer claims an inline comment; the rationale is in the cost header (U12).

* docs(plan): plan the fixes for every confirmed row of the b30908c receipt

Supersedes the 0435 plan. Root's merge bar (2026-10-13) requires a fixing
commit on the reported head for every confirmed pack violation and accepted
finding. Adds KD16 (push runs scoped from the last green main commit, legs
selected by Turbo's affected set), KD17-KD19 and units U13-U18.

* fix(bench): build the report-unwritable fallback with the report builder

Fixes b30908c full-path receipt finding #5 (maintainability: the fallback
hand-rebuilt every BenchReport field). reportOf now takes report runs, and
writeReport's fallback calls it with the report's own fields.

Plan: docs/plans/2026-10-13-0554-ci-bench-off-pr-plan.md U13.

* fix(bench): walk the phase verdicts once in the report renderer

Fixes b30908c full-path receipt finding #4 (maintainability: two
near-identical verdict walks). One traversal yields each improved or slower
phase change; the improved and phase-only lists filter it. Only lines this
PR added change.

Plan: docs/plans/2026-10-13-0554-ci-bench-off-pr-plan.md U14.

* fix(bench): move the corpus schema into test/bench and state its refusals

Fixes b30908c full-path receipt finding #2 (testing: the corpus empty-repo
refusal lost its only test when bench-decisions.integration.test.ts was
deleted).

bench-corpus.schema.ts is consumed only by the bench, so it moves from
test/e2e-core/src to test/bench/src, unchanged, and e2e-core's mod.ts drops
its export. Beside it, one in-source refusal block states every refinement
the corpus carries: each text field refuses only the empty string, and each
list (repo, mutate, testFiles) refuses only the empty list. The repo list's
refusal is the empty-repo case the deleted test covered; the bench aborts
such a corpus as corpus-unreadable.

test/bench: 54 tests (44 + 2 refusal properties + 8 generated codec laws);
test/e2e-core: 367 (375 less the moved schema's 8 codec laws).

Plan: docs/plans/2026-10-13-0554-ci-bench-off-pr-plan.md U15 (amended in
the next plan revision: a move, not a workflow).

* fix(bench): decide every run-path abort in a lawed workflow

Fixes b30908c full-path receipt finding #1 (testing: the three run-path
abort codes were produced only in run-bench.service.ts, which no test
reaches).

Each way the run path can fail is now a tagged failure carrying its cause:
RunPathUnlisted, RunPathOutsideTree, RunPathOccupied, RunPathMoveInFailed
and RunPathUnrestored (run-path.schema.ts). abortRunPath
(abort-run-path.workflow.ts) decides the abort for each: occupied ->
run-path-occupied, unrestored -> run-path-unrestored, the rest ->
run-path-move-failed, with a reason that names the side and the paths.
run-bench.service.ts keeps only the listing, the two renames and the run,
and routes every failure through the decision.

The law draws RunPathFailure from its production schema and checks the code
and that the reason names the side and paths. In-source refusal properties
cover the new path fields and each abort variant's code. An in-process test
of the renames is refused by the gates (tests/ may import only a package's
public surface, and the bench has none), so the A/A dispatch remains the
evidence for the rename order. test/bench: 68 tests.

Plan: docs/plans/2026-10-13-0554-ci-bench-off-pr-plan.md U16 (amended in
the next plan revision: a lawed decision, not an integration test).

* refactor(bench): call side A the base, not the parent

Side A is no longer always the first parent: on a push it is the lane's
last green main commit. SummarizeBenchCommand.parentSha and
PhaseRegression.parentSha become baseSha, and the regression annotation,
the step-summary table header and the regression section say "base".
Every renamed line is one this PR added (origin/main has no parentSha or
"parent" wording in these files), so the e2e-core boundary holds.

Plan: docs/plans/2026-10-13-0554-ci-bench-off-pr-plan.md KD18.

* fix(bench): bench a push from the lane's last green main commit, affected legs only

Fixes b30908c full-path receipt finding #7 (ci-wall-clock
heavy-suites-judge-every-main-commit: bench.yml compared each push with
HEAD^1 instead of the lane's last green commit).

A plan job now picks side A and the legs before any bench job starts:

- selectLegs (select-legs.workflow.ts) is the decision. On a push, side A is
  the newest main commit whose push-triggered Bench run concluded success and
  which is an ancestor of the pushed commit. A leg runs when Turbo's affected
  set holds the package containing its entry or an engine directory every leg
  builds. The full corpus runs on a dispatch, when no green ancestor exists
  (side A is then the first parent), and when the lane's own inputs changed
  (test/bench, bench.yml, the released-tarballs action, the flake pin). A
  commit with no green ancestor and no first parent is refused as NoBenchBase.
- plan-legs.service.ts gathers the inputs: the lane's green push runs (gh api),
  ancestry and the diff (git), and `turbo query affected --packages`. It fails
  when the checkout has uncommitted changes, because Turbo adds them to the
  affected set (seen locally: a dirty test/bench marked every leg affected).
- plan.ts writes `base` and `legs` to GITHUB_OUTPUT and the choice to the step
  summary; bench.yml's plan job runs it with `actions: read`, the bench job
  uses its base for side A and BENCH_BASE_SHA, and it is skipped when no leg
  is selected.
- engine-dirs.ts holds the engine directories, now shared by prepare-side and
  the plan; command.service.ts holds the command runner, moved out of
  prepare-side so the plan reuses it.

Laws: select-legs covers dispatch, no green ancestor, lane inputs changed, and
affected-only selection (drawing a prefix-without-slash path; the law refutes a
`startsWith` without the `/` boundary). In-source refusal laws cover the new
schemas. Smoke: the real plan program on this checkout (push, dispatch) and,
with a gh shim naming the parent as green, on 3cb1342, ddeb67a and 61d40db.

Plan: docs/plans/2026-10-13-0554-ci-bench-off-pr-plan.md U17, KD16.

* docs(plan): record how U15-U17 landed

Supersedes the 0554 plan. U15 moves the corpus schema into test/bench with
its refusals instead of adding a resolveTarget workflow; U16 decides every
run-path abort in a lawed workflow, because the gates refuse an integration
test of a package with no public surface; U17 adds plan-legs.service.ts
(refusing a dirty checkout, since Turbo counts uncommitted changes) and moves
the command runner into command.service.ts. The edit scope records the one
file that leaves e2e-core, and the test admission table records the refused
and withdrawn tests.

* ci(bench): price the cost header for affected-only push runs

Keeps b30908c receipt row #14 (heavy-lanes-state-their-daily-minutes)
true after #7's selection changed the run shape.

The header now states the plan-job-then-selected-legs shape, each leg's
median job-minutes (range) over the seven dispatch runs, the plan job's
0.40-0.71 min (the same setup steps as timed in those runs, plus the plan
step timed locally), and the week's price: the real plan program run over
all 110 first-parent main commits of 2026-10-06T01:54Z..2026-10-13T01:54Z,
each with its parent as side A, picks no leg for 16, one for 2 and four for
92 (64 for changed lane inputs), so 1,655 job-min (1,333-1,948), about 236
runner-min/day (190-278). The PR CI replay stays as measured for the
full-corpus shape; the selection only removes jobs from it.

Plan: docs/plans/2026-10-13-0629-ci-bench-off-pr-plan.md U18.

* docs(plan): plan the fixes for the c0cf821b review's confirmed findings

Supersedes the 0629 plan. Fixes c0cf821b #4 (KTD2 named a
run-path.service.ts that never landed) and the plan half of #6 (R13 and the
summary still said first-parent side A).

KD20 records root's merge bar for this round: every confirmed finding of the
c0cf821b review of 5a088c6 gets a fixing commit, and the final receipt is a
new full-path review of the head that holds them. U19-U26 plan one fix per
confirmed finding group: the package description, an escaped plan-job
annotation, the plan job's wall time in its step summary, Turbo's wire decoded
into paths, driver modules off the reserved *.service.ts suffix, a lawed
green-ancestor choice, and the run's regression as a tagged union.

* docs(bench): describe the green-ancestor side A in the package description

Fixes c0cf821b #1, #3 and #6 (correctness, project-standards and
maintainability, all confirmed): test/bench/package.json still said each
push is benched against its first parent. It now states the rule the plan
job applies, the same one README.md and the bench.yml header state.

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U20.

* fix(bench): escape the plan job's ::error annotation

Fixes c0cf821b #5 (maintainability, confirmed): failWith printed
`::error title=<title>::<message>` raw, and a refusal's message can hold
multi-line command output (git status, gh's error body), which breaks the
single-line workflow command. The title is now escaped as a property (%,
CR, LF, colon, comma) and the message as data (%, CR, LF), the same
replacement set e2e-core's renderer uses; that helper is private and KD13
forbids a new e2e-core export, so plan.ts keeps its own copy.

Smoke: a plan run against a repository gh cannot find prints one
`::error title=Bench plan failed::...%0A...` line.

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U21.

* fix(bench): write the plan job's wall time to its step summary

Fixes c0cf821b #13 (ci-wall-clock one-stuck-job-backstop, confirmed): the
plan job had the 60-minute backstop but wrote no wall time to its step
summary, so a stuck plan job could not be told from a slow one.

The plan job's first step now records JOB_STARTED_AT, as the bench job's
does, and plan.ts appends the job's wall time in a finalizer, so the line is
written after a selection, a refusal and an incomplete environment alike.

Smoke: the plan program on a dispatch (exit 0), on an unknown repository
(exit 1) and with no GITHUB_SHA (exit 1) each appended
"**Plan job wall time:** N s through the plan step".

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U22.

* fix(bench): decode Turbo's affected-packages JSON into the paths the plan uses

Fixes c0cf821b #10 (schema-laws data-only-schema-classes) and #11
(schema-laws rich-type-over-foreign-encoded), both confirmed:
TurboAffectedPackage and TurboAffected were schema classes no contract
required, and plan-legs walked Turbo's data.affectedPackages.items envelope
itself.

The wire is now a private S.Struct, and TurboAffectedPaths decodes it with
S.decodeTo into ReadonlyArray<string>, so plan-legs receives the paths and
nothing of Turbo's envelope. The generated codec laws now cover
TurboAffectedPaths in place of the two classes.

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U23.

* refactor(bench): move the driver modules off the reserved *.service.ts suffix

Fixes c0cf821b #8 (cell-architecture service-and-layer-boundaries,
confirmed): command.service.ts and plan-legs.service.ts were driver-backed
modules (ChildProcessSpawner, gh, git, turbo) under the suffix the rule
reserves for a Context.Service contract, and test/bench declares no
Context.Service. They become command.ts and plan-legs.ts, names the rule
leaves free for a driver-backed module, and their three importers follow.
prepare-side.service.ts and run-bench.service.ts predate this PR and are
unchanged (the review ruled them pre-existing).

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U24.

* fix(bench): choose side A in a lawed decision, the newest green ancestor

Fixes c0cf821b #2 (testing, confirmed): the push-path side A choice lived
in plan-legs, which took the first SHA in the API's listing that git
called a strict ancestor. No test covered it, and the choice depended on
the API's list order.

chooseBase (new workflow) takes every green Bench push run on main
(GreenRun: SHA, creation time in epoch seconds, and whether it is an
ancestor of the pushed commit), the event and the first parent. On a
push it picks the newest run that is an ancestor and not the pushed
commit, else the first parent ('no-green-ancestor'); on a dispatch, the
first parent ('dispatch'); with no first parent, NoBenchBase. selectLegs
now takes that BaseChoice plus the diff against it and can no longer
fail. The base must be chosen before the diff is computed, so the choice
cannot live inside selectLegs as U25 first said; the plan amendment
follows.

plan-legs lists the runs through gh's --jq (created_at via
fromdateiso8601), decodes them with GreenRunsJson, asks git once per run
whether it is an ancestor, and computes the diff only for a GreenBase.

Laws: chooseBase's dispatch and push rules over drawn GreenRuns, with the
head drawn as a green run's SHA. Two mutants were refuted: taking the
first listed run, and not excluding the pushed commit. selectLegs' laws
are now keyed on the base they receive. Refusal laws cover GreenRun's
safe-integer seconds, FirstParentReason and both commands.

Plan: docs/plans/2026-10-13-0658-ci-bench-off-pr-plan.md U25.

* docs(plan): choose side A in its own decision before selecting legs

Supersedes the 0658 plan. U25 as written put the green-ancestor choice
inside selectLegs, but selectLegs reads the diff against side A, so the
base has to be chosen before that diff exists. U25 now names chooseBase
(new) as the decision; selectLegs takes its BaseChoice. The Test Admission
table splits the select-legs row, adds the choose-base law file with the
bugs it catches, and records the U25 smoke for plan-legs.

* fix(e2e-core): decide a run's regression as a tagged union, not NullOr

Fixes c0cf821b #12 (schema-laws tagged-unions-over-state-by-presence,
confirmed): BenchSummary.regression and the summarized report outcome were
S.NullOr(PhaseRegression), so "no leg regressed" was the absence of a
value, and the renderer probed it with Option.fromNullishOr.

Regression = S.TaggedUnion({ PhaseRegressed, NoLegRegressed }) replaces
PhaseRegression and its 'phase-regressed' code literal. summarizeBench
decides NoLegRegressed explicitly, and renderBenchReport matches the two
tags with Match.valueTags for both the annotation and the markdown block.
The annotation and block title stay 'phase-regressed', now a renderer
constant, so the rendered verdict line is unchanged. The refusal law on
the code literal becomes one on the discriminant: only the two tags
decode.

The law files lose their `=== null` / `!== null` checks, which still
compiled against the union but would have passed vacuously; each is now a
tag check. test/bench/README.md says the JSON report carries the tag.

origin/main has none of these lines, so every edited line is one this PR
added (KD13). report schema 2.0, bumped by this PR, covers the shape change.

Plan: docs/plans/2026-10-13-0711-ci-bench-off-pr-plan.md U26.

* docs(plan): plan the b2fe30db review's fixes and route two findings to root

Supersedes the 0711 plan. Fixes b2fe30db #2 (testing-confirmed: KD13 said
no e2e-core export was added, but the pack fixes added LegTargetMs,
LegTime, RegressedMetric, Regression and PhaseRegressed) and answers #1
(feb5085's subject is scoped e2e-core; rewriting it needs a force push,
and the squash merge carries only the PR title).

KD21 records the round. U27-U29 fix #12 (GreenRunsJson loses its
generated laws), #11 (corpus schemas as data classes) and #6 (two
stderr-tail helpers). #9 (pin Turbo's --packages output) and #3 (test
atRunPath's renames) ask for tests the admission gate refuses: a Turbo
pin spawns git and turbo, and atRunPath is a private executor helper. The
repository's real-Turbo pins live in scripts/ under PR #319, so both go
to root. The Turbo semantics the plan job relies on were measured on
2.10.12 in a scratch repository and are recorded in KD21.

* fix(bench): root the green-run listing codec at the schema vocabulary

Fixes b2fe30db #12 (schema-laws rich-type-over-foreign-encoded,
confirmed): GreenRunsJson was GreenRunListing.pipe(...), a chain rooted
at a module-local name, which effect-schema-discovery skips. The codec
the plan job decodes gh's output with therefore had no generated
round-trip or encode-stability law. It is now
S.Array(GreenRunListing).pipe(S.fromJsonString), and
∀x_GreenRunsJson_=x and ∀x_GreenRunsJsonEnc_=x run.

Plan: docs/plans/2026-10-13-0749-ci-bench-off-pr-plan.md U27.

* fix(bench): declare the bench corpus as plain data

Fixes b2fe30db #11 (schema-laws data-only-schema-classes, confirmed):
BenchRepoEntry, BenchEnterpriseCorpus and BenchCorpus were S.Class
declarations, though none is a Workflow.make command, a decision variant
or a tagged error; every consumer only reads their fields. They are now
S.Struct schemas with exported types. BenchCorpusJson keeps its S.*-rooted
chain, so its generated laws still run, and the refusal block is
unchanged.

Plan: docs/plans/2026-10-13-0749-ci-bench-off-pr-plan.md U28.

* refactor(bench): keep one stderr-tail helper for the bench's commands

Fixes b2fe30db #6 (maintainability, confirmed): command.ts kept a
4000-character tail over a CommandOutcome, and run-bench.service.ts a
second 4096-character tail over a string, both added by this PR.
command.ts now owns the one budget (4096, private) and exports
tailOf(text); runChecked, the interrupt message and runCli's stderr
collector all use it. A setup failure keeps 96 more characters of
output.

Smoke: a plan run whose gh exits 7 after 6000 bytes of stderr fails with
an annotation that carries the last 4096 characters.

Plan: docs/plans/2026-10-13-0749-ci-bench-off-pr-plan.md U29 (the plan
says both names are exported; only tailOf has an importer, so the budget
stays private).

* fix(bench): pin Turbo's affected-packages output in the repo scripts lane

Fixes b2fe30db #9 (P1, boundary-testing pin-dependency-semantics, E7):
the plan job's Turbo --packages output was pinned only by a measurement.

scripts/turbo-affected-packages.test.ts runs the plan job's own Turbo
arguments (turboAffectedArgs, now shared with plan-legs.ts) through
node_modules/.bin/turbo, the binary `pnpm exec turbo` resolves to, in
scratch pnpm workspaces, and decodes the output through TurboAffectedPaths,
the schema plan-legs.ts decodes with. It pins three facts: a changed package
reports its repo-relative directory (packages/a), a root file reports "",
and no change reports [].

TurboAffectedPaths moves to turbo-affected.schema.ts: the scripts lane
type-checks with Deno, which cannot resolve bench-plan.schema.ts's in-source
law block (@systemfsoftware/vitest, import.meta.vitest). Its generated codec
laws still run there.

scripts/deno.json takes #319's permission hunk byte for byte, plus an
effect/ import-map entry, and scripts/assert.ts is #319's file byte for
byte, so merging #319 later is a no-op for both. Deno applies the grant
only with -P, which ci.yml:137 does not pass yet; root's workflow owner
adds it in a separate PR. Until then these three tests fail in the Repo
scripts tests step with NotCapable, as #319's do.

Plan: docs/plans/2026-10-13-0954-ci-bench-off-pr-plan.md U31, KD22.

* ci(bench): give each Bench leg job a 20-minute timeout

Root's E10 ruling: per-SHA concurrency groups stay, because every main
SHA must be judged, and each leg job is capped at 20 minutes instead of
60. A leg's longest measured job is 7.68 job-minutes. The plan job keeps 60.

Plan: docs/plans/2026-10-13-0954-ci-bench-off-pr-plan.md U32, KD2.

* docs(plan): record root's resolution of E3-E11 and the 20-minute leg cap

Supersedes the 0749 plan. KD22 records root's rulings of 2026-10-13: #9
fixed by the scripts-lane Turbo pin (U31), inert until ci.yml passes -P in
a separate root-owned PR; E10's 20-minute leg cap (U32, KD2); E8 and E11
closed by A/A run 38375500452; #4 and E4 accepted; E9 satisfied by #335.

* docs(bench): state the 20-minute leg-job timeout in the package description and plan

Fixes the f13b992..aabe4c2 correctness lens finding #2 (P3, confirmed):
cf39161 lowered each Bench leg job's timeout to 20 minutes, but the
@systemfsoftware/stryker-bench description still said GitHub's 60-minute
job timeout is the only kill, and the 0954 plan's CONST-W3 caps bullet, R13
and the read-bench-run admission row still said 60 minutes. The plan job
keeps 60, so plan.ts's step-summary line is unchanged.

Plan: docs/plans/2026-10-13-0954-ci-bench-off-pr-plan.md U32, KD2.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…ile nested scope

Findings #3 and #4 of review run 20261013-113133-f7afdb73 (PR #287), sharing one
scenario file: the record assertions now read the plan's scope.files (exact
repository-relative list) and a 64-hex sources digest, and the two-project
scenario writes an untracked source file inside the nested project and asserts
the plan schedules its mutants while scope.files names it repository-relative.
ryanleecode pushed a commit that referenced this pull request Oct 13, 2026
…se NotAssignable (#328)

* test(stryker-js-typescript-checker): pin that a type query never calls a spread object NotAssignable when the checker passes it

Failing-first regression for the re-land of #277. It drives the built checker worker over the published Plugin.CheckerRpcs protocol on a fixture copying packages/stryker-js-instrumenter/src/Format.ts:201, one of the 10 wrong-not-assignable sites that turned checker-parity-report red on main. It asserts only the worker outputs: the type-query answer for emptying the spread object and the check verdict for that same mutant.

Red on 0aebc83: the worker serves no typeQuery (client.typeQuery is not a function). Red on #277 tree (revert of 7ddec2f): answers [NotAssignable {} to TransformerOptions] while the verdict is passed.

* docs(plans): plan the type-query re-land with an allow-list over context links

Enriches the requirements for re-landing #277 with the implementation plan: restore #277 by reverting 7ddec2f, decide context enforcement by a pure allow-list over parent shapes (spreads, non-null assertions and short-circuit right operands the left type cannot reach are not enforcing), pin it on real tsgo, and carry a full-corpus parity run.

* feat(stryker-js-typescript-checker): restore type queries on tsgo by reverting #325

Reverts 7ddec2f, which brings back #277 (ee7038a) unchanged and removes the none intent the revert recorded, since that intent is false once the feature is back. The soundness fix follows in separate commits.

* fix(stryker-js-typescript-checker): pass a site's context only through shapes the checker enforces

classify-context-link.workflow.ts replaces the LINKS climb table with a default-deny allow-list over (parent kind, slot). Spreads and non-null assertions end the path as not enforced, and the right operand of ??, || and && passes through only when the left operand's type keeps it in the result. Property laws pin the default deny and the permitted outcomes of every listed pair.

* test(stryker-js-typescript-checker): pin the type-query answer and checker verdict for every link shape on tsgo

One differential row per parent shape pins the (answer, verdict) pair. tsgo gives array and call spread operands no contextual type (checker.go getContextualType has no SpreadElement case), so those rows pin Unknown no-contextual-type; the superseding plan records that change to R7 and U4.

* docs(stryker-js-typescript-checker): state which sites answer context-not-enforced

The restored release intent and the README type-query section name the shapes the checker never checks against the context: object spread, non-null assertion, and a short-circuit right side the left operand keeps out of the result.

* fix(stryker-js-typescript-checker): end the context at an any-typed short-circuit left operand

TypeScript types `any || x`, `any ?? x` and `any && x` as any, so the checker passes every candidate on the right while the type query answered NotAssignable against the declared context. The left-operand fact splits any from unknown: any reaches no operator, so those sites answer Unknown context-not-enforced; unknown keeps climbing because a left unknown only compiles under an unknown or any context, where every candidate is Assignable.

Differential rows on real tsgo pin the three any shapes (NotAssignable + passed before this commit), and pin NotAssignable + compileError for return operands in async, generator and type-predicate functions, a member beside an object spread, and a new-expression argument.

* refactor(stryker-js-typescript-checker): read context-link facts in their own module

The syntax and type-flag readers that build ClassifyContextLinkCommand move from type-query.handle.ts (1088 lines) to context-link-facts.ts; the handle keeps the checker-bound reads and drops to 927 lines. The ShorthandPropertyAssignment allow-list entry and its slot reader go: a shorthand value is a name, never an expression site, so the differential row shorthand-name pins Unknown site-not-expression and the entry was unreachable. Answers are unchanged: the differential, pins and worker suites pass as before.

* docs(plans): plan the review fixes for the type-query re-land

Supersedes the 0241 plan. U7-U12 fix every validator-confirmed finding of the 5c14733 review and the contradicted cell-architecture pack rule (no *Live singleton in a library). KTD8 corrects the reason a left unknown keeps reaching, per the delta review.

* fix(stryker-js-typescript-checker): bound each type-query file on a query grace

Fixes review finding #3 on PR #328 (P2, concurrency): serveFile ran processFile
under Semaphore.withPermit(server.lock, ...) with no time bound, so a tsgo API
call that never settles held the server lock forever and every later query for
that tsconfig waited behind it. Recovery existed only on the ServerCrash branch.

The per-file query is now bounded by QUERY_GRACE (30 seconds) beside CLOSE_GRACE,
using the file's existing Effect.timeoutOption idiom, and the timeout folds into
the same recovery the crash path takes: the server is discarded and the file is
refused. FileRefused.reason stays 'server-crashed' because no consumer acts on
the reason differently — the parity harness and the integration specs only read
it — so the timeout is named in nextAction instead. The file after it is served
by a fresh server, and a query already waiting on the discarded server's permit
re-checks isCurrent and recurses into one.

A scenario in the type-query pins feature answers a file, freezes the real tsgo
server the tsgo client spawned, then queries that file followed by another: the
wedged file is refused naming the timeout on the grace and the next file is
answered on a fresh server. With the bound removed the scenario hangs to the
test timeout.

* fix(stryker-js-typescript-checker): key context slots by the allow-list's parent kinds

Review finding #4: the parent-kind -> slot taxonomy was stated twice — as the
workflow's ALLOW_LIST and again as context-link-facts.ts's ordered guard array —
so adding a kind to one without the other silently answered `other`. ALLOW_LIST
is now a `satisfies`-checked record whose keys export as ContextParentKind, and
the slot readers are a total Record<ContextParentKind, SlotReader> looked up by
parent kind: a listed kind without a reader fails to typecheck with TS2741, and a
reader for an unlisted kind is an excess property. The lookup is a record read
instead of a first-match scan; the readers' guards were disjoint per kind, so
answers are unchanged.

Review finding #5: the laws asserted membership only, so nothing pinned which
facts yield which outcome — swapping the ?? reaching list for ||'s passed. The
law fixture now carries the operator policy table (which left-operand members
reach, and the outcome on each side of that) and the return-target outcome table,
and two laws draw every fact from the production schemas — an arbitrary operator,
an arbitrary left-operand member set, the free booleans and return target — and
assert the table's outcome, so each table entry is load-bearing.

* fix(stryker-js-typescript-checker): pin the destructuring-default shorthand as a differential row

Review delta P3: d80f922 dropped the ShorthandPropertyAssignment allow-list
entry as unreachable, on the measurement that ({ w = 5 } = obj) makes the
shorthand's name an expression site whose climb ends not-enforced, leaving
answers unchanged. Pin that shape instead of restating it: sites.ts appends
`declare const obj` and the row's line, and w becomes `let w` because the new row
assigns it, and the differential's link table gains a row expecting real tsgo's
answer and the checker's verdict for that site.

Measured against the rebuilt package: the query answers Unknown
no-contextual-type for that name — an expression site tsgo gives no contextual
type, so the candidate is not enforced — where the plain object literal's
shorthand (line 21) is refused as site-not-expression, and the checker reports a
compile error for the same change.

* fix(stryker-checker-parity): bound the type-query leg and drive it over the branch worker

Review findings 6 and 7 of the tsgo type-query re-land.

Finding 6: the branch type-query leg had no deadline. The check path bounds
every batch by the leg budget (`remainingOf`/`isPastDeadline`,
`Effect.timeoutOption`, failing as `leg-deadline`); the type-query leg called
`branchTypeQueryLines` with an input that carried no deadline, so a slow or
hung query could run past it. The budget now lives in one place
(`src/leg-deadline.ts`) and the leg reads it the same way: a project whose
budget is already spent emits the lane’s existing `Deferred` line with reason
`deadline-passed` for every file without touching the worker, the
`capabilities` handshake is bounded by the remaining budget
(`interrupted-at-deadline` when it does not answer), and every `typeQuery`
call is cut by the remaining budget, emitting `interrupted-at-deadline`. No
new line kind or reason was added.

Finding 7: `type-query-side.ts` provided `TypeQueryLive` in process, so the
parity gate never exercised the worker `capabilities`/`typeQuery` RPCs this
PR adds. The branch side now asks the branch worker bundle for them over
`Plugin.CheckerRpcs` (`capabilities`, then `typeQuery`), the same way
`run-side` drives `check`. The leg consumes the branch worker port as a
Context service, so `run-side` provides the real client and the in-source
law provides a stand-in; every answer in the lane’s records is now the
worker’s own answer, and a worker that does not declare version 2 is recorded
as `TypeQueryFileRefused` `unsupported-version` without a query being sent.
The in-process path is removed: in CI the branch worker bundle and the driver
are built from the same checkout, so comparing them pinned build equivalence
rather than the answers the lane gates on (the R18 verdict oracle already
catches a wrong answer from anywhere), and it opened a second set of tsgo
servers beside the worker’s. `TypeQueryServers`/`peakServers` went with it:
the driver can no longer observe the query servers, which now live in the
worker. The report summary says the answers come over the branch worker’s
type-query RPC.

Measured on the lane’s local pr-scope leg (shard 1/6, one sampled project,
three runs each, fresh caches): branch worker phase 530 ms before, 623 ms
after, with the untouched main side drifting 537 ms -> 588 ms over the same
pair of runs, so the leg costs roughly 40 ms (about 2%) more per project —
inside the existing leg budget.

* refactor(stryker-js-typescript-checker)!: export the type-query adapter as layer, not TypeQueryLive

Review receipt for #328: the cell-architecture pack rule service-and-layer-boundaries.md (section 2 tier table and section 4.4) bans static *Live singletons in reusable libraries; an adapter module exports a module-level layer. The ./type-query entry now exports layer, consumed as import * as TypeQueryTsgo from "@systemfsoftware/stryker-js-typescript-checker/type-query" and provided as TypeQueryTsgo.layer. The entry is provisional and was never released, so the intent is amended rather than a new break recorded.

* fix(stryker-js-plugin-interface): a refusal carries the version it refused

Review finding #2 (P2, public contract) of the tsgo type-query reland:
`TypeQueryRefused.version` was typed as the served `TypeQueryVersion` and every
refusal in the TypeScript checker was built from a `REFUSAL_VERSION` constant, so
a refusal for an unsupported request version (3, say) never carried the version
it refused — that number lived only in `nextAction` prose, while the request
version, the response version and the served version were each the version in
question.

`TypeQueryRefused.version` is `S.Int`, the version of the refused request,
matching `TypeQueryRequest.version`. Every refusal constructor in
`type-query.handle.ts` — unsupported version, function-body-needs-version-2 and
project-open-failed — takes the request's version, and `REFUSAL_VERSION` is
gone. The fixture checkers under `packages/stryker-js/tests/__fixtures__` and the
two in-test checker doubles echo the request's version as well.

Tests: `type-query-pins.integration.test.ts` asserts the version-1
function-body refusal, the version-3 refusal and the project-open-failed refusal
each carry the refused version; `type-query-worker.integration.test.ts` asserts a
version-3 refusal crosses the worker RPC carrying version 3. Both were red at
d80f922, where every refusal carried version 2.

* fix(stryker-checker-parity): pin the type-query leg and gate the corpus violation

Delta review of the review-fix commits raised five findings, all confined to the checker-parity package.

L1: the in-source LegUnderABudget law drew only answers and hangs, leaving the handshake refusal routing (handshakeRefusalOf and the refused line) unpinned. The law now draws every worker behaviour: refuses records one TypeQueryFileRefused per file carrying the worker reason, does-not-serve records unsupported-version refusals, and its die-guarded typeQuery arm proves no query was sent. A variance-free draw makes the runner impostor gate refuse the law (VacuousProperty), so the total law draws all four behaviours over two files.

L2: the deadline law used a one-file fixture with a synchronous worker, so hoisting the per-file remainingOf out of the loop passed. It now uses a two-file fixture; the hanging worker spends the budget on file 1, so file 1 is interrupted-at-deadline and file 2 deadline-passed.

L3: the leg bound only fires for runs given --deadline. The lane had no README; the package README now describes the type-query leg and states the bound applies only to runs given --deadline (the CI run step passes none).

L4: dropping the TypeQueryServers line variant made prior shard artifacts undecodable, which ci.yml folds for the cost table. A decode-only legacy TypeQueryServers variant is back in ParityLine with no producer; an in-source law pins the old shape decoding and a comparison law pins that it contributes nothing.

L5: zeroNotAssignableViolation fired on any shard with zero NotAssignable answers, misdirecting when the leg produced no answers because the branch worker failed to boot or the leg was deferred/refused. It is now gated on the shard having answers, its nextAction names those cases, and a property law pins the gate.

* fix(stryker-js-typescript-checker): bound a project open and name a query timeout

Fixes the two accepted delta findings on PR #328's review-fix commits (P2
concurrency, P3 public contract).

P2: getOrOpen ran openServer -- which awaits api.updateSnapshot -- inside
SynchronizedRef.modifyEffect on the one shared servers ref with no bound, so a
tsgo project load that never answered held that ref and wedged every query for
every tsconfig. The open is now bounded by QUERY_GRACE (the same 30 second
grace the per-file query already uses) and a timeout takes the existing
project-open-failed path: the refusal names the bound, and the API that was
opened is closed.

P3: a query that ran past its grace was refused as server-crashed, told apart
from a real server crash only by nextAction prose. FileRefusedReason in
@systemfsoftware/stryker-js-plugin-interface gains query-timed-out, and the
timeout path reports it.

Tests: type-query-pins.integration.test.ts adds a scenario that answers one
tsconfig, freezes the real tsgo child a second tsconfig spawns as it opens
(found via /proc, SIGKILLed in an ensuring), and shows the stalled open is
refused project-open-failed while the first tsconfig is still answered; the
wedged-server scenario now asserts reason query-timed-out. Both assertions were
red on f81b0d2 with the fixes reverted: the stalled-open scenario hung to the
120s test timeout, and the wedged-server scenario read server-crashed.

* test(stryker-js-typescript-checker): name what a stalled project open actually guarantees

Delta review of 792d7fe: the stalled-open scenario said another project is not blocked, but a stall holds the servers ref for up to the query grace. The title now says the project is refused within the grace and the other project is answered after it.

* fix(stryker-checker-parity): clear the type-query leg delta findings

Findings from the delta review of the lane fix:

- README: `run` drives both checker sides over one corpus shard (its
  --main-worker and --branch-worker are both required), not one side.
- projectsOf: exclude the decode-only legacy TypeQueryServers row so a stale
  line naming a project absent elsewhere cannot enter projectCount or
  typeQuery.projects; the comment now says the payload is unread, and the law
  draws the legacy project independently of the base rows.
- type-query-side fixture: die unless every fixture file yields drafts, so a
  file that produced none cannot silently reduce the per-file deadline pin to
  one file.
- type-query-side law: draw capabilities-hangs and capabilities-fails and pin
  the interrupted-at-deadline and server-crashed handshake routes.

* fix(stryker-checker-parity): keep a stale TypeQueryServers row out of every project's own lines

Delta review of c50ddd3: legacy rows were excluded from projectsOf but still reached linesOf, so a stale row on a project with a ProjectSkipped row dropped that project from summary.skipped. linesOf now excludes them too, and the legacy law also places a stale row on a skipped project. The README no longer lists a report subcommand the CLI does not register, and says compare reads each shard-<k>.ndjson with its scope-<k>.json.

* fix(stryker-checker-parity): ignore stale TypeQueryServers rows in shard hints and pin the skipped project

Delta review of 32a883f: the report's shard hints (projectShardsOf) still mapped legacy rows; the legacy law compared two decisions without pinning that the skipped project is present at all; and the schema note did not say legacy rows are excluded from each project's own lines.

* docs(changeset): name the HTML reporter the type-query release rebuilds

CI on ab07d61: #335's turbo gate counts @systemfsoftware/stryker-js-html-reporter as moved because its typecheck task depends on stryker-js-plugin-interface#build and stryker-js-cli-contract#build (turbo --dry=json), both changed here. The reporter takes both as runtime dependencies, not inlined, and uses Reporter and Options (unchanged) and SpanTaxonomy, to which this PR only adds the rpcTypeQuery span. Its own shipped output does not change, so the intent is none.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…currency mutators' shared modules

Finding #3 (P3) of full-path review run 20261013-164422-9ff42185 on PR #305: EffectCall.ts and effect-imports.ts headers called the concurrency mutators opt-in, but stockRegistry.optIn is now empty and the three are default.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…tegration test

Finding #3 of review run 20261013-170234-610f3e61 (PR #299).

mergeAndGate and mergeVerdictlessAndGate repeated the same spawn shard 1/2,
spawn shard 2/2, merge plan.json into reports/mutation, merge-failure guard,
and gate sequence, differing only in the two --out directories and a
pre-merge stripVerdictLines step. Extract runShardsMergeAndGate(fixture,
{ outs, beforeMerge? }) and call it from both; scenario names, assertions,
and outcomes are unchanged. mergeWithDowngradedStream stays as-is: it returns
the merge outcome instead of gating, so its sequence is not identical.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…confirmation

Full-path review run 20261013-174358-dc93edb8 (eight lenses, independent
validator) confirmed five findings at caa3443:

- #1 schema-laws/refusals-beside-generated-laws: the decision's local
  Deadline refinement had no refusal law. The decision now uses the
  published Mutant.RunOptionsFields.timeout, the field the deadline is
  written into, so no second refinement exists.
- #2 do-not-sleep-to-prove-a-mutation-timeout.md and
  verdict-cache-content-keyed-reuse.md described the single-expiry rule
  and the removed MutantRunPoolInvalidated. Both now state the
  confirmation rule.
- #3 the checkpoint wrote only this run's confirmed set as timeout
  evidence while reportAll merged the reused evidence too, so a run that
  died after a checkpoint dropped a reused Timeout's reproduction. One
  run-scoped evidence Ref, seeded from reuse, now feeds both writes.
- #4 a worker that never started the mutant inside its boot window was
  reported as a wall-clock expiry, so a never-started confirmation became
  reproduced nontermination. The boot miss now carries its own reason,
  worker-never-started; it still replaces the worker and is confirmed
  like an expiry, and a confirmation that never starts settles as a
  RuntimeError (MutantRunNeverStarted) with no evidence recorded.
- #5 the summed cost of a confirmed Timeout had no assertion. The live
  journey now reads the incremental record's cost (at least three
  budgets) and asserts the second run runs no mutant.
ryanleecode added a commit that referenced this pull request Oct 13, 2026
… against its own thresholds.break (#299)

* test(stryker-js): a merged report below thresholds.break passes the gate

R-GATE step 1. Under thresholds.break 100 with every mutant surviving, the unsharded run exits 1, but both shards exit 0 (the shard driver downgrades the break to info), the merge succeeds, and stryker gate exits 0 without score-below-break. Red on main 56426ca by design; the fix lands on this branch.

* docs(plan): stryker gate judges a merged report against each project's break

* feat(stryker-js): decide a per-project break verdict in a gateScoreBreak workflow

R-GATE U1. Each project's score goes through classifyExit, so score == break passes and null break or Unscored gives no verdict (ruling B). A breach fails as ScoreBelowBreak with the score-below-break code and VerdictFail. Plan revised before any unit landed: the merged report's thresholds are reportFromStream's constant (break null), not the first shard's config; per-project thresholds come from each stream's verdict line.

* feat(stryker-js)!: stryker gate judges each project of a merged report against its own thresholds.break

R-GATE U2-U4. merge records each project's thresholds (from its shard stream's verdict line) and files under a new projects field. gate decodes them through GateReportDocument (decode defaults: break null, projects []), groups files straight-line, and runs gateScoreBreak after the budget check; files outside every project are judged as '.' under the top-level break, which is how an unsharded report is judged.

ScoreBelowBreak joins CliFailure (exit 1, score-below-break) and owns its remediation. The shard-run downgrade line names stryker gate. A2 journey: two projects, break 0 and 50, the gate names only the second; it fails (gate exits 0) when the first project's break is applied to both. Plan superseded by the 2115 plan; changeset major.

* fix(stryker-js): keep the break verdict under baseline updates, note unrecorded thresholds, ratchet the dogfood break

Review rulings on e581c9f (it_bdfe56a0). Update flags write the baselines first and the break check still runs. A merged project with no verdict line in any shard stream records thresholds null; the gate notes it ('no thresholds recorded for this project; re-run its shards with the project's config') instead of passing silently, and both info lines are pinned in the workflow property. New gate journeys: an unsharded report below break under --update-baseline writes the baseline and exits 1 naming '.', and a new survivor fails on the baseline verdict with no break verdict emitted. The shared mutation config's break becomes the ratchet floor 46: main run 38083502908 (56426ca) measured stryker-js 46.45, typescript-checker 54.27, vitest-runner 69.35, e2e-core 72.20 (total 58.27; run 37960922409 totalled 55.01), and every project is judged against the shared break, so 55 would fail stryker-js. Plan superseded by the 2127 plan.

* chore(stryker-config): set each dogfood project's thresholds.break to its own measured floor

Ruling on 7ec7457 (it_bdfe56a0): a single shared break of 46 let the stronger projects fall far below their scores without failing. Each project's stryker.config.ts now overrides thresholds.break with its score on main run 38083502908 (56426ca), rounded down: stryker-js 46, typescript-checker 54, vitest-runner 69, e2e-core 72. The shared config keeps 46 for projects without an override. The old break of 100 was never enforced in sharded mode. Changeset migration and the README gate row name the project's own config. Plan superseded by the 2220 plan.

* chore(changeset): record the dogfood config touch on typescript-checker and vitest-runner as none

Changeset Check on fbaae6d named both packages: their stryker.config.ts per-project break floors move the build hash but ship nothing.

* fix(stryker-js): keep the dogfood break at 100 and leave every floor to its owner

Review P0 on #299 (CONST-E9, CONST-T3, CONST-G3): the change lowered the shared thresholds.break from 100 to 46 and added per-project floors without a CONST-W3 declaration. base.js and the four dogfood stryker.config.ts files now equal main, the floors changeset is deleted, and plan v3 supersedes the 2220 plan: R8 edits no threshold, and any lower floor is the instrument owner's separate, declared change.

* docs(plans): state that the dogfood lane enforces the break only after a release pins this gate

Review of af63359: plan R8 claimed main's Mutation lane fails at merge, but the lane runs the released CLI pinned by the stryker-published input (mutation.yml:37, package.json:71). The break check reaches the lane only after a release ships this change and the pin moves. Also corrects the mutation.yml line citations, names the now-stale CI comment for Stream K, and marks U5/U6 landed.

* test(gate): cover budget-before-break ordering (R5)

Finding #2 of the full-path review of PR #299 (run 20261013-032452-bbbb97a3): a gate run that is over the committed budget baseline and also below its break must fail on the budget and emit no score-below-break verdict, so the first failure decides.

* test(gate): pin a non-zero score against its break

Finding #3 of the full-path review of PR #299 (run 20261013-032452-bbbb97a3): the shared fixture only carried Survived/NoCoverage/Pending mutants, so every printed score was 0.00. Add a report with one Killed and one Survived mutant that scores exactly 50.00: under break 60 it exits 1 naming `scored 50.00 < break 60`, and the same report under break 50 exits 0 because an equal score passes (R2).

* docs(changeset): state the info-line condition

Finding #4 of the full-path review of PR #299 (run 20261013-032452-bbbb97a3): the changeset promised an info line for a project with no valid mutant unconditionally, but the gate emits it only when that project has a break set. Reword the sentence in consumer voice so it states the condition.

* fix(stryker-js): model unrecorded project thresholds as a variant, not null

Full-path review 20261013-043442-ce557f97 finding #2, schema-laws/tagged-unions-over-state-by-presence rules 3-4

* fix(stryker-js): log every gate breach line and the next action on failure

The exit summary bounds the failure text at 1024 characters (bin/main.ts SPAN_ERROR_LIMIT), so a report with many breaching projects truncated the trailing SCORE_BREAK_REMEDIATION line. Expose the breach lines, notes and remediation as ScoreBelowBreak.lines and log them through Effect.logInfo on the failure path of runScoreBreakGate, mirroring the pass path's cleared.lines logging, so every line reaches stderr uncut while the exit summary is unchanged.

Add a built-CLI scenario with 32 projects below break: the composed message exceeds 1024 characters, and stderr still carries every project's score-below-break line and the remediation sentence.

Review run 20261013-051057-2f111cb4, finding #6.

* test(stryker-js): pin the failing gate message's notes and next action

Extend the failure branch of the gate-score-break property law: the composed ScoreBelowBreak.message and the new ScoreBelowBreak.lines each contain the breach line and every expected note, and both end with the remediation sentence (repeated verbatim because SCORE_BREAK_REMEDIATION is module-private).

Review run 20261013-051057-2f111cb4, finding #7.

* test(stryker-js): drive the merged-report no-verdict gate path through the CLI

Add a shard-merge journey: two shards run, both of their mutation-stream verdict lines are stripped before merge (a project is Unrecorded only when no shard of it recorded a verdict), the streams merge, and stryker gate exits 0 naming '. : no thresholds recorded for this project; re-run its shards with the project config'. This drives thresholdsOfProject's Unrecorded branch and the R7 info line end to end, which were covered only by the decision-layer property test.

Review run 20261013-051057-2f111cb4, finding #1.

* docs(plans): correct the unrecorded-thresholds shape to the shipped tagged union

The 0325 plan described unrecorded thresholds as null (U1 thresholds: { break } | null; U2 thresholds: VerdictThresholds | null, 'writes null'; R7 and A3 'thresholds are null'). The shipped contract is a tagged union: ProjectScore.thresholds is ProjectThresholds (Recorded { break } | Unrecorded) and MergedProject.thresholds is MergedProjectThresholds (Recorded VerdictThresholds | Unrecorded), with shard-merge writing Unrecorded. Implementation had started, so the plan moves to a new dated file that supersedes 2026-10-13-0325 and carries the corrected shapes.

Review run 20261013-051057-2f111cb4, findings #4 and #5.

* fix(stryker-js): compare each project's score to its break inside the gate decision

Main's #317 moved score judging out of ClassifyExitCommand (score and breakingThreshold removed) into judge-mutation-score.workflow.ts. The gate decision may not call an imported workflow (dmmf-workflow make-body-purity), so it now keeps the one comparison itself: a breach is a scored project whose percentage is below its recorded break; an equal score passes, as before.

* docs(plans): renumber the gate merged-break plan and correct R1-R3/U1 to the shipped gate

R1's parenthetical, R2 and U1 named `classifyExit` as the source of the gate's per-project verdict. After #317 the unsharded score flows through `mutationVerdictCell`/`judgeMutationScore`, and the gate decision keeps the same `percentage < threshold` comparison inline (`isBelowItsBreak`, `gate-score-break.workflow.ts:111`) because a decision body may not call an imported workflow. R3 claimed the output starts with a counts-first summary line; on failure `runScoreBreakGate` logs each breach line, the notes and the next action on stderr, and the bounded exit summary carries the counts line. Implementation had started, so the plan moves to a new dated file that supersedes 2026-10-13-0602.

Review run 20261013-061137-3bcdcd0e, finding 5.

* test(gate): drop the unread breakThreshold parameter from prepareProject

prepareProject's third parameter `breakThreshold` was read only by the default `report = reportJsonOf(breakThreshold)`, so every call site that passed a report also passed a break the fixture never read. The signature is now `prepareProject(baseline, budgetBaseline = null, report = reportJsonOf('null'))`, and each caller that wants a break passes the report explicitly (`reportJsonOf(BELOW_BREAK)`, `scoredReportJsonOf('60')` and so on). Behavior and assertions are unchanged: `reportJsonOf(BELOW_BREAK)` builds the same document the default used to.

Review run 20261013-061137-3bcdcd0e, finding 9.

* fix(stryker-js): require thresholds in the gate report document

The gate decoded an absent `thresholds` as `{ break: null }`, so a report that
never carried the field read as the verdict "no break". The published report
schema (`MutationTestResult.thresholds`) declares it required and Stryker always
writes it, so declare it required: a report without it is refused through the
gate's undecodable-report path (exit 2), not silently passed. `projects` keeps
its decoding default of `[]`, an unsharded report having no projects.

Rule schema-laws/tagged-unions-over-state-by-presence; full-path ce-code-review
run 20261013-061137-3bcdcd0e finding #1.

* fix(stryker-js): build a BreakBreach only for a real breach

`BreakBreach` was constructed for every scored project and the consumer filtered
with `isBelowItsBreak`, so the type admitted a non-breach. State
`percentage < threshold` on the type as a struct check, build a breach only when
`breachOf` sees one, and delete the consumer comparison. Behavior is
byte-identical for every gate run. The refusal property draws the unrefined base
and compares acceptance against the relation written independently.

Rule schema-laws/cross-field-invariants-as-struct-checks; full-path ce-code-review
run 20261013-061137-3bcdcd0e finding #6.

* refactor(stryker-js): count projects with Natural in ScoreBelowBreak

`projects` is `command.projects.length`, a non-negative count, which the package
idiom types as `S.Natural` (`cap-survivors.workflow.ts:19`), not `S.Int`.

Rule schema-laws/invariants-as-refinements; full-path ce-code-review run
20261013-061137-3bcdcd0e finding #8.

* docs(plans): supersede the gate merged-break plan for revision 3

Rename the started plan to 2026-10-13-0803 (supersedes the 0732 file, which it
replaces) and record revision 3, clearing the three schema-laws findings of
full-path ce-code-review run 20261013-061137-3bcdcd0e: A requires `thresholds`
in GateReportDocument (tagged-unions-over-state-by-presence, #1), B states
`percentage < threshold` on BreakBreach and builds one only for a real breach
(cross-field-invariants-as-struct-checks, #6), C counts projects with
`S.Natural` (invariants-as-refinements, #8). The changeset notes that a report
with no `thresholds` is now refused.

* docs(changeset): fit the gate entry under the changelog ceiling

d7acf7a added the thresholds-refusal sentence and pushed the body to 1010 bytes, over the 900-byte changelog ceiling the changeset validator enforces. Same consumer-visible content, shorter wording.

* fix(stryker-js): gate --update-* help names the break check that still runs

Finding #1 (P2, public contract) of ce-code-review run 20261013-113133-02ae7355.
The `--update-baseline` and `--update-budget-baseline` help promised "exit 0,
instead of gating against it", but `runScoreBreakGate` runs after the baselines
are written, so a below-break update-flag run exits 1. Both descriptions now say
the baseline is written first and the break check still runs, and `--baseline`
names the break verdict beside the survivor verdict. The README rows already
stated this; no test or snapshot holds the strings.

* docs(plans): supersede the gate merged-break plan for revision 4

Finding #2 (P3) of ce-code-review run 20261013-113133-02ae7355: the plan added
by this PR claimed four CLI journeys "at the 2-4 cap" with everything else pushed
down to A3/A4, while the branch ships ten CLI scenarios (seven in
gate.integration.test.ts, two in shard-merge.integration.test.ts, one in
shard-run-verdict.integration.test.ts). Rewrite the Test layers paragraph to
enumerate them and say why each is only observable through the command, not a
property (A3) or a schema law (A4).

Rename the started plan to 2026-10-13-1452 (superseding the 0803 file it
replaces) per the plan-discipline rule: implementation had started, so this is a
new dated contract, not an in-place edit. It records revision 4 and the finding
#1 help rewording as the companion change of the same run.

* fix(stryker-js): log each gate breach line once

A below-break `stryker gate` failure printed every breach line twice on
stderr: `runScoreBreakGate` logs `refused.lines`, and
`ScoreBelowBreak.message` embedded the same lines, which the human-mode
conclusion printed as the bounded exit summary `exit 1 (VerdictFail): …`.
Narrow the message to the counts line only; `lines` keeps the complete
uncut detail (breach lines, notes, remediation) the gate logs.

Pin the shape in gate.integration.test.ts: each breach line and the
remediation line appear exactly once in stderr, and the exit summary
still names the count. Update the property assertions so `message` is the
counts line while the `lines` assertions stand.

Finding #1 of run 20261013-160959-b35ac35e.

* docs(plans): supersede the gate merged-break plan for revision 5

Finding #1 of full-path review run 20261013-160959-b35ac35e changed ScoreBelowBreak's message to the counts line only (94aae44). Plan 1452's U1 still said the error prints the same lines inside its message. The started plan is superseded by 1712, which states the counts-only summary and the single logged copy of each breach line.

* test(stryker-js): assert the survivor remediation line stays off below-break refusals

Finding #2 of review run 20261013-170234-610f3e61 (PR #299).

`ownsItsRemediation` keeps the survivor-remediation line ('accept the new
survivors with `stryker gate --update-baseline`, or kill them before the
next run') off every failure it owns, but no test asserted that absence.
Add a locally-written SURVIVOR_REMEDIATION_LINE literal and assert it appears
0 times in stderr for each built-CLI below-break journey that exits 1 via
ScoreBelowBreak: the unsharded below-break gate, the `--update-baseline`
below-break gate, and the merged many-project below-break gate.

* refactor(stryker-js): share one shard-merge-and-gate driver in the integration test

Finding #3 of review run 20261013-170234-610f3e61 (PR #299).

mergeAndGate and mergeVerdictlessAndGate repeated the same spawn shard 1/2,
spawn shard 2/2, merge plan.json into reports/mutation, merge-failure guard,
and gate sequence, differing only in the two --out directories and a
pre-merge stripVerdictLines step. Extract runShardsMergeAndGate(fixture,
{ outs, beforeMerge? }) and call it from both; scenario names, assertions,
and outcomes are unchanged. mergeWithDowngradedStream stays as-is: it returns
the merge outcome instead of gating, so its sequence is not identical.

---------

Co-authored-by: Ryan Lee <drdgvhbh@gmail.com>
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…ger discovers

Finding #3 of review run 20261013-170549-4dfc842a (PR #287): a plan-sourced
run filtered the mutants it discovered down to the ids the plan scheduled,
so a scheduled id the run no longer produces, such as after an uncommitted
configuration edit between planning and running, was neither run nor
reported. The verified-plan leaf now marks the plan it replays, and the run
refuses with exit 2 before any test runs, naming the plan, the missing ids
and `stryker plan` as the next action. `run --mutant` keeps its own
admission and its own path, so its behaviour is unchanged.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
… config

Finding #3 of correctness lens run 20261013-182108-ccfe8fc8 (PR #287):
`ShardChildFailed` always declared the runtime class, so a child that refused
with a configuration exit was re-wrapped as exit 3 through the orchestrator,
contradicting the changeset's promise that the missing-mutants refusal exits
2. Class the failure by the child's own exit code, keeping the runtime class
for every other child failure and the child's refusal line on stderr.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…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'.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
… 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.
systemfsoftware-maker added a commit that referenced this pull request Oct 18, 2026
…default

Applies the two plan-text findings that review 20261018-181633-f90fef48
confirmed on 1006f3a, as the root ruled:

- #3: the Verification list names the U1-U4 scenarios; it was missing U4,
  the guard.
- #6: kill-matrix.yml has one projects default now that its
  workflow_dispatch trigger is gone, so the Edit scope, R6, KD5 and the
  Workflow hunks line say "the projects default" (KD5: two places, :15).

No workflow or other file changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants