Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## [1.91.262.0] - 2026-10-27

**The weekly release accepts a commit a green train merged, and five lane frictions seen in operation are gone.** Trains merge faster than the main backstop settles, so most main commits ended with a neutral (`superseded`) `main-backstop` check run and the weekly cut returned `NO_GREEN_COMMIT` for a week of commits every one of which merged through a green train. The evidence already existed in `refs/notes/gstack-trains`; the gate now reads it. Alongside: `gstack-issue-links` no longer turns another repository's `(name #N, #M)` into this repository's Refs lines, `gstack-post` no longer asks for a confirmation on run ids written next to run words, `gstack-post pr-body` no longer wipes a PR body when a lane posts one section, and `gstack-ship-queue ready` finds the pregate verdict a lane recorded from a separate clone.

### Changed
- **`scripts/release-cut.ts`**: a commit on main's first-parent history is gated either by a successful backstop check run or by a train record (kind `train`, state `green`, `merging` or `merged`) whose `merged[]` lists its sha or its tree, the main commits `gstack-ship-queue verify --train` confirmed. Everything else stays required: the required check run succeeded, no red check run (a red backstop on a covered commit still refuses), nothing still running, no active freeze. A commit with neither is refused with the same message as before. The `CANDIDATE` line says `gated by main-backstop` or `gated by train <nonce>`. The notes ref is fetched and read once (`readTrainNotes`: the freeze and the train evidence together; `activeFreeze` is folded into it). New flags `--required-check <name>` (default `free-tests`), `--backstop-check <name>` (default `main-backstop`) and `--notes-ref <ref>` (default `refs/notes/gstack-trains`) let another repository run the script; `weekly-release.yml` is unchanged.
- **`lib/issue-links.ts`**: a parenthesised named reference, `(name #N[, #M]…)`, `(name PR #N)` or `(name issue #N)`, where the name is one token that is not a closure, reference, filler or version word (fix, fixes, fixing, closes, closing, resolves, addresses, implements, supersedes, refs, see, related, part, follow-up, deferred, PR, issue, bug, upstream, cf., e.g., i.e., of, to, the, v1.2, …), is a cross-repo reference to `name` (`Refs name#N`), never a same-repo Refs line or a `fix #N` promotion candidate. `(see #12)`, `(closing #12)`, `(cf. #12)`, `(#301)` and `Fixes #12` keep their meaning; the `owner/repo#N` form is unchanged.
- **`lib/gstack-post.ts`**: an 11-digit GitHub Actions run id right after a run word on the same line (run, runs, screen, screens, gate, gates, backstop, bisect, rerun, dispatched, dispatch, failed, passed, with an optional `:` `#` `=`; a generic word such as id, job, attempt or workflow exempts nothing), and every id chained after an allowlisted one through `, `, ` and `, ` & `, ` or ` or ` / ` (a `(#1234)` between allowed), is exempt from `pii.phone.e164`. `failed 40269992077 | rerun passed 40287239206`, `screen 40269992077 (#3434) and 40275443393 (#3453)` and `gate 40323989742` post without `--confirm`. A bare 11-digit number, a 10-digit national number and `+14155551234` still report.
- **`lib/gstack-post.ts`** (`pr-body`): the body file is merged by `## ` section into the PR's current body (read with `gh pr view --json body`, or `glab mr view`, through the same runner) instead of replacing it. A section in the file replaces the same-titled section in place (case-insensitive), the ````gstack-ship-receipt```` block is a section of its own so a receipt rewrite replaces only the receipt, sections the file does not rewrite are kept byte for byte, new sections are appended after them, and a current body with no `## ` headings is kept as the leading text. A `## ` line inside a code fence is not a section: a fence closes only on a run of its own character at least as long as the opener with nothing after it, so a ``` line inside a ```` block or a ```js line closes nothing, and a receipt fence that never closes is an unclosed fence (the body after it stays one kept section) rather than a receipt that swallows `## Approvals`. The file's leading text replaces the current leading text; text after a receipt block never replaces an intro; whitespace-only text in the file is dropped, so reposting the body /ship composes (receipt, blank line, sections) is byte-stable. One stderr line names the replaced, kept and added sections. `--replace` keeps the plain overwrite for a caller that composed the whole body. The redaction scan and the confirmation token cover the merged bytes, which are the bytes sent. A current body that cannot be read posts nothing (exit 3) and names `--replace`.
- **`lib/pregate/index.ts`**, **`lib/ship-queue.ts`**: `gstack-pregate` mirrors every record it writes to `<state root>/pregate/by-tree/<tree>.json` (best effort; a mirror failure never fails the record). `ready` reads the checkout's `.gstack/tmp/pregate.json` (or `--pregate`) first; when it is missing, unreadable, recorded for another tree, or current but still waiting on a remote receipt, it looks the current tree up in the mirror and uses that record when it verifies, printing `PREGATE: … (from the state root's pregate/by-tree/<tree12>.json, recorded by another checkout)`. A lane whose `gstack-pregate --remote` had to run in a separate clone (a worktree cannot host it) no longer gets `PREGATE_STALE`, `PREGATE_REMOTE_UNCLEARED` or `missing` from its own checkout. Only a run that holds the tests stage stands in (what `--stage tests --remote` writes): a mirror without the `lanes` check (an `--only timing` or preflight-only run), one recorded against another base, or one run under `--policy-from head` is named on the `PREGATE:` line and never certifies READY; a record whose own checks failed or stayed incomplete is never overridden. A mirror for another tree changes nothing; the mirror is written through a temp file and rename.

### Docs
- `docs/releases.md`: the release-gate paragraph describes both kinds of evidence, what train evidence does not relax, the `CANDIDATE` wording and the three flags. `docs/pregate.md`: the by-tree mirror and how `ready` reads it.
- `docs/troubleshooting.md`: `#redact-run-id-as-phone` (what triggers `pii.phone.e164` on a receipt token or a run id, the positions `gstack-post` allowlists, and the fix), `#gstack-post-pr-body-merge` (what `pr-body` keeps, replaces and adds, and `--replace`) and `#gate-error-no-base` (`GATE_ERROR=no_base` from `gstack-gate-diff`, the printed fetch command, and `SCOPE_ERROR=no_base` from `gstack-diff-scope` for the same cause).

### Tests
- `test/release-cut.test.ts`: on the real bare origin with train records pushed to `refs/notes/gstack-trains`, a neutral backstop plus a record naming the sha, or only the tree, is the candidate and the `CANDIDATE` line names the train; a covered commit is still refused by a red check run, a red backstop, a missing or failed required run, a running run, and a record in state `red`, `superseded`, `incomplete` or `free-green` is not evidence; with no record every neutral commit is skipped with the unchanged text and an older covered commit wins; the three flags rename what the gate reads through `planRelease` and the CLI, and the defaults do not see a record under another ref.
- `test/ship-issue-intent.test.ts`: `(gbrain #7625, #7606)`, `(gbrain PR #7630)` and `(acme/tools issue #4)` are cross references with no Refs line and no promotion candidate; `(see #12)`, `(fixes #12)`, `(PR #12)` and the other reference words stay bare refs; `(#301)` and `Fixes #12` are unchanged.
- `test/gstack-post.test.ts`: each run-word and chained shape posts; the id without a run word, an id after `see`, a 10-digit number, `+14155551234` and `run +14155551234` still need confirmation. For `pr-body`, through `runPost` with a fake runner answering `gh pr view --json body`: a first-section-only file keeps the receipt and the other sections and the stderr line names them; a same-titled section is replaced in place; a receipt-only file replaces only the receipt; new sections are appended; a `## ` inside a code fence is not a heading; `--replace` overwrites without reading the body; a headingless current body is kept as the leading text; an empty current body posts the file byte for byte; a secret in a kept section is a HIGH refusal on the merged bytes; a failed read is exit 3 with the `--replace` hint; `--replace` on another operation is a usage error.
- `test/gstack-ship-queue.test.ts`: a mirror for the current tree under `GSTACK_STATE_ROOT` makes `ready` pass from a stale `--pregate` and from a missing default file, with the `from` suffix; a mirror for another tree leaves `PREGATE_STALE` and `missing` as they were; a current explicit `--pregate` is used as before with no suffix.

### Gate integrity
- No threshold, rubric, trial count, case kind or judge prompt changed. The release gate accepts a new kind of evidence (a green, merging or merged train record naming the commit) and relaxes no other requirement; the `gstack-post` allowlist widens only for 11-digit tokens in the listed positions.

## [1.91.261.0] - 2026-10-27

**`gstack-restamp --next` moves a PR's quoted `since:` rows when its VERSION already equals the base's (gbrain #7640).** A gbrain PR stamped 0.60.468.0, lost its slot, merged master, and master was 0.60.468.0 by then. At its next slot restamp left the PR's `{ since: '0.60.468.0', … }` notice rows at .468 while the release shipped as .469, and gbrain's `compareReleases` then hid that notice from every user already on .468. Two gaps combined: with VERSION equal on both sides the branch had no stamp of its own, so nothing was rewritten everywhere, and the mention scanner that reads the lines the branch added could not pass the opening quote of a quoted row (`since:\s*(\d…)`), so it found nothing either. `--was 0.60.468.0` was no answer, because it rewrites master's own .468 rows too. The quote fix alone covers that merged-base state (the base's .468 is newer than the branch's .467 fork point, so the scanner names it); the equal-version rule below covers the state the fork-point floor cannot, a branch rebased onto the base after it reached X, or one never stamped.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.91.261.0
1.91.262.0
2 changes: 1 addition & 1 deletion agents-digest/gstack-AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# gstack digest v1.91.261.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.91.262.0 — regenerate/re-copy after upgrading gstack

Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
Expand Down
27 changes: 26 additions & 1 deletion docs/pregate.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,26 @@ the machine's) and every `requires-remote` obligation carries a receipt
receipt then carries `pregate` and `requires_remote`. `gstack-pregate verify`
runs the same check standalone.

**By tree.** Every record `gstack-pregate` writes is also mirrored, byte for
byte, to `<state root>/pregate/by-tree/<tree>.json` (the state root from
docs/state-root.md). `gstack-ship-queue ready` reads the checkout's
`.gstack/tmp/pregate.json` first; when that file is missing or was recorded
for another tree, or is current but still waits on a remote receipt, it looks
the current tree up in the mirror and uses that record when it verifies,
printing `PREGATE: … (from the state root's pregate/by-tree/<tree12>.json,
recorded by another checkout)`. This is for the lane whose `gstack-pregate
--remote` had to run from a separate clone (a worktree cannot host it): the
clone's record clears the lane's own checkout as long as both hold the same
tree. Only a run that holds the tests stage stands in (what `--stage tests
--remote` writes, `checks=[lanes]`; a `--only timing` or preflight-only run
is "a run without the tests stage"), and it must be recorded against
`origin/<base>` and not under `--policy-from head`;
otherwise the `PREGATE:` line says why the mirror cannot stand in. A record
whose own checks failed or stayed incomplete is final and no mirror overrides
it. An explicit `--pregate <file>` that verifies is used as before; a mirror
for a different tree changes nothing. Writing the mirror is best effort (a
temp file renamed into place) and never fails the record itself.

Unknown flags exit 2 with usage on every verb (an agent told to run a flag this
gstack lacks never gets a green it did not ask for). Tier 2 checks still to come
(`ratchets`, `lint`, `links`, `hermetic`, `patches`, `integrator`) are named in
Expand Down Expand Up @@ -891,7 +911,12 @@ gstack-issue-links [--base <ref>] [--issue <n>]... [--ci-run <id>]... [--superse
Reads `origin/<base>..HEAD`. `Fixes #N` only on explicit closure intent: a
`Fixes`/`Closes`/`Resolves #N` trailer line or `--issue N`. A bare mention
(a subject's `(#N)` included), a partial fix, a deferred ask and every
cross-repo `owner/repo#N` are `Refs`. Same-repo references are printed as
cross-repo `owner/repo#N` are `Refs`. A parenthesised named reference,
`(name #N, #M)`, `(name PR #N)` or `(name issue #N)`, where the name is one
token that is not a closure, reference, filler or version word, is a
cross-repo `Refs name#N`, never a same-repo Refs line or a promotion
candidate; `(see #12)`, `(closing #12)` and `(#301)` stay bare refs.
Same-repo references are printed as
promotion candidates (`reference #N ← <commit line> (promote: fix #N)` on
stderr, `candidates` in `--json`): the owner's `fix #N` reply reruns with
`--issue N`; an unattended run keeps them `Refs` and lists them in the
Expand Down
50 changes: 34 additions & 16 deletions docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,39 @@ commit on main's first-parent history, moved only by this workflow, only
forward.

**Green** means, on that exact commit: the `free-tests` check run succeeded
(the status main requires), the `main-backstop` check run succeeded, every
check run on it has completed and none failed, and `refs/notes/gstack-trains`
holds no active freeze. The Main backstop gate workflow publishes
`main-backstop` 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 the push before it was gated (superseded) or a freeze's
own backstop run owns it (deferred), failure when it froze the queue or could
not finish. Neutral is never green: a commit whose own tree was never gated is
not released. A push that gets no backstop run at all (a dependabot push, a
policy without a train block) has no `main-backstop` check run and is not
green either; a cancelled check run counts as red. A commit with a check run still going is skipped and the next
older one is considered, back through 7 days or 200 first-parent commits. The
workflow runs no tests and no paid evals 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.
(the status main requires), the commit was gated (below), every check run on
it has completed and none failed, and `refs/notes/gstack-trains` holds no
active freeze. A cancelled check run counts as red. A commit with a check run
still going is skipped and the next older one is considered, back through 7
days or 200 first-parent commits. The workflow runs no tests and no paid evals
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
`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
the push before it was gated (superseded) or a freeze's own backstop run owns
it (deferred), failure when it froze the queue or could not finish. The second
is a train record in `refs/notes/gstack-trains` (kind `train`, state
`green`, `merging` or `merged`) whose `merged[]` lists the commit's sha or
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
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
neither a successful backstop run nor a train record is not released: a
dependabot push, a policy without a train block, or a tree that was never
gated.

Another repository runs the same script with its own names:
`--required-check <name>` (default `free-tests`), `--backstop-check <name>`
(default `main-backstop`) and `--notes-ref <ref>` (default
`refs/notes/gstack-trains`). `weekly-release.yml` passes none of them.

A cut that stopped half-way (tag and `stable` pushed, release not created) is
finished by the next run, by cron or by hand: `stable` itself stays a
Expand Down Expand Up @@ -135,7 +153,7 @@ is exercised against real check runs before it merges.

| Line | Meaning |
|---|---|
| `CANDIDATE: <sha> (<date>)` | the commit that will be (or was) released |
| `CANDIDATE: <sha> (<date>) gated by main-backstop|train <nonce>` | the commit that will be (or was) released, and which evidence gated it (`[hotfix: --ref]` before `gated by` on a `--ref` cut) |
| `NOTHING_NEW: origin/main <sha> is already released as v<X>` | nothing merged since the last release; exit 0 |
| `NO_GREEN_COMMIT: ...` with `SKIPPED:` lines | nothing in the window passed the gate; exit 1 (a dry run exits 0: the skips are its answer) |
| `REFUSED: non-fast-forward: ...` | the candidate is behind stable; stable only moves forward |
Expand Down
Loading
Loading