fix: harden CI evidence and removal contracts
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user