Skip to content

ci(mutation): run mutation testing on main through systemfsoftware's reusable workflow - #5

Draft
systemfsoftware-maker wants to merge 16 commits into
mainfrom
feat/mutation
Draft

systemfsoftware-maker wants to merge 16 commits into
mainfrom
feat/mutation

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Mutation testing now runs on @systemfsoftware/api-extractor through systemfsoftware's shared reusable-mutation.yml, nightly on main and by manual dispatch on main, never per push or per pull request (root ruling, 2026-10-13). This PR is the first hook-mode caller of that workflow. The caller is wired, but no run has produced a graded score yet, so the PR stays a draft.

Status (2026-10-13, head b86386f): draft. Six blockers stand between this head and merge. The plan's Dependencies / Blockers section holds the evidence for each.

  • B1, systemfsoftware #737 (open, draft, 6db58a01): removes the mutation-job.ts timeout wrapper that killed run 2 at 960 s, and widens stryker-config's peer range.
  • B2, systemfsoftware #744 (open, 26442627): cuts seeded replays. Run 2's dry run alone took 661 s, above the 600 s job budget.
  • B3, systemfsoftware #733 (open, 78826f9f): gate --baseline fails a run on a below-break score. At today's pin 02bf965 a [FAIL] row still concludes success, so the lane cannot fail on a survivor (CONST-T3). #733 requires packages/api-extractor/mutation-baseline.json, which lands on main in its own layer first.
  • B4, survivors: unknown until a graded run. They are killed in a separate layer on main.
  • B5, systemfsoftware #747 (open, e2e1e71a): refuses a package that sets its own mutate, and stops exporting shardMutate. If the re-pin includes it, stryker.config.ts drops its mutate (plan KTD7).
  • B6, generated-law retirement: no systemfsoftware PR retires generated schema laws yet, and main at 763fd663 (#746) does not contain one. The re-pin waits for it.

After the blockers: the re-pin past #733 and B6 (U5) removes the retired generated-law wiring and any in-source decode or verdict refusal property, and adds no replacement test (root ruling, plan KTD11). Then one proof run (U6) on feat/mutation, started by a temporary push trigger that the next commit removes, must conclude success with a graded row at or above 100 and every job at or under 600 s.

Plan: docs/plans/2026-10-13-0641-ci-mutation-caller-plan.md. It supersedes docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md, which superseded docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md. Both earlier plans stay as the contracts their executed units were verified against.

Stacking waiver (operator, 2026-10-13): this PR and #7 (feat/front-ends) both change README.md, pnpm-lock.yaml and pnpm-workspace.yaml, so OP13b's overlap check fails for feat/mutation. The overlap predates b86386f (05f44c0 already had it). The operator waived the check for the b86386f push. Restacking is still open: whichever of #5 and #7 merges second must merge main and resolve those three files.

Caller

  • Triggers: schedule (cron 47 14 * * *) and workflow_dispatch (mutation.yml:9-11). The mutation job runs only when github.ref == 'refs/heads/main' (:22), so a dispatch on any other ref skips it. There is no push, pull_request or merge_group trigger. 14:47 UTC sits inside the 14:10-15:10 window that systemfsoftware #733 measured as the org's quietest hour, 30 minutes after systemfsoftware's own nightly run starts.
  • Concurrency: group mutation-${{ github.run_id }}, cancel-in-progress: false (:14-15). Every run has its own group, so a dispatch never replaces the nightly run and the nightly run never replaces a dispatch. The shared caches are keyed by run id.
  • Cost header (mutation.yml:1-5): at least 25 runner-min/day for the one nightly run, plus the same for each dispatch. A push starts no run, so the 7-day merge rate (5 first-parent commits on main, 2026-10-06T06:00Z to 2026-10-13T06:00Z) does not price it. Per-run time comes from run 38120830653 (1,469 job-seconds over six jobs) and is a floor, because Stryker was killed 960 s in. The proof run's measurement replaces it.
  • Inputs: target-seconds: 480, max-jobs: 20, timeout-minutes: 60, report-timeout-minutes: 60. sfs-deps-installable keeps its default, .#sfs-deps.
  • Permissions: contents: read at the top. The mutation job gets contents: read and actions: write, which the shared report job requests.
  • Mutate set: unchanged from main: src/**/*.workflow.ts and src/**/*.schema.ts (packages/api-extractor/stryker.config.ts:20), the verdict-decision code AGENTS.md:12 names. B5 can change this at the re-pin.
  • Shipped package outcome: nothing changes under packages/, apps/ or .changeset/, so the PR carries no changeset intent and releases nothing. The package tarball ships only dist and is byte-identical to the one the @systemfsoftware/api-extractor@v0.0.0 tag records. On merge, Release owes nothing because the v0.0.0 tag already exists for that tarball (pnpm-release-management cycle.ts:22).
  • Docs: AGENTS.md:24-26 and the README row restate the cadence.

Pins

  • systemfsoftware 02bf9653b1ca2b0a2951baf2743bb3f70d19fc77 (#735, O1) for reusable-mutation.yml. All five systemfsoftware runs on that commit concluded success before it was pinned.
    • It does not include #733 (B3). A pin past #733 needs packages/api-extractor/mutation-baseline.json on main first.
  • stryker-js-effect 9e81669b84711a9635eaa97b17dd13efea9f0c8c for the stryker-published flake input. Every stryker tarball comes from that one commit. .sfs-deps, the overrides in pnpm-workspace.yaml and pnpm-lock.yaml are regenerated, and the lockfile diff touches only stryker packages.
Tarball Before (27b1075, stryker-js v18.1.0) After (9e81669)
stryker-js 18.1.0 19.0.0
stryker-ignorer-effect-schema-declarations 0.2.1 0.3.0
stryker-js-instrumenter 12.1.2 13.0.0
stryker-js-plugin-interface 15.0.0 16.0.0
stryker-js-cli-contract 0.4.0 0.5.0
stryker-js-plugin-runtime 7.0.3 7.0.4
stryker-js-typescript-checker 8.3.1 8.3.2
stryker-js-vitest-runner 9.0.1 9.0.2
stryker-framework-interface 0.0.1 0.0.1
stryker-ignorer-in-source-vitest-block 0.1.2 0.1.2
stryker-ignorer-interface 0.1.2 0.1.2
stryker-ignorer-kit 0.1.2 0.1.2

Stryker-js 19.0.0 is still compatible with what the shared job calls:

  • It still accepts the --incrementalFile the job passes (dist/main.mjs:116917, :117032).
  • It still writes the JSON report to reports/mutation/mutation.json, where the job reads it (stryker-config lib/base.js:13; main.mjs:91348-91351).

Proof runs

Both earlier runs ran on feat/mutation with no cache, so each covered the full corpus. Run 1 ran at 7cd02ba, which still carried the temporary registration trigger; 87b52af removes it. Run 2 ran at 1ebd34e, after the removal.

Run 1, 38117312976, at 7cd02ba: failure.

  • What happened: the dry run failed 1 of 104 tests with recursionBudget is declared on ApiItem but nothing materialized it — Budget_RequiresTransform.
  • Cause: Stryker's ObjectLiteral mutant wraps the recursionBudget object passed to annotate on each Schema.suspend, so effect-schema-recursion-budget no longer sees an object literal and injects nothing.
  • Fix: stryker-ignorer-effect-schema-declarations 0.3.0 ignores that object, hence the stryker-js-effect pin above.

Run 2, 38120830653, at 1ebd34e: failure.

  • Dry run: passed, 158 tests, 07:24:17.2 to 07:35:18.9 = 661 s wall, 337.5 s of runner test time.
  • Kill: of 1354 planned mutants, 63 completed before Stryker got SIGTERM at 07:40:14. That is 960 s after it started, the timeout --kill-after=60 <2 x target-seconds> wrapper at mutation-job.ts:102-105. Exit 130 RunInterrupted.
  • Planner: with no recorded timings, it planned one unsharded job (default about 8m00s).
  • Dry-run cost: this is shared tooling, routed to unit S2. effect-sim-kernel Profile.ts:34-39 runs 250 seeded replays per gherkin scenario and ignores the fibers/steps budget. This suite's scenarios are single-fiber, and 50 seeds gave 1 path. A serial host run took 69.9 s at 250 seeds and 9.5 s at 25.
  • Root ruling: no workaround in this repository, meaning no target-seconds or max-seconds change, no local mutation-job.ts, no CONFORMANCE_PROFILE flag and no test change.

Run 2's jobs, from the jobs API (wall = completed − started, queue = started − created):

Job Result Wall Queue
mutation · event guard success 15 s 2 s
plan · mutation jobs from recorded timings success 21 s 1 s
sfs-deps · tarballs the lockfile names success 401 s 1 s
mutation · api-extractor failure 994 s 2 s
record mutation timings success 19 s 2 s
Mutation report success 19 s 2 s

The mutation and sfs-deps jobs are both over the 600 s target. No graded row exists for packages/api-extractor yet.

Timeouts

Standing rule: every job and step timeout here is an explicit 60 minutes, a backstop for a stuck job, never a budget enforced by cancelling. Workflow lines from git grep -n timeout-minutes at b86386f:

.github/workflows/ci.yml:25:    timeout-minutes: 60
.github/workflows/ci.yml:50:      lanes: '[... "timeout-minutes": 60 ... (lint, typecheck, build)]'
.github/workflows/ci.yml:51:      timeout-minutes: 60
.github/workflows/ci.yml:53:      plan-timeout-minutes: 60
.github/workflows/ci.yml:66:    timeout-minutes: 60
.github/workflows/ci.yml:91:    timeout-minutes: 60
.github/workflows/mutation.yml:30:      timeout-minutes: 60
.github/workflows/mutation.yml:31:      report-timeout-minutes: 60
.github/workflows/release.yml:31:    timeout-minutes: 60

ci.yml:52 max-seconds: 600 is still open (see the receipt below).

Open items

Shared tooling, not this repository:

  • Hard-coded timeouts: reusable-mutation.yml sets them at :58, :74 and :255.
  • No full-corpus input: the incremental restore at :198-205 and :312-328 always runs. Only a cold-cache first run covers the whole corpus.
  • Per-shard dry-run repeat: the dry run repeats per shard (mutation-job.ts:101-117).
  • effect-sim-kernel Profile.ts:34-39: the seed budget, for S2.
  • Actions pinned by tag: the shared workflow calls actions by tag, not SHA (e.g. actions/cache/restore@v6 at reusable-mutation.yml:198), so the SHA pin on the workflow does not fix what they run.
  • Unchecked incremental cache: the shared workflow restores the incremental cache by key prefix with no integrity check (:198-203).
  • Dispatch inputs: the pinned callee takes no sha or jobs input.

This repository:

  • A red night reaches nobody automatically. A scheduled run has no pull request and no pusher. GitHub notifies the user who last modified the cron syntax, and it disables a public repository's schedules after 60 days without activity. This PR adds no P0 or revert handler (red-main-reverts-its-culprit-same-day). Operator: a handler workflow.
  • Stale manifest ranges: the stryker devDependency ranges in packages/api-extractor/package.json are stale behind the overrides. They are fixed in the first-release PR, because editing the shipped manifest changes the v0.0.0 tarball. That PR must also change the vm-runner override key at pnpm-workspace.yaml:57.

The branch also carries docs/solutions/integration-issues/unversioned-public-package-is-owed-a-release.md, the lesson from main's failed Release in #3.

Verification

At b86386f:

  • actionlint 1.7.12 exits 0 on every workflow. It ran in pinned form: built from the nixpkgs revision flake.lock locks (aa48d34, through --inputs-from .), not with nix run.
  • git diff --name-only origin/main...HEAD -- packages/ apps/ .changeset/ prints nothing (R3).
  • The sha512 of .#workspace-tarballs' api-extractor-0.0.0.tgz equals the v0.0.0 tag annotation's integrity: sha512-Xc8rNV9C…D3g==.
  • CI run 38361529337: all 11 jobs succeeded. Changeset Check run 38361529653: success. The push started no Mutation run.
  • No local Stryker run.

Code review (full path, report-only, head b86386f)

ce-code-review mode:agent base:origin/main depth:full ran against head b86386f03084535cbe3f0ec2f2aecf9e8afb5abc (base 9bd73962b7). Verdict: Not ready. That is expected for this draft: B1-B6 are open, and the one P1 is the cadence departure the root ruled on (#4 below). Nothing was applied in the review round.

Packs loaded. This head's config declares four packs at systemfsoftware df908ddfe4. The review also loaded the five packs #8 enrolls at 8d07118079, ci-wall-clock among them. The resolver returned no errors or warnings for either pin. Applicability was decided by meaning (CONST-T12), and systemfsoftware CONSTITUTION.md outranks pack rules (CONST-S2).

Reviewers. correctness, project standards (AGENTS.md plus the constitution), learnings with the per-rule pack receipt, reliability, security, and adversarial. All six ran as omp agents, and their session logs show every turn served by deepseek/deepseek-v4.1-flash through OpenRouter (13-30 turns each), with no Claude, Grok or xAI turn. The non-Claude peer is omp's adversarial-reviewer agent on deepseek-v4.1-flash, 19 turns. No external peer CLI is installed. An independent validator (correctness-reviewer, deepseek-v4.1-flash) checked the three findings selected for validation.

# Sev File:line Finding Validator Disposition
1 P2 .github/workflows/mutation.yml:8 The published body described push-to-main triggers, a per-commit concurrency group and the 1832 plan. confirmed Fixed by this body.
2 P2 .github/workflows/mutation.yml:22 An off-main dispatch runs nothing, and the R4 proof needs a trigger this head lacks. rejected: AE2 and KTD5 specify the skip, and U6 is the proof's temporary trigger Reject.
3 P3 .github/workflows/mutation.yml:1 The cost header prices below the 4,500 s the callee's max-seconds default authorizes (reusable-mutation.yml:20-24 at 02bf965). rejected: the header states an explicit floor, and the rule asks for the measured per-run minutes Reject.
4 P1 .github/workflows/mutation.yml:9-11 heavy-suites-judge-every-main-commit: a heavy lane must run on every push to main, and this lane does not. not sent to validation (settled decision) Root ruling decides (2026-10-13: nightly plus dispatch on main, never per push; plan KTD5). Recorded, not fixed.
5 P2 .github/workflows/ci.yml:52; ci.yml:25,66,91; release.yml:31 one-stuck-job-backstop: max-seconds: 600 is a sub-60-minute cap, and no changed job writes its wall time to the step summary. not sent to validation max-seconds stays: the root's earlier ruling allows no max-seconds change in this repository (Proof runs, run 2). The wall-time summary step is open, a fix in #5 pending the operator.
6 P3 .github/workflows/mutation.yml:1 heavy-lanes-state-their-daily-minutes: the header prices the nightly run, not a per-push run. not sent (same subject as #3) Reject: a push starts no run, and the header carries every part the rule names.

Pack receipt (ci-wall-clock at 8d07118079, all 12 rules)

Rule Ruling At Disposition
flake-outputs-build-once-on-main comply flake.nix:18 The diff changes .#sfs-deps' input (stryker-published), so a pull-request build of it passes Gate (1).
gates-share-one-job violate ci.yml:50 The diff rewrites the lane list, which keeps lint, typecheck and build as separate jobs. Upstream: reusable-checks' lane matrix.
heavy-lanes-state-their-daily-minutes comply mutation.yml:1-5 Fixed since 05f44c0.
heavy-suites-judge-every-main-commit violate mutation.yml:9-11 Root ruling decides (#4).
comply mutation.yml:14-15 Each run has its own group; fixed since 05f44c0.
violate mutation.yml:26 The callee takes no base or scope input. Upstream: systemfsoftware reusable-mutation.
light-checks-run-the-pinned-tool no match — No changeset, commit-message or PR-title job changes. The devshell: true at changeset-check.yml:17 predates this diff.
no-dev-shell-on-verdict-lanes no match — No nix develop step added to a verdict lane.
no-lane-grades-the-repos-own-docs no match — No job or test takes the docs as its subject.
one-stuck-job-backstop comply ci.yml:25,50,51,53,66,91; release.yml:31; mutation.yml:30,31 Every changed timeout is 60.
violate ci.yml:52 max-seconds: 600 stays by root ruling (#5).
violate ci.yml:25,66,91; release.yml:31 No wall-time summary step. Open, fix in #5 pending the operator (#5).
violate mutation.yml:28 target-seconds: 480 becomes a 960 s per-package kill. Upstream: B1.
violate mutation.yml:26 The callee's guard, plan and timings jobs carry 2-, 5- and 5-minute timeouts. Upstream.
pr-gate-runs-only-what-the-diff-affects comply mutation.yml:22; ci.yml:50 No pull-request job reaches the mutation lane. The rule's review clause asks for a full-path receipt with ci-wall-clock loaded, and this is it. The earlier "violate at .compound-engineering/config.yaml:5, resolved by #8" is withdrawn: #8 is open, and this receipt is what that row was waiting for.
red-main-reverts-its-culprit-same-day violate mutation.yml:20-24 No P0 writer, workflow_run handler or issues: write. Operator: a handler workflow.
violate mutation.yml:11 workflow_dispatch takes no sha or jobs input. Upstream: callee inputs.
release-promotes-the-staging-proven-build comply release.yml:31 Backstop value only.
violate (made newly relevant) release.yml:24 The pinned release reads no lane verdicts, so a commit whose nightly run is red is still tagged. Upstream: pnpm-release-management.
setup-under-a-minute comply ci.yml:25 .#sfs-deps builds before the pull-request verdicts, which the rule allows because the diff changes its inputs.

Other packs. boundary-testing / pin-dependency-semantics complies: the SHA pin plus the U6 graded proof stand in for a differential test (flake.nix:18-24). package-topology / surface-changes-are-versioned complies: no packages/ change, and the tarball equals the tag's. The rest of cell-architecture (16, counting ports-separate-from-layers at df908ddfe4), boundary-testing, schema-laws and package-topology: no match. The diff changes no code, schema, entry point or published surface.

Residual risks. A missed or disabled nightly produces no run, so "no run" looks the same as "green". The U6 proof runs a head whose trigger lines differ from the ones that merge. Nothing in CI asserts the trigger set or that the pin is still an ancestor of systemfsoftware main. The callee's tag-pinned actions run with actions: write.

Earlier review

Report-only review at 8671277: correctness, project standards, security, reliability, adversarial and learnings reviewers. No cross-model reviewer ran, because no cross-model CLI is installed here; the adversarial lens ran in-process. The review's own "Ready to merge" verdict was rejected because R4 is unmet, and F0 was added. Fixes landed in 7d3dbc6.

ID Sev File:line Finding Ruling
F0 P1 plan R4 / U3 No proof run has concluded success with a graded row and every job at 600 s or less. Merge blocker, not a fix: stays a draft until a re-proof after O1's fix PR and S2's release.
F1 P2 .github/workflows/mutation.yml:20 At 02bf965 (before #733) a run concludes success on a [FAIL] graded row. Rejected for this PR: per D2, O1's rev is pinned and R4 reads the graded summary; the #733 re-pin follows and stays an open item.
F2 P2 AGENTS.md:24 "Mutation testing runs on main only" contradicts the workflow_dispatch trigger. Fixed in the doc, not the workflow: the dispatch trigger is a root ruling, and a branch run can't write a cache that main reads.
F3 P2 .github/workflows/mutation.yml:9 Concurrency per commit lets two runs on main overlap. Rejected: root ruling. The cache save key carries github.run_id (reusable-mutation.yml:201), so saves never collide; the worst case is a later run restoring an older cache, which costs extra mutants, not wrong results.
F4 P3 plan :234 The KTD3 edge case still said the earlier run is cancelled. Fixed: a second dispatch on the same commit waits and both report; KTD3 is marked amended by the U2 rulings.
F5 P3 flake.nix:14 The comment claimed every stryker package comes from the stryker-js-effect pin; stryker-config comes from the systemfsoftware input. Raised by two reviewers and counted once. Fixed: comment only.
F6 P3 PR status line stryker-config's stryker-js peer is optional, so the narrow range only warns. Fixed in the status wording; the plan's open item stays.
F7 P3 README.md:22 The README row describes the caller as in place before a passing proof. Rejected: the row describes the merged state, and the PR doesn't merge before the re-proof passes.

Checks at 7d3dbc6: uncached turbo run lint --force reports 0 warnings and 0 errors; .#packages.<system>.sfs-deps evaluates on x86_64-linux and aarch64-linux; nix flake check --no-build passes.

Related: systemfsoftware/systemfsoftware#735

ce-compound

ce-compound was evaluated for this PR (mode non-interactive) and ended Documentation skipped. Reason: the work is not verified. No graded mutation run exists, and U5 and U6 wait on B1-B6. The one solved lesson on this branch already has its document: docs/solutions/integration-issues/unversioned-public-package-is-owed-a-release.md, from #3.

Records the durable lesson from main's failed Release (run 38076109214)
and its fix in #3: a public member with no <name>@v<version> tag is in the
release cycle and needs a changelog for that version; "private": true is
not the fix because it also drops the member's tarball; and a new repo's
root package.json needs a "version" for the bump step. Marked as a
candidate for the systemfsoftware release pack.

Coordinates checked at pnpm-release-management b9b4a1d48cc1, cited by
symbol in the doc:
- packages/github-release-engine/src/cycle.ts:20-23 (cycleOf filters)
- nix/lib/pnpm-workspace-packages.nix:63 (public binding)
- packages/version-engine/src/bump.ts:54-57 (root manifest readSurface)
PR3 (feat/mutation) adds a thin caller of systemfsoftware's shared
reusable-mutation.yml: push to main and workflow_dispatch only, pinned
by SHA to the O1 commit that lets the shared job install .sfs-deps.
systemfsoftware main is still feb68840, which installs without
.sfs-deps, so the caller stays blocked: no workflow file lands until
the O1 rev exists. The plan records <O1-rev> as the placeholder.

The requirements came from the ce-brainstorm pass and the
implementation planning from ce-plan, both in this file: units U1-U3
with test scenarios, Compound Pack citations, and an open question for
the root. At feb68840 the shared run concludes success when a package
scores below its break threshold, so the proof reads the graded
summary row.

The ce-doc-review findings are report-only: none are applied, and
each awaits a root ruling.
Records all 14 doc-review rows in a "Doc-review rulings" section and
applies them. Accepted: D1, D3 (assumptions text), D5, D6, L1, D8, D9,
D10, D11, D12, F1. D7 is rejected because the shared report job requests
actions: write.

Root rulings:
- D2: O1 (systemfsoftware #735) does not fail the run below break;
  #733 does. The pin order follows whichever lands first, and R4 holds
  either way.
- D4: the caller has no tarball job. #735's sfs-deps job builds this
  repository's .#sfs-deps (flake.nix:113), and this repository is the
  caller #735's AE5 names.
- KTD4: every job fits 10 minutes. target-seconds <= 480, max-jobs 20,
  default timeout as backstop.

Two facts found while applying the rulings are recorded as open
questions:
- #733 requires a committed mutation-baseline.json per mutated package.
- Before #733 a package's kill cap is twice target-seconds.
… O1 (#735)

The row pointed at #710, which has landed. The shared reusable-mutation.yml still installs without .sfs-deps, and O1 (systemfsoftware #735) adds that input. Unit U1 of docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md.
Standing rule (2026-10-11): a timeout is a backstop for a stuck job,
never a budget enforced by cancelling. Budgets stay measured, with each
job's wall time reported in the PR body.

- ci.yml: the sfs-deps, host-bytes and host-compare jobs go from 10, 10
  and 5 to 60. So do the three lanes' timeout-minutes, timeout-minutes
  and plan-timeout-minutes passed to reusable-checks.yml.
- release.yml: the noop job goes from 1 to 60.

reusable-checks.yml at the pinned 732f66a0 takes each input as an
uncapped number: lanes into timeout-minutes (:212), timeout-minutes into
the test job (:55-59, :239), plan-timeout-minutes into the plan and
timings jobs (:60-64, :139, :316). max-seconds stays at 600, because it
is a planner budget, not a timeout.

The mutation-caller plan records the ruling and amends KTD4: the future
caller passes timeout-minutes and report-timeout-minutes as 60.
U2 of docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md. The new
.github/workflows/mutation.yml calls reusable-mutation.yml at
02bf9653b1ca2b0a2951baf2743bb3f70d19fc77 (systemfsoftware #735). It
was pinned once all five runs on that commit had concluded success.

- Triggers: push to main and workflow_dispatch, never pull_request.
- Concurrency: one group per SHA with cancel-in-progress false, so no
  merge's run cancels another's.
- Inputs: target-seconds 480, max-jobs 20, timeout-minutes 60 and
  report-timeout-minutes 60. sfs-deps-installable keeps its default
  .#sfs-deps (flake.nix:113), so the caller has no tarball job.
- Permissions: contents read, plus actions write for the report job,
  which also covers the plan job's actions read.

Push and dispatch runs are both incremental through the reusable's
stryker-incremental cache (reusable-mutation.yml:198-205, :312-328).
A full-corpus input is an open item for the shared-tooling unit.
…rkflow

Registration workaround from the plan's U3: GitHub dispatches only workflows it knows, and this file is not on the default branch. A push on feat/mutation registers it, and the job is skipped on that event. The next commit removes this trigger.
The registration push on 7cd02ba registered the workflow (id 381043192), and proof run 38117312976 was dispatched at that commit. The caller is back to push on main and workflow_dispatch, and the job-level if: goes with the trigger.
Proof run 38117312976 failed its dry run with Budget_RequiresTransform:
Stryker's ObjectLiteral mutant wraps the recursionBudget object passed to
annotate on each Schema.suspend, so effect-schema-recursion-budget injects
nothing. stryker-ignorer-effect-schema-declarations 0.3.0 ignores that
object; it is released at 9e81669 with stryker-js 19.0.0.

The stryker-published input moves from 27b1075 (stryker-js v18.1.0) to
9e81669, and every stryker tarball comes from that one commit. The
.sfs-deps overrides and pnpm-lock.yaml follow; the lockfile diff changes
only stryker packages. No package file changes (R6).

The plan records the run, the cause and the root ruling, plus two
shared-tooling open items: the kill cap in mutation-job.ts:102-105 and
stryker-config 0.1.0's ^17.0.0 peer on stryker-js.
Run 38120830653 passed its dry run (661 s) but the shared mutation-job.ts timeout wrapper killed Stryker at 960 s with 63 of 1354 mutants done. The dry-run cost comes from effect-sim-kernel's 250 seeded replays per scenario (Profile.ts:34-39), routed to unit S2. No workaround here; the PR waits on O1's fix PR and S2's release.
F2: AGENTS.md states where mutation runs: in CI only, never on pull
requests, on every push to main and by manual dispatch to prove the
lane on a branch. F4: the plan's KTD3 edge case now records that a
second dispatch on the same commit waits and both report, and KTD3 is
marked amended by the U2 rulings. F5: the flake.nix comment no longer
claims stryker-config comes from the stryker-js-effect pin.
Root ruling (2026-10-12): Stryker's effective mutate set is exactly
src/**/*.workflow.ts, so stryker.config.ts drops src/**/*.schema.ts.
The plan records the ruling, amends R6 for this one line and no longer
says schema files are mutated. The package tarball ships only dist, so
its bytes do not change.
… change

Root decision: the change touches only CI and the dev-only Stryker config. The shipped tarball is byte-identical to the one the v0.0.0 tag records, so nothing is released.
Review ruling (2026-10-12), from the repository's own sources:
- packages/api-extractor/stryker.config.ts returns to main's bytes:
  workflow and schema files are the verdict-decision code AGENTS.md:12
  names (CONST-T12); narrowing the graded set inside the PR it grades
  weakens the grader (CONST-E7); a one-file set yields fileless shards
  at 02bf965 (test-timings.ts:211-212,355,455).
- With no path under packages/ changed, the `none` intent is deleted.
- docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md supersedes the
  started 2149 plan (plan-discipline), and lists blockers #737, #744,
  #733 and survivors with evidence.
The caller's triggers are now a nightly schedule (14:47 UTC) and
workflow_dispatch, and its job runs only on refs/heads/main. Each run has
its own concurrency group, so a dispatch never replaces the nightly run.
The header states the lane's daily runner-minutes, which the ci-wall-clock
rule heavy-lanes-state-their-daily-minutes requires.

docs/plans/2026-10-13-0641-ci-mutation-caller-plan.md supersedes the 1832
plan: nightly cadence (U7), the re-pin past systemfsoftware #733 that
retires generated schema laws without replacement (U5, blocked on B6),
and the proof run on a temporary branch trigger (U6). AGENTS.md and the
README row restate the cadence.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…ulings (U12)

Supersedes the 1737 plan. D1 named a reader refusal (MissingSource)
that would have reversed ruling #5's fast-path decline; the adapters
decline (fast path) and refuse before tsc (compiler). D2 said tsc
refuses a shared declaration path with TS5056; tsc 7.0.2 exits 0 and
writes one file, so the compiler adapter refuses it first. The
DeepSeek delta review's two findings are ruled: the D1 text finding
is accepted by this correction, the dropped-exit-cause finding is
rejected with its reason.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…ancestry; profile harness maps .cts and counts missing emits (U13 #5, #6)

The derived tsconfig sat in a bare temp directory, so tsc looked up a
`types` entry naming a package (`vitest/globals`) in node_modules
directories above the temp dir and refused with TS2688. Four corpus
packages the earlier profile modelled were refused this way. The
derived config is now written into a scoped mirror of the package
config's directory, each ancestor carrying a symlink to the real
node_modules, so tsc's upward walk matches the package's own. The
explicit typeRoots chain this replaces is gone; derivedTsconfig
becomes writeDerivedTsconfig, which writes the mirror and returns the
config path.

e2e: the equality journey gains a package whose tsconfig names
`types: ["greeting/globals"]` from its own node_modules through an
exports subpath. It fails at c4202d9 with TS2688 and passes after.

Profile harness: .cts sources map to .d.cts and are discovered; null
and array export targets are skipped; a file missing from the 5.9.3
output is counted apart from a printing difference; tsc's stderr is
drained alongside stdout.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…mirrored node_modules learning

Supersedes the 1834 plan. U13 rules every finding of the full-path
review at e5ec09d: six fixed (#1, #2, #4, #5, #6, #8), the
mapped-type/infer anchor-50 note fixed after reproduction, the
TS2688 regression the rerun found fixed, #3 and #7 rejected with the
validator's reasons, and the Diagnostic.code refusal note made a
PR4 follow-up. KTD3 now describes the mirrored node_modules ancestry
instead of the explicit typeRoots chain, and the policy gains
isolatedDeclarations: false.

docs/profiles/2026-10-13-front-end-phases: the U13 harness at 432b608
on the corpus at 732f66a0c8. 126 of 585 shared declaration files print
differently between TypeScript 7 and 5.9.3 and none are missing; every
package's route and outcome match the second profile.

The emit-option learning gains the isolatedDeclarations case and the
types-from-node_modules case, each with its gate.
systemfsoftware-maker added a commit that referenced this pull request Oct 14, 2026
Testing P3 at c25bf9a: the compiler's 'does not exist' refusal had no
scenario beside its pinned 'resolves outside the package' sibling. The
refusal journey analyses a package whose entry names no file: the fast
path declines and the compiler refuses with a Note naming src/mod.ts.
Pins existing behaviour (ruling #5).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant