Repository navigation
ci(checker-parity): compare main's and the branch's TypeScript checker verdicts - #266
Merged
Merged
Conversation
Gap table against origin/main, measured updateSnapshot cost on the TS 7.0.2 unstable/async API, the edit-location soundness rule for the importer re-check shortcut, the CI verdict-parity design, the api-extractor TS5 ruling, and six open questions for the supervisor.
…unit Twelve stacked units: per-produce diagnostic rendering, snapshot/emit counters, the edit-location Soundness Rule shortcut, file-bounded rounds, one emit build per call, a sharded checker-parity CI lane against the merge-base checker, and the B10 type-query port for Stream I. Report-only ce-doc-review findings with proposed dispositions appended.
Applies every document-review disposition the supervisor ruled in .omp-brief/rulings-plan.md (F1-F11, F13-F18; F13 and F17 narrowed; F12 rejected) and records OQ-P1..OQ-P10 as ruled. Replaces the nine-layer sequencing with four linear layers: docs+U1-U3, lane U8-U10, type query U11-U12, shortcut U4-U7.
… correct the 8.1.0 importer note U1 of docs/plans/2026-10-09-2106-perf-typescript-checker-tsgo-native-plan.md (R11). README prerequisites match minimumTypeScriptVersion; the changeset corrects the released importer re-check claim without rewriting CHANGELOG.md.
…against its own text U2 of the TS7 checker plan (R14, KTD3). checkOne renders its mutant's diagnostics while that mutant's snapshot is current; MutantCheck carries rendered lines and Checker.cell no longer renders after the call. AE10 (per-mutant-position fixture) fails on the old code (line 16 instead of 20). The join property targets dogfood mutant 47892392410b616f.
…ces and emit builds per check U3 of the TS7 checker plan (R3, KTD6). A per-call tally on the compiler runtime counts snapshot updates, parenthesized re-splices and TCE emit builds and sums emit time; check annotates them on typescript-checker.compiler.check with typescript.counts.schema_version 1. No new span names. No package test (Test Admission refused); the parity lane's zero-updates refusal catches a tally that is never set.
U8 of the TS7 checker plan (R6, B1). A four-file project with isolatedDeclarations: true whose exported functions carry explicit return types and call each other across files, so the edit-location shortcut has a non-empty proof set on the parity lane. tsc -p (TS 7.0.2) passes; an unannotated export fails with TS9013.
…e and compares sides U9 of the TS7 checker plan (R3, R6-R10, KTD5, KTD6, KTD8, KTD9). Private package @systemfsoftware/stryker-checker-parity: 'run' discovers the R6 corpus, instruments a shard of it with the branch instrumenter, drives main's and the branch's checker worker over Plugin.CheckerRpcs, collects counts from the check spans through a local OTLP/HTTP receiver and writes versioned NDJSON; 'compare' merges every shard and fails on a verdict difference, a missing shard, or (when on) the shortcut-count and speed gates. Every record and failure carries schemaVersion 1, a closed reason code and a next action. The package's lint debt is cleared in the next commit; switch flags (--allow-local, --shortcut-gate, --speed-gate) no longer swallow the next positional.
U10 of the TS7 checker plan (R7, R9, KTD5, KTD8). checker-parity-changes decides whether a pull request reaches the corpus (every push to main runs); six checker-parity legs build main's checker at the merge-base and this commit's checker, then run both over one shard; checker-parity-report merges the shards and fails on any verdict difference or a missing shard. Every failure writes a ::error annotation and a step summary naming the reason code and the artifact to read. The shortcut-count and speed gates stay off until layer 4.
…1 added The per-mutant-position fixture's padding variable is now _b (oxlint no-unused-vars); lines and the shift mutant's body span are unchanged. The reason-format law is deleted: the OwnRenderedDiagnostics law already pins every mutant's reason over the full CheckMutantsInput schema, and the duplicate declared a module-scope schema in a test file (schema-declaration-location).
…pack The isolatedDeclarations fixture moves to __fixtures__/, which gritlint and the package's oxlint config ignore: it is corpus input with its own standalone tsconfig, not package source. The driver's export map is now generated by tsdown through @systemfsoftware/tsdown-config's sourceExports, like every other tsdown package, so publishConfig.exports exists and carries no source condition.
compare-sides.workflow.ts combines conditions with effect/Boolean every/some (complexity 1) and indexes verdicts and skipped projects through HashMap, keeping last-wins values and first-seen order. Parity.schema.ts exports schemas only; the shell applies S.fromJsonString(ParityLine) and Shard at its boundary. shard.ts hashes with @noble/hashes and offers dual signatures. The property test draws the production VerdictStatus schema. shard.property.test.ts is deleted: inShard is defined through shardOf, so its two partition laws restated the implementation, and its decode law copied the Shard refinement as its oracle.
…vices The imperative shell (run-side.ts, main.ts, otlp-receiver.ts, corpus.ts) now reads env through Config, files through FileSystem/Path, children through ChildProcessSpawner, serves OTLP on effect/http, and parses argv with effect/cli. Exit codes are mapped once at the runMain edge: 0 for parity or --help, 1 for a parity break, 2 for a driver failure. OTLP and shell-failure schemas move to Otlp.schema.ts and Shell.schema.ts; span counting is the pure span-counts.ts. compare-sides: normalising a reason now replaces the own-file position with a replacer function, so a file name holding a String.replace pattern ($&, $1) stays literal. The OwnFilePositionOnly law found it with the file name "\0\0$&". The three src/__tests__ files tested the hand-rolled parser, receiver and corpus helpers this replaces, and the test-layout rules forbid them there; their behaviour is internal to a private CLI whose only surface is the bin, so they are deleted, not moved.
checker-parity-report ran only after checker-parity-changes succeeded, so a failed change detection skipped the report and the required context read as passing. The report now always runs, fails with code shard-incomplete when change detection failed or was cancelled, and still passes when the bot-push guard skipped it.
A cached verdict is reused only when the stored identity (hash of the worker bundle directory that actually runs, the checker's program digest, and the mutant wires) equals the current one. Reuse is decided by the pure reuse-cached-verdicts workflow; the identity is a sidecar written after the verdicts, so a torn write re-checks. Replaces the hash of the checker's src tree, which missed bundled dependencies (plugin-interface, cli-contract, effect-cell-types).
The trigger subcommand runs the pure trigger-parity-lane workflow over the workspace closure pnpm reports for the checker and the driver, plus shared inputs (lockfile, workspace manifest, flake.lock, ci.yml, e2e testResources). Replaces the hand-maintained path regex in ci.yml.
… pure core report-parity-outcome maps a compare result or driver failure to exit code (0 holds, 1 broken, 2 driver failure), one ::error per violation kind, and the step summary; property laws pin each. ci.yml calls the trigger subcommand and drops the removed --main-source/--branch-source flags.
…nore-rule change Main (#262) widened the check RPC to CheckAnswer, whose ignored reason is unknown. The driver records it as the reason text so ignored verdicts still compare exactly.
…2 review Review of #266 (ce-code-review mode:agent, 7 reviewers + validator): compare refuses a run that compared zero mutants (nothing-compared, exit 1); per-shard skips no longer list a project measured in another shard; the trigger step fails on a driver failure (pipefail); shard NDJSON is appended per project so a killed leg leaves its rows; no throw in the workflow or schema files; Shell.schema.ts renamed DriverFailure.schema.ts; PhaseLine declared once; the pure-core import ban covers every pure module.
A workspace package's sources appear in the program of every project that imports it, so the first lane run mutated the instrumenter's files once per importing project (html-reporter spent 790 of its 835 s per side on them) and four shards hit the 2400 s timeout. The listing now keeps only files under the project's own directory: 581 -> 306 files over four sampled projects.
…current sides Work in progress on the root parity-budget ruling: PR legs check changed-file mutants (capped per file) plus a seeded drift sample from pr-scope.json; full corpus runs on push and workflow_dispatch; verdict cache is per file and saved with if: always() under run id + attempt + shard; legs write scope-<k>.json with the wall-time split.
…der in the schema file Per ADR-0001's closed taxonomy and ADR-0002: the unsuffixed select-scope.ts is gone. The drift-leg assignment is a decision, so it is the assign-drift-legs workflow (mutation-graded, laws in its own suite); the seeded order is a pure operation over ScopeSettings, so it lives in Parity.schema.ts with its law in-source, and both the scope workflow and the run-side shell call it instead of private copies.
…er/tsgo-checker-parity-lane
Sub-conductor ruling on dispatch 38039482257 (legs 3 and 4 made no progress over two attempts). Evidence: both stuck legs stopped after packages/stryker-js-html-reporter in corpus order, so they sat in packages/stryker-js-instrumenter, which leg 1 measured at 1066 s for 309 mutants on main's side; the per-file verdict cache never saved a file that never finished. scope-3/4.json were missing because the dispatched head 819d513 predates LegStarted (d5007c5). - Cache slot is one check batch (<= 16 mutants of one file), stored when the batch finishes. - run --deadline: no batch starts after it, a running batch is interrupted at it; deferred work is written as Deferred lines and the leg exits 2 with code leg-deadline, so the rerun resumes at the first unchecked batch. - A batch interrupted on two attempts in a row is cached as UnitOverBudget and compare fails with unit-over-budget naming side, file and mutant ids. - Full-corpus legs are balanced by measured file cost (greedy LPT over test/checker-parity/file-costs.json, seeded from run 38039482257: 165 files, 6158 s); compare --costs-out writes the merged table. - Every batch logs a heartbeat line (elapsed, side, project, file, batch); the step summary names the leg's planned load. - CI passes --deadline (2160 s full, 210 s PR) inside the unchanged step budgets; the failure-pointer step defers to the driver's own annotation on exit 2.
…er/tsgo-checker-parity-lane
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 10, 2026
…nction-body sites (WIP) Parked per sub-conductor ruling 2026-10-10 09:58 (#266 convergence round 2 first). Not yet reviewed; package gates pass locally: checker 39 files/255 tests, parity 17/245, plugin-interface laws 48.
Per sub-conductor ruling 2026-10-10 09:58 (#266 convergence round 2). The plan step packs contiguous 64-mutant blocks of each file into legs by first-fit-decreasing at 70% of the leg deadline, using a cost table merged from every finished run's leg artifacts (cache, bootstrapped from the newest earlier run on a miss). Matrix width comes from total measured cost. Verdict caches merge across legs. Replaces the hand-edited file-costs.json and the hash balancer.
…e core Both are effect-only pure modules, so they join pureCore in oxlint.config.ts (the package's convention for unsuffixed pure modules, beside shard.ts, span-counts.ts, corpus.ts); plan-legs.defaults.ts carried a suffix outside any convention and its one constant moves to the --fill flag default.
Sub-conductor ruling 2026-10-10 10:58 (#266): the proof dispatch 38061043178 bootstrapped its costs from one PR-scope run (31 files) and planned 5 legs. Every FileRate and ProjectOverhead now carries the run that measured it, and mergeCosts keeps the newest measurement per file, so fold order is irrelevant and a narrower run never drops files it did not touch (cost table v3, cache prefix checker-parity-costs-v3-). The plan job folds every recent completed ci.yml run with leg artifacts (workflow_dispatch, push to main, this branch; every attempt) and saves the folded table for the report job. An unmeasured file is estimated at its project's p90 per-mutant rate, else the corpus p90. Legs are a greedy contiguous segmentation of the (project, file, block) sequence, so adding a file never lowers the width; full-corpus plans use 16-mutant blocks. The step summary shows width, each leg's estimate against the 70% budget, and measured vs estimated files.
…er/tsgo-checker-parity-lane
systemfsoftware-maker
marked this pull request as ready for review
October 10, 2026 17:16
…lane # Conflicts: # pnpm-lock.yaml
ryanleecode
approved these changes
Oct 10, 2026
ryanleecode
left a comment
Contributor
There was a problem hiding this comment.
Verified at 905187e: all checks green except the inherited enterprise-fixture bench leg (Stream G), 0 open threads, no skips/disables/secrets; parity lane 1-2 min on PRs.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 11, 2026
…pull-request run checker-parity-report on fef35ce (run 38092982281) failed zero-shortcuts isolatedDeclarations because no leg checked the fixture: PR scope checks changed files plus 6 seeded drift projects, the fixture ranks 22nd in the seeded order, and once #266 landed on main its files were no longer changed against the merge base. The shortcut gate judges that fixture on every run, so its files now count as changed on every pull-request leg (checkedOnEveryPullRequest in corpus.ts). No guard refused its sites.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 11, 2026
…roject's closure Adds the failing acceptance scenarios for the run-inputs digest scope: - AE1 replays the repository's real lockfile across #266 (56426ca -> ec9dcf6): each of the four mutated projects must keep its saved verdicts. Today all four are refused as runInputsChanged. - AE2 uses a pnpm-generated oracle workspace where only project a moves a dependency: a must re-test and b must keep its verdicts. Today b is refused too. Both drive the published Engine surface and observe the second run's ReuseReported event. The oracle locks come from the repository's pinned pnpm via `pnpm --filter @systemfsoftware/stryker-js fixtures:lockfile-oracle`, which writes a lock only when two runs agree byte for byte.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 11, 2026
…rkflow lockfileClosure walks the project's importer and its ancestors, every dependency field of linked importers, and the snapshots and packages entries they reach, with a visited set so cycles terminate. Every unusable input (undecodable lockfile or install settings, lockfileVersion other than 9, no manifest, missing importer, dangling ref) decides the whole-file digest with a tagged reason. Laws pin the #266 shape (an unlinked importer changes nothing), exact reach for version and integrity moves, root devDependencies and install settings moving every project, and cycle termination.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 11, 2026
…inputs digest runInputsDigestOf now reads pnpm-lock.yaml and pnpm-workspace.yaml, builds a LockfileClosureCommand, and hashes the closure the lockfile-closure workflow decides: the project's importer, its ancestor importers, linked importers, the snapshots and package entries they reach, the hoisting settings and the root packageManager. Other lockfiles and an unusable pnpm-lock.yaml keep the whole-file digest; the unusable case logs one warning naming the reason code and the next action. Canonical JSON orders keys by code unit (Order.String) instead of localeCompare, and INCREMENTAL_CACHE_VERSION moves 5 -> 6 so digests from the old layout are discarded once. AE1/AE2 (the #266 lockfile edit and the oracle bump) now pass, plus AE3-AE13 as integration scenarios and in-source laws.
This was referenced Oct 11, 2026
ryanleecode
added a commit
that referenced
this pull request
Oct 12, 2026
…303) * docs(stryker-js): scope the run-inputs digest to each project's lockfile closure Requirements for Stream K: verified premises (file:line), closure rule, whole-file fallback, cache version bump, rollout through release and the stryker-published pin. * docs(stryker-js): plan the per-project lockfile closure for the run-inputs digest Enriches the brainstorm with the planning contract: Workflow.make closure decision, pnpm-lock schema, real-tool lockfile oracles, failing-first AE1/AE2, cache version 5->6, patch changeset. * docs(stryker-js): apply the plan review rulings Records hidden hoisting as an accepted gap (KD6) with a mechanical trigger: the closure input carries pnpm-workspace.yaml's hoisting keys and the root packageManager (R11, AE11, AE12). Adds the live-lockfile closure assertion (AE13) and the oracle regeneration script, corrects the P1 probe counts, and fills the U4 AE4 scenario. * test(stryker-js): pin verdict reuse across lockfile edits outside a project's closure Adds the failing acceptance scenarios for the run-inputs digest scope: - AE1 replays the repository's real lockfile across #266 (56426ca -> ec9dcf6): each of the four mutated projects must keep its saved verdicts. Today all four are refused as runInputsChanged. - AE2 uses a pnpm-generated oracle workspace where only project a moves a dependency: a must re-test and b must keep its verdicts. Today b is refused too. Both drive the published Engine surface and observe the second run's ReuseReported event. The oracle locks come from the repository's pinned pnpm via `pnpm --filter @systemfsoftware/stryker-js fixtures:lockfile-oracle`, which writes a lock only when two runs agree byte for byte. * feat(stryker-js): declare the pnpm v9 lockfile schema and its ref grammar Decodes pnpm-lock.yaml's importers, snapshots and packages with strict dependency maps, keeping every other entry field as JSON so a closure hashes whole entries. lockRefOf follows pnpm's refToRelative rule (aliases, peer suffixes, patch_hash, file: and link: refs) and packageKeyOf drops the trailing peer and patch groups; both are pinned against the forms the oracle lock records. * feat(stryker-js): decide each project's lockfile closure in a pure workflow lockfileClosure walks the project's importer and its ancestors, every dependency field of linked importers, and the snapshots and packages entries they reach, with a visited set so cycles terminate. Every unusable input (undecodable lockfile or install settings, lockfileVersion other than 9, no manifest, missing importer, dangling ref) decides the whole-file digest with a tagged reason. Laws pin the #266 shape (an unlinked importer changes nothing), exact reach for version and integrity moves, root devDependencies and install settings moving every project, and cycle termination. * fix(stryker-js): hash each project's own lockfile closure in the run-inputs digest runInputsDigestOf now reads pnpm-lock.yaml and pnpm-workspace.yaml, builds a LockfileClosureCommand, and hashes the closure the lockfile-closure workflow decides: the project's importer, its ancestor importers, linked importers, the snapshots and package entries they reach, the hoisting settings and the root packageManager. Other lockfiles and an unusable pnpm-lock.yaml keep the whole-file digest; the unusable case logs one warning naming the reason code and the next action. Canonical JSON orders keys by code unit (Order.String) instead of localeCompare, and INCREMENTAL_CACHE_VERSION moves 5 -> 6 so digests from the old layout are discarded once. AE1/AE2 (the #266 lockfile edit and the oracle bump) now pass, plus AE3-AE13 as integration scenarios and in-source laws. * chore(stryker-js): record the run-inputs closure digest as a patch * test(stryker-js): fail lockfile-closure scenarios whose engine run dies Each staged run now records whether its Exit succeeded, and every scenario expects completed runs. The live-lockfile scenario only collected fallback warnings, so a run that died before logging passed with an empty list; under a stubbed early exit it now fails on completed: false (review #303, P1). * test(stryker-js): prove each install setting by law, not by twenty engine runs ∀wmo_PnpmWorkspaceSettings_≡EachInstallSettingEditMovesEveryProject stages two projects and checks that editing hoist, hoistPattern, publicHoistPattern, nodeLinker, shamefullyHoist or the root packageManager moves both projects' run-inputs digests; blinding the shell to any one of the six refutes it. The integration file keeps one engine scenario for the end-to-end path (nodeLinker: hoisted, 4 runs) in place of the five-setting scenario and the packageManager scenario (24 runs), so it no longer leans on the 60 s default timeout (review #303, P1). File: 54.09 s -> 33.57 s; hoisting scenario: 11.23 s -> 2.18 s. * test(stryker-js): vary the lockfile settings block in the closure laws ∀lok_PnpmLockfile_≡SettingsMoveEveryProjectWhileAnOutsideEditMovesNone: moving any key of the lockfile's own settings block moves every importer's closure decision, and adding a snapshot no importer reaches moves none. The closure carries settings verbatim, yet no law varied it; dropping settings from the closure now fails this law and only this law (review #303, P2). --------- Co-authored-by: Ryan Lee <drdgvhbh@gmail.com>
ryanleecode
added a commit
that referenced
this pull request
Oct 12, 2026
…gate wrong NotAssignable in the parity lane (#277) * docs(brainstorms): requirements for the TypeScript 7 native checker unit Gap table against origin/main, measured updateSnapshot cost on the TS 7.0.2 unstable/async API, the edit-location soundness rule for the importer re-check shortcut, the CI verdict-parity design, the api-extractor TS5 ruling, and six open questions for the supervisor. * docs(plans): implementation plan for the TypeScript 7 native checker unit Twelve stacked units: per-produce diagnostic rendering, snapshot/emit counters, the edit-location Soundness Rule shortcut, file-bounded rounds, one emit build per call, a sharded checker-parity CI lane against the merge-base checker, and the B10 type-query port for Stream I. Report-only ce-doc-review findings with proposed dispositions appended. * docs(plans): apply review dispositions and the four-layer stack Applies every document-review disposition the supervisor ruled in .omp-brief/rulings-plan.md (F1-F11, F13-F18; F13 and F17 narrowed; F12 rejected) and records OQ-P1..OQ-P10 as ruled. Replaces the nine-layer sequencing with four linear layers: docs+U1-U3, lane U8-U10, type query U11-U12, shortcut U4-U7. * docs(stryker-js-typescript-checker): state the TypeScript 7 floor and correct the 8.1.0 importer note U1 of docs/plans/2026-10-09-2106-perf-typescript-checker-tsgo-native-plan.md (R11). README prerequisites match minimumTypeScriptVersion; the changeset corrects the released importer re-check claim without rewriting CHANGELOG.md. * docs(plans): record the root rulings on OQ-P5, OQ-P8 and OQ-P9 * docs(plans): record the agentic-quality standing rule in R9, U9, U10 and U12 * fix(stryker-js-typescript-checker): render each mutant's diagnostics against its own text U2 of the TS7 checker plan (R14, KTD3). checkOne renders its mutant's diagnostics while that mutant's snapshot is current; MutantCheck carries rendered lines and Checker.cell no longer renders after the call. AE10 (per-mutant-position fixture) fails on the old code (line 16 instead of 20). The join property targets dogfood mutant 47892392410b616f. * feat(stryker-js-typescript-checker): report snapshot updates, re-splices and emit builds per check U3 of the TS7 checker plan (R3, KTD6). A per-call tally on the compiler runtime counts snapshot updates, parenthesized re-splices and TCE emit builds and sums emit time; check annotates them on typescript-checker.compiler.check with typescript.counts.schema_version 1. No new span names. No package test (Test Admission refused); the parity lane's zero-updates refusal catches a tally that is never set. * test(checker-parity): add an isolatedDeclarations corpus fixture U8 of the TS7 checker plan (R6, B1). A four-file project with isolatedDeclarations: true whose exported functions carry explicit return types and call each other across files, so the edit-location shortcut has a non-empty proof set on the parity lane. tsc -p (TS 7.0.2) passes; an unannotated export fails with TS9013. * feat(checker-parity): add the parity driver that runs one checker side and compares sides U9 of the TS7 checker plan (R3, R6-R10, KTD5, KTD6, KTD8, KTD9). Private package @systemfsoftware/stryker-checker-parity: 'run' discovers the R6 corpus, instruments a shard of it with the branch instrumenter, drives main's and the branch's checker worker over Plugin.CheckerRpcs, collects counts from the check spans through a local OTLP/HTTP receiver and writes versioned NDJSON; 'compare' merges every shard and fails on a verdict difference, a missing shard, or (when on) the shortcut-count and speed gates. Every record and failure carries schemaVersion 1, a closed reason code and a next action. The package's lint debt is cleared in the next commit; switch flags (--allow-local, --shortcut-gate, --speed-gate) no longer swallow the next positional. * ci: add the checker-parity lane U10 of the TS7 checker plan (R7, R9, KTD5, KTD8). checker-parity-changes decides whether a pull request reaches the corpus (every push to main runs); six checker-parity legs build main's checker at the merge-base and this commit's checker, then run both over one shard; checker-parity-report merges the shards and fails on any verdict difference or a missing shard. Every failure writes a ::error annotation and a step summary naming the reason code and the artifact to read. The shortcut-count and speed gates stay off until layer 4. * test(stryker-js-typescript-checker): clear the two lint errors layer 1 added The per-mutant-position fixture's padding variable is now _b (oxlint no-unused-vars); lines and the shift mutant's body span are unchanged. The reason-format law is deleted: the OwnRenderedDiagnostics law already pins every mutant's reason over the full CheckMutantsInput schema, and the duplicate declared a module-scope schema in a test file (schema-declaration-location). * build(checker-parity): satisfy the repo's source-resolution gritlint pack The isolatedDeclarations fixture moves to __fixtures__/, which gritlint and the package's oxlint config ignore: it is corpus input with its own standalone tsconfig, not package source. The driver's export map is now generated by tsdown through @systemfsoftware/tsdown-config's sourceExports, like every other tsdown package, so publishConfig.exports exists and carries no source condition. * refactor(checker-parity): clear the pure core's lint debt compare-sides.workflow.ts combines conditions with effect/Boolean every/some (complexity 1) and indexes verdicts and skipped projects through HashMap, keeping last-wins values and first-seen order. Parity.schema.ts exports schemas only; the shell applies S.fromJsonString(ParityLine) and Shard at its boundary. shard.ts hashes with @noble/hashes and offers dual signatures. The property test draws the production VerdictStatus schema. shard.property.test.ts is deleted: inShard is defined through shardOf, so its two partition laws restated the implementation, and its decode law copied the Shard refinement as its oracle. * refactor(checker-parity): run the driver shell on Effect platform services The imperative shell (run-side.ts, main.ts, otlp-receiver.ts, corpus.ts) now reads env through Config, files through FileSystem/Path, children through ChildProcessSpawner, serves OTLP on effect/http, and parses argv with effect/cli. Exit codes are mapped once at the runMain edge: 0 for parity or --help, 1 for a parity break, 2 for a driver failure. OTLP and shell-failure schemas move to Otlp.schema.ts and Shell.schema.ts; span counting is the pure span-counts.ts. compare-sides: normalising a reason now replaces the own-file position with a replacer function, so a file name holding a String.replace pattern ($&, $1) stays literal. The OwnFilePositionOnly law found it with the file name "\0\0$&". The three src/__tests__ files tested the hand-rolled parser, receiver and corpus helpers this replaces, and the test-layout rules forbid them there; their behaviour is internal to a private CLI whose only surface is the bin, so they are deleted, not moved. * ci(checker-parity): fail the lane report when change detection fails checker-parity-report ran only after checker-parity-changes succeeded, so a failed change detection skipped the report and the required context read as passing. The report now always runs, fails with code shard-incomplete when change detection failed or was cancelled, and still passes when the bot-push guard skipped it. * fix(checker-parity): key each side's verdict cache on its worker bundle A cached verdict is reused only when the stored identity (hash of the worker bundle directory that actually runs, the checker's program digest, and the mutant wires) equals the current one. Reuse is decided by the pure reuse-cached-verdicts workflow; the identity is a sidecar written after the verdicts, so a torn write re-checks. Replaces the hash of the checker's src tree, which missed bundled dependencies (plugin-interface, cli-contract, effect-cell-types). * fix(checker-parity): trigger the lane from the pnpm dependency closure The trigger subcommand runs the pure trigger-parity-lane workflow over the workspace closure pnpm reports for the checker and the driver, plus shared inputs (lockfile, workspace manifest, flake.lock, ci.yml, e2e testResources). Replaces the hand-maintained path regex in ci.yml. * fix(checker-parity): decide exit code, annotations and summary in the pure core report-parity-outcome maps a compare result or driver failure to exit code (0 holds, 1 broken, 2 driver failure), one ::error per violation kind, and the step summary; property laws pin each. ci.yml calls the trigger subcommand and drops the removed --main-source/--branch-source flags. * fix(checker-parity): read checker answers as CheckAnswer after the ignore-rule change Main (#262) widened the check RPC to CheckAnswer, whose ignored reason is unknown. The driver records it as the reason text so ignored verdicts still compare exactly. * fix(checker-parity): fail when no mutant was compared; resolve layer 2 review Review of #266 (ce-code-review mode:agent, 7 reviewers + validator): compare refuses a run that compared zero mutants (nothing-compared, exit 1); per-shard skips no longer list a project measured in another shard; the trigger step fails on a driver failure (pipefail); shard NDJSON is appended per project so a killed leg leaves its rows; no throw in the workflow or schema files; Shell.schema.ts renamed DriverFailure.schema.ts; PhaseLine declared once; the pure-core import ban covers every pure module. * feat(stryker-js-plugin-interface): add the provisional type-query port New ./type-query entry: TypeQuery service tag and the schema-version-1 request, response, answer and refusal shapes (plan U11, OQ-P8: port only, no Layer). Refusals carry a version literal and a next action per the root standing rule. * fix(checker-parity): mutate each source only in the project that owns it A workspace package's sources appear in the program of every project that imports it, so the first lane run mutated the instrumenter's files once per importing project (html-reporter spent 790 of its 835 s per side on them) and four shards hit the 2400 s timeout. The listing now keeps only files under the project's own directory: 581 -> 306 files over four sampled projects. * feat(stryker-js-typescript-checker): type-query implementation and lane gate, work in progress Work in progress, paused by root ruling to fix the layer 2 parity lane budget. Checker and parity packages typecheck, lint and pass their unit suites locally; TypeQueryLive not yet smoke-run against tsgo; no PR. * feat(checker-parity): pull request scope, per-file verdict cache, concurrent sides Work in progress on the root parity-budget ruling: PR legs check changed-file mutants (capped per file) plus a seeded drift sample from pr-scope.json; full corpus runs on push and workflow_dispatch; verdict cache is per file and saved with if: always() under run id + attempt + shard; legs write scope-<k>.json with the wall-time split. * refactor(checker-parity): assign drift legs in a workflow and seed order in the schema file Per ADR-0001's closed taxonomy and ADR-0002: the unsuffixed select-scope.ts is gone. The drift-leg assignment is a decision, so it is the assign-drift-legs workflow (mutation-graded, laws in its own suite); the seeded order is a pure operation over ScopeSettings, so it lives in Parity.schema.ts with its law in-source, and both the scope workflow and the run-side shell call it instead of private copies. * chore(checker-parity): drop the property seed file the in-source law run left behind * fix(checker-parity): match the mutant's own file in absolute diagnostic paths The checker writes absolute paths in diagnostics while each Verdict carries the repo-relative fileName, so the OQ-P1 own-file position normalisation anchored at the start of the line never fired in CI: runs 38036865136 and 38037569511 failed on 28 and 31 position-only differences. The own file now matches as a path suffix at a path boundary; the position laws draw absolute roots, and a sibling sharing the name's suffix is still refused. * fix(checker-parity): write the leg scope file when the leg starts Per the sub-conductor's l2 budget ruling (P3): scope-<k>.json was written only after the leg's last project, so a timed-out leg's shard-timeout annotation named a file that did not exist. The leg now writes a tagged LegStarted record (shard, scope, settings, the leg's projects, discovery time) before the first project and replaces it with the tagged LegScope when it finishes. compare decodes either and refuses a started-only leg with shard-incomplete naming the unfinished project count. * fix(stryker-js-typescript-checker): refuse NotAssignable under any generic callee, pin the type query against tsgo The generic-call guard read getResolvedSignature, which tsgo 7.0.2 returns already instantiated, so id('x') under declare function id<T>(x: T): T reported no type parameters and answered "" NotAssignable to "x" although id('') compiles. The CallArgument fact is now declaredGeneric: any declared signature of the callee (getSignaturesOfType) has type parameters. This corrects plan KTD11's condition, which named the resolved signature. tests/type-query-pins.integration.test.ts runs the real TypeQueryLive against tsgo: AE12-AE14 answers, contextual types unchanged by the appended probe statements, a file ending in a line comment, AE15 (the #4804 panic file refused server-crashed while the next file is answered and a concurrent check keeps its verdicts), and unsupported-version and not-in-project refusals. tests/type-query.differential.test.ts applies every answered candidate through CheckerRuntime and fails on a NotAssignable whose verdict is not CompileError; it went red on the reintroduced generic-call bug. * test(stryker-js-plugin-interface): state the type-query request's refusal boundary The generated laws prove TypeQuery's schemas round-trip but never present a value they must refuse. The request refuses a fractional version and an empty file list; the in-source law draws the unrefined base with boundary seeds and fails when either refinement is widened (checked: S.Array for files, S.Finite for version). * fix(stryker-js-typescript-checker): answer NotAssignable only where a declared type enforces the context CI run 38040840996 failed wrong-not-assignable twice: a 'true' inside '[[..., true] as const]' returned from an arrow passed to Option.match, and a literal inside S.Literals([...]). Both sites had a contextual type that was inference, not a constraint, so the mutant compiled. The KTD11 condition 'not an argument of an overloaded or generic call' only looked at the immediate parent. The handle now walks from the site to where its contextual type comes from (ContextOrigin): an annotated declaration, assignment, satisfies or annotated return is DeclaredContext; a call argument carries its callee's signature facts; anything else is UnenforcedContext, answered Unknown 'context-not-enforced' (new UnknownReason, last in KTD11 order). The walk climbs parentheses, array/object literals, spreads, conditional branches, ??/||/&&, 'as const' and unannotated arrow/function bodies. Plain 'as T' / <T> assertions stop as unenforced. Pins and the differential spec gain the two CI shapes plus a plain assertion, an annotated array element and a const-asserted tuple under an annotation; both were red against the previous handle. The lane's unknown-reason counts are now keyed by the port's UnknownReason. Probed with the built TypeQueryLive on the two CI sites: both now Unknown overloaded-or-generic-call. * fix(stryker-js-typescript-checker): resolve layer-3 review findings ce-code-review (report-only, base stryker/tsgo-checker-parity-lane) on ddda557; validator confirmed 13 of 14. - project-open-failed: a tsconfig tsgo cannot open used to refuse every file not-in-project (wrong next action); openServer now refuses the request project-open-failed when the snapshot holds no project for the tsconfig. Pinned (red before). - Handler reasons: a separate type-query-reasons fixture drives no-contextual-type, site-not-expression, error-type, instantiable-target and site-not-found through the real handler, plus a second answered file on the same server. - Differential oracle now fails when the query refuses the fixture instead of passing vacuously. - Probe slots are taken from the text being appended, not re-derived from the join layout. - answerTypeQuery decides its own AnswerDecision classes (shared type id, as check-mutants does); the handle maps them to the port's TypeAnswer. The constructor shim answer-type-query.schema.ts is gone: it exported functions from a *.schema.ts. - Harness schemas moved to tests/__fixtures__/*-law.fixture.ts (schema-declaration-location). - classify-candidate law renders the Parenthesized case as '(x)', not 'x)'. - checker-parity: the type-query driver moved out of run-side.ts into type-query-side.ts (run-side 1412 -> 1210 lines); TypeQueryRefusalReason is the union of the port's two refusal schemas. * feat(checker-parity): report each project's type-query answers, reasons and refusals Sub-conductor ruling 1 on #277: R18 stays (a project with no answers passes), and the step summary and summary JSON must show a dark project. - TypeQueryProjectShare gains unknownReasons (keyed by the port's UnknownReason), refusedFiles and refusedMutants. - The step summary gains a type-query line and one line per project: answered, NotAssignable, Unknown with its non-zero reasons, refused files and mutants, and 'no answers' when nothing was answered. - The type-query count schemas are NonNegativeInt (exported from Parity.schema.ts): the new summary law drew a negative Unknown count, a state no run can produce. - Plan U11: TypeQueryLive is a fixed Layer (ruling 2), amended in place as the ruling directs. * fix(checker-parity): make every full-corpus attempt advance Sub-conductor ruling on dispatch 38039482257 (legs 3 and 4 made no progress over two attempts). Evidence: both stuck legs stopped after packages/stryker-js-html-reporter in corpus order, so they sat in packages/stryker-js-instrumenter, which leg 1 measured at 1066 s for 309 mutants on main's side; the per-file verdict cache never saved a file that never finished. scope-3/4.json were missing because the dispatched head 819d513 predates LegStarted (d5007c5). - Cache slot is one check batch (<= 16 mutants of one file), stored when the batch finishes. - run --deadline: no batch starts after it, a running batch is interrupted at it; deferred work is written as Deferred lines and the leg exits 2 with code leg-deadline, so the rerun resumes at the first unchecked batch. - A batch interrupted on two attempts in a row is cached as UnitOverBudget and compare fails with unit-over-budget naming side, file and mutant ids. - Full-corpus legs are balanced by measured file cost (greedy LPT over test/checker-parity/file-costs.json, seeded from run 38039482257: 165 files, 6158 s); compare --costs-out writes the merged table. - Every batch logs a heartbeat line (elapsed, side, project, file, batch); the step summary names the leg's planned load. - CI passes --deadline (2160 s full, 210 s PR) inside the unchanged step budgets; the failure-pointer step defers to the driver's own annotation on exit 2. * feat(stryker-js-typescript-checker): typeQuery RPC, capability and function-body sites (WIP) Parked per sub-conductor ruling 2026-10-10 09:58 (#266 convergence round 2 first). Not yet reviewed; package gates pass locally: checker 39 files/255 tests, parity 17/245, plugin-interface laws 48. * fix(checker-parity): plan full-corpus legs from measured per-file cost Per sub-conductor ruling 2026-10-10 09:58 (#266 convergence round 2). The plan step packs contiguous 64-mutant blocks of each file into legs by first-fit-decreasing at 70% of the leg deadline, using a cost table merged from every finished run's leg artifacts (cache, bootstrapped from the newest earlier run on a miss). Matrix width comes from total measured cost. Verdict caches merge across legs. Replaces the hand-edited file-costs.json and the hash balancer. * fix(stryker-js-typescript-checker): name the served type-query versions from the schema The unsupported-version refusal referenced a deleted constant and threw instead of refusing; a version 3 request now gets the refusal, pinned in type-query-pins. typeQueryServingOf moves out of TypeQuery.schema.ts into type-query-serving.ts with a pipeable signature; TYPE_QUERY_VERSIONS is gone (TypeQueryVersion.literals). Plugin-interface oxlint and lint:tsgo now exit 0. * refactor(checker-parity): hold blocks.ts and file-costs.ts to the pure core Both are effect-only pure modules, so they join pureCore in oxlint.config.ts (the package's convention for unsuffixed pure modules, beside shard.ts, span-counts.ts, corpus.ts); plan-legs.defaults.ts carried a suffix outside any convention and its one constant moves to the --fill flag default. * fix(checker-parity): plan from every run's measurements, conservatively Sub-conductor ruling 2026-10-10 10:58 (#266): the proof dispatch 38061043178 bootstrapped its costs from one PR-scope run (31 files) and planned 5 legs. Every FileRate and ProjectOverhead now carries the run that measured it, and mergeCosts keeps the newest measurement per file, so fold order is irrelevant and a narrower run never drops files it did not touch (cost table v3, cache prefix checker-parity-costs-v3-). The plan job folds every recent completed ci.yml run with leg artifacts (workflow_dispatch, push to main, this branch; every attempt) and saves the folded table for the report job. An unmeasured file is estimated at its project's p90 per-mutant rate, else the corpus p90. Legs are a greedy contiguous segmentation of the (project, file, block) sequence, so adding a file never lowers the width; full-corpus plans use 16-mutant blocks. The step summary shows width, each leg's estimate against the 70% budget, and measured vs estimated files. * fix(stryker-js-cli-contract): name the rpc.typeQuery span The checker RPC group gained typeQuery, and the span-taxonomy law (every protocol method has an rpc.<method> span) refuted it. Review finding (learnings, api-contract, adversarial; P1). * refactor(stryker-js-plugin-interface)!: serve the type-query port as the root TypeQuery namespace The ./type-query subpath is gone; every caller imports the TypeQuery namespace from the package root, the way Checker and Plugin are reached. The checker RPC group carries the type-query classes, and a consumer's declaration emit can name them only through a root export. * fix(stryker-js): build against the checker RPC group with typeQuery The engine's checker handle now names only the RPCs it calls (check, group, digest), so its declaration no longer needs the type-query answer classes (TS2883). The in-repo fake checkers answer capabilities with no type-query versions and refuse every typeQuery with unsupported-version. The RPC changeset is major: every checker worker must answer the two new RPCs (BREAK-1). Review findings: learnings/api-contract P1, changeset bump P2. * fix(stryker-js-typescript-checker): serialize type queries per tsgo server Two queries on one tsconfig interleaved the probe update and the reads: query 1 read a program holding query 2's probe text and answered candidate-not-found. Each server now has a Semaphore(1) held for the whole per-file probe; a fiber that waited on a server another fiber discarded takes the current one; a crash discards only its own server; close is bounded by a 1 s grace like the checker's. Regression: metamorphic relation in type-query.differential.test.ts (concurrent per-site queries answer as the same queries one at a time) failed before (seed 767247522: Unknown candidate-not-found vs overloaded-or-generic-call) and passes after. Review #277 P2 rows 4 and 5. * test(checker-parity): pin the type-query recall counters compileErrorTotal counts branch-side compileError verdicts only, and compileErrorAnsweredNotAssignable counts those whose own project answered NotAssignable. The law fails when the side filter is dropped or the project join is reduced to the mutant id. Review #277 P2 row 6. * test(checker-parity): draw the recall law's answer split in the generator 745bce9 failed oxlint (no-nested-quantification). The law now draws mutants answered NotAssignable and mutants answered Assignable, Unknown or nothing as two arrays, builds lines in a module helper, and counts branch compileErrors per group. It still fails when the side filter is dropped or the project join is reduced to the mutant id. * refactor(checker-parity): type the function-body mutator name against the stock catalog A renamed stock mutator now fails to compile (TS2820) instead of silently turning every function-body site into an expression site. Review #277 P3 row 10. * test(stryker-js-typescript-checker): assert the served type-query version The worker scenario now carries the version out of each serving answer and checks versions 1 and 2, served and refused. It fails when typeQueryServingOf answers the wrong version. Review #277 P3 row 11. * refactor(stryker-js-plugin-interface)!: require TypeQuerySite.kind A site's kind was an optional key whose absence meant expression, resolved at a distance in the checker (site.kind ?? 'expression') and written by presence in the lane. Every site now names its kind; the checker reads it directly and the lane writes it for every site. Review #277 P3 row 9 (schema-laws: tagged unions over state by presence). * chore(changeset): record the stryker-js RPC-group touch as a none intent #267's release consumed the intents that covered stryker-js's Checker.handle.ts typing change (a436092), so Changeset Check on 6f99d56 (run 38092173764) found stryker-js moved with no intent. The change is type-only: the engine's client is narrowed to check, group and digest. --------- Co-authored-by: Ryan Lee <drdgvhbh@gmail.com>
ryanleecode
added a commit
that referenced
this pull request
Oct 13, 2026
…s inside function bodies (#286) * docs(brainstorms): requirements for the TypeScript 7 native checker unit Gap table against origin/main, measured updateSnapshot cost on the TS 7.0.2 unstable/async API, the edit-location soundness rule for the importer re-check shortcut, the CI verdict-parity design, the api-extractor TS5 ruling, and six open questions for the supervisor. * docs(plans): implementation plan for the TypeScript 7 native checker unit Twelve stacked units: per-produce diagnostic rendering, snapshot/emit counters, the edit-location Soundness Rule shortcut, file-bounded rounds, one emit build per call, a sharded checker-parity CI lane against the merge-base checker, and the B10 type-query port for Stream I. Report-only ce-doc-review findings with proposed dispositions appended. * docs(plans): apply review dispositions and the four-layer stack Applies every document-review disposition the supervisor ruled in .omp-brief/rulings-plan.md (F1-F11, F13-F18; F13 and F17 narrowed; F12 rejected) and records OQ-P1..OQ-P10 as ruled. Replaces the nine-layer sequencing with four linear layers: docs+U1-U3, lane U8-U10, type query U11-U12, shortcut U4-U7. * docs(stryker-js-typescript-checker): state the TypeScript 7 floor and correct the 8.1.0 importer note U1 of docs/plans/2026-10-09-2106-perf-typescript-checker-tsgo-native-plan.md (R11). README prerequisites match minimumTypeScriptVersion; the changeset corrects the released importer re-check claim without rewriting CHANGELOG.md. * docs(plans): record the root rulings on OQ-P5, OQ-P8 and OQ-P9 * docs(plans): record the agentic-quality standing rule in R9, U9, U10 and U12 * fix(stryker-js-typescript-checker): render each mutant's diagnostics against its own text U2 of the TS7 checker plan (R14, KTD3). checkOne renders its mutant's diagnostics while that mutant's snapshot is current; MutantCheck carries rendered lines and Checker.cell no longer renders after the call. AE10 (per-mutant-position fixture) fails on the old code (line 16 instead of 20). The join property targets dogfood mutant 47892392410b616f. * feat(stryker-js-typescript-checker): report snapshot updates, re-splices and emit builds per check U3 of the TS7 checker plan (R3, KTD6). A per-call tally on the compiler runtime counts snapshot updates, parenthesized re-splices and TCE emit builds and sums emit time; check annotates them on typescript-checker.compiler.check with typescript.counts.schema_version 1. No new span names. No package test (Test Admission refused); the parity lane's zero-updates refusal catches a tally that is never set. * test(checker-parity): add an isolatedDeclarations corpus fixture U8 of the TS7 checker plan (R6, B1). A four-file project with isolatedDeclarations: true whose exported functions carry explicit return types and call each other across files, so the edit-location shortcut has a non-empty proof set on the parity lane. tsc -p (TS 7.0.2) passes; an unannotated export fails with TS9013. * feat(checker-parity): add the parity driver that runs one checker side and compares sides U9 of the TS7 checker plan (R3, R6-R10, KTD5, KTD6, KTD8, KTD9). Private package @systemfsoftware/stryker-checker-parity: 'run' discovers the R6 corpus, instruments a shard of it with the branch instrumenter, drives main's and the branch's checker worker over Plugin.CheckerRpcs, collects counts from the check spans through a local OTLP/HTTP receiver and writes versioned NDJSON; 'compare' merges every shard and fails on a verdict difference, a missing shard, or (when on) the shortcut-count and speed gates. Every record and failure carries schemaVersion 1, a closed reason code and a next action. The package's lint debt is cleared in the next commit; switch flags (--allow-local, --shortcut-gate, --speed-gate) no longer swallow the next positional. * ci: add the checker-parity lane U10 of the TS7 checker plan (R7, R9, KTD5, KTD8). checker-parity-changes decides whether a pull request reaches the corpus (every push to main runs); six checker-parity legs build main's checker at the merge-base and this commit's checker, then run both over one shard; checker-parity-report merges the shards and fails on any verdict difference or a missing shard. Every failure writes a ::error annotation and a step summary naming the reason code and the artifact to read. The shortcut-count and speed gates stay off until layer 4. * test(stryker-js-typescript-checker): clear the two lint errors layer 1 added The per-mutant-position fixture's padding variable is now _b (oxlint no-unused-vars); lines and the shift mutant's body span are unchanged. The reason-format law is deleted: the OwnRenderedDiagnostics law already pins every mutant's reason over the full CheckMutantsInput schema, and the duplicate declared a module-scope schema in a test file (schema-declaration-location). * build(checker-parity): satisfy the repo's source-resolution gritlint pack The isolatedDeclarations fixture moves to __fixtures__/, which gritlint and the package's oxlint config ignore: it is corpus input with its own standalone tsconfig, not package source. The driver's export map is now generated by tsdown through @systemfsoftware/tsdown-config's sourceExports, like every other tsdown package, so publishConfig.exports exists and carries no source condition. * refactor(checker-parity): clear the pure core's lint debt compare-sides.workflow.ts combines conditions with effect/Boolean every/some (complexity 1) and indexes verdicts and skipped projects through HashMap, keeping last-wins values and first-seen order. Parity.schema.ts exports schemas only; the shell applies S.fromJsonString(ParityLine) and Shard at its boundary. shard.ts hashes with @noble/hashes and offers dual signatures. The property test draws the production VerdictStatus schema. shard.property.test.ts is deleted: inShard is defined through shardOf, so its two partition laws restated the implementation, and its decode law copied the Shard refinement as its oracle. * refactor(checker-parity): run the driver shell on Effect platform services The imperative shell (run-side.ts, main.ts, otlp-receiver.ts, corpus.ts) now reads env through Config, files through FileSystem/Path, children through ChildProcessSpawner, serves OTLP on effect/http, and parses argv with effect/cli. Exit codes are mapped once at the runMain edge: 0 for parity or --help, 1 for a parity break, 2 for a driver failure. OTLP and shell-failure schemas move to Otlp.schema.ts and Shell.schema.ts; span counting is the pure span-counts.ts. compare-sides: normalising a reason now replaces the own-file position with a replacer function, so a file name holding a String.replace pattern ($&, $1) stays literal. The OwnFilePositionOnly law found it with the file name "\0\0$&". The three src/__tests__ files tested the hand-rolled parser, receiver and corpus helpers this replaces, and the test-layout rules forbid them there; their behaviour is internal to a private CLI whose only surface is the bin, so they are deleted, not moved. * ci(checker-parity): fail the lane report when change detection fails checker-parity-report ran only after checker-parity-changes succeeded, so a failed change detection skipped the report and the required context read as passing. The report now always runs, fails with code shard-incomplete when change detection failed or was cancelled, and still passes when the bot-push guard skipped it. * fix(checker-parity): key each side's verdict cache on its worker bundle A cached verdict is reused only when the stored identity (hash of the worker bundle directory that actually runs, the checker's program digest, and the mutant wires) equals the current one. Reuse is decided by the pure reuse-cached-verdicts workflow; the identity is a sidecar written after the verdicts, so a torn write re-checks. Replaces the hash of the checker's src tree, which missed bundled dependencies (plugin-interface, cli-contract, effect-cell-types). * fix(checker-parity): trigger the lane from the pnpm dependency closure The trigger subcommand runs the pure trigger-parity-lane workflow over the workspace closure pnpm reports for the checker and the driver, plus shared inputs (lockfile, workspace manifest, flake.lock, ci.yml, e2e testResources). Replaces the hand-maintained path regex in ci.yml. * fix(checker-parity): decide exit code, annotations and summary in the pure core report-parity-outcome maps a compare result or driver failure to exit code (0 holds, 1 broken, 2 driver failure), one ::error per violation kind, and the step summary; property laws pin each. ci.yml calls the trigger subcommand and drops the removed --main-source/--branch-source flags. * fix(checker-parity): read checker answers as CheckAnswer after the ignore-rule change Main (#262) widened the check RPC to CheckAnswer, whose ignored reason is unknown. The driver records it as the reason text so ignored verdicts still compare exactly. * fix(checker-parity): fail when no mutant was compared; resolve layer 2 review Review of #266 (ce-code-review mode:agent, 7 reviewers + validator): compare refuses a run that compared zero mutants (nothing-compared, exit 1); per-shard skips no longer list a project measured in another shard; the trigger step fails on a driver failure (pipefail); shard NDJSON is appended per project so a killed leg leaves its rows; no throw in the workflow or schema files; Shell.schema.ts renamed DriverFailure.schema.ts; PhaseLine declared once; the pure-core import ban covers every pure module. * feat(stryker-js-plugin-interface): add the provisional type-query port New ./type-query entry: TypeQuery service tag and the schema-version-1 request, response, answer and refusal shapes (plan U11, OQ-P8: port only, no Layer). Refusals carry a version literal and a next action per the root standing rule. * fix(checker-parity): mutate each source only in the project that owns it A workspace package's sources appear in the program of every project that imports it, so the first lane run mutated the instrumenter's files once per importing project (html-reporter spent 790 of its 835 s per side on them) and four shards hit the 2400 s timeout. The listing now keeps only files under the project's own directory: 581 -> 306 files over four sampled projects. * feat(stryker-js-typescript-checker): type-query implementation and lane gate, work in progress Work in progress, paused by root ruling to fix the layer 2 parity lane budget. Checker and parity packages typecheck, lint and pass their unit suites locally; TypeQueryLive not yet smoke-run against tsgo; no PR. * feat(checker-parity): pull request scope, per-file verdict cache, concurrent sides Work in progress on the root parity-budget ruling: PR legs check changed-file mutants (capped per file) plus a seeded drift sample from pr-scope.json; full corpus runs on push and workflow_dispatch; verdict cache is per file and saved with if: always() under run id + attempt + shard; legs write scope-<k>.json with the wall-time split. * refactor(checker-parity): assign drift legs in a workflow and seed order in the schema file Per ADR-0001's closed taxonomy and ADR-0002: the unsuffixed select-scope.ts is gone. The drift-leg assignment is a decision, so it is the assign-drift-legs workflow (mutation-graded, laws in its own suite); the seeded order is a pure operation over ScopeSettings, so it lives in Parity.schema.ts with its law in-source, and both the scope workflow and the run-side shell call it instead of private copies. * chore(checker-parity): drop the property seed file the in-source law run left behind * fix(checker-parity): match the mutant's own file in absolute diagnostic paths The checker writes absolute paths in diagnostics while each Verdict carries the repo-relative fileName, so the OQ-P1 own-file position normalisation anchored at the start of the line never fired in CI: runs 38036865136 and 38037569511 failed on 28 and 31 position-only differences. The own file now matches as a path suffix at a path boundary; the position laws draw absolute roots, and a sibling sharing the name's suffix is still refused. * fix(checker-parity): write the leg scope file when the leg starts Per the sub-conductor's l2 budget ruling (P3): scope-<k>.json was written only after the leg's last project, so a timed-out leg's shard-timeout annotation named a file that did not exist. The leg now writes a tagged LegStarted record (shard, scope, settings, the leg's projects, discovery time) before the first project and replaces it with the tagged LegScope when it finishes. compare decodes either and refuses a started-only leg with shard-incomplete naming the unfinished project count. * fix(stryker-js-typescript-checker): refuse NotAssignable under any generic callee, pin the type query against tsgo The generic-call guard read getResolvedSignature, which tsgo 7.0.2 returns already instantiated, so id('x') under declare function id<T>(x: T): T reported no type parameters and answered "" NotAssignable to "x" although id('') compiles. The CallArgument fact is now declaredGeneric: any declared signature of the callee (getSignaturesOfType) has type parameters. This corrects plan KTD11's condition, which named the resolved signature. tests/type-query-pins.integration.test.ts runs the real TypeQueryLive against tsgo: AE12-AE14 answers, contextual types unchanged by the appended probe statements, a file ending in a line comment, AE15 (the #4804 panic file refused server-crashed while the next file is answered and a concurrent check keeps its verdicts), and unsupported-version and not-in-project refusals. tests/type-query.differential.test.ts applies every answered candidate through CheckerRuntime and fails on a NotAssignable whose verdict is not CompileError; it went red on the reintroduced generic-call bug. * test(stryker-js-plugin-interface): state the type-query request's refusal boundary The generated laws prove TypeQuery's schemas round-trip but never present a value they must refuse. The request refuses a fractional version and an empty file list; the in-source law draws the unrefined base with boundary seeds and fails when either refinement is widened (checked: S.Array for files, S.Finite for version). * fix(stryker-js-typescript-checker): answer NotAssignable only where a declared type enforces the context CI run 38040840996 failed wrong-not-assignable twice: a 'true' inside '[[..., true] as const]' returned from an arrow passed to Option.match, and a literal inside S.Literals([...]). Both sites had a contextual type that was inference, not a constraint, so the mutant compiled. The KTD11 condition 'not an argument of an overloaded or generic call' only looked at the immediate parent. The handle now walks from the site to where its contextual type comes from (ContextOrigin): an annotated declaration, assignment, satisfies or annotated return is DeclaredContext; a call argument carries its callee's signature facts; anything else is UnenforcedContext, answered Unknown 'context-not-enforced' (new UnknownReason, last in KTD11 order). The walk climbs parentheses, array/object literals, spreads, conditional branches, ??/||/&&, 'as const' and unannotated arrow/function bodies. Plain 'as T' / <T> assertions stop as unenforced. Pins and the differential spec gain the two CI shapes plus a plain assertion, an annotated array element and a const-asserted tuple under an annotation; both were red against the previous handle. The lane's unknown-reason counts are now keyed by the port's UnknownReason. Probed with the built TypeQueryLive on the two CI sites: both now Unknown overloaded-or-generic-call. * fix(stryker-js-typescript-checker): resolve layer-3 review findings ce-code-review (report-only, base stryker/tsgo-checker-parity-lane) on ddda557; validator confirmed 13 of 14. - project-open-failed: a tsconfig tsgo cannot open used to refuse every file not-in-project (wrong next action); openServer now refuses the request project-open-failed when the snapshot holds no project for the tsconfig. Pinned (red before). - Handler reasons: a separate type-query-reasons fixture drives no-contextual-type, site-not-expression, error-type, instantiable-target and site-not-found through the real handler, plus a second answered file on the same server. - Differential oracle now fails when the query refuses the fixture instead of passing vacuously. - Probe slots are taken from the text being appended, not re-derived from the join layout. - answerTypeQuery decides its own AnswerDecision classes (shared type id, as check-mutants does); the handle maps them to the port's TypeAnswer. The constructor shim answer-type-query.schema.ts is gone: it exported functions from a *.schema.ts. - Harness schemas moved to tests/__fixtures__/*-law.fixture.ts (schema-declaration-location). - classify-candidate law renders the Parenthesized case as '(x)', not 'x)'. - checker-parity: the type-query driver moved out of run-side.ts into type-query-side.ts (run-side 1412 -> 1210 lines); TypeQueryRefusalReason is the union of the port's two refusal schemas. * feat(checker-parity): report each project's type-query answers, reasons and refusals Sub-conductor ruling 1 on #277: R18 stays (a project with no answers passes), and the step summary and summary JSON must show a dark project. - TypeQueryProjectShare gains unknownReasons (keyed by the port's UnknownReason), refusedFiles and refusedMutants. - The step summary gains a type-query line and one line per project: answered, NotAssignable, Unknown with its non-zero reasons, refused files and mutants, and 'no answers' when nothing was answered. - The type-query count schemas are NonNegativeInt (exported from Parity.schema.ts): the new summary law drew a negative Unknown count, a state no run can produce. - Plan U11: TypeQueryLive is a fixed Layer (ruling 2), amended in place as the ruling directs. * feat(stryker-js-typescript-checker): importer shortcut, check rounds and option (in progress) Edit-site facts, admit-importer-shortcut and plan-check-rounds workflows, the check-rounds runner over CheckSteps, the importerCheck option and a shortcut integration test that has not run yet. Paused per sub-conductor ruling to fix the layer-2 full-corpus lane first. * fix(checker-parity): make every full-corpus attempt advance Sub-conductor ruling on dispatch 38039482257 (legs 3 and 4 made no progress over two attempts). Evidence: both stuck legs stopped after packages/stryker-js-html-reporter in corpus order, so they sat in packages/stryker-js-instrumenter, which leg 1 measured at 1066 s for 309 mutants on main's side; the per-file verdict cache never saved a file that never finished. scope-3/4.json were missing because the dispatched head 819d513 predates LegStarted (d5007c5). - Cache slot is one check batch (<= 16 mutants of one file), stored when the batch finishes. - run --deadline: no batch starts after it, a running batch is interrupted at it; deferred work is written as Deferred lines and the leg exits 2 with code leg-deadline, so the rerun resumes at the first unchecked batch. - A batch interrupted on two attempts in a row is cached as UnitOverBudget and compare fails with unit-over-budget naming side, file and mutant ids. - Full-corpus legs are balanced by measured file cost (greedy LPT over test/checker-parity/file-costs.json, seeded from run 38039482257: 165 files, 6158 s); compare --costs-out writes the merged table. - Every batch logs a heartbeat line (elapsed, side, project, file, batch); the step summary names the leg's planned load. - CI passes --deadline (2160 s full, 210 s PR) inside the unchanged step budgets; the failure-pointer step defers to the driver's own annotation on exit 2. * feat(stryker-js-typescript-checker): finish the importer shortcut layer The integration test read spans after the OpenTelemetry layer shut down and reset the in-memory exporter, so every count came back empty. It now reads them inside the scope. AE7 edited the parameter, which is outside the function body, so it now edits the return statement. Checked against a planted defect: when admitImporterShortcut always takes the shortcut, AE2, AE3 and AE9 change from compileError to passed and the scenario fails. Adds the importerCheck changeset (minor). * feat(stryker-js-typescript-checker): typeQuery RPC, capability and function-body sites (WIP) Parked per sub-conductor ruling 2026-10-10 09:58 (#266 convergence round 2 first). Not yet reviewed; package gates pass locally: checker 39 files/255 tests, parity 17/245, plugin-interface laws 48. * fix(checker-parity): plan full-corpus legs from measured per-file cost Per sub-conductor ruling 2026-10-10 09:58 (#266 convergence round 2). The plan step packs contiguous 64-mutant blocks of each file into legs by first-fit-decreasing at 70% of the leg deadline, using a cost table merged from every finished run's leg artifacts (cache, bootstrapped from the newest earlier run on a miss). Matrix width comes from total measured cost. Verdict caches merge across legs. Replaces the hand-edited file-costs.json and the hash balancer. * fix(stryker-js-typescript-checker): name the served type-query versions from the schema The unsupported-version refusal referenced a deleted constant and threw instead of refusing; a version 3 request now gets the refusal, pinned in type-query-pins. typeQueryServingOf moves out of TypeQuery.schema.ts into type-query-serving.ts with a pipeable signature; TYPE_QUERY_VERSIONS is gone (TypeQueryVersion.literals). Plugin-interface oxlint and lint:tsgo now exit 0. * refactor(checker-parity): hold blocks.ts and file-costs.ts to the pure core Both are effect-only pure modules, so they join pureCore in oxlint.config.ts (the package's convention for unsuffixed pure modules, beside shard.ts, span-counts.ts, corpus.ts); plan-legs.defaults.ts carried a suffix outside any convention and its one constant moves to the --fill flag default. * fix(checker-parity): plan from every run's measurements, conservatively Sub-conductor ruling 2026-10-10 10:58 (#266): the proof dispatch 38061043178 bootstrapped its costs from one PR-scope run (31 files) and planned 5 legs. Every FileRate and ProjectOverhead now carries the run that measured it, and mergeCosts keeps the newest measurement per file, so fold order is irrelevant and a narrower run never drops files it did not touch (cost table v3, cache prefix checker-parity-costs-v3-). The plan job folds every recent completed ci.yml run with leg artifacts (workflow_dispatch, push to main, this branch; every attempt) and saves the folded table for the report job. An unmeasured file is estimated at its project's p90 per-mutant rate, else the corpus p90. Legs are a greedy contiguous segmentation of the (project, file, block) sequence, so adding a file never lowers the width; full-corpus plans use 16-mutant blocks. The step summary shows width, each leg's estimate against the 70% budget, and measured vs estimated files. * fix(stryker-js-cli-contract): name the rpc.typeQuery span The checker RPC group gained typeQuery, and the span-taxonomy law (every protocol method has an rpc.<method> span) refuted it. Review finding (learnings, api-contract, adversarial; P1). * refactor(stryker-js-plugin-interface)!: serve the type-query port as the root TypeQuery namespace The ./type-query subpath is gone; every caller imports the TypeQuery namespace from the package root, the way Checker and Plugin are reached. The checker RPC group carries the type-query classes, and a consumer's declaration emit can name them only through a root export. * fix(stryker-js): build against the checker RPC group with typeQuery The engine's checker handle now names only the RPCs it calls (check, group, digest), so its declaration no longer needs the type-query answer classes (TS2883). The in-repo fake checkers answer capabilities with no type-query versions and refuse every typeQuery with unsupported-version. The RPC changeset is major: every checker worker must answer the two new RPCs (BREAK-1). Review findings: learnings/api-contract P1, changeset bump P2. * fix(stryker-js-typescript-checker): check an edited file in the project that owns it Under project references every referencing program holds the referenced project's sources but reports no semantic errors for them. projectOfFile returned the first program holding the file, so on the enterprise fixture the importer shortcut read gates.ts diagnostics from analytics, saw none, and passed real compile errors; the TCE emit for those false passes then wrote core/src/*.js into the fixture, which turned check's oxlint red. The project whose rootFiles list the file now answers first. Regression: importer-shortcut scenario over a two-project fixture (app references lib) fails before, passes after. e2e-core placement suite 7/7 with no stray files. * fix(stryker-js-typescript-checker): serialize type queries per tsgo server Two queries on one tsconfig interleaved the probe update and the reads: query 1 read a program holding query 2's probe text and answered candidate-not-found. Each server now has a Semaphore(1) held for the whole per-file probe; a fiber that waited on a server another fiber discarded takes the current one; a crash discards only its own server; close is bounded by a 1 s grace like the checker's. Regression: metamorphic relation in type-query.differential.test.ts (concurrent per-site queries answer as the same queries one at a time) failed before (seed 767247522: Unknown candidate-not-found vs overloaded-or-generic-call) and passes after. Review #277 P2 rows 4 and 5. * test(checker-parity): pin the type-query recall counters compileErrorTotal counts branch-side compileError verdicts only, and compileErrorAnsweredNotAssignable counts those whose own project answered NotAssignable. The law fails when the side filter is dropped or the project join is reduced to the mutant id. Review #277 P2 row 6. * test(checker-parity): draw the recall law's answer split in the generator 745bce9 failed oxlint (no-nested-quantification). The law now draws mutants answered NotAssignable and mutants answered Assignable, Unknown or nothing as two arrays, builds lines in a module helper, and counts branch compileErrors per group. It still fails when the side filter is dropped or the project join is reduced to the mutant id. * refactor(checker-parity): type the function-body mutator name against the stock catalog A renamed stock mutator now fails to compile (TS2820) instead of silently turning every function-body site into an expression site. Review #277 P3 row 10. * test(stryker-js-typescript-checker): assert the served type-query version The worker scenario now carries the version out of each serving answer and checks versions 1 and 2, served and refused. It fails when typeQueryServingOf answers the wrong version. Review #277 P3 row 11. * refactor(stryker-js-plugin-interface)!: require TypeQuerySite.kind A site's kind was an optional key whose absence meant expression, resolved at a distance in the checker (site.kind ?? 'expression') and written by presence in the lane. Every site now names its kind; the checker reads it directly and the lane writes it for every site. Review #277 P3 row 9 (schema-laws: tagged unions over state by presence). * test(stryker-js-typescript-checker): refuse an unknown importerCheck value A scenario in importer-shortcut.integration.test.ts checks the annotated body edit under importerCheck 'sometimes', 1, 'always', 'location-rule' and no option, on real tsgo. An unknown value must fail the check with the invalid-checker-options refusal naming the mutant; 'always' takes no shortcut; the rule, set or defaulted, takes one. It closes the planted option-schema gap on #286: emptying the importerCheck struct lets an unknown value decode, so the check no longer refuses it. * fix(stryker-js-typescript-checker): keep the importer shortcut off a body's closing brace and off files with syntax errors Edit-site facts move into edit-site.schema.ts beside their schemas (ADR-0002); edit-site.ts is gone. The scan stops descending into nodes that end before the span, check() keeps only the last round as lastMutants, and projectsOf is no longer exported. One fixture and one scenario row per Soundness Rule guard (declare global, require, typeof import, non-module file, removed terminator, syntax error) plus shared-round solo fallback and unheld-member scenarios. Review #286 rows 2, 5, 7, 10, 11, R1, R3 and the shared-round gaps. * test(stryker-js-typescript-checker): pin one-update two-file snapshots and the importerCheck digest The U6 pin drives typescript/unstable/async over a virtual file system: one updateSnapshot serves both changed texts and re-checks a rewritten file. The digest scenario shows importerCheck moves the program digest. Review #286 gaps. * fix(checker-parity): judge speed only on projects checked fresh on both sides speedViolationsFor compares projects with a non-cached check on main and branch; with none, parity holds and warns speed-gate-unmeasured with a next action in the step summary and an Actions annotation. Review #286 row 3 and R4. * chore(changeset): record the stryker-js RPC-group touch as a none intent #267's release consumed the intents that covered stryker-js's Checker.handle.ts typing change (a436092), so Changeset Check on 6f99d56 (run 38092173764) found stryker-js moved with no intent. The change is type-only: the engine's client is narrowed to check, group and digest. * fix(checker-parity): check the isolatedDeclarations fixture on every pull-request run checker-parity-report on fef35ce (run 38092982281) failed zero-shortcuts isolatedDeclarations because no leg checked the fixture: PR scope checks changed files plus 6 seeded drift projects, the fixture ranks 22nd in the seeded order, and once #266 landed on main its files were no longer changed against the merge base. The shortcut gate judges that fixture on every run, so its files now count as changed on every pull-request leg (checkedOnEveryPullRequest in corpus.ts). No guard refused its sites. * test(checker-parity): the shortcut gate fixture takes the importer shortcut Real tsgo checks every stock mutant of the isolatedDeclarations fixture with importerCheck location-rule and always: the shortcut decides at least one mutant and every verdict matches. Planting admitImporterShortcut to refuse every site fails it (shortcutTaken false). * fix(checker-parity): keep the isolatedDeclarations fixture on every pull-request leg checker-parity-report on ad5769a (run 38097541662) still failed zero-shortcuts for isolatedDeclarations: 8e4c82b made changedOnThisLeg select every fixture file, but processProject first asks inScopeOf, which only admits a project that samples on the leg or has a changed file in its directory. The fixture did neither once main held it, so it never reached the file filter (no leg's scope-k.json lists it). One pure rule now decides both questions: checkedOnLeg and projectOnLeg in corpus.ts over a ProjectLeg schema (Parity.schema.ts), and run-side builds a ProjectLeg once per project. Laws in corpus.workflow.property.test.ts draw ProjectLeg: a fixture file checked on its shard puts the fixture project on the leg, fixture files are checked on their shard whatever the pull request changed, and an unsampled project with no change is off the leg. Removing the fixture clause from projectOnLeg (the shipped bug) or from checkedOnLeg fails them. * fix(checker-parity): count in the summary exactly the projects the speed gate compares The summary's measuredProjectCount and the main/branch timing totals used measuredOnBoth (any fresh phase line on both sides), while the speed gate compares checkedOnBoth (a fresh check call on both sides). A run could report projects as measured that the gate never compared. summaryOf now uses checkedOnBoth, and measuredOnBoth is deleted. The law SummaryMeasuredCount pins the count and totals to the compared set; it fails when the old set is restored. * refactor(stryker-js-typescript-checker): move round and shortcut decisions into their schema files Ruling 1 (ADR-0002): CheckRound.schema.ts holds SoloRound, SharedRound and roundOfLayer, which takes a non-empty layer, so an empty round cannot be built (ruling 9). ImporterShortcut.schema.ts holds the decision union with takesShortcut and fallbackKeyOf. The effect-schema lint (schema-file-exports-schemas-only) refuses the counting helper whose parameter is an array of optional decisions, so the span counts stay in check-rounds.ts. RoundCandidate ids are branded MutantId. A check call that names one mutant twice is refused by the PlanCheckRoundsCommand schema and fails with reason duplicate-mutant-ids and a next action (BREAK-1, major changeset), not a defect. The importer-shortcut feature pins the refusal. * fix(stryker-js-typescript-checker): read file facts without a fake span, and stop the edit-site scan at the edit Ruling 7: FileFacts (typescriptModule, declaresGlobal) is its own schema read by fileFactsOf, and the handle's declare-global check uses it in place of an end-of-file span. Ruling 6: the scan returns at the first node that starts after the edit, and a counter of inspected nodes is checked against an unpruned model. Ruling 2: in-file laws state that an admitted span stops before the closing brace and that widening an edit never turns a refusal into an admit. Planting the unpruned scan or a brace-inclusive editable end fails these laws. * fix(stryker-js-typescript-checker): never take the importer shortcut for a constructor body An unannotated class property takes its type from the constructor's assignments, so a constructor body edit can change what an importer sees while the edited file stays clean. Real tsgo 7.0.2: 'this.start = 1' to "'one'" leaves constructed.ts clean and fails consumer.ts with TS2322. Constructors leave SIGNATURE_FREE_KINDS; the rule scenario gains a constructor row that fails if constructors are admitted again (review cycle 3, finding 2). * refactor(stryker-js-typescript-checker): give decided one exported home check-rounds.ts and type-query.handle.ts each carried a private copy of decided; both now import the one ts-compiler.handle.ts exports (ADR-0002: each pure operation has one home; review cycle 3, finding 6). * test(stryker-js-typescript-checker): laws for the shortcut schema operations and refusals takesShortcut, fallbackKeyOf and fileFactsOf each get an in-source law (finding 3); fileFactsOf runs real tsgo over drawn extensions, export and declare-global choices. SharedRound ids, MutantBound, distinct round candidates and EditSpan state what they refuse against named domain predicates (finding 7). Each law fails on a planted change to its subject. * fix(stryker-js-typescript-checker): refuse the importer shortcut for a file another project compiles Review cycle 3, finding 5. The shortcut read the mutated file's errors from its owning project only. A second open project that includes or imports the same file checks it under its own compiler options and can reject an edit the owner accepts, so the shortcut passed a mutant that 'always' rejects. Probed on tsgo 7.0.2: a project that reaches the owner through references, directly or through another project, holds the file but reports none of its diagnostics; a project that includes or imports it without a reference path reports them under its own options. So the refusal counts only holders with no reference path to the owner. A literal "more than one holder" refusal would turn the shortcut off for every project-references monorepo. - The tsconfig walk records each config's references; otherCompilersOf (Tsconfig.schema.ts) is the pure count, with a transitive-closure law. - DecideImporterShortcutCommand carries compiledElsewhere; the new shared-file clause (tree original) reports under its own fallback key. - importer-shortcut-overlap: two referenced projects include one file, one with noImplicitReturns and one with noUncheckedIndexedAccess; each edit is rejected by one of them only, so the scenario fails whichever owns the file. Before the fix the rule run passed one edit that 'always' rejected. * test(stryker-js-typescript-checker): pin the unread fallback count for a file no project includes Review cycle 3 gap: nothing asserted typescript.importer_shortcut.fallback.unread.count. A mutant in a file the tsconfig excludes is read from disk but held by no project, so its edit-site facts are never read; both modes pass it with nothing blamed and only the rule run counts it as unread. Dropping the unread key fails the scenario. * test(stryker-js-typescript-checker): check two calls on one checker so each starts from the original files Review cycle 3 gap: no scenario sent a second check call to the same checker. The harness now runs a list of calls on one checker. The first call checks an edit that retypes a function the consumer reads; the second checks an edit in the consumer, which must pass. Skipping the reset of the previous call's last round, or leaving its files out of the snapshot refresh, fails the scenario. * fix(stryker-js-typescript-checker): give invalid options and duplicate ids their own tagged errors InvalidCheckerOptions and DuplicateMutantIds replace the two CompilerFailed reasons this layer added (CONST-D2, review round 3 P1). Each carries its code and next action in the message; the scenarios assert both. Raising the duplicate refusal as InvalidCheckerOptions fails the duplicate scenario. * refactor(stryker-js-typescript-checker): move check-rounds decisions into a workflow and schema operations Round-3 ruling Q1 step 1 and Q2. Each decision check-rounds.ts made inline now lives where the mutation set and a law reach it: - concludeSharedRound (new workflow): a shared round is kept only when every member took the shortcut, else redone one mutant at a time. - importerCheckOf / readsEditSites (Checker.schema.ts): the location-rule default and which mode reads edit sites. - shortcutCountsOf (ImporterShortcut.schema.ts): per-outcome shortcut and fallback counts, zero under 'always'. - ShortcutClause / ShortcutTree move beside the decision they tag. Planted bugs: default flipped to 'always', counts under 'always', unread folded into none each fail an in-source law; every->some in concludeSharedRound fails the shared-round integration scenario (the vacuity gate refused a redo law, so it was deleted rather than reshaped). * refactor(stryker-js-typescript-checker): run the check rounds on the compiler handle and delete check-rounds.ts Round-3 ruling Q1 step 2. With every decision moved into a workflow or schema operation (previous commit), what is left of check-rounds.ts is sequencing over the handle's lastMutants and snapshot state, so it moves back into ts-compiler.handle.ts and the unsuffixed module is deleted (no shim). check keeps its SpanTaxonomy span (typescriptCheckerCompilerCheck), as every Effect.fn in the repo names a taxonomy span. The helpers only the round loop used (CheckSteps, checkStepsOf, classifyBatchTce, annotateDiagnosticSample, CompilerState, EditTree, emptyTally, runtimeOf, TSCompilerRuntime) are no longer exported. * test(stryker-js-typescript-checker): pin the importer shortcut for a set accessor body Round-3 finding P2 (testing). stored.ts pairs an annotated getter with a set accessor; the rule scenario gains a row whose edit sits in the setter body. It takes the shortcut and gets the same verdict under both importerCheck values. Dropping SET_ACCESSOR from SIGNATURE_FREE_KINDS fails the scenario. * refactor(stryker-js-typescript-checker): group mutant ids by file in one schema operation fileIdsOf in FileIds.schema.ts groups ids by file, files in first-seen order and ids in input order. groupMutants and planCheckRounds both use it instead of two copies of the same accumulator. Law: ∀m_FileIdsOf_≡FilesInFirstSeenOrderEachHoldingItsIdsInInputOrder; planting prepend for append fails it. * refactor(stryker-js-typescript-checker): pick a mutant's diagnostics from its decision in one place decidedOutcomeOf reads own-file errors when the decision takes the importer shortcut and every program's errors otherwise; the solo path and kept shared rounds both use it. Swapping its branches fails three importer-shortcut scenarios. * fix(stryker-js-instrumenter): render an error as errno only when it names its syscall An error with a string code but no syscall (Node ERR_* errors, the checker's InvalidCheckerOptions and DuplicateMutantIds) rendered as '<name>: <code> (undefined) <stack>'. It now renders as '<name>: <message>' and its stack. Law ∀e_ErrorTextOf_≡ErrnoFormatOnlyWhenTheErrorNamesItsSyscall; the checker refusal scenarios assert the rendered cause starts with '<Tag>: typescript checker (<code>): ' and holds the next action. Planting the old predicate fails the law and both refusal scenarios. * fix(stryker-js-typescript-checker): refuse unknown typescriptChecker keys and split the speed gate out of compare-sides Review fixes for the full-path ce-code-review of 8457ce7 (run 20261013-173047-a6b753e3), validator-confirmed findings 1, 3 and 6: - 1 (P1): compare-sides.workflow.property.test.ts crossed 1000 lines (849 -> 1033) in this diff. The speed gate moves into its own speed-gate.workflow.ts with its six laws in speed-gate.workflow.property.test.ts; compare-sides calls it and reads the decision. Same violations, warnings and summary values; 35 laws before and after. - 3 (P2): the typescriptChecker block was decoded with the default excess-property policy, so a misspelled key (importercheck) was silently ignored while the published JSON schema refuses it. The block is now decoded on its own with onExcessProperty: 'error' and fails with InvalidCheckerOptions; the option scenario gains a misspelled-key and an extra-key row, red under the old decode. - 6 (P3): the isolatedDeclarations zero-shortcuts nextAction told the operator to exercise an importer re-check path, the opposite of what the gate checks. --------- Co-authored-by: Ryan Lee <drdgvhbh@gmail.com>
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
CI now runs main's TypeScript checker and this branch's checker over the same mutants and fails the pull request on any verdict difference. Before this, a checker change could only be judged by a full mutation run on main, after merge. This is the evidence lane the TypeScript 7 checker work needs: layer 4's importer shortcut and batching land behind it and must keep every verdict equal to main's.
This is layer 2 of 4 of the TypeScript 7 native checker stack (base: layer 1,
stryker/tsgo-checker, #264). Layer 3 adds the type-query port and the lane's wrong-not-assignable gate; layer 4 adds the edit-location shortcut and turns on the shortcut-count and speed gates. Plan:docs/plans/2026-10-09-2106-perf-typescript-checker-tsgo-native-plan.md(U8, U9, U10).Lane budget (root parity-budget ruling)
The first full-corpus PR run (38026090265, head
b91f2c66a) put every one of 6 legs into the 2400 s shell timeout with 2 to 7 of 37 projects finished, and no verdict cache was ever saved. Step 0 split: setup and both builds 50 to 95 s per leg, the run step 97% of every leg; check cost spans 0.07 s per mutant (median) to 4.1 s per mutant in files whose program pulls the repo's sources; projects ran one at a time, main then branch.The lane is now scoped by event:
test/checker-parity/pr-scope.json(seedchecker-parity-pr-1, 6 drift projects, 4 mutants each, at most 6 per changed file); the step summary prints seed and sizes. A PR touching no checker, instrumenter, tsgo or driver input skips the lane and the report passes withnot-applicable.mainandworkflow_dispatch: the full corpus. The plan job folds the leg measurements of every recentci.ymlrun into the cost table, estimates each file (measured rate, else its project's p90 per-mutant rate, else the corpus p90, else 10 s per mutant), and packs 16-mutant blocks into as many legs as the 70% budget needs (36 min step deadline × 0.7 = 1512 s per leg).if: always()underchecker-parity-<k>-<run_id>-<attempt>and restored by thechecker-parity-<k>-prefix, so a rerun of a failed leg resumes. Both sides check concurrently.nothing-comparedare unchanged.819d513e0(lane 286 s)Projects not measured on the green PR run (8 of 10 measured); neither is a failure:
test/e2e/testResources/typescript-checker-fixture/tsconfig.references.json: no mutant of this PR's changed files or of the drift sample lies in it ("no mutants in scope on this leg"). The full-corpus run (push tomain,workflow_dispatch) checks every source file it owns.test/e2e/testResources/typescript-checker-fixture/non-existent-tsconfig.json: the e2e fixture names a tsconfig that does not exist on purpose, so R6 skips and lists it on every run ("tsconfig not found"). The full-corpus run does not cover it either; there is nothing to compile.Lane =
checker-parity-changesstart tochecker-parity-reportend. Change detection 71 to 75 s, report 72 to 74 s. The cold and warm runs measured the lane before the normalisation fix below and ended red in compare (28 and 31 position-only differences), so their times stand but their verdict does not; 38038127731 is the green head (ParityHolds, 164 mutants per side, 8 of 10 projects measured). Warm is barely faster than cold because each push edits this PR's owntest/checker-paritysources, which are corpus files and are checked fresh by design, and because the run step is 25 to 52 s of a 100 to 142 s leg: setup is about 30 s and the two builds 35 to 50 s.Head
d5007c565(P3 fix): CI 38039704581 green, every job. Lane 272 s; legs 1 to 6 took 113, 88, 114, 112, 121 and 130 s; change detection 68 s, report 70 s.What changes
tsconfig.app.json, everytsconfigFilenamed by a checker-enabledtest/e2e/testResources/*/stryker*.config.ts, plus a newisolatedDeclarationsfixture (test/checker-parity/__fixtures__/isolated-declarations/). Each source is mutated only in the project that owns it.@systemfsoftware/stryker-checker-parity.run --scope pr|fulldrives each side's shipped worker bundle (dist/main.mjs) overPlugin.CheckerRpcs, reads the check-span counts from layer 1 through a local OTLP/HTTP receiver, and writes NDJSON plusscope-<k>.json. Pure decisions:select-scope.workflow.ts(PR scope),assign-drift-legs.workflow.ts(drift project to leg),reuse-cached-verdicts.workflow.ts,compare-sides.workflow.ts,report-parity-outcome.workflow.ts; the seeded order lives inParity.schema.ts(ADR-0002).src/run-side.tsandsrc/main.tsstay where they are. Moving them into adrivers//bin/folder was ruled out (a technical-kind folder, with no groundeddrivers/convention, and about 2,250 lines of churn across every layer for no behaviour). The test tool's file naming goes to the repo-wide placement work.checker-parity-changesrunstrigger(pnpm dependency closure of the checker and driver, the e2e fixtures, the lockfile,ci.yml) and outputsrunandmode. Sixchecker-parity (k)legs;checker-parity-reportis the one context to require.Verdictcarries the repo-relativefileName; the own-file match is now a path suffix at a path boundary (819d513e0). Replayed over the two red runs' real rows: 28 → 0 and 31 → 0 violations.How an agent consumes this
::errorannotation and a$GITHUB_STEP_SUMMARYblock with a reason code and next action:shard-timeout/shard-failedper leg,cache-miss(warning),shard-incompletein the report. Artifacts:checker-parity-<run_id>-<k>(filesshard-<k>.ndjson,scope-<k>.json),checker-parity-summary-<run_id>(checker-parity-summary.json)._tag+schemaVersion: 1):Verdict,CheckCall,GroupCall,DigestCall,Counts,ProjectBootFailed,ProjectSkipped,CacheEntry,TelemetryMissing;LegStarted(written when a leg begins) thenLegScope(when it finishes) inscope-<k>.json. Schema:test/checker-parity/src/Parity.schema.ts.ParityHoldsorParityBrokenwithviolations(first 50displayed,omittedCount), each withcodeandnextAction:verdict-mismatch,boot-asymmetry,zero-snapshot-updates,telemetry-missing,nothing-compared; from layer 4zero-shortcuts,slower-than-main.<code>: <reason>+next action:on stderr; codesusage-error,refused-outside-ci,shard-incomplete,decode-failed,worker-boot-failed,telemetry-missing,telemetry-version-unsupported,rpc-failed,io-failed). Local runs are refused unlessCI=trueor--allow-local.Local verification
test/checker-parityat819d513e0:tsc -bexit 0;oxlint .0 errors;lint:tsgo0 errors;vitest run15 files, 178 tests passed;buildok.rank % shards,driftProjectsbound, single-project collapse),seededOrderpermutation, per-file cap and per-project cap inselect-scope, own-file position law against the pre-fix anchor.dist/main.mjs:trigger --base <merge-base>→run=true reason=closure-changed mode=pr; withGITHUB_EVENT_NAME=workflow_dispatch→run=true reason=full-corpus mode=full.Review
ce-code-review(report-only, focused: correctness in-context plus a local adversarial read; no cross-model peer, none installed) on base layer 1 + main (3eb05925a) to819d513e0, 41 files:corpus.tskeeps program files inside the project directory), so such a file is never mutated on the full corpus either; the cost is a wasted trigger. Kept as P3.(1,1)sentinel": dismissed. The sentinel (ts-compiler.handle.ts:1766) is an own-file(line,col), which R7/OQ-P1 exempts; the plan already records the unbounded exemption and that a magnitude check needs GATE1 approval (plan Risks).819d513e0only lets the rule match absolute paths.isolatedDeclarationsfixture out of PR scope": binding on layer 4 (sub-conductor ruling). With--shortcut-gateon, R8's fixture-alone zero-count refusal would fail every PR whose scope misses the fixture; layer 4 must include the fixture in PR scope when it turns the gate on.shard-incompletewith next action when a leg is cancelled.scope-<k>.jsonwritten only at leg end: fixed ind5007c565(sub-conductor ruling). The leg writes a taggedLegStartedrecord (shard, scope, settings, the leg's projects, discovery time) before its first project and replaces it with the taggedLegScopewhen it finishes;comparerefuses a started-only leg withshard-incomplete. Smoke on the built driver with run 38038127731's leg 1 rows:LegScope→ exit 0;LegStarted→ exit 2shard-incomplete ... started 2 project(s) but never finished; an untagged file → exit 2.Review, round 2 (
ce-code-review mode:agent base:stryker/tsgo-checker, report-only)Head
9e111409a, seven reviewers (correctness, adversarial, project-standards, testing, maintainability, reliability, learnings) plus the validator; verdict "Not ready". The reviewers ran on the host model: no cross-model reviewer is installed here (recorded as a finding, per ruling). Every finding is fixed inb91f2c66aunless noted:throwin the pure workflowcompare-sides.workflow.tsthrowin any*.workflow.tsor*.schema.tsfile.throwinsideParity.schema.tsShell.schema.ts)DriverFailure.schema.ts.pipefail, and9ea37fce3fails the report when change detection fails (the ruling's P1 "report-skipped-reads-as-pass", accepted).comparerefuses a run that compared zero mutants withnothing-compared(exit 1).PhaseLineis declared once, inParity.schema.ts.Sub-conductor rulings on this round:
a218cf71b). The trigger is the checker's workspace dependency closure from pnpm's own graph (0d5beb237). Both decisions are pure and have laws (reuse-cached-verdicts,trigger-parity-lane).41118a4b9. The outcome → exit code, annotations and summary decision is pure core, with a property law inreport-parity-outcome.workflow.property.test.ts: broken parity gives exit 1 and one bounded::errorper mismatch kind; holding parity gives 0; a driver failure gives 2 with a reason code and next action.82f343af4,5ed4efd87,6d1ee1dfa,f9cc2221f): accepted by root on proof run 38063951675 (below).CI on the exact head
905187ef4(mergesorigin/main56426ca94, which is #264 squashed): CI run 38086327338 is 15/15 green, includingcheck, the five e2e legs, all sixchecker-paritylegs andchecker-parity-report. Nix, Changeset Check and Commitlint are green.bench · test/e2e/testResources/enterprise-monorepo-fixturefailssetup-timed-out(run 38086327459). It fails the same way on every layer of this stack and on other PRs, root's fix: restore main's build after the #276/#283 and #282/#274 merges #295 included; main's CI does not run Bench. The other four bench legs are green.checkon the previous headdbe8e5777(run 38081142087) was red onstryker-jstests/incremental-reuse.integration.test.ts› "A refused re-run starts a mutant with its previous killing test": the scenario's second run exited with a failure (runSucceeded: false) right after "Found 2 of 5 file(s) to be mutated". The cause is not in this layer.stryker-js, its instrumenter, plugin interface and vitest runner are byte-identical to main and to fix(stryker-js-typescript-checker): own error positions, check counts #264's head, and this layer'sci.ymlonly adds jobs. Six concurrent local copies of the scenario ondbe8e5777gave one failure of a different kind (the third run's killed verdicts and killers changed); eight more passed. It is a load-sensitive scenario in the engine's incremental reuse. Its assertion drops the second run's exit cause, so the CI failure mode cannot be read from the log. On905187ef4thestryker-jstest task was a turbo cache hit, so this greencheckdid not re-run that scenario.Full corpus
Proof run of the cost planner on the main path:
workflow_dispatch38063951675 onf9cc2221f, started cold, converged in 1 attempt.checker-parity-reportThe only red jobs in that run are the 5 e2e legs (exit 124), the same failure main has today (Stream K). The previous full-corpus run, 38049913120, needed two attempts and most of its 6 legs timed out.
This stack shares
packages/stryker-js-typescript-checker/README.md,tests/check-mutants.integration.test.ts(layer 1) andpnpm-lock.yamlwith Stream A's #261. Whichever lands second merges the other.Dogfood mutant ids: none.
test/checker-parityis outside every dogfood mutate glob. No changeset: the package is private.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.