Files
pulse/scripts
rcourtman c157b921b0 Recover activation for release-train snapshot runs (#3297)
## What

`recover-release-activation.yml` now also admits failed
`create-release.yml`
runs dispatched from release-train snapshot refs
(`release-candidate/<packet>`
or `release-dispatch/<packet>`), not only from `main`.

A snapshot run is admitted only when main's `release_snapshot.py` proves
its
commit is the merged head of exactly one `release-candidate/<packet>`
release
PR. The source run's own `prepare` job ran that branch's code, so it is
not
trusted. Recovery now refuses to run unless dispatched on `main`,
fetches full
history for the ancestry check, and gains `pull-requests: read` for the
PR
lookups.

## Why

The release train has dispatched on `release-candidate/*` branches since
October, and will move to post-merge `release-dispatch/*` branches so
release
secrets can be confined to refs that exist only after merge. A train
release
that failed at activation could not use this recovery path at all.

## Stacking

This branch is built on #3295 (snapshot check accepts
`release-dispatch/*`
refs), whose commit is included here. The two PRs touch different files.

## Validation

- New executable test runs the recovery admission against stubbed GitHub
and
git: `main`, `release-candidate/*` and `release-dispatch/*` runs are
admitted;
lane branches, missing, unmerged or mismatched PRs, a different head
commit,
two matching PRs, malformed refs and a merge missing from main are
rejected.
- Mutations that each fail it: skipping the provenance check, and not
mapping
`release-dispatch/*` to its candidate head. Two more (admitting any
branch in
the run predicate, dropping the head-SHA filter in PR selection) are
caught
  by `release_snapshot.py`'s own checks instead.
- `release_promotion_policy_test.py` 73/73, `check_workflow_trust.py`
and its
  44 tests, `release_snapshot_test.py` 7/7, all on a full checkout.
2026-10-25 23:31:19 +00:00
..
…
…
…
2026-10-25 08:39:03 +00:00