fix(pregate): bind flaky exceptions to the consuming PR head, quote the grant command, recheck red runs (review fixes)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-11-03 02:05:07 +00:00
1 parent 549613e13e
commit a96e76d3c6
7 files changed
+76 -20

No files matched your search

+3
View File
@@ -40,6 +40,7 @@ import { versionAtRef } from '../lib/restamp';
import { branchSides, 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 { approvalForReceipt } from '../lib/approval-citations';
import {
@@ -473,6 +474,8 @@ function pregateFieldFor(repoRoot: string, file: string, head: string): { ok: tr
if (!read.file) return { ok: false, errors: read.errors.map(e => ({ code: e.code, message: `${e.message} at ${e.path}` })) };
const v = verifyPregate(repoRoot, read.file);
if (!v.ok) return { ok: false, errors: [{ code: v.code!, message: v.message ?? 'pregate is not current' }] };
const foreign = exceptionConsumerProblem(read.file, head);
if (foreign) return { ok: false, errors: [{ code: 'PREGATE_REMOTE_UNCLEARED', message: foreign }] };
const remote = read.file.requires_remote ?? [];
const exceptions = read.file.exceptions ?? [];
return { ok: true, field: { pregate: `${read.file.verdict ?? 'pass'} @${read.file.tree.slice(0, 12)} (${read.file.checks.length} checks, current for ${head.slice(0, 12)})`, requires_remote: remote.length ? remote.map(r => `${r.lane}: ${r.receipt}`) : undefined, flaky_exception: exceptions.length ? exceptions.map(e => ({ lane: e.lane, runs: e.runs, cases: e.cases, grant: e.grant, by: e.by })) : undefined } };
+1 -1
View File
@@ -6,4 +6,4 @@
- **docs/pregate.md#flaky-exception**, including why `base_red` is not used: it proves a failure by reproducing it on the base, and a timing flake passes there.
### Tests
- `test/pregate-flaky-exception.test.ts`: a red run with one named flake clears once the bound grant exists, and verify, ready and the receipt report it; refusals for an unnamed or extra failure, a case that did not fail, more than three cases, an unattributed failure, a failed non-shard job, a missing result or log, a timed-out shard, a log failure the result omits, a file in the diff or its import closure, a paid or security test, another head, another PR head, a newer run, a moved attempt, and a grant with another scope, outcome or speaker; ready still refuses red CI. These tests failed before the change.
- `test/pregate-flaky-exception.test.ts`: a red run with one named flake clears once the bound grant exists, and verify, ready and the receipt report it; refusals for an unnamed or extra failure, a case that did not fail, more than three cases, an unattributed failure, a failed non-shard job, a missing result or log, a timed-out shard, a log failure the result omits, a file in the diff or its import closure, a paid or security test, another head, another PR head, a newer run, a run from another repository or fork, a stale or unfinished run, a moved attempt (also after the inventory runs), and a grant with another scope, outcome or speaker; ready still refuses red CI, and refuses an exception granted for another PR or head. The printed grant command quotes case names for the shell. These tests failed before the change.
+11 -5
View File
@@ -979,9 +979,11 @@ cases. Record the owner's exact words in that row, then rerun the same clear wit
- **The files are not the PR's business.** A named file in the PR's diff
(`case_in_diff`), or one the import graph reaches from any changed path
(`case_in_closure`, lib/pregate/import-graph.ts with the base branch's
`dependencies`), refuses. A paid test file (`case_paid`) and a test of a
security scanner (`case_security`: cso, secrets, gitleaks, sealed, redact, osv)
are never excepted. Security scans themselves are pregate checks and CI jobs,
`dependencies`), refuses. The closure follows imports only: a test that reads a
changed doc, template or workflow through the file system is not reached, so
required CI stays the check on it. A paid test file (`case_paid`) and a
security test (`case_security`: a file name with cso, secrets, gitleaks, sealed,
redact or redaction, osv, security, sanitize or inject) are never excepted. Security scans themselves are pregate checks and CI jobs,
not test cases, so no exception can reach them.
- **One head, one grant.** The runs must have tested pregate.json's own head and
tree, with no stamp-only substitution (`head_mismatch`); the PR must be open with
@@ -989,8 +991,12 @@ cases. Record the owner's exact words in that row, then rerun the same clear wit
(`newer_run`), and the run may not move to another attempt during the check
(`run_changed`). The grant row must carry this exception's scope
(`grant_mismatch` otherwise), record `approved` (`grant_not_approved`) and be
the owner's words, not an agent's (`grant_not_owner`). A new head needs a new
run and a new grant; a grant is never rebound.
the owner's words, not an agent's (`grant_not_owner`). That last check reads
the row's `by` field, which `gstack-approvals add --by` takes as written, so it
only catches a row honestly labeled `agent:`. The record is as trustworthy as
whoever writes it, the same as every gstack approval. A new head needs a new
run and a new grant; a grant is never rebound: `ready` and the ship receipt
refuse an exception granted for another PR or head, even when the tree matches.
pregate.json records the grant under `exceptions` (lane, repository, PR, head,
tree, runs, cases, grant id, who said it, their words and the binding), and the
+18 -5
View File
@@ -25,9 +25,7 @@ import type { PregateException, PregateFile } from '../headless-artifacts';
import { parseRunRef, verifyRun, type ApiJob, type ApiRun, type RunArtifactDeps, type RunDeps, type RunEvidence, type RunProfile } from '../run-evidence';
import { wtreeFingerprint } from '../script-invocation';
import { resolveBaseBranch } from '../ship-policy';
import { classifyBunTestOutputLine, parseBunFailureResult, stripAnsiLine } from '../../scripts/lib/shard-engine';
import type { FreeCiPlan, FreeCiResult, FreeShardOutcome } from '../../scripts/test-free-shards';
import { isPaidTestFile } from '../../test/helpers/paid-test-set';
import { isConfigUnavailable, loadConfigFromBase } from './config';
import { buildGraph, testsFor } from './import-graph';
@@ -51,12 +49,13 @@ const SHARD_LAYOUTS: Record<string, ShardLayout> = {
'windows-free-tests.yml': { plan: 'windows-plan', result: 'windows-result-', logs: 'windows-free-test-shard-logs-', shardJob: 'windows-free-shard', aggregate: 'windows-free-tests' },
};
/** A test of a security scanner (/cso, secrets, gitleaks, the sealed guard, redaction, OSV): never excepted. */
const SECURITY_TEST = /(?:^|[-_.])(?:cso|secrets?|gitleaks|sealed|redact|osv)(?=[-_.])/;
const SECURITY_TEST = /(?:^|[-_.])(?:cso|secrets?|gitleaks|sealed|redact\w*|osv|security|sanitize\w*|injection|inject)(?=[-_.])/;
const SHA = /^[0-9a-f]{40}$/;
const LOG_FILE_HEADER = /^(\S.*\.test\.(?:[cm]?[jt]s|tsx|jsx)):$/;
const sha256 = (text: string) => createHash('sha256').update(text).digest('hex');
const short = (s: string | undefined) => (s ?? '').slice(0, 12);
const shellQuote = (s: string) => `'${s.replace(/'/g, `'\\''`)}'`;
/** The gstack-approvals scope that grants one exception: a digest of every field it binds, cases in sorted order. */
export function exceptionBinding(e: ExceptionSubject): string {
@@ -111,6 +110,18 @@ export function exceptionProblem(file: PregateFile): string | null {
return orphan ? `pregate.json records a flaky exception for ${orphan.lane} (grant ${orphan.grant}) that no receipt uses; clear again` : null;
}
/** Why `file`'s exceptions cannot stand for this PR head: each binds the PR and head it was granted for, and a same-tree commit or another PR reusing the record never inherits it. */
export function exceptionConsumerProblem(file: PregateFile, head: string, pr?: string): string | null {
const e = (file.exceptions ?? []).find(x => x.head !== head || (pr !== undefined && String(x.pr) !== pr));
return e ? `flaky exception grant ${e.grant} is bound to PR #${e.pr} at ${short(e.head)}, not ${pr ? `PR #${pr} at ` : ''}${short(head)}; a new head needs a new run and a new grant` : null;
}
/** Why red run `id` no longer stands as completed, failed attempt `attempt`; read again once the whole clear is assembled. */
export function redRunChanged(deps: RunDeps, id: number, attempt: number): string | null {
const again = deps.run(id);
return again.run_attempt === attempt && again.status === 'completed' && again.conclusion === 'failure' ? null : `run ${id} moved to attempt ${again.run_attempt} (${again.status === 'completed' ? again.conclusion : again.status}) while it was being verified`;
}
/** verify's ok-line suffix naming each exception. */
export function exceptionSummary(file: PregateFile): string {
return (file.exceptions ?? []).map(e => `flaky exception ${e.cases.join(', ')} (grant ${e.grant}, by ${e.by}, run ${e.runs.join(',')})`).join('; ');
@@ -303,6 +314,7 @@ function logProblem(art: FailureDeps, id: number, name: string, n: number, plann
const stripped = printed.replace(/\\/g, '/').replace(/^(?:\.{1,2}\/)+/, '');
return planned.includes(stripped) ? stripped : planned.find(p => stripped.endsWith(`/${p}`) || p.endsWith(`/${stripped}`)) ?? stripped;
};
const { classifyBunTestOutputLine, parseBunFailureResult, stripAnsiLine } = require('../../scripts/lib/shard-engine') as typeof import('../../scripts/lib/shard-engine');
for (const [, text] of logs) {
let file: string | null = null;
let recap = false;
@@ -358,6 +370,7 @@ export function flakyDecision(file: PregateFile, lane: string, check: { repo: st
const notFailed = input.cases.find(c => !failures.includes(c));
if (notFailed) return invalid(lane, 'case_not_failed', `${notFailed} did not fail in ${label}; its failures are ${failures.join(', ')}`);
const files = [...new Set(input.cases.map(c => c.slice(0, c.indexOf('::'))))];
const { isPaidTestFile } = require('../../test/helpers/paid-test-set') as typeof import('../../test/helpers/paid-test-set');
const paid = files.find(f => isPaidTestFile(f));
if (paid) return invalid(lane, 'case_paid', `${paid} is a paid test file; the paid gate covers it, never an exception`);
const security = files.find(f => SECURITY_TEST.test(path.posix.basename(f)));
@@ -377,9 +390,9 @@ export function flakyDecision(file: PregateFile, lane: string, check: { repo: st
const subject: ExceptionSubject = { lane, repo: check.repo, pr: input.pr!, head: file.head!, tree: file.tree, runs: runs.evidence.map(e => `${e.run_id}/${e.run_attempt}`), cases: [...input.cases].sort() };
const binding = exceptionBinding(subject);
if (!input.grant) {
const ask = `Accept flaky ${subject.cases.join(', ')} in run ${subject.runs.join(',')} for PR #${subject.pr} at ${short(subject.head)}? Pregate only; required CI still needs a green run.`.replace(/"/g, "'").slice(0, 300);
const ask = `Accept flaky ${subject.cases.join(', ')} in run ${subject.runs.join(',')} for PR #${subject.pr} at ${short(subject.head)}? Pregate only; required CI still needs a green run.`.slice(0, 300);
return invalid(lane, 'grant_needed', `every failure in ${label} is named (${subject.cases.join(', ')}) and outside the PR's diff and its import closure; the owner must grant the exception`,
`ask the owner, record their words with gstack-approvals add --ask "${ask}" --words "<the owner's exact words>" --by "<owner>" --scope "${binding}" --source pregate-flaky-exception, then rerun this clear with --grant <the row id it prints>`);
`ask the owner, record their words with gstack-approvals add --ask ${shellQuote(ask)} --words "<the owner's exact words>" --by "<owner>" --scope "${binding}" --source pregate-flaky-exception, then rerun this clear with --grant <the row id it prints>`);
}
const row = input.grantRow?.(input.grant) ?? null;
if (!row) return invalid(lane, 'grant_missing', `no gstack-approvals row ${input.grant} in this branch's record`);
+4 -5
View File
@@ -26,7 +26,7 @@ import { timingCheck } from './checks/timing';
import { NON_DOWNGRADABLE, PREGATE_CONFIG_PATH, userPolicyAbsent } from './config';
import { sha256, withFingerprint } from './context';
import { baseBranchOf, baseMismatchFix, describeSubstitution, substituteStampOnlyTree } from './remote-receipt';
import { EXCEPTION_RECEIPT, exceptionProblem, exceptionSummary, flakyDecision, flakyRuns, parseCases, type FailureDeps, type FlakyInput } from './flaky-exception';
import { EXCEPTION_RECEIPT, exceptionProblem, exceptionSummary, flakyDecision, flakyRuns, parseCases, redRunChanged, type FailureDeps, type FlakyInput } from './flaky-exception';
import { resolveBaseBranch } from '../ship-policy';
import { parseRunReceipt, parseRunRef, runChanged, runReceiptFields, uncoveredFiles, verifyRun, type ApiJob, type ApiRun, type RemoteGateRuns, type RunDeps, type RunEvidence, type RunProfile } from '../run-evidence';
import type { CheckDef, CheckResult, PregateContext, Stage } from './types';
@@ -396,17 +396,16 @@ function clearByRuns(file: PregateFile, lane: string, refs: string[], check: Run
}
const missing = uncoveredFiles([...required], evidence);
if (missing.length) return { refused: 'PREGATE_RECEIPT_INVALID', message: `REUSE: refused reason=${isGate ? 'inventory_uncovered' : 'tests_uncovered'} — ${missing.length} of the ${required.size} test file(s) ${lane} must cover appear in no run's tree.json (${missing.slice(0, 5).join(', ')}${missing.length > 5 ? ', …' : ''}); ${lane} stays uncleared` };
const granted = red ? flakyDecision(file, lane, check, red, flaky!, repoRoot) : null;
if (granted && 'refused' in granted) return granted;
for (const e of evidence) {
if (red?.red.some(r => r.evidence === e)) continue;
let changed: string | null;
try { changed = runChanged(check.deps, e.run_id, e.run_attempt); }
try { changed = red?.red.some(r => r.evidence === e) ? redRunChanged(check.deps, e.run_id, e.run_attempt) : runChanged(check.deps, e.run_id, e.run_attempt); }
catch (err: any) { changed = `reading run ${e.run_id} again failed: ${String(err?.message ?? err).slice(0, 200)}`; }
if (changed) return { refused: 'PREGATE_RECEIPT_INVALID', message: `REUSE: refused reason=run_changed — ${changed}; ${lane} stays uncleared` };
}
const tests = evidence.reduce((n, e) => n + (e.tests ?? e.files.length), 0);
const accepted = sub.substitution ? ` accepted-for=${file.tree.slice(0, 12)} stamp-only=${sub.substitution.files.length}` : '';
const granted = red ? flakyDecision(file, lane, check, red, flaky!, repoRoot) : null;
if (granted && 'refused' in granted) return granted;
const exception = granted?.exception;
return { receipt: `${isGate ? 'remote-gate' : 'run'} tree=${trees[0]!.slice(0, 12)} tests=${tests}${skippedCount ? ` skipped=${skippedCount}` : ''}${exception ? ` exception=${exception.grant} cases=${exception.cases.length}` : ''}${accepted} ${runReceiptFields(evidence, check.now)}`, attested: false, substitution: sub.substitution, ...(exception ? { exception } : {}) };
}
+3 -1
View File
@@ -29,7 +29,7 @@ import * as fs from 'node:fs';
import * as path from 'node:path';
import type { PregateFile } from './headless-artifacts';
import { CHECKS, pregateMirrorPath, readPregateFile, verifyPregate, PREGATE_OUT_REL } from './pregate/index';
import { flakyExceptionLines } from './pregate/flaky-exception';
import { exceptionConsumerProblem, flakyExceptionLines } from './pregate/flaky-exception';
import { ensureTmpExcluded, restamp, versionAtRef } from './restamp';
import type { ResultCodeName } from './result-codes';
import { extractReceipt } from './ship-receipt';
@@ -260,6 +260,8 @@ export function ready(ctx: QueueContext, o: { pr: string; pregate?: string; gate
pregateLine = `PREGATE: ${read.file.verdict} tree=${read.file.tree.slice(0, 12)} head=${(read.file.head ?? '').slice(0, 12)} checks=${read.file.checks.length} (from the state root's pregate/by-tree/${treeNow.tree.slice(0, 12)}.json, recorded by another checkout)`;
} else if (gap) pregateLine += `; the state root's mirror for this tree cannot stand in: ${gap}`;
}
const foreign = accepted && exceptionConsumerProblem(accepted, head, o.pr);
if (foreign) { pregateOk = false; accepted = null; pregateLine += ` — ${foreign} (PREGATE_REMOTE_UNCLEARED)`; }
const receipt = bindShippedReceipt(ctx.repoRoot, `origin/${ctx.base}`, info.body ?? '', o.pr, acceptedReceiptHeads(ctx, policy, head, info.body ?? ''));
const attempt = currentAttempt(ctx.repoRoot, o.pr);
const hint = conflictHint(info.mergeable, ci, info.headRefName, info.isCrossRepository === true, head);
+36 -3
View File
@@ -50,7 +50,7 @@ async function pregateAt(repo: FixtureRepo): Promise<PregateFile> {
}
interface Shard { files: string[]; failures?: Array<[string | null, string]>; status?: 'passed' | 'failed' | 'timed-out'; unattributed?: number; retry?: { files: string[]; failures: Array<[string, string]> }; log?: string | null }
interface RunOver { conclusion?: string; jobs?: (jobs: ApiJob[]) => ApiJob[]; missing?: string[]; pull?: { head_sha: string; state: string; head_repo: string | null }; newer?: boolean; attemptAfter?: number }
interface RunOver { conclusion?: string; jobs?: (jobs: ApiJob[]) => ApiJob[]; missing?: string[]; pull?: { head_sha: string; state: string; head_repo: string | null }; newer?: boolean; attemptAfter?: number; api?: Partial<ApiRun> }
const DEFAULT_SHARDS: Shard[] = [
{ files: ['test/retry.test.js', 'test/a.test.ts'], failures: [['test/retry.test.js', 'retries three times']] },
@@ -98,7 +98,7 @@ function redRun(repo: FixtureRepo, head: string, shards: Shard[] = DEFAULT_SHARD
let reads = 0;
const api = (): ApiRun => ({
id: 400, run_attempt: reads++ > 0 && over.attemptAfter ? over.attemptAfter : 1, status: 'completed', conclusion: over.conclusion ?? (anyFailed ? 'failure' : 'success'), event: 'workflow_dispatch', head_sha: head,
path: '.github/workflows/free-tests.yml', created_at: '2026-10-29T10:00:00Z', updated_at: at, repository: { full_name: 'fx/fx' }, head_repository: { full_name: 'fx/fx' },
path: '.github/workflows/free-tests.yml', created_at: '2026-10-29T10:00:00Z', updated_at: at, repository: { full_name: 'fx/fx' }, head_repository: { full_name: 'fx/fx' }, ...over.api,
});
const deps: RunDeps = {
run: id => { if (id !== 400) throw new Error('HTTP 404'); return api(); },
@@ -130,7 +130,7 @@ describe.skipIf(process.platform === 'win32')('clear --flaky-exception: an owner
const file = await pregateAt(repo);
const check = redRun(repo, head);
const asked = clearDecision(file, LANE, { runs: ['400'], evidence: check, flaky: { cases: [FLAKE], pr: 7 } }, repo.work);
expect(msg(asked)).toMatch(/^PREGATE_RECEIPT_INVALID: FLAKY_EXCEPTION: refused reason=grant_needed — .*test\/retry\.test\.js::retries three times.*; fix: .*gstack-approvals add --ask ".*" --words "<the owner's exact words>" --by "<owner>" --scope "flaky-exception:[0-9a-f]{64}" --source pregate-flaky-exception/);
expect(msg(asked)).toMatch(/^PREGATE_RECEIPT_INVALID: FLAKY_EXCEPTION: refused reason=grant_needed — .*test\/retry\.test\.js::retries three times.*; fix: .*gstack-approvals add --ask '.*' --words "<the owner's exact words>" --by "<owner>" --scope "flaky-exception:[0-9a-f]{64}" --source pregate-flaky-exception/);
const d = clearDecision(file, LANE, { runs: ['400'], evidence: check, flaky: { cases: [FLAKE], pr: 7, grant: 'a-1', grantRow: grantFor(asked) } }, repo.work);
if ('refused' in d) throw new Error(d.message);
expect(d.receipt).toBe(`remote-gate tree=${file.tree.slice(0, 12)} tests=18 exception=a-1 cases=1 runs=400/1 profiles=${PROFILE_NAME} heads=${head.slice(0, 12)} verified=2026-10-29T12:00:00.000Z`);
@@ -195,6 +195,32 @@ describe.skipIf(process.platform === 'win32')('clear --flaky-exception: an owner
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { conclusion: 'cancelled' }))).toContain('reason=run_not_failure');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { newer: true }))).toContain('reason=newer_run');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { attemptAfter: 2 }))).toContain('reason=run_changed');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { api: { repository: { full_name: 'other/fx' } } }))).toContain('reason=other_repository');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { api: { head_repository: { full_name: 'fork/fx' } } }))).toContain('reason=fork_run');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { api: { path: '.github/workflows/evals.yml' } }))).toContain('reason=workflow_mismatch');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { api: { event: 'push' } }))).toContain('reason=event_mismatch');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { api: { status: 'in_progress' } }))).toContain('reason=run_in_progress');
expect(clear(redRun(repo, head, DEFAULT_SHARDS, { jobs: js => js.map(j => ({ ...j, completed_at: '2026-10-27T10:30:00Z' })) }))).toContain('reason=run_stale');
}, 120_000);
test('a red run that moves while the clear is assembled refuses, and the printed grant command quotes case names for the shell', async () => {
const repo = fixture();
const head = repo.git('rev-parse', 'HEAD');
const file = await pregateAt(repo);
const check = redRun(repo, head);
const asked = clearDecision(file, LANE, { runs: ['400'], evidence: check, flaky: { cases: [FLAKE], pr: 7 } }, repo.work);
let rerun = false;
const run = check.deps.run;
const moving: RunCheck = { ...check, deps: { ...check.deps, run: id => (rerun ? { ...run(id), run_attempt: 2, status: 'in_progress' } : run(id)) }, inventory: () => { rerun = true; return ['test/a.test.ts', 'test/b.test.ts'] } };
expect(msg(clearDecision(file, LANE, { runs: ['400'], evidence: moving, flaky: { cases: [FLAKE], pr: 7, grant: 'a-1', grantRow: grantFor(asked) } }, repo.work))).toContain('reason=run_changed — run 400 moved to attempt 2 (in_progress)');
const name = "times out $(touch pwned) `id` it's";
const shard: Shard[] = [{ files: ['test/retry.test.js', 'test/a.test.ts'], failures: [['test/retry.test.js', name]] }, DEFAULT_SHARDS[1]!];
const fix = msg(clearDecision(file, LANE, { runs: ['400'], evidence: redRun(repo, head, shard), flaky: { cases: [`test/retry.test.js::${name}`], pr: 7 } }, repo.work));
const ask = /--ask ('(?:[^']|'\\'')*') --words/.exec(fix)?.[1];
expect(ask).toBeDefined();
const echoed = Bun.spawnSync(['bash', '-c', `printf %s ${ask}`], { cwd: repo.work, timeout: 10_000 });
expect(echoed.stdout.toString()).toContain(`test/retry.test.js::${name}`);
expect(fs.existsSync(path.join(repo.work, 'pwned'))).toBe(false);
}, 120_000);
test('a retry that passed is a recovered flake; a retry that failed again keeps only its own failures', async () => {
@@ -216,6 +242,8 @@ describe.skipIf(process.platform === 'win32')('clear --flaky-exception: an owner
expect(one('test/util.test.js', 'adds')).toContain('reason=case_in_closure — test/util.test.js depends on a file the PR changes (lib/util.js)');
expect(one('test/skill-e2e-flaky.test.ts', 'judge')).toContain('reason=case_paid — test/skill-e2e-flaky.test.ts is a paid test file');
expect(one('test/cso-docker-policy.test.ts', 'times out')).toContain('reason=case_security — test/cso-docker-policy.test.ts tests a security scan');
expect(one('test/ship-template-redaction.test.ts', 'blocks')).toContain('reason=case_security');
expect(one('browse/test/content-security.test.ts', 'blocks')).toContain('reason=case_security');
const touched = fixture();
commit(touched, { 'test/retry.test.js': "const test = require('node:test');\ntest('retries three times', () => {});\n" }, 'touch the flaky test');
@@ -280,5 +308,10 @@ describe.skipIf(process.platform === 'win32')('ready with a flaky exception', ()
const red = queue({ check_runs: [{ status: 'completed', conclusion: 'failure' }] });
expect(red.status).toBe(EXIT.refused);
expect(red.err).toMatch(/CI is not green at .*\(QUEUE_NOT_READY\)/);
const other = { ...exception, pr: 2 };
fs.writeFileSync(pregate, JSON.stringify({ ...JSON.parse(fs.readFileSync(pregate, 'utf8')), exceptions: [{ ...other, grant: 'a-1', by: 'Garry', words: WORDS, binding: exceptionBinding(other) }] }));
const moved = queue({ check_runs: [{ status: 'completed', conclusion: 'success' }] });
expect(moved.status).toBe(EXIT.refused);
expect(moved.err).toContain('flaky exception grant a-1 is bound to PR #2');
}, 120_000);
});