Repository navigation
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
Closed
dependabot[bot] wants to merge 1 commit into
dependabot[bot] wants to merge 1 commit into
Conversation
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/effect-4.0.0-rc.113
branch
4 times, most recently
from
September 14, 2026 12:37
324278a to
3daea1c
Compare
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/effect-4.0.0-rc.113
branch
4 times, most recently
from
September 15, 2026 22:44
eac2007 to
b73733b
Compare
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/effect-4.0.0-rc.113
branch
8 times, most recently
from
September 18, 2026 07:32
7d80770 to
62d1d6e
Compare
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/effect-4.0.0-rc.113
branch
6 times, most recently
from
September 19, 2026 16:35
58f03e4 to
644f553
Compare
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.
This was referenced Oct 13, 2026
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).
This was referenced Oct 13, 2026
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
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps effect from 4.0.0-rc.112 to 4.0.0-rc.115.
Release notes
Sourced from effect's releases.
... (truncated)
Changelog
Sourced from effect's changelog.
... (truncated)
Commits
4a05d49Version Packages (rc) (#8192)657254bOptimize Schema initialization (#8196)f9ef0e9fix(http): omit body in toWeb for 204, 205, and 304 (#8190)4f73f9efix(persistence): parameterize persistence lookup keys (#8187)cdb3ac9Version Packages (rc) (#8159)9941e6dfeat(cli): make message optional for Prompt.Select and Prompt.MultiSelect (#8...6d55555fix(FileSystem): keep stat when optional inode exceeds MAX_SAFE_INTEGER (#8164)3ff4952Support TTL callbacks in Effect.cachedWithTTL (#8177)f48fb1cAdd Match.valueTags API contract type tests (#8175)482b7d7feat(SchemaRepresentation): improve JSON Schema imports (#8169)