/* RFC-0566 deploy.atomic.rollback — atomic symlink swap back to the previous platform artifact. Do not rebuild from source — rollback swaps to existing previous symlink target. RFC-0566: initial deploy.atomic.rollback 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 type { AtomicSwapResult } from "./types.ts"; import { artifactDir, atomicSymlinkSwap, currentSymlinkPath, previousSymlinkPath, readSymlinkBasename, } from "./deploy-utils.ts"; import { runDeployArtifactVerify } from "./artifact-verify.ts"; export async function runDeployAtomicRollback( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const { workspaceRoot, logger } = context; const previousPath = previousSymlinkPath(workspaceRoot); const previousHash = await readSymlinkBasename(previousPath, context.io); if (!previousHash) { logger.error("[deploy.atomic.rollback] no previous artifact to roll back to"); return { exitCode: 1, summary: "[deploy.atomic.rollback] no-previous-artifact", nextSteps: [ { action: "[deploy.atomic.rollback] no-previous-artifact", kind: "required" as const }, ], data: { swapped: false, previousHash: null, newHash: "", swapTimeMs: 0, }, }; } const dir = artifactDir(workspaceRoot, previousHash); if (!(await context.io.exists(dir))) { logger.error(`[deploy.atomic.rollback] previous artifact ${previousHash} directory missing`); return { exitCode: 1, summary: `[deploy.atomic.rollback] previous artifact ${previousHash} missing`, nextSteps: [ { action: `[deploy.atomic.rollback] previous artifact ${previousHash} missing`, kind: "required" as const, }, ], data: { swapped: false, previousHash, newHash: "", swapTimeMs: 0, }, }; } const verifyResult = await runDeployArtifactVerify( { ...input, flags: { ...input.flags, hash: previousHash } }, context, ); if (!verifyResult.data?.verified) { throw new Error( `[deploy.atomic.rollback] previous artifact ${previousHash} hash verification failed (hash-mismatch)`, ); } const currentPath = currentSymlinkPath(workspaceRoot); const currentHash = await readSymlinkBasename(currentPath, context.io); const startTime = Date.now(); await atomicSymlinkSwap(currentPath, dir); const swapTimeMs = Date.now() - startTime; logger.success( `[deploy.atomic.rollback] rolled back to ${previousHash} in ${swapTimeMs}ms` + (currentHash ? ` (from ${currentHash})` : ""), ); return { data: { swapped: true, previousHash: currentHash, newHash: previousHash, swapTimeMs, }, exitCode: 0, summary: `[deploy.atomic.rollback] rolled back to ${previousHash.slice(0, 16)}... in ${swapTimeMs}ms`, nextSteps: [ { action: `Verify health: pnpm exec werkstatt run leitstand.health --site --channel main`, kind: "optional", }, ], }; }