docs(ship-policy, troubleshooting, harness): train land refusals, resume and required checks; stamped bisect; three-way manifest merges

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-29 09:43:12 +00:00
1 parent d414d9db24
commit fa62ea3555
3 files changed
+48 -18

No files matched your search

+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) |
+17 -8
View File
@@ -392,12 +392,18 @@ Slots merge with `train land --train <nonce> --pr <n>`, never a slot call:
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).
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`).
(`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
@@ -548,9 +554,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
@@ -585,7 +592,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
@@ -1200,7 +1208,7 @@ 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`). A repo counter that must be predecessor + 1 (a
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
@@ -1262,7 +1270,8 @@ 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. Under a train policy `gstack-ship-queue
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.
+22 -7
View File
@@ -4104,12 +4104,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`
@@ -4125,8 +4131,10 @@ 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), 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
@@ -4165,7 +4173,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
@@ -4203,7 +4213,7 @@ 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.
@@ -4287,7 +4297,8 @@ and report it.
**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>,…`).
@@ -4440,6 +4451,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