diff --git a/lib/ship-receipt-depth.ts b/lib/ship-receipt-depth.ts index bbab03d75..7cd524523 100644 --- a/lib/ship-receipt-depth.ts +++ b/lib/ship-receipt-depth.ts @@ -295,7 +295,7 @@ export function bindShippedReceipt(root: string, baseRef: string, body: string, */ export function targetedClassesSince(root: string, from: string, head: string): readonly TargetedClass[] | null { if (!git(root, ['merge-base', '--is-ancestor', from, head]).ok) return null; - const at = headInputs(root, from, head); + const at = headInputs(root, from, head, undefined, true); let gateTagged: number | null = 0; if (gateScanNeeded(at.c.paths, at.c.root)) { const g = runScriptSync(path.join(BIN_DIR, 'gstack-gate-diff'), [from], { cwd: root, encoding: 'utf8', timeout: 120_000, maxBuffer: 16 * 1024 * 1024 }); diff --git a/test/review-exclude.test.ts b/test/review-exclude.test.ts index 4c8465b2b..aefa18c08 100644 --- a/test/review-exclude.test.ts +++ b/test/review-exclude.test.ts @@ -253,6 +253,7 @@ describe('the receipt binds the selection the reviewers read', () => { const receiptIn = (body: string) => extractReceipt(fs.readFileSync(body, 'utf8')).receipt!; const writeReceipt = (repo: FixtureRepo, body: string, extra: string[]) => runBin(repo, 'gstack-ship-receipt', ['write', '--pr', '7', '--base', 'main', '--version', '1.0.0', '--session-kind', 'unattended', '--body', body, ...extra]); + const targetedAtHead = (repo: FixtureRepo) => ['--review-targeted', JSON.stringify({ classes: ['money', 'gate', 'auth', 'secret'], rules: 'r', brief_sha: 'a'.repeat(64), status: 'done', findings: 0, raised_by: 'targeted', head: repo.git('rev-parse', 'HEAD') })]; // Value: protects=a docs-only range whose only file sits under a base pattern and carries a gate tag binds the same selection at // plan and receipt write, so the receipt can be written; fails_when=receiptReviewAtHead skips the gate scan when the paths are // docs-class and excludes the file the plan kept as gate-tagged (/ship review of #3852, red-team#1: digests 652d0aa8 vs @@ -291,7 +292,7 @@ describe('the receipt binds the selection the reviewers read', () => { expect(missing.err).toContain(`field=review_exclude expected=${digest} recorded=none source=origin/main@`); expect(missing.err).toContain('gstack-review-dispatch verify --ledger '); expect(fs.existsSync(body)).toBe(false); - expect(writeReceipt(repo, body, ['--review-exclude', digest!]).status).toBe(EXIT.ok); + expect(writeReceipt(repo, body, ['--review-exclude', digest!, ...targetedAtHead(repo)]).status).toBe(EXIT.ok); expect(receiptIn(body).review_exclude).toMatchObject({ digest, excluded: EXCLUDED.length, kept: KEPT.length }); onBase(repo, { 'docs/other.md': '# other\n' }); diff --git a/test/review-targeted.test.ts b/test/review-targeted.test.ts index 940d6dfe6..d624c869c 100644 --- a/test/review-targeted.test.ts +++ b/test/review-targeted.test.ts @@ -376,7 +376,7 @@ describe('the /ship commands carry the targeted record to the receipt (GST-464)' fs.writeFileSync(exclude, '*.log'); expect(spawnSync('bash', ['-c', entry!], { cwd: repo.work, encoding: 'utf8', timeout: 10_000 }).status).toBe(0); expect(fs.readFileSync(exclude, 'utf8').split('\n')).toEqual(expect.arrayContaining(['*.log', '/.gstack/tmp/'])); - fs.writeFileSync(path.join(repo.work, recordPath!), JSON.stringify({ classes: ['money'], rules: TARGETED_RULES, brief_sha: targetedBriefSha(), status: 'done', findings: 0, raised_by: 'targeted' }) + '\n'); + fs.writeFileSync(path.join(repo.work, recordPath!), JSON.stringify({ classes: ['money'], rules: TARGETED_RULES, brief_sha: targetedBriefSha(), status: 'done', findings: 0, raised_by: 'targeted', head: repo.git('rev-parse', 'HEAD') }) + '\n'); const args = expand(); expect(args).toEqual(['--review-targeted', `@${recordPath}`]); expect(repo.git('ls-files', '--others', '--exclude-standard')).toBe('');