mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 00:44:07 +01:00
fix(ship-receipt): record the installed gstack VERSION, not the newest policy-key version (GST-1366) (#4002)
* fix(ship-receipt): record the installed gstack VERSION, not the newest policy-key version (GST-1366) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): fragment for GST-1366 receipt gstack_version 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>
This commit is contained in:
1 parent
ef6425036e
commit
87d7f0f54e
6 files changed
+42
-9
No files matched your search
@@ -41,7 +41,7 @@ import { branchSides, stampOnlyLine, type BranchSide } from '../lib/stamp-only';
|
||||
import { runScriptSync } from '../lib/script-invocation';
|
||||
import { readPregateFile, verifyPregate } from '../lib/pregate/index';
|
||||
import { exceptionConsumerProblem } from '../lib/pregate/flaky-exception';
|
||||
import { isUnavailable, loadPolicyFromBase, matchesAny, policyUnavailableLine, resolveBaseBranch, runningGstackVersion, stampFilesInTree, validatePolicy, type ShipPolicy } from '../lib/ship-policy';
|
||||
import { installedGstackVersion, isUnavailable, loadPolicyFromBase, matchesAny, policyUnavailableLine, resolveBaseBranch, stampFilesInTree, validatePolicy, type ShipPolicy } from '../lib/ship-policy';
|
||||
import { approvalForReceipt } from '../lib/approval-citations';
|
||||
import {
|
||||
CARRY_METADATA_GLOBS, CARRY_NAMED_PATHS, CARRY_TEST_GLOBS,
|
||||
@@ -305,7 +305,7 @@ function cmdWrite(rest: string[], json: boolean): number {
|
||||
policy: flag(rest, '--policy') ?? tree?.policy, queue_mode: flag(rest, '--queue-mode'), preregistration: flag(rest, '--preregistration'), history: flag(rest, '--history'),
|
||||
...pregate, issues: issuesFile ? receiptIssuesFrom(issuesFile) : undefined, approval: approval.kind === 'ok' ? approval.approval : undefined,
|
||||
review_loop: reviewLoop, ...(docs?.ok ? { docs: docs.docs } : {}), ...product,
|
||||
gstack_version: runningGstackVersion(), review_depth: reviewDepth, review_loop_exception: loopException?.ok ? loopException.exception : undefined, review_targeted: targeted?.ok ? targeted.targeted : undefined,
|
||||
gstack_version: installedGstackVersion(), review_depth: reviewDepth, review_loop_exception: loopException?.ok ? loopException.exception : undefined, review_targeted: targeted?.ok ? targeted.targeted : undefined,
|
||||
base_red: pause ? receiptBaseRed(pause) : undefined,
|
||||
review_exclude: receiptExclude(review.exclude),
|
||||
no_stop: noStopArg(flag(rest, '--no-stop')),
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- **A /ship receipt now names the gstack release that wrote it.** `gstack_version` in the receipt used to record the version gstack reads policies as, which jumps to the newest policy key the checkout knows. On a 1.95.5.0 checkout that carries the unreleased 1.96.0.0 keys, every receipt said 1.96.0.0, a release that never ran. The receipt now records the checkout's VERSION file. Policy reading is unchanged, so a checkout that carries a new key still reads policies written for it. (GST-1366)
|
||||
+3
-2
@@ -1279,8 +1279,9 @@ unreadable base policy is `POLICY_SOURCE_UNAVAILABLE`); one
|
||||
(`absent/<max> over` for a path the tree lacks), exit 1 on any `over`, whose
|
||||
repair is trimming the file, never raising the cap in the same PR.
|
||||
|
||||
**`review_depth` and `gstack_version`.** `write` records the gstack that
|
||||
wrote the receipt and the review depth at the checked-out head
|
||||
**`review_depth` and `gstack_version`.** `write` records the gstack release
|
||||
that wrote the receipt (its `VERSION` file, never the newer policy-key version
|
||||
an unstamped tree reads policies as) and the review depth at the checked-out head
|
||||
([review-policy.md#review-depth](review-policy.md#review-depth)):
|
||||
`{ depth, class, runtime_lines, rules, base, registry, gate_scan: { tagged, head }, reasons, risk, mode }`
|
||||
(`mode` is lean, panel or platform; `declared` and `detected` appear with `write --declare-risk`, and the platform depth adds `platform_review`, see [review-policy.md](review-policy.md#review-depth)),
|
||||
|
||||
@@ -118,7 +118,7 @@ export interface ShipReceipt {
|
||||
carry?: { from: string; reason: string; product_patch_id: string; changed_paths: string[]; merge_commits: number; range_diff: string; covers: string[]; reruns: string[]; trees?: string[]; ancestry?: 'linear' | 'rewritten' };
|
||||
/** The reviewed head's tree and its product patch-id against the base (gstack-ship-receipt write; carry rebinds both): what a carry across rewritten history compares. */
|
||||
head_tree?: string; product_patch_id?: string;
|
||||
/** The gstack that wrote the receipt, and the review depth the reviewed head needs, recomputed at write (lib/ship-receipt-depth.ts receiptDepthAtHead). */
|
||||
/** The gstack release that wrote the receipt (its VERSION file, never the newer policy-key version it reads policies as), and the review depth the reviewed head needs, recomputed at write (lib/ship-receipt-depth.ts receiptDepthAtHead). */
|
||||
gstack_version?: string;
|
||||
review_depth?: { depth: 'platform' | 'light' | 'standard' | 'full'; class: 'docs' | 'tests' | 'code' | 'gate'; runtime_lines: number; rules: string; base: string; registry: string | null; gate_scan: { tagged: number | null; head: string }; reasons: string[]; risk?: string[]; mode?: 'lean' | 'panel' | 'platform'; platform_review?: { id: string; head: string; reviewer: string }; declared?: string[]; detected?: string[] };
|
||||
/** The owner's acceptance of a stop after a fix-check edit under frozen scope (#3559); the PR body repeats it word for word. */
|
||||
|
||||
+8
-3
@@ -231,14 +231,19 @@ export * from './ship-policy-keys';
|
||||
|
||||
const newer = (a: string, b: string): boolean => cmpVersion(parseVersion(a)!, parseVersion(b)!) > 0;
|
||||
|
||||
/** The release this checkout installs: its VERSION file, the version a ship receipt records (GST-1366). */
|
||||
export function installedGstackVersion(): string {
|
||||
try { return fs.readFileSync(path.resolve(import.meta.dir, '..', 'VERSION'), 'utf8').trim(); } catch { return '0.0.0.0'; }
|
||||
}
|
||||
|
||||
/**
|
||||
* The release this checkout reads policies as: its VERSION file, or the
|
||||
* newest key it knows when that is later (an unstamped tree that carries a
|
||||
* new key already reads policies written for it).
|
||||
* new key already reads policies written for it). Never a recorded version:
|
||||
* that is installedGstackVersion().
|
||||
*/
|
||||
export function runningGstackVersion(): string {
|
||||
let own = '0.0.0.0';
|
||||
try { own = fs.readFileSync(path.resolve(import.meta.dir, '..', 'VERSION'), 'utf8').trim(); } catch {}
|
||||
const own = installedGstackVersion();
|
||||
const known = [...Object.values(POLICY_KEY_INTRODUCED), BUMP_NEVER_KEY_VERSION, TRAIN_GATES_KEY_VERSION].reduce((a, b) => (newer(b, a) ? b : a));
|
||||
return parseVersion(own) && !newer(known, own) ? own : known;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
import { afterEach, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { EXIT } from '../lib/headless-artifacts';
|
||||
import { extractReceipt, renderReceiptBlock } from '../lib/ship-receipt';
|
||||
import { bindShippedReceipt, receiptDepthAtHead, receiptDepthVerdict, reviewLoopExceptionLine } from '../lib/ship-receipt-depth';
|
||||
import { cleanup, commit, makeFixtureRepo, runBin, type FixtureRepo } from './helpers/pregate-fixture';
|
||||
import { bunIsolationArgs } from '../lib/bun-isolation';
|
||||
import { runScriptSync } from '../lib/script-invocation';
|
||||
import { BUMP_NEVER_KEY_VERSION, POLICY_KEY_INTRODUCED, TRAIN_GATES_KEY_VERSION } from '../lib/ship-policy';
|
||||
import { cmpVersion, parseVersion } from '../lib/version-source';
|
||||
import { cleanup, commit, makeFixtureRepo, ROOT, runBin, type FixtureRepo } from './helpers/pregate-fixture';
|
||||
|
||||
const repos: FixtureRepo[] = [];
|
||||
afterEach(() => { while (repos.length) cleanup(repos.pop()!); });
|
||||
@@ -54,6 +59,27 @@ describe('write records the review depth at the checked-out head', () => {
|
||||
expect(bad.err).toContain('(RECEIPT_INVALID)');
|
||||
expect(fs.existsSync(other)).toBe(false);
|
||||
}, 60_000);
|
||||
|
||||
// Value: protects=a receipt names the gstack release that wrote it, never a newer policy key its tree carries, so a
|
||||
// version check against a receipt cannot be passed by an older build (GST-1366); fails_when=write records the
|
||||
// key-inflated version policy reading uses, or policy reading stops seeing the newest key; seam=none
|
||||
test('a gstack whose VERSION is older than its newest policy key records that VERSION; policy reading still reads at the key', () => {
|
||||
const repo = makeFixtureRepo(); repos.push(repo);
|
||||
commit(repo, { 'lib/util.js': lines(3) });
|
||||
const installed = path.join(repo.root, 'installed-gstack');
|
||||
const sourceOnly = { recursive: true, filter: (file: string) => path.basename(file) !== 'node_modules' };
|
||||
for (const dir of ['bin', 'lib', 'scripts']) fs.cpSync(path.join(ROOT, dir), path.join(installed, dir), sourceOnly);
|
||||
fs.writeFileSync(path.join(installed, 'VERSION'), '1.0.0.0\n');
|
||||
const body = path.join(repo.root, 'body.md');
|
||||
const write = runScriptSync(path.join(installed, 'bin', 'gstack-ship-receipt'), ['write', '--pr', '7', '--base', 'main', '--version', '1.0.0', '--session-kind', 'unattended', '--body', body],
|
||||
{ cwd: repo.work, encoding: 'utf8', timeout: 180_000, maxBuffer: 64 * 1024 * 1024, env: { ...process.env, ...repo.env } });
|
||||
expect(write.status, write.stderr).toBe(EXIT.ok);
|
||||
expect(receiptIn(body).gstack_version).toBe('1.0.0.0');
|
||||
const newestKey = [...Object.values(POLICY_KEY_INTRODUCED), BUMP_NEVER_KEY_VERSION, TRAIN_GATES_KEY_VERSION].reduce((a, b) => (cmpVersion(parseVersion(b)!, parseVersion(a)!) > 0 ? b : a));
|
||||
const reader = spawnSync(process.execPath, [...bunIsolationArgs(), '-e', `console.log((await import(${JSON.stringify(path.join(installed, 'lib', 'ship-policy.ts'))})).runningGstackVersion())`],
|
||||
{ cwd: repo.work, encoding: 'utf8', timeout: 60_000 });
|
||||
expect(reader.stdout.trim(), reader.stderr).toBe(newestKey);
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
describe('the queue refuses a receipt below the depth its head needs', () => {
|
||||
|
||||
Reference in new issue
Block a user