Skip to content

feat(mutation): share the full cold run as a reusable workflow - #29

Merged
msalvatti merged 1 commit into
mainfrom
feat/reusable-mutation-full
Aug 8, 2026
Merged

feat(mutation): share the full cold run as a reusable workflow#29
msalvatti merged 1 commit into
mainfrom
feat/reusable-mutation-full

Conversation

@msalvatti

Copy link
Copy Markdown
Member

Why

Every library whose per-push mutation run exceeds five minutes is moving to an incremental Stryker run. That is the right trade for a gate on every push, but it is a trade: an incremental run reuses stored verdicts, and Stryker's own guidance is that reused verdicts for static mutants can be stale. A score built on reuse is a fast signal, not the truth — so the truth needs a cold run behind it.

That cold run already exists, written once in nest-auth. Nine repositories are about to need it. Copying it would put a 130-line decision — what counts as "the score can have moved", why the job is pinned to the default branch, why a partial baseline must not be saved — in nine places to drift apart.

What it fixes that the copy could not

The caller's workflow file is derived, not hardcoded. The copy compares against gh run list --workflow mutation-full.yml. A caller that names its file anything else finds no previous run — forever, and green, because "no previous run" fails open into running. The failure mode is a weekly cold run that never skips and never says why. This reads github.workflow_ref instead, so the name is whatever the caller actually called it.

The pathspec list is an input with a superset default. Repositories differ in which Jest and TypeScript config files they have. A pathspec that does not exist in the caller matches nothing rather than erroring — verified — so one default covers every layout, and a repository with an unusual one can still override.

What stays with the caller

The schedule (on: schedule cannot live in a reusable), the ceiling, and the Stryker configuration. Those are genuinely per-repository: the ceiling is calibrated to a measured cold duration, and the schedules are staggered so a Monday morning does not start ten cold suites at once.

A caller becomes:

name: Mutation (full)
on:
  schedule:
    - cron: '13 3 * * 1'
  workflow_dispatch:
permissions:
  contents: read
  actions: read
jobs:
  full:
    uses: bymaxone/.github/.github/workflows/mutation-full.yml@v1
    with:
      timeout-minutes: 90

Verification

The file parses, declares workflow_call, exposes three inputs, and pins all three third-party actions by SHA. The filename derivation was checked against both mutation-full.yml and a deliberately different name.

Sequencing

This does not reach any library on merge. v1 moves only when a vN.Y.Z tag is pushed — the release gate added in #28 — and no vN.Y.Z tag exists yet, so v1 currently sits one commit behind main. The callers cannot be opened until a release is cut, and cutting the first one also exercises release-major-alias.yml for the first time.

Every library over the five-minute per-push budget is moving to an incremental
Stryker run, which needs a cold measurement behind it — an incremental run
reuses stored verdicts, and Stryker's guidance is that reused verdicts for
STATIC mutants can be stale. That cold run was written once, in nest-auth, and
copying it into eight more repositories would put a 130-line decision in nine
places to drift.

It also fixes two things the copy could not. The caller's workflow file is now
DERIVED from `github.workflow_ref` rather than hardcoded — the copy compared
against `gh run list --workflow mutation-full.yml`, so a caller naming its file
anything else would find no previous run, forever, and green, because "no
previous run" fails open into running. And the pathspec list is an input with a
superset default; a pathspec absent from a caller matches nothing rather than
erroring, so one default fits repositories with different Jest and TypeScript
config layouts.

The caller keeps what is genuinely per-repository: the schedule, the ceiling,
and its Stryker configuration.
Copilot AI lite review requested due to automatic review settings August 8, 2026 13:51
@msalvatti
msalvatti merged commit f31348a into main Aug 8, 2026
5 checks passed
@msalvatti
msalvatti deleted the feat/reusable-mutation-full branch August 8, 2026 13:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new reusable GitHub Actions workflow that runs a weekly “cold” (full) Stryker mutation suite for Node libraries/apps, with logic to skip runs when score-affecting files haven’t changed and to refresh the incremental baseline cache used by per-push mutation jobs.

Changes:

  • Adds .github/workflows/mutation-full.yml reusable workflow (workflow_call) with inputs for timeout, command, and score-relevant pathspecs.
  • Implements “should we run?” logic by comparing against the last successful run of the caller’s workflow file.
  • Saves the Stryker incremental baseline cache and uploads mutation reports when the suite runs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +64 to +67
permissions:
contents: read
# `gh run list`, in the step that decides whether anything can have moved the score.
actions: read
msalvatti added a commit to bymaxone/nest-ai-tokens that referenced this pull request Aug 8, 2026
…each week (#64)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-auth that referenced this pull request Aug 8, 2026
…each week (#86)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-cache that referenced this pull request Aug 8, 2026
…each week (#70)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-config that referenced this pull request Aug 8, 2026
…each week (#47)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-core that referenced this pull request Aug 8, 2026
…each week (#55)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-logger that referenced this pull request Aug 8, 2026
…each week (#74)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-notification that referenced this pull request Aug 8, 2026
…each week (#52)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-queue that referenced this pull request Aug 8, 2026
…each week (#87)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-realtime that referenced this pull request Aug 8, 2026
…each week (#94)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
msalvatti added a commit to bymaxone/nest-storage that referenced this pull request Aug 8, 2026
…each week (#58)

## The change

The per-push mutation run becomes **incremental**, and a **weekly cold
run** is added behind it.

Mutation is the deepest gate in this repository and it was cold on every
push that touched `src/` — the honest measurement, but it puts the whole
cold duration in the path of every merge.

`incremental: true` makes the routine run re-test only what changed.
That is a trade rather than a free win: an incremental run reuses stored
verdicts, and Stryker's own guidance is that reused verdicts for
**static** mutants can be stale. So the truth is measured cold once a
week, and that run also refreshes the baseline the following week's
pushes reuse.

## Where the logic lives

The weekly job is a **thin caller**. The 130 lines that decide what
counts as "the score can have moved", why the job is pinned to the
default branch, and why a partial baseline must never be saved live once
in [`bymaxone/.github`](bymaxone/.github#29) and
are shared by every library.

What stays here is what is genuinely local:

- **the schedule**, staggered against the other libraries so a Monday
morning does not start ten cold suites at once;
- **the ceilings**, calibrated to this repository's own measured cold
duration.

## What the weekly run skips

It compares against the commit the last successful run measured and
skips when nothing that can move the score has changed. That surface is
deliberately wider than the per-push job's `^(src/)`: the score also
moves when a **test** changes (a new test kills a mutant that survived),
when the Stryker or Jest configuration changes, or when a dependency
changes underneath the suite. Gating on `src/` alone would skip exactly
the week someone spent writing tests — the week most likely to change
the number.

## Why the ceiling is not the old default

The per-push run is fast now, but the ceiling exists for the **cold**
one: the first run after this merges has no baseline, and neither does
any run whose cache expired. The job is *cancelled* rather than failed
when it lands, which reads as an infrastructure blip rather than a
budget — that is exactly how nest-auth's mutation gate produced nothing
for weeks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants