Files
tech-log-frontend/scripts/lib/local-release-evidence.ts

1347 lines
45 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { z, type ZodType } from "zod";
import { EXPECTED_CONTRACT_SET_PACKAGES } from "../../src/features/installed-contract-contributions.ts";
import {
ROUTE_REGISTRY,
ROUTE_RUNTIME_CONTRACT,
} from "../../src/features/installed-feature-contracts.ts";
import {
buildManifestArtifactSchema,
bundlePerformanceArtifactSchema,
dependencyDiffArtifactSchema,
dependencyInventoryArtifactSchema,
licenseReportArtifactSchema,
localEvidenceAssessmentArtifactSchema,
provenanceArtifactSchema,
releaseManifestArtifactSchema,
releaseVerificationArtifactSchema,
runtimeConfigArtifactSchema,
sbomArtifactSchema,
supplyChainVerificationArtifactSchema,
vulnerabilityReportArtifactSchema,
} from "../contracts/release-artifacts.ts";
import {
CANONICAL_VITE_MANIFEST_PATH,
verifyBuildManifestOutputs,
} from "./build-manifest-outputs.ts";
import { assertMatchesJsonSchema } from "./json-schema.ts";
import {
LOCAL_EVIDENCE_ASSESSMENT_PATH,
LOCAL_EVIDENCE_POLICY_INPUT_PATHS,
LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS,
RELEASE_CANDIDATE_EVIDENCE_PATHS,
RELEASE_CANDIDATE_MANIFEST_PATH,
collectDistOutputs,
distSha256,
releaseCandidateManifestSchema,
type ReleaseCandidateManifest,
} from "./release-candidate.ts";
import { verifyReleaseRuntimeCoherence } from "./release-runtime-coherence.ts";
import { findViteDynamicRouteChunk } from "./vite-route-chunks.ts";
import { digestReleaseInputFiles } from "./release-input-evidence.ts";
import {
compareStoredDependencyEvidence,
compareStoredLicenseEvidence,
compareStoredLocalVulnerabilityReport,
recomputeDependencyEvidence,
recomputeLicenseEvidence,
verifyLocalSupplyChainDefaults,
verifyStoredDistChecksums,
} from "./local-policy-evidence.ts";
import {
buildRepositoryFileInventory,
parseRepositoryFileInventoryPolicy,
} from "./repository-file-inventory.ts";
import {
parseSecretScanPolicy,
secretScanSarifSchema,
evaluateRepositorySecretScan,
verifyStoredSecretScan,
} from "./secret-scan-evaluator.ts";
import { secretScanRules } from "./secret-scan.ts";
import {
isValidSha512Integrity,
parsePnpmLockfilePackages,
supplyChainDigest,
verifySupplyChainCoherence,
} from "./supply-chain.ts";
const sha256 = z.string().regex(/^[a-f0-9]{64}$/u);
export const supplyChainCoherenceReportSchema = z
.object({
schemaVersion: z.literal(1),
status: z.enum(["PASS", "FAIL"]),
dependencyCount: z.int().nonnegative(),
lockfileSha256: sha256,
distSha256: sha256,
sbomSha256: sha256,
failures: z.array(z.string()),
})
.strict();
export type SupplyChainCoherenceReport = z.infer<
typeof supplyChainCoherenceReportSchema
>;
export async function verifyLocalSupplyChainEvidence(
repositoryRoot = process.cwd(),
): Promise<SupplyChainCoherenceReport> {
const failures: string[] = [];
const inventory = await parseArtifact(
repositoryRoot,
"artifacts/release/dependency-inventory.json",
dependencyInventoryArtifactSchema,
"dependency inventory",
failures,
);
const sbom = await parseArtifact(
repositoryRoot,
"artifacts/release/sbom.cdx.json",
sbomArtifactSchema,
"SBOM",
failures,
);
const provenance = await parseArtifact(
repositoryRoot,
"artifacts/release/provenance.json",
provenanceArtifactSchema,
"local provenance",
failures,
);
const verification = await parseArtifact(
repositoryRoot,
"artifacts/security/supply-chain-verification.json",
supplyChainVerificationArtifactSchema,
"supply-chain verification",
failures,
);
for (const [schemaPath, artifact, label] of [
[
"schemas/artifacts/dependency-inventory.schema.json",
inventory,
"dependency inventory",
],
[
"schemas/artifacts/supply-chain-verification.schema.json",
verification,
"supply-chain verification",
],
] as const) {
if (!artifact) continue;
try {
assertMatchesJsonSchema(
await readJson(repositoryRoot, schemaPath),
artifact,
label,
);
} catch {
failures.push(`${label} JSON Schema mismatch`);
}
}
let lockfileText = "";
let lockfileSha256 = "0".repeat(64);
try {
const rawLockfile = await readFile(
path.join(repositoryRoot, "pnpm-lock.yaml"),
);
lockfileText = rawLockfile.toString("utf8");
lockfileSha256 = createHash("sha256").update(rawLockfile).digest("hex");
} catch {
failures.push("raw pnpm-lock.yaml is missing or unreadable");
}
let distDigest = "0".repeat(64);
try {
distDigest = distSha256(await collectDistOutputs(repositoryRoot));
} catch {
failures.push("candidate dist is missing or unreadable");
}
const sbomSha256 = sbom ? supplyChainDigest(sbom) : "0".repeat(64);
let coherenceFailures: readonly string[] = Object.freeze([]);
if (inventory && sbom && provenance) {
coherenceFailures = verifySupplyChainCoherence(
sbom,
inventory,
provenance,
distDigest,
).failures;
failures.push(...coherenceFailures);
}
if (
!inventory ||
!verification ||
inventory.lockfileSha256 !== lockfileSha256 ||
verification.lockfileSha256 !== lockfileSha256
) {
failures.push("inventory/verification lockfile digest mismatch");
}
if (
!verification ||
verification.localStatus !== "PASS" ||
verification.failures.length > 0 ||
verification.distSha256 !== distDigest ||
verification.sbomSha256 !== sbomSha256
) {
failures.push("verification digest/status set is incoherent");
}
if (verification) {
failures.push(...verifyLocalSupplyChainDefaults(verification));
}
if (inventory && sbom && provenance && verification) {
try {
const policy = parseRepositoryFileInventoryPolicy(
await readJson(
repositoryRoot,
"config/security/secret-scan-policy.json",
),
);
const repositoryInventory = await buildRepositoryFileInventory({
repositoryRoot,
trackedRoots: policy.trackedRoots,
generatedRoots: policy.generatedRoots,
optionalRoots: policy.optionalRoots,
});
const sourceSetSha256 = await digestReleaseInputFiles(
repositoryInventory.trackedFiles,
(file) => readFile(path.join(repositoryRoot, file)),
);
if (
verification.sourceSetSha256 !== sourceSetSha256 ||
provenance.predicate.materials.sourceSetSha256 !== sourceSetSha256 ||
provenance.predicate.materials.sbomSha256 !== sbomSha256
) {
failures.push("source/SBOM provenance materials are incoherent");
}
} catch {
failures.push("release source inventory is unavailable or unreadable");
}
}
if (inventory && verification) {
try {
const dependencyPolicy = recomputeDependencyEvidence({
inventory,
baseline: await optionalReadJson(
repositoryRoot,
"config/security/dependency-baseline.json",
),
baselineApproval: await optionalReadJson(
repositoryRoot,
"config/security/dependency-baseline.approval.json",
),
dependencyChangeEvidence: await readJson(
repositoryRoot,
"config/security/dependency-change-evidence.json",
),
});
const licensePolicy = recomputeLicenseEvidence({
inventory,
policy: await readJson(
repositoryRoot,
"config/security/dependency-policy.json",
),
});
const expectedFailures = [
...licensePolicy.failures,
...dependencyPolicy.failures,
...coherenceFailures,
];
if (
supplyChainDigest(verification.dependencyDiff) !==
supplyChainDigest(dependencyPolicy.dependencyDiff) ||
supplyChainDigest(verification.highRiskReview) !==
supplyChainDigest(dependencyPolicy.highRisk) ||
supplyChainDigest(verification.failures) !==
supplyChainDigest(expectedFailures) ||
verification.localStatus !==
(expectedFailures.length === 0 ? "PASS" : "FAIL")
) {
failures.push(
"supply-chain verification policy fields do not match recomputed evidence",
);
}
} catch {
failures.push("supply-chain verification policy inputs are invalid");
}
}
const lockRows = parsePnpmLockfilePackages(lockfileText);
const inventoryRows = inventory?.dependencies ?? [];
const inventoryByIdentity = new Map<string, (typeof inventoryRows)[number]>(
inventoryRows.map(
(entry) => [`${entry.name}@${entry.version}`, entry] as const,
),
);
if (lockRows.length !== inventoryRows.length) {
failures.push("transitive dependency count differs from lockfile");
}
for (const lockRow of lockRows) {
const identity = `${lockRow.name}@${lockRow.version}`;
const dependency = inventoryByIdentity.get(identity);
if (
!dependency ||
dependency.integrity !== lockRow.integrity ||
!isValidSha512Integrity(lockRow.integrity)
) {
failures.push(`lockfile inventory integrity mismatch: ${identity}`);
}
}
return supplyChainCoherenceReportSchema.parse({
schemaVersion: 1,
status: failures.length === 0 ? "PASS" : "FAIL",
dependencyCount: inventoryRows.length,
lockfileSha256,
distSha256: distDigest,
sbomSha256,
failures,
});
}
export const LOCAL_EVIDENCE_VERIFIER_ID =
"tech-log-frontend/local-evidence-verifier";
export const LOCAL_EVIDENCE_VERIFIER_VERSION = "1";
export {
LOCAL_EVIDENCE_POLICY_INPUT_PATHS,
LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS,
} from "./release-candidate.ts";
export async function createLocalEvidenceAssessment(
repositoryRoot = process.cwd(),
): Promise<z.infer<typeof localEvidenceAssessmentArtifactSchema>> {
const root = path.resolve(repositoryRoot);
const outputs = await collectDistOutputs(root);
const evidencePaths = RELEASE_CANDIDATE_EVIDENCE_PATHS.filter(
(memberPath) => memberPath !== LOCAL_EVIDENCE_ASSESSMENT_PATH,
);
const evidenceInputs = (
await Promise.all([
...outputs.map(async ({ path: memberPath }) => digestInput(root, memberPath)),
...evidencePaths.map((memberPath) => digestInput(root, memberPath)),
])
).sort((left, right) => asciiCompare(left.path, right.path));
const lockfile = evidenceInputs.find(({ path: memberPath }) => memberPath === "pnpm-lock.yaml");
const sbom = evidenceInputs.find(
({ path: memberPath }) => memberPath === "artifacts/release/sbom.cdx.json",
);
if (!lockfile || !sbom) throw new Error("local assessment candidate inputs are incomplete");
const candidate: ReleaseCandidateManifest = {
schemaVersion: 1,
distSha256: distSha256(outputs),
lockfileSha256: lockfile.sha256,
bundleSha256: supplyChainDigest(evidenceInputs),
files: evidenceInputs,
};
const evaluated = await evaluateProducerLocalChecks(root, candidate);
const [build, release, provenance, supply, sbomDocument, policyInputs, secretScan] = await Promise.all([
readJson(root, "artifacts/release/build-manifest.json").then((value) =>
buildManifestArtifactSchema.parse(value),
),
readJson(root, "dist/release-manifest.json").then((value) =>
releaseManifestArtifactSchema.parse(value),
),
readJson(root, "artifacts/release/provenance.json").then((value) =>
provenanceArtifactSchema.parse(value),
),
readJson(root, "artifacts/security/supply-chain-verification.json").then((value) =>
supplyChainVerificationArtifactSchema.parse(value),
),
readJson(root, "artifacts/release/sbom.cdx.json").then((value) =>
sbomArtifactSchema.parse(value),
),
Promise.all(
LOCAL_EVIDENCE_POLICY_INPUT_PATHS.map((policyPath) =>
digestInput(root, policyPath),
),
),
evaluateRepositorySecretScan({ repositoryRoot: root }),
]);
const identityFailures: string[] = [];
if (build.commitSha !== release.commitSha) {
identityFailures.push("producer build/release source revision mismatch");
}
if (
provenance.predicate.materials.sourceSetSha256 !== supply.sourceSetSha256
) {
identityFailures.push("producer provenance/supply source-set mismatch");
}
if (supply.distSha256 !== candidate.distSha256) {
identityFailures.push("producer supply/candidate dist digest mismatch");
}
if (supply.lockfileSha256 !== candidate.lockfileSha256) {
identityFailures.push("producer supply/candidate lockfile digest mismatch");
}
if (supply.sbomSha256 !== supplyChainDigest(sbomDocument)) {
identityFailures.push("producer supply/candidate SBOM digest mismatch");
}
const checks = {
...evaluated.checks,
...(identityFailures.some((failure) => failure.includes("build/release"))
? { release: "FAIL" as const }
: {}),
...(identityFailures.some((failure) => !failure.includes("build/release"))
? { supplyChain: "FAIL" as const }
: {}),
};
const failures = [...evaluated.failures, ...identityFailures];
const status = failures.length === 0 && Object.values(checks).every(
(check) => check === "PASS",
)
? ("PASS" as const)
: ("FAIL" as const);
const verifierSources = policyInputs.filter(({ path: policyPath }) =>
(LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS as readonly string[]).includes(policyPath),
);
if (verifierSources.length !== LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS.length) {
throw new Error("local assessment verifier source set is incomplete");
}
const secretPolicy = policyInputs.find(
({ path: policyPath }) => policyPath === "config/security/secret-scan-policy.json",
);
const secretSarif = evidenceInputs.find(
({ path: evidencePath }) => evidencePath === "artifacts/security/scan.sarif",
);
if (!secretPolicy || !secretSarif) {
throw new Error("local assessment secret scan inputs are incomplete");
}
return localEvidenceAssessmentArtifactSchema.parse({
schemaVersion: 1,
artifactType: "local-evidence-assessment",
generatedAt: build.generatedAt,
status,
verifier: {
id: LOCAL_EVIDENCE_VERIFIER_ID,
version: LOCAL_EVIDENCE_VERIFIER_VERSION,
sourceSha256: supplyChainDigest(verifierSources),
},
source: {
revision: build.commitSha,
sourceSetSha256: supply.sourceSetSha256,
},
candidate: {
distSha256: candidate.distSha256,
lockfileSha256: candidate.lockfileSha256,
sbomSha256: sbom.sha256,
},
secretScan: {
policySha256: secretPolicy.sha256,
sarifSha256: secretSarif.sha256,
scanInputSha256: secretScan.scanInputSha256,
},
policyInputs,
evidenceInputs,
checks,
failures,
});
}
type LocalCheckName =
| "release"
| "supplyChain"
| "dependencyPolicy"
| "licensePolicy"
| "vulnerabilityPolicy"
| "secretScan";
async function evaluateProducerLocalChecks(
root: string,
candidate: ReleaseCandidateManifest,
options: Readonly<{ archived?: boolean }> = {},
): Promise<Readonly<{
checks: Readonly<Record<LocalCheckName, "PASS" | "FAIL">>;
failures: readonly string[];
}>> {
const checks: Record<LocalCheckName, "PASS" | "FAIL"> = {
release: "PASS",
supplyChain: "PASS",
dependencyPolicy: "PASS",
licensePolicy: "PASS",
vulnerabilityPolicy: "PASS",
secretScan: "PASS",
};
const failures: string[] = [];
const evaluate = async (
check: LocalCheckName,
operation: () => Promise<readonly string[]>,
): Promise<void> => {
try {
const diagnostics = await operation();
if (diagnostics.length > 0) {
checks[check] = "FAIL";
failures.push(...diagnostics.map((failure) => `${check}:${failure}`));
}
} catch (error) {
checks[check] = "FAIL";
failures.push(
`${check}:${error instanceof Error ? error.message : String(error)}`,
);
}
};
await evaluate("release", async () => {
const [build, release, runtime, stored] = await Promise.all([
readJson(root, "artifacts/release/build-manifest.json").then((value) =>
buildManifestArtifactSchema.parse(value),
),
readJson(root, "dist/release-manifest.json").then((value) =>
releaseManifestArtifactSchema.parse(value),
),
readJson(root, "dist/config.json").then((value) =>
runtimeConfigArtifactSchema.parse(value),
),
readJson(root, "artifacts/release/verification.json").then((value) =>
releaseVerificationArtifactSchema.parse(value),
),
]);
const diagnostics: string[] = [];
if (
build.commitSha !== release.commitSha ||
build.buildId !== release.buildId ||
build.releaseId !== release.releaseId ||
build.generatedAt !== release.builtAt
) {
diagnostics.push("build/release identity mismatch");
}
if (
!stored.passed ||
!stored.artifact.checked ||
!stored.artifact.compatible ||
stored.artifact.mismatches.length > 0 ||
stored.artifact.releaseId !== release.releaseId ||
stored.generatedAt !== release.builtAt ||
stored.fixtures.length === 0 ||
stored.fixtures.some((fixture) => !fixture.passed)
) {
diagnostics.push("stored release verification is not a coherent PASS");
}
if (!runtime.BUILD_ID || !runtime.RELEASE_ID) {
diagnostics.push("runtime release identity is missing");
} else {
const apiContractVersion =
release.schemaVersion === 1 && "API_CONTRACT_VERSION" in runtime
? runtime.API_CONTRACT_VERSION
: undefined;
const coherence = await verifyReleaseRuntimeCoherence({
release,
runtime: {
BUILD_ID: runtime.BUILD_ID,
RELEASE_ID: runtime.RELEASE_ID,
CONFIG_SCHEMA_VERSION: runtime.CONFIG_SCHEMA_VERSION,
...(apiContractVersion === undefined
? {}
: { API_CONTRACT_VERSION: apiContractVersion }),
},
contractPackages: EXPECTED_CONTRACT_SET_PACKAGES,
});
diagnostics.push(...coherence.mismatches.map((item) => `runtime:${item}`));
}
diagnostics.push(...(await verifyBuildManifestOutputs(build, { repositoryRoot: root })));
return diagnostics;
});
await evaluate("supplyChain", async () => {
const [inventory, sbom, provenance, supply, coherence, lockfileBytes] = await Promise.all([
readJson(root, "artifacts/release/dependency-inventory.json").then((value) =>
dependencyInventoryArtifactSchema.parse(value),
),
readJson(root, "artifacts/release/sbom.cdx.json").then((value) =>
sbomArtifactSchema.parse(value),
),
readJson(root, "artifacts/release/provenance.json").then((value) =>
provenanceArtifactSchema.parse(value),
),
readJson(root, "artifacts/security/supply-chain-verification.json").then((value) =>
supplyChainVerificationArtifactSchema.parse(value),
),
readJson(root, "artifacts/security/supply-chain-coherence.json").then((value) =>
supplyChainCoherenceReportSchema.parse(value),
),
readFile(path.join(root, "pnpm-lock.yaml")),
]);
const diagnostics: string[] = [];
const lockfileSha256 = createHash("sha256").update(lockfileBytes).digest("hex");
const outputs = await collectDistOutputs(root);
const currentDistSha256 = distSha256(outputs);
const sbomSha256 = supplyChainDigest(sbom);
const independentlyCoherent = verifySupplyChainCoherence(
sbom,
inventory,
provenance,
currentDistSha256,
);
if (
supply.localStatus !== "PASS" ||
supply.failures.length > 0 ||
supply.distSha256 !== candidate.distSha256 ||
supply.lockfileSha256 !== candidate.lockfileSha256 ||
supply.sbomSha256 !== sbomSha256 ||
supply.sourceSetSha256 !== provenance.predicate.materials.sourceSetSha256 ||
inventory.lockfileSha256 !== lockfileSha256 ||
candidate.lockfileSha256 !== lockfileSha256 ||
candidate.distSha256 !== currentDistSha256 ||
coherence.status !== "PASS" ||
coherence.failures.length > 0 ||
coherence.distSha256 !== candidate.distSha256 ||
coherence.lockfileSha256 !== candidate.lockfileSha256 ||
coherence.sbomSha256 !== sbomSha256 ||
coherence.dependencyCount !== inventory.dependencies.length ||
independentlyCoherent.failures.length > 0
) {
diagnostics.push("stored supply-chain evidence is not a coherent PASS");
}
diagnostics.push(...verifyLocalSupplyChainDefaults(supply));
diagnostics.push(
...verifyStoredDistChecksums(
outputs,
await readFile(path.join(root, "artifacts/release/checksums.txt"), "utf8"),
),
);
const lockRows = parsePnpmLockfilePackages(lockfileBytes.toString("utf8"));
const inventoryByIdentity = new Map(
inventory.dependencies.map((entry) => [`${entry.name}@${entry.version}`, entry] as const),
);
if (lockRows.length !== inventory.dependencies.length) {
diagnostics.push("transitive dependency count differs from lockfile");
}
for (const lockRow of lockRows) {
const dependency = inventoryByIdentity.get(`${lockRow.name}@${lockRow.version}`);
if (
!dependency ||
dependency.integrity !== lockRow.integrity ||
!isValidSha512Integrity(lockRow.integrity)
) {
diagnostics.push(`lockfile inventory integrity mismatch: ${lockRow.name}@${lockRow.version}`);
}
}
return diagnostics;
});
await evaluate("dependencyPolicy", async () => {
const [inventory, stored] = await Promise.all([
readJson(root, "artifacts/release/dependency-inventory.json").then((value) =>
dependencyInventoryArtifactSchema.parse(value),
),
readJson(root, "artifacts/security/dependency-diff.json").then((value) =>
dependencyDiffArtifactSchema.parse(value),
),
]);
const recomputed = recomputeDependencyEvidence({
inventory,
baseline: await optionalReadJson(root, "config/security/dependency-baseline.json"),
baselineApproval: await optionalReadJson(
root,
"config/security/dependency-baseline.approval.json",
),
dependencyChangeEvidence: await readJson(
root,
"config/security/dependency-change-evidence.json",
),
});
return compareStoredDependencyEvidence(recomputed, stored);
});
await evaluate("licensePolicy", async () => {
const [inventory, stored, policy] = await Promise.all([
readJson(root, "artifacts/release/dependency-inventory.json").then((value) =>
dependencyInventoryArtifactSchema.parse(value),
),
readJson(root, "artifacts/security/license-report.json").then((value) =>
licenseReportArtifactSchema.parse(value),
),
readJson(root, "config/security/dependency-policy.json"),
]);
return compareStoredLicenseEvidence(
recomputeLicenseEvidence({ inventory, policy }),
stored,
);
});
await evaluate("vulnerabilityPolicy", async () => {
const vulnerability = vulnerabilityReportArtifactSchema.parse(
await readJson(root, "artifacts/security/vulnerability-report.json"),
);
return compareStoredLocalVulnerabilityReport(
candidate.lockfileSha256,
vulnerability,
);
});
await evaluate("secretScan", async () => {
if (options.archived) {
const policy = parseSecretScanPolicy(
await readJson(root, "config/security/secret-scan-policy.json"),
);
const sarif = secretScanSarifSchema.parse(
await readJson(root, "artifacts/security/scan.sarif"),
);
const diagnostics: string[] = [];
if (
policy.trackedRoots.length === 0 ||
policy.generatedRoots.length === 0 ||
sarif.runs[0]!.results.length > 0 ||
JSON.stringify(sarif.runs[0]!.tool.driver.rules.map(({ id }) => id)) !==
JSON.stringify(secretScanRules().map(({ id }) => id))
) {
diagnostics.push("archived secret scan is not an independently valid PASS");
}
return diagnostics;
}
const evaluation = await evaluateRepositorySecretScan({ repositoryRoot: root });
return verifyStoredSecretScan(
evaluation,
await readJson(root, "artifacts/security/scan.sarif"),
);
});
return Object.freeze({ checks: Object.freeze(checks), failures: Object.freeze(failures) });
}
async function digestInput(
repositoryRoot: string,
memberPath: string,
): Promise<Readonly<{ path: string; bytes: number; sha256: string }>> {
const absolute = path.resolve(repositoryRoot, memberPath);
const relative = path.relative(repositoryRoot, absolute);
if (
relative === "" ||
relative === ".." ||
relative.startsWith(`..${path.sep}`) ||
path.isAbsolute(relative)
) {
throw new TypeError(`local assessment input escapes repository: ${memberPath}`);
}
const bytes = await readFile(absolute);
return Object.freeze({
path: memberPath,
bytes: bytes.byteLength,
sha256: createHash("sha256").update(bytes).digest("hex"),
});
}
function asciiCompare(left: string, right: string): number {
return left < right ? -1 : left > right ? 1 : 0;
}
export async function verifyArchivedLocalEvidence(input: Readonly<{
extractionRoot: string;
expectedManifest: ReleaseCandidateManifest;
}>): Promise<Readonly<{
status: "PASS" | "FAIL";
identity: null | Readonly<{
sourceRevision: string;
sourceSetSha256: string;
assessmentSha256: string;
secretScan: Readonly<{
policySha256: string;
sarifSha256: string;
scanInputSha256: string;
}>;
}>;
failures: readonly string[];
}>> {
const extractionRoot = path.resolve(input.extractionRoot);
const failures: string[] = [];
let extractedManifest: ReleaseCandidateManifest | null = null;
try {
extractedManifest = releaseCandidateManifestSchema.parse(
await readJson(extractionRoot, RELEASE_CANDIDATE_MANIFEST_PATH),
);
} catch {
failures.push("extracted release candidate manifest is missing or invalid");
}
if (
extractedManifest &&
JSON.stringify(extractedManifest) !== JSON.stringify(input.expectedManifest)
) {
failures.push("caller expectedManifest differs from extracted manifest");
}
let assessment: z.infer<typeof localEvidenceAssessmentArtifactSchema> | null = null;
let assessmentSha256 = "";
let assessmentBytes: Buffer | null = null;
try {
assessmentBytes = await readFile(
path.join(extractionRoot, LOCAL_EVIDENCE_ASSESSMENT_PATH),
);
assessmentSha256 = createHash("sha256").update(assessmentBytes).digest("hex");
assessment = localEvidenceAssessmentArtifactSchema.parse(
JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(assessmentBytes)) as unknown,
);
} catch {
failures.push("local evidence assessment is missing or invalid");
}
if (assessment && extractedManifest) {
const assessmentMember = extractedManifest.files.find(
({ path: memberPath }) => memberPath === LOCAL_EVIDENCE_ASSESSMENT_PATH,
);
if (
!assessmentMember ||
assessmentMember.bytes !== assessmentBytes?.byteLength ||
assessmentMember.sha256 !== assessmentSha256
) {
failures.push("local assessment manifest binding mismatch");
}
if (
assessment.verifier.id !== LOCAL_EVIDENCE_VERIFIER_ID ||
assessment.verifier.version !== LOCAL_EVIDENCE_VERIFIER_VERSION
) {
failures.push("local assessment verifier identity mismatch");
}
const policyPaths = assessment.policyInputs.map(({ path: policyPath }) => policyPath);
if (JSON.stringify(policyPaths) !== JSON.stringify(LOCAL_EVIDENCE_POLICY_INPUT_PATHS)) {
failures.push("local assessment policyInputs exact set mismatch");
}
for (const policyInput of assessment.policyInputs) {
try {
const bytes = await readFile(path.join(extractionRoot, policyInput.path));
const member = extractedManifest.files.find(
({ path: memberPath }) => memberPath === policyInput.path,
);
if (
!member ||
member.bytes !== policyInput.bytes ||
member.sha256 !== policyInput.sha256 ||
bytes.byteLength !== policyInput.bytes ||
createHash("sha256").update(bytes).digest("hex") !== policyInput.sha256
) {
failures.push(`archived policy input binding mismatch: ${policyInput.path}`);
}
} catch {
failures.push(`archived policy input is missing or invalid: ${policyInput.path}`);
}
}
const verifierSources = assessment.policyInputs.filter(({ path: policyPath }) =>
(LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS as readonly string[]).includes(policyPath),
);
if (
verifierSources.length !== LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS.length ||
supplyChainDigest(verifierSources) !== assessment.verifier.sourceSha256
) {
failures.push("local assessment verifier-source digest mismatch");
}
const expectedEvidenceInputs = extractedManifest.files.filter(
({ path: memberPath }) => memberPath !== LOCAL_EVIDENCE_ASSESSMENT_PATH,
);
if (JSON.stringify(assessment.evidenceInputs) !== JSON.stringify(expectedEvidenceInputs)) {
failures.push("local assessment evidenceInputs exact member binding mismatch");
}
const sbom = extractedManifest.files.find(
({ path: memberPath }) => memberPath === "artifacts/release/sbom.cdx.json",
);
const secretPolicy = extractedManifest.files.find(
({ path: memberPath }) => memberPath === "config/security/secret-scan-policy.json",
);
const secretSarif = extractedManifest.files.find(
({ path: memberPath }) => memberPath === "artifacts/security/scan.sarif",
);
if (
assessment.candidate.distSha256 !== extractedManifest.distSha256 ||
assessment.candidate.lockfileSha256 !== extractedManifest.lockfileSha256 ||
!sbom ||
assessment.candidate.sbomSha256 !== sbom.sha256
) {
failures.push("local assessment candidate digest binding mismatch");
}
if (
!secretPolicy ||
!secretSarif ||
assessment.secretScan.policySha256 !== secretPolicy.sha256 ||
assessment.secretScan.sarifSha256 !== secretSarif.sha256
) {
failures.push("local assessment secret scan artifact binding mismatch");
}
if (assessment.status !== "PASS" || Object.values(assessment.checks).includes("FAIL")) {
failures.push("local evidence assessment is not PASS");
}
try {
const [supply, coherence] = await Promise.all([
readJson(extractionRoot, "artifacts/security/supply-chain-verification.json").then(
(value) => supplyChainVerificationArtifactSchema.parse(value),
),
readJson(extractionRoot, "artifacts/security/supply-chain-coherence.json").then(
(value) => supplyChainCoherenceReportSchema.parse(value),
),
]);
if (
supply.localStatus !== "PASS" ||
supply.failures.length > 0 ||
coherence.status !== "PASS" ||
coherence.failures.length > 0
) {
failures.push("archived supply-chain subordinate evidence is not PASS");
}
} catch {
failures.push("archived supply-chain subordinate evidence is missing or invalid");
}
const independent = await evaluateProducerLocalChecks(
extractionRoot,
extractedManifest,
{ archived: true },
);
if (
independent.failures.length > 0 ||
JSON.stringify(independent.checks) !== JSON.stringify(assessment.checks)
) {
failures.push(
...independent.failures.map((failure) => `archived local check:${failure}`),
);
failures.push("archived local checks do not independently reproduce assessment PASS");
}
const identities = await readArchivedIdentities(extractionRoot, failures);
if (
identities.buildRevision !== assessment.source.revision ||
identities.releaseRevision !== assessment.source.revision
) {
failures.push("local assessment source revision identity mismatch");
}
if (
identities.provenanceSourceSetSha256 !== assessment.source.sourceSetSha256 ||
identities.supplySourceSetSha256 !== assessment.source.sourceSetSha256
) {
failures.push("local assessment source-set identity mismatch");
}
}
const uniqueFailures = Object.freeze([...new Set(failures)]);
const passingAssessment = uniqueFailures.length === 0 ? assessment : null;
return Object.freeze({
status: passingAssessment ? "PASS" : "FAIL",
identity: passingAssessment
? Object.freeze({
sourceRevision: passingAssessment.source.revision,
sourceSetSha256: passingAssessment.source.sourceSetSha256,
assessmentSha256,
secretScan: passingAssessment.secretScan,
})
: null,
failures: uniqueFailures,
});
}
async function readArchivedIdentities(
extractionRoot: string,
failures: string[],
): Promise<Readonly<{
buildRevision: unknown;
releaseRevision: unknown;
provenanceSourceSetSha256: unknown;
supplySourceSetSha256: unknown;
}>> {
try {
const [buildDocument, releaseDocument, provenanceDocument, supplyDocument] = await Promise.all([
readJson(extractionRoot, "artifacts/release/build-manifest.json"),
readJson(extractionRoot, "dist/release-manifest.json"),
readJson(extractionRoot, "artifacts/release/provenance.json"),
readJson(extractionRoot, "artifacts/security/supply-chain-verification.json"),
]);
const build = buildManifestArtifactSchema.parse(buildDocument);
const release = releaseManifestArtifactSchema.parse(releaseDocument);
const provenance = provenanceArtifactSchema.parse(provenanceDocument);
const supply = supplyChainVerificationArtifactSchema.parse(supplyDocument);
return Object.freeze({
buildRevision: build.commitSha,
releaseRevision: release.commitSha,
provenanceSourceSetSha256: provenance.predicate.materials.sourceSetSha256,
supplySourceSetSha256: supply.sourceSetSha256,
});
} catch {
failures.push("archived source/build/provenance identities are missing or invalid");
return Object.freeze({
buildRevision: null,
releaseRevision: null,
provenanceSourceSetSha256: null,
supplySourceSetSha256: null,
});
}
}
export async function assessLocalEvidenceForProducer(input: Readonly<{
repositoryRoot?: string;
candidate: ReleaseCandidateManifest;
}>): Promise<Readonly<{
status: "PASS" | "FAIL";
failures: readonly string[];
}>> {
const repositoryRoot = path.resolve(input.repositoryRoot ?? process.cwd());
const failures: string[] = [];
const supplyReport = await verifyLocalSupplyChainEvidence(repositoryRoot);
failures.push(...supplyReport.failures);
if (supplyReport.lockfileSha256 !== input.candidate.lockfileSha256) {
failures.push("candidate/raw lockfile digest mismatch");
}
const buildManifest = await parseArtifact(
repositoryRoot,
"artifacts/release/build-manifest.json",
buildManifestArtifactSchema,
"build manifest",
failures,
);
const release = await parseArtifact(
repositoryRoot,
"dist/release-manifest.json",
releaseManifestArtifactSchema,
"release manifest",
failures,
);
const runtime = await parseArtifact(
repositoryRoot,
"dist/config.json",
runtimeConfigArtifactSchema,
"runtime config",
failures,
);
const storedRelease = await parseArtifact(
repositoryRoot,
"artifacts/release/verification.json",
releaseVerificationArtifactSchema,
"release verification",
failures,
);
const storedSupply = await parseArtifact(
repositoryRoot,
"artifacts/security/supply-chain-coherence.json",
supplyChainCoherenceReportSchema,
"supply-chain coherence",
failures,
);
await validateSupportingArtifacts(
repositoryRoot,
failures,
supplyReport.lockfileSha256,
);
if (buildManifest) {
try {
assertMatchesJsonSchema(
await readJson(
repositoryRoot,
"schemas/artifacts/build-manifest.schema.json",
),
buildManifest,
"build manifest",
);
} catch {
failures.push("build manifest JSON Schema mismatch");
}
failures.push(
...(await verifyBuildManifestOutputs(buildManifest, { repositoryRoot })),
);
}
if (release && runtime) {
if (!runtime.BUILD_ID || !runtime.RELEASE_ID) {
failures.push("runtime release identity is missing");
} else {
const apiContractVersion =
release.schemaVersion === 1 && "API_CONTRACT_VERSION" in runtime
? runtime.API_CONTRACT_VERSION
: undefined;
if (release.schemaVersion === 1 && apiContractVersion === undefined) {
failures.push("runtime API contract identity is missing");
}
const coherence = await verifyReleaseRuntimeCoherence({
release,
runtime: {
BUILD_ID: runtime.BUILD_ID,
RELEASE_ID: runtime.RELEASE_ID,
CONFIG_SCHEMA_VERSION: runtime.CONFIG_SCHEMA_VERSION,
...(apiContractVersion === undefined
? {}
: { API_CONTRACT_VERSION: apiContractVersion }),
},
contractPackages: EXPECTED_CONTRACT_SET_PACKAGES,
});
failures.push(...coherence.mismatches.map((item) => `release:${item}`));
}
await verifyReleaseOutputs(
repositoryRoot,
release,
buildManifest,
failures,
);
}
if (
!storedRelease ||
!storedRelease.passed ||
!storedRelease.artifact.checked ||
!storedRelease.artifact.compatible ||
storedRelease.artifact.mismatches.length > 0 ||
storedRelease.fixtures.length === 0 ||
storedRelease.fixtures.some((fixture) => !fixture.passed) ||
storedRelease.artifact.releaseId !== release?.releaseId ||
storedRelease.generatedAt !== release?.builtAt
) {
failures.push("stored release verification is not a coherent PASS");
}
if (
!storedSupply ||
storedSupply.status !== "PASS" ||
storedSupply.failures.length > 0 ||
storedSupply.dependencyCount !== supplyReport.dependencyCount ||
storedSupply.lockfileSha256 !== supplyReport.lockfileSha256 ||
storedSupply.distSha256 !== supplyReport.distSha256 ||
storedSupply.sbomSha256 !== supplyReport.sbomSha256
) {
failures.push("stored supply-chain coherence is not a recomputed PASS");
}
await verifySecretScan(repositoryRoot, failures);
return Object.freeze({
status: failures.length === 0 ? "PASS" : "FAIL",
failures: Object.freeze([...new Set(failures)]),
});
}
async function validateSupportingArtifacts(
repositoryRoot: string,
failures: string[],
lockfileSha256: string,
): Promise<void> {
let actualOutputs: Awaited<ReturnType<typeof collectDistOutputs>> | null =
null;
try {
actualOutputs = await collectDistOutputs(repositoryRoot);
} catch {
failures.push("supporting evidence dist inputs are unreadable");
}
const bundle = await parseArtifact(
repositoryRoot,
"artifacts/performance/bundle.json",
bundlePerformanceArtifactSchema,
"bundle report",
failures,
);
if (
bundle &&
actualOutputs &&
JSON.stringify(bundle.outputs) !== JSON.stringify(actualOutputs)
) {
failures.push("bundle report does not describe current dist bytes");
}
if (actualOutputs) {
try {
failures.push(
...verifyStoredDistChecksums(
actualOutputs,
await readFile(
path.join(repositoryRoot, "artifacts/release/checksums.txt"),
"utf8",
),
),
);
} catch {
failures.push("stored dist checksums are missing or unreadable");
}
}
const inventory = await parseArtifact(
repositoryRoot,
"artifacts/release/dependency-inventory.json",
dependencyInventoryArtifactSchema,
"dependency inventory",
failures,
);
const dependencyDiff = await parseArtifact(
repositoryRoot,
"artifacts/security/dependency-diff.json",
dependencyDiffArtifactSchema,
"dependency diff",
failures,
);
if (inventory && dependencyDiff) {
try {
const recomputed = recomputeDependencyEvidence({
inventory,
baseline: await optionalReadJson(
repositoryRoot,
"config/security/dependency-baseline.json",
),
baselineApproval: await optionalReadJson(
repositoryRoot,
"config/security/dependency-baseline.approval.json",
),
dependencyChangeEvidence: await readJson(
repositoryRoot,
"config/security/dependency-change-evidence.json",
),
});
failures.push(
...compareStoredDependencyEvidence(recomputed, dependencyDiff),
);
} catch {
failures.push("dependency policy evidence is missing or invalid");
}
}
const license = await parseArtifact(
repositoryRoot,
"artifacts/security/license-report.json",
licenseReportArtifactSchema,
"license report",
failures,
);
if (inventory && license) {
try {
const recomputed = recomputeLicenseEvidence({
inventory,
policy: await readJson(
repositoryRoot,
"config/security/dependency-policy.json",
),
});
failures.push(...compareStoredLicenseEvidence(recomputed, license));
} catch {
failures.push("license policy evidence is missing or invalid");
}
}
const vulnerability = await parseArtifact(
repositoryRoot,
"artifacts/security/vulnerability-report.json",
vulnerabilityReportArtifactSchema,
"local vulnerability report",
failures,
);
if (vulnerability) {
failures.push(
...compareStoredLocalVulnerabilityReport(lockfileSha256, vulnerability),
);
}
const provenance = await parseArtifact(
repositoryRoot,
"artifacts/release/provenance.json",
provenanceArtifactSchema,
"local provenance",
failures,
);
if (
provenance?.predicate.runDetails.metadata.invocationId !== "LOCAL_UNSIGNED"
) {
failures.push("local provenance must remain LOCAL_UNSIGNED");
}
}
async function verifyReleaseOutputs(
repositoryRoot: string,
release: z.infer<typeof releaseManifestArtifactSchema>,
buildManifest: z.infer<typeof buildManifestArtifactSchema> | null,
failures: string[],
): Promise<void> {
let viteManifest: Record<string, unknown> = {};
try {
const raw = await readFile(
path.join(repositoryRoot, CANONICAL_VITE_MANIFEST_PATH),
"utf8",
);
viteManifest = asRecord(JSON.parse(raw), "Vite manifest");
if (createHash("sha256").update(raw).digest("hex") !== release.assetManifestHash) {
failures.push("release asset manifest hash mismatch");
}
} catch {
failures.push("Vite manifest is missing or invalid");
}
if (
buildManifest &&
(buildManifest.buildId !== release.buildId ||
buildManifest.commitSha !== release.commitSha ||
buildManifest.releaseId !== release.releaseId ||
buildManifest.generatedAt !== release.builtAt)
) {
failures.push("build/release identity mismatch");
}
const runtimeContracts: Readonly<Record<string, { moduleId: string }>> =
ROUTE_RUNTIME_CONTRACT;
for (const definition of Object.values(ROUTE_REGISTRY)) {
const runtime = runtimeContracts[definition.routeId];
const viteEntry = runtime
? findViteDynamicRouteChunk(
Object.fromEntries(
Object.entries(viteManifest).flatMap(([key, entry]) => {
if (!isRecord(entry) || typeof entry.file !== "string") return [];
return [[key, {
file: entry.file,
...(typeof entry.name === "string" ? { name: entry.name } : {}),
...(typeof entry.isDynamicEntry === "boolean"
? { isDynamicEntry: entry.isDynamicEntry }
: {}),
...(Array.isArray(entry.dynamicImports) &&
entry.dynamicImports.every((item) => typeof item === "string")
? { dynamicImports: entry.dynamicImports as string[] }
: {}),
}]];
}),
),
runtime.moduleId,
)
: undefined;
const file = isRecord(viteEntry) ? viteEntry.file : null;
if (
typeof file !== "string" ||
release.routeChunks[definition.chunkId] !== file ||
buildManifest?.outputs.routeChunks[definition.chunkId] !== file
) {
failures.push(`release route chunk mismatch: ${definition.chunkId}`);
}
}
}
async function verifySecretScan(
repositoryRoot: string,
failures: string[],
): Promise<void> {
try {
const evaluation = await evaluateRepositorySecretScan({ repositoryRoot });
failures.push(
...verifyStoredSecretScan(
evaluation,
await readJson(repositoryRoot, "artifacts/security/scan.sarif"),
),
);
} catch {
failures.push("secret scan SARIF is missing or invalid");
}
}
async function parseArtifact<T>(
repositoryRoot: string,
file: string,
schema: ZodType<T>,
label: string,
failures: string[],
): Promise<T | null> {
try {
return schema.parse(await readJson(repositoryRoot, file));
} catch {
failures.push(`${label} executable schema mismatch`);
return null;
}
}
async function readJson(repositoryRoot: string, file: string): Promise<unknown> {
return JSON.parse(await readFile(path.join(repositoryRoot, file), "utf8"));
}
async function optionalReadJson(
repositoryRoot: string,
file: string,
): Promise<unknown | null> {
try {
return await readJson(repositoryRoot, file);
} catch {
return null;
}
}
function asRecord(value: unknown, label: string): Record<string, unknown> {
if (!isRecord(value)) throw new TypeError(`${label} must be a JSON object`);
return value;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}