mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
* v1.91.23.0 feat(cso): private two-architecture release scoring and self-checking runtime release gates
* Keep the Linux-only native-evidence and requalification tests out of the Windows lane
* Skip the two Linux-release-only checks on Windows in-file instead of growing the shard list
* fix(cso): opaque producer inputs and receipts; requalification triggers independent of cwd
Producer inputs are {schemaVersion: 2, cellRef, skill, source, execution} and
receipts bind cellRef + inputHash; collection rebuilds the cell from the
matrix, so a cell VM never learns its case or variant. requalificationTriggers
resolves Bun's cwd-relative metafile inputs against the repository root.
* test(evals): bound the review judge's thinking; guarded browser cleanup is not a probe
review/SKILL.md workflow: the ~64k-token bundle let default-effort thinking
spend all 8192 output tokens without an answer in CI. Medium effort with a
64k streaming cap kept 24/24 measured samples under 5k tokens and 63 s with
the same gating scores (as ship already does).
qa-only-no-fix: the fixture prompt routes browser cleanup through the deadline
guard, but the checkpoint detector treated every guarded run as a probe that
needs a checkpoint. A child that only closes the tab with the supplied browse
binary, after the last probe, is cleanup; anything else stays a probe. The
captured CI transcript (run 37252022510) now passes and new scenarios pin
the negative controls.
* test(evals): keep the review judge at default effort; only lift its output cap
Truncation was the defect: thinking and the answer share max_tokens, and the
~64k-token review bundle once spent all 8192. The registration keeps default
effort and response format and gets ship's 64k streaming cap; the guard test
pins exactly that.
125 lines
6.4 KiB
TypeScript
125 lines
6.4 KiB
TypeScript
#!/usr/bin/env bun
|
|
/** Bind a qualification dispatch to the one successful protected-main staging run that signed its images. */
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { canonical } from '../lib/cso/contracts';
|
|
import { committedImageBuildMatrix } from './cso-image-matrix';
|
|
|
|
export const STAGING_WORKFLOW_PATH = '.github/workflows/cso-runtime-images.yml';
|
|
export const NATIVE_JOB = 'qualify-native';
|
|
const SLSA_PROVENANCE = 'https://slsa.dev/provenance/v1';
|
|
const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
const INVOCATION = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/actions\/runs\/([1-9][0-9]*)\/attempts\/[1-9][0-9]*$/;
|
|
const MAX_FILE_BYTES = 4 * 1024 * 1024;
|
|
|
|
export interface StagingRunEvidence {
|
|
repository: string;
|
|
runId: string;
|
|
sourceCommit: string;
|
|
runtimeIds: string[];
|
|
run: unknown;
|
|
jobPages: unknown;
|
|
}
|
|
|
|
/**
|
|
* Resolve the staging run from verified SLSA provenance. The predicate's
|
|
* invocation and the Fulcio certificate's run URI must agree, and every
|
|
* verified statement for every image must name the same run.
|
|
*/
|
|
export function stagingRunId(repository: string, verifications: unknown[]): string {
|
|
if (!REPOSITORY.test(repository) || verifications.length === 0) throw new Error('INVALID_STAGING_PROVENANCE');
|
|
const runs = new Set<string>();
|
|
for (const verification of verifications) {
|
|
if (!Array.isArray(verification) || verification.length === 0) throw new Error('INVALID_STAGING_PROVENANCE');
|
|
for (const item of verification) {
|
|
const result = (item as any)?.verificationResult;
|
|
if (result?.statement?.predicateType !== SLSA_PROVENANCE) throw new Error('INVALID_STAGING_PROVENANCE');
|
|
const invocation = result.statement.predicate?.runDetails?.metadata?.invocationId;
|
|
const match = typeof invocation === 'string' ? INVOCATION.exec(invocation) : null;
|
|
if (!match || match[1] !== repository || result.signature?.certificate?.runInvocationURI !== invocation) {
|
|
throw new Error('STAGING_INVOCATION_MISMATCH');
|
|
}
|
|
runs.add(match[2]);
|
|
}
|
|
}
|
|
if (runs.size !== 1) throw new Error('STAGING_RUN_NOT_UNIQUE');
|
|
return [...runs][0];
|
|
}
|
|
|
|
/** Every statement in one qualification dispatch names the same staged source commit. */
|
|
export function dispatchSourceCommit(event: unknown): string {
|
|
const statements = (event as any)?.client_payload?.statements;
|
|
if (!Array.isArray(statements) || statements.length === 0) throw new Error('INVALID_QUALIFICATION_DISPATCH');
|
|
const commits = new Set(statements.map((statement: any) => statement?.sourceCommit));
|
|
const [commit] = [...commits];
|
|
if (commits.size !== 1 || typeof commit !== 'string' || !/^[a-f0-9]{40}$/.test(commit)) throw new Error('STAGING_SOURCE_COMMIT_MISMATCH');
|
|
return commit;
|
|
}
|
|
|
|
/** The resolved run must be the protected-main staging dispatch at the statements' commit, with every native gate passed. */
|
|
export function verifyStagingRun(evidence: StagingRunEvidence): void {
|
|
const { repository, runId, sourceCommit, runtimeIds } = evidence;
|
|
const run = evidence.run as any;
|
|
if (!run || typeof run !== 'object' || String(run.id) !== runId) throw new Error('STAGING_RUN_ID_MISMATCH');
|
|
if (run.path !== STAGING_WORKFLOW_PATH) throw new Error('STAGING_WORKFLOW_MISMATCH');
|
|
if (run.repository?.full_name !== repository || run.head_repository?.full_name !== repository) throw new Error('STAGING_REPOSITORY_MISMATCH');
|
|
if (run.event !== 'workflow_dispatch' || run.head_branch !== 'main') throw new Error('STAGING_RUN_NOT_PROTECTED_MAIN_DISPATCH');
|
|
if (run.status !== 'completed' || run.conclusion !== 'success') throw new Error('STAGING_RUN_NOT_SUCCESSFUL');
|
|
if (run.head_sha !== sourceCommit) throw new Error('STAGING_SOURCE_COMMIT_MISMATCH');
|
|
|
|
const pages = evidence.jobPages;
|
|
if (!Array.isArray(pages) || pages.length === 0) throw new Error('INVALID_STAGING_JOBS');
|
|
const totals = new Set(pages.map((page: any) => page?.total_count));
|
|
if (pages.some((page: any) => !Array.isArray(page?.jobs)) || totals.size !== 1) throw new Error('INVALID_STAGING_JOBS');
|
|
const jobs = pages.flatMap((page: any) => page.jobs);
|
|
if (jobs.length !== [...totals][0]) throw new Error('INCOMPLETE_STAGING_JOBS');
|
|
|
|
const native = jobs.filter((job: any) => typeof job?.name === 'string' && job.name.startsWith(NATIVE_JOB));
|
|
const expected = runtimeIds.map(id => `${NATIVE_JOB} ${id}`).sort();
|
|
if (runtimeIds.length === 0 || canonical(native.map((job: any) => job.name).sort()) !== canonical(expected)) {
|
|
throw new Error('STAGING_NATIVE_JOBS_MISMATCH');
|
|
}
|
|
if (native.some((job: any) => String(job.run_id) !== runId || job.status !== 'completed' || job.conclusion !== 'success')) {
|
|
throw new Error('STAGING_NATIVE_GATE_NOT_PASSED');
|
|
}
|
|
}
|
|
|
|
function readJson(file: string): unknown {
|
|
const absolute = path.resolve(file);
|
|
const stat = fs.lstatSync(absolute);
|
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size <= 0 || stat.size > MAX_FILE_BYTES) throw new Error('UNSAFE_STAGING_EVIDENCE_FILE');
|
|
try { return JSON.parse(fs.readFileSync(absolute, 'utf8')); } catch { throw new Error('INVALID_STAGING_EVIDENCE_FILE'); }
|
|
}
|
|
|
|
function option(args: string[], name: string): string {
|
|
const index = args.indexOf(name);
|
|
const value = index >= 0 ? args[index + 1] : undefined;
|
|
if (!value || value.startsWith('--')) throw new Error(`Missing ${name}`);
|
|
return value;
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
try {
|
|
const [command, ...args] = process.argv.slice(2);
|
|
if (command === 'run-id' && args[0] === '--repository' && args.length >= 3) {
|
|
process.stdout.write(`${stagingRunId(args[1], args.slice(2).map(readJson))}\n`);
|
|
} else if (command === 'verify' && args.length === 10) {
|
|
const event = readJson(option(args, '--event'));
|
|
verifyStagingRun({
|
|
repository: option(args, '--repository'),
|
|
runId: option(args, '--run-id'),
|
|
sourceCommit: dispatchSourceCommit(event),
|
|
runtimeIds: committedImageBuildMatrix().include.map(row => row.runtimeId),
|
|
run: readJson(option(args, '--run')),
|
|
jobPages: readJson(option(args, '--jobs')),
|
|
});
|
|
process.stdout.write('STAGING RUN VERIFIED\n');
|
|
} else {
|
|
throw new Error('Usage: cso-staging-run.ts run-id --repository OWNER/REPO PROVENANCE.json... | verify --repository OWNER/REPO --run-id ID --event EVENT.json --run RUN.json --jobs JOB-PAGES.json');
|
|
}
|
|
} catch (error) {
|
|
process.stderr.write((error instanceof Error ? error.message : 'STAGING_RUN_VERIFICATION_FAILED') + '\n');
|
|
process.exitCode = 1;
|
|
}
|
|
}
|