diff --git a/lib/pregate/context.ts b/lib/pregate/context.ts index 754f535b5..8e4bb5209 100644 --- a/lib/pregate/context.ts +++ b/lib/pregate/context.ts @@ -220,7 +220,7 @@ export function buildContext(o: BuildContextOptions): ContextLoad { const user = rejected ? null : loadConfigFromUser(o.userConfigPath, o.knownChecks); if (user) configLoad = user; else explain.push(`user policy: ${o.userConfigPath} ${rejected ? `ignored (${rejected})` : '(absent)'}`); - if (!user && !rejected && o.policyFrom === 'base') absentUserPolicy = o.userConfigPath; + if (!user && !rejected) absentUserPolicy = o.userConfigPath; } if (isConfigUnavailable(configLoad)) return { refused: { code: 'POLICY_SOURCE_UNAVAILABLE', message: configLoad.detail } }; if (o.policyFrom === 'head' && !configLoad.errors.length) { @@ -246,7 +246,7 @@ export function buildContext(o: BuildContextOptions): ContextLoad { return { ctx: { repoRoot: o.repoRoot, base, baseRef, baseSha: baseSha.out, mergeBase, head, wtree, touched: touchedFiles(o.repoRoot, mergeBase), - config, configLabel: configLoad.label, configKind: configLoad.kind, scratch, configFile: configLoad.kind === 'user' && o.userConfigPath ? o.userConfigPath : PREGATE_CONFIG_PATH, ...(configLoad.kind === 'user' && configLoad.digest ? { configDigest: configLoad.digest } : {}), ...(absentUserPolicy ? { absentUserPolicy } : {}), registry: registryLoad.registry as Registry, registryLabel: registryLoad.source.label, + config, configLabel: configLoad.label, configKind: configLoad.kind, scratch, configFile: configLoad.kind === 'user' && o.userConfigPath ? o.userConfigPath : PREGATE_CONFIG_PATH, ...(configLoad.digest ? { configDigest: configLoad.digest } : {}), ...(absentUserPolicy ? { absentUserPolicy } : {}), registry: registryLoad.registry as Registry, registryLabel: registryLoad.source.label, lanes, platform, allowRepoCommands: o.allowRepoCommands, timeoutMs: o.timeoutMs ?? (config.timeout_s ?? 600) * 1000, timeoutSource: o.timeoutMs !== undefined ? '--timeout' : config.timeout_s ? `timeout_s from ${configLoad.label}` : 'the 600 s default', stallTimeoutMs: o.stallTimeoutMs ?? (config.stall_timeout_s ?? DEFAULT_STALL_TIMEOUT_S) * 1000, progress: o.progress ?? (line => process.stderr.write(`${line}\n`)), diff --git a/lib/pregate/index.ts b/lib/pregate/index.ts index 925c88f84..4a579e57e 100644 --- a/lib/pregate/index.ts +++ b/lib/pregate/index.ts @@ -182,7 +182,7 @@ export function verifyPregate(repoRoot: string, file: PregateFile, treeNow: Wtre * environment: the policy file and its digest, or under the defaults the path that was looked for and absent. */ function userPolicyChange(file: PregateFile): string | null { - const recorded = file.policy?.startsWith('user ') ? file.policy.slice('user '.length) : null; + const recorded = file.policy?.startsWith('user ') ? file.policy.slice('user '.length) : file.policy?.startsWith('head ') ? / over user (.+)$/.exec(file.policy)?.[1] ?? null : null; if (recorded) { if (!file.policy_digest) return `pregate.json names the user-owned policy ${recorded} without its digest; rerun gstack-pregate`; let text: string; diff --git a/test/pregate-head-policy.test.ts b/test/pregate-head-policy.test.ts index fee996f98..b8ead3d72 100644 --- a/test/pregate-head-policy.test.ts +++ b/test/pregate-head-policy.test.ts @@ -99,6 +99,12 @@ describe('gstack-pregate --policy-from head: additive only (GST-419)', () => { expect(dropped.err).toContain('guards'); commit(repo, { '.gstack/pregate.json': JSON.stringify({ ...user, dependencies: NEW_LANE }) }); expect(pregate(repo, ['--policy-from', 'head']).status).toBe(EXIT.ok); + const verify = () => runBin(repo, 'gstack-pregate', ['verify'], { GSTACK_PROJECT_SLUG: SLUG }); + expect(verify().status).toBe(EXIT.ok); + fs.writeFileSync(userPolicy, JSON.stringify({ ...user, guards: [] })); + const stale = verify(); + expect(stale.status).not.toBe(EXIT.ok); + expect(stale.err + stale.out).toContain('changed after pregate.json was recorded'); }, 60_000); // Value: protects=a PR that introduces the first policy still bootstraps under head; fails_when=the bootstrap path is refused; why_new=rule scope; seam=none; no_claim=none diff --git a/test/pregate-remote.test.ts b/test/pregate-remote.test.ts index 4a88a40d3..2fe9b2c2e 100644 --- a/test/pregate-remote.test.ts +++ b/test/pregate-remote.test.ts @@ -172,7 +172,7 @@ describe('gstack-pregate --remote (C-c)', () => { const changed = remote(repo, ['--remote', '--allow-repo-commands', '--policy-from', 'head', '--spend-cap', '2']); expect(changed.status).toBe(EXIT.refused); expect(changed.err).toContain('remote_gate differs from the trusted policy'); - }); + }, 60_000); test('a wrong tree, zero tests, a failure, a missing line or a non-zero exit leaves the obligation uncleared and the check incomplete; unreported spend counts at the cap', () => { const repo = gated();