Repository navigation
feat(stryker-js-cli-contract): stream mutant lines carry their facts and next action - #298
Open
systemfsoftware-maker wants to merge 24 commits into
Open
systemfsoftware-maker wants to merge 24 commits into
systemfsoftware-maker wants to merge 24 commits into
Conversation
…and next action Each `mutant` line and `mutant-detail` event is a union over `status`: Survived/NoCoverage/Timeout/RuntimeError carry `original`, `coveredBy` and a `next` action (`RunEvent.NextAction`); Killed carries the full `killedBy`; every status carries its `<code>: <detail>` reason from the plugin-interface vocabulary, with reuse emitting `remembered`. - next-action.schema.ts / next-action.ts: closed NextAction set, pure `nextActionOf` (dual over its facts). - NextActionTests generates constructively (S.declare + toCodecArbitrary) and states its refusal boundary in-source (schema-laws pack). - Engine: mutant-facts.ts builds lines from the pre-instrumentation source; reporting, early results and incremental reuse emit coded reasons. - MCP rerun and `stryker run --mutant` detail read the same facts. - agent-facts.integration.test.ts: AE1/AE2 against the real engine and vm runner; incremental-reuse asserts reused lines decode to `remembered`.
… statuses and reproducer nextActionTestsOf owns the three-name cut beside the schema whose refinement depends on it; the next-action table keys on Mutant.ActionableStatus; reproducerOf is exported and used by the settlement detail and the MCP rerun. The engine builds actionable facts only in their branches, indexes rerun results once, and names the per-run source map sourceTextsOf.
…s-u3 # Conflicts: # packages/stryker-js-cli-contract/etc/stryker-js-cli-contract.api.md # packages/stryker-js-cli-contract/tests/run-event-wire-line.integration.test.ts
…ant lines - Each status variant is built from one explicit tuple, and a type-level gate refuses a union that misses a status. - statusReason is required on every variant; a settled mutant without one fails the run with the mutant-facts-invalid run failure code. - The remembered reason prefix decodes through a schema in plugin-interface. - original is null when the source file cannot be read; in-place runs cite the pre-instrumentation text. - A survivor without measured coverage asks for perTest coverage (fix-config). - Clear-text prints only the reason detail; README stream sample is decoded against the published wire codec.
tsgo's newSchemaClass rule refuses `new` on a Schema class; CI check failed @systemfsoftware/stryker-js#lint:tsgo on mutant-facts.ts:35.
…s-u3 # Conflicts: # .changeset/ignored-reasons-on-the-stream.md
This was referenced Oct 11, 2026
… 9.0 Release v19.0.0 shipped stream 8.0, so the mutant-line change (required per-status facts, mutant-detail fields moved) needs the next major: the contract-version law refused it as stream-version under 8.0. - StreamSchemaVersion is '9.0'; contract/stream.schema.json regenerated with generate:contract. - README stream sample and tests that decode or compare real CLI output read 9.0. - Changeset migration note names the 8.0 -> 9.0 move.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 12, 2026
…anged 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.
This was referenced Oct 13, 2026
Contributor
|
Closed by the root conductor: one open PR per stream from now on, and this one is waiting on other work (#329 and a red check). Branch kept; the stream reopens it (or a fresh PR off main) when it becomes that stream's active PR. |
Contributor
|
Reopened by the root conductor: closing this was wrong. It stays open and is driven to merge. |
ryanleecode
added a commit
that referenced
this pull request
Oct 13, 2026
…d cell (#317) * refactor(stryker-js): acquire the sandbox through cells whose gates are workflows (U3) Sandbox.blueprint.ts (511 lines) now holds only the kind, its withPreprocessor step and its scoped/layer targets (67 lines, one Blueprint.make, no Effect.fn). Its targets run acquireSandboxCell. planSandboxAcquisition decides from inPlace, backupDirectory, symlinkNodeModules and buildCommand: in place with or without a restore, copied with or without node_modules links, and the build command when it is non-empty. acquire-sandbox.cell.ts writes one straight line per variant. The restore stays an Effect.addFinalizer on the caller's scope. Build and link run after it through Cell.gate, never through cell.run. judgeSandboxBuild (sandbox-build.cell.ts) passes a zero exit and fails with the same message main built. classifySandboxDirectory (link-node-modules.cell.ts) skips the temp directory before it matches node_modules, as main's walk did. planNodeModulesLinks links every node_modules the walk found, in order. MakeSandboxInput, SandboxSpec and FilePreprocessor move to Sandbox.schema.ts. Laws for the four workflows draw their production command schemas. * fix(stryker-js): keep the build and node_modules link inside the sandbox acquisition The Sandbox blueprint now runs the acquisition: a stryker.sandbox.acquire span around the prepare cell, the build cell under its own scope, and the link cell. prepare-sandbox.cell.ts (was acquire-sandbox.cell.ts) decides in place or copied under the new stryker.sandbox.prepare span. The link cell runs on every path and logs "Start symlink node_modules" each time, as main does; planNodeModulesLinks decides NodeModulesNotLinked when the sandbox does not link. The build cell's read phase no longer closes a scope; the blueprint scopes each build run. The unused withPreprocessor step and the preprocessors field are gone. The sandbox-acquisition-telemetry suite pins the span tree, step order, symlink log and in-place restore for three paths; it passes on main's code and fails when the build and link steps are swapped or dropped. * docs(repo): refresh plan entry 6 (mutation verdict) against main ddeb67a Re-grounds U6 on main ddeb67a: refreshed line references (P20), the log texts and exit precedence no suite pins (P21), the four classify-exit survivors re-read from mutation-report-444 (P22), overlap with #298, #271, #297 and #277 with a merge-tree probe (P23), and the cell's span choice (P24, KTD12). U6 no longer stacks on U2, which merged as #268. Raises Q1-Q3 for the conductor before ce-work. * docs(repo): supersede the grab-bag plan with U6 as a Sandwich layer on #297 The plan has executed units (#260, #268, #272), so the change lands as a new dated plan declaring `supersedes:` (harness rule plan-discipline); REPO-D2 caps a PR at one added plan file, which a replacement satisfies. - KTD12: the verdict cell is a Sandwich under a new span, stryker.mutationReporting.judgeScore. ADR-0001's cell row refuses Cell.fromEffect stage wrappers; judging inline in reportAll keeps the verdict's I/O out of a cell (R10). The taxonomy entry edits two files #297 edits, so U6 stacks on U3 (OP13b, P25). - R14: main's spans keep name, attributes and ancestors; a new span is a SpanTaxonomy entry with a cli-contract patch changeset, as #297 did. - U6 adds tests/drain-failure-outranks-verdict.integration.test.ts with a revert proof; P14 rewritten, P23 refreshed, P24 and P25 added. * refactor(stryker-js): judge the mutation score in its own workflow and cell U6 of the grab-bag decomposition plan. The score-vs-threshold decision leaves mutation-reporting.service.ts and classify-exit.workflow.ts: - judgeMutationScore (judge-mutation-score.workflow.ts) decides one of five variants, one per verdict log branch on main; each carries a single-valued exitClass, so no arm can return null or undefined. - mutationVerdictCell is a Sandwich under the new span stryker.mutationReporting.judgeScore (KTD12); its write handlers emit main's log lines verbatim and answer the variant's exit class. - classifyExit keeps only precedence over pending classes; every decision variant carries its exitClass, so reportAll's reverse maps go. - reportAll drains, runs the cell, then ranks [verdict, terminalDrain] once; a drain RuntimeError outranks a VerdictFail by precedence. - SpanTaxonomy gains mutationReportingJudgeScore; span-taxonomy.json is regenerated with generate:contract. tests/drain-failure-outranks-verdict.integration.test.ts pins the drain precedence no suite graded (P21): it passes on the pre-change code and fails with the drain class dropped from reportAll's pending list. * test(stryker-js): trace the judgeScore span and decode the judge oracle Review ruling r29 on U6 (grab-bag decomposition plan, entry 6): - G2: tests/drain-failure-outranks-verdict.integration.test.ts gains a scenario that runs the same in-process mutation run under an InMemorySpanExporter and asserts exactly one stryker.mutationReporting.judgeScore span, parented by stryker.mutationReporting.reportAll, with verdict VerdictFail. With the cell replaced by the bare workflow the scenario fails (parents []). - G4: the judgeMutationScore laws decode the oracle through the ScoreJudgement schema and compare with its schema equivalence instead of JSON.stringify. - F1: the plan says the classifyExit pair and single laws were consolidated into the one pending-array law, and records the span scenario and its revert proof. * docs(repo): exclude docs/plans from the U6 removal check Review ruling r30: the plan's U6 Verification grep matched the plan's own mentions of the removed symbols, so it could never return nothing. It now excludes lock files and docs/plans, as earlier plans do, and returns no matches at this head. * test(stryker-js): grade drain-over-verdict through the built CLI Intent r38 (operator rules 2026-10-12): an integration test drives the built CLI or the published API and asserts only what a user observes. - tests/drain-failure-outranks-verdict.integration.test.ts now runs dist/main.mjs on a consumer project with an on-disk reporter plugin (tests/__fixtures__/refusing-reporter/) loaded through `plugins`. One outline: with the refusing plugin the run exits 3, without it 1; each row decodes the stdout verdict record and checks the score is under the break threshold. - The in-process span-parentage scenario is removed: parentage is not in the published span contract. - Plan U6 test lines drop the hand-applied mutant proofs and name the mutant classes as targets for the next main Mutation report. * docs(repo): cite src/bin/main.ts for the CLI OTEL_* reads in plan U6 Debate r39: the span-parentage line named bin/main.ts, but the file is packages/stryker-js/src/bin/main.ts. * fix(stryker-js): decode the break threshold into a tagged union before the verdict decides Review of #317 (run 20261013-032741-afbae46e, validator-confirmed): JudgeMutationScoreCommand carried breakingThreshold as S.NullOr(Percentage) and decide probed its presence, which schema-laws tagged-unions-over-state-by-presence and CONST-D4 forbid. The field now decodes the options' null|Percentage into NoBreakThreshold | BreakAt { threshold } (encode forbidden: the threshold is only ever read from options), and decide dispatches with Match.valueTags. The cell's read is unchanged; laws draw the field's type side. * fix(stryker-js): encode the tagged break threshold back to its options value The command field decoded null|percentage into NoBreakThreshold|BreakAt but refused to encode, so the generated in-source codec laws (∀x_JudgeMutationScoreCommandEnc_=x, ∀x_JudgeMutationScoreCommand_=x) failed on CI run 38344123637. Encode now maps NoBreakThreshold to null and BreakAt to its threshold, the inverse every other decodeTo in this package defines (Plugins.schema.ts). * docs(plans): supersede U6 with the tagged break-threshold codec Review of 9cf18e7 confirmed two P3s: the 1732 plan's U6 approach and test-scenario bullets still described breakingThreshold as S.NullOr(Report.Percentage) with a constructor-defaulted null exitClass. The 0456 plan supersedes section U6 only (as the 2348 plan does for U9), stating BreakThresholdFromOption, the tag dispatch, the decoded-side law draw and the plain S.Null exitClass. --------- Co-authored-by: Ryan Lee <drdgvhbh@gmail.com>
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
…lution The root accepted D1, D2, D3 (guards checked first), D5 and D6, the Done grep, no-rename, no-shim, R6, R7, the inventory and the landing table. Its five review corrections are now in the plan: - no scratch-edit proofs: refusals are committed negative fixtures or a closed deliberate-red PR (bench lint ban, E2E-1's missing-journey case); - a contract codec is never mutation-graded, because Mutation selects *.workflow.ts only; PR C's codec is graded by its schema laws; - #306 and #327 fixed the ManifestDocument and CountedReportJson flakes, so the law moves unchanged with no follow-up unit; - D2's corpus change is timed before the post-#316 speed baseline, or recorded as a reviewed corpus change; - landing order A, then C at any time, B after #316 and #298, D last. PR A's evidence and its one-time cost (the first main Mutation run after merge re-verifies every mutant, because the cache path list changes) are named in the plan.
ryanleecode
pushed a commit
that referenced
this pull request
Oct 13, 2026
* docs(plans): plan the dissolution of test/e2e-core into the e2e harness, the bench and the CLI contract Per-file destinations for all 80 files, a four-layer stack (Mutation de-enrolment, bench, stream codec, harness and deletion), Stream K sequencing and the landing order of the open PRs that touch the package. ce-doc-review skipped: the root reviews this plan (r42). * docs(plans): refresh the test/e2e-core dissolution plan against main and the root's corrections Applies the root's six corrections of 22:25: MUTATED_PROJECTS is data at lockfile-closure-digest.integration.test.ts:29-34 and changes with mutation PROJECTS; stryker-js-cli-contract is not in PROJECTS, so the moved stream codec is ungraded until Stream K enrols it; the lint coupling at test/bench/oxlint.config.ts:1, test/bench/tsconfig.node.json:11, test/e2e/oxlint.config.ts:17 and test/e2e/AGENTS.md:21; kill-matrix.yml 9/15/82 and mutation.yml 36/43/64/118; the exact inventory (29 src modules plus mod.ts, 13 laws, 7 suites, 18 fixtures, 12 package files); and verified or [unverified]-marked pack and config claims. Adds Done and Does-not-count, the 28 outside files that name the package by layer, #287's overlap with PR D, #321's merge, and D6 for the three fixture schemas under tests-own-no-schemas. * docs(plans): record the root's plan review of the test/e2e-core dissolution The root accepted D1, D2, D3 (guards checked first), D5 and D6, the Done grep, no-rename, no-shim, R6, R7, the inventory and the landing table. Its five review corrections are now in the plan: - no scratch-edit proofs: refusals are committed negative fixtures or a closed deliberate-red PR (bench lint ban, E2E-1's missing-journey case); - a contract codec is never mutation-graded, because Mutation selects *.workflow.ts only; PR C's codec is graded by its schema laws; - #306 and #327 fixed the ManifestDocument and CountedReportJson flakes, so the law moves unchanged with no follow-up unit; - D2's corpus change is timed before the post-#316 speed baseline, or recorded as a reviewed corpus change; - landing order A, then C at any time, B after #316 and #298, D last. PR A's evidence and its one-time cost (the first main Mutation run after merge re-verifies every mutant, because the cache path list changes) are named in the plan. * ci(mutation): grade only the shipped products, not test/e2e-core Mutation and the kill matrix mutated four projects: the three product packages and test/e2e-core, a private test-tooling package. Test tooling is not shipped, so grading it spends shard time on code no user runs (plan R7). - mutation.yml drops test/e2e-core from PROJECTS (:36), from the INCREMENTAL_REPORTS cache paths (:43), and from the incremental (:64) and build (:118) cache keys. - kill-matrix.yml drops it from both default project lists (:9, :15) and from its build cache key (:82), which stays byte-identical to mutation.yml's so the kill matrix still restores the mutation build. - lockfile-closure-digest.integration.test.ts drops it from MUTATED_PROJECTS, the test's copy of Mutation's project list, so its live-lockfile scenario checks the projects Mutation actually grades. The package itself, its mutation script and stryker.config.ts stay until the last layer deletes the package. One-time cost: a cache entry's version is a hash of its path list, so the first main Mutation run after this lands cannot restore the previous incremental reports and re-verifies every mutant once. * test(stryker-js): read the mutated projects from mutation.yml in the lockfile test The lockfile-closure scenarios checked "every mutated project" against a hard-coded copy of Mutation's corpus, which nothing compared with the workflow. The test now reads env.PROJECTS from mutation.yml, so the two lists cannot drift apart (review run 20261013-084434-c89dc2ab, testing P2). CONST-W3: this PR edits .github/workflows/mutation.yml and kill-matrix.yml, which AGENTS.md:7 marks read-only and CONST-E9 treats as a judgment surface. Root assigned that edit when it accepted the plan (PR A). The edit only removes test/e2e-core from the corpus; no threshold, baseline or check changes. .stryker/budget-baseline.json stays as measured over four projects, and its owner decides whether to re-measure it. Plan 0900 supersedes 0218 with these review outcomes. * chore(changeset): record a none intent for the lockfile test's corpus change release.jsonc now gates intents on turbo's typecheck graph (#335), whose inputs include tests, so the test edit in this PR needs an intent. Nothing published changes. * docs(plans): name the workflow-derived corpus wherever plan 0900 cited MUTATED_PROJECTS Delta review run 20261013-090355-9173e728 (P3): the Problem Frame, the PR D ordering risk, the landing note and P9 still described the retired constant. They now give it as main's state at 381b3f5 and name the read of mutation.yml env.PROJECTS that replaces it.
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.
…s-u3 # Conflicts: # test/e2e-core/tests/bench-decisions.integration.test.ts
The overwrite guard from #329 reads the run event record openings from the top-level members of the RunEvent union. This branch made the mutant line a nested union over status, so it contributed no opening, and a whole file holding a torn mutant line was refused instead of truncated. The openings now come from every object member, through nested unions, without duplicates.
…he built CLI agent-facts and wall-clock-timeout ran Engine.mutationTestCell in process. Both now run the built stryker binary on a real project and read only its stream records, exit code and JSON report. The remembered reason code on reused verdicts moves from the in-process incremental-reuse suite, which is back to main's text, into the agent-facts feature.
…s-u3 # Conflicts: # packages/stryker-js-cli-contract/README.md # packages/stryker-js-cli-contract/etc/stryker-js-cli-contract.api.md
A settled mutant reaches the stream only when admitMutantReason admits its status reason: a Pending mutant, a missing reason and a reason outside its status's vocabulary are refused, and the run fails with mutant-facts-invalid. The decision is a pure workflow with property laws, so mutation grades it; the stream builder now matches only streamable statuses. MutantFactsInvalid types its mutant id as the branded Mutant.MutantId and keeps a refused facts schema issue as its cause.
A survivor whose measured coverage names no test (a static mutant the suite loads) read "N covering tests ran". Its reason now says no test covers it alone and how many tests ran with it active. The agent-facts feature runs the built CLI over a module-level constant the suite loads and never asserts on.
…nt emits The changeset tells report readers that statusReason in the JSON report carries its code too. The --mutant help, the option description and both READMEs name the status reason and the facts each status admits instead of a covering and a killing test on every mutant-detail.
…s-u3 # Conflicts: # README.md # packages/stryker-js/README.md
The covered-not-killed description now says what to strengthen when the mutant names no covering test, and the migration note names the statuses whose mutant-detail carries coveredBy again.
… test A survivor names no covering test when it is static, and also whenever coverage was not measured per test. The covered-not-killed and strengthen-tests descriptions now say both, and strengthen-tests is selected whenever coverage analysis was on.
…age a run reports The covered-not-killed and strengthen-tests descriptions now state the condition the code tests (the run reported coverage for the mutant, and that coverage may name no single test) instead of listing causes of an empty covering-test list, each of which missed a case.
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.
What
Every
mutantline on the run event stream carries what an agent needs to act on that mutant, typed by status (RunEvent.RunMutantTestedEvent, a union overstatus):statusReasonas<code>: <detail>, the code fromMutant.StatusReason(plugin-interface's shared vocabulary, no parallel set); the JSON report'sstatusReasoncarries the code tookilledByoriginal(the source text the mutant replaces;nullwhen the file could not be read),coveredByandnext, a closedRunEvent.NextAction; NoCoverage:originalandnextstrengthen-tests(its first 3 covering tests, a total, a reproducer); when that coverage names no test (a static mutant, or a mutant undercoverageAnalysis: 'all') the reason saysno test covers it alone; N tests ran with it activeandtests.totalis 0; with no coverage reported it getsfix-configA mutant reaches the stream only when
admitMutantReason(a pure workflow,admit-mutant-reason.workflow.ts) admits its reason: a Pending mutant, a missing reason, or a reason outside its status's vocabulary fails the run withmutant-facts-invalidnaming the mutant, instead of writing an incomplete line. Stream schema9.0; the changeset carries the migration note (major for plugin-interface and stryker-js, minor for cli-contract while it is0.x).Also on this branch, from merging
main: #329's stream-file overwrite guard read run-event record openings from top-level union members only and so missed the nestedmutantunion; the openings now come from every object member (stream-file-head.schema.ts,eca06076). Per the root's M4 rule,agent-factsandwall-clock-timeoutdrive the builtstrykerbinary on a real project (d560f159);framework-streamandrun-event-streamstay wire-codec suites over the exportedRunEvent.makeRunEventStream/RunEventDrain(root's decision this session).Stream version 9.0
Release v19.0.0 shipped stream
8.0with cli-contract 0.5.0. Since #302 the contract-version law reads the released documents from git at the version commit, the newest first-parent commit of the merge base that sets main's declared version, whose parent declares a lower one (tests/contract-version-law.integration.test.ts:166-176,tests/__fixtures__/contract-law.fixture.ts:228-245). On current main that commit is9e81669b8for both cli-contract (0.4.0 → 0.5.0) and plugin-interface (15.0.0 → 16.0.0), and itscontract/stream.schema.jsondeclares8.0. This PR's change is breaking: a line missing its status's fields or reason code is refused, and themutant-detailfields move. The law's stream rule (contract-law.fixture.ts:178-207, unchanged by #302) passes a breaking change only when the committed stream major is higher than the released one. At8.0it refuses withcode: stream-version, "the released document declares stream version 8.0 and the committed document declares 8.0; a breaking stream change requires a higher stream version major"; at9.0the law raises nostream-versionfailure. That was checked by runningevaluateContractLawon9e81669b8's stream document against this head's document as committed (9.0) and with the version set back to 8.0.StreamSchemaVersionis9.0,contract/stream.schema.jsonis regenerated withgenerate:contract, and the README sample plus the tests that decode or compare real CLI output read9.0. The changeset's migration note says: "The stream version moves from 8.0 to 9.0." #273'ssubsumptionkey is unchanged; no other contract change.Verification (local, head
23133502,mainmerged atb08379dd)tsc -bworkspace typecheck,lint+lint:tsgo(stryker-js, cli-contract, plugin-interface),lint:conventions,dprint check,api:check: clean90cc1999; the two later commits change only descriptions, after which cli-contract 212/212, plugin-interface 347/347 and engine in-source 1627/1627 pass on23133502)mainworktree's built binary the three converted built-CLI scenarios fail: agent-facts' facts and remembered-code scenarios, and wall-clock-timeout (3 failed of 3). The overwrite-guard law∀ec_WholeFileHoldingATornRunEventRecord_≡Allowedfails on replayproperty=141736395;seed=344594339withouteca06076and passes with it.admitMutantReason: admitting Pending fails 3 laws; admitting a missing reason fails∀c_CommandWithoutReason_≡Refused; skipping the vocabulary check fails∀ct_CommandWhoseReasonNamesNoCode_≡Refused. Restoring the old survived wording fails the new static-survivor scenario ("1 covering tests ran, none failed"received). Each sabotage was reverted, and the suites pass after.main.CI on
23133502checkchecker-parity(1)-(6),checker-parity-changes,lanechecker-parity-reportchecker-parity-report: the gate is red, and the measurements do not show a #298 slowdown. Its one violation:slower-than-main, branch 183,478.6 ms against main 179,768.3 ms (+2.1%). All six parity legs passed, and both sides checked the same 165 mutants with the same work: 30 check calls, 195 snapshot updates, 30 emit builds. #298 does change the checker worker's dependency closure: the worker depends on plugin-interface and cli-contract, and #298 edits both. That is why the lane ran. The gate sums three line kinds per side, CheckCall, GroupCall and DigestCall (Parity.schema.ts:180), with no tolerance (speed-gate.workflow.ts:97). From the run's own shard NDJSON (artifactschecker-parity-38467175398-1..6):packages/stryker-jsdigest, branch − main per leg (ms)2313350290cc1999ci.ymlonly)digestRPC on a freshly booted worker: building the TypeScript program for the project, then hashing its files (run-side.ts:746,ts-compiler.handle.ts:339-344). Both sides do that over the same corpus, the branch checkout (main.ts:568repoRoot: path.resolve('.')).packages/stryker-jscarries most of the excess (+3,883 ms), and its per-leg sign flips. For the same program, with the same digest on every leg, main's own time ranges from 12,028 to 19,803 ms across legs.3bfedd8to44e1c87#342 and build(deps): bump actions/download-artifact from 7 to 8 in the github-actions group across 1 directory #341 show it at +1,416 and +1,400 ms, and both fail the gate at +4.7–4.8%. Main's side always runs first on every leg, and the two worker bundles are built in different trees (ci.yml:491-501builds main's worker in a merge-base worktree;:537-538pass the two workers). Which of the two makes the branch side slower is not established here.90cc1999ran the gate (it contains feat(stryker-js-typescript-checker): skip importer re-checks for edits inside function bodies #286) and passed at −0.3%. Between90cc1999and23133502, the worker's plugin-interface and cli-contract source differs only by two description strings, plus their two copies incontract/stream.schema.json.Disposition: this is not a #298 checker regression. With identical worker code the gate passed at −0.3% and then failed at +2.1%, and it fails PRs that leave the checker untouched. The defect is in the #286 speed gate on
main: it counts program load as check time, it applies no noise margin, and it carries a branch-side offset. This PR does not chase it. Merge needs that gate fixed onmain, or an explicit waiver from the gate's owner for this required check. A rerun would only draw another sample of the same noise.Review receipt
Full-path
ce-code-review, report-only, on122d0430: lenses correctness, adversarial, testing, project-standards, learnings, plus three pack lenses (cell-architecture; schema-laws with mutation-discipline; boundary-testing, package-topology, ci-wall-clock and turbo-task-graph). The first dispatch of five persona lenses pointed at a wrong persona path; they were re-run with their personas, and the first returns were kept as extra input, so no finding was dropped. Every finding went to a fresh Stage 5b validator; three were re-validated with evidence the first validator had not read (#3 the two gate outputs, #5 the packageturbo.json, #10 the pack file). Then one correctness lens over each later delta, every finding fixed or validator-rejected.Models that served the review, read from omp's session records: every lens and every validator ran on DeepSeek V4.1 Flash (
deepseek/deepseek-v4.1-flash); the merge leaf on Claude Haiku 4.5. No external cross-model peer CLI (codex, claude, grok, cursor-agent, opencode, gemini) is installed on this host.Full-path round on
122d0430(11 findings)S.String, not the refined brandedMutant.MutantId84946922:MutantFactsInvalid.mutantIdisMutant.MutantId.S.Stringwhere every sibling error uses theMutant.MutantIdbrand84946922: same fix as #1.remembered: <detail>strip is written three times in one PR, and the new module built for that vocabulary is not usedMutant.RememberedReason, and both shared homes are refused by the repo's gates (schema-file-exports-schemas-onlyon a*.schema.ts,make-body-purityon an imported helper in a workflow). The first validator had confirmed it; the attempted fix hit both gates and was reverted.contract-documents.integration.test.ts:34-40, package README:50-53).packages/stryker-js-cli-contract/turbo.jsonsetstest.cache: false, so the guard is never replayed from cache, and a README-only PR skipscheckby the lane policy (scripts/change-lane.ts:14). The first validator, which had not read that file, had confirmed it.ef662a17: A survivor whose measured coverage names no test readsno test covers it alone; N tests ran with it active, none failed. The new built-CLI scenario (static module-level constant) fails with the old wording.b7b99a4d: The changeset states the JSON report'sstatusReasoncarries its code, and how to get the bare message.84946922: The admission moved into the pure workflowadmit-mutant-reason.workflow.ts(inside the*.workflow.tsmutate glob) with four property laws; each clause, broken alone, fails its law (below).b7b99a4d:--mutanthelp, the option description and both READMEs name the status reason and the facts each status admits.84946922:MutantFactsInvalidcarries the refusedSchemaIssueascause(first validator: unresolved, the pack file was outside its checkout; second, given the file: confirmed).seed-status-fault.ts:54,:85) and their target files are unchanged by this PR; the abort predates it.Delta rounds
f31bb7f5coveredBya87306b7f31bb7f5covered-not-killeddocumentation did not cover a survivor naming no covering testa87306b790cc1999coverageAnalysis: 'all'a non-static mutant getscoveredBy: []too0fe8fb200fe8fb20strengthen-testsdescription tied selection to the coverage mode, not to reported coverage231335020fe8fb20covered-not-killedlist of causes for an emptycoveredBystill missed a case23133502: both descriptions now state only the condition the code tests23133502Pack rulings
Full-path round, 57 rules across seven packs:
Every matched rule (comply or violate) with the line its ruling rests on:
expected-values-never-come-from-the-subjects-outputpackages/stryker-js-cli-contract/tests/contract-documents.integration.test.ts:40no-mocks-on-internal-gluepackages/stryker-js/tests/wall-clock-timeout.integration.test.ts:11real-system-oraclespackages/stryker-js/tests/wall-clock-timeout.integration.test.ts:109condition-branch-agreementpackages/stryker-js-cli-contract/etc/stryker-js-cli-contract.api.md:16enumerated-entry-modulespackages/stryker-js-cli-contract/src/RunEvent/mod.ts:1import-time-inertnesspackages/stryker-js-cli-contract/src/next-action.ts:1one-access-pathpackages/stryker-js-cli-contract/src/mod.ts:2public-signature-typespackages/stryker-js-cli-contract/src/next-action.ts:7surface-changes-are-versioned.changeset/mutant-lines-carry-their-facts.md:2no-lane-grades-the-repos-own-docspackages/stryker-js-cli-contract/tests/readme-stream-sample.integration.test.ts:63pr-gate-runs-only-what-the-diff-affectspackages/stryker-js-cli-contract/tests/readme-stream-sample.integration.test.ts:25selection-and-caching-go-through-turboturbo.json:61callable-vs-resource-syntaxpackages/stryker-js-cli-contract/src/next-action.ts:44decode-never-castpackages/stryker-js-cli-contract/src/run-event.schema.ts:238four-channel-contracts84946922)packages/stryker-js/src/run/mutant-facts.ts:51pipeable-dual-paritypackages/stryker-js-cli-contract/src/next-action.ts:44pure-decision-workflowspackages/stryker-js/src/Reporter/render-clear-text-report.workflow.ts:376service-and-layer-boundariespackages/stryker-js/src/status-reason.ts:10single-namespace-barrelpackages/stryker-js-cli-contract/src/RunEvent/mod.ts:1arbitrary-filter-floorspackages/stryker-js-cli-contract/src/next-action.schema.ts:18cross-field-invariants-as-struct-checkspackages/stryker-js-cli-contract/src/run-event.schema.ts:101data-only-schema-classespackages/stryker-js-cli-contract/src/run-event.schema.ts:207invariants-as-refinements84946922)packages/stryker-js/src/Run.schema.ts:52law-failure-is-a-codec-defectpackages/stryker-js-cli-contract/src/run-event.schema.ts:235refusals-beside-generated-lawspackages/stryker-js-cli-contract/src/next-action.schema.ts:115tagged-unions-over-state-by-presencepackages/stryker-js-cli-contract/src/run-event.schema.ts:207tests-own-no-schemaspackages/stryker-js-cli-contract/tests/run-event-wire-encode.integration.test.ts:47kill-survivors-with-contract-propertiespackages/stryker-js/src/__tests__/render-clear-text-report.workflow.property.test.ts:194Delta round on
f31bb7f5(the fix commits and the README merge resolution), matched rules: 12 comply, 45 not matched, 0 violate.callable-vs-resource-syntaxpackages/stryker-js/src/admit-mutant-reason.workflow.ts:61decode-never-castpackages/stryker-js/src/admit-mutant-reason.workflow.ts:46four-channel-contractspackages/stryker-js/src/Run.schema.ts:52pure-decision-workflowspackages/stryker-js/src/admit-mutant-reason.workflow.ts:61no-mocks-on-internal-gluepackages/stryker-js/tests/agent-facts.integration.test.ts:171real-system-oraclespackages/stryker-js/tests/agent-facts.integration.test.ts:171data-only-schema-classespackages/stryker-js/src/Run.schema.ts:50invariants-as-refinementspackages/stryker-js/src/Run.schema.ts:52tests-own-no-schemaspackages/stryker-js/src/__tests__/admit-mutant-reason.workflow.property.test.ts:14import-time-inertnesspackages/stryker-js/src/admit-mutant-reason.workflow.ts:6surface-changes-are-versioned.changeset/mutant-lines-carry-their-facts.md:4kill-survivors-with-contract-propertiespackages/stryker-js/src/__tests__/admit-mutant-reason.workflow.property.test.ts:39Delta rounds on
90cc1999and0fe8fb20: 57 not matched each (description-only changes). On23133502: 56 not matched, 1 violate (boundary-testing/expected-values-never-come-from-the-subjects-outputatpackages/stryker-js-cli-contract/contract/stream.schema.json:647), the re-raised drift-test finding, rejected by the validator above.Overlaps with open PRs (coordinated by the root)
The OP13b overlap gate is waived by the root for this PR: U3 opens off
mainand mergesmainup as other streams land; no force-push. Files shared with each open PR (git diff --name-only origin/main...<head>, mainb08379dd):stryker/t10-u2-probe-eligibility:packages/stryker-js-plugin-interface/etc/stryker-js-plugin-interface.api.md,packages/stryker-js-plugin-interface/src/Mutant/mod.ts,packages/stryker-js/src/mutation-reporting.service.ts,packages/stryker-js/tests/run-event-stream.integration.test.tsstryker/runaway-mutant-safety:packages/stryker-js/src/run/mutant-run.ts,packages/stryker-js/src/run/mutant-settlement.ts,packages/stryker-js/tests/wall-clock-timeout.integration.test.tsstryker/mutant-quality-l6-concurrency-default:packages/stryker-js-cli-contract/README.md,packages/stryker-js-cli-contract/etc/stryker-js-cli-contract.api.mdstryker/mutant-quality-l5-equivalence-refusals:README.md,packages/stryker-js-cli-contract/README.md,packages/stryker-js-cli-contract/contract/stream.schema.json,packages/stryker-js-cli-contract/src/stream-version.schema.ts,packages/stryker-js-cli-contract/tests/run-event-wire-line.integration.test.ts,packages/stryker-js-plugin-interface/src/ignore-rule.schema.ts,packages/stryker-js/tests/framework-stream.integration.test.ts,packages/stryker-js/tests/run-event-stream.integration.test.ts,packages/stryker-js/tests/shard-merge.integration.test.ts,test/e2e/tests/failing-run.e2e.test.tsstryker/mutant-quality-l4-uncovered-block:README.md,packages/stryker-js-cli-contract/README.md,packages/stryker-js-cli-contract/contract/stream.schema.json,packages/stryker-js-cli-contract/src/stream-version.schema.ts,packages/stryker-js-cli-contract/tests/run-event-wire-line.integration.test.ts,packages/stryker-js-plugin-interface/etc/stryker-js-plugin-interface.api.md,packages/stryker-js-plugin-interface/src/Mutant/mod.ts,packages/stryker-js-plugin-interface/src/ignore-rule.schema.ts,packages/stryker-js/src/__tests__/incremental-diff.workflow.property.test.ts,packages/stryker-js/src/incremental-diff.workflow.ts,packages/stryker-js/src/run/mutant-settlement.ts,packages/stryker-js/tests/framework-stream.integration.test.ts,packages/stryker-js/tests/run-event-stream.integration.test.ts,packages/stryker-js/tests/shard-merge.integration.test.ts,test/e2e/tests/failing-run.e2e.test.tsstryker/verdict-store-ci:README.md,packages/stryker-js-cli-contract/contract/stream.schema.json,packages/stryker-js-cli-contract/src/run-event.schema.ts,packages/stryker-js-cli-contract/src/stream-version.schema.ts,packages/stryker-js-cli-contract/tests/run-event-wire-line.integration.test.ts,packages/stryker-js-plugin-interface/etc/stryker-js-plugin-interface.api.md,packages/stryker-js/README.md,packages/stryker-js/src/__tests__/incremental-diff.workflow.property.test.ts,packages/stryker-js/src/incremental-diff.workflow.ts,packages/stryker-js/src/mutation-reporting.service.ts,packages/stryker-js/src/run/instrument.ts,packages/stryker-js/src/run/mutant-run.ts,packages/stryker-js/src/run/mutant-settlement.ts,packages/stryker-js/tests/framework-stream.integration.test.ts,packages/stryker-js/tests/run-event-stream.integration.test.ts,packages/stryker-js/tests/shard-merge.integration.test.ts,test/e2e/tests/failing-run.e2e.test.tsstryker/mutant-quality-l2-diff-plan:README.md,packages/stryker-js-cli-contract/README.md,packages/stryker-js-cli-contract/contract/stream.schema.json,packages/stryker-js-cli-contract/etc/stryker-js-cli-contract.api.md,packages/stryker-js-cli-contract/src/stream-version.schema.ts,packages/stryker-js-cli-contract/tests/run-event-wire-line.integration.test.ts,packages/stryker-js/src/bin/cli-command.ts,packages/stryker-js/tests/framework-stream.integration.test.ts,packages/stryker-js/tests/run-event-stream.integration.test.ts,packages/stryker-js/tests/shard-merge.integration.test.ts,test/e2e/tests/failing-run.e2e.test.tsstryker/verdict-store:README.md,packages/stryker-js-cli-contract/contract/stream.schema.json,packages/stryker-js-cli-contract/src/run-event.schema.ts,packages/stryker-js-cli-contract/src/stream-version.schema.ts,packages/stryker-js-cli-contract/tests/run-event-wire-line.integration.test.ts,packages/stryker-js-plugin-interface/etc/stryker-js-plugin-interface.api.md,packages/stryker-js/README.md,packages/stryker-js/src/__tests__/incremental-diff.workflow.property.test.ts,packages/stryker-js/src/incremental-diff.workflow.ts,packages/stryker-js/src/mutation-reporting.service.ts,packages/stryker-js/src/run/instrument.ts,packages/stryker-js/src/run/mutant-run.ts,packages/stryker-js/src/run/mutant-settlement.ts,packages/stryker-js/tests/framework-stream.integration.test.ts,packages/stryker-js/tests/run-event-stream.integration.test.ts,packages/stryker-js/tests/shard-merge.integration.test.ts,test/e2e/tests/failing-run.e2e.test.ts#271, #289, #287, #296 and #304 also declare stream
9.0against the same released8.0; their breaking changes ride the one unreleased major, so whichever lands second keeps9.0and merges the stream document.#6 —
∀e_NonTerminal_≡FramedWhateverTheModerestored; counterexample and dispositionCONFORMANCE_REPLAY="property=2854989700;seed=1890475051;runs=100"; after 11 shrinks it is[{"mode":"human","signal":"tool","headerWritten":false,"terminalSeen":false,"completed":0,"total":0}, mutantTested].The seed file
src/__tests__/__property_seeds__/frame-run-event.workflow.property.test.ts.jsonlwas untracked: thered run itself wrote it. The only tracked seed file in the package is
incremental-diff.workflow.property.test.ts.jsonl.frame-run-event.workflow.ts:198-201,event: command.event).The old law asserted reference identity (
result.success.event === event). On base,RunMutantTestedEventwas anS.TaggedClass(baserun-event.schema.ts:130); its instances pass throughEventFramed.makeas the same object.U3 turned it into a union of
S.Structvariants over status, and schema construction rebuilds plain structs.Probe on this branch:
EventFramed.make(...).event === eventholds for 50/50Heartbeat(class) samples and failsfor
RunMutantTestedEvent(struct union). The framing decision has no defect; the identity check depended on theclass representation.
S.toEquivalence(S.toType(RunEvent.RunEvent)). Nothing about the claim is weakened: any field that differs still fails.(
…;attempt=4;size=0;path=0×11;failure=ReturnedFalse) both reproduce the input above.PropertyRefuted … 0 shrinks … Shrunk input: undefined. That isReplayMismatch { reason: "PropertyPassed" }(effect/src/internal/arbitrary/runner.ts:597): the recorded attemptnow passes. The harness prints a mismatch, which carries no input, as a refutation with an undefined input.
README sample
The README stream sample on main fails the new decode test: its Killed line is refused at
statusReason(Expected string, gotnull). The updated sample passes.Mutants the new code exposes
admit-mutant-reason.workflow.tsis inside the dogfoodmutateglob (src/**/*.workflow.ts), so the nightly onmainafter merge grades it against the four laws.run/mutant-facts.ts,status-reason.ts,run/mutant-run.tsand thestryker-js-cli-contractpackage stay outside main'smutatescope.Mutant-line size on main's corpus
Source: run 38083502908 (
mutation-report-438, main56426ca94, the commit this branch merged). The mergedmutation.jsoncarries nocoveredByand nokilledByon any mutant, though per-test coverage was measured (491NoCoverage). So each line was rebuilt from the four per-project
stryker-incremental.jsonfiles in the same artifact,using real covering and killing test names, and encoded through
RunEvent.RunEventWireLine. All 11,041 lines wereaccepted by the status constructors.
The largest line (4,713 B) fits the 10,000-byte R11 budget by itself. The per-mutant stream as a whole is not bounded
here: it is still on default machine stdout until the summary-first unit (R11/R12) moves it, and survivor lines roughly
double at p50. Separate finding: the merged CI report drops
coveredByandkilledBy(not fixed here).coveredBydistribution on the same corpusA median alone misleads here: NoCoverage is 0 by definition, and many killed, survived and compile-error mutants list no test. Count of covering test names per mutant, from the four
stryker-incremental.jsonfiles (the mergedmutation.jsonhascoveredByon 0 of 11,041 mutants):coveredByabsentSurvived lines show at most 3 covering tests in
next.tests.shownplusnext.tests.total, so the survivornextstays small even at the max (28).