* fix(#2452): stop shallow-fetching the base ref in three-dot-diff CI gates
`mutation.yml` re-fetched the base branch with `--depth=1` after checking out
with `fetch-depth: 0`. The shallow re-fetch truncates the base ref's ancestry,
so `git diff --name-only origin/<base>...HEAD` in scripts/mutation-matrix.cjs
can no longer compute a merge base and aborts with
`fatal: origin/next...HEAD: no merge base` (exit 2).
The `detect` job then fails and the `mutate` shards never run — so the 80%
mutation-score threshold went UNVERIFIED rather than enforced. The failure is
branch-position dependent, which is why it went unnoticed: a branch already
level with the base incidentally passes (its merge base IS the single fetched
commit), while a branch that is BEHIND fails. Observed on PRs #2436 and #2005.
`changeset-required.yml` and `docs-required.yml` shallow-fetched the base ref
too (`--depth=50`), shrinking the same window further. All three now fetch the
BASE REF unshallowed.
Their shallow *checkout* depth is left at 50: that is a separate, deliberate
cost control with fail-closed semantics, owned by
tests/policy-lint-shallow-checkout.test.cjs. Only the base-ref fetch changes.
The two `${{ }}` interpolations in mutation.yml's run: blocks now pass the base
name through `env:`, matching the sibling workflows.
Regression coverage in tests/mutation-workflow-base-ref.test.cjs:
- a per-workflow contract guard asserting the base fetch carries no --depth
(RED on origin/next for all three files, GREEN here). The YAML step parser
handles block scalars and skips commented-out steps, so a future refactor
to a multi-line `run:` cannot silently degrade the guard.
- a real-git mechanism proof with boundary coverage at the shallow edge:
with the base advanced 60 commits past the branch point, --depth=1 and
--depth=60 both fail with `no merge base`, --depth=61 (merge base exactly
at the boundary) succeeds, and an unbounded fetch succeeds. Each variant
uses an independent clone, because a plain fetch does not un-shallow a repo
that already carries a .git/shallow boundary.
Also fixes a startup race in tests/run-with-timeout.test.cjs surfaced by this
branch's gsd-test run (C1, linux-node24). The heartbeat file only appeared
~100ms after the grandchild's runtime was up, but the window is 1s spanning two
cold node starts, so on a loaded runner the timeout fired before any heartbeat
existed and the precondition failed for reasons unrelated to reaping. The child
now writes its heartbeat once synchronously at startup; the frozen-vs-ticking
comparison that actually proves reaping is unchanged.
Closes#2452
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(changeset): backfill PR number to 2485
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>