screen #3613: merge 9e1593ded9 onto main 319ef500ca

This commit is contained in:
garrytan committed 2000-01-01 00:00:00 +00:00
commit fd11b56eef
49 files changed
+3074 -88

No files matched your search

+7
View File
@@ -257,6 +257,13 @@ loop, per train:
`gh pr merge --match-head-commit`; `verify --train` again.
4. `train prune` when every slot has merged.
Under `changelog_fragments` ([ship-policy.md](ship-policy.md#train-land)) lanes READY with a
fragment (`changelog.d/<slug>.md`) instead of an `[Unreleased]` section, there is no slot
call and no lane stamp, and step 3 is one command per slot: `train land --train <nonce> --pr
<n>` commits the stamped prefix S_i on [pin, main], pushes it, waits for checks, merges and
confirms main is S_i exactly. Outside a train, `gstack-ship-queue land --pr <n>` is the same
step for one PR ([ship-policy.md](ship-policy.md#land)).
A red train: `train bisect` on the red cases over every prefix. An attributed PR leaves
the train and goes back to its lane; red on the base branch itself is fixed forward
through an urgent one-PR train (or `train build --repair` while the queue is frozen, which
+5 -2
View File
@@ -450,7 +450,8 @@ receipt for H when the two trees differ only as a stamp does:
stamps, each hunk in them must be the exact old → new rewrite under
`stamp_patterns` (literal `{version}` templates) or the built-in forms
(`(was X)`, `since: X`, `vX`), and the version file, `package.json`'s
`version`, a lockfile's root version keys and a CHANGELOG re-heading have
`version`, a lockfile's root version keys and a CHANGELOG re-heading (under
`changelog_fragments`, also the fold of the fragments the head deletes) have
their own built-in rules. There is no second list and no looser rule here:
the file verdicts `clear` reaches on a pair are the verdicts
`gstack-tree-receipt --gated G --current H` reaches, and its gate rule holds
@@ -469,7 +470,9 @@ receipt for H when the two trees differ only as a stamp does:
the policy label (`origin/<base>@<sha>:.gstack/ship-policy.json`) and every
differing path with the rule that classed it (`version-file`,
`version-manifest`, `manifest-version`, `lockfile-root-version`,
`changelog-heading`, `stamp_files`, `stamp_paths`, `release_outputs`). `clear` prints the same
`changelog-heading`, `changelog-fragments` for the CHANGELOG under the fold,
`changelog_fragments` for a folded fragment's deletion, `stamp_files`,
`stamp_paths`, `release_outputs`). `clear` prints the same
as `PREGATE_RECEIPT_SUBSTITUTED: …`, `verify` repeats it on its ok line, and
the ship receipt carries the receipt string. The receipt's own tests and
failed checks apply as before (its exit status is checked only when
+9 -3
View File
@@ -129,8 +129,9 @@ A lane (one crew thread, one branch) does this, in order. Every command is
harness-generic; only the thread and the PR tool are Capy.
1. **Branch from the default branch, build, test with focused checks.** Write
the CHANGELOG entry under `## [Unreleased]`; leave VERSION at main's value.
Never pre-pick a version.
the CHANGELOG entry under `## [Unreleased]` (under `changelog_fragments`, a
fragment in the policy's fragment directory instead); leave VERSION at
main's value. Never pre-pick a version.
2. **Push the final head.** Everything below binds to the pushed head; a later
push invalidates the receipt, the pregate record and the review binding.
3. **Open the PR** with the host's PR tool (Capy: `tools.pr.create`, never
@@ -182,6 +183,11 @@ reference:
<sha>`, or `--merge` where the repository keeps merge commits), and
verifies again. A stamp commit that holds anything beyond the stamp files
fails `verify --train`, freezes the queue and costs a backstop gate.
Under `changelog_fragments` there is no slot call and no lane stamp: the
controller runs `gstack-ship-queue train land --train <nonce> --pr 41` per
slot, which pushes the stamped prefix the gate covered, waits for checks,
merges and confirms main
([ship-policy.md#train-land](ship-policy.md#train-land)).
4. `train prune` when every slot has merged.
**Who merges.** One controller, never the lane. Both crews that ran this
@@ -317,7 +323,7 @@ closed) holds the longer history behind these numbers; from here on
| Running a workflow | the SKILL.md files, `GSTACK_SESSION_KIND=unattended`, `GSTACK_RESULT:` lines | the `GSTACK_LAUNCH` assignments, `GSTACK_CAPY=1` and the `capy-asks` block, no enforced hooks (safety skills are advisory) |
| Approvals | `gstack-approvals`, `gstack-owner-brief render --captain`, the `## Approvals` table | the Captain relay (`needs_you`), the Captain drive `approvals/` mirror |
| Pull requests | `/ship`, the ship receipt, `gstack-pr-shots` | `tools.pr.create` (callbacks), `tools.pr.uploadAsset` (before/after images) |
| Merging | `gstack-ship-queue` (ready, train build, verdict, verify), `gstack-restamp`, `gh pr merge --match-head-commit` | the controller is a Captain thread; slot calls are thread messages |
| Merging | `gstack-ship-queue` (ready, train build, verdict, verify; train land and land under `changelog_fragments`), `gstack-restamp`, `gh pr merge --match-head-commit` | the controller is a Captain thread; slot calls are thread messages |
| Flaky pass | the rerun rule, `flake-records-a*` and `flake-ledger-*` artifacts, `test:health` | the daily automation and the ledger in the Captain's Drive |
| Release | `release-cut`, `weekly-release.yml`, the `stable` channel | nothing |
| Scorecard | `gstack-factory-scorecard`, `/retro` Step 7.5 | nothing (the CSV can be kept in the Captain's Drive) |
+223 -6
View File
@@ -61,6 +61,7 @@ refuses the file (`POLICY_GSTACK_TOO_OLD`). `validate` ends with
| `min_gstack_version` | version or `null` | `null` | The oldest gstack that may read the file; see the compatibility rule above. |
| `docs` | `{ caps: [{ path, max_bytes }] }` or `null` | `null` | Documentation files with a byte cap. `/ship` Step 14.5 runs `gstack-ship-receipt docs-caps --base <base>` (the caps come from `origin/<base>`'s policy, never the branch's copy; `--policy <file>` is for a repository bootstrapping its policy) before the verification gate and prints one `DOCS_CAP: <path> <bytes>/<max> ok\|over` line per cap; any `over` (an absent file counts) exits 1 and blocks the ship until the file is trimmed or the cap raised on the base branch (`DOCS_AUDIT_BLOCKED`). At most 64 caps, each path contained. See [the ship receipt](#ship-receipt). Needs `min_gstack_version`. |
| `train` | `{ gate_workflow, free_workflow, full_gate_check, record_artifact, max_size, fill_hours, screen_selection, trial_reservation_usd }` or `null` | `null` | Turns on [merge trains](#run-a-merge-train), read from the base revision only. Every field is required and an unknown field is `POLICY_INVALID`: `max_size` 1 to 6, `fill_hours` above 0 and at most 24, `screen_selection` `gstack-paid` \| `none`, `trial_reservation_usd` above 0 and at most 100. `null` keeps the per-PR queue exactly as it is. Needs `min_gstack_version`. |
| `changelog_fragments` | `{ dir, required_checks? }` or `null` | `null` | PRs carry unversioned [CHANGELOG fragments](#changelog-fragments) in `dir` (one flat, repo-relative directory, no `..`) and the merge step ([`land`](#land)) assigns the version. `required_checks` (up to 50 distinct names) are the checks [`train land`](#stamped-trains-under-changelog_fragments-train-land) requires present and green on a stamp commit. `null` keeps `[Unreleased]` sections and per-PR stamps. Needs `min_gstack_version`. |
Containment, checked by `validate` and again at write time: paths and globs are
repo-relative with no `..` segment, under 256 characters, at most 64 per list,
@@ -148,7 +149,8 @@ ship receipt into the coordinator protocol, so nobody proves a stamp diff by
hand. Lanes own their PRs and never merge; the coordinator owns the order and
every merge. Each step ends in a line the other side acts on, then
`GSTACK_RESULT: skill=ship-queue status=complete|refused run=<path>`. Nothing
here pushes.
here pushes, except `ready` under `changelog_fragments` (its READY receipt goes
to `refs/notes/gstack-trains`) and [`land`](#land).
**1. Lane: report ready (unstamped).** After the gate and pregate pass on the
pushed head:
@@ -169,8 +171,12 @@ ci: green at <head12> (passed=31 failed=0 pending=0 skipped=2)
PREGATE: pass tree=<tree12> head=<head12> checks=6
gate: command="<gate_command>" full_e2e=no gate-ahead=none
SHIP_RECEIPT: pr=42 head=<head12> …
READY_RECEIPT: <state root>/ship-queue/ready/<repo key>/42-<head>.json (notes pushed to origin refs/notes/gstack-trains)
```
Under `changelog_fragments`, a `READY_RECEIPT:` line follows, written only when
every check above passed; `land` refuses a pin without one.
CI's Version Gate (`scripts/compare-pr-version.ts`) accepts that head as
unstamped when `VERSION` and `package.json`'s version match its merge base, the
CHANGELOG change is confined to `[Unreleased]`, and any changed release output
@@ -359,6 +365,65 @@ are mirrored to origin as git notes on `refs/notes/gstack-trains` before any
remote effect, so a lost machine loses nothing: `train status` on a clean
state root rebuilds them. `GSTACK_SHIP_QUEUE_NOW` pins the clock for tests.
<a id="train-land"></a>
### Stamped trains under `changelog_fragments`: `train land`
When the base policy also declares [`changelog_fragments`](#changelog-fragments),
`train build` builds every prefix twice. T_i is the plain chain above. S_i is
what slot i publishes: S_0 is the base and S_i = stamp(merge(S_(i-1), pin_i)),
stamped by `gstack-restamp` with S_(i-1) as its base, so slot i folds only its
own fragments (a stacked pin's parent fragments are already folded) and the
versions run in slot order, each with its slot's effective bump. Before
anything is pushed, each S_i must be merge(S_(i-1), pin_i) modulo stamps,
keep every release entry of S_(i-1) byte for byte, and equal T_i outside
release paths (version files, CHANGELOG, fragments, `release_outputs`,
`stamp_paths`, `stamp_files`); otherwise `QUEUE_TRAIN_STAMP_MISMATCH`. Pass
`--allow-repo-commands` when the policy has a `restamp_command` or
`release_tool`. The record keeps both chains (`prefixes` and `stamped`), the
train head and branch are S_k, and the one full gate runs on S_k, the exact
tree the last merge publishes.
Slots merge with `train land --train <nonce> --pr <n>`, never a slot call:
1. The record must be the gated head's own chain. Walking first parents from
S_k gives T0 and S_1..S_k with the recorded commits and trees, and each
S_i's second parent is slot i's pin. A record rebuilt from notes that
differs anywhere refuses (`QUEUE_PROOF_MISSING`), and every later step uses
only that verified chain. main's tree must equal S_(i-1)'s exactly (slot 1:
the train base), and the PR's head its pin, with its /ship receipt. main
equal to an earlier prefix refuses and names the slot to land first; main
equal to a later prefix confirms the slot (a rerun). A base that cannot be
fetched refuses (`QUEUE_PROOF_MISSING`), a fork head refuses
(`QUEUE_TRAIN_UNTRUSTED`), and a PR whose base is no longer the train's
base refuses (`QUEUE_NOT_READY`), here and again before the merge.
2. It commits S_i's tree with parents [pin, main], adds the
`evals-no-debounce` label (a tool-made head gets no later push, so the
10-minute quiet window protects nothing), and pushes under
`--force-with-lease=<branch>:<pin>`; a foreign push is never overwritten
(`QUEUE_TRAIN_MOVED`). The stamp commit is deterministic and journaled
(`land-<n>.json` beside the train record) before the push, so a rerun
adopts its own stamp head instead of reading it as a move. A push that
fails while the branch still reads the pin is `QUEUE_NOT_READY`; rerun.
3. It waits for the PR's checks on that commit (`--no-wait` prints the
command). Required checks run live at the slot until GSTA-156's exact-tree
reuse is on main. Every name in `changelog_fragments.required_checks` must
be present and green on the stamp commit (a check run of that name, all of
its `<name> (…)` matrix runs, or a status context); a missing one holds the
merge (`QUEUE_NOT_READY`) and the fix names it. Without the list, any green
set counts, so set it to the checks branch protection does not enforce.
4. It refuses if main moved during the checks, merges with
`--match-head-commit`, and confirms main's tree is S_i exactly. Any other
main is the freeze path above (`QUEUE_TRAIN_TREE_MISMATCH`, backstop).
A slot counts as merged only when GitHub reports its PR MERGED. Main at
S_i with the PR still open refuses (`QUEUE_PROOF_MISSING`), and the
train's merged count, its freeze decision and the freeze record read
GitHub's state for every slot, never the record's `merged` list alone.
`verify --train` refuses a stamped train and names `train land`. The backstop
covers main only when its exact tree is some S_i of a green record (plain
T_i records still cover modulo stamps). A dropped slot changes every later
version, so the train is rebuilt, as today.
<a id="ship-before-ready"></a>
**0. Lane: /ship before READY.** Every READY follows a /ship run on the branch
at the head being READY'd, including a re-READY after a push (Garry,
@@ -493,9 +558,10 @@ records does not stand; see [the ship receipt](#ship-receipt)),
`QUEUE_TRAIN_CONFLICT: train <id> stopped at #<pr> (conflicts: <files>) after
merging #<a>,#<b>; nothing dispatched, no branch pushed; fix: …` line names
the build without that PR) and `QUEUE_TRAIN_FREE_RED` (free tests red,
missing, cancelled or on another SHA). In a merge, version files take the
prefix's side and the CHANGELOG merges by section, since the stamp at merge
rewrites both; a PR whose CHANGELOG drops or renames a released heading (or
missing, cancelled or on another SHA). In a merge, the version file takes the
prefix's side, a manifest or npm lockfile conflict merges three-way with the
prefix's version (so a pin's dependency edit survives), and the CHANGELOG
merges by section, since the stamp at merge rewrites them; a PR whose CHANGELOG drops or renames a released heading (or
carries a released entry's text in a section of its own) is a CHANGELOG
conflict, never merged with the damage, except the stacked child whose parent
was released since their merge base, which `mergeChangelogs` carries (see *A
@@ -530,7 +596,8 @@ gstack-ship-queue train rebuild --train <nonce>
```
The bisect dispatches the red cases (`cases`) on every prefix T0..Tk
(`prefix_shas`), one trial each, after checking every prefix is an ancestor
(`prefix_shas`; for a [stamped train](#train-land), T0 and S_1..S_k, the
chain its gate ran), one trial each, after checking every prefix is an ancestor
of `train/<nonce>` (`QUEUE_TRAIN_PREFIX_INVALID`). It reserves every
case×prefix trial (unknown costs at `trial_reservation_usd`) and launches only
the prefixes that fit the cap (`QUEUE_TRAIN_BISECT_CAPPED`; completed prefixes
@@ -779,6 +846,7 @@ which carry the workflow consequences the skill prose would otherwise spell out:
| `ship: preregistration: …` | `gstack-evidence ancestor --from-policy --base <base>` must print only `ANCESTOR: ok` before push |
| `ship: release_tool: …` | the command runs only inside `gstack-restamp` (staging worktree, outputs confined to `release_outputs`) |
| `ship: review_scope …` | Step 9.4 item 0 runs `gstack-review-scope classify` on each pass's findings; see [review_scope](#review-scope) |
| `ship: changelog_fragments <dir>: …` | Step 12 writes no version; Step 13 writes the entry as `<dir>/<branch-slug>.md` (entry text only, `bump:` front matter only when the change warrants more than the policy bump), never an `[Unreleased]` section, linted with `gstack-prose-lint <file> --gate`; `gstack-ship-queue land` assigns the version; see [changelog fragments](#changelog-fragments) |
The PR body's first section is the ship receipt (`gstack-ship-receipt write … --body`),
written by the pr-body section of `/ship` after the body is composed.
@@ -1158,6 +1226,154 @@ Read from the base branch and run only with `--allow-repo-commands`, like
of its own choosing cannot run here; give it a mode that takes the version and
writes only stamps.
The command also gets `GSTACK_RESTAMP_BASE_SHA`, the release predecessor: the
base branch commit the version is allocated after (origin/<base> for a per-PR
restamp and for `land`, S_(i-1) for a [stamped train](#train-land)'s S_i). A repo counter that must be predecessor + 1 (a
SAVE_VERSION, a rules number) reads its old value from that commit
(`git show "$GSTACK_RESTAMP_BASE_SHA:<file>"`), never from the branch, so two PRs
cut from one base never both claim the same number. Regenerated goldens go in
`release_outputs`.
<a id="changelog-fragments"></a>
## `changelog_fragments`: entries without versions, versioned at merge
With `changelog_fragments: { "dir": "changelog.d" }` on the base branch, a PR
never edits VERSION, the manifest `version` key or a released CHANGELOG
section. It adds one or more fragments, and the merge step assigns the
version, so two PRs cut from one base cannot collide and no lane redoes a
stamp after another PR merges.
A fragment is `<dir>/<slug>.md`, one regular file directly in `dir`
(`README.md` there is skipped). Its body is the entry text, exactly what the
CHANGELOG section will hold, with no `## [x]` heading. It may open with front
matter that raises the bump:
```markdown
---
bump: minor
---
**What changed, in one sentence.**
### Fixed
- ...
```
The effective bump of a stamp is the highest of the policy bump and every
folded fragment's `bump:` (`micro` < `patch` < `minor` < `major`). Fragments
are read as Git blobs at the commit being stamped (`git ls-tree` and
`git cat-file`, never the working tree), so a symlink, a gitlink, a nested
path, a non-`.md` file, an empty fragment, one over 64 KiB, one carrying a
release heading, or one whose name already landed on the base with other text
is `CHANGELOG_FRAGMENT_INVALID`, and nothing is allocated. Only fragments the
branch adds against the base count.
**/ship.** `show-effective` prints a `ship: changelog_fragments` line, so /ship
writes no version in Step 12 and writes Step 13's entry as
`<dir>/<branch-slug>.md`, linted with `gstack-prose-lint <file> --gate`, never an
`[Unreleased]` section.
**The fold.** `gstack-restamp` (and so `land`) concatenates the fragment
bodies in path order above any legacy `[Unreleased]` section, re-heads both as
one `## [<version>] - <date>` section at the top, and deletes the fragment
files, in one stamp. The tree receipt, and so `gstack-pregate clear`'s
stamp-only substitution, calls a deleted fragment a stamp (rule
`changelog_fragments`) only when the new CHANGELOG section is exactly the
deleted fragments' bodies in path order above any legacy `[Unreleased]` text
(rule `changelog-fragments`); an edited fold, or a fragment deleted without
its text, is a change.
**Version Gate.** The workflow runs on every PR (no path filter), so a PR
that touches no release file still has to prove its entry. Under the key, an
unstamped head must keep VERSION, the manifest `version` key and every
released CHANGELOG section at its merge base's values, and add at least one
valid fragment (a legacy `[Unreleased]`-only entry passes during migration;
a `[bot]` author, such as Dependabot, needs none). Other manifest edits, a
dependency or a script, pass. A head made by the merge step, a commit whose
parents are [pin, base], must carry the base's version plus the effective
bump of the fragments its pin adds; a stacked pin's parent fragment whose
text the base already released is skipped, not counted again. Under a train policy `gstack-ship-queue
ready` applies the same rule itself; without one, `ready` requires the head's
checks green, Version Gate included.
<a id="land"></a>
## `gstack-ship-queue land`: the per-PR merge step
`land --pr <n>` merges one READY PR whose head is unstamped. It replaces the
lane's stamp round trip. Nobody stamps by hand; the tool makes and pushes the
stamp commit. Steps, each journaled in
`.gstack/tmp/ship-queue/land/<n>.json` and resumable by rerunning the same
command:
1. **Refuse** a fork head, a closed PR, a PR whose base is another branch, a
base policy with `changelog_fragments` off (`QUEUE_NOT_READY`), a pin
without a READY receipt (`gstack-ship-queue ready --pr <n>` records one
on `refs/notes/gstack-trains` once its pregate, full_e2e and receipt checks
pass; land checks nothing weaker), a /ship receipt that is missing or
bound to another head (`QUEUE_RECEIPT_MISSING`, `QUEUE_RECEIPT_STALE`), and
required checks that are not green at the pin.
2. **Lock.** Take the repository land lock, `refs/notes/gstack-land` (its own
ref, never the trains notes ref), by exact lease. It records the PR, holder,
phase and journal. A held lock is `QUEUE_LAND_LOCKED` naming the holder;
`--break-lock` takes over a crashed holder's lock and is refused while that
holder entered a push or a merge less than 10 minutes ago, and for a drift
lock. A refusal before the push releases the lock.
3. **Merge and stamp.** In a scratch worktree, merge the base into the pin.
Conflicts in release fields only (the version file and `release_outputs`
take the base's side, the CHANGELOG merges by section, a manifest or npm
lockfile is re-merged with every side's release version set to the base's,
so a clean dependency edit survives) are resolved; anything else is
`QUEUE_LAND_CONFLICT`, nothing pushed, back to the lane. A legacy pin that
bumped VERSION itself has its release fields put back to its merge base
first and its entry re-headed `[Unreleased]` (a manifest version source
keeps its other edits; a symlinked release file refuses). A PR with no entry gets a
fragment written from its title. Then `gstack-restamp --next` folds and
allocates (`restamp_command` and `release_tool` need
`--allow-repo-commands`, as everywhere).
4. **Push.** `commit-tree` the stamped tree with parents [pin, base] and push
it to the PR branch under `--force-with-lease=<branch>:<pin>`. The journal
keeps the reviewed pin apart from the last pushed head, so a rerun leases
on what it pushed and never on a head someone else pushed
(`QUEUE_TRAIN_MOVED`, never overwritten).
5. **Wait** for the PR's checks on the stamp commit, polling every 30 seconds
with the same check read the pin passed (`--timeout <min>`, default 60). `--no-wait`
stops after the push and prints `gstack-pr-wait <n> --until green &&
gstack-ship-queue land --pr <n>` to run instead.
CI runs for real on the stamp commit in this mode.
6. **Merge.** Re-read the base first: if it moved, restamp from the new base
and push again under the last-pushed lease, at most twice
(`QUEUE_LAND_BASE_BUSY` on a third move, which releases the lock). Then
`gh pr merge --squash --match-head-commit <stamp>` (`--merge` under
`history: merge-only`) and confirm the base's
tree is the stamped tree on the base land stamped. The stamp date is taken
once and reused by every restamp. A rerun after GitHub merged the PR
(an error reply, a crash) goes straight to that tree check.
7. **Drift.** GitHub's merge API cannot bind the base, so a merge made outside
`land` can slip in between the check and the squash. The post-merge check
then fails with `QUEUE_LAND_DRIFT` and keeps the lock.
`land --pr <n> --repair` opens a PR from the base whose only change is a
fragment for what the outside merge brought (its unfolded fragments, moved,
or one entry naming its commits) and records it on the lock; /ship and land
that PR, from any checkout, and its land takes over the drift lock and
releases it. A rerun of `--repair` finds the PR it already opened.
`--dry-run` merges and stamps in a scratch worktree and prints the version and
tree, taking no lock and pushing nothing.
<a id="migrating-to-fragments"></a>
### Migrating to fragments
Before turning the key on, run `gstack-ship-queue land --census` (read-only).
It prints one line per open PR, saying whether land would fold its fragments, re-head a legacy
`[Unreleased]`, rewrite a tentative per-PR VERSION bump, resolve a
release-field-only conflict, write an entry from the title, or refuse on a
product conflict. Then set `changelog_fragments` and `min_gstack_version` in
one policy PR; a claim-at-open repository switches `queue_mode` to
`stamp-at-merge` in the same PR. Open PRs need no change: `land` handles each
census case. Rollback is removing the key. With it off, `gstack-restamp`
neither folds nor deletes fragment files, so first land every open PR that
carries a fragment (`land --census` lists them) or move each fragment's text
under `[Unreleased]` in its PR; otherwise that entry never reaches CHANGELOG.md.
<a id="tree-receipt"></a>
## The tree receipt
@@ -1181,7 +1397,8 @@ Hunks are classified per file type: the version file (old → new only), the
manifest (`version` key only), npm and bun lockfiles (root `version` keys
only, where `gstack-version-bump` writes; a dependency entry is `changed`),
the CHANGELOG (a heading move or re-heading, compared as an order-independent
multiset of entries), `stamp_paths` and `release_outputs` (every
multiset of entries; under `changelog_fragments`, also exactly the deleted
fragments folded under the new heading, and those deletions are stamps), `stamp_paths` and `release_outputs` (every
removed/added pair must be an exact `(was X)` / `since: X` / `vX` rewrite (a quote around X in the first two forms included) with
X the gated version, or a newer version of the repo's width that the
stamp_paths stage moves (a tentative `*_KEY_VERSION` written as the next
+98 -7
View File
@@ -4242,12 +4242,18 @@ HEAD is not the PR's pushed head (read through `gh pr view`), CI at that
exact head is not green (failed, pending or unreadable), or the pregate result
is missing, failed or recorded on another tree (`PREGATE_STALE`). Under
`--dispatch-ci` it is also a dispatched workflow run that concluded red (a red
run clears no pregate lane) or a `gh workflow run` that failed.
run clears no pregate lane) or a `gh workflow run` that failed. `train land`
uses it while a slot cannot merge yet: checks on the stamp commit are not
green, a name in `changelog_fragments.required_checks` is missing or not green
there, the PR's base is no longer the train's base, the stamp push failed while
the branch still read the pin, `gh pr merge` refused, or the merge is not yet
visible on main.
**Fix.** Commit and push, wait for CI on that head, rerun
`gstack-pregate run --allow-repo-commands` when the tree changed, then rerun
`gstack-ship-queue ready --pr <n>`. The block it prints before refusing shows
which line is not yet true.
which line is not yet true. For `train land`, do what `NEXT:` says, then rerun
the same `train land` command; it adopts its own journaled stamp commit.
<a id="queue-proof-missing"></a>
### `QUEUE_PROOF_MISSING`
@@ -4263,8 +4269,13 @@ train still building, running or merging, `train watch` on an unknown record,
unreadable run), `verify --pr <n> --train <nonce>` when main's tree mismatches
and whether a slot merged on GitHub cannot be read (so whether the queue must
freeze is unknown; it never answers "nothing merged" from an unreadable
state), and `ready --screen` when the scratch merge branch cannot be
pushed. Nothing was written unless the `QUEUE_STAMP_REFUSED:` line says
state), `train land` when the base or the train branch cannot be fetched or
the record's T0 and S_1..S_k are not the gated head's first-parent chain
(each S_i's second parent slot i's pin), `train land` when main's tree is the
slot's S_i but GitHub does not report the PR as MERGED (a record whose slot
numbers or merged entries disagree with GitHub),
and `ready --screen` when the
scratch merge branch cannot be pushed. Nothing was written unless the `QUEUE_STAMP_REFUSED:` line says
`committed=yes`.
**Fix.** Supply the named input (fetch the gated head, pass the current base
@@ -4303,7 +4314,9 @@ predecessor PR moved after the gate.
**Meaning.** The ship policy sets `full_e2e: true`, and no passing full-gate
result is bound to the tree being stamped or verified: no recorded gate-ahead
attempt for that gated head, no `--gate-exit 0 --gate-log <file>` from a run
on it, or the recorded run exited non-zero.
on it, or the recorded run exited non-zero. The train commands use it too: a
PR that is not in the train, a train without a green gate, `verify --train` on
a stamped train and `train land` on a plain one (each names the other).
**Fix.** Run the gate the policy's `gate_command` names in the gate-ahead
worktree and bind it with `gstack-ship-queue gate-ahead --record`, or pass the
@@ -4341,18 +4354,92 @@ a `train` block with `min_gstack_version` on the base branch first
**Meaning.** A pinned PR's head repository is not the base repository. The
train branch and its dispatches run with repository secrets, so fork work
never rides a train. `ready --dispatch-ci` refuses a fork head for the same
reason and dispatches nothing.
reason and dispatches nothing, and `train land` refuses one and pushes nothing.
**Fix.** Run the printed `train build --prs <list without it>`; the fork PR
stays on the trusted-branch path.
<a id="changelog-fragment-invalid"></a>
### `CHANGELOG_FRAGMENT_INVALID`
**Meaning.** A CHANGELOG fragment under the policy's `changelog_fragments`
directory is not one regular `.md` file directly in that directory with entry
text, optional `bump:` front matter and no release heading, or it is over
64 KiB, or a fragment of that name already landed with other text. Nothing was
allocated or written. See [changelog fragments](ship-policy.md#changelog-fragments).
**Fix.** Rewrite the named fragment as a flat `<dir>/<slug>.md` holding only
the entry text (and optional `bump: minor|major` front matter); rename it if
the name already landed. Then rerun.
<a id="queue-land-locked"></a>
### `QUEUE_LAND_LOCKED`
**Meaning.** Another `gstack-ship-queue land` holds the repository land lock
(`refs/notes/gstack-land`), or took it from under this run, so this one would
race it for the next version. The line names the holder, its PR and phase.
**Fix.** Wait for the holder to finish. If it crashed, rerun with
`--break-lock`; that is refused while the holder entered a push or a merge
less than 10 minutes ago. A lock in phase `drift` is released by landing its
repair PR ([QUEUE_LAND_DRIFT](#queue-land-drift)).
<a id="queue-land-conflict"></a>
### `QUEUE_LAND_CONFLICT`
**Meaning.** The PR conflicts with the base branch in a product file (or a
release field that cannot be resolved alone, such as a real dependency
conflict in a manifest), so the merge step cannot stamp it. Nothing was
pushed.
**Fix.** The lane merges the base into the PR, resolves the named files, runs
/ship and reports READY again.
<a id="queue-land-drift"></a>
### `QUEUE_LAND_DRIFT`
**Meaning.** After the squash merge, the base branch's tree is not the
stamped tree on the base `land` stamped: a merge made outside `land` slipped
in between the last check and the merge. The lock stays held, in phase
`drift`, so no other land allocates on a release record that is now wrong.
**Fix.** Run `gstack-ship-queue land --pr <n> --repair`. It opens a PR whose
only change is a fragment for what the outside merge brought. Run /ship on it
and land it; that land takes over the lock and releases it. Then find what
merged outside `land`.
<a id="queue-land-base-busy"></a>
### `QUEUE_LAND_BASE_BUSY`
**Meaning.** The base branch moved during the stamp commit's CI on each of
the two allowed restamps, so `land` stopped instead of restamping again.
**Fix.** Find what is merging outside `land` (every merge should go through
it), then rerun `gstack-ship-queue land --pr <n>`; the lock was released and
the rerun restamps from the pin.
<a id="queue-train-stamp-mismatch"></a>
### `QUEUE_TRAIN_STAMP_MISMATCH`
**Meaning.** While building a stamped train (`changelog_fragments`), a prefix
S_i could not be stamped, or it is not the merge of S_(i-1) and its pin
modulo stamps, or it rewrote an earlier release entry, or it differs from the
plain prefix T_i outside release paths. Nothing was pushed or dispatched. See
[stamped trains](ship-policy.md#train-land).
**Fix.** Read the named slot and files. A stamp hook (`restamp_command`,
`release_tool`) that writes product files is fixed in the hook; an invalid
fragment goes back to its lane. Otherwise rebuild the train without that slot
and report it.
<a id="queue-train-moved"></a>
### `QUEUE_TRAIN_MOVED`
**Meaning.** A PR's head is no longer the SHA the train pinned (at build, or
past its pin beyond stamps at verify), so the train would not test what
merges. At verify after earlier slots merged, the queue also freezes
(`QUEUE_TRAIN_FROZEN`).
(`QUEUE_TRAIN_FROZEN`). At `train land` it is a PR head or branch that moved
off the pin (or off land's own stamp commit); land never overwrites it.
**Fix.** Ask the lane for a fresh READY on the new head, then build the train
with the new pin (`train build --prs <n>@<new sha>,…`).
@@ -4505,6 +4592,10 @@ tested. After a merge, a main that differs from the prefix after the PR in
the CHANGELOG alone, while being its own first parent's CHANGELOG plus the
PR's own entry (the rule the pre-merge verify applied to the head), is not a
mismatch ([one CHANGELOG rule, before and after](ship-policy.md#run-a-merge-train)).
For a stamped train, `train land` compares exact trees: main must be S_(i-1)
before the merge, must not move during the slot's checks, and must be S_i
after it ([stamped trains](ship-policy.md#train-land)); main equal to an
earlier prefix refuses without freezing and names the slot to land first.
**Fix.** Stop merging. If a PR of the train merged (verified, or reported
MERGED by GitHub without a verified tree, slot 1's own squash included), the