Repository navigation
feat(repo): ignorer protocol on ancestors-as-data and the oxc vocabulary - #10
Merged
Merged
Conversation
turbo.json: `test` no longer declares `coverage/**` as an output — CI produces
coverage yet nothing consumes the artifact (no upload step, no threshold) and
local runs produce none, so the glob only emitted "no output files found"
warnings on three packages.
`gate:tasks` and `gate:dist` now accept `TURBO_BASE`; when set they pass
`--filter=...[<base>]`. The CI workflow supplies the pull-request base and
checks out full history so the base ref resolves; a push to main leaves it
unset and the gate runs the whole workspace as before.
Measured on a dependency-only diff (cli/package.json + lockfile):
`turbo lint typecheck test` drops from 56 tasks across 14 packages to 10
across 7, and `turbo build` from 14 to 7.
`gate:tasks` no longer runs `typecheck:node`: root-level TypeScript
(commitlint.config.ts, .lintstagedrc.js) is now checked only by the root
`pnpm typecheck` script, not in CI. Adding it back to `gate:tasks` reverses
this if the owner wants root coverage in CI.
CONST-W3 declaration: this change edits `.github/workflows/ci.yml`, which root
AGENTS.md classes as an evaluator surface ("never edit the instrument that
grades work"). The edit was directed by the instrument's owner in this
session: it adds an environment variable and `fetch-depth: 0`, and changes
which packages the gate grades, not how it grades them. Reversal is the
deletion of the two added lines.
… AST module The ESTree type vocabulary leaves the instrumenter entirely: `@types/estree` is gone and no file imports from 'estree' any more — the AST is typed on `@oxc-project/types`, the parser's own view of the same nodes. `src/estree.ts` becomes `src/Ast.ts`, because it is a domain module (node builders, traversal, comment attachment), not a module named after an external spec. Constructed nodes keep their runtime shape: kinds this package builds extend a local `BuiltFrom<T>` base so an absent span stays optional, while parsed nodes keep oxc's required members; the `estree-walker` runtime load is untouched Refs: #10
The `workflow-make-boundary` ignorer is a leaky abstraction here: it encodes the systemfsoftware DMMF stack's `Workflow.make` convention, which this repo does not own. The package, its changeset, its entry in the engine's base preset, and its entry in the plugin-manifest specifier list are removed; the plan doc's ignorer count follows. Also carries the `@microsoft/api-extractor` catalog entry needed for the API-report rollout Refs: #10
Every publishable package now carries an API report and a validation run, in the house pattern: a root `api-extractor.json` per package, an `api-extractor.<entry>.json` plus `tsconfig.api-<entry>.json` for each extra subpath, `api:check`/`api:update` scripts, and a turbo `api:check` task wired into `build`, `typecheck`, `test`, `lint` and `attw`. Sixteen reports land under `<package>/etc/` — engine's five entries, vitest-runner's two, and one per single-entry package. `temp/` is gitignored and `**/etc/*.api.md` joins the dprint excludes, because API Extractor owns that file's layout (declared schema-file exemption, not a formatting bypass). The unused `vite-tsconfig-paths` devDependency and its catalog entry are gone with it, which also clears the only unmet peer in the workspace (`tsconfck` wanted TypeScript ^5 while the catalog pins ^7)
…checkout API Extractor writes its reports with CRLF, and `* text=auto eol=lf` normalised them to LF on the way into git — so a fresh checkout handed api-extractor LF text and the comparison failed while the working tree (still CRLF) looked green. The reports are now pinned with `**/etc/*.api.md -text`, which keeps the bytes the tool produces, and the report files re-enter git with their real line endings. `api:check` passes from a clean index
`test` and `lint` did not declare the toolchain presets they import: `packages/toolchain/vitest-config/lib/**` is what every package's `vitest.config.ts` loads, and `packages/toolchain/oxlint-ignorer-config/lib/**` is what every ignorer's `oxlint.config.ts` loads. Editing either left the consuming tasks at the same hash and status HIT, so a preset change shipped a stale green. Both globs are now inputs — the mutation probe now moves exactly the one task that consumes the preset, and nothing else. `test.inputs` matches the house glob verbatim; `lint.inputs` mirrors the house's two oxlint-config glob entries against this repo's vendored path
The package named interface carried 373 source lines, ten of which were interfaces: a hand-rolled validation toolkit plus AST schemas and guards, kept alive only by the entry contract's deleted schema member. It now declares the contract and nothing else — the descriptor, `NodePath`, `UnknownNode`, and the canonical AST vocabulary aliased from the parser that produces those nodes. The built entry exports no value, and the toolkit's dependency leaves the workspace with it. The ancestor walk each ignorer needs moved into that ignorer, so no shared runtime hides behind the interface
Direction: the owner asked for the family's own setup — strict, not a copy of the systemfsoftware preset. The old file imported that family's rule packs, which is how the Effect/DMMF corpus ended up grading ignorer packages while their manifests declared no such dependency. It now imports nothing: the rules are assembled from the linter's built-in plugins and categories, and the preset holds a decision to one path, refuses casts and `any` on data read from outside, and rejects every import that would put the StrykerJS runtime behind an ignorer. The package drops `private`, gains an API surface with a committed report, and is documented for authors working outside this repo
dprint reformatted the rolled-up declaration on every commit while API Extractor rewrites it with CRLF, so the file could not settle. It joins the generated-report exclusion in both the formatter config and the checkout policy, which keeps the committed bytes identical to what the tool writes
- NodePath is plain data ({ node, ancestors }) from the types-only ignorer
interface; the language package re-exports it and the engine's
plain-ignorer loader type-checks entries against it
- both ignorer packages drop their private AST mirrors and guard over
the ancestor array with oxc-typed, closed-shape predicates
- the instrumenter walks with oxc-walker (estree-walker leaves the
catalog), traversal stops via a Schema.TaggedError, and the AST
vocabulary is derived from @oxc-project/types through type-fest with
Simplify at the alias level; walker-seam aliases admit raw nodes
This was referenced Oct 10, 2026
ryanleecode
pushed a commit
that referenced
this pull request
Oct 10, 2026
* docs(plans): scope unit I mutant subsumption and proved equivalents Requirements-only brainstorm for static subsumption (Layer 1) and z3-solver equivalence proving (Layer 2), with the kill-matrix no-signal-loss predicate and open questions for the supervisor. * docs(plans): plan unit I mutant subsumption implementation Enrich the requirements-only plan with the planning contract: eleven stacked units, the kill-matrix drop audit (R16/R17 predicate made total, Unattested rule verdict), the z3-solver driver outside the CLI bundle, and S3 moved last behind its own drop audit. Apply the report-only ce-doc-review findings: R24 cites SMT-LIB '=' semantics, R29 restated as a sampling net, R2/R4/R13 aligned with KTD2/KTD4, merged report carries statusReason. * docs(plans): replace SMT layer with type-guided generation in unit I plan Apply the root's rulings: drop z3-solver and async-mutex, make Layer 2 type-guided generation from TS 7 checker facts read on the unmutated program, add generic check-time re-admission to PR-A, and regroup into four stacked PRs (PR-A, PR-B, PR-W, PR-C). Fold in the round-2 report-only document review. * fix(stryker-js-instrumenter)!: stop dropping condition-position relational mutants The sufficient-set table Ignored the complement operator and one boolean literal at every relational site in an if, loop, or ternary test. Its proof assumes numeric operands. In if (a < b) with a = null and b = undefined, the dropped true mutant enters the branch and none of the kept a <= b, a != b, false does; operands whose valueOf has side effects defeat the operator drops too. Delete the table's consumers (relationalSufficientReplacement, the relationalSufficient fact, and the policy's redundant-relational rule). Generation is unchanged, so mutant ids are unchanged. Plan U12 (R4, R7; AE4). * feat(stryker-js-instrumenter): decide relational complement subsumption per site subsumeMutants is a pure workflow over serializable site and candidate facts: at a relational site under the default policy, a statically kept complement (>= for <, > for <=, <= for >, < for >=) is Subsumed by every statically kept dominator (<=, <, >=, >). The four ordering operators read both operands once through ToPrimitive(number) and differ only in how they map the one IsLessThan result, so whenever the dominator differs from the original the complement equals the dominator. Nothing calls the workflow yet. Plan U2 (R1, R3, R5, R7, R10, R11). * feat(stryker-js-instrumenter): drop subsumed relational complements with a redundancy reference planMutants decides each candidate's static reason first (directive, excluded mutator, ignorer, mutant-set policy, arid), then asks subsumeMutants which statically kept complements a kept ordering operator dominates. A subsumed mutant stays placed, is Ignored as redundant-relational, and carries a typed redundancy reference whose first dominator id is the kept mutant. The plugin Mutant gains the optional redundancy field (refused unless Ignored) and redundancyStatusReason, which renders the reason code, dominator id and next action. Plan U4, U5 (plugin Mutant part); R3, R5, R7, R8, R9, R11; AE1, AE3. * feat(stryker-js-plugin-interface): name why a subsumed mutant was re-admitted A plugin Mutant gains an optional readmission field: the subsumption rule and, per named dominator, a cause code (ignored at check, compile error, ignored at plan, remembered without running, unsettled) with detail. The decoder refuses it together with a redundancy reference or an Ignored status. Plan U5, U13 (R31, R9); agentic-quality rule. * feat(stryker-js): re-admit subsumed mutants at check time and keep drop references on records A subsumed mutant is held out of the checker and runner until its dominators settle. If none of them runs (ignored at check, compile error, ignored at plan, remembered without running) it is re-admitted and runs, naming each dominator and why; otherwise it settles Ignored naming a dominator that ran. The shard plan keeps it with its first dominator. The run stream moves to 7.0 with redundancy/readmission on every mutant line, the incremental record keeps both, and a record carrying either is re-decided each run under the new decidedPerRun refusal, ranked below every other cause. Plan U5, U13; R5, R9, R31, AE2. * test(e2e): expect stream schema 8.0 on the failing-run terminal record PR-A moves the run stream to 8.0 (merge of #262); the failing-run journey asserts the terminal error record's schemaVersion. * fix(stryker-js-instrumenter): type a candidate's arid reason as ignore-rule vocabulary The candidate schema took any string as aridReason, so main's EveryIgnoreReasonNamesItsRule law (#262) failed whenever a draw carried non-vocabulary arid text. The field is now Mutant.IgnoreStatusReasonText. * refactor(stryker-js): reuse optionalField and derive duplicated lists ce-simplify-code pass: plan-request and mutation-test-plan use the shared optionalField; RUNNING_STATUSES derives from Settled; a readmission takes its rule from the held entry; Mutator.service imports RelationalOperator from its schema file and the re-export goes. * feat(stryker-js-plugin-interface)!: carry one subsumption union on every mutant record A mutant's `redundancy` and `readmission` fields become one optional `subsumption` tagged union: `Subsumed` (rule, dominators) only on an Ignored mutant, `Readmitted` (rule, causes) only on a mutant that ran. The plugin Mutant schema, the 8.0 wire line, the incremental report and the reuse records all carry the single key, and a reference that disagrees with its status is refused (review #4, #5). Tests: a refusal law on the plugin Mutant decode, a wire-line scenario for both tags on Ignored and Killed lines, literal status-reason text and the `>=` complement row in the instrumenter (#2, #3), and readmit scenarios for every reachable cause code: ignored at check, compile error, remembered without running, unsettled (#8). A subsumed mutant closed early by ignoreStatic keeps the static reason and no reference, because its dominator is static too (#13). * fix(stryker-js): keep the subsumption reference through stryker merge The merged JSON report now carries each mutant's `subsumption` reference from the shard streams, so a sharded run reports the same records as an unsharded one. The shard-merge journey adds a relational site and co-locates the subsumed pair in one shard (review #7). * docs: describe complement subsumption and agree on stream schema 8.0 The READMEs and the mutation-testing skill name the relational complement drop and its readmission (review #1). The changesets agree that the stream moves to 8.0, describe the single subsumption union, and state the score change (review #10). * docs: show the 8.0 subsumption key in the machine-stream sample The README's machine-stream sample still carried the removed redundancy and readmission keys, so the documented 8.0 mutant lines did not decode. The cli-contract README now names stream version 8.0. * fix(stryker-js-cli-contract): state the Readmitted rule the codec enforces A Readmitted reference is accepted on any mutant that is not Ignored: a re-admitted plan mutant has no status yet, and a checkpoint writes it as Pending before it settles. The refusal messages and changesets said it must have run, which the codec never checked. * refactor(stryker-js): one present-field helper and a Subsumed-only planning test The merge command builds every optional report key through one presentField helper. The planner strips run fields only from Subsumed mutants, matching the shard placement and early-result paths. * test(e2e): annotate the subsumed relational complements as Ignored Under the default mutant-set policy the `<=` complement of `n > 0` and `count > 0` is Ignored, subsumed by the kept `>=` mutant. The calc, vm-vitest and svelte fixtures still annotated every EqualityOperator mutant Killed, so their oracle scenarios failed on the PR's e2e legs.
This was referenced Oct 11, 2026
Merged
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
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.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
…iver chain reads an accessor Findings #4 and #10 (review run 20261013-032451-a9153d08, validator-confirmed): isReferenceChain admitted any non-optional property access with no symbol lookup, so a getter receiver typed as a collection was culled as constant-collection-size even though the ignored mutant evaluates the accessor its kept sibling skips. Resolve every link of the receiver chain with the checker (getSymbolAtLocation) and keep the mutant when any link is an accessor (SymbolFlags.Accessor) or resolves to nothing. An integration scenario reads an array's length through a getter and asserts the mutant stays passed rather than ignored; it fails without the guard.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
…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.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
… hand-written table Findings #10 (and its advisory #11) of ce-code-review run 20261013-055603-242b8fc9: the property's `expected` copied production's constant/general/sibling logic, so it could not falsify a bug the two copies shared. State the (lengthSide x operator x againstZero) cells as explicit literal rows with their constant answer and read the oracle from that table, keeping the receiver/sibling rules only as the contract's stated preconditions. Generation still draws the production schema and the law still refutes a constant impostor, so the harness's vacuity gate stays satisfied.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
…d-relation workflow Findings #6 and #10 of full-path review run 20261013-035820-4d26adec. The decision-to-mutant application (the guard index and withGuard) leaves the 1543-line Transformer for apply-guards.ts beside guard-relation.workflow.ts, which produces the decisions (CONST-N1). The Transformer now makes a single withGuards call; guardOf is inlined at its one call site (#10).
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 13, 2026
…he vacuity gate refuses CI run 38363051350 (check, job 115182283579) failed the suite: the vacuity gate found that a constant [] impostor held for 100 runs of ∀command_Decisions_≡IndependentDecisionTable, because Arbitrary.schema(ClassifyCollectionSizeCommand) rarely draws a kept, qualifying comparison beside its kept literal. The law also read its answers from a hand-written known-answer table. The architect-property-tests rule deletes a law a gate refuses rather than reshaping it, and keeps a law only when no other suite pins the behavior. check-mutants.integration.test.ts drives the built checker over every constant row (xs.length >= 0, xs.length < 0, 0 <= s.length, 0 > s.length) and every kept guard (non-zero bound, no backing sibling, non-constant operator, non-collection and mixed receivers, call, element, optional and await receivers, accessor, tuple, this, strictNullChecks off). Ruling S1 F1 deleted this file's earlier vacuous law on the same grounds. Supersedes the law added for findings #2/#7/#8 of review run 20261013-032451-a9153d08 (fe6b1e5) and reshaped for #10/#11 of run 20261013-055603-242b8fc9 (28da5be).
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The ignorer family's protocol is now two fields over a path that carries its ancestors as data, and the workspace types and walks ASTs in oxc's own vocabulary. A consumer installs one types-only package; no ignorer, and no part of the instrumenter behind them, declares a node shape by hand any more.
What changed
shouldIgnore. It now exportsstrykerIgnorers: { name, shouldIgnore(path: NodePath): string | undefined }[], and the engine checks exactly that: a non-stringnameor non-callableshouldIgnorefails the load withPluginLoadFailedErrornaming the module. NativestrykerPluginsmodules load unchanged.NodePathwas aparentPathchain with fiveis*()methods, and each ignorer shipped its ownAstNode.tsandAncestorWalk.ts. It is now{ node: unknown, ancestors: readonly unknown[] }— plain data, nearest ancestor first, no methods — and both packages' node shapes and walkers are deleted, their guards inlined into the decision file over the ancestor array.@oxc-project/types— the parser's own view of the same objects — inlined into its emitted declarations, so consumers gain no dependency. The instrumenter went further: its thirty-odd local node declarations are gone, replaced by 139 aliases over the same types, derived throughtype-fest.estree-walkerruntime load are gone; the mutation traversal and the Svelte walk both resolveoxc-walker.stop()aborts through aSchema.TaggedError, because the walker exposes onlyskip/remove/replaceand no abort primitive; comment records stay excluded by key.stryker-ignorer-workflow-make-boundaryis deleted. It encodes the systemfsoftware DMMF stack'sWorkflow.makeconvention, which this repo does not own. Its entry left the engine's base preset, the plugin-manifest specifier list, and the changeset set.api-extractor.json, plusapi-extractor.<entry>.jsonandtsconfig.api-<entry>.jsonper extra subpath. Sixteen reports live under<package>/etc/, covering engine's five entries, vitest-runner's two, and one per single-entry package.testandlintdid not declare the shared toolchain presets they import. Editingpackages/toolchain/vitest-config/lib/**or.../oxlint-ignorer-config/lib/**left the consuming tasks at an unchanged hash and statusHIT— a stale green. Both are inputs now.Two gates stayed out of this PR by design: the CI workflow, and the root
check:ci/gate:tasks/gate:distscripts. Wiringapi:checkinto them is the owner's edit.Verification
pnpm check:cigreen at the tip of the branch (format, task gate, dist gate).turbo lint typecheck test --forcegreen.ancestorswith noparentPathin the input.angular-ignorer.integration.test.ts, which drives the publishedinstrumentsurface with the publishedangularIgnoreras configuration data and pins both Angular status reasons; the Svelte suite now reaches its mutants throughoxc-walker.dist/index.mjsisexport {},dist/index.d.tscontains noimport, and the API report carries noae-forgotten-export.estree-walkerappears nowhere underpackages/**source or manifests.turbo api:check --force: 23/23, all sixteen reports current.attw --packclean for every subpath export, under node10 / node16-cjs / node16-esm / bundler.pnpm peers check: no unmet peers. Removing the unusedvite-tsconfig-pathsdevDependency droppedtsconfck, which wanted TypeScript^5against this catalog's^7.**/etc/*.api.md -text). API Extractor writes CRLF, and the repo's* text=auto eol=lfwas normalising the reports to LF in git, which madeapi:checkfail on a fresh checkout while the working tree stayed green.Review accounting
Structured review raised two P0s against
.github/workflows/ci.yml— the graded change scoped the gate it is graded by. That change is reverted; the scoping idea belongs to the gate's owner. Review drove the contract cut: the descriptor no longer demands a schema member and the engine no longer re-declares one. Follow-ups recorded, not defects: an engine scenario loading a publisheddistentry, a native-only loader scenario, duplicate-name warnings for plain entries, and five turbo graph proposals that would deviate from the house pattern (the largest:api:check's script re-runs thetsdownits own turbo dependency already ran).The walker swap was driven by a challenge worth recording:
Built<T>cannot beSimplifyd inside itself (tsc reports TS2615/TS2589 on the recursive AST), andoxc-walker'swalkis not generic, so the aliases at that seam admit parsed nodes alongside synthesized ones rather than casting.Docs
docs/plans/2026-09-14-0520-refactor-ignorer-plain-entry-plan.md(supersedes the deleted 0150 plan).docs/solutions/tooling-decisions/plain-entry-contract-without-a-declared-schema.md.