fix: harden CI evidence and removal contracts

This commit is contained in:
DongHyeonka
2026-08-02 14:48:04 +09:00
parent 1bb2cc4a20
commit f49d147b01
20 changed files with 1175 additions and 767 deletions
+50
View File
@@ -1,4 +1,6 @@
import { spawnSync } from "node:child_process";
import { lstat } from "node:fs/promises";
import path from "node:path";
import {
ciCheckoutIdentityFailures,
@@ -37,6 +39,28 @@ let passed = true;
const DEFAULT_STEP_TIMEOUT_MS = 30 * 60 * 1_000;
const MAX_STEP_OUTPUT_BYTES = 16 * 1024 * 1_024;
const LOG_DIAGNOSTIC_RESERVE_BYTES = 4_096;
const freshlyProducedArtifactIds = new Set<string>();
const commandGeneratedEvidence = gate.evidenceArtifactIds
.map((artifactId) => contractIndex.artifacts.get(artifactId))
.filter((artifact) => artifact?.production === "command-generated");
async function observeArtifactGeneration(relativePath: string): Promise<string> {
try {
const metadata = await lstat(path.join(process.cwd(), relativePath), {
bigint: true,
});
return [
metadata.dev,
metadata.ino,
metadata.size,
metadata.mtimeNs,
metadata.ctimeNs,
].join(":");
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return "missing";
throw error;
}
}
const appendOutput = (...values: readonly string[]): boolean => {
for (const value of values.filter(Boolean)) {
const addedBytes = Buffer.byteLength(value, "utf8") + 1;
@@ -97,6 +121,17 @@ if (passed) {
for (const commandId of gate.commandIds) {
const step = contractIndex.commands.get(commandId);
if (!step) throw new TypeError(`CI gate command disappeared after validation: ${commandId}`);
const producedArtifacts = commandGeneratedEvidence.filter((artifact) =>
artifact.producerCommandIds.includes(commandId)
);
const generationBefore = new Map(
await Promise.all(
producedArtifacts.map(async (artifact) => [
artifact.id,
await observeArtifactGeneration(artifact.path),
] as const),
),
);
const commandLine = `$ corepack pnpm ${step.script} ${(step.args ?? []).join(" ")}`.trim();
if (!appendOutput(commandLine) || logSchema.maxBytes - outputBytes <= LOG_DIAGNOSTIC_RESERVE_BYTES) {
appendOutput("gate aggregate output budget exhausted before command execution");
@@ -159,6 +194,21 @@ if (passed) {
passed = false;
break;
}
for (const artifact of producedArtifacts) {
const generationAfter = await observeArtifactGeneration(artifact.path);
if (generationAfter !== generationBefore.get(artifact.id)) {
freshlyProducedArtifactIds.add(artifact.id);
}
}
}
}
if (passed) {
for (const artifact of commandGeneratedEvidence) {
if (!freshlyProducedArtifactIds.has(artifact.id)) {
appendOutput(`command-generated evidence was not freshly produced: ${artifact.path}`);
passed = false;
}
}
}