mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-02 06:54:50 +01:00
* feat(ship): review loop converges (frozen finding scope, 3-pass cap, NO FINDINGS first-class) and receipts carry across conflict-only merges
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(ship): keep the Step 9 first-entry and Fix-First phrasing the skip/requeue contract asserts; fill the review record's pass, review_scope and range_base placeholders in the caller template test; narrow a test type
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes (pass 1) and the review-loop budget wording
Two blocking findings from /ship's own pass 1, both reproduced in the QA smoke before the fix:
gstack-ship-receipt carry hashed the product diff with git patch-id --stable, which strips
whitespace, so a space moved in a reviewed shell line carried as "no product change"; and it
classed package.json and every lockfile as release metadata, so a dependency swap carried too.
The patch-id is now --verbatim, package.json and VERSION are metadata only while the change is a
version stamp (lib/pregate/stamp-only.ts), lockfiles are product unless the policy names them,
the test globs name code extensions, a dirty working tree refuses before anything is compared,
and a receipt carried twice keeps the reviewed head as carry.from.
gstack-review-scope: an unresolvable base is POLICY_SOURCE_UNAVAILABLE instead of a silent
working-tree read; --range-base is validated; git runs with core.quotePath=false; range_base is
null on pass 1; a blocking finding with no path is fix; category "testing" is a test finding
below the floor; defer searches once, files nothing on a failed search and exits 0 unfiled;
summary refuses zero matching passes and the receipt write is chained to it with &&; a finding
the diff causes elsewhere names the changed line in `cause` and classify follows it.
Review-loop budget wording (ideas from tstack's sol-loop and pstack's interrogate, rewritten;
NOTICE.md): the blocker / should / nit glossary and two stop rules in Step 9.4, the checklist
and docs/review-policy.md; a lead-judgment stage in the finding merge both /review and /ship
render. The core read diffs $RANGE_BASE from pass 2 on. Caps and pins re-measured with reasons.
Step 7 coverage audit: four tests added in existing files, the --review-loop case moved to its
own test (the shared test tripped bun's 5 s default).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pin the renumbered review-army stages and point the wording test at product code
CI at c3e27fc failed three free-tier tests, all this branch's own: the
workflow-required-steps fixture and shared-libs-rendering still pinned the
pre-Lead-judgment stage numbers, and test-of-test-ratchet counted
review-loop-budget-wording.test.ts as test-only code because it read only
generated markdown. The fixture and rendering test now pin stages 1-8, and the
wording test renders both armies through generateReviewArmy and reads the
authored ship template as well as the generated sections.
Pass-2 maintainability notes folded in: docs/troubleshooting.md names the two
new RECEIPT_CARRY_REFUSED causes (dirty tree, unreadable status) and the two new
REVIEW_SCOPE_INPUT causes (non-hex --range-base, summary with no matching
pass); gstack-review-scope's help states that classify reads the base branch's
policy and refuses an unresolvable base unless --policy is passed.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: date a review-log row from its binding start when it has no timestamp
The Step 9.4 template asks the writer to fill `timestamp`; the logger stamps
`review_binding.started_at` but never `timestamp`. `gstack-review-scope
summary` dropped such a row silently, so this PR's own pass 2 was missing from
REVIEW_LOOP (passes=1 for a two-pass loop). A row now dates from `timestamp`,
else from its binding start; a row with neither is still not counted.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pass-4 review fixes (carry stamp rule, deleted-file locus, scanned defer, diff prefixes, row time)
Five findings at the blocking floor from the Step 11 outside voice (gpt-6.1-sol)
and the native adversarial pass, fixed as a fourth pass approved by GStack
Minder under the three-pass policy:
- carry: a path the policy names under stamp_files, stamp_paths or
release_outputs is metadata only while its change is a stamp (versionStampOnly
for a manifest kind, stampOnlyText otherwise), the rule the tree receipt
applies; a logic change in a stamped source file is product. Every
metadata-listed path is read at both heads and a non-stamp change drops its
globs from the product exclusion.
- review-scope: parseHunks keeps a deleted file as a whole changed location, so
a cause anchored in a removed export is the diff's to fix, not deferred.
- defer: issues are filed through lib/gstack-post's publication scan; a HIGH
finding in the title or body refuses and a MEDIUM one needs a human, both
recorded as unfiled with the reason; --repo names the destination.
- classify: both diffs pin --src-prefix=a/ --dst-prefix=b/ --no-textconv, so
diff.mnemonicPrefix cannot send every finding outside.
- summary: a row dates from review_binding.started_at before the writer-typed
timestamp, so a date-only value cannot drop a real pass.
Docs: ship-policy.md#receipt-carry, review-policy.md, the Step 9.4 template
and the CHANGELOG gate-integrity note record the rule and the pass.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(review-army): assert the Red Team branch the run took in the N+1 case
Under the blocking floor the Performance specialist rates a 12-line N+1
INFORMATIONAL, so the conditional Red Team does not activate; the case
encoded the old severity (N+1 is CRITICAL, a Red Team always follows). It
now asserts the branch the run took: a Red Team that ran must follow the
Performance specialist; no Red Team is correct only when the report's own
count says 0 critical and its Red Team line names that reason. The N+1
detection assertion is unchanged. The free contract test gains the
informational-not-activated and informational-claimed controls and the
budget test's synthetic report carries the count line.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.256.0 stamp
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
233 lines
17 KiB
TypeScript
233 lines
17 KiB
TypeScript
/**
|
||
* 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 <skill>) 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 <list without it>); 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 <n>@<new sha>,...)',
|
||
},
|
||
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/<nonce> 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 <list without it>), 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 <nonce>: it finds the run by nonce, or marks the dispatch absent so train build --train <nonce> 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 <nonce> --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 <nonce> --spend-cap <usd>); 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 <nonce>), 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 <nonce> --spend-cap <usd>); 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/<nonce>, or a red case is not in the gate’s plan, so the bisect inputs are refused',
|
||
fix: 'check that train/<nonce> 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 <n>@<sha>); 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 <n> --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/<n>/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 <n> --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: <reason>` 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 <owner> <YYYY-MM-DD> via <plan sha|comment or review URL> for <class>@<digest12>` 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 <dir>) or maintainer comment names the APPROVAL_SCOPE token or the class and every effect path; for evidence gstack cannot read, attest it with `for <class>@<digest12>` on the line. Only the owner approves: never write the line without their approval',
|
||
},
|
||
APPROVAL_SOURCE_UNAVAILABLE: {
|
||
anchor: 'approval-source-unavailable',
|
||
summary: 'origin/<base> is not a resolvable commit, so the committed .gstack/approval-classes.json cannot be read and nothing was checked',
|
||
fix: 'git fetch origin <base> (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 <file> or stdin), --pass <n> from the invocation record, and from pass 2 on --range-base <sha> (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/<base>, 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<string, ResultCode>;
|