Files
gstack/lib/cso/runtime-catalog.ts
T
garrytan 42e53413da feat: require a zero-error product typecheck and a test type-debt ratchet
Adds tsconfig.json (strict) over product code, fixes its remaining 90
diagnostics (type-only, interface corrections, and explicit narrowing),
and adds a typecheck job to the required free-tests aggregate running
bun run typecheck, the test-code ratchet (identity -> count baseline, fails
on new, repeated, or unlocked fixed diagnostics), and the lib/cso format
check. Reuses fixes from #2447 where they still applied.
2026-09-29 14:34:02 +00:00

358 lines
13 KiB
TypeScript

/** A runtime becomes executable only after trusted CI qualification and catalog review. */
import committedCatalog from './runtime-catalog.json';
import type { CsoStack, PreparationPlan } from './preparation';
import { CsoError, canonical, sha256 } from './contracts';
export const CSO_HELPER_ABI = 3;
export type RuntimePlatform = 'linux/amd64' | 'linux/arm64';
interface RuntimeQualificationProvenance {
sourceCommit: string;
workflow: string;
sbomDigest: string;
provenanceDigest: string;
verifiedProvenance: true;
}
export type RuntimeQualification = RuntimeQualificationProvenance &
(
| {
kind: 'application';
containmentPassed: true;
coldStartPassed: true;
positiveNegativeAssertionsPassed: true;
heldOutRepairPassed: true;
}
| {
kind: 'postgresql';
containmentPassed: true;
coldStartPassed: true;
multiDatabasePassed: true;
readinessPassed: true;
}
);
export interface QualifiedRuntime {
id: string;
stack: CsoStack | 'postgresql';
platform: RuntimePlatform;
state: 'qualified';
image: string;
entrypoint: '/opt/cso/entrypoint';
helperAbi: number;
versions: Record<string, string>;
policyVersion: 'cso-isolation-v1';
qualifiedAt: string;
qualification: RuntimeQualification;
}
/** Reviewed build metadata is informative. It never makes an image executable. */
export interface ReviewedRuntimeProfile {
id: string;
stack: CsoStack | 'postgresql';
platform: RuntimePlatform;
state: 'build_reviewed';
versions: Record<string, string>;
reviewedAt: string;
}
export interface RuntimeCatalog {
schemaVersion: 1;
revision: string;
previousRevision: string | null;
helperAbi: number;
buildRevision: string;
profiles: ReviewedRuntimeProfile[];
promotion?: {
sourceCommit: string;
workflow: string;
/** Canonical digest of the retained executable runtime matrix. */
evidenceDigest: string;
/** Canonical digest of the complete release-gate statements retained externally. */
qualificationEvidenceDigest: string;
};
runtimes: QualifiedRuntime[];
}
const DIGEST = /^sha256:[a-f0-9]{64}$/;
const IMAGE = /^(?:[a-z0-9.-]+(?::[0-9]+)?\/)?[a-z0-9][a-z0-9._/-]*@sha256:[a-f0-9]{64}$/;
const ID = /^[a-z0-9][a-z0-9._-]{0,100}$/;
const BUILD_REVISION = /^[a-z0-9][a-z0-9._-]{0,100}$/;
const STACKS = ['node', 'bun', 'python', 'rails', 'postgresql'] as const;
const PLATFORMS = ['linux/amd64', 'linux/arm64'] as const;
const QUALIFICATION_WORKFLOW = /^https:\/\/github\.com\/garrytan\/gstack\/actions\/runs\/[0-9]+$/;
const REQUIRED: Record<string, string[]> = {
node: ['node', 'npm', 'cso-preparation'],
bun: ['bun', 'cso-preparation'],
python: ['python', 'uv', 'cso-preparation'],
rails: ['ruby', 'bundler', 'cso-preparation'],
postgresql: ['postgresql'],
};
function versionsKey(versions: Record<string, string>): string {
return JSON.stringify(Object.entries(versions).sort(([a], [b]) => a.localeCompare(b)));
}
function validateRuntimeIdentity(value: {
id: string;
stack: string;
platform: string;
versions: Record<string, string>;
}): void {
if (typeof value.id !== 'string' || !ID.test(value.id)) throw new Error('INVALID_RUNTIME_ID');
if (
!STACKS.includes(value.stack as (typeof STACKS)[number]) ||
!PLATFORMS.includes(value.platform as RuntimePlatform)
)
throw new Error('UNSUPPORTED_RUNTIME_PLATFORM');
if (
!value.versions ||
typeof value.versions !== 'object' ||
Array.isArray(value.versions) ||
!Object.keys(value.versions).length ||
Object.values(value.versions).some(
(version) => typeof version !== 'string' || !/^[0-9][a-zA-Z0-9.+_-]*$/.test(version),
)
)
throw new Error('UNPINNED_RUNTIME_VERSION');
if (Object.keys(value.versions).sort().join(',') !== [...REQUIRED[value.stack]].sort().join(','))
throw new Error('MISSING_RUNTIME_TOOL_VERSION');
if (
['node', 'bun', 'python', 'rails'].includes(value.stack) &&
value.versions['cso-preparation'] !== '1.0.0'
)
throw new Error('INCOMPATIBLE_PREPARATION_HELPER');
}
export function validateRuntimeCatalog(value: unknown): asserts value is RuntimeCatalog {
const catalog = value as RuntimeCatalog;
if (
!catalog ||
catalog.schemaVersion !== 1 ||
catalog.helperAbi !== CSO_HELPER_ABI ||
typeof catalog.revision !== 'string' ||
!BUILD_REVISION.test(catalog.revision) ||
!Array.isArray(catalog.runtimes)
)
throw new Error('INCOMPATIBLE_RUNTIME_CATALOG');
if (
catalog.previousRevision !== null &&
(typeof catalog.previousRevision !== 'string' || !BUILD_REVISION.test(catalog.previousRevision))
)
throw new Error('INVALID_RUNTIME_CATALOG');
if (
!Array.isArray(catalog.profiles) ||
catalog.profiles.length !== STACKS.length * PLATFORMS.length ||
typeof catalog.buildRevision !== 'string' ||
!BUILD_REVISION.test(catalog.buildRevision)
)
throw new Error('INVALID_REVIEWED_RUNTIME_PROFILES');
const profiles = new Map<string, ReviewedRuntimeProfile>(),
profileIdentities = new Set<string>();
for (const profile of catalog.profiles) {
validateRuntimeIdentity(profile);
const identity = `${profile.stack}:${profile.platform}`;
if (
profiles.has(profile.id) ||
profileIdentities.has(identity) ||
profile.state !== 'build_reviewed' ||
!Number.isFinite(Date.parse(profile.reviewedAt))
)
throw new Error('INVALID_REVIEWED_RUNTIME_PROFILE');
profiles.set(profile.id, profile);
profileIdentities.add(identity);
}
for (const stack of STACKS)
for (const platform of PLATFORMS) {
if (!profileIdentities.has(`${stack}:${platform}`))
throw new Error('INCOMPLETE_REVIEWED_RUNTIME_MATRIX');
}
if (catalog.promotion !== undefined) {
if (
!/^[a-f0-9]{40}$/.test(catalog.promotion.sourceCommit) ||
!QUALIFICATION_WORKFLOW.test(catalog.promotion.workflow) ||
!DIGEST.test(catalog.promotion.evidenceDigest) ||
!DIGEST.test(catalog.promotion.qualificationEvidenceDigest) ||
Object.keys(catalog.promotion).sort().join(',') !==
['evidenceDigest', 'qualificationEvidenceDigest', 'sourceCommit', 'workflow'].sort().join(',')
) {
throw new Error('INVALID_RUNTIME_PROMOTION');
}
}
if (catalog.runtimes.length !== 0 && catalog.runtimes.length !== STACKS.length * PLATFORMS.length)
throw new Error('INCOMPLETE_QUALIFIED_RUNTIME_MATRIX');
const ids = new Set<string>();
const runtimeIdentities = new Set<string>();
for (const runtime of catalog.runtimes) {
const qualification = runtime?.qualification;
if (!runtime) throw new Error('INVALID_RUNTIME_ID');
validateRuntimeIdentity(runtime);
const identity = `${runtime.stack}:${runtime.platform}`;
if (ids.has(runtime.id) || runtimeIdentities.has(identity)) throw new Error('INVALID_RUNTIME_ID');
ids.add(runtime.id);
runtimeIdentities.add(identity);
const arch = runtime.platform === 'linux/amd64' ? 'amd64' : 'arm64';
const expectedImage = new RegExp(
`^ghcr\\.io/garrytan/gstack/cso-staging/${runtime.stack}-${arch}@sha256:[a-f0-9]{64}$`,
);
if (
runtime.state !== 'qualified' ||
!IMAGE.test(runtime.image) ||
!expectedImage.test(runtime.image) ||
runtime.entrypoint !== '/opt/cso/entrypoint' ||
runtime.helperAbi !== CSO_HELPER_ABI ||
runtime.policyVersion !== 'cso-isolation-v1'
)
throw new Error('UNQUALIFIED_RUNTIME');
const reviewed = profiles.get(runtime.id);
if (
!reviewed ||
reviewed.stack !== runtime.stack ||
reviewed.platform !== runtime.platform ||
versionsKey(reviewed.versions) !== versionsKey(runtime.versions)
)
throw new Error('RUNTIME_BUILD_PROFILE_MISMATCH');
if (
!qualification ||
!/^[a-f0-9]{40}$/.test(qualification.sourceCommit) ||
!QUALIFICATION_WORKFLOW.test(qualification.workflow) ||
!DIGEST.test(qualification.sbomDigest) ||
!DIGEST.test(qualification.provenanceDigest) ||
qualification.verifiedProvenance !== true ||
!Number.isFinite(Date.parse(runtime.qualifiedAt))
)
throw new Error('MISSING_RUNTIME_QUALIFICATION');
const keys = Object.keys(qualification).sort();
const common = [
'kind',
'sourceCommit',
'workflow',
'sbomDigest',
'provenanceDigest',
'verifiedProvenance',
];
if (['node', 'bun', 'python', 'rails'].includes(runtime.stack)) {
if (
qualification.kind !== 'application' ||
qualification.containmentPassed !== true ||
qualification.coldStartPassed !== true ||
qualification.positiveNegativeAssertionsPassed !== true ||
qualification.heldOutRepairPassed !== true ||
keys.join(',') !==
[
...common,
'containmentPassed',
'coldStartPassed',
'positiveNegativeAssertionsPassed',
'heldOutRepairPassed',
]
.sort()
.join(',')
)
throw new Error('MISSING_APPLICATION_QUALIFICATION');
} else {
if (
qualification.kind !== 'postgresql' ||
qualification.containmentPassed !== true ||
qualification.coldStartPassed !== true ||
qualification.multiDatabasePassed !== true ||
qualification.readinessPassed !== true ||
keys.join(',') !==
[...common, 'containmentPassed', 'coldStartPassed', 'multiDatabasePassed', 'readinessPassed']
.sort()
.join(',')
)
throw new Error('MISSING_POSTGRESQL_QUALIFICATION');
}
}
if (catalog.runtimes.length > 0) {
for (const identity of profileIdentities)
if (!runtimeIdentities.has(identity)) throw new Error('INCOMPLETE_QUALIFIED_RUNTIME_MATRIX');
if (!catalog.promotion) throw new Error('MISSING_RUNTIME_PROMOTION');
if (
catalog.runtimes.some(
(runtime) =>
runtime.qualification.sourceCommit !== catalog.promotion!.sourceCommit ||
runtime.qualification.workflow !== catalog.promotion!.workflow,
)
)
throw new Error('RUNTIME_PROMOTION_MISMATCH');
if (catalog.promotion.evidenceDigest !== `sha256:${sha256(canonical(catalog.runtimes))}`) {
throw new Error('RUNTIME_PROMOTION_EVIDENCE_MISMATCH');
}
} else if (catalog.promotion) throw new Error('INVALID_RUNTIME_PROMOTION');
}
const committed: unknown = committedCatalog;
validateRuntimeCatalog(committed);
export const RUNTIME_CATALOG: RuntimeCatalog = committed;
export function assertRuntimeCompatible(plan: PreparationPlan, runtime: QualifiedRuntime): void {
if (plan.schemaVersion !== 1 || plan.status !== 'ready' || runtime.stack !== plan.stack)
throw new CsoError(
'INCOMPATIBLE_INPUT',
`Prepared ${plan.stack} source cannot run in ${runtime.stack} runtime ${runtime.id}`,
);
for (const [declared, rawRange] of Object.entries(plan.runtimeRequirements)) {
if (!rawRange) continue;
let tool = declared,
range = rawRange;
if (declared === 'packageManager') {
const match = rawRange.match(/^([a-z][a-z0-9_-]*)@(.+)$/i);
if (!match)
throw new CsoError('PREREQUISITE', 'Package manager declaration must bind a named version range');
tool = match[1];
range = match[2];
}
const version = runtime.versions[tool];
if (!version)
throw new CsoError(
'PREREQUISITE',
`Qualified runtime ${runtime.id} does not declare a real ${tool} release`,
);
let satisfies = false;
try {
satisfies = Bun.semver.satisfies(version.replace(/^v/, ''), range);
} catch {}
if (!satisfies)
throw new CsoError(
'PREREQUISITE',
`Qualified ${tool} ${version} does not satisfy source requirement ${range}`,
);
}
}
export function selectRuntime(
profile: string,
platform: RuntimePlatform,
catalog: RuntimeCatalog = RUNTIME_CATALOG,
): QualifiedRuntime {
validateRuntimeCatalog(catalog);
const matches = catalog.runtimes.filter(
(runtime) => runtime.platform === platform && (runtime.id === profile || runtime.stack === profile),
);
if (matches.length === 0) {
const reviewed =
catalog.profiles?.filter(
(item) => item.platform === platform && (item.id === profile || item.stack === profile),
) ?? [];
const detail =
reviewed.length === 1
? ` Reviewed build profile ${reviewed[0].id} is awaiting a qualified image promotion.`
: '';
throw new Error(
`MISSING_QUALIFIED_RUNTIME: ${profile} on ${platform}; build, qualify, and review a digest catalog before target execution.${detail}`,
);
}
if (matches.length !== 1)
throw new Error(`AMBIGUOUS_RUNTIME: select an exact qualified runtime id for ${profile}.`);
return matches[0];
}
/** Rollback only pairs the previous catalog with a compatible helper; reports have their own schema. */
export function rollbackCatalog(current: RuntimeCatalog, previous: RuntimeCatalog): RuntimeCatalog {
validateRuntimeCatalog(current);
validateRuntimeCatalog(previous);
if (current.previousRevision !== previous.revision || current.helperAbi !== previous.helperAbi)
throw new Error('INCOMPATIBLE_RUNTIME_ROLLBACK');
return previous;
}