refactor: generate CI workflow from gate contracts

This commit is contained in:
DongHyeonka
2026-08-02 13:53:25 +09:00
parent 777ce5c9ed
commit 1bb2cc4a20
45 changed files with 8599 additions and 1177 deletions
+74 -18
View File
@@ -10,6 +10,9 @@ import {
} from "node:fs/promises";
import path from "node:path";
import { parseCiGateContract } from "./contracts/ci-gates.ts";
import { generateCiWorkflow } from "./generate-ci-workflow.ts";
const fixtureRoot = path.resolve(".tmp/realtime-runtime-removal");
const pnpmCli = requireEnvironment("npm_execpath");
const runtimePaths = [
@@ -31,6 +34,10 @@ const runtimeScripts = [
"check:realtime-boundaries:fixture",
"test:realtime-removal",
] as const;
const removedEvidencePathFragments = [
"realtime-boundaries",
"realtime-runtime-removal",
] as const;
const copyTargets = [
"src",
"tests",
@@ -292,32 +299,81 @@ for (const scriptPath of [
]) {
await rm(path.join(fixtureRoot, scriptPath), { force: true });
}
// The root snapshot locks the full repository inventory. This removal fixture
// validates its smaller registry through check:ci and its regenerated workflow.
await rm(
path.join(fixtureRoot, "tests/unit/ci-workflow-generation.test.ts"),
{ force: true },
);
await rm(
path.join(
fixtureRoot,
"tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap",
),
{ force: true },
);
const gatesPath = path.join(fixtureRoot, "config/ci/gates.json");
const gatesDocument = JSON.parse(await readFile(gatesPath, "utf8")) as {
gates: Record<
string,
{
steps: Array<{ script: string }>;
evidence: string[];
}
>;
};
for (const gate of Object.values(gatesDocument.gates)) {
gate.steps = gate.steps.filter(
({ script }) =>
!runtimeScripts.some((runtimeScript) => runtimeScript === script),
const gatesDocument = structuredClone(
parseCiGateContract(JSON.parse(await readFile(gatesPath, "utf8"))),
);
const removedScripts = new Set<string>(runtimeScripts);
const removedCommandIds = new Set(
gatesDocument.commands
.filter(({ script }) => removedScripts.has(script))
.map(({ id }) => id),
);
if (removedCommandIds.size !== runtimeScripts.length) {
throw new Error("Realtime CI command removal set is incomplete");
}
const removedArtifactIds = new Set(
gatesDocument.artifacts
.filter(({ path: artifactPath }) =>
removedEvidencePathFragments.some((fragment) =>
artifactPath.includes(fragment),
),
)
.map(({ id }) => id),
);
for (const fragment of removedEvidencePathFragments) {
if (
!gatesDocument.artifacts.some(({ path: artifactPath }) =>
artifactPath.includes(fragment),
)
) {
throw new Error(`Realtime CI evidence is missing: ${fragment}`);
}
}
gatesDocument.commands = gatesDocument.commands.filter(
({ id }) => !removedCommandIds.has(id),
);
gatesDocument.artifacts = gatesDocument.artifacts.filter(
({ id }) => !removedArtifactIds.has(id),
);
for (const gate of gatesDocument.gates) {
gate.commandIds = gate.commandIds.filter(
(commandId) => !removedCommandIds.has(commandId),
);
gate.evidence = gate.evidence.filter(
(evidence) =>
!evidence.includes("realtime-boundaries") &&
!evidence.includes("realtime-runtime-removal"),
gate.evidenceArtifactIds = gate.evidenceArtifactIds.filter(
(artifactId) => !removedArtifactIds.has(artifactId),
);
}
const referencedSchemaIds = new Set(
gatesDocument.artifacts.map(({ schemaId }) => schemaId),
);
gatesDocument.artifactSchemas = gatesDocument.artifactSchemas.filter(
({ id }) => referencedSchemaIds.has(id),
);
const validatedGates = parseCiGateContract(gatesDocument);
await writeFile(
gatesPath,
`${JSON.stringify(gatesDocument, null, 2)}\n`,
`${JSON.stringify(validatedGates, null, 2)}\n`,
);
await generateCiWorkflow({
root: fixtureRoot,
contract: validatedGates,
check: false,
});
await assertNoRuntimeImports(fixtureRoot);
const checks: Array<readonly [string, boolean]> = [