From e139fee6b729c6ee93541a071bb52dcdcdeac3dd Mon Sep 17 00:00:00 2001 From: garrytan Date: Tue, 27 Oct 2026 16:32:52 +0000 Subject: [PATCH] Docs audit: troubleshooting codes and ship-policy sections match the shipped train rules (GSTA-129) The /document-release audit (edit mode) corrected two docs the PR had not reached: docs/troubleshooting.md's QUEUE_TRAIN_TREE_MISMATCH, QUEUE_PROOF_MISSING, QUEUE_TRAIN_FROZEN and POLICY_SOURCE_UNAVAILABLE entries, and docs/ship-policy.md's train build conflict rule (the carried stacked child is the exception), the verify freeze paragraph (slot-1 GitHub-MERGED freeze, unreadable-state refusal) and the restamp stamp_paths stage (base-added lines stay; an unrunnable base diff refuses). Five mid-sentence colons in the PR's own paragraphs are rewritten for the prose lint, and the CHANGELOG Docs bullet names docs/troubleshooting.md. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- docs/captain.md | 2 +- docs/ship-policy.md | 27 ++++++++++++++++++--------- docs/troubleshooting.md | 29 +++++++++++++++++++++++------ 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9054cec8a..9241d1d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - `test/ship-queue-train.test.ts`: a stacked child's merged-in head the pre-merge verify accepted confirms after its squash merge (froze before), with the correction bullet accepted pre-merge; a post-merge CHANGELOG holding a line the pin never wrote still freezes, and the freeze lists the slot GitHub reports MERGED under `merged`; a slot-1 squash GitHub reports MERGED that fails the post-merge check freezes, and an unreadable GitHub state refuses; a stacked child behind another slot is gated on the carried CHANGELOG (the released parent's text appears once, under its own heading, with the child's corrections in the child's entry). `test/restamp-base-merge.test.ts` (new): train 24's shape (pin forked before the base's key, base merge staged), for a key naming an older release and for one at the base version; the dry run lists the four stamp files plus the branch's own key file only, committed or staged, and the receipt bound is asserted for both keys. `test/changelog-merge-damage.test.ts`: the real a78ce08a/3e4240a7/d87eec95 CHANGELOGs (`test/fixtures/merge-train-3453/`) merge to main's sections unchanged plus #3453's entry with the two corrections; without the base they stay null; shape controls for an unedited, a dropped, an added and an empty child. ### Docs -- docs/ship-policy.md § Run a merge train: the one CHANGELOG rule, the stamp-files rule (never a fifth), the stacked-child-after-squash resolution, and that an unstamped head changes a release output by regenerating it at its own VERSION (the Version Gate has accepted that since 1.91.237.0; #3434 reverted its digest change against the older gate). docs/captain.md § Merge trains and captain/templates/slot-call.md carry the stamp-files rule (the files the slot call names: `STAMP_FILES`, plus the file carrying a `*_KEY_VERSION` the PR introduced when the call names it; a file the restamp lists that the call did not name is left out and reported) and the stacked-child rule. +- docs/ship-policy.md § Run a merge train: the one CHANGELOG rule, the stamp-files rule (never a fifth), the stacked-child-after-squash resolution, and that an unstamped head changes a release output by regenerating it at its own VERSION (the Version Gate has accepted that since 1.91.237.0; #3434 reverted its digest change against the older gate). docs/troubleshooting.md: `QUEUE_TRAIN_TREE_MISMATCH`, `QUEUE_PROOF_MISSING`, `QUEUE_TRAIN_FROZEN` and `POLICY_SOURCE_UNAVAILABLE` describe the post-merge CHANGELOG exception, the unreadable-state refusal, the GitHub-MERGED freeze record and the restamp base-diff refusal. docs/captain.md § Merge trains and captain/templates/slot-call.md carry the stamp-files rule (the files the slot call names: `STAMP_FILES`, plus the file carrying a `*_KEY_VERSION` the PR introduced when the call names it; a file the restamp lists that the call did not name is left out and reported) and the stacked-child rule. ### Gate integrity - Correction to the 1.91.276.0 entry below: "copies of shared sections twice until a follow-up dedupes them." should read "versions of shared sections beside the shared file (office-hours' copies are deduped in the entry above).". diff --git a/docs/captain.md b/docs/captain.md index aa6e616e0..1ba7788af 100644 --- a/docs/captain.md +++ b/docs/captain.md @@ -215,7 +215,7 @@ fifth: a file the restamp lists that the call did not name is not a stamp, so th leaves it out and says so in its READY. The restamp itself never moves a line the base added since the pin's fork point, base merge committed or staged. A pin stacked on a PR that squash-merged earlier conflicts beyond the CHANGELOG where the -parent's squash touched the pin's lines: the lane takes its pin's bytes for those files +parent's squash touched the pin's lines, so the lane takes its pin's bytes for those files (`verify` checks the tree against T, which already carries the parent), and `mergeChangelogs` carries the pin's edits to the parent's now-released entry as Gate integrity corrections in the pin's own entry. The post-merge `verify` judges the CHANGELOG diff --git a/docs/ship-policy.md b/docs/ship-policy.md index d9694b05d..8b1750528 100644 --- a/docs/ship-policy.md +++ b/docs/ship-policy.md @@ -486,7 +486,9 @@ 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 carries a released entry's text in a section of its own) is a CHANGELOG -conflict, never merged with the damage. READY receipts and train records are +conflict, never merged with the damage, except the stacked child whose parent +was released since their merge base, which `mergeChangelogs` carries (see *A +stacked child whose parent merged earlier*, below). READY receipts and train records are keyed on origin's GitHub owner/repo, so lanes whose origin uses another URL form (git.capy.ai, ssh, a token URL) share the coordinator's queue. @@ -579,8 +581,8 @@ GSTA-67 shape, a released heading dropped by hand, and stays a hand merge. **A generator change on an unstamped head.** A release output such as `agents-digest/gstack-AGENTS.md` is stamped at the slot, and an unstamped PR -changes it the ordinary way: edit the generator, regenerate at the branch's -own `VERSION` (its merge base's), and commit both. CI's Version Gate +changes it the ordinary way, which is to edit the generator, regenerate at the +branch's own `VERSION` (its merge base's), and commit both. CI's Version Gate regenerates the output at that VERSION and reads a byte-identical result as unstamped ([what /ship reads](#ship), since 1.91.237.0); the freshness test compares against the checkout's VERSION, so it passes before and after the @@ -604,18 +606,25 @@ writes for that pin against main, above it); after it, main's tree must equal the prefix after it modulo stamps, or differ from it in the CHANGELOG alone while being its own first parent's CHANGELOG (the main the pre-merge verify saw, itself the prefix before modulo stamps) plus the PR's own entry under the -same rule. One CHANGELOG rule, before and after: a head the pre-merge verify -accepted confirms after its squash merge with the same CHANGELOG, and a +same rule. One CHANGELOG rule applies before and after, so a head the pre-merge +verify accepted confirms after its squash merge with the same CHANGELOG, and a stacked child whose one `[Unreleased]` became two released sections is not a mismatch (train 24, 2026-10-27, froze on exactly that). Anything else is `QUEUE_TRAIN_TREE_MISMATCH` (or `QUEUE_TRAIN_MOVED`). When nothing of the train -has merged, that is a plain refusal: build a new train on current main. When +has merged, that is a plain refusal, and a new train is built on current main. When earlier slots already merged, main holds a prefix no gate covered on its own, -so verify freezes the queue: the freeze goes to local state and the notes ref +so verify freezes the queue. The freeze goes to local state and the notes ref first, then the full gate is dispatched on main's SHA at once (the backstop), and the `QUEUE_TRAIN_FROZEN:` line lists the merged PRs, main's tree and the backstop run. A slot GitHub reports MERGED counts as merged in that record, -verified tree or not, so the rebuild after the repair never seats it again. While frozen, ordinary trains and other trains' merges are refused +verified tree or not, so the rebuild after the repair never seats it again +(the reason says "merged on GitHub without a verified tree"); a first slot +whose squash GitHub reports MERGED but whose tree fails the post-merge check +freezes the same way, since main already holds the unverified squash. When +whether a slot merged on GitHub cannot be read, verify refuses +(`QUEUE_PROOF_MISSING`, "whether # merged on GitHub could not be read") +and asks to be rerun once `gh pr view` answers; an unreadable state is never +"nothing merged". While frozen, ordinary trains and other trains' merges are refused (`QUEUE_TRAIN_FROZEN`). Admit one repair PR at its exact head: ```bash @@ -933,7 +942,7 @@ Stages, each journaled in `.gstack/tmp/restamp-journal.json`: 1. **version**: `gstack-version-bump write --version ` in the staging worktree (VERSION, the manifest, npm lockfiles); skipped when `restamp_command` owns the version. 2. **release_tool**: the policy command, under the contract below. 3. **changelog**: the PR's `[Unreleased]` or old-version section moves to the top under ``, above its predecessor's entry. A duplicate heading the PR introduces (`CHANGELOG_DUPLICATE_HEADING`: the headings are counted against the base's CHANGELOG at `origin/` and at the fork point, so a version the base already heads twice passes as long as the PR adds no further copy) and a top entry that is not `` (`CHANGELOG_TOP_MISMATCH`) are errors; the section being re-headed itself may exist only once. -4. **stamp_paths**: exact `(was X)`, `since: X` and `vX` rewrites, plus every `stamp_patterns` template, within `stamp_paths` and `stamp_files`. A quote around X in the first two forms is part of the form and is kept as written: `since: 'X'`, `since: "X"`, `(was 'X')` and `(was "X")` move like the bare ones (the shape of a quoted source row such as gbrain's `{ since: '0.60.468.0', … }`), an opened quote must close right after X (`since: '1.2.3-beta'` and `since: '1.2.3"` are not the form, in the writer and in the tree receipt alike), and `vX` already accepts a quote before the `v`. Which X moves is decided by provenance. The branch's own tentative stamp (its VERSION when it moved past base) and `--was `, when given, are each rewritten on every line of every stamp file. Beyond that, only lines the branch itself added are read, by their position in the file (`git diff --unified=0` from the fork point), so a base line with the same text, or a CRLF checkout whose diff is LF-normalized, is told apart: a stamp-form mention there of a version newer than the version at the branch's fork point is a tentative stamp and moves (this is what finds a quoted row after a lost slot and a merge of the base, since the base's release is newer than the fork point), and when the branch has no stamp of its own because its VERSION equals the base's and its fork point is the base itself (a branch rebased onto the base after it reached X, or one never stamped) a mention of the base version on a line it added is its stale stamp and moves too, while the same version on a line the base already carries is the base's release and stays. The stage detail names both counts per file (`VERSION equals base X, so X moved only on lines this branch added and stayed on the base's own lines (: N moved, M kept)`), so the stamp commit shows which lines moved and why. The rule reads line ownership, not meaning: a branch-added line inside `stamp_paths` that means the base's own release in a stamp form (`Requires vX or later` written on a branch forked at X) moves with it, which the `--dry-run` diff and the per-file counts show before anything is written; write such a reference outside a stamp form, or keep that file out of `stamp_paths`. `--was X` is the opposite tool: it rewrites X everywhere, the base's own lines included, and re-heads the CHANGELOG section `## [X]`. Only the repo's own version width counts, so a 3-digit dependency pin in a 4-digit repo is never a candidate. +4. **stamp_paths**: exact `(was X)`, `since: X` and `vX` rewrites, plus every `stamp_patterns` template, within `stamp_paths` and `stamp_files`. A quote around X in the first two forms is part of the form and is kept as written: `since: 'X'`, `since: "X"`, `(was 'X')` and `(was "X")` move like the bare ones (the shape of a quoted source row such as gbrain's `{ since: '0.60.468.0', … }`), an opened quote must close right after X (`since: '1.2.3-beta'` and `since: '1.2.3"` are not the form, in the writer and in the tree receipt alike), and `vX` already accepts a quote before the `v`. Which X moves is decided by provenance. The branch's own tentative stamp (its VERSION when it moved past base) and `--was `, when given, are each rewritten on every line of every stamp file. Beyond that, only lines the branch itself added are read, by their position in the file (`git diff --unified=0` from the fork point), so a base line with the same text, or a CRLF checkout whose diff is LF-normalized, is told apart: a stamp-form mention there of a version newer than the version at the branch's fork point is a tentative stamp and moves (this is what finds a quoted row after a lost slot and a merge of the base, since the base's release is newer than the fork point), and when the branch has no stamp of its own because its VERSION equals the base's and its fork point is the base itself (a branch rebased onto the base after it reached X, or one never stamped) a mention of the base version on a line it added is its stale stamp and moves too, while the same version on a line the base already carries is the base's release and stays. The stage detail names both counts per file (`VERSION equals base X, so X moved only on lines this branch added and stayed on the base's own lines (: N moved, M kept)`), so the stamp commit shows which lines moved and why. A line the base added since the fork point is the base's even when that diff shows it (a merge of the base on the branch, committed or still staged, brings every line the base added since the fork into the diff from the fork point, main's own release keys included): it is left out, the detail says `N line(s) the base added since the fork point are the base's, not this branch's, and stay (: N)`, and a base diff git cannot run refuses the stage (`POLICY_SOURCE_UNAVAILABLE`, `cannot diff .. for the stamp_paths stage`) rather than reading as an empty set that would move the base's lines again. The rule reads line ownership, not meaning: a branch-added line inside `stamp_paths` that means the base's own release in a stamp form (`Requires vX or later` written on a branch forked at X) moves with it, which the `--dry-run` diff and the per-file counts show before anything is written; write such a reference outside a stamp form, or keep that file out of `stamp_paths`. `--was X` is the opposite tool: it rewrites X everywhere, the base's own lines included, and re-heads the CHANGELOG section `## [X]`. Only the repo's own version width counts, so a 3-digit dependency pin in a 4-digit repo is never a candidate. 5. **mirror**: tier 2; declared and validated, reported as not run. 6. **apply**: every changed file is written to the branch only after all stages validated, each against the fingerprinted preimage; a file edited meanwhile is `RESTAMP_CONFLICT` and the applied writes roll back (rollback refuses to overwrite a file whose bytes differ from the recorded postimage). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 04255e7bb..6746440df 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2648,7 +2648,11 @@ from the working directory (a bare `bun gstack-pregate`), since a branch could then pick the user policy: run `gstack-pregate` directly or as `bun --no-env-file`. `gstack-ship-queue ready` under a train policy fetches `origin/` first and refuses with this code, quoting git's error, when -that fetch fails. +that fetch fails. `gstack-restamp`'s `stamp_paths` stage refuses with it when +the diff that tells the base's added lines from the branch's own cannot run +(`cannot diff .. for the stamp_paths stage (); nothing written`), since an empty diff would read as "the base added +nothing" and move the base's lines again. **Fix.** `git fetch origin --depth=1`, then rerun. To run against the branch's own file deliberately, pass `--policy-from head --allow-repo-commands`; @@ -4084,7 +4088,10 @@ gate-ahead merge could not be recomputed with `git merge-tree`. The train commands use it too: `train forget` on a record that does not exist or on a train still building, running or merging, `train watch` on an unknown record, `verify --pr --screen` on a run with no verdict to read (an INCOMPLETE or -unreadable run), and `ready --screen` when the scratch merge branch cannot be +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 `committed=yes`. @@ -4322,17 +4329,27 @@ the repair merges (a changed train). **Meaning.** Main's tree is neither the train's prefix before this PR nor the one after it, modulo stamps, so the merge would not publish what the gate -tested. +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)). -**Fix.** Stop merging. If earlier PRs of the train merged, the queue is now -frozen (`QUEUE_TRAIN_FROZEN`); otherwise build a new train on current main. +**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 +queue is now frozen (`QUEUE_TRAIN_FROZEN`); otherwise build a new train on +current main. When whether a slot merged on GitHub cannot be read, verify +refuses with `QUEUE_PROOF_MISSING` instead of either answer; rerun it once +`gh pr view` answers. ### `QUEUE_TRAIN_FROZEN` **Meaning.** A train stopped partway through publication, so main holds a prefix no gate covered on its own. The `QUEUE_TRAIN_FROZEN:` line lists the -merged PRs, main's tree and the backstop run dispatched on main's SHA. The +merged PRs (a slot GitHub reports MERGED counts as merged, verified tree or +not, and the reason then says `merged on GitHub without a verified tree`, so +the repair's rebuild never seats it again), main's tree and the backstop run +dispatched on main's SHA. The freeze lives in local state and on `refs/notes/gstack-trains`. Ordinary trains and other trains' merges are refused until a repair lands.