refactor: validate generated evidence artifacts

This commit is contained in:
DongHyeonka
2026-08-02 04:33:01 +09:00
parent 2c3cab2518
commit c9f5887cac
18 changed files with 1874 additions and 320 deletions
+7 -2
View File
@@ -3,7 +3,6 @@ import {
mkdir,
readFile,
readdir,
writeFile,
} from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
@@ -16,6 +15,8 @@ import {
verifyRegistryBaselineApproval,
} from "./lib/registry-compatibility.ts";
import { assertMatchesJsonSchema } from "./lib/json-schema.ts";
import { registrySnapshotArtifactSchema } from "./contracts/release-artifacts.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
type RegistryRow = Record<string, unknown>;
type RegistryRows = Record<string, RegistryRow>;
@@ -675,7 +676,11 @@ if (usesRepositoryBaseline && failures.length === 0) {
}
}
await mkdir(path.dirname(artifactPath), { recursive: true });
await writeFile(artifactPath, `${JSON.stringify(report, null, 2)}\n`);
await writeValidatedJsonArtifact({
path: artifactPath,
schema: registrySnapshotArtifactSchema,
value: report,
});
if (failures.length > 0) {
process.stderr.write(`Registry governance failed:\n${failures.join("\n")}\n`);
+8 -5
View File
@@ -1,10 +1,12 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { mkdir, readFile } from "node:fs/promises";
import {
evaluateFieldBudget,
percentile75,
} from "../src/application/policies/performance-budgets.ts";
import { fieldWebVitalsArtifactSchema } from "./contracts/release-artifacts.ts";
import { validateFieldEvidenceInput } from "./lib/field-vitals-evidence.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
const inputPath =
process.env.FIELD_WEB_VITALS_INPUT ||
@@ -94,10 +96,11 @@ const report = {
};
await mkdir("artifacts/performance", { recursive: true });
await writeFile(
"artifacts/performance/field-web-vitals.json",
`${JSON.stringify(report, null, 2)}\n`,
);
await writeValidatedJsonArtifact({
path: "artifacts/performance/field-web-vitals.json",
schema: fieldWebVitalsArtifactSchema,
value: report,
});
if (!passed) {
process.stderr.write(
`Field Web Vitals: ${status} (approved threshold decision and valid 28-day production evidence are required)\n`,
+424
View File
@@ -1 +1,425 @@
import { z } from "zod";
export * from "../../src/contracts/release-artifacts.ts";
const nonEmptyString = z.string().min(1);
const timestamp = z.iso.datetime();
const sha256 = z.string().regex(/^[a-f0-9]{64}$/u);
const jsonObject = z.record(z.string(), z.json());
export const moduleInventoryArtifactSchema = z
.object({
schemaVersion: z.literal(1),
chunks: z.array(
z
.object({
fileName: nonEmptyString,
modules: z.array(nonEmptyString),
})
.strict(),
),
})
.strict();
export const jsonSchemaDocumentArtifactSchema = z
.object({
$schema: z.literal("https://json-schema.org/draft/2020-12/schema"),
})
.catchall(z.json());
const dependencyInventoryRowSchema = z
.object({
name: nonEmptyString,
version: nonEmptyString,
direct: z.boolean(),
scope: z.enum(["production", "development"]),
optional: z.boolean(),
license: nonEmptyString,
integrity: z.string().regex(/^sha512-/u),
dependencies: z.array(nonEmptyString),
})
.strict();
export const dependencyInventoryArtifactSchema = z
.object({
schemaVersion: z.literal(2),
packageManager: nonEmptyString,
lockfileSha256: sha256,
dependencyCount: z.int().nonnegative(),
directDependencyCount: z.int().nonnegative(),
dependencies: z.array(dependencyInventoryRowSchema),
})
.strict()
.superRefine((inventory, context) => {
if (inventory.dependencyCount !== inventory.dependencies.length) {
context.addIssue({
code: "custom",
path: ["dependencyCount"],
message: "must equal dependencies.length",
});
}
const actualDirect = inventory.dependencies.filter(
(dependency) => dependency.direct,
).length;
if (inventory.directDependencyCount !== actualDirect) {
context.addIssue({
code: "custom",
path: ["directDependencyCount"],
message: "must equal the number of direct dependencies",
});
}
});
const dependencyUpgradeSchema = z
.object({
name: nonEmptyString,
from: nonEmptyString,
to: nonEmptyString,
})
.strict();
export const dependencyDiffSchema = z
.object({
added: z.array(nonEmptyString),
removed: z.array(nonEmptyString),
changed: z.array(nonEmptyString),
upgrades: z.array(dependencyUpgradeSchema),
})
.strict();
export const supplyChainVerificationArtifactSchema = z
.object({
schemaVersion: z.literal(1),
localStatus: z.enum(["PASS", "FAIL"]),
promotionStatus: z.enum(["PASS", "FAIL_UNVERIFIED"]),
lockfileSha256: sha256,
sourceSetSha256: sha256,
distSha256: sha256,
sbomSha256: sha256,
dependencyDiff: dependencyDiffSchema,
highRiskReview: z.array(nonEmptyString),
vulnerabilityStatus: z.enum(["PASS", "FAIL", "FAIL_UNVERIFIED"]),
provenanceAttestationStatus: z.enum(["PASS", "FAIL_UNVERIFIED"]),
failures: z.array(z.string()),
})
.strict();
const registryChangeSchema = z
.object({
changeId: nonEmptyString,
registryId: nonEmptyString,
rowName: nonEmptyString,
field: nonEmptyString,
kind: nonEmptyString,
impact: z.enum(["none", "additive", "behavior-change", "breaking"]),
before: z.json().optional(),
after: z.json().optional(),
})
.strict();
const registryArtifactRowSchema = z
.object({
registryId: nonEmptyString,
owner: nonEmptyString,
source: nonEmptyString,
rowCount: z.int().nonnegative(),
contract: jsonObject,
rows: jsonObject,
})
.strict();
export const registrySnapshotArtifactSchema = z
.object({
schemaVersion: z.literal(2),
generatedAt: timestamp,
baselineDigest: sha256.nullable(),
currentDigest: sha256,
compatibility: z
.object({
impact: z.enum([
"not-evaluated",
"none",
"additive",
"behavior-change",
"breaking",
]),
changes: z.array(registryChangeSchema),
})
.strict(),
failures: z.array(z.string()),
registries: z.array(registryArtifactRowSchema),
})
.strict();
const outputDigestSchema = z
.object({
path: nonEmptyString,
bytes: z.int().nonnegative(),
gzipBytes: z.int().nonnegative(),
sha256,
})
.strict();
export const bundlePerformanceArtifactSchema = z
.object({
schemaVersion: z.literal(1),
generatedAt: timestamp,
context: z
.object({
nodeVersion: nonEmptyString,
packageManager: nonEmptyString,
runnerImage: nonEmptyString,
})
.strict(),
outputs: z.array(outputDigestSchema).min(1),
})
.strict();
const cyclonedxComponentSchema = z
.object({
type: z.literal("library"),
"bom-ref": nonEmptyString,
name: nonEmptyString,
version: nonEmptyString,
scope: z.enum(["optional", "required"]),
hashes: z.array(
z.object({ alg: z.literal("SHA-512"), content: nonEmptyString }).strict(),
),
licenses: z.array(
z.object({ expression: nonEmptyString }).strict(),
),
properties: z.array(
z.object({ name: nonEmptyString, value: nonEmptyString }).strict(),
),
})
.strict();
export const sbomArtifactSchema = z
.object({
bomFormat: z.literal("CycloneDX"),
specVersion: z.literal("1.6"),
serialNumber: nonEmptyString,
version: z.literal(1),
metadata: z
.object({
component: z
.object({
type: z.literal("application"),
name: nonEmptyString,
version: nonEmptyString,
})
.strict(),
properties: z.array(
z.object({ name: nonEmptyString, value: nonEmptyString }).strict(),
),
})
.strict(),
components: z.array(cyclonedxComponentSchema),
dependencies: z.array(
z
.object({ ref: nonEmptyString, dependsOn: z.array(nonEmptyString) })
.strict(),
),
})
.strict();
export const provenanceArtifactSchema = z
.object({
_type: z.literal("https://in-toto.io/Statement/v1"),
subject: z
.array(
z
.object({
name: z.literal("dist"),
digest: z.object({ sha256 }).strict(),
})
.strict(),
)
.length(1),
predicateType: z.literal("https://slsa.dev/provenance/v1"),
predicate: z
.object({
buildDefinition: z
.object({
buildType: nonEmptyString,
externalParameters: jsonObject,
internalParameters: jsonObject,
resolvedDependencies: z.array(
z
.object({ uri: nonEmptyString, digest: z.object({ sha256 }).strict() })
.strict(),
),
})
.strict(),
runDetails: z
.object({
builder: z.object({ id: nonEmptyString }).strict(),
metadata: z.object({ invocationId: nonEmptyString }).strict(),
})
.strict(),
materials: z
.object({ lockfileSha256: sha256, sourceSetSha256: sha256, sbomSha256: sha256 })
.strict(),
})
.strict(),
})
.strict();
export const dependencyDiffArtifactSchema = z
.object({
schemaVersion: z.literal(2),
baselineDigest: sha256.nullable(),
currentDigest: sha256,
...dependencyDiffSchema.shape,
highRisk: z.array(nonEmptyString),
reviewFailures: z.array(z.string()),
})
.strict();
export const licenseReportArtifactSchema = z
.object({
schemaVersion: z.literal(1),
status: z.enum(["PASS", "FAIL"]),
dependencyCount: z.int().nonnegative(),
results: z.array(
z
.object({
package: nonEmptyString,
license: nonEmptyString,
passed: z.boolean(),
reason: z.string().nullable(),
})
.strict(),
),
failures: z.array(z.string()),
})
.strict();
export const vulnerabilityReportArtifactSchema = z
.object({
schemaVersion: z.literal(1),
provider: nonEmptyString,
scannedLockfileSha256: sha256,
status: z.enum(["PASS", "FAIL", "FAIL_UNVERIFIED"]),
findings: z.array(jsonObject),
exceptionsApplied: z.array(jsonObject),
failures: z.array(z.string()),
blocking: z.array(z.string()),
})
.strict();
export const fieldWebVitalsArtifactSchema = z
.object({
schemaVersion: z.literal(1),
generatedAt: timestamp,
window: z
.object({ days: z.literal(28), start: timestamp, end: timestamp })
.strict(),
context: z
.object({
source: nonEmptyString,
sourceSystem: z.string().nullable(),
exportId: z.string().nullable(),
network: z.literal("production-real-user"),
routeAggregation: z.literal("route-id-only"),
releaseId: z.string().nullable(),
privacyApprovalRef: z.string().nullable(),
thresholdDecisionRef: z.string().nullable(),
validationFailures: z.array(z.string()),
})
.strict(),
metrics: z
.object({
p75LcpMs: z.number().finite().nonnegative().nullable(),
p75Cls: z.number().finite().nonnegative().nullable(),
p75InpMs: z.number().finite().nonnegative().nullable(),
})
.strict(),
thresholds: z
.object({
p75LcpMs: z.number().finite().nonnegative(),
p75Cls: z.number().finite().nonnegative(),
p75InpMs: z.number().finite().nonnegative(),
minimumEligibleSamples: z.int().positive().nullable(),
})
.strict(),
eligibility: z
.object({
consentRequired: z.literal(true),
totalSamples: z.int().nonnegative(),
eligibleSamples: z.int().nonnegative(),
minimumEligibleSamples: z.int().positive().nullable(),
routeSamples: z.record(z.string(), z.int().nonnegative()),
})
.strict(),
status: z.enum(["PASS", "FAIL_THRESHOLD", "FAIL_UNVERIFIED"]),
passed: z.boolean(),
})
.strict();
export const labPerformanceArtifactSchema = z
.object({
schemaVersion: z.literal(1),
generatedAt: timestamp,
context: jsonObject,
metrics: jsonObject,
thresholds: jsonObject,
fixtures: z.array(
z.object({ name: nonEmptyString, passed: z.boolean() }).strict(),
),
passed: z.boolean(),
})
.strict();
export const releaseVerificationArtifactSchema = z
.object({
schemaVersion: z.literal(1),
generatedAt: timestamp,
artifact: z
.object({
checked: z.boolean(),
compatible: z.boolean(),
mismatches: z.array(z.string()),
releaseId: nonEmptyString,
})
.strict(),
fixtures: z.array(
z
.object({
name: nonEmptyString,
expectedCompatible: z.boolean(),
actualCompatible: z.boolean(),
mismatches: z.array(z.string()),
passed: z.boolean(),
})
.strict(),
),
passed: z.boolean(),
})
.strict();
export const runbookRecordArtifactSchema = z
.object({
schemaVersion: z.literal(1),
runbookId: z.string().regex(/^FE-RB-00[1-5]$/u),
releaseId: nonEmptyString,
drillTimestamp: timestamp,
triggerInjected: nonEmptyString,
triggerAsserted: z.boolean(),
containmentAsserted: z.boolean(),
escalationPathAsserted: z.boolean(),
recoveryAssertions: z.array(
z
.object({
assertion: nonEmptyString,
evidence: nonEmptyString,
passed: z.boolean(),
})
.strict(),
),
negativeFixtureFailedAsExpected: z.boolean(),
windowObservedBucket: nonEmptyString,
providerVerificationRequired: z.boolean(),
passed: z.boolean(),
})
.strict();
+8 -5
View File
@@ -1,4 +1,4 @@
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { access, mkdir, readFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { shouldRetry } from "../src/adapters/http/retry-policy.ts";
@@ -15,7 +15,9 @@ import {
} from "../src/contracts/release-artifacts.ts";
import { projectTelemetryEvent } from "../src/contracts/telemetry.ts";
import { EXPECTED_CONTRACT_SET_PACKAGES } from "../src/features/installed-contract-contributions.ts";
import { runbookRecordArtifactSchema } from "./contracts/release-artifacts.ts";
import { verifyReleaseRuntimeCoherence } from "./lib/release-runtime-coherence.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
type RecoveryAssertion = Readonly<{
assertion: string;
@@ -354,10 +356,11 @@ async function main(): Promise<void> {
};
const artifactDirectory = `artifacts/runbooks/${runbookId}`;
await mkdir(artifactDirectory, { recursive: true });
await writeFile(
`${artifactDirectory}/record.json`,
`${JSON.stringify(record, null, 2)}\n`,
);
await writeValidatedJsonArtifact({
path: `${artifactDirectory}/record.json`,
schema: runbookRecordArtifactSchema,
value: record,
});
if (!passed) {
process.stderr.write(`${runbookId} drill failed.\n`);
process.exit(1);
+131
View File
@@ -0,0 +1,131 @@
import { mkdir, readFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { z } from "zod";
import {
buildManifestArtifactSchema,
dependencyInventoryArtifactSchema,
jsonSchemaDocumentArtifactSchema,
registrySnapshotArtifactSchema,
supplyChainVerificationArtifactSchema,
} from "./contracts/release-artifacts.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
const ARTIFACT_SCHEMA_DEFINITIONS = Object.freeze([
{
relativePath: "schemas/artifacts/build-manifest.schema.json",
id: undefined,
schema: buildManifestArtifactSchema,
},
{
relativePath: "schemas/artifacts/dependency-inventory.schema.json",
id: "https://clean-architecture-frontend.local/schemas/dependency-inventory.schema.json",
schema: dependencyInventoryArtifactSchema,
},
{
relativePath: "schemas/artifacts/registry-snapshot.schema.json",
id: undefined,
schema: registrySnapshotArtifactSchema,
},
{
relativePath: "schemas/artifacts/supply-chain-verification.schema.json",
id: "https://clean-architecture-frontend.local/schemas/supply-chain-verification.schema.json",
schema: supplyChainVerificationArtifactSchema,
},
] as const);
type GenerateArtifactSchemasOptions = Readonly<{
root?: string;
check?: boolean;
}>;
function canonicalize(value: unknown): unknown {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.entries(value)
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, item]) => [key, canonicalize(item)]),
);
}
return value;
}
function generatedSchema(
definition: (typeof ARTIFACT_SCHEMA_DEFINITIONS)[number],
): Readonly<Record<string, unknown>> {
const schema = z.toJSONSchema(definition.schema, {
target: "draft-2020-12",
});
return canonicalize({
...schema,
...(definition.id === undefined ? {} : { $id: definition.id }),
}) as Readonly<Record<string, unknown>>;
}
export async function generateArtifactSchemas(
options: GenerateArtifactSchemasOptions = {},
): Promise<void> {
const root = path.resolve(options.root ?? process.cwd());
const documents = ARTIFACT_SCHEMA_DEFINITIONS.map((definition) => ({
definition,
document: generatedSchema(definition),
}));
const drift: string[] = [];
for (const { definition, document } of documents) {
const destination = path.join(root, definition.relativePath);
const validatedDocument = jsonSchemaDocumentArtifactSchema.parse(document);
const expected = `${JSON.stringify(validatedDocument, null, 2)}\n`;
if (options.check) {
let actual: string | null = null;
try {
actual = await readFile(destination, "utf8");
} catch {
// A missing or unreadable checked-in schema is drift.
}
if (actual !== expected) drift.push(definition.relativePath);
continue;
}
await mkdir(path.dirname(destination), { recursive: true });
await writeValidatedJsonArtifact({
path: destination,
schema: jsonSchemaDocumentArtifactSchema,
value: document,
});
}
if (drift.length > 0) {
throw new Error(`Artifact JSON Schema drift:\n- ${drift.join("\n- ")}`);
}
}
function argumentValue(name: string): string | undefined {
const index = process.argv.indexOf(name);
return index >= 0 ? process.argv[index + 1] : undefined;
}
const invokedPath = process.argv[1];
if (
invokedPath !== undefined &&
import.meta.url === pathToFileURL(invokedPath).href
) {
try {
await generateArtifactSchemas({
root: argumentValue("--root"),
check: process.argv.includes("--check"),
});
process.stdout.write(
process.argv.includes("--check")
? "Artifact JSON Schemas: PASS\n"
: "Artifact JSON Schemas: GENERATED\n",
);
} catch (error) {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
}
}
+40 -42
View File
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, readFile, rm } from "node:fs/promises";
import process from "node:process";
import { z } from "zod";
@@ -9,14 +9,17 @@ import {
} from "../src/features/installed-feature-contracts.ts";
import {
buildManifestArtifactSchema,
jsonSchemaDocumentArtifactSchema,
moduleInventoryArtifactSchema,
releaseManifestV2ArtifactSchema,
runtimeConfigV2ArtifactSchema,
} from "../src/contracts/release-artifacts.ts";
} from "./contracts/release-artifacts.ts";
import { buildContractSet } from "./generate-contract-set.ts";
import {
assertCiBuildEnvironment,
buildDate,
} from "./lib/build-environment.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
assertCiBuildEnvironment(process.env);
type ViteManifestEntry = Readonly<{
@@ -41,12 +44,15 @@ const moduleInventory = await readFile(
"dist/.vite/module-inventory.json",
"utf8",
);
parseModuleInventory(JSON.parse(moduleInventory));
const moduleInventoryDocument = moduleInventoryArtifactSchema.parse(
JSON.parse(moduleInventory),
);
const moduleInventoryBytes = `${JSON.stringify(moduleInventoryDocument, null, 2)}\n`;
const assetManifestHash = createHash("sha256")
.update(viteManifest)
.digest("hex");
const moduleInventoryHash = createHash("sha256")
.update(moduleInventory)
.update(moduleInventoryBytes)
.digest("hex");
const runtimeConfig = runtimeConfigV2ArtifactSchema.parse(
JSON.parse(await readFile("dist/config.json", "utf8")),
@@ -108,28 +114,37 @@ const releaseManifest = releaseManifestV2ArtifactSchema.parse({
await mkdir("artifacts/release", { recursive: true });
await mkdir("artifacts/quality", { recursive: true });
await writeFile(
"artifacts/quality/vite-module-inventory.json",
moduleInventory,
);
await writeValidatedJsonArtifact({
path: "artifacts/quality/vite-module-inventory.json",
schema: moduleInventoryArtifactSchema,
value: moduleInventoryDocument,
});
await rm("dist/.vite/module-inventory.json");
await writeFile("dist/config.json", `${JSON.stringify(runtimeConfig, null, 2)}\n`);
await writeFile(
"dist/release-manifest.json",
`${JSON.stringify(releaseManifest, null, 2)}\n`,
);
await writeFile(
"dist/runtime-config.schema.json",
`${JSON.stringify(runtimeConfigJsonSchema, null, 2)}\n`,
);
await writeFile(
"artifacts/release/runtime-config.schema.json",
`${JSON.stringify(runtimeConfigJsonSchema, null, 2)}\n`,
);
await writeFile(
"artifacts/release/build-manifest.json",
`${JSON.stringify(manifest, null, 2)}\n`,
);
await writeValidatedJsonArtifact({
path: "dist/config.json",
schema: runtimeConfigV2ArtifactSchema,
value: runtimeConfig,
});
await writeValidatedJsonArtifact({
path: "dist/release-manifest.json",
schema: releaseManifestV2ArtifactSchema,
value: releaseManifest,
});
await writeValidatedJsonArtifact({
path: "dist/runtime-config.schema.json",
schema: jsonSchemaDocumentArtifactSchema,
value: runtimeConfigJsonSchema,
});
await writeValidatedJsonArtifact({
path: "artifacts/release/runtime-config.schema.json",
schema: jsonSchemaDocumentArtifactSchema,
value: runtimeConfigJsonSchema,
});
await writeValidatedJsonArtifact({
path: "artifacts/release/build-manifest.json",
schema: buildManifestArtifactSchema,
value: manifest,
});
function parsePackageMetadata(value: unknown): Readonly<{
version: string;
@@ -165,23 +180,6 @@ function parseViteManifest(
return entries;
}
function parseModuleInventory(value: unknown): void {
if (
!isRecord(value) ||
value.schemaVersion !== 1 ||
!Array.isArray(value.chunks) ||
value.chunks.some(
(chunk) =>
!isRecord(chunk) ||
typeof chunk.fileName !== "string" ||
!Array.isArray(chunk.modules) ||
chunk.modules.some((moduleId) => typeof moduleId !== "string"),
)
) {
throw new TypeError("Vite module inventory is invalid");
}
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
+77 -67
View File
@@ -10,6 +10,16 @@ import {
} from "node:fs/promises";
import path from "node:path";
import {
bundlePerformanceArtifactSchema,
dependencyDiffArtifactSchema,
dependencyInventoryArtifactSchema,
licenseReportArtifactSchema,
provenanceArtifactSchema,
sbomArtifactSchema,
supplyChainVerificationArtifactSchema,
vulnerabilityReportArtifactSchema,
} from "./contracts/release-artifacts.ts";
import {
diffDependencyInventories,
flattenPnpmDependencyTree,
@@ -22,6 +32,7 @@ import {
verifySupplyChainCoherence,
type DependencyInventoryDiff,
} from "./lib/supply-chain.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
type Document = Record<string, unknown>;
@@ -428,81 +439,80 @@ const verification = {
: "FAIL_UNVERIFIED",
failures: localFailures,
};
const bundleReport = {
schemaVersion: 1,
generatedAt: new Date().toISOString(),
context: {
nodeVersion: process.version,
packageManager: String(packageJson.packageManager ?? ""),
runnerImage:
process.env.CI_RUNNER_IMAGE ?? `${process.platform}-${process.arch}`,
},
outputs,
};
const dependencyDiffReport = {
schemaVersion: 2,
baselineDigest: baseline ? supplyChainDigest(baseline) : null,
currentDigest: supplyChainDigest(inventory),
...dependencyDiff,
highRisk: reviewResult.highRisk,
reviewFailures: reviewResult.failures,
};
const licenseReport = {
schemaVersion: 1,
status: licenseResult.passed ? "PASS" : "FAIL",
dependencyCount: inventory.dependencyCount,
results: licenseResult.results,
failures: licenseResult.failures,
};
await mkdir("artifacts/performance", { recursive: true });
await mkdir("artifacts/release", { recursive: true });
await mkdir("artifacts/security", { recursive: true });
await writeFile(
"artifacts/performance/bundle.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
context: {
nodeVersion: process.version,
packageManager: String(packageJson.packageManager ?? ""),
runnerImage:
process.env.CI_RUNNER_IMAGE ?? `${process.platform}-${process.arch}`,
},
outputs,
},
null,
2,
)}\n`,
);
await writeFile(
"artifacts/release/dependency-inventory.json",
`${JSON.stringify(inventory, null, 2)}\n`,
);
await writeFile(
"artifacts/release/sbom.cdx.json",
`${JSON.stringify(sbom, null, 2)}\n`,
);
await writeFile(
"artifacts/release/provenance.json",
`${JSON.stringify(provenance, null, 2)}\n`,
);
await writeValidatedJsonArtifact({
path: "artifacts/performance/bundle.json",
schema: bundlePerformanceArtifactSchema,
value: bundleReport,
});
await writeValidatedJsonArtifact({
path: "artifacts/release/dependency-inventory.json",
schema: dependencyInventoryArtifactSchema,
value: inventory,
});
await writeValidatedJsonArtifact({
path: "artifacts/release/sbom.cdx.json",
schema: sbomArtifactSchema,
value: sbom,
});
await writeValidatedJsonArtifact({
path: "artifacts/release/provenance.json",
schema: provenanceArtifactSchema,
value: provenance,
});
await writeFile(
"artifacts/release/checksums.txt",
`${outputs.map((output) => `${output.sha256} ${output.path}`).join("\n")}\n`,
);
await writeFile(
"artifacts/security/dependency-diff.json",
`${JSON.stringify(
{
schemaVersion: 2,
baselineDigest: baseline ? supplyChainDigest(baseline) : null,
currentDigest: supplyChainDigest(inventory),
...dependencyDiff,
highRisk: reviewResult.highRisk,
reviewFailures: reviewResult.failures,
},
null,
2,
)}\n`,
);
await writeFile(
"artifacts/security/license-report.json",
`${JSON.stringify(
{
schemaVersion: 1,
status: licenseResult.passed ? "PASS" : "FAIL",
dependencyCount: inventory.dependencyCount,
results: licenseResult.results,
failures: licenseResult.failures,
},
null,
2,
)}\n`,
);
await writeFile(
"artifacts/security/vulnerability-report.json",
`${JSON.stringify(vulnerabilityReport, null, 2)}\n`,
);
await writeFile(
"artifacts/security/supply-chain-verification.json",
`${JSON.stringify(verification, null, 2)}\n`,
);
await writeValidatedJsonArtifact({
path: "artifacts/security/dependency-diff.json",
schema: dependencyDiffArtifactSchema,
value: dependencyDiffReport,
});
await writeValidatedJsonArtifact({
path: "artifacts/security/license-report.json",
schema: licenseReportArtifactSchema,
value: licenseReport,
});
await writeValidatedJsonArtifact({
path: "artifacts/security/vulnerability-report.json",
schema: vulnerabilityReportArtifactSchema,
value: vulnerabilityReport,
});
await writeValidatedJsonArtifact({
path: "artifacts/security/supply-chain-verification.json",
schema: supplyChainVerificationArtifactSchema,
value: verification,
});
if (!localPassed) {
process.stderr.write(
+85
View File
@@ -0,0 +1,85 @@
import { randomUUID } from "node:crypto";
import {
open as openFile,
rename as renameFile,
rm as removeFile,
} from "node:fs/promises";
import path from "node:path";
import type { z } from "zod";
export type ValidatedJsonArtifactInput = Readonly<{
path: string;
schema: z.ZodType;
value: unknown;
}>;
export type ValidatedJsonArtifactFileSystem = Readonly<{
open: (path: string, flags: "wx") => Promise<{
writeFile(data: string, encoding: "utf8"): Promise<unknown>;
close(): Promise<unknown>;
}>;
rename: (source: string, destination: string) => Promise<unknown>;
rm: (path: string, options: Readonly<{ force: true }>) => Promise<unknown>;
}>;
type ValidatedJsonArtifactWriterDependencies = Readonly<{
createNonce?: () => string;
fileSystem?: ValidatedJsonArtifactFileSystem;
}>;
const defaultFileSystem: ValidatedJsonArtifactFileSystem = Object.freeze({
open: async (target, flags) => openFile(target, flags),
rename: async (source, destination) => renameFile(source, destination),
rm: async (target, options) => removeFile(target, options),
});
/**
* Builds a writer whose only publish operation is an atomic sibling rename.
* Dependency injection is limited to the file-system boundary so failure
* ownership can be verified without exposing a caller-selected cleanup path.
*/
export function createValidatedJsonArtifactWriter(
dependencies: ValidatedJsonArtifactWriterDependencies = {},
) {
const createNonce = dependencies.createNonce ?? randomUUID;
const fileSystem = dependencies.fileSystem ?? defaultFileSystem;
return async function writeArtifact(
input: ValidatedJsonArtifactInput,
): Promise<void> {
const parsed = input.schema.parse(input.value);
const serialized = JSON.stringify(parsed, null, 2);
if (serialized === undefined) {
throw new TypeError("Validated JSON artifact is not serializable");
}
const temporaryPath = path.join(
path.dirname(input.path),
`.${path.basename(input.path)}.${createNonce()}.tmp`,
);
let ownsTemporaryFile = false;
try {
const handle = await fileSystem.open(temporaryPath, "wx");
ownsTemporaryFile = true;
try {
await handle.writeFile(`${serialized}\n`, "utf8");
} finally {
await handle.close();
}
await fileSystem.rename(temporaryPath, input.path);
} catch (error) {
if (ownsTemporaryFile) {
try {
await fileSystem.rm(temporaryPath, { force: true });
} catch {
// Preserve the publishing failure; cleanup is confined to our nonce.
}
}
throw error;
}
};
}
export const writeValidatedJsonArtifact =
createValidatedJsonArtifactWriter();
+16 -17
View File
@@ -1,5 +1,5 @@
import { spawn } from "node:child_process";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { mkdir, readFile } from "node:fs/promises";
import { performance } from "node:perf_hooks";
import process from "node:process";
@@ -7,6 +7,8 @@ import { chromium } from "@playwright/test";
import { evaluateLabBudget } from "../src/application/policies/performance-budgets.ts";
import { ROUTE_REGISTRY } from "../src/features/installed-feature-contracts.ts";
import { labPerformanceArtifactSchema } from "./contracts/release-artifacts.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
type ContractPerformanceEvidence = {
lcpMs: number;
@@ -142,22 +144,19 @@ try {
];
const passed = result.passed && fixtures.every((fixture) => fixture.passed);
await mkdir("artifacts/performance", { recursive: true });
await writeFile(
"artifacts/performance/lab.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
context: contextMetadata,
metrics,
thresholds,
fixtures,
passed,
},
null,
2,
)}\n`,
);
await writeValidatedJsonArtifact({
path: "artifacts/performance/lab.json",
schema: labPerformanceArtifactSchema,
value: {
schemaVersion: 1,
generatedAt: new Date().toISOString(),
context: contextMetadata,
metrics,
thresholds,
fixtures,
passed,
},
});
if (!passed) {
throw new Error(`Lab performance failed: ${JSON.stringify(metrics)}`);
}
+8 -5
View File
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { mkdir, readFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import {
@@ -23,6 +23,8 @@ import {
} from "../src/features/installed-feature-contracts.ts";
import { assertMatchesJsonSchema } from "./lib/json-schema.ts";
import { verifyReleaseRuntimeCoherence } from "./lib/release-runtime-coherence.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
import { releaseVerificationArtifactSchema } from "./contracts/release-artifacts.ts";
type CoherenceFixture = Readonly<{
name: string;
@@ -212,10 +214,11 @@ const report = {
};
await mkdir("artifacts/release", { recursive: true });
await writeFile(
"artifacts/release/verification.json",
`${JSON.stringify(report, null, 2)}\n`,
);
await writeValidatedJsonArtifact({
path: "artifacts/release/verification.json",
schema: releaseVerificationArtifactSchema,
value: report,
});
if (!passed) {
process.stderr.write(