Files
gstack/test/ship-measure-sweep.test.ts
Garry Tanandcapy-ai[bot] db745675bd v1.91.33.0 feat(ship): measurement bar with explained-red and pooled decisions, weekly flaky-test sweep on main (#3059)
* refactor(eval-history): share pass-rate history loading as loadPassRateHistory()

eval:pass-rates' main block loaded local dirs or the weekly trial-outcomes
artifacts inline. Move that into an exported function returning records,
scope and fetch errors, so the off-ship sweep reads the same history the
same way. Behavior unchanged.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(ship-measure): the measurement bar and the weekly off-ship sweep CLI

Decide each diagnostic measurement with one bar (scripts/lib/measure-bar.ts):
MEETS at 9/10; MEETS-qualified at 8/10 when every red is a mechanical
provider error, judge noise or a cited model miss; EXTEND at 7/10 (or an
unqualified 8/10) for one more batch on identical inputs, decided once on
the pooled 20; BELOW otherwise and for any timeout, hang, regression or
fixable cause. Behavior uses 12 trials (11/10/9; 22/20 of 24); judge counts
median-panel outputs. A batch with more than 30% provider-evidence failures
is void and redispatched once. Below-strict reds nobody classified return
needs-classify.

ship-measure gains classify (append-only records; provider refused without
evidence), decide and extend (refuses a changed identity, names the field,
never a third batch), exit codes 5 and 6, and a pooling identity (frozen
tree, runner, CLI, model, CI image, Bun, policy and harness versions).

ship-measure sweep ranks main's gate cases by their cost to the all-green
probability from eval:pass-rates history, measures the top K with the same
runner and bar under a weekly cap shared with the last 7 days of sweep
reports (ship_measure_sweep_cases, ship_measure_sweep_budget_usd), and
writes a report with the predicted all-green probability before and after.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(ship): the measure loop decides by the bar's exit codes

Step 4 branches on MEETS/MEETS-qualified, needs-classify (with the
qualifying and fix-round classes), EXTEND once and BELOW; the rest of the
loop prose is tightened to offset most of the growth. ship's union size
cap moves from 1.495 to 1.4975 (measured 281,058 / 187,706 = 1.4973).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci: weekly eval-sweep workflow on main

Mondays 12:00 UTC and on dispatch (k, cap_usd, dry_run): runs
ship-measure sweep in the published CI image by digest with the census's
paid-eval setup, downloads the last 7 days of sweep reports first so the
weekly cap holds across runs, uploads the report and captures, and writes
the job summary. Read-only grants; it never pushes or opens a pull request.
One sweep at a time repository-wide, never cancelled mid-spend.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: the measurement bar table and the weekly sweep

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: v1.91.33.0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Revert "ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation"

This reverts commit f4c58f2869.

* ci: classify eval-sweep.yml, the sweep CLI and actionlint labels as free-only PR inputs

None is read by a PR-gate case, so a change to them selects no paid case
instead of restoring the full gate (test/free-fixtures.test.ts workflow
inventory).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship-measure): the selection preflight accepts name-mode cases

A case registered beside others in its file lists its trial shard as
<file>#<id>~t1, not <file> (trial 1), so the preflight refused every
name-mode case (for example ship-exploratory-late-input) before any paid
trial. Accept both forms.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Revert "ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation"

This reverts commit 9619734307.

* test: record the test-pr-profile.ts change as non-behavioral for HARNESS_VERSION

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): the name-mode preflight fix

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship-measure): a partly funded sweep row shows the trials that ran

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(sweep): the fake single-case command is a Bun script, so it runs on Windows

The Windows free lane runs test/ship-measure-sweep.test.ts; CreateProcess
cannot exec a shebang shell stub, so every fake trial errored and the cap
stopped the sweep after one trial.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(plan-mode-no-op): the CEO sub-test reviews a fixed pasted plan

Without a plan, plan-ceo-review reviewed the checkout's branch diff, so its
pre-question work (audit and landscape searches) grew with whatever PR the
gate ran on. On this PR it timed out at 300s in 3 of 10 trials while still
researching the diff (ship-measure baseline 7/10). With a fixed small plan,
the same way the devex sub-test already runs, it measured 10/10 (CEO
sub-test 83-255s). No budget or threshold changed.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship-measure): each diagnostic trial gets its own EVALS_RUN_ID

Evidence-retaining cases (functional QA, docs faults) refuse to start
without EVALS_RUN_ID, which only CI sets, so measuring them locally ran
0/10 sessions. qa-functional-cli-report, red once in this PR's gate,
measured 10/10 once the trials started ($11.13).

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>
2026-10-07 14:00:06 +00:00

203 lines
13 KiB
TypeScript

/**
* Free contracts for the weekly off-ship qualification sweep
* (scripts/ship-measure-sweep.ts): which gate cases it picks from pass-rate
* history, the predicted all-green probability before and after, the weekly
* spend cap enforced through the admission budget across earlier sweep
* reports, the report it writes after every case, and a $0 CLI run (dry run,
* and a fake single-case command). Nothing here makes a paid call.
*/
import { afterAll, describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
import { EVAL_POLICY } from '../test/helpers/periodic-exclude-data';
import { TRIAL_OUTCOME_SCHEMA, type TrialOutcomeRecord } from '../test/helpers/eval-store';
import { MEASURE_DEFAULTS, type TrialRequest, type TrialResult } from '../scripts/ship-measure';
import { SWEEP_SCHEMA, measuredRedRate, planSweep, predictedAllGreen, runSweep, weeklySpent, type SweepReport } from '../scripts/ship-measure-sweep';
const ROOT = path.resolve(import.meta.dir, '..');
const temps: string[] = [];
const tmp = (prefix: string) => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); temps.push(dir); return dir; };
afterAll(() => { for (const dir of temps) fs.rmSync(dir, { recursive: true, force: true }); });
const A = 'ship-skipped-queued-finding';
const B = 'investigate-owned-completion';
const C = 'investigate-owned-abort';
const Q = 'investigate-owned-ending-error';
function record(id: string, run: number, failed: boolean, extra: Partial<TrialOutcomeRecord> = {}): TrialOutcomeRecord {
return {
schema: TRIAL_OUTCOME_SCHEMA, case: id, file: `test/${id}.test.ts`, tier: 'gate', kind: 'rule', trial: 1, panel: { n: 1, k: 1 }, attempt: 1,
outcome: failed ? 'failed' : 'passed', ...(failed ? { failure_class: 'assertion' as const } : {}), duration_ms: 1000, cost_usd: 1,
policy_version: EVAL_POLICY.version, quarantined: false, execution: 'executed', source: 'shard', run_id: String(run), lane: 'gate',
series_identity: 's1', model: 'm', cli_version: 'c', recorded_at: `2026-09-${10 + run}T00:00:00Z`, ...extra,
} as TrialOutcomeRecord;
}
/** Three weekly runs: A red twice, B red once, C never, a periodic case red (not gate), a retired id red. */
function history(): TrialOutcomeRecord[] {
const out: TrialOutcomeRecord[] = [];
for (const run of [1, 2, 3]) {
out.push(record(A, run, run !== 3), record(B, run, run === 2), record(C, run, false));
out.push(record('retired-case-id', run, true), record('some-periodic-case', run, true, { tier: 'periodic', lane: 'periodic' }));
}
return out;
}
describe('picking cases from gate history', () => {
test('ranks live, non-quarantined gate cases with a red by their cost to the all-green probability, and picks K', () => {
const withQuarantined = [...history(), ...[1, 2, 3].map(run => record(Q, run, true))];
expect(planSweep(withQuarantined, 3, { quarantine: {} }).picked.map(c => c.case)).toEqual([Q, A, B]);
const plan = planSweep(withQuarantined, 2, { quarantine: { [Q]: {} } });
expect(plan.candidates.map(c => c.case)).toEqual([A, B]);
expect(plan.picked.map(c => c.case)).toEqual([A, B]);
const [a, b] = plan.candidates;
expect(a).toMatchObject({ reds: 2, verdicts: 3, scoredTrials: 3, costPerTrialUsd: 1, kind: 'rule' });
expect(a!.passRate).toBeCloseTo(1 / 3, 5);
expect(a!.redRate).toBeGreaterThan(b!.redRate);
expect(a!.contribution).toBeGreaterThan(b!.contribution);
expect(plan.censusCases).toEqual([A, B, C, Q, 'retired-case-id'].sort());
const product = plan.censusCases.reduce((p, id) => p * (1 - plan.rates[id]!), 1);
expect(plan.allGreen).toBeCloseTo(product, 10);
expect(planSweep(history(), 1).picked.map(c => c.case)).toEqual([A]);
});
test('a measured case replaces its history rate; a behavior rate converts to the 3-trial panel red risk', () => {
expect(measuredRedRate('rule', 0, 10, 0)).toBe(0);
expect(measuredRedRate('rule', 1, 10, 0.05)).toBeCloseTo((1 + 4 * 0.05) / 14, 10);
const q = 2 / 16;
expect(measuredRedRate('behavior', 2, 12, 0)).toBeCloseTo(1 - ((1 - q) ** 3 + 3 * (1 - q) ** 2 * q), 10);
const plan = planSweep(history(), 2);
const after = predictedAllGreen(plan, { [A]: 0 })!;
expect(after).toBeCloseTo(plan.allGreen! / (1 - plan.rates[A]!), 10);
});
});
describe('the weekly cap', () => {
const report = (dir: string, id: string, startedAt: string, chargedUsd: number, dryRun = false) => {
fs.mkdirSync(path.join(dir, id), { recursive: true });
fs.writeFileSync(path.join(dir, id, 'sweep-report.json'), JSON.stringify({ schema: SWEEP_SCHEMA, sweepId: id, startedAt, chargedUsd, dryRun }));
};
test('sums the last 7 days of sweep reports once each, skipping dry runs, older reports and this sweep', () => {
const dir = tmp('sweep-prior-');
const now = Date.parse('2026-10-12T12:00:00Z');
report(dir, 'a', '2026-10-10T00:00:00Z', 40);
report(path.join(dir, 'copy'), 'a', '2026-10-10T00:00:00Z', 40);
report(dir, 'b', '2026-10-06T00:00:00Z', 25);
report(dir, 'old', '2026-10-01T00:00:00Z', 100);
report(dir, 'dry', '2026-10-11T00:00:00Z', 0, true);
report(dir, 'self', '2026-10-12T11:00:00Z', 9);
expect(weeklySpent([dir], now, 'self')).toBe(65);
expect(weeklySpent([path.join(dir, 'missing')], now)).toBe(0);
});
});
describe('running the sweep with a fake runner', () => {
const fake = (fail: (r: TrialRequest) => boolean) => {
const calls: TrialRequest[] = [];
const runner = async (r: TrialRequest): Promise<TrialResult> => { calls.push(r); return fail(r) ? { passed: false, costUsd: 1, failureCause: 'assertion' } : { passed: true, costUsd: 1 }; };
return { calls, runner };
};
const base = (outDir: string, runner: (r: TrialRequest) => Promise<TrialResult>, extra: Partial<Parameters<typeof runSweep>[0]> = {}) => runSweep({
records: history(), historyScope: 'fixture history', k: 2, capUsd: 150, config: MEASURE_DEFAULTS, outDir, priorDirs: [], dryRun: false, parallel: 5,
ref: 'main', sha: 'a'.repeat(40), runner, runnerId: 'fake', preflight: () => ({ ok: true, detail: 'fake' }), identity: () => ({ tree: 'fixed' }),
evalDir: tmp('sweep-history-'), log: () => {}, now: Date.parse('2026-10-12T12:00:00Z'), ...extra,
});
test('measures each picked case at its kind\'s N with the bar, writes the report, and predicts the all-green probability after', async () => {
const outDir = tmp('sweep-out-');
const run = fake(r => r.caseId === A && r.trial <= 4);
const report = await base(outDir, run.runner);
expect(run.calls.filter(c => c.caseId === A)).toHaveLength(10);
expect(run.calls.filter(c => c.caseId === B)).toHaveLength(10);
expect(report.rows.map(r => [r.case, r.status, r.decision, `${r.measuredPasses}/${r.measuredTrials}`])).toEqual([[A, 'measured', 'BELOW', '6/10'], [B, 'measured', 'MEETS', '10/10']]);
expect(report).toMatchObject({ label: 'diagnostic', verdict: null, chargedUsd: 20, priorWeekUsd: 0, k: 2 });
expect(report.allGreenAfter).not.toBe(report.allGreenBefore);
const dir = path.join(outDir, `sweep-${report.sweepId}`);
const saved = JSON.parse(fs.readFileSync(path.join(dir, 'sweep-report.json'), 'utf8')) as SweepReport;
expect(saved.rows[0]!.captures).toBe(path.join(`sweep-${report.sweepId}`, A, 'baseline'));
expect(fs.existsSync(path.join(outDir, saved.rows[0]!.captures!, 't01', 'trial.json'))).toBe(true);
const md = fs.readFileSync(path.join(dir, 'sweep-report.md'), 'utf8');
expect(md).toContain('| # | Case | Kind | History rate | Measured | Decision | Cost (est / actual) | Captures |');
expect(md).toMatch(/Predicted gate all-green probability .*before \d+(\.\d)?% → after \d+(\.\d)?%/);
expect(md).toContain(`| 1 | ${A} | rule | 33.3% of 3 trials; 2/3 red verdicts`);
expect(md).toContain('never pushes or opens a pull request');
});
test('stops at the weekly cap: a partly funded case stops at the budget, the rest are skipped and listed', async () => {
const outDir = tmp('sweep-cap-');
const prior = tmp('sweep-cap-prior-');
fs.writeFileSync(path.join(prior, 'sweep-report.json'), JSON.stringify({ schema: SWEEP_SCHEMA, sweepId: 'earlier', startedAt: '2026-10-08T00:00:00Z', chargedUsd: 136, dryRun: false }));
const run = fake(() => false);
const report = await base(outDir, run.runner, { priorDirs: [prior], capUsd: 150, k: 3, records: history().map(r => r.case === C && r.run_id === '1' ? record(C, 1, true) : r) });
expect(report.priorWeekUsd).toBe(136);
// B and C tie (one red in three); ties fall to the case id, so C ranks before B.
expect(report.rows.map(r => [r.case, r.status])).toEqual([[A, 'measured'], [C, 'budget_exhausted'], [B, 'skipped']]);
// A partly funded case reports the trials that ran, not the planned N.
expect([report.rows[1]!.measuredPasses, report.rows[1]!.measuredTrials]).toEqual([4, 4]);
expect(run.calls).toHaveLength(14);
expect(report.chargedUsd).toBe(14);
expect(report.rows[2]!.note).toContain('weekly cap $150 exhausted');
expect(fs.readFileSync(path.join(outDir, `sweep-${report.sweepId}`, 'sweep-report.md'), 'utf8')).toContain(`- ${B}: weekly cap $150 exhausted`);
});
test('a dry run plans and estimates without running anything or charging the cap', async () => {
const outDir = tmp('sweep-dry-');
const run = fake(() => false);
const report = await base(outDir, run.runner, { dryRun: true });
expect(run.calls).toHaveLength(0);
expect(report.rows.map(r => [r.case, r.status, r.estimatedUsd])).toEqual([[A, 'planned', 10], [B, 'planned', 10]]);
expect(report.chargedUsd).toBe(0);
expect(weeklySpent([outDir], Date.parse('2026-10-12T13:00:00Z'))).toBe(0);
});
test('a case whose selection preflight fails is skipped before any paid call', async () => {
const run = fake(() => false);
const report = await base(tmp('sweep-preflight-'), run.runner, { k: 1, preflight: () => ({ ok: false, detail: '--list planned nothing' }) });
expect(report.rows[0]).toMatchObject({ status: 'skipped', note: 'selection preflight failed (no paid call made): --list planned nothing' });
expect(run.calls).toHaveLength(0);
});
});
describe('ship-measure sweep CLI ($0)', () => {
const historyDir = () => {
const dir = tmp('sweep-cli-history-');
fs.writeFileSync(path.join(dir, 'trial-outcomes.jsonl'), `${history().map(r => JSON.stringify(r)).join('\n')}\n`);
return dir;
};
const cli = (cwd: string, ...args: string[]) => spawnSync(process.execPath, ['run', path.join(ROOT, 'scripts/ship-measure.ts'), 'sweep', ...args], {
cwd, encoding: 'utf8', timeout: 120_000, env: { ...process.env, GSTACK_STATE_ROOT: tmp('sweep-cli-state-'), GITHUB_STEP_SUMMARY: '' },
});
test('--dry-run reads local history, prints the plan and the before probability, and spends nothing', () => {
const cwd = tmp('sweep-cli-');
const r = cli(cwd, '--history-dir', historyDir(), '--k', '2', '--dry-run', '--out', path.join(cwd, 'out'));
expect(r.status).toBe(0);
expect(r.stdout).toContain('(dry run: nothing ran, $0)');
expect(r.stdout).toContain(`| 1 | ${A} | rule |`);
expect(r.stdout).toContain('would measure 10 trials');
});
test('a fake single-case command runs the whole sweep end to end at $0', () => {
const cwd = tmp('sweep-cli-fake-');
// A Bun script, not a shell stub, so the fake runner also executes on Windows.
const script = path.join(cwd, 'fake-trial.ts');
fs.writeFileSync(script, 'console.log("cost_usd: 0");\nif (process.env.GSTACK_SHIP_MEASURE_TRIAL === "3") { console.log("failure_cause: assertion"); process.exit(1); }\nconsole.log(`PASS ${process.argv[2]}`);\n');
const bun = /\s/.test(process.execPath) ? 'bun' : process.execPath;
const r = cli(cwd, '--history-dir', historyDir(), '--k', '1', '--cap-usd', '1', '--command', `${bun} ${script} {case}`, '--jobs', '5', '--out', path.join(cwd, 'out'));
expect(r.status).toBe(0);
expect(r.stdout).toContain(`DIAGNOSTIC ${A} baseline: observed 9/10 trials (baseline); MEETS`);
expect(r.stdout).toMatch(new RegExp(`\\| 1 \\| ${A} \\| rule \\| [^|]+ \\| 9/10 \\| MEETS \\|`));
});
test('history that cannot be read fails closed with nothing measured', () => {
const cwd = tmp('sweep-cli-nohistory-');
const r = spawnSync(process.execPath, ['run', path.join(ROOT, 'scripts/ship-measure.ts'), 'sweep', '--dry-run', '--repo', 'garrytan/no-such-repo-xyz', '--out', path.join(cwd, 'out')], {
cwd, encoding: 'utf8', timeout: 120_000, env: { ...process.env, PATH: path.dirname(process.execPath), GSTACK_STATE_ROOT: tmp('sweep-cli-state-') },
});
expect(r.status).toBe(1);
expect(r.stderr).toContain('pass-rate history unavailable');
});
});