From 64a9db8f6371392ee36ea823914beed95f7782c1 Mon Sep 17 00:00:00 2001 From: garrytan Date: Thu, 29 Oct 2026 05:03:29 +0000 Subject: [PATCH] feat(ship-queue): train land checks the depth-checked receipt (#3567); regenerate the doctor anchors Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-doctor-components.sh | 1 + lib/ship-queue-train-land.ts | 6 +++--- test/ship-queue-train-land.test.ts | 12 +++++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bin/gstack-doctor-components.sh b/bin/gstack-doctor-components.sh index d02a688e8..09a121523 100644 --- a/bin/gstack-doctor-components.sh +++ b/bin/gstack-doctor-components.sh @@ -281,6 +281,7 @@ gstack_result_anchor() { QUEUE_TRAIN_BISECT_CAPPED) printf '%s' 'queue-train-bisect-capped' ;; QUEUE_TRAIN_PREFIX_INVALID) printf '%s' 'queue-train-prefix-invalid' ;; QUEUE_TRAIN_REBUILD_REFUSED) printf '%s' 'queue-train-rebuild-refused' ;; + QUEUE_TRAIN_STAMP_MISMATCH) printf '%s' 'queue-train-stamp-mismatch' ;; QUEUE_TRAIN_TREE_MISMATCH) printf '%s' 'queue-train-tree-mismatch' ;; QUEUE_TRAIN_STALE) printf '%s' 'queue-train-stale' ;; QUEUE_TRAIN_FROZEN) printf '%s' 'queue-train-frozen' ;; diff --git a/lib/ship-queue-train-land.ts b/lib/ship-queue-train-land.ts index 46bf53677..907f38d7b 100644 --- a/lib/ship-queue-train-land.ts +++ b/lib/ship-queue-train-land.ts @@ -13,7 +13,7 @@ import * as path from 'node:path'; import { ciAtHead, type QueueContext } from './ship-queue'; import { git, writeJsonAtomic } from './ship-queue-gate'; import { waitChecks } from './ship-queue-land'; -import { bindReceipt } from './ship-receipt'; +import { bindShippedReceipt } from './ship-receipt-depth'; import { checkRun, prInfo } from './ship-queue-train-github'; import { activeFreeze, attemptsOf, trainFile } from './ship-queue-train-record'; import { fetchBase, fetchTrainBranch, findTrain, frozenLines, nextFor, outFailed, outOk, outPending, outRefused, trainEnv, type TrainOut } from './ship-queue-train'; @@ -59,8 +59,8 @@ export async function trainLand(ctx: QueueContext, o: { train: string; pr: strin if (info.isCrossRepository) return outRefused({ code: 'QUEUE_TRAIN_UNTRUSTED', message: `#${o.pr} is a fork head; train land pushes only crew branches` }, lines, 'none', run); if (!j || j.base !== main.sha) { if (info.headRefOid !== pin && info.headRefOid !== j?.stamp_commit) return stopped(env, t, 'QUEUE_TRAIN_MOVED', `#${o.pr}'s head is ${info.headRefOid.slice(0, 12)}, not its pin ${pin.slice(0, 12)}`, main.sha, lines, true); - const shipped = bindReceipt(info.body, o.pr, [pin]); - lines.push(shipped.line); + const shipped = bindShippedReceipt(root, `refs/remotes/${env.remote}/${ctx.base}`, info.body, o.pr, [pin]); + lines.push(shipped.line, ...(shipped.depthLine ? [shipped.depthLine] : [])); if (!shipped.ok) return outRefused({ code: shipped.code, message: shipped.message }, lines, `#${o.pr}'s lane runs /ship at its pin and READYs again`, run); const commit = git(root, ['commit-tree', s.tree, '-p', pin, '-p', main.sha, '-m', `v${s.version} stamp: train ${t.nonce} S${s.index} (#${o.pr})`]).out; const label = ctx.gh(['pr', 'edit', o.pr, '--add-label', 'evals-no-debounce']); diff --git a/test/ship-queue-train-land.test.ts b/test/ship-queue-train-land.test.ts index 683a751c4..896eb1f68 100644 --- a/test/ship-queue-train-land.test.ts +++ b/test/ship-queue-train-land.test.ts @@ -10,7 +10,7 @@ import * as path from 'node:path'; import { EXIT } from '../lib/headless-artifacts'; import { cleanup } from './helpers/restamp-fixture'; import { - GREEN_GATE, TRAIN_POLICY, line, nonceOf, openPr, queue, readyReceipt, trainFixture, trainJson, type TrainFixture, + GREEN_GATE, TRAIN_POLICY, line, nonceOf, openPr, queue, readyReceipt, shippedBody, trainFixture, trainJson, type TrainFixture, } from './helpers/ship-queue-train-fixture'; const POLICY = { ...TRAIN_POLICY, release_tool: null, release_outputs: [], changelog_fragments: { dir: 'changelog.d' }, min_gstack_version: '1.92.0.0' }; @@ -118,6 +118,16 @@ describe('stamped trains: build and land', () => { expect(line(drift.out, 'QUEUE_TRAIN_FROZEN:')).toMatch(/^QUEUE_TRAIN_FROZEN: train \S+ merged #1/); }, 300_000); + test('the receipt check is the depth-checked one train build uses: a receipt whose writer records depth but which records none refuses, nothing pushed', () => { + const { fx, h1, nonce } = stampedTrain(); + expect(queue(fx, ['train', 'verdict', '--train', nonce]).status).toBe(EXIT.ok); + fx.write(d => { (d.prs['1'] as any).body = shippedBody('1', h1, { gstack_version: '1.91.307.0' }); }); + const r = land(fx, nonce, '1'); + expect(r.err).toContain('(QUEUE_RECEIPT_DEPTH_RAISED)'); + expect(r.out).toMatch(/^REVIEW_DEPTH: receipt=none required=/m); + expect(fx.repo.git('--git-dir', fx.repo.origin, 'rev-parse', 'refs/heads/pr-1')).toBe(h1); + }, 180_000); + test('a plain train refuses train land and keeps the slot-stamp path', () => { const fx = trainFixture(TRAIN_POLICY, 'gstack-train-land-'); fixtures.push(fx); wrap(fx); const h1 = openPr(fx, '1');