mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 15:57:33 +01:00
feat(release-gate): gate main every 8 hours and feed the stable release (inert until adopted)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
923fc0a6b8
commit
2dcbe9dbbd
20 files changed
+1415
-30
No files matched your search
+4
-1
@@ -298,7 +298,10 @@ through an urgent one-PR train (or `train build --repair` while the queue is fro
|
||||
refuses `--urgent`); all prefixes green earns exactly one unchanged rebuild;
|
||||
anything else goes to a repair lane. An infra-only census earns one re-dispatch. A
|
||||
backstop gate runs on every push to the base branch and freezes the queue when it finds
|
||||
a tree no green train covered; one repair PR at its exact head clears the freeze.
|
||||
a tree no green train covered; one repair PR at its exact head clears the freeze. A
|
||||
policy with a `release_gate` block swaps that per-push backstop for one full gate on main
|
||||
every 8 hours, whose red verdict holds the stable release instead of freezing the queue
|
||||
([ship-policy.md#release-gate](ship-policy.md#release-gate)).
|
||||
|
||||
**Wave PRs when the queue is long.** Every slot pays its own
|
||||
stamp, its own round of required checks and its own version, one after another. When more
|
||||
|
||||
+11
-2
@@ -33,7 +33,7 @@ of its own; it only reads what the trains and the backstop already recorded.
|
||||
With nothing green in the window it fails and lists why each commit was
|
||||
skipped.
|
||||
|
||||
**Gated** means one of two pieces of evidence. The first is the
|
||||
**Gated** means one of three pieces of evidence. The first is the
|
||||
`main-backstop` check run: the Main backstop gate workflow publishes it on
|
||||
each push to main from a job of its own, success when a green train record
|
||||
covers the tree or the backstop gated it green, neutral when main moved past
|
||||
@@ -45,7 +45,12 @@ its tree: those entries are the main commits `gstack-ship-queue verify
|
||||
--train` confirmed, so a commit a green train merged is gated even when trains
|
||||
merged faster than the backstop could settle and its own backstop run ended
|
||||
neutral or never ran. The `CANDIDATE` line says which: `gated by
|
||||
main-backstop` or `gated by train <nonce>`. Train evidence relaxes nothing
|
||||
main-backstop` or `gated by train <nonce>`. The third is a GREEN release
|
||||
record (kind `release`) on the commit, written by the release gate
|
||||
([docs/ship-policy.md#release-gate](ship-policy.md#release-gate)) when the
|
||||
policy declares `release_gate`; the line then says `gated by release gate run
|
||||
<id>`. A RED release record with no GREEN one on a later commit that
|
||||
contains it refuses the cut like a freeze. Train evidence relaxes nothing
|
||||
else: a red backstop run, a failed or missing `free-tests` run, any other red
|
||||
check run or a run still going keeps the commit out, and a record in any other
|
||||
state (`red`, `superseded`, `free-green`, …) is not evidence. A commit with
|
||||
@@ -240,6 +245,10 @@ overrides the key for one command. The resolution lives in one helper,
|
||||
- `bin/gstack-upgrade` and `/gstack-upgrade`: they fast-forward to the channel
|
||||
ref (`UPSTREAM_REF=origin/stable` in the skill's output) and say which channel
|
||||
they followed.
|
||||
- `capy/skill/gstack/scripts/install.sh`, the Capy skill's installer (wrapper
|
||||
revision 4): a fresh clone or a floor upgrade follows `stable` when the channel
|
||||
resolves to it and `stable` meets the support floor, and follows main
|
||||
otherwise. `--revision` still pins an exact commit.
|
||||
|
||||
Two edge cases are handled on purpose:
|
||||
|
||||
|
||||
@@ -1118,6 +1118,81 @@ every lane to the per-PR queue at once. `--urgent` ships one PR alone,
|
||||
`--no-screen` skips the paid screen, `--broad` widens it to the full PR selection,
|
||||
and `train build --prs <list without it>` drops a PR at any step.
|
||||
|
||||
<a id="release-gate"></a>
|
||||
## `release_gate`: one full gate on main every 8 hours
|
||||
|
||||
Trains gate each batch of PRs, and the main backstop gates every push. The
|
||||
release gate replaces the backstop with one full paid gate on main's head at
|
||||
most every `cadence_hours`, and only when main moved since the last gate. It
|
||||
feeds the stable release (docs/releases.md) and never writes `stable` itself:
|
||||
`scripts/release-cut.ts` stays the only writer, with its 72-hour floor and its
|
||||
security and regression exceptions unchanged.
|
||||
|
||||
The surface is off unless main's `.gstack/ship-policy.json` declares the
|
||||
block (key version 1.94.0.0, so `min_gstack_version` must be at least that):
|
||||
|
||||
```json
|
||||
"release_gate": {
|
||||
"cadence_hours": 8, "gate_workflow": "evals.yml", "branch": "main",
|
||||
"stable_ref": "stable", "check_name": "main-backstop",
|
||||
"on_red": "hold-release", "bisect_spend_cap_usd": 40
|
||||
}
|
||||
```
|
||||
|
||||
Every field is required. `cadence_hours` runs from 8 (the workflow's
|
||||
schedule) to 168. `check_name` is the check run release-cut reads as gated,
|
||||
so gstack keeps `main-backstop`. `bisect_spend_cap_usd` caps one red gate's
|
||||
bisect (0 skips it).
|
||||
|
||||
**What runs.** `.github/workflows/release-gate.yml` fires every 8 hours and
|
||||
on a manual dispatch from main. Without the block it stops after a git-and-jq
|
||||
policy read, so no paid work starts and nothing is written. With the block it runs
|
||||
`gstack-release-gate run`, which:
|
||||
|
||||
1. skips when main has not moved since `stable` or since the last gate, or
|
||||
when the last gate is younger than `cadence_hours` (`--ignore-cadence`
|
||||
overrides only the age);
|
||||
2. writes a `release` record (`gstack-trains:release:<repo>:<sha>`) to
|
||||
`refs/notes/gstack-trains` before it dispatches, so a lost run resumes;
|
||||
3. dispatches the full gate on that sha and reads its verdict and flake
|
||||
records. A case that failed once and passed its one rule-16 rerun is green
|
||||
and is listed as flaky;
|
||||
4. records GREEN, RED or INCOMPLETE. An INCOMPLETE gate is re-dispatched once
|
||||
by the next run, then left for a person;
|
||||
5. on RED, bisects the red cases over main's first-parent commits since the
|
||||
last green gate (at most 4 rounds, within the spend cap) and records the
|
||||
first bad commit;
|
||||
6. publishes `check_name` on the gated commit from a separate job that holds
|
||||
`checks: write` and runs no repository code: success for a green gate,
|
||||
failure for a red or failed one.
|
||||
|
||||
**What release-cut reads.** A GREEN release record on a commit counts as
|
||||
gated, the same as a successful `main-backstop` check run. A RED record
|
||||
with no GREEN record on a later commit that contains it holds every cut, like a
|
||||
queue freeze: the `REFUSED` line names the red sha and cases. Land the revert
|
||||
or the fix-forward, then gate again with `gstack-release-gate run
|
||||
--ignore-cadence` (or dispatch the workflow). Nothing else clears a hold.
|
||||
|
||||
**The per-push backstop stands down.** `evals-main-backstop.yml` keeps its
|
||||
push trigger. While the block is set, its policy step reports `train=false`,
|
||||
so a push dispatches no gate, freezes nothing and publishes no check run. Main
|
||||
is never gated twice. Trains keep running until the train lane retires them.
|
||||
|
||||
```bash
|
||||
gstack-release-gate run [--dry-run] [--sha <sha>] [--ignore-cadence]
|
||||
gstack-release-gate status # the newest record, the hold if any, pending commits
|
||||
gstack-release-gate bisect [--sha <sha>] [--spend-cap <usd>] [--dry-run]
|
||||
```
|
||||
|
||||
Common flags: `--repo`, `--policy <file>` (read the block from a file instead
|
||||
of main, for a dry run or a proof before adoption), `--remote`,
|
||||
`--summary`, `--outputs`, `--poll-seconds`, `--budget-minutes`. Every line
|
||||
starts with `RELEASE:`. Exit 0 means gated, skipped, inert or a dry run; 1
|
||||
means held (red); 2 means failed or incomplete.
|
||||
|
||||
**Escape hatch.** Remove the block on main. The per-push backstop gates the
|
||||
next push again, and release-cut reads its check runs as before.
|
||||
|
||||
<a id="ship"></a>
|
||||
## What `/ship` reads from it
|
||||
|
||||
|
||||
Reference in new issue
Block a user