/** * Result codes for the release path: gstack-ship-queue and the ship-policy * floor, pregate timing and its working-tree fingerprint, regen with missing * dependencies, approval classes and * the state-machine matrix. Kept beside lib/result-codes.ts (which spreads * this table into RESULT_CODES) so * the owner module stays within the module-size ratchet; the same rules apply: * one stable docs/troubleshooting.md anchor, a summary and a fix clause each. */ import type { ResultCode } from './result-codes'; export const RELEASE_RESULT_CODES = { // Field list 2, W1: gstack-ship-queue and the ship-policy compatibility floor. POLICY_GSTACK_TOO_OLD: { anchor: 'policy-gstack-too-old', summary: 'the ship policy sets min_gstack_version above this gstack, so this gstack refuses to read it', fix: 'upgrade gstack (bin/gstack-upgrade, or the session install with --for ) to the version named, then rerun', }, QUEUE_NOT_READY: { anchor: 'queue-not-ready', summary: 'the lane is not ready to report: the working tree, HEAD or CI state does not match the pushed PR head', fix: 'commit and push the work (or wait for CI on that exact head), then rerun gstack-ship-queue ready', }, QUEUE_PROOF_MISSING: { anchor: 'queue-proof-missing', summary: 'a proof input the queue step needs is missing or does not match (gated head, base, stamp journal, version or tree)', fix: 'supply the named input (fetch the gated head, pass the expected sha), or regate; nothing was written', }, QUEUE_STAMP_NOT_STAMP_ONLY: { anchor: 'queue-stamp-not-stamp-only', summary: 'the stamp commit differs from the gated head by more than stamps, so the gate does not cover it', fix: 'inspect the printed residual paths; drop the stray edit, or run the gate again on the stamped head', }, QUEUE_VERIFY_MISMATCH: { anchor: 'queue-verify-mismatch', summary: 'the coordinator could not reproduce the lane’s stamp claim: the PR head, the recomputed gate-ahead merge or the gated tree differs', fix: 'do not merge; ask the lane to rerun gstack-ship-queue stamp (or gate-ahead) on the current queue and report again', }, QUEUE_GATE_MISSING: { anchor: 'queue-gate-missing', summary: 'the policy sets full_e2e and no passing full-gate record is bound to this tree', fix: 'run the gate the policy names (gstack-ship-queue gate-ahead, then --record) or pass the record of a passing run on this head', }, GATE_AHEAD_ATTEMPT_INVALID: { anchor: 'gate-ahead-attempt-invalid', summary: 'the gate-ahead attempt is unknown, already recorded, or its worktree no longer holds the gated commit', fix: 'list attempts with gstack-ship-queue gate-ahead --list, then record the live one, or prepare a new attempt', }, // GSTA-49 lane T1: merge trains in gstack-ship-queue (docs/ship-policy.md#run-a-merge-train). QUEUE_TRAIN_OFF: { anchor: 'queue-train-off', summary: 'the base revision’s .gstack/ship-policy.json declares no `train` block, so the merge-train surface is off and the per-PR queue applies', fix: 'use the per-PR queue (ready, stamp, verify), or land a `train` block with min_gstack_version on the base branch first', }, QUEUE_TRAIN_UNTRUSTED: { anchor: 'queue-train-untrusted', summary: 'a PR’s head repository is not the base repository; fork work never runs in a train (the train branch and its dispatch carry repository secrets)', fix: 'leave the PR out of the train (gstack-ship-queue train build --prs ); fork work stays on the trusted-branch path', }, QUEUE_TRAIN_MOVED: { anchor: 'queue-train-moved', summary: 'a PR’s head (or the train branch) is no longer the SHA the train pinned, so the train would not test what merges', fix: 'ask the lane for a fresh READY on the new head, then rebuild the train with the new pin (train build --prs @,...)', }, QUEUE_TRAIN_CONFLICT: { anchor: 'queue-train-conflict', summary: 'merging a PR onto the previous prefix conflicts outside the version files and the CHANGELOG; nothing was dispatched and no branch pushed', fix: 'run the printed train build without that PR; the PR’s lane merges main (with the earlier PRs once they land) and reports READY again', }, QUEUE_TRAIN_FREE_RED: { anchor: 'queue-train-free-red', summary: 'the free tests dispatched on train/ are red, missing, cancelled or ran on another SHA, so the paid gate was not dispatched', fix: 'read the free-tests run; a PR-caused red drops that PR (train build --prs ), a flaky one goes to its owner before the next build', }, QUEUE_TRAIN_DISPATCH_UNKNOWN: { anchor: 'queue-train-dispatch-unknown', summary: 'a train dispatch was attempted but its run could not be found by nonce, so whether it started is unknown; it is never re-dispatched blindly', fix: 'run gstack-ship-queue train status --train : it finds the run by nonce, or marks the dispatch absent so train build --train may dispatch it', }, QUEUE_TRAIN_WAITING: { anchor: 'queue-train-waiting', summary: 'the train is below its size and its oldest READY PR has waited less than the policy’s fill time, so it does not dispatch yet', fix: 'add READY PRs, rerun once the printed time has passed, or pass --urgent for a one-PR train', }, QUEUE_TRAIN_PENDING: { anchor: 'queue-train-pending', summary: 'the train’s run (free tests, the full gate or the bisect) has not finished, so there is no verdict yet', fix: 'rerun the printed NEXT: command later; nothing is dispatched again while a run is pending', }, QUEUE_TRAIN_INCOMPLETE: { anchor: 'queue-train-incomplete', summary: 'the train gate did not complete its census (INFRA or INCOMPLETE panels, an unbound, partial or cancelled shard, a slice killed before upload, or no published record), so it is never green', fix: 'when every gap is machine-classified INFRA or INCOMPLETE, take the one re-dispatch EVAL_POLICY.infraRedispatch allows (train rebuild --train --infra) and report both runs; otherwise diagnose the run', }, QUEUE_TRAIN_RED: { anchor: 'queue-train-red', summary: 'the train’s full gate is red on the train tree; its PRs do not merge, and nothing is retried to change the verdict', fix: 'bisect the red cases (train bisect --train --spend-cap ); after a rebuild’s second red, route the cases to a repair lane', }, QUEUE_TRAIN_RED_ON_MAIN: { anchor: 'queue-train-red-on-main', summary: 'the bisect found the red cases red on every prefix, main (T0) included: main is broken, not the train’s PRs', fix: 'fix forward on main first (a one-PR train), then rebuild this train unchanged', }, QUEUE_TRAIN_UNATTRIBUTED: { anchor: 'queue-train-unattributed', summary: 'the bisect pattern blames no PR and not main (all green, or not monotone); one trial cannot tell a flake from a break', fix: 'all green: the one preregistered rebuild (train rebuild --train ), reporting both runs; any other pattern: a repair lane, then a changed train', }, QUEUE_TRAIN_BISECT_CAPPED: { anchor: 'queue-train-bisect-capped', summary: 'the bisect’s reserved price (every case×prefix trial, unknown costs at the policy reservation) passes the spend cap; completed prefixes are kept and the outcome stays unattributed', fix: 'resume with a higher cap (train bisect --train --spend-cap ); only prefixes without a result run', }, QUEUE_TRAIN_PREFIX_INVALID: { anchor: 'queue-train-prefix-invalid', summary: 'a bisect prefix is not an ancestor of the train head on train/, or a red case is not in the gate’s plan, so the bisect inputs are refused', fix: 'check that train/ still points at the recorded head (train status); a moved branch needs a new train', }, QUEUE_TRAIN_REBUILD_REFUSED: { anchor: 'queue-train-rebuild-refused', summary: 'the rebuild is not allowed: the bisect was not all green, the train was already rebuilt (or re-dispatched), or there is no bisect or INFRA record', fix: 'route the red cases to a repair lane; the train is rebuilt only once the repair merges (a changed train)', }, QUEUE_TRAIN_TREE_MISMATCH: { anchor: 'queue-train-tree-mismatch', summary: 'main’s tree is neither the train’s prefix before this PR nor after it (modulo stamps), so the merge does not publish what the gate tested', fix: 'stop merging; if earlier PRs of the train merged, the queue is frozen (QUEUE_TRAIN_FROZEN); otherwise rebuild the train on current main', }, QUEUE_TRAIN_FROZEN: { anchor: 'queue-train-frozen', summary: 'a train stopped partway through publication, so main holds a prefix no gate covered on its own; ordinary trains are refused until a repair lands', fix: 'admit one repair PR at its exact head (train build --repair --prs @); the freeze clears when verify finds main’s tree after that merge in a green record', }, // CREW rule 22 (Garry, 2026-10-25): /ship runs before every READY; the queue refuses a PR whose body has no current receipt. QUEUE_RECEIPT_MISSING: { anchor: 'queue-receipt-missing', summary: 'the PR body carries no valid /ship receipt, so the PR was never shipped through gstack and cannot be READY’d, admitted to a train or merged', fix: 'run /ship on the branch (unattended it writes the receipt into an open PR at HEAD with gstack-ship-receipt write --pr --body …, else leaves it in .gstack/tmp/ship-receipt.md for the parent to post), then gstack-ship-queue ready again; there is no flag to skip this', }, QUEUE_RECEIPT_STALE: { anchor: 'queue-receipt-stale', summary: 'the PR body’s /ship receipt is for another head than the one being READY’d or merged (a push after /ship, or a receipt copied from another PR)', fix: 'rerun /ship on the current head so the receipt names it, then gstack-ship-queue ready again; a slot’s stamp commit keeps the receipt written for its pinned head', }, QUEUE_SCREEN_STALE_REF: { anchor: 'queue-screen-stale-ref', summary: 'GitHub’s refs/pull//merge is missing or still merges an older head than the PR’s: the PR conflicts with the base (a CHANGELOG-only conflict counts), so a screen dispatched on it would plan no cases and report success', fix: 'resolve the conflict with the base branch (or wait for GitHub to recompute the merge ref), push, then gstack-ship-queue ready --pr --screen again; a lane never READYs on a screen that planned nothing', }, READY_SCREEN_CHOICE_REQUIRED: { anchor: 'ready-screen-choice-required', summary: 'the base policy turns trains on, so READY needs an explicit screen choice: --screen (pay for a diff-relevant screen now) or --no-screen', fix: 'rerun gstack-ship-queue ready with the printed recommendation (--screen or --no-screen); say in the READY block if focused local cases already ran', }, // Field list 2 W4 (pregate `timing`). PREGATE_TIMING_ASSERTION: { anchor: 'pregate-timing-assertion', summary: 'an added or changed test line compares an elapsed wall-clock or CPU time against a bound, or sleeps and then asserts (a warning, not a block)', fix: 'assert on the outcome, an injected clock or a forced deadline instead; when the bound is the contract, add `gstack-timing-ok: ` in a comment on the line or the line above', }, // Lane D (GSTA-112): the no-policy warning and the environment-cause verdict. PREGATE_NO_POLICY: { anchor: 'pregate-no-policy', summary: 'the repository has no pregate policy yet (no .gstack/pregate.json on the base branch and no user-owned file) and a touched file maps to no test lane; a warn, with a starter policy printed', fix: 'review the printed starter, write it with `gstack-pregate init`, edit the TODO entries, then commit .gstack/pregate.json on the base branch; from then on an unmapped file is PREGATE_NO_LANE', }, PREGATE_ENVIRONMENT: { anchor: 'pregate-environment', summary: 'a check could not run because of the machine, not the diff: a runner or guard binary missing from PATH, a network error or quota refusal before any test ran, a full disk, or an unverified runtime; incomplete, never pass', fix: 'read the `environment:` line (kind and evidence), repair that on this machine or run the lane remotely (`--remote`, `gstack-pregate clear`), then rerun; a waiver cites the kind, never a test name', }, // Field list 2, W2 (approval classes) and W3 (state-machine round-trip matrix). APPROVAL_CLASSES_INVALID: { anchor: 'approval-classes-invalid', summary: '`.gstack/approval-classes.json` is malformed, a class matches nothing, or a path escapes the repository', fix: 'keep `classes` a non-empty list of uniquely named classes, each with `paths`, `patterns` or `migrations: true`, and every glob repo-relative (`gstack-approval-classes validate` checks the working-tree copy)', }, APPROVAL_CLASS_FOUND: { anchor: 'approval-class-found', summary: 'the branch changes something a declared approval class covers', fix: 'get the owner\'s approval and put `Approved by via for @` in the PR body, using the APPROVAL_SCOPE token the check printed', }, APPROVAL_CITATION_MISSING: { anchor: 'approval-citation-missing', summary: 'a declared approval class has no citation that resolves and binds to its current effects, so no receipt was written', fix: 'add an `Approved by` line whose plan (with --approval-run ) or maintainer comment names the APPROVAL_SCOPE token or the class and every effect path; for evidence gstack cannot read, attest it with `for @` on the line. Only the owner approves: never write the line without their approval', }, APPROVAL_SOURCE_UNAVAILABLE: { anchor: 'approval-source-unavailable', summary: 'origin/ is not a resolvable commit, so the committed .gstack/approval-classes.json cannot be read and nothing was checked', fix: 'git fetch origin (classes are never read from the working tree)', }, STATE_MATRIX_INCOMPLETE: { anchor: 'state-matrix-incomplete', summary: 'the state-machines specialist output lacks a kind × owner action cell, maps a cell to a test that does not exist, or leaves an untested cell without a CRITICAL finding', fix: 'rerun the state-machines specialist on the same diff; its review coverage stays incomplete until gstack-state-matrix check passes', }, // Fix wave 2026-10-15, PR 5 (item 10b, #3197). WTREE_UNAVAILABLE: { anchor: 'wtree-unavailable', summary: 'cannot fingerprint the working tree: bin/gstack-wtree could not start (on Windows, no Git Bash was found) or it ran and failed', fix: 'on Windows point GSTACK_GIT_BASH at Git Bash\'s bash.exe (Git Bash: export GSTACK_GIT_BASH=\'C:/Program Files/Git/bin/bash.exe\'; PowerShell: $env:GSTACK_GIT_BASH = \'C:\\Program Files\\Git\\bin\\bash.exe\'), or install Git for Windows (winget install --id Git.Git -e); elsewhere rerun ./setup so bin/gstack-wtree is present; then rerun gstack-pregate', }, // Fix wave W5 (GSTA-31): pregate regen without installed dependencies. REGEN_DEPS_MISSING: { anchor: 'regen-deps-missing', summary: 'a regen command exited 0 with different outputs while package.json declares dependencies and node_modules is missing, so the outputs were not verified', fix: 'install the dependencies (the printed command, such as bun install), then rerun the check', }, // The /ship review loop's finding scope (gstack-review-scope) and the receipt carry (gstack-ship-receipt carry). REVIEW_SCOPE_INPUT: { anchor: 'review-scope-input', summary: 'gstack-review-scope was given input it cannot classify: findings that are not a JSON array of objects, a --pass or --policy outside its values, or a pass 2+ call without --range-base', fix: 'pass the Step 9.3 findings as a JSON array (--findings or stdin), --pass from the invocation record, and from pass 2 on --range-base (HEAD at the start of the previous pass)', }, REVIEW_SCOPE_RANGE: { anchor: 'review-scope-range', summary: 'gstack-review-scope could not read the diff it scopes findings against: the base or the range base does not resolve, or the range base is not an ancestor of HEAD', fix: 'fetch origin/, check --range-base against the saved pass heads, and start a full pass (--pass 1) when history was rewritten', }, RECEIPT_CARRY_REFUSED: { anchor: 'receipt-carry-refused', summary: 'gstack-ship-receipt carry could not carry the /ship receipt to HEAD: the PR body has no receipt, the receipt head is not an ancestor of HEAD (rebase or force-push), or the product diff could not be compared', fix: 'merge the base in instead of rebasing, keep the reviewed head in HEAD\'s history, then rerun carry; otherwise run /ship on the new head', }, RECEIPT_CARRY_PRODUCT_CHANGED: { anchor: 'receipt-carry-product-changed', summary: 'the product diff against the base changed between the receipt\'s head and HEAD (a product file differs beyond a base merge, a test-only fix or release metadata), so the reviewed verdict does not cover HEAD', fix: 'run /ship on the new head; the receipt carries only when every changed path is a test, release metadata or the base merge and the product patch-id is unchanged', }, } as const satisfies Record;