import { mkdir, readFile, writeFile } from "node:fs/promises"; import process from "node:process"; import { DEPLOYMENT_TARGETS, findAdmissionViolations, isDeploymentTarget, type AdmissionInput, } from "../src/contracts/deployment-admission.ts"; import { parseRuntimeConfigArtifact } from "../src/contracts/release-artifacts.ts"; /** * §6.4 / FE-GATE-027. Refuses to admit an artifact to an environment it was not * built for. * * Release coherence already proves the artifacts agree with each other. It * cannot prove they belong in production, because a local build is coherent * with itself: `APP_ENV: local`, `AUTH_MODE: demo` and a loopback API pass * every existing gate. This gate closes that by making the destination an * explicit, declared input and refusing anything that does not match it. * * It fails closed in both directions. An undeclared destination is a refusal, * not a default, so an artifact can never be admitted by omission; and every * rule is stated as a reason to refuse, so an unreadable field cannot pass. */ const RUNTIME_CONFIG_PATH = "dist/config.json"; const RECORD_PATH = "artifacts/release/deployment-admission.json"; async function main(): Promise { const declared = process.env["RELEASE_TARGET"]; if (!isDeploymentTarget(declared)) { process.stderr.write( "release admission refused: RELEASE_TARGET must be declared as one of " + `${DEPLOYMENT_TARGETS.join(", ")}; received ${ declared === undefined ? "nothing" : declared }.\n` + "An artifact is never admitted by default — name the environment it is for.\n", ); process.exitCode = 1; return; } let document: unknown; try { document = JSON.parse(await readFile(RUNTIME_CONFIG_PATH, "utf8")); } catch (error) { process.stderr.write( `release admission refused: ${RUNTIME_CONFIG_PATH} is unreadable: ${ error instanceof Error ? error.message : String(error) }\n`, ); process.exitCode = 1; return; } let config: AdmissionInput; try { config = parseRuntimeConfigArtifact(document) as AdmissionInput; } catch (error) { process.stderr.write( `release admission refused: ${RUNTIME_CONFIG_PATH} is not a valid runtime config: ${ error instanceof Error ? error.message : String(error) }\n`, ); process.exitCode = 1; return; } const violations = findAdmissionViolations(declared, config); await mkdir("artifacts/release", { recursive: true }); await writeFile( RECORD_PATH, `${JSON.stringify( { schemaVersion: 1, target: declared, appEnv: config.APP_ENV, authMode: config.AUTH_MODE, apiBaseUrl: config.API_BASE_URL, buildId: config.BUILD_ID ?? null, releaseId: config.RELEASE_ID ?? null, status: violations.length === 0 ? "ADMITTED" : "REFUSED", violations, }, null, 2, )}\n`, "utf8", ); if (violations.length > 0) { process.stderr.write( `release admission refused for ${declared}:\n${violations .map((violation) => ` ${violation.field}: ${violation.reason}`) .join("\n")}\n`, ); process.exitCode = 1; return; } process.stdout.write( `release admission: ${declared} ADMITTED ` + `(APP_ENV=${config.APP_ENV}, AUTH_MODE=${config.AUTH_MODE}, ` + `API=${config.API_BASE_URL}); record at ${RECORD_PATH}\n`, ); } await main();