From 8e7bd7ca4f8ca6eb40c4f001290ac89bf31a8a0f Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sat, 10 Oct 2026 21:52:39 +0000 Subject: [PATCH 01/15] docs(solutions): a public package at 0.0.0 with no tag is owed a release Records the durable lesson from main's failed Release (run 38076109214) and its fix in #3: a public member with no @v 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) --- ...sioned-public-package-is-owed-a-release.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/solutions/integration-issues/unversioned-public-package-is-owed-a-release.md diff --git a/docs/solutions/integration-issues/unversioned-public-package-is-owed-a-release.md b/docs/solutions/integration-issues/unversioned-public-package-is-owed-a-release.md new file mode 100644 index 0000000..0dcd76f --- /dev/null +++ b/docs/solutions/integration-issues/unversioned-public-package-is-owed-a-release.md @@ -0,0 +1,71 @@ +--- +title: A public package at 0.0.0 with no release tag is owed a release that has no changelog +date: 2026-10-10 +category: integration-issues +module: release workflow (pnpm-release-management caller) +problem_type: integration_issue +component: tooling +symptoms: + - "Release on main fails in the job \"release · tag · GitHub releases\" with `Missing changelog for @systemfsoftware/api-extractor@0.0.0`" + - The failed run leaves the tag `@systemfsoftware/api-extractor@v0.0.0` on origin with no GitHub Release + - "Without a root `version`, `version-management bump` refuses with `VersionIntentMalformed` (path=package.json)" +root_cause: incomplete_setup +resolution_type: config_change +severity: high +tags: [release, pnpm-release-management, changelog, private-package, workspace-tarballs, root-version, release-pack-candidate] +--- + +# A public package at 0.0.0 with no release tag is owed a release that has no changelog + +**Release pack candidate.** This applies to every systemfsoftware family repository that calls pnpm-release-management, not only this one. It is a candidate rule for the systemfsoftware release pack. + +## Problem + +The first push to `main` with Release wired (`b7e907a`, run 38076109214) failed. `@systemfsoftware/api-extractor` was public at `0.0.0`, had never been released and had no `CHANGELOG.md`. Release treated it as owed a `0.0.0` release and refused for want of a changelog, after it had already pushed the tag. + +## Symptoms + +```text +Missing changelog for @systemfsoftware/api-extractor@0.0.0 +``` + +The remote tag `@systemfsoftware/api-extractor@v0.0.0` was left behind with no GitHub Release, because the reusable `release.yml` tags before it creates GitHub Releases. + +## What Didn't Work + +- **`"private": true` on the package** (`f1ff85b`, rejected and reverted in `d3e31dd`). It does take the package out of the release cycle. It also takes it out of the flake: `mkPnpmWorkspacePackages` keeps only non-private workspace members, so `.#workspace-tarballs` became `[]` and the flake lost this repository's own tarball. A family repo's product is that tarball. + +## Solution + +PR #3 (`627713f`): + +- The package stays public at `0.0.0`. +- The root `package.json` gains `"version": "0.0.0"`, the surface the bump step reads. +- `release.yml` accepts `workflow_dispatch`, with the release job gated to `refs/heads/main` and a no-op job off `main`, so a manual dispatch on a branch writes nothing. + +The `v0.0.0` tag the failed run pushed stays. With it on origin, the pinned `github-release-engine plan` at PR #3's head gave `phase=version` and the bump consumed the pending intent without moving a version (probe recorded in PR #3's body). The tag is deleted only in the change that moves the version off `0.0.0` (`.changeset/README.md`). + +## Why This Works + +All three mechanisms are in pnpm-release-management at `b9b4a1d48cc16241b33f9afd8ab99b82b05ce04f`, the commit this repository's release callers pin. + +1. **Who is owed a release.** `cycleOf` in the `github-release-engine` package builds the cycle from every member that is publishable, has no `@v` tag on the remote and has no legacy release. Each `CycleEntry` carries the changelog path for that version. A public `0.0.0` member with no tag is therefore owed `0.0.0`, and that release needs a changelog the repository never wrote. +2. **Why `private` is the wrong lever.** `mkPnpmWorkspacePackages` keeps only the members whose manifest is not `private` (its `public` binding), and only those become tarballs. A private member leaves the release cycle and the flake output at once. +3. **Why the root needs a version.** `version-engine`'s bump reads the version surface of the root manifest (`request.manifest`) before it plans anything. A new repository's root `package.json` has no `version`, so the bump refuses before it looks at intents. + +Invariant, over every public workspace member $m$ when Release runs on `main`: + +$$\text{tag}(m) \notin \text{remoteTags} \implies \text{changelog}(m, \text{version}(m)) \text{ exists}$$ + +## Prevention + +- Before Release first runs on `main`, every public workspace member has either its `@v` tag on origin or a `CHANGELOG.md` entry for its current version. A new family repo ships its first version's changelog in the same change that wires Release. +- The root `package.json` of every repository that calls pnpm-release-management carries a `version`. +- Never mark a package `private` to keep it out of a release. It also drops the package's tarball from `mkPnpmWorkspacePackages`. +- While a public package sits at a version whose tag exists, a change to its tarball ships a non-`none` intent; with `none`, the plan refuses with `tag-integrity-mismatch` (`.changeset/README.md`). + +## Related Issues + +- Plan: `docs/plans/2026-10-10-2113-fix-release-version-surface-plan.md`. +- PR #3, systemfsoftware/api-extractor-effect: the probe outputs and the rejected `private` approach. +- Open upstream question for pnpm-release-management: check for the changelog before pushing tags, so a refused release leaves no tag. From d8c5cb151a7bcd0e5b79239fb729713ab41c1efc Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sat, 10 Oct 2026 22:41:04 +0000 Subject: [PATCH 02/15] docs(plan): plan the mutation caller, blocked on systemfsoftware O1 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 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. --- ...2026-10-10-2149-ci-mutation-caller-plan.md | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md new file mode 100644 index 0000000..155b68b --- /dev/null +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -0,0 +1,213 @@ +--- +title: Mutation Caller - Plan +type: ci +date: 2026-10-10 +topic: mutation-caller +artifact_contract: ce-unified-plan/v1 +product_contract_source: ce-brainstorm +execution: code +--- + +# Mutation Caller - Plan + +## Goal Capsule + +- **Objective:** every push to `main` runs mutation testing on `@systemfsoftware/api-extractor`'s workflow and schema code, so a surviving mutant turns `main` red. +- **Means:** a thin caller of systemfsoftware's shared `reusable-mutation.yml`, pinned to the commit where unit O1 lets that workflow install the `.sfs-deps` tarballs (KTD1). +- **Product authority:** this plan, then `AGENTS.md`, then `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md`, whose KTD20 and its mutation Key Decision this plan carries out. The front-ends, resolver and phase profile that the 1146 plan's KTD1 put in PR3 are not active scope here. +- **Open blockers:** O1. systemfsoftware `main` (`feb68840`) has `reusable-mutation.yml` from #710 (`9154691`), but its mutation job installs with plain `pnpm install --frozen-lockfile` (`.github/actions/install-deps`) and takes no input that could place `.sfs-deps/` first. This lockfile names `file:.sfs-deps/*.tgz`, so that install fails here. The O1 rev is `` until it lands. +- **Open question for the root:** at `feb68840` the shared run concludes `success` when a package scores below its break threshold, so the shared workflow alone does not yet turn `main` red on a survivor (Dependencies / Assumptions). PR3 delivers the caller and a graded proof either way; whether the red signal comes from O1 or another systemfsoftware unit is the root's call (Outstanding Questions). +- **Execution profile:** PR3 on `feat/mutation`, a `gh stack` layer on `main`. The agent commits and pushes; the operator merges. U1 can run as soon as `ce-work` starts; U2 and U3 start only when the O1 rev exists. +- **Stop conditions:** the O1 rev does not exist (stop after U1, PR3 open and marked blocked); the O1 rev's mutation job still installs without `.sfs-deps` (stop and report to the root); the proof run's summary grades the package below its break threshold (stop; R4 holds the merge). + +--- + +## Product Contract + +Product Contract preservation: refined R4, AE3, AE4 — the proof reads the graded verdict in the run's mutation summary as well as the run's conclusion, because at `feb68840` the conclusion alone does not show a below-break score. Objective unchanged; the gap it now depends on is an open question for the root. Outstanding Question on caller inputs resolved in place (KTD4). + +### Summary + +PR3 adds the mutation caller for this repository: it runs on push to `main` and on manual dispatch, never on a pull request, and calls the shared workflow at the O1 rev. Until that rev exists the caller stays blocked and PR3 carries only this plan and the README status. + +### Problem Frame + +Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break threshold 100 in `packages/api-extractor/stryker.config.ts`), and it has never run on this package. PR2 shipped everything the package side needs and left the workflow blocked on O1 (1146 plan, KTD20). #710 has since landed the shared workflow, so the README row that waits on #710 is stale. The remaining gap is the tarball install, and the root ruled that it is fixed upstream, not here. + +### Key Decisions + +- **The `.sfs-deps` gap is fixed in systemfsoftware's shared `reusable-mutation.yml` by unit O1, not in this repository.** (session-settled: user-directed — chosen over a copied, forked or hand-rolled mutation workflow here: root ruling, binding.) Governs R2, R3. +- **Mutation runs on push to `main` and on `workflow_dispatch`, never on `pull_request`.** (session-settled: user-directed — chosen over local or per-PR mutation: carried from the 1146 plan.) Governs R1. +- **The proof is one dispatch run on the PR branch, registered by the 1146 plan's workaround.** GitHub dispatches only workflow files that exist on the default branch, so the branch carries a temporary trigger with every job `if:`-skipped, removed before the final head. (session-settled: user-directed — chosen over a push run on `main` as the first evidence: carried from the 1146 plan.) Governs R1, R4. +- **A failing graded score on the proof run blocks the merge.** Stryker has never run on this package and the break threshold is 100, so the first score may fall short; the shared run still concludes `success`, so only the graded score shows it. Governs R4. +- **No caller file lands before the O1 rev exists.** A push-triggered caller whose `uses:` ref cannot resolve would fail every push to `main`, so the placeholder lives in this plan, not in a workflow file. Governs R3. +- **No package-side work.** PR2's wiring already matches what the shared job runs, so PR3 changes no package file. Governs R6. + +### Requirements + +**Caller** + +- R1. At PR3's final head the caller runs only on push to `main` and on `workflow_dispatch`; it never has a `pull_request` or `merge_group` trigger. +- R2. The caller is a thin `workflow_call` of systemfsoftware's `reusable-mutation.yml` pinned by commit SHA at the O1 rev, with only that workflow's inputs and the permissions its jobs ask for. +- R3. Until the O1 rev exists, the caller is recorded as blocked on O1 with the placeholder ``, and no caller file is committed. + +**Proof** + +- R4. Once the O1 rev exists, one `workflow_dispatch` run of the caller on the PR branch concludes `success` and its mutation summary grades `@systemfsoftware/api-extractor` at or above its break threshold; the PR body links the run, and PR3 does not merge without it. + +**Docs** + +- R5. The README roadmap row for mutation and the PR body name O1's `.sfs-deps` input as the blocker while R3 holds, and the pinned rev once R2 lands. +- R6. `packages/api-extractor/stryker.config.ts`, the package's `mutation` script and its stryker devDependencies stay as PR2 shipped them. + +### Acceptance Examples + +- AE1. **Covers R1.** **Given** the caller on `main`, **when** a pull request is opened, **then** no mutation run starts. +- AE2. **Covers R3.** **Given** O1 has not landed, **when** PR3's head is pushed, **then** it contains no caller workflow file and the plan and PR body show `` as the blocker. +- AE3. **Covers R2, R4.** **Given** the O1 rev exists, **when** the caller is dispatched on the PR branch, **then** the shared job installs from `.sfs-deps`, runs the package's `mutation` script, the run concludes `success` and the summary's verdict for the package is a pass; its URL is in the PR body. +- AE4. **Covers R4.** **Given** the dispatch run's summary grades the package below its break threshold, **when** the PR is reviewed, **then** it stays unmerged until a later run on the same caller grades it at or above the threshold. + +### Scope Boundaries + +- No local mutation workflow, composite action or copy of any shared step (R2). +- No stryker run outside CI. +- No mutation on the e2e app: it has no `mutation` script, and the shared planner picks packages by that script. +- Killing survivors the first run finds is separate work outside PR3's wiring; per R4 it lands before PR3 merges. +- Considered, not built: a `.gitignore` entry for `reports/`. Stryker writes reports only inside CI's checkout here (KTD20 of the 1146 plan: no local stryker), so nothing reaches `git status`; a local mutation script would change that. +- Considered, not built: a caller-side check that fails the run on a below-threshold score. That is a step around the shared job, which R2 and the binding O1 ruling exclude; the ask goes upstream (Open Questions). + +### Dependencies / Assumptions + +- O1 adds a `.sfs-deps` step or input to `reusable-mutation.yml`'s install; its shape decides the caller's inputs. +- The shared planner finds `@systemfsoftware/api-extractor` through its `mutation` script, and the shared job runs `pnpm --filter mutation` and reads `reports/mutation/mutation.json`, which the shared stryker config writes. +- The shared mutation job fails only when a package writes no report; a below-threshold score still concludes `success` (`mutation-job.ts` `runJob` and `build-mutation-summary.ts` `buildRequireError` at `feb68840`). The graded score is read from the report job's summary. + +### Outstanding Questions + +**Deferred to Planning** — none remain; caller inputs are resolved in KTD4. + +**For the root (does not block PR3)** + +- Should O1, or a separate systemfsoftware unit, make the shared mutation run fail when a package's score is below its break threshold? Until it does, `main` shows a below-threshold score only in the run summary. + +### Sources + +- systemfsoftware `9154691` (#710) and `main` `feb68840`: `.github/workflows/reusable-mutation.yml` (inputs, `mutation-event-guard`, mutation job install step, report job permissions), `.github/actions/install-deps/action.yml`, `.github/workflows/mutation.yml` (systemfsoftware's own caller), `scripts/tools/test-timings.ts` (`workspacePackagesWith`), `scripts/tools/mutation-job.ts` (`strykerUnderCap`, `runJob`), `scripts/tools/build-mutation-summary.ts` (`buildRequireError`). +- systemfsoftware `732f66a0c8` `packages/toolchain/stryker-config/lib/base.js` (reporters and report paths). +- `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md` KTD20 and its Key Decisions on O1, mutation and the dispatch workaround. +- GitHub Docs, "Manually running a workflow": `workflow_dispatch` requires the workflow file on the default branch (https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow). +- `README.md` roadmap table, `packages/api-extractor/stryker.config.ts`, `packages/api-extractor/package.json`. +- Compound Packs (`.compound-engineering/config.yaml`, systemfsoftware `df908ddf`): `package-topology/surface-changes-are-versioned` applies through R6: no package byte changes, so PR3 needs no changeset, and the Changeset Check passes on a PR that touches no publishable package. `boundary-testing/pin-dependency-semantics` covers the plan's reliance on the shared workflow's behaviour. PR3 has no code path to hold a differential test, so the SHA pin (KTD1) and U3's graded proof take its place. `cell-architecture` and `schema-laws` govern package source, which PR3 does not touch. + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. **One caller file, `.github/workflows/mutation.yml`, calls `reusable-mutation.yml@` with a trailing comment naming O1.** It follows `release.yml` and `ci.yml`: a reusable workflow pinned by full SHA. The shared job's `sfs-tools` step checks out its scripts at `job.workflow_sha`, so the one pin fixes the workflow and the tools together. Covers R2. +- KTD2. **Permissions: `contents: read` at the top, and the mutation job grants `contents: read` plus `actions: write`.** That is systemfsoftware's own caller's grant. The shared plan job reads the timing record (`actions: read`) and the report job deletes per-job artifacts (`actions: write`); a reusable workflow can only narrow the caller's token, and the report job's delete step is `continue-on-error`, so a missing grant fails silently. Covers R2. +- KTD3. **Concurrency `mutation-${{ github.ref }}` with `cancel-in-progress: true`, as systemfsoftware's own caller sets it.** The shared workflow declares none, and two runs on one ref race the stryker incremental cache and the timing record. A cancelled `main` run loses only its own record; the next push re-plans. Covers R1. +- KTD4. **No timing inputs; the shared defaults stand** (`target-seconds` 900, `timeout-minutes` 75, `max-jobs` 20, `max-seconds` 4500), as systemfsoftware's own caller uses them. The brainstorm's "10-minute job budget" applies to `reusable-checks.yml`, not mutation. The only inputs are the ones O1 adds for `.sfs-deps`. Covers R2. +- KTD5. **Triggers at the final head: `push` with `branches: [main]`, and `workflow_dispatch`.** The shared `mutation-event-guard` refuses `pull_request`, `pull_request_target` and `merge_group`, but a trigger that never fires is the cheaper guarantee. Covers R1. +- KTD6. **The proof reads the graded score from the report job's summary, not the run's conclusion.** Per the Dependencies entry, conclusion `success` only proves a report was written. Covers R4. + +### High-Level Technical Design + +The caller's life across PR3, directional: + +```mermaid +stateDiagram-v2 + [*] --> Blocked: PR3 opened, O1 unlanded (U1) + Blocked --> Wired: O1 rev exists, caller pinned to it (U2) + Wired --> Registered: temporary push trigger, every job if-skipped (U3) + Registered --> Proved: dispatch on feat/mutation, summary grades pass + Registered --> Held: summary grades below break + Held --> Registered: survivors killed in a separate layer + Proved --> Final: temporary trigger removed, CI green + Final --> [*]: operator merges; push to main runs the caller +``` + +### Assumptions + +- The first `main` run plans from an empty timing record: the shared plan job reads only records from the default branch, so the PR-branch dispatch's record does not carry over. It pays the full stryker cost once, within the 75-minute default. +- Change reuse between runs is stryker's incremental file, restored from the `stryker-incremental-` cache; the shared plan job passes no turbo dry run, so no push is skipped as unchanged. + +### Risks + +- O1 may land a `.sfs-deps` mechanism whose inputs differ from this plan's guess; U2 takes whatever O1 defines and records it in the PR body (R5). +- The 1146 workaround may not register the file for dispatch; then U3 stops and reports, and the root decides between merging without a proof run and another registration route. + +--- + +## Implementation Units + +### U1. State the O1 blocker in the README + +- **Goal:** the README's roadmap row stops pointing at #710 and names O1's `.sfs-deps` input as the blocker. +- **Requirements:** R3, R5. +- **Dependencies:** none. +- **Files:** `README.md`. +- **Approach:** rewrite the "Mutation testing on `main`" row's status to "blocked on O1: the shared `reusable-mutation.yml` installs without `.sfs-deps`". No other row changes. +- **Patterns to follow:** the table's existing status wording. +- **Test scenarios:** Test expectation: none -- documentation only. +- **Verification:** the row names O1 and the missing `.sfs-deps` input, and no file under `.github/workflows/` mentions `reusable-mutation`. + +### U2. Add the mutation caller pinned to the O1 rev + +- **Goal:** `.github/workflows/mutation.yml` calls the shared workflow at the O1 rev with the triggers, grant and concurrency of KTD2, KTD3 and KTD5. +- **Requirements:** R1, R2, R5. +- **Dependencies:** U1; the O1 rev exists on systemfsoftware `main`. +- **Files:** `.github/workflows/mutation.yml` (new), `README.md` (row names the pinned rev). +- **Approach:** + 1. Confirm the O1 rev's `reusable-mutation.yml` places `.sfs-deps` before its install; if it does not, stop (Goal Capsule stop condition). + 2. Write the caller per KTD1-KTD5, passing only the inputs O1 defines for `.sfs-deps` (KTD4). + 3. Update the README row to the pinned rev. +- **Patterns to follow:** `.github/workflows/release.yml` (SHA pin with trailing comment, explicit permissions, concurrency); systemfsoftware's `.github/workflows/mutation.yml` (grant and concurrency). +- **Execution note:** CI configuration; prove it by the U3 dispatch run, not by tests. +- **Test scenarios:** checked against the file at review and observed in U3's run; no repository test can exercise a workflow trigger (no code path). + - Happy path (AE3): `workflow_dispatch` on `feat/mutation` → `mutation-event-guard` passes, the plan job lists `@systemfsoftware/api-extractor` as the only package, the mutation job installs from `.sfs-deps` and runs its `mutation` script. + - Edge (AE1): a pull request against `main` → no run starts, because the file has no `pull_request` or `merge_group` trigger. + - Edge: a push to a branch other than `main` at the final head → no run starts (`branches: [main]`). + - Edge (KTD3): a second dispatch on the same ref while one runs → the earlier run is cancelled and only the later one reports. + - Error: the O1 rev drops the `.sfs-deps` placement → the install step fails and the mutation job is red; U2 step 1 catches this before the pin lands. +- **Verification:** `actionlint` passes on the file; the pinned SHA resolves to a commit on systemfsoftware `main` that contains `reusable-mutation.yml`. + +### U3. Prove the caller with one dispatch run + +- **Goal:** one dispatch run on `feat/mutation` that meets R4, linked from the PR body, with no temporary trigger left at the final head. +- **Requirements:** R1, R4, R5; AE3, AE4. +- **Dependencies:** U2. +- **Files:** `.github/workflows/mutation.yml` (temporary trigger added, then removed). +- **Approach:** + 1. Add a temporary `push` trigger for `feat/mutation` with every job `if:`-skipped on that event, push, and confirm the skipped run registered the workflow. + 2. Dispatch the caller with `--ref feat/mutation` and watch it to a conclusion. + 3. Read the report job's summary row for `@systemfsoftware/api-extractor` (KTD6). + 4. Remove the temporary trigger in the next commit; record the run URL and the graded score in the PR body. +- **Patterns to follow:** the 1146 plan's dispatch workaround; PR #3's off-main dispatch proof recorded in its body. +- **Test scenarios:** observed in the dispatch run and the PR body; the run is the proof. + - Happy path (AE3): the run concludes `success` and the summary's row for `@systemfsoftware/api-extractor` reads verdict pass with thresholds `100/100/100`. + - Error (AE4): any survivor → the row's verdict is fail while the run still concludes `success`; PR3 stays unmerged and the survivors go to a separate layer. + - Error: the package writes no report → the mutation job concludes `failure` (`runJob` returns not ok); stop and report. + - Edge: the temporary push run on `feat/mutation` → every job is skipped and the run concludes `success`, which registers the file for dispatch. +- **Verification:** the run concluded `success`, the summary grades the package at or above 100, the final head's triggers are exactly `push: main` and `workflow_dispatch`, and CI is green on that head. + +--- + +## Verification Contract + +| Gate | Applies to | Done signal | +| --- | --- | --- | +| `nix run nixpkgs#actionlint -- .github/workflows/mutation.yml` | U2, U3 | exit 0 | +| `git -C merge-base --is-ancestor origin/main` | U2 | exit 0 | +| Dispatch run of `mutation.yml` on `feat/mutation`, watched with `xd://github` `run_watch` | U3 | conclusion `success`; summary grades `@systemfsoftware/api-extractor` at or above its break threshold | +| `pnpm lint`, `pnpm typecheck`, `pnpm test` inside `nix develop` | every head | exit 0 (no package file changes, so these guard against accidental edits) | +| `ci.yml` and Changeset Check on the PR head | every head | green | + +--- + +## Definition of Done + +- While O1 is unlanded: U1 and this plan are committed and pushed, PR3 is open as a `gh stack` layer on `main`, and its body names `` as the blocker (AE2). +- After O1 lands: U2 and U3 are done, the PR body links the dispatch run and its graded score, the final head carries no temporary trigger, and CI is green on it. +- No package file differs from `main` (R6), and no abandoned workflow attempts remain in the diff. From 11fc8468cf95b6217b41df82831b596be9186ddb Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 00:07:36 +0000 Subject: [PATCH 03/15] docs(plan): apply the root's doc-review rulings to the mutation caller 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. --- ...2026-10-10-2149-ci-mutation-caller-plan.md | 229 +++++++++++++----- 1 file changed, 163 insertions(+), 66 deletions(-) diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index 155b68b..c259a77 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -13,22 +13,27 @@ execution: code ## Goal Capsule - **Objective:** every push to `main` runs mutation testing on `@systemfsoftware/api-extractor`'s workflow and schema code, so a surviving mutant turns `main` red. -- **Means:** a thin caller of systemfsoftware's shared `reusable-mutation.yml`, pinned to the commit where unit O1 lets that workflow install the `.sfs-deps` tarballs (KTD1). -- **Product authority:** this plan, then `AGENTS.md`, then `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md`, whose KTD20 and its mutation Key Decision this plan carries out. The front-ends, resolver and phase profile that the 1146 plan's KTD1 put in PR3 are not active scope here. -- **Open blockers:** O1. systemfsoftware `main` (`feb68840`) has `reusable-mutation.yml` from #710 (`9154691`), but its mutation job installs with plain `pnpm install --frozen-lockfile` (`.github/actions/install-deps`) and takes no input that could place `.sfs-deps/` first. This lockfile names `file:.sfs-deps/*.tgz`, so that install fails here. The O1 rev is `` until it lands. -- **Open question for the root:** at `feb68840` the shared run concludes `success` when a package scores below its break threshold, so the shared workflow alone does not yet turn `main` red on a survivor (Dependencies / Assumptions). PR3 delivers the caller and a graded proof either way; whether the red signal comes from O1 or another systemfsoftware unit is the root's call (Outstanding Questions). -- **Execution profile:** PR3 on `feat/mutation`, a `gh stack` layer on `main`. The agent commits and pushes; the operator merges. U1 can run as soon as `ce-work` starts; U2 and U3 start only when the O1 rev exists. -- **Stop conditions:** the O1 rev does not exist (stop after U1, PR3 open and marked blocked); the O1 rev's mutation job still installs without `.sfs-deps` (stop and report to the root); the proof run's summary grades the package below its break threshold (stop; R4 holds the merge). +- **Means:** a thin caller of systemfsoftware's shared `reusable-mutation.yml`, pinned to the commit where unit O1 (systemfsoftware #735) lets that workflow install the `.sfs-deps` tarballs (KTD1). +- **Product authority:** this plan, then `AGENTS.md`, then `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md`, whose KTD20 and its mutation Key Decision this plan carries out. The front-ends, resolver and phase profile that the 1146 plan's KTD1 put in its PR3 slice are not active scope here. +- **Open blockers:** O1 is systemfsoftware #735 (branch `ci/sfs-deps-input`, head `483b9af`, not on `main`). systemfsoftware `main` (`feb68840`) has `reusable-mutation.yml` from #710 (`9154691`), but its mutation job installs with plain `pnpm install --frozen-lockfile` (`.github/actions/install-deps`) and takes no input that could place `.sfs-deps/` first. This lockfile names `file:.sfs-deps/*.tgz`, so that install fails here. The O1 rev is `` until #735 merges. +- **Red signal (root ruling on D2):** O1 does not make a below-break score fail the run. systemfsoftware #733 (head `b80306e`, not on `main`) does. The pin order is in Outstanding Questions. R4 holds either way: no merge without a passing graded score. +- **Execution profile:** the mutation-caller PR on `feat/mutation`, a `gh stack` layer on `main`. The agent commits and pushes; the operator merges. U1 runs now; U2 and U3 start only when the O1 rev exists. +- **Stop conditions:** + - The O1 rev does not exist: stop after U1. + - The pinned rev's mutation job still installs without `.sfs-deps`: stop and report to the root. + - The pinned rev's inputs cannot hold every mutation job to 10 minutes, or a proof-run job takes longer: stop and report it as a question (KTD4). + - The pinned rev requires a `mutation-baseline.json` that the package lacks: stop and report (Risks). + - The proof run grades the package below its break threshold: stop; R4 holds the merge. --- ## Product Contract -Product Contract preservation: refined R4, AE3, AE4 — the proof reads the graded verdict in the run's mutation summary as well as the run's conclusion, because at `feb68840` the conclusion alone does not show a below-break score. Objective unchanged; the gap it now depends on is an open question for the root. Outstanding Question on caller inputs resolved in place (KTD4). +Product Contract preservation: R4, AE3 and AE4 are refined. The proof reads the graded verdict in the run's mutation summary as well as the run's conclusion, because before #733 the conclusion alone does not show a below-break score. R4 also bounds each job at 10 minutes (KTD4). R6 is scoped to this PR's own commits (D1). The Objective is unchanged, and the red signal is ruled to come from systemfsoftware #733 (Outstanding Questions). ### Summary -PR3 adds the mutation caller for this repository: it runs on push to `main` and on manual dispatch, never on a pull request, and calls the shared workflow at the O1 rev. Until that rev exists the caller stays blocked and PR3 carries only this plan and the README status. +The mutation-caller PR adds the mutation caller for this repository. The caller runs on push to `main` and on manual dispatch, never on a pull request, and calls the shared workflow at the O1 rev. Until that rev exists the caller stays blocked, and this PR carries only this plan and the README status. ### Problem Frame @@ -36,68 +41,105 @@ Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break t ### Key Decisions -- **The `.sfs-deps` gap is fixed in systemfsoftware's shared `reusable-mutation.yml` by unit O1, not in this repository.** (session-settled: user-directed — chosen over a copied, forked or hand-rolled mutation workflow here: root ruling, binding.) Governs R2, R3. +- **The `.sfs-deps` gap is fixed in systemfsoftware's shared `reusable-mutation.yml` by unit O1 (#735), not in this repository.** (session-settled: user-directed — chosen over a copied, forked or hand-rolled mutation workflow here: root ruling, binding.) Governs R2, R3. - **Mutation runs on push to `main` and on `workflow_dispatch`, never on `pull_request`.** (session-settled: user-directed — chosen over local or per-PR mutation: carried from the 1146 plan.) Governs R1. - **The proof is one dispatch run on the PR branch, registered by the 1146 plan's workaround.** GitHub dispatches only workflow files that exist on the default branch, so the branch carries a temporary trigger with every job `if:`-skipped, removed before the final head. (session-settled: user-directed — chosen over a push run on `main` as the first evidence: carried from the 1146 plan.) Governs R1, R4. -- **A failing graded score on the proof run blocks the merge.** Stryker has never run on this package and the break threshold is 100, so the first score may fall short; the shared run still concludes `success`, so only the graded score shows it. Governs R4. +- **A failing graded score on the proof run blocks the merge.** Stryker has never run on this package and the break threshold is 100, so the first score may fall short. At a pre-#733 pin the run still concludes `success` and only the graded row shows the failure; from #733 on, the gate fails the run. Survivor fixes land on `main` in a separate layer first (D1). Governs R4. - **No caller file lands before the O1 rev exists.** A push-triggered caller whose `uses:` ref cannot resolve would fail every push to `main`, so the placeholder lives in this plan, not in a workflow file. Governs R3. -- **No package-side work.** PR2's wiring already matches what the shared job runs, so PR3 changes no package file. Governs R6. +- **This PR's own commits do no package-side work.** PR2's wiring already matches what the shared job runs at `feb68840`. Governs R6. ### Requirements **Caller** -- R1. At PR3's final head the caller runs only on push to `main` and on `workflow_dispatch`; it never has a `pull_request` or `merge_group` trigger. +- R1. At this PR's final head the caller runs only on push to `main` and on `workflow_dispatch`; it never has a `pull_request` or `merge_group` trigger. - R2. The caller is a thin `workflow_call` of systemfsoftware's `reusable-mutation.yml` pinned by commit SHA at the O1 rev, with only that workflow's inputs and the permissions its jobs ask for. - R3. Until the O1 rev exists, the caller is recorded as blocked on O1 with the placeholder ``, and no caller file is committed. **Proof** -- R4. Once the O1 rev exists, one `workflow_dispatch` run of the caller on the PR branch concludes `success` and its mutation summary grades `@systemfsoftware/api-extractor` at or above its break threshold; the PR body links the run, and PR3 does not merge without it. +- R4. Once the O1 rev exists, one `workflow_dispatch` run of the caller on the PR branch meets all of these: + - It concludes `success`. + - Its mutation summary grades `@systemfsoftware/api-extractor` at or above its break threshold. + - Every mutation job finishes within 10 minutes. + + The PR body links the run and lists each job's time, and this PR does not merge without such a run. **Docs** -- R5. The README roadmap row for mutation and the PR body name O1's `.sfs-deps` input as the blocker while R3 holds, and the pinned rev once R2 lands. -- R6. `packages/api-extractor/stryker.config.ts`, the package's `mutation` script and its stryker devDependencies stay as PR2 shipped them. +- R5. While R3 holds, the README roadmap row for mutation and the PR body name O1 (systemfsoftware #735) and its `.sfs-deps` input as the blocker. Once R2 lands, they name the pinned rev. +- R6. This PR's own commits leave `packages/api-extractor/stryker.config.ts`, the package's `mutation` script and its stryker devDependencies as PR2 shipped them. Survivor fixes reach this branch only by merging `origin/main`. ### Acceptance Examples - AE1. **Covers R1.** **Given** the caller on `main`, **when** a pull request is opened, **then** no mutation run starts. -- AE2. **Covers R3.** **Given** O1 has not landed, **when** PR3's head is pushed, **then** it contains no caller workflow file and the plan and PR body show `` as the blocker. -- AE3. **Covers R2, R4.** **Given** the O1 rev exists, **when** the caller is dispatched on the PR branch, **then** the shared job installs from `.sfs-deps`, runs the package's `mutation` script, the run concludes `success` and the summary's verdict for the package is a pass; its URL is in the PR body. -- AE4. **Covers R4.** **Given** the dispatch run's summary grades the package below its break threshold, **when** the PR is reviewed, **then** it stays unmerged until a later run on the same caller grades it at or above the threshold. +- AE2. **Covers R3.** **Given** O1 has not landed, **when** this PR's head is pushed, **then** it contains no caller workflow file and the plan and PR body show `` as the blocker. +- AE3. **Covers R2, R4.** **Given** the O1 rev exists, **when** the caller is dispatched on the PR branch, **then** all of these hold, and the run's URL and job times are in the PR body: + - The shared workflow's `sfs-deps` job builds this repository's `.#sfs-deps`, and the mutation job runs the package's `mutation` script. + - Every mutation job finishes within 10 minutes. + - The run concludes `success`. + - The summary grades the package at or above its break threshold (KTD6). +- AE4. **Covers R4, R6.** **Given** the dispatch run grades the package below its break threshold, **when** the PR is reviewed, **then** it stays unmerged until all of these hold: + - A separate layer that kills the survivors is on `main`. + - `feat/mutation` has merged `origin/main`. + - A later dispatch grades the package at or above the threshold. ### Scope Boundaries - No local mutation workflow, composite action or copy of any shared step (R2). +- No tarball-producing job in the caller (root ruling on D4). The pinned rev's own `sfs-deps` job builds this repository's flake output. - No stryker run outside CI. - No mutation on the e2e app: it has no `mutation` script, and the shared planner picks packages by that script. -- Killing survivors the first run finds is separate work outside PR3's wiring; per R4 it lands before PR3 merges. +- Survivors the first run finds are killed in a separate layer that merges to `main` first. Then `feat/mutation` merges `origin/main` (no rebase, no force push) and dispatches again (R4, R6). - Considered, not built: a `.gitignore` entry for `reports/`. Stryker writes reports only inside CI's checkout here (KTD20 of the 1146 plan: no local stryker), so nothing reaches `git status`; a local mutation script would change that. -- Considered, not built: a caller-side check that fails the run on a below-threshold score. That is a step around the shared job, which R2 and the binding O1 ruling exclude; the ask goes upstream (Open Questions). +- Considered, not built: a caller-side check that fails the run on a below-threshold score. That would be a step around the shared job, which R2 and the binding O1 ruling exclude. The red signal comes from systemfsoftware #733 (Outstanding Questions). ### Dependencies / Assumptions -- O1 adds a `.sfs-deps` step or input to `reusable-mutation.yml`'s install; its shape decides the caller's inputs. -- The shared planner finds `@systemfsoftware/api-extractor` through its `mutation` script, and the shared job runs `pnpm --filter mutation` and reads `reports/mutation/mutation.json`, which the shared stryker config writes. -- The shared mutation job fails only when a package writes no report; a below-threshold score still concludes `success` (`mutation-job.ts` `runJob` and `build-mutation-summary.ts` `buildRequireError` at `feb68840`). The graded score is read from the report job's summary. +- O1 (#735) adds a `sfs-deps` job to `reusable-mutation.yml`. The job builds the caller's flake installable, `.#sfs-deps` by default, overridable through the input `sfs-deps-installable`. It stores the tarballs under a key derived from the lockfile's `.sfs-deps/` paths and integrities, and every installing job restores them before its install (#735 `reusable-mutation.yml:45-50,134-160`; #735 plan KTD1, KTD4). +- This repository's flake already exposes `packages..sfs-deps` (`flake.nix:113`, from `sfsDepsOf`). The caller therefore passes no installable and needs no flake change or copy. U2 checks this against #735's interface as merged. +- This repository is the hook-mode caller that #735's AE5 names: its `reusable-checks.yml` call runs `.github/actions/checks-lane` (`ci.yml:41-48`). It is also the first real outside caller of #735's `.sfs-deps` path, so the proof run's link goes in this PR's body and in #735's (root ruling on D4). +- AE5 itself covers the checks lanes, and `reusable-mutation.yml` has no hook. The mutation run therefore proves #735's R1 for `reusable-mutation.yml`. The checks-lane path waits for `ci.yml` to adopt #735. +- At `feb68840`, the shared planner finds `@systemfsoftware/api-extractor` through its `mutation` script. The shared job runs `pnpm --filter mutation` and reads `reports/mutation/mutation.json`, which the shared stryker config writes. +- At `feb68840` and #735, the shared mutation job fails only when a package writes no report; a below-threshold score still concludes `success` (`mutation-job.ts` `runJob` and `build-mutation-summary.ts` `buildRequireError` at `feb68840`). The graded score is read from the report job's summary. +- #733 replaces `mutation-job.ts` and `build-mutation-summary.ts` with `stryker plan`, `coverage`, `run --shard`, `merge` and `gate --baseline`. It keeps `break: 100` and fails the run on a break failure. Its consumer contract requires `@systemfsoftware/stryker-js` in the workspace root, a `mutation` script and an `incremental: true` Stryker config per mutated package, and a committed `mutation-baseline.json` beside each config (#733 `.github/AGENTS.md:32`). ### Outstanding Questions -**Deferred to Planning** — none remain; caller inputs are resolved in KTD4. +**Deferred to Planning:** none remain; caller inputs are resolved in KTD4. + +**For the root** -**For the root (does not block PR3)** +- **Red signal (D2), ruled.** O1 (#735) does not make a below-break score fail the run, and #733 does. + - If #733 merges first, pin the O1 rev at or after #733's merge commit. + - If O1 merges first, pin O1's rev, keep R4's graded-summary read, and re-pin after #733 merges in a follow-up. -- Should O1, or a separate systemfsoftware unit, make the shared mutation run fail when a package's score is below its break threshold? Until it does, `main` shows a below-threshold score only in the run summary. + R4 holds either way: no merge without a passing graded score. +- **Upstream (D8).** The SHA pin does not cover the shared workflow's tag-pinned actions: `actions/cache@v6` (`feb68840` `reusable-mutation.yml:158,271,283`) and `denoland/setup-deno@v2` (`.github/actions/sfs-tools/action.yml:34`). Should systemfsoftware pin them by SHA? +- **Open: baseline file at a #733-or-later pin.** #733 requires `packages/api-extractor/mutation-baseline.json`, and a missing one fails the run with `mutation-baseline-missing` (#733 `reusable-mutation.yml:541-546`). That file is a package change, so R6 keeps it out of this PR. Which layer adds it before U2 pins past #733? +- **Open: 10-minute jobs at a pre-#733 pin.** Before #733, a package's kill cap is twice `target-seconds` (KTD4). If no value of at most 480 holds stryker plus install and build to 600 s, U2 stops here. ### Sources -- systemfsoftware `9154691` (#710) and `main` `feb68840`: `.github/workflows/reusable-mutation.yml` (inputs, `mutation-event-guard`, mutation job install step, report job permissions), `.github/actions/install-deps/action.yml`, `.github/workflows/mutation.yml` (systemfsoftware's own caller), `scripts/tools/test-timings.ts` (`workspacePackagesWith`), `scripts/tools/mutation-job.ts` (`strykerUnderCap`, `runJob`), `scripts/tools/build-mutation-summary.ts` (`buildRequireError`). +- systemfsoftware `9154691` (#710) and `main` `feb68840`: + - `.github/workflows/reusable-mutation.yml`: inputs, `mutation-event-guard`, the mutation job's install step, the report job's permissions and cache keys. + - `.github/actions/install-deps/action.yml`. + - `.github/actions/sfs-tools/action.yml`. + - `.github/workflows/mutation.yml`, systemfsoftware's own caller. + - `scripts/tools/test-timings.ts` (`workspacePackagesWith`). + - `scripts/tools/mutation-job.ts` (`strykerUnderCap`, `runJob`, `labelOf`, `VERDICT_*`). + - `scripts/tools/build-mutation-summary.ts` (`buildRequireError`). +- systemfsoftware #735 (head `483b9af`): `.github/workflows/reusable-mutation.yml` (`sfs-deps-installable`, the `sfs-deps` job, `target-seconds` sizing) and `docs/plans/2026-10-10-1927-ci-sfs-deps-input-plan.md` (KTD1, KTD4, AE5). +- systemfsoftware #733 (head `b80306e`): `.github/workflows/reusable-mutation.yml` (inputs, `SHARD_OVERHEAD_SECONDS`, the plan's target check, `mutation-baseline-missing`) and `.github/AGENTS.md` (consumer prerequisites, caller permissions). - systemfsoftware `732f66a0c8` `packages/toolchain/stryker-config/lib/base.js` (reporters and report paths). - `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md` KTD20 and its Key Decisions on O1, mutation and the dispatch workaround. - GitHub Docs, "Manually running a workflow": `workflow_dispatch` requires the workflow file on the default branch (https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow). -- `README.md` roadmap table, `packages/api-extractor/stryker.config.ts`, `packages/api-extractor/package.json`. -- Compound Packs (`.compound-engineering/config.yaml`, systemfsoftware `df908ddf`): `package-topology/surface-changes-are-versioned` applies through R6: no package byte changes, so PR3 needs no changeset, and the Changeset Check passes on a PR that touches no publishable package. `boundary-testing/pin-dependency-semantics` covers the plan's reliance on the shared workflow's behaviour. PR3 has no code path to hold a differential test, so the SHA pin (KTD1) and U3's graded proof take its place. `cell-architecture` and `schema-laws` govern package source, which PR3 does not touch. +- GitHub validates a called workflow's job permissions against the caller's grant at startup: microsoft/hve-core#2407 and github/gh-aw#21071. +- In this repository: `README.md` roadmap table, `flake.nix` (`packages..sfs-deps`), `.github/workflows/ci.yml` (`checks-lane` hook), `packages/api-extractor/stryker.config.ts` and `packages/api-extractor/package.json`. +- Compound Packs (`.compound-engineering/config.yaml`, systemfsoftware `df908ddf`): + - `package-topology/surface-changes-are-versioned` applies through R6. No package byte changes in this PR's commits, so it needs no changeset, and the Changeset Check passes on a PR that touches no publishable package. + - `boundary-testing/pin-dependency-semantics` covers the plan's reliance on the shared workflow's behaviour. This PR has no code path to hold a differential test, so the SHA pin (KTD1) and U3's graded proof take its place. + - `cell-architecture` and `schema-laws` govern package source, which this PR does not touch. --- @@ -105,38 +147,49 @@ Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break t ### Key Technical Decisions -- KTD1. **One caller file, `.github/workflows/mutation.yml`, calls `reusable-mutation.yml@` with a trailing comment naming O1.** It follows `release.yml` and `ci.yml`: a reusable workflow pinned by full SHA. The shared job's `sfs-tools` step checks out its scripts at `job.workflow_sha`, so the one pin fixes the workflow and the tools together. Covers R2. -- KTD2. **Permissions: `contents: read` at the top, and the mutation job grants `contents: read` plus `actions: write`.** That is systemfsoftware's own caller's grant. The shared plan job reads the timing record (`actions: read`) and the report job deletes per-job artifacts (`actions: write`); a reusable workflow can only narrow the caller's token, and the report job's delete step is `continue-on-error`, so a missing grant fails silently. Covers R2. -- KTD3. **Concurrency `mutation-${{ github.ref }}` with `cancel-in-progress: true`, as systemfsoftware's own caller sets it.** The shared workflow declares none, and two runs on one ref race the stryker incremental cache and the timing record. A cancelled `main` run loses only its own record; the next push re-plans. Covers R1. -- KTD4. **No timing inputs; the shared defaults stand** (`target-seconds` 900, `timeout-minutes` 75, `max-jobs` 20, `max-seconds` 4500), as systemfsoftware's own caller uses them. The brainstorm's "10-minute job budget" applies to `reusable-checks.yml`, not mutation. The only inputs are the ones O1 adds for `.sfs-deps`. Covers R2. +- KTD1. **One caller file, `.github/workflows/mutation.yml`, calls `reusable-mutation.yml@` with a trailing comment naming O1.** It follows `release.yml` and `ci.yml`: a reusable workflow pinned by full SHA. The shared job's `sfs-tools` step checks out its scripts at `job.workflow_sha`, so the one pin fixes the workflow and its tools together. The pin does not fix the shared workflow's tag-pinned actions (`actions/cache@v6`, `denoland/setup-deno@v2`); that is an upstream question (D8). Covers R2. +- KTD2. **Permissions: `contents: read` at the top, and the mutation job grants `contents: read` plus `actions: write`,** as systemfsoftware's own caller does. The shared plan job reads the timing record (`actions: read`), and the report job requests `actions: write` (`feb68840` `reusable-mutation.yml:261-263`). A called workflow's jobs can only narrow the caller's grant. GitHub refuses the run at startup when a called job requests more than the caller grants (microsoft/hve-core#2407, github/gh-aw#21071). U2 re-derives the grant from the pinned rev's job `permissions:` blocks. Covers R2. +- KTD3. **Concurrency `mutation-${{ github.ref }}` with `cancel-in-progress: true`, as systemfsoftware's own caller sets it.** The shared workflow declares no concurrency. Its stryker caches are keyed with the run ID (`feb68840` `reusable-mutation.yml:161,274`), so two runs on one ref do not overwrite each other's entry. The setting stays because systemfsoftware's own caller uses it (D6). Covers R1. +- KTD4. **Every mutation job fits in 10 minutes (root ruling).** The caller passes `target-seconds` of at most 480. `max-jobs` stays at 20, its default at `feb68840` and #735, and is not passed: #733 removes the input, and GitHub refuses an input the called workflow does not define. `timeout-minutes` keeps its default, as a backstop only. The value depends on the pinned rev: + - **From #733 on:** a shard's planned cost, including the 120 s `SHARD_OVERHEAD_SECONDS`, must fit `target-seconds`, or the plan fails (#733 `reusable-mutation.yml:44,134,195-197`). 480 therefore holds a shard inside 10 minutes. + - **Before #733:** a package's kill cap is twice `target-seconds` and a job's budget four times (#735 `reusable-mutation.yml:11,212`), so 480 allows a 960 s package. U2 lowers the value until the cap plus install and build fits 600 s. If no value does, U2 stops and reports it as a question. + + The proof run's per-job times go in the PR body, each at most 10 minutes. No other input is passed, because #735's default installable is this repository's `.#sfs-deps` (Dependencies). Covers R2, R4. - KTD5. **Triggers at the final head: `push` with `branches: [main]`, and `workflow_dispatch`.** The shared `mutation-event-guard` refuses `pull_request`, `pull_request_target` and `merge_group`, but a trigger that never fires is the cheaper guarantee. Covers R1. -- KTD6. **The proof reads the graded score from the report job's summary, not the run's conclusion.** Per the Dependencies entry, conclusion `success` only proves a report was written. Covers R4. +- KTD6. **The proof reads the graded score from the report job's summary, not only the run's conclusion.** + - **Before #733:** the summary row is labelled by directory, `packages/api-extractor`, and its verdict cell is `[OK]`, `[WARN]` or `[FAIL]` (`feb68840` `scripts/tools/mutation-job.ts:74,333-335`). A `success` conclusion proves only that a report was written. + - **From #733 on:** `gate --baseline` fails the run on a break failure, and the proof still reads the package's summary row. + + Covers R4. ### High-Level Technical Design -The caller's life across PR3, directional: +The caller's life across this PR, directional: ```mermaid stateDiagram-v2 - [*] --> Blocked: PR3 opened, O1 unlanded (U1) + [*] --> Blocked: PR opened, O1 unlanded (U1) Blocked --> Wired: O1 rev exists, caller pinned to it (U2) Wired --> Registered: temporary push trigger, every job if-skipped (U3) - Registered --> Proved: dispatch on feat/mutation, summary grades pass + Registered --> Proved: dispatch on feat/mutation, summary grades pass, jobs within 10 min Registered --> Held: summary grades below break - Held --> Registered: survivors killed in a separate layer + Held --> Registered: survivor layer on main, origin/main merged Proved --> Final: temporary trigger removed, CI green Final --> [*]: operator merges; push to main runs the caller ``` ### Assumptions -- The first `main` run plans from an empty timing record: the shared plan job reads only records from the default branch, so the PR-branch dispatch's record does not carry over. It pays the full stryker cost once, within the 75-minute default. -- Change reuse between runs is stryker's incremental file, restored from the `stryker-incremental-` cache; the shared plan job passes no turbo dry run, so no push is skipped as unchanged. +- The first `main` run plans from an empty timing record. The shared plan job reads records only from the default branch, so the PR-branch dispatch neither carries its record over nor splits work by measured cost. +- Before #733, the per-package limit is the kill cap, twice `target-seconds` (#735 `reusable-mutation.yml:212`), not the 75-minute job timeout. +- From #733 on, a project with no incremental record is priced from `seed-costs`, whose default is `.github/mutation-seed-costs.json`. This repository has no such file, so each mutant is priced at 1 s (#733 `reusable-mutation.yml:15-22`). +- Change reuse between runs is stryker's incremental file, restored from the `stryker-incremental-` cache. Before #733, the shared plan job passes no turbo dry run, so no push is skipped as unchanged. ### Risks -- O1 may land a `.sfs-deps` mechanism whose inputs differ from this plan's guess; U2 takes whatever O1 defines and records it in the PR body (R5). -- The 1146 workaround may not register the file for dispatch; then U3 stops and reports, and the root decides between merging without a proof run and another registration route. +- #735's interface may change before it merges. U2 checks the merged interface and records it in the PR body (R5). +- The 1146 workaround may not register the file for dispatch. U3 then stops and reports, and the root picks another registration route. The proof rule stays: no merge without a passing dispatch run (D10). +- A #733-or-later pin needs `packages/api-extractor/mutation-baseline.json`, which R6 keeps out of this PR. U2 stops before pinning past #733 until that file is on `main`. --- @@ -148,49 +201,64 @@ stateDiagram-v2 - **Requirements:** R3, R5. - **Dependencies:** none. - **Files:** `README.md`. -- **Approach:** rewrite the "Mutation testing on `main`" row's status to "blocked on O1: the shared `reusable-mutation.yml` installs without `.sfs-deps`". No other row changes. +- **Approach:** rewrite the status of the "Mutation testing on `main`" row to "blocked on O1 (systemfsoftware #735): the shared `reusable-mutation.yml` installs without `.sfs-deps`". No other row changes. - **Patterns to follow:** the table's existing status wording. - **Test scenarios:** Test expectation: none -- documentation only. -- **Verification:** the row names O1 and the missing `.sfs-deps` input, and no file under `.github/workflows/` mentions `reusable-mutation`. +- **Verification:** the row names O1 and the missing `.sfs-deps` input, and at U1's commit no file under `.github/workflows/` mentions `reusable-mutation` (F1). ### U2. Add the mutation caller pinned to the O1 rev -- **Goal:** `.github/workflows/mutation.yml` calls the shared workflow at the O1 rev with the triggers, grant and concurrency of KTD2, KTD3 and KTD5. +- **Goal:** `.github/workflows/mutation.yml` calls the shared workflow at the O1 rev with the triggers, grant, concurrency and sizing of KTD2-KTD5. - **Requirements:** R1, R2, R5. - **Dependencies:** U1; the O1 rev exists on systemfsoftware `main`. - **Files:** `.github/workflows/mutation.yml` (new), `README.md` (row names the pinned rev). - **Approach:** - 1. Confirm the O1 rev's `reusable-mutation.yml` places `.sfs-deps` before its install; if it does not, stop (Goal Capsule stop condition). - 2. Write the caller per KTD1-KTD5, passing only the inputs O1 defines for `.sfs-deps` (KTD4). - 3. Update the README row to the pinned rev. -- **Patterns to follow:** `.github/workflows/release.yml` (SHA pin with trailing comment, explicit permissions, concurrency); systemfsoftware's `.github/workflows/mutation.yml` (grant and concurrency). + 1. Pick the rev by the D2 ruling (Outstanding Questions): at or after #733's merge commit if #733 merged first, otherwise O1's merge commit. Before pinning past #733, confirm `packages/api-extractor/mutation-baseline.json` is on `main`, or stop (Risks). + 2. Inspect the pinned O1 interface for how `.sfs-deps` arrives. Confirm that its `sfs-deps` job builds `.#sfs-deps` by default and that every installing job restores the tarballs before its install. If either check fails, stop (D4). + 3. Re-derive the grant from the pinned rev's job `permissions:` blocks (KTD2). + 4. Set `target-seconds` from the pinned rev's sizing rule (KTD4). If no value of at most 480 holds a job to 600 s, stop and report it as a question. + 5. Write the caller per KTD1-KTD5. + 6. Update the README row to the pinned rev. +- **Patterns to follow:** + - `.github/workflows/release.yml`: SHA pin with trailing comment, explicit permissions, concurrency. + - systemfsoftware's `.github/workflows/mutation.yml`: grant and concurrency. - **Execution note:** CI configuration; prove it by the U3 dispatch run, not by tests. - **Test scenarios:** checked against the file at review and observed in U3's run; no repository test can exercise a workflow trigger (no code path). - - Happy path (AE3): `workflow_dispatch` on `feat/mutation` → `mutation-event-guard` passes, the plan job lists `@systemfsoftware/api-extractor` as the only package, the mutation job installs from `.sfs-deps` and runs its `mutation` script. + - Happy path (AE3): `workflow_dispatch` on `feat/mutation`: + - `mutation-event-guard` passes. + - The `sfs-deps` job builds `.#sfs-deps`. + - The plan lists `@systemfsoftware/api-extractor` as the only package. + - The mutation job installs and runs the package's `mutation` script. - Edge (AE1): a pull request against `main` → no run starts, because the file has no `pull_request` or `merge_group` trigger. - Edge: a push to a branch other than `main` at the final head → no run starts (`branches: [main]`). - Edge (KTD3): a second dispatch on the same ref while one runs → the earlier run is cancelled and only the later one reports. - - Error: the O1 rev drops the `.sfs-deps` placement → the install step fails and the mutation job is red; U2 step 1 catches this before the pin lands. -- **Verification:** `actionlint` passes on the file; the pinned SHA resolves to a commit on systemfsoftware `main` that contains `reusable-mutation.yml`. + - Edge (KTD4): the plan's predicted job cost at the chosen `target-seconds` stays within 600 s. + - Error: the O1 rev drops the `.sfs-deps` restore → the install step fails and the mutation job is red. U2 step 2 catches this before the pin lands. +- **Verification:** `actionlint` passes on the file, and the pinned SHA resolves to a commit on systemfsoftware `main` that contains `reusable-mutation.yml`. ### U3. Prove the caller with one dispatch run -- **Goal:** one dispatch run on `feat/mutation` that meets R4, linked from the PR body, with no temporary trigger left at the final head. +- **Goal:** one dispatch run on `feat/mutation` that meets R4, linked from this PR's body and #735's, with no temporary trigger left at the final head. - **Requirements:** R1, R4, R5; AE3, AE4. - **Dependencies:** U2. - **Files:** `.github/workflows/mutation.yml` (temporary trigger added, then removed). - **Approach:** - 1. Add a temporary `push` trigger for `feat/mutation` with every job `if:`-skipped on that event, push, and confirm the skipped run registered the workflow. + 1. Add a temporary `push` trigger for `feat/mutation` with every job `if:`-skipped on that event, and push. Confirm the workflow is dispatchable: `GET /repos/systemfsoftware/api-extractor-effect/actions/workflows/mutation.yml` returns it. The skipped run's conclusion is not the signal (D12). 2. Dispatch the caller with `--ref feat/mutation` and watch it to a conclusion. - 3. Read the report job's summary row for `@systemfsoftware/api-extractor` (KTD6). - 4. Remove the temporary trigger in the next commit; record the run URL and the graded score in the PR body. -- **Patterns to follow:** the 1146 plan's dispatch workaround; PR #3's off-main dispatch proof recorded in its body. + 3. Read the report job's summary row for the package (KTD6) and every mutation job's duration. + 4. Remove the temporary trigger in the next commit. Record the run URL, the graded score and the per-job times in the PR body, and link the run in #735's body. + 5. If the run grades the package below its break threshold, follow AE4: the survivor layer lands on `main` first, `feat/mutation` merges `origin/main` (no rebase, no force push), and step 2 runs again. +- **Patterns to follow:** the 1146 plan's dispatch workaround; the off-main dispatch proof of the merged `fix/release-unversioned` PR (run 38082603176), recorded in its body. - **Test scenarios:** observed in the dispatch run and the PR body; the run is the proof. - - Happy path (AE3): the run concludes `success` and the summary's row for `@systemfsoftware/api-extractor` reads verdict pass with thresholds `100/100/100`. - - Error (AE4): any survivor → the row's verdict is fail while the run still concludes `success`; PR3 stays unmerged and the survivors go to a separate layer. + - Happy path (AE3): the run concludes `success`, and every mutation job finishes within 10 minutes. Before #733, the summary row `packages/api-extractor` reads `[OK]` with thresholds `100/100/100`; from #733 on, the gate passes. + - Error (AE4): any survivor. Before #733 the row reads `[FAIL]` while the run concludes `success`; from #733 on the run fails. Either way this PR stays unmerged, and the survivors go to a separate layer (D1). + - Error: a mutation job runs longer than 10 minutes → stop and report it as a question (KTD4). - Error: the package writes no report → the mutation job concludes `failure` (`runJob` returns not ok); stop and report. - - Edge: the temporary push run on `feat/mutation` → every job is skipped and the run concludes `success`, which registers the file for dispatch. -- **Verification:** the run concluded `success`, the summary grades the package at or above 100, the final head's triggers are exactly `push: main` and `workflow_dispatch`, and CI is green on that head. + - Edge: the temporary push run on `feat/mutation` skips every job. Whatever its conclusion, the Actions API lists `mutation.yml` afterwards, and a dispatch with `--ref feat/mutation` is accepted. +- **Verification:** + - The run concluded `success`, and the summary grades the package at or above 100. + - Every mutation job took at most 10 minutes. + - The final head's triggers are exactly `push: main` and `workflow_dispatch`, and CI is green on that head. --- @@ -198,16 +266,45 @@ stateDiagram-v2 | Gate | Applies to | Done signal | | --- | --- | --- | +| `git grep -n reusable-mutation -- .github/workflows/` | U1's commit | exit 1 (no match) | | `nix run nixpkgs#actionlint -- .github/workflows/mutation.yml` | U2, U3 | exit 0 | | `git -C merge-base --is-ancestor origin/main` | U2 | exit 0 | -| Dispatch run of `mutation.yml` on `feat/mutation`, watched with `xd://github` `run_watch` | U3 | conclusion `success`; summary grades `@systemfsoftware/api-extractor` at or above its break threshold | -| `pnpm lint`, `pnpm typecheck`, `pnpm test` inside `nix develop` | every head | exit 0 (no package file changes, so these guard against accidental edits) | +| Dispatch run of `mutation.yml` on `feat/mutation`, watched with `xd://github` `run_watch` | U3 | conclusion `success`; summary grades `@systemfsoftware/api-extractor` at or above its break threshold; every mutation job at most 10 minutes | +| `git diff --name-only origin/main...HEAD -- packages/ apps/` | every head | prints nothing (R6) | | `ci.yml` and Changeset Check on the PR head | every head | green | --- ## Definition of Done -- While O1 is unlanded: U1 and this plan are committed and pushed, PR3 is open as a `gh stack` layer on `main`, and its body names `` as the blocker (AE2). -- After O1 lands: U2 and U3 are done, the PR body links the dispatch run and its graded score, the final head carries no temporary trigger, and CI is green on it. -- No package file differs from `main` (R6), and no abandoned workflow attempts remain in the diff. +- **While O1 is unlanded:** U1 and this plan are committed and pushed, the PR is open as a `gh stack` layer on `main`, and its body names `` as the blocker (AE2). +- **After O1 lands:** + - U2 and U3 are done. + - The PR body links the dispatch run with its graded score and per-job times, and #735's body links the same run. + - The final head carries no temporary trigger, and CI is green on it. +- **Throughout:** this PR's own commits change no package file (`git diff --name-only origin/main...HEAD -- packages/ apps/` prints nothing; R6), and no abandoned workflow attempts remain in the diff. + +--- + +## Doc-review rulings + +The root ruled on the report-only `ce-doc-review` of this plan at `d8c5cb1`. Five reviewers ran: coherence, feasibility, scope, adversarial and security. No cross-model pass ran. + +| ID | Sev | Finding | Ruling | +| --- | --- | --- | --- | +| D1 | P1 | Survivors were killed outside this PR, yet a pass was required before merge and R6 forbade package changes | Accepted: survivor fixes land on `main` in a separate layer first; `feat/mutation` merges `origin/main` (no rebase, no force push) and dispatches again; R6 covers this PR's own commits (Scope Boundaries, R6, AE4, DoD) | +| D2 | P1 | Nothing made `main` red on a survivor | Root: no new unit. O1 (#735) does not fix it, and systemfsoftware #733 does. Pin order and R4 are in Outstanding Questions | +| D3 | P1 | The Assumptions named the 75-minute timeout as the limit; before #733 the per-package cap is twice `target-seconds`, and a branch dispatch never splits by measured cost | Accepted for the Assumptions text. Its "pass no explicit `target-seconds`" half is superseded by the root's KTD4 ruling | +| D4 | P2 | O1's interface might need a caller-side tarball job | Root: no tarball job in the caller. #735's `sfs-deps` job builds this repository's `.#sfs-deps` (`flake.nix:113`), and U2 checks #735's final interface. This repository is the caller #735's AE5 names, and the proof link goes in both PR bodies (Dependencies, U2, U3) | +| D5 | P2 | "PR3" named both this PR and the merged GitHub PR #3 | Accepted: "the mutation-caller PR (`feat/mutation`)", and the merged PR by its branch `fix/release-unversioned` | +| D6 | P2 | KTD3's cache race did not exist: the cache keys include the run ID | Accepted: reasoning corrected, setting kept (KTD3) | +| D7 | P2 | Drop `actions: write` from the grant | Rejected: the report job requests `actions: write`, so the caller must grant it | +| L1 | P2 | KTD2 said a missing grant fails silently; it stops the run at startup | Accepted: KTD2 corrected, and U2 re-derives the grant from the pinned rev (U2 step 3) | +| D8 | P2 | The SHA pin does not cover the shared workflow's tag-pinned actions | Accepted: stated in KTD1 and asked upstream (Outstanding Questions) | +| D9 | P2 | Local lint, typecheck and test repeated what CI already runs | Accepted: replaced by the `packages/ apps/` diff check (Verification Contract) | +| D10 | P2 | The Risks fallback "merge without a proof run" contradicted R4 | Accepted: the root picks another registration route, and the proof rule stays (Risks) | +| D11 | P3 | The summary row is labelled `packages/api-extractor` with verdict `[OK]`, not a package name and "pass" | Accepted (KTD6, AE3, U3) | +| D12 | P3 | A temporary push run whose jobs are all skipped may not conclude `success` | Accepted: U3 checks that the workflow is dispatchable, not the run's result | +| F1 | P3 | U1's "no workflow mentions `reusable-mutation`" check read as applying to the final head | Accepted: scoped to U1's commit | + +KTD4 is a root overrule outside these rows. Every job fits in 10 minutes: `target-seconds` is at most 480, `max-jobs` stays at 20, and `timeout-minutes` keeps its default only as a backstop. The proof run's per-job times go in the PR body. If the shared inputs cannot hold jobs to 10 minutes, the work stops and the gap is reported as a question. From f9001a2d0a44f46672b1d8a45d0d68cc14390e4e Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 00:07:45 +0000 Subject: [PATCH 04/15] docs(readme): mark main's mutation testing blocked on systemfsoftware 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. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f61b770..35b4e4a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The design and its requirements live in | systemfsoftware and stryker-js-effect packages from the flake (`.sfs-deps`) | in this repository | | Lint, typecheck, build and test on systemfsoftware's reusable checks; tsdown, vitest and lint presets | in this repository | | Releases: changesets and GitHub Releases through pnpm-release-management | in this repository | -| Mutation testing on `main` | waits on systemfsoftware's reusable mutation workflow (#710) | +| Mutation testing on `main` | blocked on O1 (systemfsoftware #735): the shared `reusable-mutation.yml` installs without `.sfs-deps` | ## The verdict From 8c193c9eacb899627761f677a7360eea30cdea88 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 05:17:02 +0000 Subject: [PATCH 05/15] ci: set every job and step timeout to a uniform 60 minutes 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. --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/release.yml | 2 +- .../plans/2026-10-10-2149-ci-mutation-caller-plan.md | 8 ++++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d95291d..6b700b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: sfs-deps: name: systemfsoftware tarballs from the flake runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 60 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 @@ -47,10 +47,10 @@ jobs: actions: read uses: systemfsoftware/systemfsoftware/.github/workflows/reusable-checks.yml@732f66a0c85819b2005f9e0930a595bc4de91ee1 with: - lanes: '[{"id": "lint", "name": "lint", "timeout-minutes": 10, "run": "pnpm lint"}, {"id": "typecheck", "name": "typecheck", "timeout-minutes": 10, "run": "pnpm typecheck"}, {"id": "build", "name": "build · attw", "timeout-minutes": 10, "run": "pnpm build && pnpm --filter @systemfsoftware/api-extractor attw"}]' - timeout-minutes: 10 + lanes: '[{"id": "lint", "name": "lint", "timeout-minutes": 60, "run": "pnpm lint"}, {"id": "typecheck", "name": "typecheck", "timeout-minutes": 60, "run": "pnpm typecheck"}, {"id": "build", "name": "build · attw", "timeout-minutes": 60, "run": "pnpm build && pnpm --filter @systemfsoftware/api-extractor attw"}]' + timeout-minutes: 60 max-seconds: 600 - plan-timeout-minutes: 10 + plan-timeout-minutes: 60 host-bytes: name: Output bytes on ${{ matrix.arch }} @@ -63,7 +63,7 @@ jobs: - arch: arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} - timeout-minutes: 10 + timeout-minutes: 60 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -88,7 +88,7 @@ jobs: name: Same bytes on every host needs: host-bytes runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 60 steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5fc27cc..c63d715 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: name: no release off main, so the run concludes success, not skipped if: github.ref != 'refs/heads/main' runs-on: ubuntu-latest - timeout-minutes: 1 + timeout-minutes: 60 permissions: {} steps: - run: echo "Release writes only from refs/heads/main; $GITHUB_REF runs nothing." diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index c259a77..e7f94a8 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -308,3 +308,11 @@ The root ruled on the report-only `ce-doc-review` of this plan at `d8c5cb1`. Fiv | F1 | P3 | U1's "no workflow mentions `reusable-mutation`" check read as applying to the final head | Accepted: scoped to U1's commit | KTD4 is a root overrule outside these rows. Every job fits in 10 minutes: `target-seconds` is at most 480, `max-jobs` stays at 20, and `timeout-minutes` keeps its default only as a backstop. The proof run's per-job times go in the PR body. If the shared inputs cannot hold jobs to 10 minutes, the work stops and the gap is reported as a question. + +## Timeout ruling (2026-10-11) + +Standing rule (Ryan): a timeout is a backstop for a stuck job, never a budget enforced by cancelling. Every job and step timeout in this repository is a uniform, explicit 60 minutes. Budgets stay measured: each job's wall time is reported in the PR body. + +- Applied to `.github/workflows/ci.yml` (the `sfs-deps`, `host-bytes` and `host-compare` jobs, plus the three lanes' `timeout-minutes`, `timeout-minutes` and `plan-timeout-minutes` passed to `reusable-checks.yml`) and to `release.yml`'s `noop` job. `.github/actions/checks-lane` has no timeout. +- `reusable-checks.yml` at the pinned `732f66a0` takes each of these as a `type: number` input with no upper bound. The lane value goes straight into `timeout-minutes` (`:212`), `timeout-minutes` into the test job (`:55-59`, `:239`), and `plan-timeout-minutes` into the plan and timings jobs (`:60-64`, `:139`, `:316`). `max-seconds: 600` is a planner budget, not a timeout, and stays. +- This amends KTD4: U2's caller passes `timeout-minutes: 60` and `report-timeout-minutes: 60` explicitly, rather than keeping the shared defaults. The 10-minute figure stays as a measured budget whose per-job times are reported, not a limit enforced by cancelling. From 6569cae452e0b9aa03c91e435c0896d690721c94 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 05:41:49 +0000 Subject: [PATCH 06/15] ci(mutation): call systemfsoftware's reusable mutation workflow at O1 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. --- .github/workflows/mutation.yml | 25 +++++++++++++++++++ README.md | 2 +- ...2026-10-10-2149-ci-mutation-caller-plan.md | 12 ++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/mutation.yml diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..5e233a8 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,25 @@ +name: Mutation + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: mutation-${{ github.sha }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + mutation: + permissions: + contents: read + actions: write + uses: systemfsoftware/systemfsoftware/.github/workflows/reusable-mutation.yml@02bf9653b1ca2b0a2951baf2743bb3f70d19fc77 + with: + target-seconds: 480 + max-jobs: 20 + timeout-minutes: 60 + report-timeout-minutes: 60 diff --git a/README.md b/README.md index 35b4e4a..201aff1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The design and its requirements live in | systemfsoftware and stryker-js-effect packages from the flake (`.sfs-deps`) | in this repository | | Lint, typecheck, build and test on systemfsoftware's reusable checks; tsdown, vitest and lint presets | in this repository | | Releases: changesets and GitHub Releases through pnpm-release-management | in this repository | -| Mutation testing on `main` | blocked on O1 (systemfsoftware #735): the shared `reusable-mutation.yml` installs without `.sfs-deps` | +| Mutation testing on `main` | in this repository: systemfsoftware's reusable mutation workflow at `02bf965` (#735), incremental per push | ## The verdict diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index e7f94a8..1e9b873 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -112,7 +112,7 @@ Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break t - **Red signal (D2), ruled.** O1 (#735) does not make a below-break score fail the run, and #733 does. - If #733 merges first, pin the O1 rev at or after #733's merge commit. - - If O1 merges first, pin O1's rev, keep R4's graded-summary read, and re-pin after #733 merges in a follow-up. + - If O1 merges first, pin O1's rev, keep R4's graded-summary read, and re-pin after #733 merges in a follow-up. **Taken (2026-10-11):** O1 merged first as `02bf9653b1ca2b0a2951baf2743bb3f70d19fc77`, and #733 is still open. The caller pins `02bf965`; the re-pin past #733 is a later PR. R4 holds either way: no merge without a passing graded score. - **Upstream (D8).** The SHA pin does not cover the shared workflow's tag-pinned actions: `actions/cache@v6` (`feb68840` `reusable-mutation.yml:158,271,283`) and `denoland/setup-deno@v2` (`.github/actions/sfs-tools/action.yml:34`). Should systemfsoftware pin them by SHA? @@ -316,3 +316,13 @@ Standing rule (Ryan): a timeout is a backstop for a stuck job, never a budget en - Applied to `.github/workflows/ci.yml` (the `sfs-deps`, `host-bytes` and `host-compare` jobs, plus the three lanes' `timeout-minutes`, `timeout-minutes` and `plan-timeout-minutes` passed to `reusable-checks.yml`) and to `release.yml`'s `noop` job. `.github/actions/checks-lane` has no timeout. - `reusable-checks.yml` at the pinned `732f66a0` takes each of these as a `type: number` input with no upper bound. The lane value goes straight into `timeout-minutes` (`:212`), `timeout-minutes` into the test job (`:55-59`, `:239`), and `plan-timeout-minutes` into the plan and timings jobs (`:60-64`, `:139`, `:316`). `max-seconds: 600` is a planner budget, not a timeout, and stays. - This amends KTD4: U2's caller passes `timeout-minutes: 60` and `report-timeout-minutes: 60` explicitly, rather than keeping the shared defaults. The 10-minute figure stays as a measured budget whose per-job times are reported, not a limit enforced by cancelling. + +## U2 rulings (2026-10-11) + +- **Pin:** `02bf9653b1ca2b0a2951baf2743bb3f70d19fc77` (#735), taken only once every systemfsoftware run on it had concluded success. Nothing past #733 is pinned. +- **Concurrency (amends KTD3):** the group is `mutation-${{ github.sha }}` with `cancel-in-progress: false`, so no merge's run cancels or replaces another merge's run. +- **Inputs (amends KTD4):** `target-seconds: 480`, `max-jobs: 20`, `timeout-minutes: 60` and `report-timeout-minutes: 60`, each passed explicitly. `sfs-deps-installable` keeps its default `.#sfs-deps`, which evaluates on x86_64-linux and aarch64-linux. +- **Full corpus (ruling (a)):** + - Push and dispatch runs are both incremental through the reusable's cache: an unconditional restore with the `stryker-incremental--` prefix (`reusable-mutation.yml:198-205`), and the report job's restore and save (`:312-328`). + - Full-corpus never runs per push, which meets the root rule. The first run on a cold cache covers the whole corpus. + - A full-corpus dispatch input belongs to the shared-tooling unit and is an open item. There is no local workaround, no cache-busting input and no copy of the reusable. From 7cd02ba5036ec410b9b368c2821310ab4e4d1d86 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 05:42:09 +0000 Subject: [PATCH 07/15] ci(mutation): temporarily trigger on feat/mutation to register the workflow 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. --- .github/workflows/mutation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 5e233a8..51b237a 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -2,7 +2,7 @@ name: Mutation on: push: - branches: [main] + branches: [main, feat/mutation] workflow_dispatch: concurrency: @@ -14,6 +14,7 @@ permissions: jobs: mutation: + if: github.event_name != 'push' || github.ref == 'refs/heads/main' permissions: contents: read actions: write From 87b52afb150ea33fd74ee256288076f3ba53bdc2 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 06:14:08 +0000 Subject: [PATCH 08/15] ci(mutation): remove the temporary feat/mutation 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. --- .github/workflows/mutation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 51b237a..5e233a8 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -2,7 +2,7 @@ name: Mutation on: push: - branches: [main, feat/mutation] + branches: [main] workflow_dispatch: concurrency: @@ -14,7 +14,6 @@ permissions: jobs: mutation: - if: github.event_name != 'push' || github.ref == 'refs/heads/main' permissions: contents: read actions: write From 1ebd34e1f1455c357b043f46f6da087707a4889b Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 07:06:47 +0000 Subject: [PATCH 09/15] build(deps): take the stryker family from stryker-js-effect 9e81669 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. --- ...2026-10-10-2149-ci-mutation-caller-plan.md | 15 + flake.lock | 786 +++++++++++++++--- flake.nix | 10 +- pnpm-lock.yaml | 124 +-- pnpm-workspace.yaml | 18 +- 5 files changed, 756 insertions(+), 197 deletions(-) diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index 1e9b873..e2b386e 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -326,3 +326,18 @@ Standing rule (Ryan): a timeout is a backstop for a stuck job, never a budget en - Push and dispatch runs are both incremental through the reusable's cache: an unconditional restore with the `stryker-incremental--` prefix (`reusable-mutation.yml:198-205`), and the report job's restore and save (`:312-328`). - Full-corpus never runs per push, which meets the root rule. The first run on a cold cache covers the whole corpus. - A full-corpus dispatch input belongs to the shared-tooling unit and is an open item. There is no local workaround, no cache-busting input and no copy of the reusable. + +## Proof run 1 ruling (2026-10-11) + +- **Result:** run [38117312976](https://github.com/systemfsoftware/api-extractor-effect/actions/runs/38117312976), dispatched on `feat/mutation` at `7cd02ba` on a cold cache, concluded `failure`. Stryker's dry run failed 1 of 104 tests: `src/schema-laws.test.ts` reported `recursionBudget is declared on ApiItem but nothing materialized it — Budget_RequiresTransform`. Nothing was mutated, and the summary row reads `packages/api-extractor | no report | [WARN]`. The mutation job ran 682 s, 642 s of it in the dry run. That figure is re-measured on the next run. +- **Cause:** + - `effect-schema-recursion-budget@2.0.0` injects the arbitrary hook only when the first argument of `.annotate(...)` on a `Schema.suspend(...)` is an object literal (`dist/index.mjs`, `injectionOf`). Stryker's ObjectLiteral mutant wraps that object in a conditional, so nothing is injected. + - `stryker-ignorer-effect-schema-declarations@0.2.1` does not ignore that object. Its generation keys apply only to the second argument of `makeFilter`, `makeFilterGroup` and `declare`. It ignores the first argument of `annotate` only when every key in it is a documentation key. +- **Ruling (root):** + - The fix is upstream and already released. Ignorer 0.3.0 ignores an `annotate`/`annotations` first argument that holds `recursionBudget`, whatever its other keys. It is released at stryker-js-effect `9e81669b84711a9635eaa97b17dd13efea9f0c8c` with the rest of the stryker family (stryker-js 19.0.0). + - The `stryker-published` flake input moves to that commit, and every stryker tarball comes from it. `.sfs-deps`, the overrides in `pnpm-workspace.yaml` and `pnpm-lock.yaml` are regenerated from it. + - This is toolchain only. No `// Stryker disable` comment and no package source edit, so R6 holds. + - The next proof run is dispatched by the root. No temporary trigger is re-added, because the workflow is already registered. +- **Shared-tooling open items** (not this repository): + - `mutation-job.ts:102-105` runs Stryker under `timeout --kill-after=60 <2 x target-seconds>`, which is 960 s at 480: a kill cap inside the shared tooling. + - `stryker-config@0.1.0` declares peer `@systemfsoftware/stryker-js: ^17.0.0`, which neither 18 nor 19 satisfies. diff --git a/flake.lock b/flake.lock index f5a06b3..67855f1 100644 --- a/flake.lock +++ b/flake.lock @@ -46,12 +46,105 @@ "comment-checker_10": { "inputs": { "nixpkgs": [ + "stryker-published", + "stryker-published", "systemfsoftware", - "pnpm-release-management", "nixpkgs" ], "rust-overlay": "rust-overlay_12" }, + "locked": { + "lastModified": 1789259917, + "narHash": "sha256-ciFWJFfpl8xTFJTnoquMb58RA7iVqvpi4Sfda9qyFTw=", + "owner": "systemfsoftware", + "repo": "comment-checker", + "rev": "00cc6a6bac7756d1e59d30fd566623e9b21443eb", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "comment-checker", + "type": "github" + } + }, + "comment-checker_11": { + "inputs": { + "nixpkgs": [ + "stryker-published", + "systemfsoftware", + "nixpkgs" + ], + "rust-overlay": "rust-overlay_14" + }, + "locked": { + "lastModified": 1790884787, + "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", + "owner": "systemfsoftware", + "repo": "comment-checker", + "rev": "3bfedd853c1d6dca666e65e71e9f8a31d47db336", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "comment-checker", + "type": "github" + } + }, + "comment-checker_12": { + "inputs": { + "nixpkgs": [ + "stryker-published", + "systemfsoftware", + "pnpm-release-management", + "nixpkgs" + ], + "rust-overlay": "rust-overlay_15" + }, + "locked": { + "lastModified": 1790884787, + "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", + "owner": "systemfsoftware", + "repo": "comment-checker", + "rev": "3bfedd853c1d6dca666e65e71e9f8a31d47db336", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "comment-checker", + "type": "github" + } + }, + "comment-checker_13": { + "inputs": { + "nixpkgs": [ + "systemfsoftware", + "nixpkgs" + ], + "rust-overlay": "rust-overlay_17" + }, + "locked": { + "lastModified": 1790884787, + "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", + "owner": "systemfsoftware", + "repo": "comment-checker", + "rev": "3bfedd853c1d6dca666e65e71e9f8a31d47db336", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "comment-checker", + "type": "github" + } + }, + "comment-checker_14": { + "inputs": { + "nixpkgs": [ + "systemfsoftware", + "pnpm-release-management", + "nixpkgs" + ], + "rust-overlay": "rust-overlay_18" + }, "locked": { "lastModified": 1790884787, "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", @@ -117,7 +210,7 @@ "pnpm-release-management", "nixpkgs" ], - "rust-overlay": "rust-overlay_4" + "rust-overlay": "rust-overlay_5" }, "locked": { "lastModified": 1790884787, @@ -140,7 +233,7 @@ "stryker-published", "nixpkgs" ], - "rust-overlay": "rust-overlay_5" + "rust-overlay": "rust-overlay_6" }, "locked": { "lastModified": 1790884787, @@ -164,7 +257,7 @@ "pnpm-release-management", "nixpkgs" ], - "rust-overlay": "rust-overlay_6" + "rust-overlay": "rust-overlay_7" }, "locked": { "lastModified": 1790884787, @@ -185,17 +278,17 @@ "nixpkgs": [ "stryker-published", "stryker-published", - "systemfsoftware", + "stryker-published", "nixpkgs" ], - "rust-overlay": "rust-overlay_7" + "rust-overlay": "rust-overlay_8" }, "locked": { - "lastModified": 1789259917, - "narHash": "sha256-ciFWJFfpl8xTFJTnoquMb58RA7iVqvpi4Sfda9qyFTw=", + "lastModified": 1790884787, + "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", "owner": "systemfsoftware", "repo": "comment-checker", - "rev": "00cc6a6bac7756d1e59d30fd566623e9b21443eb", + "rev": "3bfedd853c1d6dca666e65e71e9f8a31d47db336", "type": "github" }, "original": { @@ -208,17 +301,19 @@ "inputs": { "nixpkgs": [ "stryker-published", - "systemfsoftware", + "stryker-published", + "stryker-published", + "pnpm-release-management", "nixpkgs" ], "rust-overlay": "rust-overlay_9" }, "locked": { - "lastModified": 1789259917, - "narHash": "sha256-ciFWJFfpl8xTFJTnoquMb58RA7iVqvpi4Sfda9qyFTw=", + "lastModified": 1790884787, + "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", "owner": "systemfsoftware", "repo": "comment-checker", - "rev": "00cc6a6bac7756d1e59d30fd566623e9b21443eb", + "rev": "3bfedd853c1d6dca666e65e71e9f8a31d47db336", "type": "github" }, "original": { @@ -230,17 +325,20 @@ "comment-checker_9": { "inputs": { "nixpkgs": [ + "stryker-published", + "stryker-published", + "stryker-published", "systemfsoftware", "nixpkgs" ], - "rust-overlay": "rust-overlay_11" + "rust-overlay": "rust-overlay_10" }, "locked": { - "lastModified": 1790884787, - "narHash": "sha256-XZz0bdSY5zjlRDme0dfC98oYcAf3WOpMCueoU39iXjY=", + "lastModified": 1789259917, + "narHash": "sha256-ciFWJFfpl8xTFJTnoquMb58RA7iVqvpi4Sfda9qyFTw=", "owner": "systemfsoftware", "repo": "comment-checker", - "rev": "3bfedd853c1d6dca666e65e71e9f8a31d47db336", + "rev": "00cc6a6bac7756d1e59d30fd566623e9b21443eb", "type": "github" }, "original": { @@ -249,6 +347,42 @@ "type": "github" } }, + "crane": { + "locked": { + "lastModified": 1787326676, + "narHash": "sha256-lWhBbBvC05/xwivKBBiM2YNizpmgqCgyOIzomvRuwxs=", + "owner": "ipetkov", + "repo": "crane", + "rev": "692f7e9ef2ece8125b466f66f2af532b3edaed0d", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "ref": "v0.24.0", + "repo": "crane", + "type": "github" + } + }, + "gritlint": { + "inputs": { + "crane": "crane", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay_4" + }, + "locked": { + "lastModified": 1791623562, + "narHash": "sha256-izDg0irkawru/08NJjNNHGdyp8+Wx81U9IpI8sENklA=", + "owner": "systemfsoftware", + "repo": "gritlint", + "rev": "1d60be38284be2c9a8af73f67334d43a841c9fd7", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "gritlint", + "type": "github" + } + }, "importPnpmLock": { "inputs": { "nixpkgs": [ @@ -271,6 +405,30 @@ "type": "github" } }, + "importPnpmLock_10": { + "inputs": { + "nixpkgs": [ + "systemfsoftware", + "pnpm-release-management", + "nixpkgs" + ], + "systems": "systems_10" + }, + "locked": { + "lastModified": 1790096829, + "narHash": "sha256-mnjMXjY/fo5OQH/iYNPijIqo0jAb/z7EhHtfXLlzOrs=", + "owner": "Scrumplex", + "repo": "importPnpmLock.nix", + "rev": "ca18d47b0e98a4b51404f69ce5ab51efb6ebc151", + "type": "github" + }, + "original": { + "owner": "Scrumplex", + "repo": "importPnpmLock.nix", + "rev": "ca18d47b0e98a4b51404f69ce5ab51efb6ebc151", + "type": "github" + } + }, "importPnpmLock_2": { "inputs": { "nixpkgs": [ @@ -391,11 +549,62 @@ "importPnpmLock_7": { "inputs": { "nixpkgs": [ + "stryker-published", + "stryker-published", + "stryker-published", + "nixpkgs" + ], + "systems": "systems_7" + }, + "locked": { + "lastModified": 1790096829, + "narHash": "sha256-mnjMXjY/fo5OQH/iYNPijIqo0jAb/z7EhHtfXLlzOrs=", + "owner": "Scrumplex", + "repo": "importPnpmLock.nix", + "rev": "ca18d47b0e98a4b51404f69ce5ab51efb6ebc151", + "type": "github" + }, + "original": { + "owner": "Scrumplex", + "repo": "importPnpmLock.nix", + "type": "github" + } + }, + "importPnpmLock_8": { + "inputs": { + "nixpkgs": [ + "stryker-published", + "stryker-published", + "stryker-published", + "pnpm-release-management", + "nixpkgs" + ], + "systems": "systems_8" + }, + "locked": { + "lastModified": 1790096829, + "narHash": "sha256-mnjMXjY/fo5OQH/iYNPijIqo0jAb/z7EhHtfXLlzOrs=", + "owner": "Scrumplex", + "repo": "importPnpmLock.nix", + "rev": "ca18d47b0e98a4b51404f69ce5ab51efb6ebc151", + "type": "github" + }, + "original": { + "owner": "Scrumplex", + "repo": "importPnpmLock.nix", + "rev": "ca18d47b0e98a4b51404f69ce5ab51efb6ebc151", + "type": "github" + } + }, + "importPnpmLock_9": { + "inputs": { + "nixpkgs": [ + "stryker-published", "systemfsoftware", "pnpm-release-management", "nixpkgs" ], - "systems": "systems_7" + "systems": "systems_9" }, "locked": { "lastModified": 1790096829, @@ -429,6 +638,22 @@ } }, "nixpkgs_2": { + "locked": { + "lastModified": 1791191277, + "narHash": "sha256-i8KuaINOYLvB2oQikGvtRmdZRxATRxsI9Yad2pp7dEA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "aa48d347080940b8a2b8d2f48228674e280a3514", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1790822859, "narHash": "sha256-69xHQhAeMAD2wDXO7T2pcOZIF9Sga2W+JkmY2a11Ops=", @@ -444,7 +669,7 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { "lastModified": 1790822859, "narHash": "sha256-69xHQhAeMAD2wDXO7T2pcOZIF9Sga2W+JkmY2a11Ops=", @@ -460,7 +685,23 @@ "type": "github" } }, - "nixpkgs_4": { + "nixpkgs_5": { + "locked": { + "lastModified": 1790822859, + "narHash": "sha256-69xHQhAeMAD2wDXO7T2pcOZIF9Sga2W+JkmY2a11Ops=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c59305bab2065cfecc4944690d9eedbb56f3a9fa", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_6": { "locked": { "lastModified": 1790185690, "narHash": "sha256-xJ+X4hBtOcAFGBOe5nAMyMUeF9foJBmIOu3NjBqBycU=", @@ -476,7 +717,7 @@ "type": "github" } }, - "nixpkgs_5": { + "nixpkgs_7": { "locked": { "lastModified": 1790185690, "narHash": "sha256-xJ+X4hBtOcAFGBOe5nAMyMUeF9foJBmIOu3NjBqBycU=", @@ -492,7 +733,23 @@ "type": "github" } }, - "nixpkgs_6": { + "nixpkgs_8": { + "locked": { + "lastModified": 1791191277, + "narHash": "sha256-i8KuaINOYLvB2oQikGvtRmdZRxATRxsI9Yad2pp7dEA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "aa48d347080940b8a2b8d2f48228674e280a3514", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_9": { "locked": { "lastModified": 1791191277, "narHash": "sha256-i8KuaINOYLvB2oQikGvtRmdZRxATRxsI9Yad2pp7dEA=", @@ -508,126 +765,319 @@ "type": "github" } }, - "pnpm-release-management": { + "pnpm-release-management": { + "inputs": { + "comment-checker": "comment-checker_2", + "importPnpmLock": "importPnpmLock_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791596974, + "narHash": "sha256-fE5LJdLoFR/aTaKhWCf3Xzgn0fx19AYQB2uEVVr25wM=", + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "b9b4a1d48cc16241b33f9afd8ab99b82b05ce04f", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "b9b4a1d48cc16241b33f9afd8ab99b82b05ce04f", + "type": "github" + } + }, + "pnpm-release-management_2": { + "inputs": { + "comment-checker": "comment-checker_4", + "importPnpmLock": "importPnpmLock_4", + "nixpkgs": [ + "stryker-published", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791502532, + "narHash": "sha256-qdQxG+HsoOPPyoYqaMvcIHH02lHKoOe0qW2at/BpLug=", + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "21fe6192e38d913c2f325d1185dcc14b380cb348", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "type": "github" + } + }, + "pnpm-release-management_3": { + "inputs": { + "comment-checker": "comment-checker_6", + "importPnpmLock": "importPnpmLock_6", + "nixpkgs": [ + "stryker-published", + "stryker-published", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791502532, + "narHash": "sha256-qdQxG+HsoOPPyoYqaMvcIHH02lHKoOe0qW2at/BpLug=", + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "21fe6192e38d913c2f325d1185dcc14b380cb348", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "type": "github" + } + }, + "pnpm-release-management_4": { + "inputs": { + "comment-checker": "comment-checker_8", + "importPnpmLock": "importPnpmLock_8", + "nixpkgs": [ + "stryker-published", + "stryker-published", + "stryker-published", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791495074, + "narHash": "sha256-C2yy1cFxnpslzErFrZE7EEPrD4QU6GvgVNO2L0Kvm5M=", + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "524505a2e829203e91a5c0d17e7cccee7094e998", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "type": "github" + } + }, + "pnpm-release-management_5": { + "inputs": { + "comment-checker": "comment-checker_12", + "importPnpmLock": "importPnpmLock_9", + "nixpkgs": [ + "stryker-published", + "systemfsoftware", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791510278, + "narHash": "sha256-UXWg+FzruRo4IyLB8iYjOu4+sODj5N3cE03+VILreGs=", + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "8cd6e83009531de040cd9c03e1370b4966fd2a12", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "8cd6e83009531de040cd9c03e1370b4966fd2a12", + "type": "github" + } + }, + "pnpm-release-management_6": { + "inputs": { + "comment-checker": "comment-checker_14", + "importPnpmLock": "importPnpmLock_10", + "nixpkgs": [ + "systemfsoftware", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791510278, + "narHash": "sha256-UXWg+FzruRo4IyLB8iYjOu4+sODj5N3cE03+VILreGs=", + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "8cd6e83009531de040cd9c03e1370b4966fd2a12", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "pnpm-release-management", + "rev": "8cd6e83009531de040cd9c03e1370b4966fd2a12", + "type": "github" + } + }, + "root": { + "inputs": { + "are-the-types-wrong-effect": "are-the-types-wrong-effect", + "nixpkgs": [ + "systemfsoftware", + "nixpkgs" + ], + "pnpm-release-management": "pnpm-release-management", + "stryker-published": "stryker-published", + "systemfsoftware": "systemfsoftware_4" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "are-the-types-wrong-effect", + "comment-checker", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1787367552, + "narHash": "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_10": { + "inputs": { + "nixpkgs": [ + "stryker-published", + "stryker-published", + "stryker-published", + "systemfsoftware", + "comment-checker", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1787367552, + "narHash": "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_11": { "inputs": { - "comment-checker": "comment-checker_2", - "importPnpmLock": "importPnpmLock_2", "nixpkgs": [ + "stryker-published", + "stryker-published", + "stryker-published", + "systemfsoftware", "nixpkgs" ] }, "locked": { - "lastModified": 1791596974, - "narHash": "sha256-fE5LJdLoFR/aTaKhWCf3Xzgn0fx19AYQB2uEVVr25wM=", - "owner": "systemfsoftware", - "repo": "pnpm-release-management", - "rev": "b9b4a1d48cc16241b33f9afd8ab99b82b05ce04f", + "lastModified": 1790234952, + "narHash": "sha256-tjQa1vDoHJir4lasZTakz/xgmsfB6SRjeQqaIbefFOk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "ed34466c59b766481248959a21a6e46cf0c60468", "type": "github" }, "original": { - "owner": "systemfsoftware", - "repo": "pnpm-release-management", - "rev": "b9b4a1d48cc16241b33f9afd8ab99b82b05ce04f", + "owner": "oxalica", + "repo": "rust-overlay", "type": "github" } }, - "pnpm-release-management_2": { + "rust-overlay_12": { "inputs": { - "comment-checker": "comment-checker_4", - "importPnpmLock": "importPnpmLock_4", "nixpkgs": [ "stryker-published", + "stryker-published", + "systemfsoftware", + "comment-checker", "nixpkgs" ] }, "locked": { - "lastModified": 1791502532, - "narHash": "sha256-qdQxG+HsoOPPyoYqaMvcIHH02lHKoOe0qW2at/BpLug=", - "owner": "systemfsoftware", - "repo": "pnpm-release-management", - "rev": "21fe6192e38d913c2f325d1185dcc14b380cb348", + "lastModified": 1787367552, + "narHash": "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9", "type": "github" }, "original": { - "owner": "systemfsoftware", - "repo": "pnpm-release-management", + "owner": "oxalica", + "repo": "rust-overlay", "type": "github" } }, - "pnpm-release-management_3": { + "rust-overlay_13": { "inputs": { - "comment-checker": "comment-checker_6", - "importPnpmLock": "importPnpmLock_6", "nixpkgs": [ "stryker-published", "stryker-published", + "systemfsoftware", "nixpkgs" ] }, "locked": { - "lastModified": 1791495074, - "narHash": "sha256-C2yy1cFxnpslzErFrZE7EEPrD4QU6GvgVNO2L0Kvm5M=", - "owner": "systemfsoftware", - "repo": "pnpm-release-management", - "rev": "524505a2e829203e91a5c0d17e7cccee7094e998", + "lastModified": 1790234952, + "narHash": "sha256-tjQa1vDoHJir4lasZTakz/xgmsfB6SRjeQqaIbefFOk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "ed34466c59b766481248959a21a6e46cf0c60468", "type": "github" }, "original": { - "owner": "systemfsoftware", - "repo": "pnpm-release-management", + "owner": "oxalica", + "repo": "rust-overlay", "type": "github" } }, - "pnpm-release-management_4": { + "rust-overlay_14": { "inputs": { - "comment-checker": "comment-checker_10", - "importPnpmLock": "importPnpmLock_7", "nixpkgs": [ + "stryker-published", "systemfsoftware", + "comment-checker", "nixpkgs" ] }, "locked": { - "lastModified": 1791510278, - "narHash": "sha256-UXWg+FzruRo4IyLB8iYjOu4+sODj5N3cE03+VILreGs=", - "owner": "systemfsoftware", - "repo": "pnpm-release-management", - "rev": "8cd6e83009531de040cd9c03e1370b4966fd2a12", + "lastModified": 1790756160, + "narHash": "sha256-NPs4nNLxCpho3Ctj8O7t/bzmX0zR8r0zndKghAFEz5U=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "ed3a19fd0439ed618ec5fe1e12f0ba69a8be38b5", "type": "github" }, "original": { - "owner": "systemfsoftware", - "repo": "pnpm-release-management", - "rev": "8cd6e83009531de040cd9c03e1370b4966fd2a12", + "owner": "oxalica", + "repo": "rust-overlay", "type": "github" } }, - "root": { + "rust-overlay_15": { "inputs": { - "are-the-types-wrong-effect": "are-the-types-wrong-effect", "nixpkgs": [ + "stryker-published", "systemfsoftware", - "nixpkgs" - ], - "pnpm-release-management": "pnpm-release-management", - "stryker-published": "stryker-published", - "systemfsoftware": "systemfsoftware_3" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "are-the-types-wrong-effect", + "pnpm-release-management", "comment-checker", "nixpkgs" ] }, "locked": { - "lastModified": 1787367552, - "narHash": "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=", + "lastModified": 1790756160, + "narHash": "sha256-NPs4nNLxCpho3Ctj8O7t/bzmX0zR8r0zndKghAFEz5U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9", + "rev": "ed3a19fd0439ed618ec5fe1e12f0ba69a8be38b5", "type": "github" }, "original": { @@ -636,7 +1086,7 @@ "type": "github" } }, - "rust-overlay_10": { + "rust-overlay_16": { "inputs": { "nixpkgs": [ "stryker-published", @@ -645,11 +1095,11 @@ ] }, "locked": { - "lastModified": 1790234952, - "narHash": "sha256-tjQa1vDoHJir4lasZTakz/xgmsfB6SRjeQqaIbefFOk=", + "lastModified": 1790928989, + "narHash": "sha256-5r3MN/Rx7N9tLapCBmUq6/zCc3C9J7gNif1HybD1fJQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ed34466c59b766481248959a21a6e46cf0c60468", + "rev": "368fee9beaab04ca6fe7af28db63caa9badb22fa", "type": "github" }, "original": { @@ -658,7 +1108,7 @@ "type": "github" } }, - "rust-overlay_11": { + "rust-overlay_17": { "inputs": { "nixpkgs": [ "systemfsoftware", @@ -680,7 +1130,7 @@ "type": "github" } }, - "rust-overlay_12": { + "rust-overlay_18": { "inputs": { "nixpkgs": [ "systemfsoftware", @@ -703,7 +1153,7 @@ "type": "github" } }, - "rust-overlay_13": { + "rust-overlay_19": { "inputs": { "nixpkgs": [ "systemfsoftware", @@ -772,17 +1222,16 @@ "inputs": { "nixpkgs": [ "stryker-published", - "pnpm-release-management", - "comment-checker", + "gritlint", "nixpkgs" ] }, "locked": { - "lastModified": 1790756160, - "narHash": "sha256-NPs4nNLxCpho3Ctj8O7t/bzmX0zR8r0zndKghAFEz5U=", + "lastModified": 1790928989, + "narHash": "sha256-5r3MN/Rx7N9tLapCBmUq6/zCc3C9J7gNif1HybD1fJQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ed3a19fd0439ed618ec5fe1e12f0ba69a8be38b5", + "rev": "368fee9beaab04ca6fe7af28db63caa9badb22fa", "type": "github" }, "original": { @@ -795,7 +1244,7 @@ "inputs": { "nixpkgs": [ "stryker-published", - "stryker-published", + "pnpm-release-management", "comment-checker", "nixpkgs" ] @@ -819,7 +1268,6 @@ "nixpkgs": [ "stryker-published", "stryker-published", - "pnpm-release-management", "comment-checker", "nixpkgs" ] @@ -843,17 +1291,17 @@ "nixpkgs": [ "stryker-published", "stryker-published", - "systemfsoftware", + "pnpm-release-management", "comment-checker", "nixpkgs" ] }, "locked": { - "lastModified": 1787367552, - "narHash": "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=", + "lastModified": 1790756160, + "narHash": "sha256-NPs4nNLxCpho3Ctj8O7t/bzmX0zR8r0zndKghAFEz5U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9", + "rev": "ed3a19fd0439ed618ec5fe1e12f0ba69a8be38b5", "type": "github" }, "original": { @@ -867,16 +1315,17 @@ "nixpkgs": [ "stryker-published", "stryker-published", - "systemfsoftware", + "stryker-published", + "comment-checker", "nixpkgs" ] }, "locked": { - "lastModified": 1790234952, - "narHash": "sha256-tjQa1vDoHJir4lasZTakz/xgmsfB6SRjeQqaIbefFOk=", + "lastModified": 1790756160, + "narHash": "sha256-NPs4nNLxCpho3Ctj8O7t/bzmX0zR8r0zndKghAFEz5U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ed34466c59b766481248959a21a6e46cf0c60468", + "rev": "ed3a19fd0439ed618ec5fe1e12f0ba69a8be38b5", "type": "github" }, "original": { @@ -889,17 +1338,19 @@ "inputs": { "nixpkgs": [ "stryker-published", - "systemfsoftware", + "stryker-published", + "stryker-published", + "pnpm-release-management", "comment-checker", "nixpkgs" ] }, "locked": { - "lastModified": 1787367552, - "narHash": "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=", + "lastModified": 1790756160, + "narHash": "sha256-NPs4nNLxCpho3Ctj8O7t/bzmX0zR8r0zndKghAFEz5U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9", + "rev": "ed3a19fd0439ed618ec5fe1e12f0ba69a8be38b5", "type": "github" }, "original": { @@ -911,10 +1362,35 @@ "stryker-published": { "inputs": { "comment-checker": "comment-checker_3", + "gritlint": "gritlint", "importPnpmLock": "importPnpmLock_3", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "pnpm-release-management": "pnpm-release-management_2", "stryker-published": "stryker-published_2", + "systemfsoftware": "systemfsoftware_3" + }, + "locked": { + "lastModified": 1791670316, + "narHash": "sha256-Miv5t105V+AXDByHpb39LTpAAQhsQORYwZZC6R2cvdQ=", + "owner": "systemfsoftware", + "repo": "stryker-js-effect", + "rev": "9e81669b84711a9635eaa97b17dd13efea9f0c8c", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "stryker-js-effect", + "rev": "9e81669b84711a9635eaa97b17dd13efea9f0c8c", + "type": "github" + } + }, + "stryker-published_2": { + "inputs": { + "comment-checker": "comment-checker_5", + "importPnpmLock": "importPnpmLock_5", + "nixpkgs": "nixpkgs_4", + "pnpm-release-management": "pnpm-release-management_3", + "stryker-published": "stryker-published_3", "systemfsoftware": "systemfsoftware_2" }, "locked": { @@ -932,12 +1408,12 @@ "type": "github" } }, - "stryker-published_2": { + "stryker-published_3": { "inputs": { - "comment-checker": "comment-checker_5", - "importPnpmLock": "importPnpmLock_5", - "nixpkgs": "nixpkgs_3", - "pnpm-release-management": "pnpm-release-management_3", + "comment-checker": "comment-checker_7", + "importPnpmLock": "importPnpmLock_7", + "nixpkgs": "nixpkgs_5", + "pnpm-release-management": "pnpm-release-management_4", "systemfsoftware": "systemfsoftware" }, "locked": { @@ -957,9 +1433,9 @@ }, "systemfsoftware": { "inputs": { - "comment-checker": "comment-checker_7", - "nixpkgs": "nixpkgs_4", - "rust-overlay": "rust-overlay_8" + "comment-checker": "comment-checker_9", + "nixpkgs": "nixpkgs_6", + "rust-overlay": "rust-overlay_11" }, "locked": { "lastModified": 1790907431, @@ -977,9 +1453,9 @@ }, "systemfsoftware_2": { "inputs": { - "comment-checker": "comment-checker_8", - "nixpkgs": "nixpkgs_5", - "rust-overlay": "rust-overlay_10" + "comment-checker": "comment-checker_10", + "nixpkgs": "nixpkgs_7", + "rust-overlay": "rust-overlay_13" }, "locked": { "lastModified": 1790907431, @@ -997,10 +1473,31 @@ }, "systemfsoftware_3": { "inputs": { - "comment-checker": "comment-checker_9", - "nixpkgs": "nixpkgs_6", - "pnpm-release-management": "pnpm-release-management_4", - "rust-overlay": "rust-overlay_13" + "comment-checker": "comment-checker_11", + "nixpkgs": "nixpkgs_8", + "pnpm-release-management": "pnpm-release-management_5", + "rust-overlay": "rust-overlay_16" + }, + "locked": { + "lastModified": 1791532978, + "narHash": "sha256-sTU04XlVKzLOLmRY9JK/ghnhgcLPRDDLCY4B7N10+m8=", + "owner": "systemfsoftware", + "repo": "systemfsoftware", + "rev": "b11483c230fa85a7c9bf44c2124acc30b941c4bd", + "type": "github" + }, + "original": { + "owner": "systemfsoftware", + "repo": "systemfsoftware", + "type": "github" + } + }, + "systemfsoftware_4": { + "inputs": { + "comment-checker": "comment-checker_13", + "nixpkgs": "nixpkgs_9", + "pnpm-release-management": "pnpm-release-management_6", + "rust-overlay": "rust-overlay_19" }, "locked": { "lastModified": 1791616521, @@ -1032,6 +1529,21 @@ "type": "github" } }, + "systems_10": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "systems_2": { "locked": { "lastModified": 1681028828, @@ -1121,6 +1633,36 @@ "repo": "default", "type": "github" } + }, + "systems_8": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_9": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 9936beb..d2a4926 100644 --- a/flake.nix +++ b/flake.nix @@ -9,10 +9,12 @@ # `packageManager` pins. systemfsoftware.url = "github:systemfsoftware/systemfsoftware/732f66a0c85819b2005f9e0930a595bc4de91ee1"; nixpkgs.follows = "systemfsoftware/nixpkgs"; - # stryker-js-effect at the commit of `@systemfsoftware/stryker-js@v18.1.0`. - # Nothing follows: its own lock reproduces the released tarballs byte for - # byte, the integrity each release tag records. - stryker-published.url = "github:systemfsoftware/stryker-js-effect/27b1075905a6c69e74eff163567ac25dd4839a1b"; + # stryker-js-effect at the release commit of `@systemfsoftware/stryker-js@v19.0.0` + # and `@systemfsoftware/stryker-ignorer-effect-schema-declarations@v0.3.0`. + # Every stryker package comes from this one commit. Nothing follows: its own + # lock reproduces the released tarballs byte for byte, the integrity each + # release tag records. + stryker-published.url = "github:systemfsoftware/stryker-js-effect/9e81669b84711a9635eaa97b17dd13efea9f0c8c"; # are-the-types-wrong-effect at `@systemfsoftware/arethetypeswrong-cli@v4.2.0`, # for its `attw` binary. Nothing follows, for the same reason. are-the-types-wrong-effect.url = "github:systemfsoftware/are-the-types-wrong-effect/d43a8020f7dc82a32de410823c6da5298c53a3b6"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2176976..49438f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,18 +225,18 @@ overrides: '@systemfsoftware/vitest': file:.sfs-deps/vitest-2.0.0.tgz '@systemfsoftware/vitest-config': file:.sfs-deps/vitest-config-0.1.0.tgz '@systemfsoftware/stryker-framework-interface': file:.sfs-deps/stryker-framework-interface-0.0.1.tgz - '@systemfsoftware/stryker-ignorer-effect-schema-declarations': file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz + '@systemfsoftware/stryker-ignorer-effect-schema-declarations': file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz '@systemfsoftware/stryker-ignorer-in-source-vitest-block': file:.sfs-deps/stryker-ignorer-in-source-vitest-block-0.1.2.tgz '@systemfsoftware/stryker-ignorer-interface': file:.sfs-deps/stryker-ignorer-interface-0.1.2.tgz '@systemfsoftware/stryker-ignorer-kit': file:.sfs-deps/stryker-ignorer-kit-0.1.2.tgz - '@systemfsoftware/stryker-js': file:.sfs-deps/stryker-js-18.1.0.tgz - '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz - '@systemfsoftware/stryker-js-instrumenter': file:.sfs-deps/stryker-js-instrumenter-12.1.2.tgz - '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz - '@systemfsoftware/stryker-js-plugin-runtime': file:.sfs-deps/stryker-js-plugin-runtime-7.0.3.tgz - '@systemfsoftware/stryker-js-typescript-checker': file:.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz - '@systemfsoftware/stryker-js-vitest-runner': file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz - '@systemfsoftware/stryker-js-vm-runner@npm:@systemfsoftware/stryker-js-vitest-runner@^9.0.1': file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz + '@systemfsoftware/stryker-js': file:.sfs-deps/stryker-js-19.0.0.tgz + '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz + '@systemfsoftware/stryker-js-instrumenter': file:.sfs-deps/stryker-js-instrumenter-13.0.0.tgz + '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz + '@systemfsoftware/stryker-js-plugin-runtime': file:.sfs-deps/stryker-js-plugin-runtime-7.0.4.tgz + '@systemfsoftware/stryker-js-typescript-checker': file:.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz + '@systemfsoftware/stryker-js-vitest-runner': file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz + '@systemfsoftware/stryker-js-vm-runner@npm:@systemfsoftware/stryker-js-vitest-runner@^9.0.2': file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz importers: @@ -320,22 +320,22 @@ importers: version: file:.sfs-deps/oxlint-config-recommended-4.0.0.tgz(effect@4.0.2)(oxlint-tsgolint@7.0.2001)(oxlint@1.82.0(oxlint-tsgolint@7.0.2001))(typescript@7.0.2) '@systemfsoftware/stryker-config': specifier: file:../../.sfs-deps/stryker-config-0.1.0.tgz - version: file:.sfs-deps/stryker-config-0.1.0.tgz(@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-18.1.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))) + version: file:.sfs-deps/stryker-config-0.1.0.tgz(@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-19.0.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))) '@systemfsoftware/stryker-ignorer-effect-schema-declarations': - specifier: file:../../.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz - version: file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz(rolldown@1.2.13) + specifier: file:../../.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz + version: file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz(rolldown@1.2.13) '@systemfsoftware/stryker-ignorer-in-source-vitest-block': specifier: file:../../.sfs-deps/stryker-ignorer-in-source-vitest-block-0.1.2.tgz version: file:.sfs-deps/stryker-ignorer-in-source-vitest-block-0.1.2.tgz(rolldown@1.2.13) '@systemfsoftware/stryker-js': - specifier: file:../../.sfs-deps/stryker-js-18.1.0.tgz - version: file:.sfs-deps/stryker-js-18.1.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))) + specifier: file:../../.sfs-deps/stryker-js-19.0.0.tgz + version: file:.sfs-deps/stryker-js-19.0.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))) '@systemfsoftware/stryker-js-typescript-checker': - specifier: file:../../.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz - version: file:.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(rolldown@1.2.13)(supports-color@8.1.1) + specifier: file:../../.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz + version: file:.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(rolldown@1.2.13)(supports-color@8.1.1) '@systemfsoftware/stryker-js-vitest-runner': - specifier: file:../../.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz - version: file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))) + specifier: file:../../.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz + version: file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))) '@systemfsoftware/tsconfig': specifier: file:../../.sfs-deps/tsconfig-2.0.1.tgz version: file:.sfs-deps/tsconfig-2.0.1.tgz @@ -1327,9 +1327,9 @@ packages: resolution: {integrity: sha512-3Sw+RlwsFsl5mlDwgvgy/YvR5TOSfxa7CLRDkr7qWHXcppUkOmJTFbCMTgKjm67zXY6aMmZb/5JZG85pGc9A9A==, tarball: file:.sfs-deps/stryker-framework-interface-0.0.1.tgz} version: 0.0.1 - '@systemfsoftware/stryker-ignorer-effect-schema-declarations@file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz': - resolution: {integrity: sha512-5NIzS3PK361vXAevHLPcKrkwze597VbLsrC0utH/6VdWD9Y1/m53z9dpwp1hSe9ZRIJ3HKW7/PKTSXzemn7q3Q==, tarball: file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz} - version: 0.2.1 + '@systemfsoftware/stryker-ignorer-effect-schema-declarations@file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz': + resolution: {integrity: sha512-5QEfkMBS3CNX/+UpURHlhKIpLPKpAG62HYMrgeJPmbAGXNjzaaaluecuRfWZLfIcbuL6yYCZjGBAfeOFO7vgPA==, tarball: file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz} + version: 0.3.0 '@systemfsoftware/stryker-ignorer-in-source-vitest-block@file:.sfs-deps/stryker-ignorer-in-source-vitest-block-0.1.2.tgz': resolution: {integrity: sha512-sESsrNlXfdav+of+yBaOH/fE8I0jASa0paIvc/6lasId0heWRVlaSTAlaFLTlXX6vSgxAgjaOTT3OFERPnY6Dw==, tarball: file:.sfs-deps/stryker-ignorer-in-source-vitest-block-0.1.2.tgz} @@ -1343,40 +1343,40 @@ packages: resolution: {integrity: sha512-tCKgNbVWU7WPbTxLd1XO+MUFJKo1JmEtwCe56cOpS40NkZN11u8KLr9BQRbM1I8opLSgzqGqsCSwRJsCUPhE+w==, tarball: file:.sfs-deps/stryker-ignorer-kit-0.1.2.tgz} version: 0.1.2 - '@systemfsoftware/stryker-js-cli-contract@file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz': - resolution: {integrity: sha512-Al6/xLfkql9rhXDAPkleSD69Q+iqRxyK2+ZlYPmJo21JI8hFApX3abL4h6w1fFhZ+vNF8/9dZbDr1MIQRzjn+w==, tarball: file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz} - version: 0.4.0 + '@systemfsoftware/stryker-js-cli-contract@file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz': + resolution: {integrity: sha512-P8SWK3EN6Uj4basgc6K49k7w9gUXGprVcMuncGHBjMvD8tWQd4Ncj+qTUzGf7U5FiTtOKHXCflFZUhdCjl9LHg==, tarball: file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz} + version: 0.5.0 engines: {node: '>=20.0.0'} - '@systemfsoftware/stryker-js-instrumenter@file:.sfs-deps/stryker-js-instrumenter-12.1.2.tgz': - resolution: {integrity: sha512-+9atN/9bLwoGIYtacpk+DUZpWa/Onpy3DfQqKGKXcds73/ki1BeXi29Y/PpXBdPStLbqtWsoHTT88i4agwBqWQ==, tarball: file:.sfs-deps/stryker-js-instrumenter-12.1.2.tgz} - version: 12.1.2 + '@systemfsoftware/stryker-js-instrumenter@file:.sfs-deps/stryker-js-instrumenter-13.0.0.tgz': + resolution: {integrity: sha512-Q5B6DYUHcGPt7nlSTFy4rHd90qalywyPge2YPSlT7RipZFHIZUYrcXunfkwDIHKqvTQ69r6Lg07+mIxazov6vg==, tarball: file:.sfs-deps/stryker-js-instrumenter-13.0.0.tgz} + version: 13.0.0 engines: {node: '>=20.0.0'} - '@systemfsoftware/stryker-js-plugin-interface@file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz': - resolution: {integrity: sha512-DOJ2Ed4I2KpwnkkHjk3KY+LcA8DKXr6e9hsVPK4+j7kXKbH6nGVJwVP3uC/EWJL3ihbH1Mwmr3j6kn63LibKzA==, tarball: file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz} - version: 15.0.0 + '@systemfsoftware/stryker-js-plugin-interface@file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz': + resolution: {integrity: sha512-MgNILzdCo9pyBFdWl+p24UJQ+xxJCPPQe0wCFOJ5cGwZ1hKoZ4esOSyS/xKVvb0n1JZyQgnhZM7pbIo0a32zNQ==, tarball: file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz} + version: 16.0.0 engines: {node: '>=20.0.0'} - '@systemfsoftware/stryker-js-plugin-runtime@file:.sfs-deps/stryker-js-plugin-runtime-7.0.3.tgz': - resolution: {integrity: sha512-wQ1YXpe2Jnj49WikhigGk6TI+IHMLAkmGT+mvGZK6PDKls4fZax1RSVv/0ZpoACB4c0L/Rtme+Tl2Rh/inAwrQ==, tarball: file:.sfs-deps/stryker-js-plugin-runtime-7.0.3.tgz} - version: 7.0.3 + '@systemfsoftware/stryker-js-plugin-runtime@file:.sfs-deps/stryker-js-plugin-runtime-7.0.4.tgz': + resolution: {integrity: sha512-A7G00zFj+5fADVN+24QMNrdRZ6fQWMtptZ3NMhim4SrYHRl01vO4hZ4Dxx0CbF8Sitvjzo9WXieD5Qguh+p+nA==, tarball: file:.sfs-deps/stryker-js-plugin-runtime-7.0.4.tgz} + version: 7.0.4 engines: {node: '>=22.18.0'} - '@systemfsoftware/stryker-js-typescript-checker@file:.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz': - resolution: {integrity: sha512-SM3mFEyj3q3/3CgJs+1WXLBpBtfVKmN4ADLNxVIER57AbIdMAbBr/bU0kVhvJmUcxMEqQYy+8fiA/tgmgOsWdA==, tarball: file:.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz} - version: 8.3.1 + '@systemfsoftware/stryker-js-typescript-checker@file:.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz': + resolution: {integrity: sha512-gDNwIti807JuH72C9O11Me7OxKI1sA8seeZlo1j6CedZBvpSuST4Iagc9U2jkMtTcbNChMHLqEQNtYm2E+2dGQ==, tarball: file:.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz} + version: 8.3.2 - '@systemfsoftware/stryker-js-vitest-runner@file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz': - resolution: {integrity: sha512-a0yptek9pPAQwLf2rnCy67ZSy8geb2+/A+Gh99PJxyyr2h5FEucARZIYHBV6H65OctJF40CZpshcJo6WnfKyXA==, tarball: file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz} - version: 9.0.1 + '@systemfsoftware/stryker-js-vitest-runner@file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz': + resolution: {integrity: sha512-JuxiL90UVEBBTP8SjvSG1l3ZdXCWZn9T1agKydgp2HefQZzP01qV1oidBwht609WqKXlkHBfCmHg1hIuF2M1FQ==, tarball: file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz} + version: 9.0.2 engines: {node: '>=22.18.0'} peerDependencies: vitest: ^5 - '@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-18.1.0.tgz': - resolution: {integrity: sha512-1jS8pzkgkhUXVvK8N4QKDEXyZhlEEnPuYXFRAJ7f38rToHq2k8fu0OZsccLSOM8lbVofpReM86RwidNVtrYWSw==, tarball: file:.sfs-deps/stryker-js-18.1.0.tgz} - version: 18.1.0 + '@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-19.0.0.tgz': + resolution: {integrity: sha512-AeV1mfskws8Nx/o5YDd2XABlFX7OLVfxAaDJuYT+bJaWXaMFu9WfTwNV4fC0PPP85q5nLViA0GKOAkFDgnuAZw==, tarball: file:.sfs-deps/stryker-js-19.0.0.tgz} + version: 19.0.0 engines: {node: '>=24.13.1'} hasBin: true peerDependencies: @@ -3206,15 +3206,15 @@ snapshots: oxc-parser: 0.150.0 typescript: 7.0.2 - '@systemfsoftware/stryker-config@file:.sfs-deps/stryker-config-0.1.0.tgz(@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-18.1.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))))': + '@systemfsoftware/stryker-config@file:.sfs-deps/stryker-config-0.1.0.tgz(@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-19.0.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))))': optionalDependencies: - '@systemfsoftware/stryker-js': file:.sfs-deps/stryker-js-18.1.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))) + '@systemfsoftware/stryker-js': file:.sfs-deps/stryker-js-19.0.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1))) '@systemfsoftware/stryker-framework-interface@file:.sfs-deps/stryker-framework-interface-0.0.1.tgz': dependencies: '@systemfsoftware/stryker-ignorer-interface': file:.sfs-deps/stryker-ignorer-interface-0.1.2.tgz - '@systemfsoftware/stryker-ignorer-effect-schema-declarations@file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz(rolldown@1.2.13)': + '@systemfsoftware/stryker-ignorer-effect-schema-declarations@file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz(rolldown@1.2.13)': dependencies: '@systemfsoftware/stryker-ignorer-interface': file:.sfs-deps/stryker-ignorer-interface-0.1.2.tgz '@systemfsoftware/stryker-ignorer-kit': file:.sfs-deps/stryker-ignorer-kit-0.1.2.tgz(rolldown@1.2.13) @@ -3239,40 +3239,40 @@ snapshots: transitivePeerDependencies: - rolldown - '@systemfsoftware/stryker-js-cli-contract@file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz': + '@systemfsoftware/stryker-js-cli-contract@file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz': dependencies: - '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz + '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz effect: 4.0.2 - '@systemfsoftware/stryker-js-instrumenter@file:.sfs-deps/stryker-js-instrumenter-12.1.2.tgz(rolldown@1.2.13)': + '@systemfsoftware/stryker-js-instrumenter@file:.sfs-deps/stryker-js-instrumenter-13.0.0.tgz(rolldown@1.2.13)': dependencies: '@eslint-community/regexpp': 4.12.2 '@noble/hashes': 2.4.0 '@oxc-project/types': 0.150.0 '@systemfsoftware/effect-cell-types': file:.sfs-deps/effect-cell-types-12.0.0.tgz(effect@4.0.2) '@systemfsoftware/stryker-framework-interface': file:.sfs-deps/stryker-framework-interface-0.0.1.tgz - '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz - '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz + '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz + '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz effect: 4.0.2 oxc-parser: 0.150.0 oxc-walker: 1.1.1(@oxc-project/types@0.150.0)(oxc-parser@0.150.0)(rolldown@1.2.13) transitivePeerDependencies: - rolldown - '@systemfsoftware/stryker-js-plugin-interface@file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz': + '@systemfsoftware/stryker-js-plugin-interface@file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz': dependencies: '@systemfsoftware/stryker-ignorer-interface': file:.sfs-deps/stryker-ignorer-interface-0.1.2.tgz effect: 4.0.2 - '@systemfsoftware/stryker-js-plugin-runtime@file:.sfs-deps/stryker-js-plugin-runtime-7.0.3.tgz': + '@systemfsoftware/stryker-js-plugin-runtime@file:.sfs-deps/stryker-js-plugin-runtime-7.0.4.tgz': dependencies: '@opentelemetry/api': 1.9.1 '@systemfsoftware/effect-cell-types': file:.sfs-deps/effect-cell-types-12.0.0.tgz(effect@4.0.2) - '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz - '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz + '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz + '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz effect: 4.0.2 - '@systemfsoftware/stryker-js-typescript-checker@file:.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(rolldown@1.2.13)(supports-color@8.1.1)': + '@systemfsoftware/stryker-js-typescript-checker@file:.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(rolldown@1.2.13)(supports-color@8.1.1)': dependencies: '@effect/opentelemetry': 4.0.2(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2) '@noble/hashes': 2.4.0 @@ -3280,10 +3280,10 @@ snapshots: '@opentelemetry/sdk-node': 0.222.0(@opentelemetry/api@1.9.1)(supports-color@8.1.1) '@opentelemetry/sdk-trace-base': 2.12.0(@opentelemetry/api@1.9.1) '@systemfsoftware/effect-cell-types': file:.sfs-deps/effect-cell-types-12.0.0.tgz(effect@4.0.2) - '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz - '@systemfsoftware/stryker-js-instrumenter': file:.sfs-deps/stryker-js-instrumenter-12.1.2.tgz(rolldown@1.2.13) - '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz - '@systemfsoftware/stryker-js-plugin-runtime': file:.sfs-deps/stryker-js-plugin-runtime-7.0.3.tgz + '@systemfsoftware/stryker-js-cli-contract': file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz + '@systemfsoftware/stryker-js-instrumenter': file:.sfs-deps/stryker-js-instrumenter-13.0.0.tgz(rolldown@1.2.13) + '@systemfsoftware/stryker-js-plugin-interface': file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz + '@systemfsoftware/stryker-js-plugin-runtime': file:.sfs-deps/stryker-js-plugin-runtime-7.0.4.tgz effect: 4.0.2 typescript: 7.0.2 transitivePeerDependencies: @@ -3298,7 +3298,7 @@ snapshots: - rolldown - supports-color - '@systemfsoftware/stryker-js-vitest-runner@file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))': + '@systemfsoftware/stryker-js-vitest-runner@file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))': dependencies: '@effect/opentelemetry': 4.0.2(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2) '@opentelemetry/exporter-trace-otlp-http': 0.222.0(@opentelemetry/api@1.9.1) @@ -3318,7 +3318,7 @@ snapshots: - effect - supports-color - '@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-18.1.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))': + '@systemfsoftware/stryker-js@file:.sfs-deps/stryker-js-19.0.0.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1))(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))': dependencies: '@effect/opentelemetry': 4.0.2(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2) '@effect/platform-node': 4.0.2(effect@4.0.2)(redis@6.3.0(@opentelemetry/api@1.9.1)) @@ -3330,7 +3330,7 @@ snapshots: '@opentelemetry/sdk-metrics': 2.12.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.12.0(@opentelemetry/api@1.9.1) '@systemfsoftware/effect-cell-types': file:.sfs-deps/effect-cell-types-12.0.0.tgz(effect@4.0.2) - '@systemfsoftware/stryker-js-vm-runner': '@systemfsoftware/stryker-js-vitest-runner@file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))' + '@systemfsoftware/stryker-js-vm-runner': '@systemfsoftware/stryker-js-vitest-runner@file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz(@opentelemetry/api-logs@0.222.0)(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.222.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.12.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.2)(supports-color@8.1.1)(vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(vite@8.2.1(@types/node@24.19.1)(yaml@2.9.1)))' effect: 4.0.2 transitivePeerDependencies: - '@opentelemetry/api-logs' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b5c41cd..649d3b5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -43,18 +43,18 @@ overrides: "@systemfsoftware/vitest": file:.sfs-deps/vitest-2.0.0.tgz "@systemfsoftware/vitest-config": file:.sfs-deps/vitest-config-0.1.0.tgz "@systemfsoftware/stryker-framework-interface": file:.sfs-deps/stryker-framework-interface-0.0.1.tgz - "@systemfsoftware/stryker-ignorer-effect-schema-declarations": file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.2.1.tgz + "@systemfsoftware/stryker-ignorer-effect-schema-declarations": file:.sfs-deps/stryker-ignorer-effect-schema-declarations-0.3.0.tgz "@systemfsoftware/stryker-ignorer-in-source-vitest-block": file:.sfs-deps/stryker-ignorer-in-source-vitest-block-0.1.2.tgz "@systemfsoftware/stryker-ignorer-interface": file:.sfs-deps/stryker-ignorer-interface-0.1.2.tgz "@systemfsoftware/stryker-ignorer-kit": file:.sfs-deps/stryker-ignorer-kit-0.1.2.tgz - "@systemfsoftware/stryker-js": file:.sfs-deps/stryker-js-18.1.0.tgz - "@systemfsoftware/stryker-js-cli-contract": file:.sfs-deps/stryker-js-cli-contract-0.4.0.tgz - "@systemfsoftware/stryker-js-instrumenter": file:.sfs-deps/stryker-js-instrumenter-12.1.2.tgz - "@systemfsoftware/stryker-js-plugin-interface": file:.sfs-deps/stryker-js-plugin-interface-15.0.0.tgz - "@systemfsoftware/stryker-js-plugin-runtime": file:.sfs-deps/stryker-js-plugin-runtime-7.0.3.tgz - "@systemfsoftware/stryker-js-typescript-checker": file:.sfs-deps/stryker-js-typescript-checker-8.3.1.tgz - "@systemfsoftware/stryker-js-vitest-runner": file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz - "@systemfsoftware/stryker-js-vm-runner@npm:@systemfsoftware/stryker-js-vitest-runner@^9.0.1": file:.sfs-deps/stryker-js-vitest-runner-9.0.1.tgz + "@systemfsoftware/stryker-js": file:.sfs-deps/stryker-js-19.0.0.tgz + "@systemfsoftware/stryker-js-cli-contract": file:.sfs-deps/stryker-js-cli-contract-0.5.0.tgz + "@systemfsoftware/stryker-js-instrumenter": file:.sfs-deps/stryker-js-instrumenter-13.0.0.tgz + "@systemfsoftware/stryker-js-plugin-interface": file:.sfs-deps/stryker-js-plugin-interface-16.0.0.tgz + "@systemfsoftware/stryker-js-plugin-runtime": file:.sfs-deps/stryker-js-plugin-runtime-7.0.4.tgz + "@systemfsoftware/stryker-js-typescript-checker": file:.sfs-deps/stryker-js-typescript-checker-8.3.2.tgz + "@systemfsoftware/stryker-js-vitest-runner": file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz + "@systemfsoftware/stryker-js-vm-runner@npm:@systemfsoftware/stryker-js-vitest-runner@^9.0.2": file:.sfs-deps/stryker-js-vitest-runner-9.0.2.tgz versioning: changelog: From 8671277520e7d284803483166621c39f1f976ffa Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 09:27:18 +0000 Subject: [PATCH 10/15] docs(plan): record proof run 2 and the root ruling on its dry-run cost 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. --- ...2026-10-10-2149-ci-mutation-caller-plan.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index e2b386e..d199128 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -341,3 +341,22 @@ Standing rule (Ryan): a timeout is a backstop for a stuck job, never a budget en - **Shared-tooling open items** (not this repository): - `mutation-job.ts:102-105` runs Stryker under `timeout --kill-after=60 <2 x target-seconds>`, which is 960 s at 480: a kill cap inside the shared tooling. - `stryker-config@0.1.0` declares peer `@systemfsoftware/stryker-js: ^17.0.0`, which neither 18 nor 19 satisfies. + +## Proof run 2 ruling (2026-10-11) + +- **Result:** run [38120830653](https://github.com/systemfsoftware/api-extractor-effect/actions/runs/38120830653), dispatched on `feat/mutation` at `1ebd34e`, concluded `failure`. + - The dry run passed: 158 tests, 07:24:17.2 to 07:35:18.9, which is 661 s wall and 337.5 s of runner test time. + - Stryker planned 1354 mutants and completed 63. + - At 07:40:14, 960 s after it started, Stryker got SIGTERM from the `timeout --kill-after=60 <2 x target-seconds>` wrapper (`mutation-job.ts:102-105`) and exited 130 `RunInterrupted`. + - The planner found no recorded timings and planned one unsharded job (default about 8m00s). + - Jobs: mutation 994 s, `sfs-deps` 401 s. +- **Cause of the dry-run cost:** this is shared tooling, routed by the root to unit S2. In systemfsoftware, effect-sim-kernel `Profile.ts:34-39` runs 250 seeded replays per gherkin scenario and ignores the fibers/steps budget. This repository's scenarios are single-fiber, and 50 seeds gave 1 path. A serial host run of the suite took 69.9 s at 250 seeds and 9.5 s at 25. +- **Ruling (root):** no workaround in this repository: + - no `target-seconds` or `max-seconds` change; + - no local `mutation-job.ts`; + - no `CONFORMANCE_PROFILE` flag; + - no test change. +- **Blocked on:** + - O1's fix PR, which removes the `mutation-job.ts` timeout wrapper and widens the `stryker-config` peer range; + - S2's effect-sim-kernel seed-budget release. +- **Re-proof required:** every job at or under 600 s, with a graded row for `packages/api-extractor`. From 7d3dbc65ee8990f72a31a7561e7b9a467b2e50db Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 11 Oct 2026 10:16:32 +0000 Subject: [PATCH 11/15] docs: apply the root's rulings on PR #5's review 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. --- AGENTS.md | 3 ++- docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md | 4 ++-- flake.nix | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6dc2027..13233be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,8 @@ verdict. Requirements and decisions: `docs/plans/2026-10-10-1146-feat-effect-api - The family lint preset is never overridden locally: no rule set to `off`, ignore pattern or disable comment (gate: review of `oxlint.config.ts` and source diffs). A rule that forbids correct code is reported upstream as a preset defect. -- Mutation testing runs on `main` only. +- Mutation testing runs in CI only, never on pull requests: on every push to `main`, and + by manual dispatch (used to prove the lane on a branch). ## Commands diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index d199128..a57e601 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -149,7 +149,7 @@ Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break t - KTD1. **One caller file, `.github/workflows/mutation.yml`, calls `reusable-mutation.yml@` with a trailing comment naming O1.** It follows `release.yml` and `ci.yml`: a reusable workflow pinned by full SHA. The shared job's `sfs-tools` step checks out its scripts at `job.workflow_sha`, so the one pin fixes the workflow and its tools together. The pin does not fix the shared workflow's tag-pinned actions (`actions/cache@v6`, `denoland/setup-deno@v2`); that is an upstream question (D8). Covers R2. - KTD2. **Permissions: `contents: read` at the top, and the mutation job grants `contents: read` plus `actions: write`,** as systemfsoftware's own caller does. The shared plan job reads the timing record (`actions: read`), and the report job requests `actions: write` (`feb68840` `reusable-mutation.yml:261-263`). A called workflow's jobs can only narrow the caller's grant. GitHub refuses the run at startup when a called job requests more than the caller grants (microsoft/hve-core#2407, github/gh-aw#21071). U2 re-derives the grant from the pinned rev's job `permissions:` blocks. Covers R2. -- KTD3. **Concurrency `mutation-${{ github.ref }}` with `cancel-in-progress: true`, as systemfsoftware's own caller sets it.** The shared workflow declares no concurrency. Its stryker caches are keyed with the run ID (`feb68840` `reusable-mutation.yml:161,274`), so two runs on one ref do not overwrite each other's entry. The setting stays because systemfsoftware's own caller uses it (D6). Covers R1. +- KTD3. **Concurrency `mutation-${{ github.ref }}` with `cancel-in-progress: true`, as systemfsoftware's own caller sets it.** (amended by the U2 rulings) The shared workflow declares no concurrency. Its stryker caches are keyed with the run ID (`feb68840` `reusable-mutation.yml:161,274`), so two runs on one ref do not overwrite each other's entry. The setting stays because systemfsoftware's own caller uses it (D6). Covers R1. - KTD4. **Every mutation job fits in 10 minutes (root ruling).** The caller passes `target-seconds` of at most 480. `max-jobs` stays at 20, its default at `feb68840` and #735, and is not passed: #733 removes the input, and GitHub refuses an input the called workflow does not define. `timeout-minutes` keeps its default, as a backstop only. The value depends on the pinned rev: - **From #733 on:** a shard's planned cost, including the 120 s `SHARD_OVERHEAD_SECONDS`, must fit `target-seconds`, or the plan fails (#733 `reusable-mutation.yml:44,134,195-197`). 480 therefore holds a shard inside 10 minutes. - **Before #733:** a package's kill cap is twice `target-seconds` and a job's budget four times (#735 `reusable-mutation.yml:11,212`), so 480 allows a 960 s package. U2 lowers the value until the cap plus install and build fits 600 s. If no value does, U2 stops and reports it as a question. @@ -231,7 +231,7 @@ stateDiagram-v2 - The mutation job installs and runs the package's `mutation` script. - Edge (AE1): a pull request against `main` → no run starts, because the file has no `pull_request` or `merge_group` trigger. - Edge: a push to a branch other than `main` at the final head → no run starts (`branches: [main]`). - - Edge (KTD3): a second dispatch on the same ref while one runs → the earlier run is cancelled and only the later one reports. + - Edge (KTD3): a second dispatch on the same commit while one runs → it waits, then runs, and both report; GitHub replaces a waiting run if a third is queued on the same commit. Pushes to main are different commits and never wait on each other. - Edge (KTD4): the plan's predicted job cost at the chosen `target-seconds` stays within 600 s. - Error: the O1 rev drops the `.sfs-deps` restore → the install step fails and the mutation job is red. U2 step 2 catches this before the pin lands. - **Verification:** `actionlint` passes on the file, and the pinned SHA resolves to a commit on systemfsoftware `main` that contains `reusable-mutation.yml`. diff --git a/flake.nix b/flake.nix index d2a4926..46eb3f5 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,8 @@ nixpkgs.follows = "systemfsoftware/nixpkgs"; # stryker-js-effect at the release commit of `@systemfsoftware/stryker-js@v19.0.0` # and `@systemfsoftware/stryker-ignorer-effect-schema-declarations@v0.3.0`. - # Every stryker package comes from this one commit. Nothing follows: its own + # Every stryker-js-effect package comes from this one commit; stryker-config + # comes from the systemfsoftware input. Nothing follows: its own # lock reproduces the released tarballs byte for byte, the integrity each # release tag records. stryker-published.url = "github:systemfsoftware/stryker-js-effect/9e81669b84711a9635eaa97b17dd13efea9f0c8c"; From 54eda8c9db5da2eeb687d0edfbd054eb75902134 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 12 Oct 2026 14:39:09 +0000 Subject: [PATCH 12/15] test(mutation): mutate only api-extractor's workflow files 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. --- .../2026-10-10-2149-ci-mutation-caller-plan.md | 13 ++++++++++--- packages/api-extractor/stryker.config.ts | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md index a57e601..ddeaf85 100644 --- a/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +++ b/docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md @@ -37,7 +37,7 @@ The mutation-caller PR adds the mutation caller for this repository. The caller ### Problem Frame -Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break threshold 100 in `packages/api-extractor/stryker.config.ts`), and it has never run on this package. PR2 shipped everything the package side needs and left the workflow blocked on O1 (1146 plan, KTD20). #710 has since landed the shared workflow, so the README row that waits on #710 is stale. The remaining gap is the tarball install, and the root ruled that it is fixed upstream, not here. +Mutation is the gate on `src/**/*.workflow.ts` (break threshold 100 in `packages/api-extractor/stryker.config.ts`; schema files left the mutate set by the mutate-set ruling below), and it has never run on this package. PR2 shipped everything the package side needs and left the workflow blocked on O1 (1146 plan, KTD20). #710 has since landed the shared workflow, so the README row that waits on #710 is stale. The remaining gap is the tarball install, and the root ruled that it is fixed upstream, not here. ### Key Decisions @@ -68,7 +68,7 @@ Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break t **Docs** - R5. While R3 holds, the README roadmap row for mutation and the PR body name O1 (systemfsoftware #735) and its `.sfs-deps` input as the blocker. Once R2 lands, they name the pinned rev. -- R6. This PR's own commits leave `packages/api-extractor/stryker.config.ts`, the package's `mutation` script and its stryker devDependencies as PR2 shipped them. Survivor fixes reach this branch only by merging `origin/main`. +- R6. This PR's own commits leave `packages/api-extractor/stryker.config.ts`, the package's `mutation` script and its stryker devDependencies as PR2 shipped them. Survivor fixes reach this branch only by merging `origin/main`. (amended by the mutate-set ruling: `stryker.config.ts`'s `mutate` narrows to `src/**/*.workflow.ts`; the script and devDependencies stay as PR2 shipped them.) ### Acceptance Examples @@ -137,7 +137,7 @@ Mutation is the gate on `src/**/*.workflow.ts` and `src/**/*.schema.ts` (break t - GitHub validates a called workflow's job permissions against the caller's grant at startup: microsoft/hve-core#2407 and github/gh-aw#21071. - In this repository: `README.md` roadmap table, `flake.nix` (`packages..sfs-deps`), `.github/workflows/ci.yml` (`checks-lane` hook), `packages/api-extractor/stryker.config.ts` and `packages/api-extractor/package.json`. - Compound Packs (`.compound-engineering/config.yaml`, systemfsoftware `df908ddf`): - - `package-topology/surface-changes-are-versioned` applies through R6. No package byte changes in this PR's commits, so it needs no changeset, and the Changeset Check passes on a PR that touches no publishable package. + - `package-topology/surface-changes-are-versioned` applies through R6. No byte of the package's tarball changes in this PR's commits: it ships only `dist` (`packages/api-extractor/package.json` `files`), and the mutate-set ruling's edit is to `stryker.config.ts`, which is not in it. - `boundary-testing/pin-dependency-semantics` covers the plan's reliance on the shared workflow's behaviour. This PR has no code path to hold a differential test, so the SHA pin (KTD1) and U3's graded proof take its place. - `cell-architecture` and `schema-laws` govern package source, which this PR does not touch. @@ -360,3 +360,10 @@ Standing rule (Ryan): a timeout is a backstop for a stuck job, never a budget en - O1's fix PR, which removes the `mutation-job.ts` timeout wrapper and widens the `stryker-config` peer range; - S2's effect-sim-kernel seed-budget release. - **Re-proof required:** every job at or under 600 s, with a graded row for `packages/api-extractor`. + +## Mutate-set ruling (2026-10-12) + +- **Ruling (root):** the effective Stryker mutate set is exactly `src/**/*.workflow.ts`. `packages/api-extractor/stryker.config.ts:20` is `mutate: shardMutate(['src/**/*.workflow.ts'])`; `src/**/*.schema.ts` is no longer mutated. +- **Effective set:** `shardMutate` returns its patterns unchanged when `STRYKER_SHARD` is unset, and keeps them plus negations of other shards' files when set (`@systemfsoftware/stryker-config` `lib/base.js:82-90`). The config's `mutate` overrides `sharedConfig`'s. Today the pattern matches one file, `src/judge-surfaces.workflow.ts`. +- **Scope:** no other source or workflow change. R6 is amended for this one line. +- **Re-proof:** still waits on O1's fix PR (systemfsoftware #737) and S2's effect-sim-kernel release. diff --git a/packages/api-extractor/stryker.config.ts b/packages/api-extractor/stryker.config.ts index f30b9c7..3d804fd 100644 --- a/packages/api-extractor/stryker.config.ts +++ b/packages/api-extractor/stryker.config.ts @@ -17,7 +17,7 @@ const config: PartialStrykerOptions = defineConfig({ import.meta.resolve('@systemfsoftware/stryker-ignorer-effect-schema-declarations'), import.meta.resolve('@systemfsoftware/stryker-ignorer-in-source-vitest-block'), ], - mutate: shardMutate(['src/**/*.workflow.ts', 'src/**/*.schema.ts']), + mutate: shardMutate(['src/**/*.workflow.ts']), thresholds: { high: 100, low: 100, break: 100 }, dryRunTimeoutMinutes: 10, }) From 48092e40ab1755d7d2da9dfb5755e5c9b000baae Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 12 Oct 2026 14:50:33 +0000 Subject: [PATCH 13/15] chore(changeset): record a none intent for api-extractor's mutate-set 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. --- .changeset/young-lamps-add.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/young-lamps-add.md diff --git a/.changeset/young-lamps-add.md b/.changeset/young-lamps-add.md new file mode 100644 index 0000000..b9a8d32 --- /dev/null +++ b/.changeset/young-lamps-add.md @@ -0,0 +1,5 @@ +--- +"@systemfsoftware/api-extractor": none +--- + +Mutation testing covers only the package's workflow files; the shipped tarball is unchanged. From 05f44c017b3ea08ffbcb3358c0ddcb6a604a0539 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 12 Oct 2026 18:34:55 +0000 Subject: [PATCH 14/15] ci(mutation): restore PR2's mutate set and supersede the caller plan 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. --- .changeset/young-lamps-add.md | 5 - ...2026-10-12-1832-ci-mutation-caller-plan.md | 152 ++++++++++++++++++ packages/api-extractor/stryker.config.ts | 2 +- 3 files changed, 153 insertions(+), 6 deletions(-) delete mode 100644 .changeset/young-lamps-add.md create mode 100644 docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md diff --git a/.changeset/young-lamps-add.md b/.changeset/young-lamps-add.md deleted file mode 100644 index b9a8d32..0000000 --- a/.changeset/young-lamps-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@systemfsoftware/api-extractor": none ---- - -Mutation testing covers only the package's workflow files; the shipped tarball is unchanged. diff --git a/docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md b/docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md new file mode 100644 index 0000000..882656a --- /dev/null +++ b/docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md @@ -0,0 +1,152 @@ +--- +title: Mutation Caller - Plan +type: ci +date: 2026-10-12 +topic: mutation-caller +artifact_contract: ce-unified-plan/v1 +product_contract_source: ce-brainstorm +execution: code +supersedes: docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md +--- + +# Mutation Caller - Plan + +## Goal Capsule + +- **Objective:** every push to `main` runs mutation testing on `@systemfsoftware/api-extractor`'s verdict-decision code, and a surviving mutant turns `main` red. The decision code is `src/**/*.workflow.ts` and the `src/**/*.schema.ts` files beside the types it operates on (`AGENTS.md:12`). +- **Means:** a thin caller, `.github/workflows/mutation.yml`, of systemfsoftware's shared `reusable-mutation.yml`, pinned by full SHA. +- **Scope:** CI and dev toolchain only. No path under `packages/` or `apps/` changes, the package tarball stays byte-identical to the one the `@systemfsoftware/api-extractor@v0.0.0` tag records, and the PR carries no changeset intent. +- **Authority:** systemfsoftware `CONSTITUTION.md` and the Compound Packs in `.compound-engineering/config.yaml` bind first; a brief that conflicts with them goes to the operator. Then this plan, then `AGENTS.md`, then `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md` (KTD20). The 1146 plan's KTD18 timeout values (10 minutes) are superseded by KTD4 here. +- **Supersedes** `docs/plans/2026-10-10-2149-ci-mutation-caller-plan.md`, which stays as the contract its executed units (U1, U2, the two proof runs) were verified against. Carried forward: its R1, R2, R4, R5 and KTD1-KTD6 as amended by its Timeout, U2 and Proof run rulings. Withdrawn: its Mutate-set ruling (KTD7 here). Met and dropped: its R3 (the caller is pinned). +- **Execution profile:** the mutation-caller PR on `feat/mutation`, a `gh stack` layer on `main`, a draft until the Definition of Done holds. The agent commits and pushes; the operator merges. +- **Stop conditions:** + - A unit needs a path under `packages/` or `apps/`: stop; that work is a separate layer on `main` (R3). + - The pin for the proof run does not satisfy R5: no merge. + - A proof-run job exceeds 600 s, or the summary grades below the break threshold: stop and report; R4 holds the merge. + +--- + +## Product Contract + +### Requirements + +- R1. At this PR's final head the caller runs only on push to `main` and on `workflow_dispatch`; it never has a `pull_request` or `merge_group` trigger. +- R2. The caller is a thin `workflow_call` of `reusable-mutation.yml` pinned by commit SHA, with only that workflow's inputs and the permissions its jobs ask for. +- R3. This PR's own commits change no path under `packages/` or `apps/` and add no `.changeset/` intent. Survivor fixes and baseline files reach this branch only by merging `origin/main`. +- R4. Before merge, one `workflow_dispatch` run of the caller on `feat/mutation`, at a head whose pin satisfies R5: + - concludes `success`; + - grades `@systemfsoftware/api-extractor` at or above its break threshold (100) in the report job's summary; + - runs every job in at most 600 s, measured, not enforced by cancelling (KTD4). + + The PR body links the run with each job's time. +- R5. The pinned rev fails the run when the package grades below its break threshold, so the lane can fail on a survivor (`CONST-T3`). +- R6. The README roadmap row names the pinned rev; the PR body names the pinned rev and every open blocker with its state. + +### Acceptance Examples + +- AE1. **Covers R1.** **Given** the caller on `main`, **when** a pull request is opened, **then** no mutation run starts. +- AE2. **Covers R4, R5.** **Given** a pin that satisfies R5, **when** the caller is dispatched on `feat/mutation`, **then** the run concludes `success`, the summary grades the package at or above 100, and every job is at most 600 s. +- AE3. **Covers R4, R5.** **Given** a surviving mutant, **when** the dispatch run grades the package below 100, **then** the run concludes `failure`, and the PR stays unmerged until a survivor layer is on `main`, `feat/mutation` has merged `origin/main`, and a later dispatch meets AE2. +- AE4. **Covers R3.** **Given** any head of this PR, **when** `git diff --name-only origin/main...HEAD -- packages/ apps/ .changeset/` runs, **then** it prints nothing. + +### Scope Boundaries + +- No local mutation workflow, composite action, caller-side gate or copy of any shared step (R2). The red signal comes from the pinned rev (R5). +- No stryker run outside CI. +- No change to the mutate set, the package's `mutation` script or its stryker devDependencies; the shared stryker toolchain moves only through the flake input, `pnpm-workspace.yaml` overrides and the lockfile. +- No mutation on the e2e app: it has no `mutation` script. + +### Dependencies / Blockers + +Each blocker is in systemfsoftware unless named otherwise. State as of 2026-10-12. + +- **B1. #737** (open, draft, head `a2a58d08`): removes `mutation-job.ts`'s `timeout --kill-after=60 <2 x target-seconds>` wrapper and widens `@systemfsoftware/stryker-config`'s peer range to stryker-js 19. Evidence: proof run [38120830653](https://github.com/systemfsoftware/api-extractor-effect/actions/runs/38120830653) at `1ebd34e` was killed 960 s into Stryker, with 63 of 1354 mutants done and no report. +- **B2. #744** (open, the S2 unit): stops seeded replays where no choice point exists. Evidence: the same run's dry run took 661 s of wall time, above R4's 600 s on its own; Stryker always runs the dry run, so no mutate set avoids it (StrykerJS docs, "Incremental"). +- **B3. #733** (open, head `558d1c32f`): replaces the shared job with `stryker plan`, `coverage`, `run --shard`, `merge` and `gate --baseline`, which fails the run on a break failure. That satisfies R5; `02bf965` does not, because its mutation job fails only when no report was written (`mutation-job.ts:162`, `build-mutation-summary.ts:142-143`). #733 requires a committed `packages/api-extractor/mutation-baseline.json`, which R3 places in a separate layer on `main`. +- **B4. Survivors:** unknown until a graded run. They are killed in a separate layer on `main` (AE3). + +### Sources + +- systemfsoftware `02bf9653b1ca2b0a2951baf2743bb3f70d19fc77` (#735): `.github/workflows/reusable-mutation.yml`, `scripts/tools/mutation-job.ts`, `scripts/tools/build-mutation-summary.ts`, `scripts/tools/test-timings.ts`; `CONSTITUTION.md`. +- systemfsoftware #733, #737 and #744, at the heads above. +- `@systemfsoftware/stryker-js` 19.0.0 `CHANGELOG.md` (empty mutate sets and fileless shards pass). +- pnpm-release-management `b9b4a1d48cc16241b33f9afd8ab99b82b05ce04f`: `github-release-engine` `cycleOf` and `verifyIntegrity`. +- StrykerJS docs, "Incremental": https://stryker-mutator.io/docs/stryker-js/incremental/ + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. **One caller file, `.github/workflows/mutation.yml`, calls `reusable-mutation.yml` pinned by full SHA** (`mutation.yml:20`). The shared job checks out its tools at `job.workflow_sha`, so the pin fixes the workflow and its tools together. Today's pin is `02bf965` (#735). Covers R2. +- KTD2. **Permissions: `contents: read` at the top; the mutation job grants `contents: read` and `actions: write`,** the most the called jobs request. A called job cannot widen the caller's grant. Covers R2. +- KTD3. **Concurrency `mutation-${{ github.sha }}`, `cancel-in-progress: false`.** No merge's run cancels or replaces another's; the shared caches are keyed by run ID, so concurrent runs do not overwrite each other's entry. Covers R1. +- KTD4. **Inputs at `02bf965`: `target-seconds: 480`, `max-jobs: 20`, `timeout-minutes: 60`, `report-timeout-minutes: 60`, each explicit.** A timeout is a backstop for a stuck job, never a budget enforced by cancelling: every job and step timeout in this repository is 60 minutes, and the 600 s job budget is measured and reported. `sfs-deps-installable` keeps its default, `.#sfs-deps`. Re-derived at every re-pin from the pinned rev's inputs (#733 removes `max-jobs`). Covers R2, R4. +- KTD5. **Triggers: `push` with `branches: [main]`, and `workflow_dispatch`.** The shared `mutation-event-guard` also refuses pull-request events, but a trigger that never fires is the cheaper guarantee. Covers R1. +- KTD6. **The proof reads the package's summary row, not only the run's conclusion.** Covers R4. +- KTD7. **The mutate set stays as PR2 shipped it: `shardMutate(['src/**/*.workflow.ts', 'src/**/*.schema.ts'])`.** + - It is the decision code `AGENTS.md:12` names, chosen by what the code does (`CONST-T12`). + - Narrowing it inside the PR whose merge it grades would weaken the grader (`CONST-E7`). + - One matched file cannot be sharded: at `02bf965` mutation packages are always shardable (`test-timings.ts:455`), the planner sizes shards by recorded seconds (`:211-212`) and records a sharded package as the sum of its shards (`:355`), and stryker-js 19.0.0 runs a full dry run for a shard with no file. + - It cannot fix the time budget: the dry run is the dominant cost (B2). +- KTD8. **No changeset intent.** No path under `packages/` changes, so the Changeset Check has nothing to require (run 38131376753 passed on `7d3dbc6` with no intent). On merge, Release finds no pending intent, and the `v0.0.0` tag already exists for the byte-identical tarball, so no release is owed (`github-release-engine/src/cycle.ts:22`); Release on `main` concluded `success` in the same state (run 38093132532 at `9bd7396`). +- KTD9. **The stryker toolchain comes from one stryker-js-effect commit, `9e81669b84711a9635eaa97b17dd13efea9f0c8c`** (stryker-js 19.0.0, ignorer 0.3.0), through the `stryker-published` flake input, the `pnpm-workspace.yaml` overrides and the lockfile. `ci.yml`'s `consumer-store` check enforces that the lockfile's integrities match the flake's tarballs. The ignorer's handling of `recursionBudget` is exercised by every mutation dry run over the schema files that carry it (`model/api-model.schema.ts:145`, `model/type-expr.schema.ts:170,189`). + +### Risks + +- #733 changes the caller interface. The re-pin (U5) re-derives the inputs, permissions and sizing from the pinned rev. +- A cold incremental cache makes the first graded run cover all 1354 mutants; Stryker saves an interrupted run's partial results to its incremental file. +- The shared workflow's tag-pinned actions (`actions/cache@v6`, `denoland/setup-deno@v2`) are outside the SHA pin; that is an upstream question. + +--- + +## Implementation Units + +### U1. README row (done) + +The "Mutation testing on `main`" row names the pinned rev (R6). **Files:** `README.md`. + +### U2. Caller pinned to `02bf965` (done) + +`.github/workflows/mutation.yml` per KTD1-KTD5. **Files:** `.github/workflows/mutation.yml`, `.github/workflows/ci.yml`, `.github/workflows/release.yml` (60-minute timeouts), `AGENTS.md`. + +### U3. Stryker toolchain at stryker-js-effect `9e81669` (done) + +KTD9. **Files:** `flake.nix`, `flake.lock`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`. + +### U4. Restore the PR2 mutate set; no intent + +KTD7, KTD8. **Files:** `packages/api-extractor/stryker.config.ts` returns to `main`'s bytes; `.changeset/young-lamps-add.md` is deleted. **Verification:** AE4 prints nothing; the tarball's sha512 equals the `v0.0.0` tag's integrity. + +### U5. Re-pin past #733 + +- **Dependencies:** B1 and B3 merged; `packages/api-extractor/mutation-baseline.json` on `main` in a separate layer. +- **Approach:** pin the first systemfsoftware `main` commit containing #733 and #737 whose runs all concluded success; re-derive inputs and permissions from that rev (KTD4); update the README row and the PR body (R6). +- **Verification:** `actionlint` exits 0; `git merge-base --is-ancestor origin/main` in a systemfsoftware clone exits 0. + +### U6. Proof dispatch + +- **Dependencies:** U5 and B2. +- **Approach:** dispatch the caller with `--ref feat/mutation`, watch it with `xd://github` `run_watch`, read the summary row and every job's time, and record them in the PR body. A below-break grade follows AE3. + +--- + +## Verification Contract + +| Gate | Applies to | Done signal | +| --- | --- | --- | +| `git diff --name-only origin/main...HEAD -- packages/ apps/ .changeset/` | every head | prints nothing (R3) | +| `ci.yml` and Changeset Check on the PR head | every head | green | +| `nix run nixpkgs#actionlint -- .github/workflows/*.yml` | U2, U5 | exit 0 | +| `sha512` of `.#workspace-tarballs`' `api-extractor-0.0.0.tgz` vs the `v0.0.0` tag annotation's `integrity` | every head | equal (KTD8) | +| `git -C merge-base --is-ancestor origin/main` | U5 | exit 0 | +| Dispatch run of `mutation.yml` on `feat/mutation` | U6 | AE2 | + +--- + +## Definition of Done + +- U4, U5 and U6 are done; R1-R6 hold at the final head. +- The PR body links the U6 run with its graded score and per-job times. +- Until then the PR stays a draft, and its body lists B1-B4 with their state. diff --git a/packages/api-extractor/stryker.config.ts b/packages/api-extractor/stryker.config.ts index 3d804fd..f30b9c7 100644 --- a/packages/api-extractor/stryker.config.ts +++ b/packages/api-extractor/stryker.config.ts @@ -17,7 +17,7 @@ const config: PartialStrykerOptions = defineConfig({ import.meta.resolve('@systemfsoftware/stryker-ignorer-effect-schema-declarations'), import.meta.resolve('@systemfsoftware/stryker-ignorer-in-source-vitest-block'), ], - mutate: shardMutate(['src/**/*.workflow.ts']), + mutate: shardMutate(['src/**/*.workflow.ts', 'src/**/*.schema.ts']), thresholds: { high: 100, low: 100, break: 100 }, dryRunTimeoutMinutes: 10, }) From b86386f03084535cbe3f0ec2f2aecf9e8afb5abc Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Tue, 13 Oct 2026 06:47:09 +0000 Subject: [PATCH 15/15] ci(mutation): run nightly and by dispatch on main, never per push 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. --- .github/workflows/mutation.yml | 12 +- AGENTS.md | 5 +- README.md | 2 +- ...2026-10-13-0641-ci-mutation-caller-plan.md | 166 ++++++++++++++++++ 4 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 docs/plans/2026-10-13-0641-ci-mutation-caller-plan.md diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 5e233a8..8ef2d71 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -1,12 +1,17 @@ +# Cost: at least 25 runner-min/day for the one full run a night; a push to main 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. Each manual dispatch costs one more such run. +# Per run: 1,469 job-seconds over six jobs, run 38120830653, a floor: Stryker was killed +# 960 s in with 63 of 1354 mutants done. The proof run's measurement replaces it. name: Mutation on: - push: - branches: [main] + schedule: + - cron: "47 14 * * *" workflow_dispatch: concurrency: - group: mutation-${{ github.sha }} + group: mutation-${{ github.run_id }} cancel-in-progress: false permissions: @@ -14,6 +19,7 @@ permissions: jobs: mutation: + if: github.ref == 'refs/heads/main' permissions: contents: read actions: write diff --git a/AGENTS.md b/AGENTS.md index 13233be..6f6c2af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,8 +21,9 @@ verdict. Requirements and decisions: `docs/plans/2026-10-10-1146-feat-effect-api - The family lint preset is never overridden locally: no rule set to `off`, ignore pattern or disable comment (gate: review of `oxlint.config.ts` and source diffs). A rule that forbids correct code is reported upstream as a preset defect. -- Mutation testing runs in CI only, never on pull requests: on every push to `main`, and - by manual dispatch (used to prove the lane on a branch). +- Mutation testing runs in CI only, on `main`: nightly, and by manual dispatch on `main`. + Never per push or on pull requests (gate: `.github/workflows/mutation.yml`'s triggers + and its `refs/heads/main` job condition). ## Commands diff --git a/README.md b/README.md index 201aff1..945dc68 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The design and its requirements live in | systemfsoftware and stryker-js-effect packages from the flake (`.sfs-deps`) | in this repository | | Lint, typecheck, build and test on systemfsoftware's reusable checks; tsdown, vitest and lint presets | in this repository | | Releases: changesets and GitHub Releases through pnpm-release-management | in this repository | -| Mutation testing on `main` | in this repository: systemfsoftware's reusable mutation workflow at `02bf965` (#735), incremental per push | +| Mutation testing on `main` | in this repository: systemfsoftware's reusable mutation workflow at `02bf965` (#735), nightly and by manual dispatch on `main` | ## The verdict diff --git a/docs/plans/2026-10-13-0641-ci-mutation-caller-plan.md b/docs/plans/2026-10-13-0641-ci-mutation-caller-plan.md new file mode 100644 index 0000000..9d4cb00 --- /dev/null +++ b/docs/plans/2026-10-13-0641-ci-mutation-caller-plan.md @@ -0,0 +1,166 @@ +--- +title: Mutation Caller - Plan +type: ci +date: 2026-10-13 +topic: mutation-caller +artifact_contract: ce-unified-plan/v1 +product_contract_source: ce-brainstorm +execution: code +supersedes: docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md +--- + +# Mutation Caller - Plan + +## Goal Capsule + +- **Objective:** mutation testing grades `@systemfsoftware/api-extractor`'s verdict-decision code once a night on `main` and on manual dispatch on `main`, and a surviving mutant turns that run red. The decision code is `src/**/*.workflow.ts` and the `src/**/*.schema.ts` files beside the types it operates on (`AGENTS.md:12`). +- **Means:** a thin caller, `.github/workflows/mutation.yml`, of systemfsoftware's shared `reusable-mutation.yml`, pinned by full SHA. +- **Scope:** CI and dev toolchain only, until the re-pin (U5). The package tarball stays byte-identical to the one the `@systemfsoftware/api-extractor@v0.0.0` tag records, and the PR's release intent is `none` (KTD8). +- **Authority:** systemfsoftware `CONSTITUTION.md` and the Compound Packs this repository enrolls bind first; a conflict between them and a root ruling is recorded with both citations and the ruling decides. Then this plan, then `AGENTS.md`, then `docs/plans/2026-10-10-1146-feat-effect-api-extractor-plan.md` (KTD20). +- **Supersedes** `docs/plans/2026-10-12-1832-ci-mutation-caller-plan.md`, which stays as the contract its executed units (U1-U4) were verified against. Changed: the cadence (R1, KTD3, KTD5, KTD10), the proof run (R4, U6), the re-pin's package scope (R3, KTD8, KTD11). Carried unchanged: R2, R5, R6 and KTD1, KTD2, KTD4, KTD6, KTD7, KTD9. +- **Execution profile:** the mutation-caller PR on `feat/mutation`, a draft until the Definition of Done holds. The agent commits and pushes; the operator merges. +- **Stop conditions:** + - A unit other than U5 needs a path under `packages/` or `apps/`: stop; that work is a separate layer on `main` (R3). + - The pin for the proof run does not satisfy R5: no merge. + - A proof-run job exceeds 600 s, or the summary grades below the break threshold: stop and report; R4 holds the merge. + +--- + +## Product Contract + +### Requirements + +- R1. At this PR's final head the caller has exactly two triggers, a nightly `schedule` and `workflow_dispatch`, and runs only on `refs/heads/main`. It has no `push`, `pull_request` or `merge_group` trigger. +- R2. The caller is a thin `workflow_call` of `reusable-mutation.yml` pinned by commit SHA, with only that workflow's inputs and the permissions its jobs ask for. +- R3. Before U5, this PR's own commits change no path under `packages/` or `apps/` and add no `.changeset/` intent. At U5 the only `packages/api-extractor` change is the removal KTD11 names, with a `none` intent. Survivor fixes and baseline files reach this branch only by merging `origin/main`. +- R4. Before merge, one run of the caller on `feat/mutation`, at a head whose pin satisfies R5, started by the temporary trigger of U6: + - concludes `success`; + - grades `@systemfsoftware/api-extractor` at or above its break threshold (100) in the report job's summary; + - runs every job in at most 600 s, measured, not enforced by cancelling (KTD4). + + The PR body links the run with each job's time. +- R5. The pinned rev fails the run when the package grades below its break threshold, so the lane can fail on a survivor (`CONST-T3`). +- R6. The README roadmap row names the pinned rev and the cadence; the PR body names the pinned rev and every open blocker with its state. + +### Acceptance Examples + +- AE1. **Covers R1.** **Given** the caller on `main`, **when** a pull request is opened or a commit is pushed to `main`, **then** no mutation run starts. +- AE2. **Covers R1.** **Given** the caller on `main`, **when** it is dispatched with a ref other than `main`, **then** its mutation job is skipped. +- AE3. **Covers R4, R5.** **Given** a pin that satisfies R5, **when** the U6 run starts on `feat/mutation`, **then** it concludes `success`, the summary grades the package at or above 100, and every job is at most 600 s. +- AE4. **Covers R4, R5.** **Given** a surviving mutant, **when** the run grades the package below 100, **then** it concludes `failure`, and the PR stays unmerged until a survivor layer is on `main`, `feat/mutation` has merged `origin/main`, and a later run meets AE3. +- AE5. **Covers R3.** **Given** any head before U5, **when** `git diff --name-only origin/main...HEAD -- packages/ apps/ .changeset/` runs, **then** it prints nothing. + +### Scope Boundaries + +- No local mutation workflow, composite action, caller-side gate or copy of any shared step (R2). The red signal comes from the pinned rev (R5). +- No stryker run outside CI. +- No new tests: no generated-law test, no decode or verdict refusal property, before or at U5 (KTD11). +- No change to the mutate set, the package's `mutation` script or its stryker devDependencies before U5; the shared stryker toolchain moves only through the flake input, `pnpm-workspace.yaml` overrides and the lockfile. +- No mutation on the e2e app: it has no `mutation` script. + +### Dependencies / Blockers + +Each blocker is in systemfsoftware unless named otherwise. State as of 2026-10-13. + +- **B1. #737** (open, draft, head `6db58a01`): removes `mutation-job.ts`'s `timeout --kill-after=60 <2 x target-seconds>` wrapper and widens `@systemfsoftware/stryker-config`'s peer range to stryker-js 19. Evidence: run [38120830653](https://github.com/systemfsoftware/api-extractor-effect/actions/runs/38120830653) at `1ebd34e` was killed 960 s into Stryker, with 63 of 1354 mutants done and no report. +- **B2. #744** (open, head `26442627`): stops seeded replays where no choice point exists. Evidence: the same run's dry run took 661 s, above R4's 600 s on its own. +- **B3. #733** (open, head `78826f9f`): replaces the shared job with `stryker plan`, `coverage`, `run --shard`, `merge` and `gate --baseline`, which fails the run on a break failure (R5), and moves systemfsoftware's own lane to nightly. It requires a committed `packages/api-extractor/mutation-baseline.json`, which R3 places in a separate layer on `main`. +- **B4. Survivors:** unknown until a graded run. They are killed in a separate layer on `main` (AE4). +- **B5. #747** (open, head `e2e1e71ab9`): the shared Mutation plan refuses, before Stryker starts, a package that sets its own `mutate` or would mutate a non-workflow file, and `@systemfsoftware/stryker-config` stops exporting `shardMutate`. If the U5 pin contains it, KTD7 changes as that decision states. +- **B6. Generated-law retirement** (not yet authored upstream): no systemfsoftware PR retires generated schema laws, and `main` at `763fd66399` (#746) does not contain one. U5 waits for it. + +### Sources + +- systemfsoftware `02bf9653b1ca2b0a2951baf2743bb3f70d19fc77` (#735): `.github/workflows/reusable-mutation.yml`, `scripts/tools/mutation-job.ts`, `scripts/tools/build-mutation-summary.ts`, `scripts/tools/test-timings.ts`; `CONSTITUTION.md`. +- systemfsoftware #733 (`78826f9f59`): `.github/workflows/mutation.yml` (nightly `schedule` plus `workflow_dispatch`, a concurrency group per run id, the cost header) and its body's cadence section (the 14:10-15:10 UTC window). +- systemfsoftware #737, #744 and #747, at the heads above; #733's cadence section names nightly on `main`, never per push or per PR, as the house rule. +- GitHub Docs, "Events that trigger workflows": a scheduled workflow runs on the latest commit of the default branch, and in a public repository it is disabled after 60 days without repository activity; `workflow_dispatch` fires only for a workflow file on the default branch. GitHub Docs, "Notifications for workflow runs": a scheduled run's notifications go to the user who last modified the cron syntax. +- ci-wall-clock pack at systemfsoftware `8d071180798f9ea82ef5a4fd877152dacc02f6b6`: `heavy-suites-judge-every-main-commit`, `heavy-lanes-state-their-daily-minutes`. + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. **One caller file, `.github/workflows/mutation.yml`, calls `reusable-mutation.yml` pinned by full SHA.** The shared job checks out its tools at `job.workflow_sha`, so the pin fixes the workflow and its tools together. Today's pin is `02bf965` (#735). Covers R2. +- KTD2. **Permissions: `contents: read` at the top; the mutation job grants `contents: read` and `actions: write`,** the most the called jobs request. Covers R2. +- KTD3. **Concurrency `mutation-${{ github.run_id }}`, `cancel-in-progress: false`.** 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. This is #733's shape. Covers R1. +- KTD4. **Inputs at `02bf965`: `target-seconds: 480`, `max-jobs: 20`, `timeout-minutes: 60`, `report-timeout-minutes: 60`, each explicit.** A timeout is a backstop for a stuck job, never a budget enforced by cancelling. `sfs-deps-installable` keeps its default, `.#sfs-deps`. Re-derived at every re-pin from the pinned rev's inputs (#733 removes `max-jobs`). Covers R2, R4. +- KTD5. **Triggers: `schedule` with cron `47 14 * * *`, and `workflow_dispatch`; the mutation job carries `if: github.ref == 'refs/heads/main'`.** A schedule already runs only on the default branch; the condition makes a dispatch on any other ref skip the job (AE2). 14:47 UTC sits inside the 14:10-15:10 window #733 measured as the org's quietest hour, 30 minutes after systemfsoftware's own 14:17 run starts. The root ruled the cadence on 2026-10-13: nightly plus manual dispatch on `main`, never per push or per pull request. That departs from the ci-wall-clock rule `heavy-suites-judge-every-main-commit`, which asks for a run on every push to `main`; the ruling decides, and the PR body records the conflict. Covers R1. +- KTD6. **The proof reads the package's summary row, not only the run's conclusion.** Covers R4. +- KTD7. **The mutate set stays as PR2 shipped it: `shardMutate(['src/**/*.workflow.ts', 'src/**/*.schema.ts'])`,** the decision code `AGENTS.md:12` names (`CONST-T12`). Narrowing it inside the PR whose merge it grades would weaken the grader (`CONST-E7`). If the U5 pin contains #747 (B5), the shared package no longer exports `shardMutate` and refuses an own `mutate`, so `stryker.config.ts` drops `mutate` and spreads the shared config, and `AGENTS.md:12` is restated to name only `*.workflow.ts` as mutated. That narrowing comes from the pinned house rule, not from this PR, and the PR body names it. +- KTD8. **Release intent `none`.** Before U5 no path under `packages/` changes, so the PR carries no intent file and the Changeset Check reports that no publishable package changed (runs on `05f44c0` and earlier heads). At U5 the removal KTD11 names changes `packages/api-extractor` without changing its tarball, and an intent `"@systemfsoftware/api-extractor": none` records that. The tarball's sha512 must equal the `v0.0.0` tag's integrity at every head, so the `none` never meets `tag-integrity-mismatch`. +- KTD9. **The stryker toolchain comes from one stryker-js-effect commit, `9e81669b84711a9635eaa97b17dd13efea9f0c8c`** (stryker-js 19.0.0, ignorer 0.3.0), through the `stryker-published` flake input, the `pnpm-workspace.yaml` overrides and the lockfile. `ci.yml`'s `consumer-store` check enforces that the lockfile's integrities match the flake's tarballs. +- KTD10. **The caller states its cost in a header comment** (`heavy-lanes-state-their-daily-minutes`): one scheduled run a night plus each dispatch, priced from run 38120830653's six jobs (1,469 job-seconds). That run was cut 960 s into Stryker (B1), so the header labels the figure as a floor; U6 replaces it with a graded run's measurement. +- KTD11. **The re-pin (U5) to the systemfsoftware commit that retires generated schema laws (B6) removes this package's retired wiring and adds no replacement.** Removed: the `inlineSchemaTests()` plugin in `packages/api-extractor/vitest.config.ts`, the generated `src/schema-laws.test.ts`, the `@systemfsoftware/effect-schema-law` and `@systemfsoftware/effect-schema-vite` devDependencies with their `.sfs-deps` members and overrides, and every in-source `import.meta.vitest` decode or verdict refusal property in `src/` at that time. If the pinned rev also retires in-source tests, tsdown's `import.meta.vitest` define and the `stryker-ignorer-in-source-vitest-block` ignorer go too. Kept: `src/__tests__/judge-surfaces.workflow.property.test.ts`. It is the decision cell's own property suite, not in-source and not generated, and it kills the lane's workflow mutants. No generated-law test or refusal property replaces the removed ones, now or at U5. The test-layer matrix requires codec laws for `schema` cells. The root ruling of 2026-10-13 retires them without replacement, and the ruling decides. + +### Assumptions challenged (destructive review) + +- *"The nightly run always happens."* GitHub may delay a scheduled run under load, and disables a public repository's schedules after 60 days without activity. A missed night produces no verdict; a dispatch on `main` replaces it. +- *"A red night reaches someone."* A scheduled run has no pull request and no pusher. GitHub notifies the user who last changed the cron. The red-main P0 handler (`red-main-reverts-its-culprit-same-day`) is not in this PR; the PR body lists it as open. +- *"A branch proof run is a dispatch."* `workflow_dispatch` fires only for a workflow on the default branch, and the KTD5 condition skips any other ref. So the pre-merge proof uses a temporary trigger on `feat/mutation` (U6), removed before the final head. + +### Risks + +- #733 changes the caller interface. U5 re-derives the inputs, permissions and sizing from the pinned rev. +- A cold incremental cache makes the first graded run cover all 1354 mutants. +- The shared workflow's tag-pinned actions are outside the SHA pin; that is an upstream question. + +--- + +## Implementation Units + +### U1. README row (done; amended by U7) + +The "Mutation testing on `main`" row names the pinned rev (R6). **Files:** `README.md`. + +### U2. Caller pinned to `02bf965` (done; amended by U7) + +`.github/workflows/mutation.yml` per KTD1, KTD2, KTD4. **Files:** `.github/workflows/mutation.yml`, `.github/workflows/ci.yml`, `.github/workflows/release.yml` (60-minute timeouts), `AGENTS.md`. + +### U3. Stryker toolchain at stryker-js-effect `9e81669` (done) + +KTD9. **Files:** `flake.nix`, `flake.lock`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`. + +### U4. Restore the PR2 mutate set; no intent (done) + +KTD7, KTD8. **Files:** `packages/api-extractor/stryker.config.ts` returns to `main`'s bytes; `.changeset/young-lamps-add.md` is deleted. + +### U7. Nightly cadence + +- **Approach:** set the triggers, the job condition and the concurrency group per KTD3 and KTD5; add the KTD10 cost header; restate the cadence in `AGENTS.md`'s mutation rule and the README row. +- **Files:** `.github/workflows/mutation.yml`, `AGENTS.md`, `README.md`. +- **Verification:** `actionlint` exits 0; AE5 prints nothing. + +### U5. Re-pin past #733 + +- **Dependencies:** B1, B3 and B6 merged; `packages/api-extractor/mutation-baseline.json` on `main` in a separate layer. +- **Approach:** pin the first systemfsoftware `main` commit containing #733, #737 and B6 whose runs all concluded success; re-derive inputs and permissions from that rev (KTD4); apply KTD11 with its `none` intent (KTD8), and KTD7's change if the rev contains #747; update the README row and the PR body (R6). These package edits ship in the re-pin commit: an earlier removal would drop laws upstream still ships, and a later one would leave the flake naming tarballs the pinned rev no longer builds. +- **Verification:** `actionlint` exits 0; `git merge-base --is-ancestor origin/main` in a systemfsoftware clone exits 0; the tarball's sha512 equals the `v0.0.0` tag's integrity; `pnpm test` passes in CI with no generated-law test file. + +### U6. Proof run + +- **Dependencies:** U5 and B2. +- **Approach:** one commit adds `push: branches: [feat/mutation]` and widens the KTD5 condition to that ref; watch the run with `xd://github` `run_watch`, read the summary row and every job's time, record them in the PR body; the next commit restores R1's triggers and condition. A below-break grade follows AE4. + +--- + +## Verification Contract + +| Gate | Applies to | Done signal | +| --- | --- | --- | +| `git diff --name-only origin/main...HEAD -- packages/ apps/ .changeset/` | every head before U5 | prints nothing (R3) | +| `ci.yml` and Changeset Check on the PR head | every head | green | +| `nix run nixpkgs#actionlint -- .github/workflows/*.yml` | U7, U5, U6 | exit 0 | +| `sha512` of `.#workspace-tarballs`' `api-extractor-0.0.0.tgz` vs the `v0.0.0` tag annotation's `integrity` | every head | equal (KTD8) | +| `git -C merge-base --is-ancestor origin/main` | U5 | exit 0 | +| The U6 run of `mutation.yml` on `feat/mutation` | U6 | AE3 | + +--- + +## Definition of Done + +- U5, U6 and U7 are done; R1-R6 hold at the final head. +- The PR body links the U6 run with its graded score and per-job times. +- Until then the PR stays a draft, and its body lists B1-B6 with their state.