From fa62ea355565f5e224659df9f06dcb1ef1b10358 Mon Sep 17 00:00:00 2001 From: garrytan Date: Thu, 29 Oct 2026 09:43:12 +0000 Subject: [PATCH] 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> --- docs/recommended-harness.md | 12 +++++++++--- docs/ship-policy.md | 25 +++++++++++++++++-------- docs/troubleshooting.md | 29 ++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/docs/recommended-harness.md b/docs/recommended-harness.md index 687ccb0fe..4b74b85d3 100644 --- a/docs/recommended-harness.md +++ b/docs/recommended-harness.md @@ -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: `, 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 --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) | diff --git a/docs/ship-policy.md b/docs/ship-policy.md index dc40e0d53..ec51c5feb 100644 --- a/docs/ship-policy.md +++ b/docs/ship-policy.md @@ -392,12 +392,18 @@ Slots merge with `train land --train --pr `, 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=:`; a foreign push is never overwritten - (`QUEUE_TRAIN_MOVED`). + (`QUEUE_TRAIN_MOVED`). The stamp commit is deterministic and journaled + (`land-.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 stopped at # (conflicts: ) after merging #,#; 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 ``` 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/` (`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/ 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:"`), 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. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f936db6cb..f6d1b51fa 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -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 `. 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. ### `QUEUE_PROOF_MISSING` @@ -4125,8 +4131,10 @@ train still building, running or merging, `train watch` on an unknown record, unreadable run), `verify --pr --train ` 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 ` 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 `; 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 @,…`). @@ -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