/* RFC-0566 deploy.artifact.verify — verify artifact content hash and signature against manifest. Do not swap symlinks — that is deploy.atomic.swap's job. RFC-0566: initial deploy.artifact.verify handler. RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt */ import type { KernelCommandInput, KernelCommandResult, KernelRuntimeContext, } from "@warpgogol/werkstatt-engine/kernel"; import { verifyJsonSignatureAsync, requireEnv } from "@warpgogol/werkstatt-engine/integrity"; import type { ArtifactVerifyResult } from "./types.ts"; import { artifactDir, distPath, hashArtifactDir, readManifest } from "./deploy-utils.ts"; function flagString(input: KernelCommandInput, key: string): string | undefined { const v = input.flags[key]; return typeof v === "string" ? v : undefined; } export async function runDeployArtifactVerify( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const { workspaceRoot, logger } = context; const hash = flagString(input, "hash"); if (!hash) { throw new Error("[deploy.artifact.verify] --hash is required"); } const dir = artifactDir(workspaceRoot, hash); if (!(await context.io.exists(dir))) { logger.error(`[deploy.artifact.verify] artifact ${hash} not found`); return { exitCode: 1, summary: `[deploy.artifact.verify] artifact ${hash} not found`, nextSteps: [ { action: `[deploy.artifact.verify] artifact ${hash} not found`, kind: "required" as const, }, ], data: { verified: false, hash, expectedHash: hash, signatureVerified: false }, }; } const manifest = await readManifest(workspaceRoot, hash); const distDir = distPath(workspaceRoot, hash); if (!(await context.io.exists(distDir))) { logger.error(`[deploy.artifact.verify] dist directory missing for artifact ${hash}`); return { exitCode: 1, summary: `[deploy.artifact.verify] dist directory missing for ${hash}`, nextSteps: [ { action: `[deploy.artifact.verify] dist directory missing for ${hash}`, kind: "required" as const, }, ], data: { verified: false, hash, expectedHash: manifest.hash, signatureVerified: false }, }; } const { treeHash } = await hashArtifactDir(distDir); const hashMatches = treeHash === manifest.hash; let signatureVerified = false; if (manifest.signature) { try { const publicKeyPem = await requireEnv("SIGNING_PUBLIC_KEY", workspaceRoot); const payload: Record = { hash: manifest.hash, files: manifest.files, totalSize: manifest.totalSize, builtAt: manifest.builtAt, gitSha: manifest.gitSha, buildHost: manifest.buildHost, }; signatureVerified = await verifyJsonSignatureAsync({ payload, signatureHex: manifest.signature, publicKeyPem, }); } catch { signatureVerified = false; } } const verified = hashMatches && (signatureVerified || !manifest.signature); if (!verified) { logger.error( `[deploy.artifact.verify] verification failed for ${hash} (hash: ${hashMatches}, sig: ${signatureVerified})`, ); return { exitCode: 1, summary: `[deploy.artifact.verify] ${hash} verification failed (hash-mismatch)`, nextSteps: [ { action: `[deploy.artifact.verify] ${hash} verification failed (hash-mismatch)`, kind: "required" as const, }, ], data: { verified: false, hash: treeHash, expectedHash: manifest.hash, signatureVerified }, }; } logger.success(`[deploy.artifact.verify] artifact ${hash} verified`); return { data: { verified: true, hash: treeHash, expectedHash: manifest.hash, signatureVerified }, exitCode: 0, summary: `[deploy.artifact.verify] ${hash} verified (hash: ok, sig: ${signatureVerified})`, nextSteps: [ { action: `Deploy the artifact: pnpm exec werkstatt run leitstand.dev-deploy --release `, kind: "optional", }, ], }; }