#!/usr/bin/env -S bun --no-env-file --config=/dev/null /** * eval-pass-rates (alias: eval-flake-rank) — per-case trial pass rates. * * Reads trial records (one JSONL line per trial: case, kind, trial, outcome, * exit_reason, duration, cost, model, CLI version, series identity, run id, * sha, policy_version) from the last N completed `evals-periodic.yml` runs of * the weekly history: scheduled runs on `main` plus `main` dispatches * (`--branch ` reads one branch's runs for inspection). Trials from * branch census runs in the same window pool into a series main has also * run, never into a new one; weeks for quarantine expiry count main only, * downloading only each run's small `trial-outcomes` artifact through `gh`, * plus any local eval dirs, and * prints per-case per-trial pass rates with 95% Wilson intervals. * * A series is one case under one input identity (EVAL_POLICY v2: the bytes * the case owns plus HARNESS_VERSION, stamped by scripts/eval-trial-series.ts * caseSeriesIdentitiesV2), grouped by model and CLI version, per * policy_version. A new identity starts a new series; earlier * series stay visible. Only trials of the current series under the reader's * own EVAL_POLICY.version feed the labels and alarms: an older policy's * trials are display-only, and a newer policy's trials (written by a later * checkout) are ignored with a printed count, so a reader rolled back past a * policy bump never pools records it cannot interpret. Legacy eval-store records (`--backfill`, `--dir`) are * imported as pre-policy trials (first attempt only; a missing attempt means * 1) and are display-only. * * Labels: INCONCLUSIVE (below the entry rule's minimum trials), BROKEN (latest run 0/n * after a prior interval at or above the entry rate), FLAKY (failures and an * interval straddling the entry rate), FAILING (interval below the entry * rate), PASSING (otherwise). * * The weekly gate (`--gate`) exits non-zero with ACTION REQUIRED when a * non-quarantined case meets the quarantine entry rule, a rule case behaves * like a behavior case, a blocking case's current-identity rate is * significantly below its previous identity (one-sided Fisher exact, * Holm-controlled across cases), or a CASE_QUARANTINE entry has met its exit * rule, expired, or pushed its tier over the cap. History that cannot be * fetched fails the gate closed. * * Usage: * bun run eval:pass-rates # last 10 weekly runs (main: scheduled + dispatched) * bun run eval:pass-rates --case --runs 20 * bun run eval:pass-rates --dir # local eval dirs / downloaded artifacts (repeatable) * bun run eval:pass-rates --backfill # also import legacy slice artifacts, labeled pre-policy * bun run eval:pass-rates --json | --gate * bun run eval:pass-rates --headroom | --reds | --run # report views (scripts/lib/eval-history.ts) * bun run eval:pass-rates --help # every flag, no network */ import * as fs from 'node:fs'; import * as path from 'node:path'; import { isPartialEval, isFinalizedEvalResultFile, evalEntryOutcome, failureClassOf, parseTrialOutcomes, sanitizeTrialError, TRIAL_OUTCOME_SCHEMA, type EvalCaseKind, type EvalResult, type TrialOutcomeRecord } from '../test/helpers/eval-store'; import { flakeLedgerPath, type FlakeLedgerEntry } from './test-free-shards'; import { E2E_KINDS, E2E_TIERS, E2E_TOUCHFILES, GLOBAL_TOUCHFILES, LLM_JUDGE_TOUCHFILES } from '../test/helpers/touchfiles-data'; import { CASE_QUARANTINE, EVAL_POLICY } from '../test/helpers/periodic-exclude-data'; import { CASE_TEST_NAMES } from './test-paid-shards'; import { resolveStateRoot } from '../lib/state-root'; import { GH_JOBS, PASS_RATES_USAGE, criticalPath, formatCriticalPath, formatHeadroom, formatRedLedger, headroom, headroomAlarms, parsePassRatesArgs, redLedger, triageRun, CENSUS_RED_GUIDE, type CriticalPath } from './lib/eval-history'; import { downloadRunArtifacts, GH_HISTORY, isPooledTrialRun, isWeeklyHistoryRun, listWeeklyRuns, parseFlakeLedger, repoSlug, TRIAL_OUTCOMES_MAX_BYTES, type WeeklyRun } from './lib/ci-history'; interface TestSeries { name: string; runs: number; passes: number; fails: number; manualAccepted: number; retriedPasses: number; totalAttempts: number; totalCostUsd: number; totalDurationMs: number; lastSeen: string; } export function aggregate(evalFiles: string[]): Map { const series = new Map(); for (const file of evalFiles) { let run: EvalResult; try { run = JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { continue; } if (isPartialEval(run, file)) continue; // in-progress accumulators are not runs if (!Array.isArray(run.tests)) continue; // Group this run's entries by name so N attempts = 1 run of that test. const byName = new Map(); for (const t of run.tests) { const list = byName.get(t.name) ?? []; list.push(t); byName.set(t.name, list); } for (const [name, entries] of byName) { const s = series.get(name) ?? { name, runs: 0, passes: 0, fails: 0, manualAccepted: 0, retriedPasses: 0, totalAttempts: 0, totalCostUsd: 0, totalDurationMs: 0, lastSeen: '', }; const final = entries[entries.length - 1]; s.totalAttempts += entries.length; const outcome = evalEntryOutcome(final); if (outcome === 'manual-review') s.manualAccepted += 1; else { s.runs += 1; if (outcome === 'passed') s.passes += 1; else s.fails += 1; if (outcome === 'passed' && entries.length > 1) s.retriedPasses += 1; } for (const e of entries) { s.totalCostUsd += e.cost_usd || 0; s.totalDurationMs += e.duration_ms || 0; } if (run.timestamp > s.lastSeen) s.lastSeen = run.timestamp; series.set(name, s); } } return series; } export function collectEvalFiles(dir: string, sinceDays = 60): string[] { if (!fs.existsSync(dir)) return []; const cutoff = Date.now() - sinceDays * 86_400_000; const out: string[] = []; for (const name of fs.readdirSync(dir, { recursive: true }) as string[]) { if (!isFinalizedEvalResultFile(name)) continue; const full = path.join(dir, name); try { // Recency bound (review finding): E2E results embed full transcripts // (MBs each) and the scan is otherwise unbounded over all-time history. if (fs.statSync(full).mtimeMs < cutoff) continue; } catch { continue; } out.push(full); } return out; } /** The local free-suite flake ledger (CI ledgers are read by test:health through scripts/lib/ci-history.ts). */ function readFreeLedger(): FlakeLedgerEntry[] { try { return parseFlakeLedger(fs.readFileSync(flakeLedgerPath(), 'utf-8')); } catch { return []; } } // --- Trial records --- /** * A trial record as pass-rates reads it: eval-store's trial-outcomes schema * plus what the report job stamps (scripts/eval-trial-series.ts): the series * identity, the full consumed-input fingerprint and HARNESS_VERSION, both * provenance only. policy_version 0 marks a pre-policy (backfilled) record. */ export type TrialRecord = TrialOutcomeRecord & { series_identity?: string; series_fingerprint?: string; harness_version?: number }; /** Every `trial-outcomes*.jsonl` file under a directory, size-capped, schema-validated by eval-store. */ export function readTrialOutcomeDir(dir: string): { records: TrialRecord[]; errors: string[] } { const records: TrialRecord[] = []; const errors: string[] = []; if (!fs.existsSync(dir)) return { records, errors }; for (const name of fs.readdirSync(dir, { recursive: true }) as string[]) { if (!/^trial-outcomes[^/\\]*\.jsonl$/.test(path.basename(name))) continue; const full = path.join(dir, name); const parsed = parseTrialOutcomes(fs.readFileSync(full, 'utf8'), { maxBytes: TRIAL_OUTCOMES_MAX_BYTES }); records.push(...parsed.records.map(record => ({ ...record, series_identity: typeof (record as TrialRecord).series_identity === 'string' ? (record as TrialRecord).series_identity!.slice(0, 64) : undefined }))); errors.push(...parsed.errors.map(error => `${full}: ${error}`)); } return { records, errors }; } // --- Registry attribution and series identity --- export interface Registry { kinds: Record; tiers: Record; touchfiles: Record; judgeTouchfiles: Record; globals: readonly string[]; testNames: Record; } export const LIVE_REGISTRY: Registry = { kinds: E2E_KINDS, tiers: E2E_TIERS, touchfiles: E2E_TOUCHFILES, judgeTouchfiles: LLM_JUDGE_TOUCHFILES, globals: GLOBAL_TOUCHFILES, testNames: CASE_TEST_NAMES, }; /** A case's tier: its E2E_TIERS value, or 'judge' for an LLM-judge entry. */ export function caseTier(id: string, registry: Registry = LIVE_REGISTRY): string { return registry.tiers[id] ?? (id in registry.judgeTouchfiles ? 'judge' : 'unknown'); } /** * Attribute a legacy eval-store record to a registry id: the case-shard slug * suffix (`--`), the recorded name or its exact slug (`/qa b6-static` * is `qa-b6-static`), a CASE_TEST_NAMES label, or the only id its shard file * registers. Anything else is unattributed (null). */ export function attributeLegacyRecord(name: string, shard: string | undefined, registry: Registry = LIVE_REGISTRY): string | null { const known = (id: string) => id in registry.kinds; const [slugFile, slugCase] = (shard ?? '').split('--'); if (slugCase && known(slugCase)) return slugCase; if (known(name)) return name; const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''); if (known(slug)) return slug; const labeled = Object.entries(registry.testNames).find(([, label]) => label === name)?.[0]; if (labeled && known(labeled)) return labeled; if (slugFile) { const file = `test/${slugFile}.test.ts`; const owners = Object.keys(registry.touchfiles).filter(id => registry.touchfiles[id]!.includes(file)); if (owners.length === 1 && known(owners[0]!)) return owners[0]!; } return null; } /** * Import legacy eval-store result files as pre-policy trials (policy_version * 0, source 'backfill'): first attempt only (a missing attempt means 1), * attributed by registry id, never guessed. A manual-review acceptance carries * no automated verdict: it is counted and shown, never scored. Without a CI * run, each local result file is its own run. */ export function backfillEvalFiles(files: string[], run?: { run_id: string; sha?: string; timestamp?: string }, registry: Registry = LIVE_REGISTRY): { records: TrialRecord[]; unattributed: string[]; manualReviews: string[] } { const records: TrialRecord[] = []; const unattributed = new Set(); const manualReviews: string[] = []; for (const file of files) { let result: EvalResult & { shard?: string; claude_cli_version?: string }; try { result = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { continue; } if (isPartialEval(result, file) || !Array.isArray(result.tests)) continue; const seen = new Set(); for (const entry of result.tests) { if ((entry.attempt ?? 1) !== 1 || seen.has(entry.name)) continue; seen.add(entry.name); const id = attributeLegacyRecord(entry.name, result.shard, registry); if (!id) { unattributed.add(entry.name); continue; } const outcome = evalEntryOutcome(entry); if (outcome === 'manual-review') { manualReviews.push(id); continue; } records.push({ schema: TRIAL_OUTCOME_SCHEMA, case: id, file: result.shard ? `test/${result.shard.split('--')[0]}.test.ts` : 'unknown', tier: caseTier(id, registry), kind: registry.kinds[id]!, trial: 1, panel: { n: 1, k: 1 }, attempt: 1, outcome, ...(outcome === 'failed' ? { failure_class: failureClassOf(entry) } : {}), exit_reason: entry.exit_reason, error: sanitizeTrialError(entry.error), duration_ms: Math.max(0, entry.duration_ms || 0), cost_usd: Math.max(0, entry.cost_usd || 0), ...(entry.cost_known === false ? { cost_known: false } : {}), model: entry.model, cli_version: result.claude_cli_version, policy_version: 0, quarantined: false, execution: entry.execution === 'reused' ? 'reused' : 'executed', source: 'backfill', run_id: run?.run_id ?? `local:${file}`, sha: run?.sha ?? result.git_sha, recorded_at: run?.timestamp ?? result.timestamp, }); } } return { records, unattributed: [...unattributed].sort(), manualReviews }; } // --- Statistics --- /** 95% Wilson score interval for k successes in n trials. */ export function wilsonInterval(k: number, n: number, z = 1.96): { lo: number; hi: number } { if (n <= 0) return { lo: 0, hi: 1 }; const p = k / n, z2 = z * z, denom = 1 + z2 / n; const center = (p + z2 / (2 * n)) / denom; const half = (z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n))) / denom; return { lo: Math.max(0, center - half), hi: k === n ? 1 : Math.min(1, center + half) }; } function logChoose(n: number, k: number): number { let sum = 0; for (let i = 1; i <= k; i++) sum += Math.log(n - k + i) - Math.log(i); return sum; } /** * One-sided Fisher exact p-value that the CURRENT pass rate is below the * PREVIOUS one: P(X <= curPass) under the hypergeometric null with the * observed margins. */ export function fisherOneSidedLower(curPass: number, curN: number, prevPass: number, prevN: number): number { const passes = curPass + prevPass, total = curN + prevN; const denom = logChoose(total, passes); let p = 0; for (let x = Math.max(0, passes - prevN); x <= curPass; x++) p += Math.exp(logChoose(curN, x) + logChoose(prevN, passes - x) - denom); return Math.min(1, p); } /** Holm step-down: the indices whose p-values are rejected at family-wise alpha. */ export function holmRejections(pValues: number[], alpha: number): Set { const order = pValues.map((p, index) => ({ p, index })).sort((a, b) => a.p - b.p); const rejected = new Set(); for (let rank = 0; rank < order.length; rank++) { if (order[rank]!.p > alpha / (order.length - rank)) break; rejected.add(order[rank]!.index); } return rejected; } // --- Analysis --- export type PassRateLabel = 'INCONCLUSIVE' | 'BROKEN' | 'FLAKY' | 'FAILING' | 'PASSING'; export type AlarmKind = 'drift' | 'rule-as-behavior' | 'regression' | 'quarantine-exit' | 'quarantine-expired' | 'quarantine-cap' | 'quarantine-invalid' | 'headroom'; /** The EVAL_POLICY fields pass-rates reads (structural, so tests can vary them). */ export interface PassRatePolicy { version: number; quarantine: { entry: { rate: number; minTrials: number }; exit: { rate: number; minTrials: number }; capFraction: number; expiryWeeklyRuns: number }; drift: { fisherAlpha: number; fisherMinPerSide: number }; } export type QuarantineEntry = (typeof CASE_QUARANTINE)[string]; /** Tiers whose cases block a lane; quarantine applies only to them. */ export const BLOCKING_TIERS: readonly string[] = ['gate', 'periodic']; const QUARANTINE_FAILURE_CLASSES: readonly string[] = ['detector', 'harness', 'model-latency']; export interface SeriesStats { key: string; identity: string; model: string; cli: string; policyVersion: number; passes: number; /** Scored trials: passed + failed (skipped trials carry no verdict). */ trials: number; infra: number; interval: { lo: number; hi: number }; firstSeen: string; lastSeen: string; runs: string[]; } export interface CasePassRate { case: string; kind: EvalCaseKind; tier: string; quarantined: boolean; label: PassRateLabel; /** Manual-review acceptances: visible, never scored. */ manualReviews: number; current: SeriesStats | null; previous: SeriesStats | null; prePolicy: SeriesStats | null; series: SeriesStats[]; latestRun: { runId: string; passes: number; trials: number } | null; } export interface Alarm { kind: AlarmKind; case: string; message: string } export interface PassRateReport { policyVersion: number; cases: CasePassRate[]; alarms: Alarm[]; /** Trials under this reader's EVAL_POLICY.version: the only ones scored. */ postPolicyTrials: number; /** Backfilled pre-policy trials (policy_version 0): display only. */ prePolicyTrials: number; /** Trials under an earlier policy version: display only, never scored. */ olderPolicyTrials: number; /** Trials under a later policy version than this reader: ignored entirely. */ newerPolicyTrials: number; unattributed: string[]; errors: string[]; } export interface AnalyzeOptions { registry?: Registry; quarantine?: Record; policy?: PassRatePolicy; /** Completed weekly-run timestamps in the window, for quarantine expiry. */ weeklyRuns?: string[]; /** * Run ids of branch census runs (isPooledTrialRun). Their trials count only * toward a series that a non-pooled record also has, so a branch can extend * main's history but never start, or become, a case's current series. */ pooledRunIds?: ReadonlySet; now?: number; unattributed?: string[]; errors?: string[]; manualReviews?: string[]; } const at = (record: TrialRecord) => record.recorded_at ?? ''; const runOf = (record: TrialRecord) => `${record.run_id ?? record.sha ?? 'local'}#${record.attempt}`; function seriesStats(key: string, records: TrialRecord[]): SeriesStats { const scored = records.filter(record => record.outcome !== 'skipped'); const passes = scored.filter(record => record.outcome === 'passed').length; const times = records.map(at).sort(); const first = records[0]!; return { key, identity: first.series_identity ?? 'unknown', model: first.model ?? 'unknown', cli: first.cli_version ?? 'unknown', policyVersion: first.policy_version, passes, trials: scored.length, infra: scored.filter(record => record.outcome === 'failed' && record.failure_class === 'infra').length, interval: wilsonInterval(passes, scored.length), firstSeen: times[0] ?? '', lastSeen: times[times.length - 1] ?? '', runs: [...new Set(records.map(runOf))], }; } /** Weekly runs completed after an entry's enteredAt; offline, whole weeks elapsed. */ export function quarantineRunsSince(enteredAt: string, weeklyRuns: string[] | undefined, now: number): number { const entered = Date.parse(enteredAt); if (!Number.isFinite(entered)) return Number.POSITIVE_INFINITY; if (weeklyRuns && weeklyRuns.length) return weeklyRuns.filter(time => Date.parse(time) > entered).length; return Math.floor((now - entered) / (7 * 86_400_000)); } /** * Static CASE_QUARANTINE problems, shared by the free policy test and the * weekly gate: an id that is not a blocking-tier E2E case, a missing field, * a failure class outside detector / harness / model-latency (a product * defect is fixed or named, never quarantined), a malformed or future date, * and a tier over its cap. */ export function quarantinePolicyProblems(quarantine: Record, registry: Registry = LIVE_REGISTRY, policy: PassRatePolicy = EVAL_POLICY, now = Date.now()): Alarm[] { const problems: Alarm[] = []; const invalid = (id: string, message: string) => problems.push({ kind: 'quarantine-invalid', case: id, message: `${id}: ${message}` }); const perTier = new Map(); for (const [id, entry] of Object.entries(quarantine)) { const tier = registry.tiers[id]; if (!tier || !(id in registry.kinds)) { invalid(id, 'CASE_QUARANTINE names no registered E2E case'); continue; } if (!BLOCKING_TIERS.includes(tier)) invalid(id, `tier ${tier} is not blocking; only ${BLOCKING_TIERS.join(' and ')} cases are quarantined`); for (const field of ['reason', 'failureClass', 'tracking', 'owner', 'enteredAt', 'exit'] as const) { if (typeof entry[field] !== 'string' || !entry[field].trim()) invalid(id, `missing ${field}`); } if (typeof entry.reason === 'string' && entry.reason.trim().length < 40) invalid(id, 'reason must be a written diagnosis (at least 40 characters)'); if (!QUARANTINE_FAILURE_CLASSES.includes(entry.failureClass)) { invalid(id, `failureClass ${JSON.stringify(entry.failureClass)} is not ${QUARANTINE_FAILURE_CLASSES.join(', ')}; a product defect is fixed or named as a red, never quarantined`); } const entered = Date.parse(entry.enteredAt); if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.enteredAt ?? '') || !Number.isFinite(entered)) invalid(id, 'enteredAt must be YYYY-MM-DD'); else if (entered > now) invalid(id, 'enteredAt is in the future'); perTier.set(tier, (perTier.get(tier) ?? 0) + 1); } for (const [tier, count] of perTier) { const size = Object.values(registry.tiers).filter(value => value === tier).length; const cap = Math.floor(size * policy.quarantine.capFraction); if (count > cap) problems.push({ kind: 'quarantine-cap', case: tier, message: `${count} quarantined ${tier} cases exceed the ${pct(policy.quarantine.capFraction)} cap (${cap} of ${size})` }); } return problems; } export function analyzePassRates(records: TrialRecord[], options: AnalyzeOptions = {}): PassRateReport { const registry = options.registry ?? LIVE_REGISTRY; const quarantine = options.quarantine ?? CASE_QUARANTINE; const policy = options.policy ?? EVAL_POLICY; const now = options.now ?? Date.now(); const newerPolicyTrials = records.filter(record => record.policy_version > policy.version).length; const byCase = new Map(); for (const record of records) { if (record.policy_version > policy.version) continue; const list = byCase.get(record.case) ?? []; list.push(record); byCase.set(record.case, list); } for (const id of options.manualReviews ?? []) if (!byCase.has(id)) byCase.set(id, []); const cases: CasePassRate[] = []; for (const [id, list] of [...byCase].sort(([a], [b]) => a.localeCompare(b))) { list.sort((a, b) => at(a).localeCompare(at(b)) || runOf(a).localeCompare(runOf(b)) || a.trial - b.trial); const groups = new Map(); const keyOf = (record: TrialRecord) => record.policy_version === 0 ? 'pre-policy' : [record.series_identity ?? 'unknown', record.model ?? 'unknown', record.cli_version ?? 'unknown', `v${record.policy_version}`].join('|'); const pooled = (record: TrialRecord) => !!options.pooledRunIds?.has(record.run_id ?? ''); const mainKeys = new Set(list.filter(record => !pooled(record)).map(keyOf)); for (const record of list) { const key = keyOf(record); if (pooled(record) && !mainKeys.has(key)) continue; const group = groups.get(key) ?? []; group.push(record); groups.set(key, group); } const series = [...groups].map(([key, group]) => seriesStats(key, group)) .sort((a, b) => a.lastSeen.localeCompare(b.lastSeen)); const post = series.filter(entry => entry.policyVersion === policy.version); const current = post[post.length - 1] ?? null; const previous = post[post.length - 2] ?? null; const scored = current ? groups.get(current.key)!.filter(record => record.outcome !== 'skipped') : []; const latestRun = scored.length ? runOf(scored[scored.length - 1]!) : null; const latest = scored.filter(record => runOf(record) === latestRun); const prior = scored.filter(record => runOf(record) !== latestRun); const priorPasses = prior.filter(record => record.outcome === 'passed').length; const entryRate = policy.quarantine.entry.rate; let label: PassRateLabel; if (latest.length > 0 && latest.every(record => record.outcome === 'failed') && prior.length > 0 && wilsonInterval(priorPasses, prior.length).lo >= entryRate) label = 'BROKEN'; else if (!current || current.trials < policy.quarantine.entry.minTrials) label = 'INCONCLUSIVE'; else if (current.interval.hi < entryRate) label = 'FAILING'; else if (current.passes < current.trials && current.interval.lo < entryRate) label = 'FLAKY'; else label = 'PASSING'; cases.push({ case: id, kind: registry.kinds[id] ?? list[0]!.kind, tier: caseTier(id, registry), quarantined: id in quarantine, label, current, previous, manualReviews: (options.manualReviews ?? []).filter(name => name === id).length, prePolicy: series.find(entry => entry.policyVersion === 0) ?? null, series, latestRun: latestRun ? { runId: latestRun, passes: latest.filter(record => record.outcome === 'passed').length, trials: latest.length } : null, }); } const alarms: Alarm[] = []; const rate = (stats: SeriesStats) => stats.passes / stats.trials; for (const entry of cases) { const current = entry.current; if (!current) continue; const below = current.trials >= policy.quarantine.entry.minTrials && rate(current) < policy.quarantine.entry.rate; if (below && !entry.quarantined && BLOCKING_TIERS.includes(entry.tier)) alarms.push({ kind: 'drift', case: entry.case, message: `${entry.case} passes ${current.passes}/${current.trials} (below ${pct(policy.quarantine.entry.rate)} over >= ${policy.quarantine.entry.minTrials} trials): fix it, or propose a CASE_QUARANTINE entry with a written diagnosis (product defects are never quarantined)` }); if (below && entry.kind === 'rule') alarms.push({ kind: 'rule-as-behavior', case: entry.case, message: `${entry.case}: rule case behaving like behavior (${current.passes}/${current.trials}): fix or reclassify` }); if (entry.quarantined && current.trials >= policy.quarantine.exit.minTrials && rate(current) >= policy.quarantine.exit.rate) { alarms.push({ kind: 'quarantine-exit', case: entry.case, message: `${entry.case} passes ${current.passes}/${current.trials} (>= ${pct(policy.quarantine.exit.rate)}): remove its CASE_QUARANTINE entry` }); } } const tested = cases.filter(entry => BLOCKING_TIERS.includes(entry.tier) && entry.current && entry.previous && entry.current.trials >= policy.drift.fisherMinPerSide && entry.previous.trials >= policy.drift.fisherMinPerSide); const pValues = tested.map(entry => fisherOneSidedLower(entry.current!.passes, entry.current!.trials, entry.previous!.passes, entry.previous!.trials)); for (const index of holmRejections(pValues, policy.drift.fisherAlpha)) { const entry = tested[index]!; alarms.push({ kind: 'regression', case: entry.case, message: `${entry.case}: current identity ${entry.current!.passes}/${entry.current!.trials} is significantly below the previous ${entry.previous!.passes}/${entry.previous!.trials} (one-sided Fisher p=${pValues[index]!.toFixed(4)}, Holm over ${tested.length} cases)` }); } for (const [id, entry] of Object.entries(quarantine)) { const runs = quarantineRunsSince(entry.enteredAt, options.weeklyRuns, now); if (runs >= policy.quarantine.expiryWeeklyRuns) alarms.push({ kind: 'quarantine-expired', case: id, message: `${id}: entered ${runs} weekly runs ago (limit ${policy.quarantine.expiryWeeklyRuns}): fix it, name it as a red, or re-diagnose with fresh evidence` }); } alarms.push(...quarantinePolicyProblems(quarantine, registry, policy, now)); const count = (match: (version: number) => boolean) => records.filter(record => match(record.policy_version)).length; return { policyVersion: policy.version, cases, alarms, postPolicyTrials: count(version => version === policy.version), prePolicyTrials: count(version => version === 0), olderPolicyTrials: count(version => version > 0 && version < policy.version), newerPolicyTrials, unattributed: options.unattributed ?? [], errors: options.errors ?? [] }; } function pct(value: number): string { return `${Math.round(value * 1000) / 10}%`; } function formatStats(stats: SeriesStats | null): string { if (!stats) return '-'; return `${stats.passes}/${stats.trials} [${pct(stats.interval.lo)}–${pct(stats.interval.hi)}]${stats.infra ? ` (${stats.infra} infra)` : ''}`; } export function formatPassRates(report: PassRateReport, options: { caseFilter?: string } = {}): string { const lines: string[] = []; lines.push(`pass-rates: policy v${report.policyVersion}, ${report.postPolicyTrials} post-policy trial(s), ${report.prePolicyTrials} pre-policy (display only)` + (report.olderPolicyTrials ? `, ${report.olderPolicyTrials} under an older policy (display only)` : '')); if (report.newerPolicyTrials) { lines.push(` ignored ${report.newerPolicyTrials} trial(s) recorded under a policy newer than v${report.policyVersion}: this checkout predates them. ` + 'Fix: run pass-rates from a checkout at or after the commit that bumped EVAL_POLICY.version (docs/TESTING_INTERNALS.md#pass-rate-policy-versions).'); } if (report.postPolicyTrials === 0) lines.push(' no post-policy trials yet: every series starts INCONCLUSIVE'); const cases = report.cases.filter(entry => !options.caseFilter || entry.case === options.caseFilter); lines.push(' label kind tier current series pre-policy manual case'); for (const entry of cases) { const group = entry.current ? ` ${entry.current.model} / ${entry.current.cli}` : ''; const reset = entry.previous ? ' (baseline reset)' : ''; lines.push(` ${entry.label.padEnd(12)} ${entry.kind.padEnd(8)} ${entry.tier.padEnd(8)} ${formatStats(entry.current).padEnd(29)} ` + `${formatStats(entry.prePolicy).padEnd(18)} ${String(entry.manualReviews).padStart(6)} ${entry.case}${entry.quarantined ? ' [quarantined]' : ''}${group}${reset}`); } if (report.unattributed.length) lines.push(` unattributed records (${report.unattributed.length}, never guessed): ${report.unattributed.slice(0, 20).join(', ')}`); if (report.errors.length) lines.push(` rejected ${report.errors.length} invalid trial line(s): ${report.errors.slice(0, 5).join('; ')}`); if (report.alarms.length) { lines.push(`ACTION REQUIRED (${report.alarms.length}):`); for (const alarm of report.alarms) lines.push(` [${alarm.kind}] ${alarm.message}`); } return lines.join('\n'); } export interface PassRateHistory { records: TrialRecord[]; unattributed: string[]; errors: string[]; manualReviews: string[]; weeklyRuns?: string[]; fetched: WeeklyRun[]; pooledRunIds?: Set; historyError: string | null; scope: string; cacheDir: string; } /** * The trial history every eval:pass-rates view reads (and the ship-measure * sweep ranks from): local dirs, or the trial-outcomes artifacts of the last * N weekly runs on the branch plus the branch census runs that pool into * main's series. A fetch failure is returned as historyError, never thrown. */ export function loadPassRateHistory(o: { repo: string; workflow: string; branch: string; runsLimit: number; dirs: string[]; sinceDays: number; backfill?: boolean; runId?: number }): PassRateHistory { const records: TrialRecord[] = []; const unattributed = new Set(); const errors: string[] = []; const manualReviews: string[] = []; let historyError: string | null = null; let weeklyRuns: string[] | undefined; let fetched: WeeklyRun[] = []; let pooledRunIds: Set | undefined; const cacheDir = path.join(path.resolve(resolveStateRoot()), 'eval-pass-rates-cache', o.repo.replace('/', '-')); const importDir = (dir: string, run: { run_id: string; sha?: string; timestamp?: string } | undefined, legacyDays: number) => { const trials = readTrialOutcomeDir(dir); records.push(...trials.records); errors.push(...trials.errors); const legacy = backfillEvalFiles(collectEvalFiles(dir, legacyDays), run); records.push(...legacy.records); manualReviews.push(...legacy.manualReviews); legacy.unattributed.forEach(name => unattributed.add(name)); }; if (o.dirs.length) { for (const dir of o.dirs) importDir(dir, undefined, o.sinceDays); } else { try { const weekly = listWeeklyRuns({ repo: o.repo, workflow: o.workflow, branches: [o.branch], limit: o.runsLimit }).filter(run => o.branch !== 'main' || isWeeklyHistoryRun(run)); weeklyRuns = weekly.map(run => run.createdAt); // Branch census trials pool into main's matching series over the same window (isPooledTrialRun). const oldest = weekly[weekly.length - 1]?.createdAt; const pooledRuns = o.branch === 'main' && oldest ? listWeeklyRuns({ repo: o.repo, workflow: o.workflow, branches: [''], limit: 100 }).filter(run => isPooledTrialRun(run) && run.createdAt >= oldest) : []; pooledRunIds = new Set(pooledRuns.map(run => `${run.id}`)); const runs = [...weekly, ...pooledRuns]; if (o.runId !== undefined && !runs.some(run => run.id === o.runId)) runs.push(GH_JOBS.getRun(o.repo, o.runId)); fetched = runs; const match = o.backfill ? (name: string) => name.startsWith('trial-outcomes') || /^(paid-slice-\d+|gate-census-\d+)(-a\d+)?$/.test(name) : (name: string) => name.startsWith('trial-outcomes'); for (const run of runs) { const dirsForRun = downloadRunArtifacts({ repo: o.repo, run, match, cacheDir, maxBytes: o.backfill ? 64 * 1024 * 1024 : undefined }); for (const dir of dirsForRun) importDir(dir, { run_id: `${run.id}`, sha: run.sha, timestamp: run.createdAt }, 3650); } } catch (error) { historyError = error instanceof Error ? error.message : String(error); } } const scope = o.dirs.length ? `local dirs ${o.dirs.join(', ')}` : `${o.repo} ${o.workflow} on ${o.branch}${pooledRunIds?.size ? ` + ${pooledRunIds.size} pooled branch census run(s)` : ''}, ` + `last ${o.runsLimit} completed run(s): ${fetched.map(run => run.id).join(', ') || 'none'}`; return { records, unattributed: [...unattributed].sort(), errors, manualReviews, weeklyRuns, fetched, pooledRunIds, historyError, scope, cacheDir }; } // --- GitHub history (shared reader: scripts/lib/ci-history.ts) --- export { downloadRunArtifacts, GH_HISTORY, listWeeklyRuns, TRIAL_OUTCOMES_MAX_BYTES, type HistoryFetcher, type RunArtifact, type WeeklyRun } from './lib/ci-history'; if (import.meta.main) { const parsed = parsePassRatesArgs(process.argv.slice(2), id => Object.hasOwn(E2E_TIERS, id) || Object.hasOwn(LLM_JUDGE_TOUCHFILES, id) || (/^test\/[\w./-]+\.test\.ts$/.test(id) && fs.existsSync(id))); if ('help' in parsed) { console.log(PASS_RATES_USAGE); process.exit(0); } if ('error' in parsed) { console.error(`eval:pass-rates: ${parsed.error}\n${PASS_RATES_USAGE}`); process.exit(2); } const { dirs, json: asJson, gate, backfill, caseFilter, runs: runsLimit, sinceDays, view } = parsed; const repo = parsed.repo ?? repoSlug(); const workflow = parsed.workflow ?? 'evals-periodic.yml'; const branch = parsed.branch ?? 'main'; const history = loadPassRateHistory({ repo, workflow, branch, runsLimit, dirs, sinceDays, backfill, runId: parsed.runId }); const { records, errors, manualReviews, weeklyRuns, fetched, pooledRunIds, historyError, scope, cacheDir } = history; const unattributed = new Set(history.unattributed); const scoped = caseFilter ? records.filter(record => record.case === caseFilter) : records; const caseHeadroom = headroom(scoped); if (view !== 'rates') { const critical: CriticalPath[] = []; if (view !== 'reds' && !dirs.length) { for (const run of fetched.filter(run => view === 'headroom' || run.id === parsed.runId)) { try { critical.push(criticalPath(String(run.id), GH_JOBS.listJobs(repo, run.id))); } catch { critical.push({ run: String(run.id), jobs: 0, slowest: null, wallMs: null }); } } } const lines = view === 'headroom' ? formatHeadroom(caseHeadroom, critical) : view === 'reds' ? formatRedLedger(redLedger(scoped)) : historyError ? [] : triageRun({ repo, run: fetched.find(run => run.id === parsed.runId)!, records, caseFilter, cacheDir, fetcher: GH_HISTORY, download: (match, maxBytes) => downloadRunArtifacts({ repo, run: fetched.find(run => run.id === parsed.runId)!, match, cacheDir, maxBytes }) }) .concat(formatCriticalPath(critical), historyError ? [] : [`guide: ${CENSUS_RED_GUIDE}`]); if (asJson) console.log(JSON.stringify({ scope, historyError, view, lines }, null, 2)); else { console.log(`scope: ${scope}`); if (historyError) console.log(`history unavailable (${historyError})`); for (const line of lines) console.log(line); } process.exit(historyError ? 1 : 0); } const full = analyzePassRates(records, { weeklyRuns, pooledRunIds, unattributed: [...unattributed].sort(), errors, manualReviews }); const alarms = [...full.alarms, ...(gate ? headroomAlarms(caseHeadroom) : [])].filter(alarm => !caseFilter || alarm.case === caseFilter); const report = { ...full, cases: full.cases.filter(entry => !caseFilter || entry.case === caseFilter), alarms }; const ledger = readFreeLedger(); if (asJson) { console.log(JSON.stringify({ repo, workflow, branch, scope, dirs, historyError, ...report, freeLedger: ledger }, null, 2)); } else { console.log(`scope: ${scope}`); if (historyError) console.log(`pass-rates: history unavailable (${historyError}); every label below is INCONCLUSIVE`); console.log(formatPassRates(report, { caseFilter })); if (ledger.length > 0) { const byFile = new Map(); for (const e of ledger) byFile.set(e.file, (byFile.get(e.file) ?? 0) + 1); console.log(`free-suite flaky-passes (${flakeLedgerPath()}):`); for (const [file, n] of [...byFile.entries()].sort((a, b) => b[1] - a[1])) { console.log(` ${String(n).padStart(3)}x ${file}`); } } } if (gate && (historyError || report.alarms.length)) process.exit(1); }