/* RFC-0866: Shared 13-phase deploy execution pipeline for dev/alt/main channels. Runs after authorizeAndDeploy() returns ok: true. Does not perform authorization — that is the responsibility of deploy-helpers.ts. Does not define the DeploymentAdapter interface — that lives in adapter.ts. Does not register commands — that lives in leitstand.module.ts. RFC-1284: post-propagate access-pin reconcile — after a successful dev/alt propagation, re-assert ACCESS_PIN from the freshly re-read system state when protection is desired and no suspension marker is open. Non-fatal diagnostic on accessReconcile; ACCESS-01 in sternsystem.validate is the hard backstop. RFC-1284: fix review REVIEW-CODE-2026-10-15-02: RFC-1284 — single-store marker, flag=false proceed, surfaced reconcile F1: marker store unified on the git-tracked cache clone — access commands, certify entry check, and deploy reconcile all use non-smart readSystemState/writeSystemState (decisions Q-6); the mission-window blindness and the DNA-106 false-resume are gone. F2: --auto-manage-pin=false + suspension at entry skips entry-resume and proceeds on the open channel (decisions Q-7) — RFC/AC-8 text amended. F3: accessReconcile surfaced in data + summary of dev-deploy/propagate. F4: CHANGE_SUMMARY items on all touched files. F5: sternsystem.validate declares ACCESS-01/02 + system-state.yaml reads. F6: isProtectedLiveTarget exported and covered (AC-12 probe repointed). F7: fresh-read ordering proven — suspension opened mid-deploy suppresses the re-assert. RFC-1297: buildAuthHeader re-exported from sternsystem/live-drift.ts (canonical home); gate selection uses gateForChannel. RFC-1325: provisional workers.dev as first-class site host Single host predicate module in werkstatt-shared; provisional registration derives dev/alt/main workers.dev channels from the account subdomain; first dev deploy auto-protects provisional hosts; provisional main responses carry noindex; dns-aid skips provisional sites; site.domain.adopt cuts over to a custom domain. RFC-1325: review r1 — pin-carrying probes, AC-3 write ordering, dns-aid validate parity, hardened host lint RFC-0866, RFC-0925, RFC-0931, RFC-0948, RFC-1092, RFC-1097, RFC-1138, RFC-1179, RFC-1188, RFC-1190 */ import path from "node:path"; import type { DeploymentAdapter, PropagateInput, HealthInput } from "./adapter.ts"; import { resolveOutboundAgentClient } from "./wba-signer.ts"; import type { DeploymentStaticConfig, DeploymentChannel, PurgeResult, HealthCheck, PropagationResult, } from "@warpgogol/werkstatt-engine/schemas"; import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint"; import type { AgentContractCheckResult } from "@warpgogol/werkstatt-shared/agent"; import { verifyFreshness, runMissionCheckWithResilience, type FreshnessResult, type MissionContext, } from "./leitstand-commands.ts"; import type { AuthorizeOutcome } from "./deploy-helpers.ts"; import type { DeploymentEffectRecordV1 } from "../certification/deployment/authority.ts"; import { buildEffectRecord, writeDeploymentEffectRecord, gateForChannel, } from "./deploy-helpers.ts"; import { appendAndCommitBordbuch } from "../bordbuch/bordbuch-commit-helper.ts"; import { writeSystemStateSmart, readSystemStateSmart, readSystemState, } from "../sternsystem/registry-io.ts"; import { collectPurgeUrls, purgeCacheByUrls, skippedPurgeResult } from "./cache-purge.ts"; import { readBehaviorSnapshot, sourceDotenv, filterEnv } from "./adapters/index.ts"; import { runWranglerSecretPut } from "./adapters/wrangler-secrets.ts"; import { fingerprintTree } from "@warpgogol/werkstatt-engine/fingerprint/semantic"; import { atomicWriteFile } from "../werkstatt/atomic.ts"; import { signBuildIdentity, writeReleasePublicKey, signLatestBuildArtifacts, } from "../integrity/signing.ts"; import { loadPrivateKey } from "@warpgogol/werkstatt-engine/signing"; import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; import { isProvisionalHost } from "@warpgogol/werkstatt-shared/middleware/site-host"; // RFC-1297: canonical home moved to sternsystem/live-drift.ts so // sternsystem.validate / ship preflight / rollback share the same PIN header; // re-exported here so existing imports and test mocks keep working. import { buildAuthHeader } from "../sternsystem/live-drift.ts"; export { buildAuthHeader }; const noopLogger = { info: (_m: string) => {}, warn: (_m: string) => {}, success: (_m: string) => {}, }; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } const HEALTH_CHECK_MAX_ATTEMPTS = 3; const HEALTH_CHECK_BACKOFF_DELAYS_MS = [3_000, 6_000]; export interface DeployExecutionContext { systemId: string; releaseId: string | undefined; candidateId: string; artifactHash: Sha256Digest; authResult: AuthorizeOutcome; workspaceRoot: string; cacheCloneDir: string; systemConfig: DeploymentStaticConfig; adapter: DeploymentAdapter; operationId: string; missionId?: string; commitSha?: string; /** RFC-1190: full resolution record — how missionId/commitSha were obtained. */ missionContext?: MissionContext; gateDecisionPath: string; secretsFilePath?: string; skipEvidenceSync?: boolean; forceBuild?: boolean; } export interface FeatureSmokeCheckResult { checked: boolean; markersFound: string[]; markersMissing: string[]; error: string | null; } export interface DeployExecutionResult { deploymentUrl: string; buildSkipped: boolean; buildIdentity: { releaseId: string; written: boolean; path: string }; releaseSignResult?: { buildIdentitySigned: boolean; signatureHex: string | null; publicKeyUrl: string | null; reusedExistingSignature: boolean; }; freshness: FreshnessResult; purgeResult?: PurgeResult; healthState: "healthy" | "unhealthy" | "unknown"; healthChecks: HealthCheck[]; featureSmokeCheck?: FeatureSmokeCheckResult; /** RFC-1116: external agent-surface contract check result — evidence, non-fatal. */ agentContractCheck?: AgentContractCheckResult; effectRecord: DeploymentEffectRecordV1; bordbuchCommitted: boolean; systemStateUpdated: boolean; evidenceSynced: boolean; evidenceSyncError: string | null; /** RFC-1190: dev-channel post-deploy mission.check outcome (recorded, not just thrown). */ axiomStatus: "pass" | "fail" | "not-run"; axiomExitCode: number; /** RFC-1284: post-propagate ACCESS_PIN reconcile outcome — present only when the channel was eligible for reconciliation (dev/alt, pin declared, no suspension). */ accessReconcile?: { ok: boolean; error?: string; pinIssued?: string }; /** RFC-1190: mission identity resolution record propagated to the deploy result. */ missionContext?: MissionContext; failingPhase?: string; errorMessage?: string; } function getChannelConfig( dep: DeploymentStaticConfig, channel: "dev" | "alt" | "main", ): DeploymentChannel { const channelConfig = channel === "dev" ? dep.channels.dev : channel === "alt" ? dep.channels.alt : dep.channels.main; if (!channelConfig) { throw new Error(`[executeDeployPhases] channel '${channel}' is not defined for system`); } return channelConfig; } function isDevWorkersUrl(url: string): boolean { return isProvisionalHost(url); } async function runPurgeStep( workspaceRoot: string, releaseId: string, deploymentUrl: string, secretsFilePath: string | undefined, ): Promise { const secretsEnv = secretsFilePath ? await sourceDotenv(secretsFilePath) : {}; const env = { ...filterEnv(process.env), ...secretsEnv }; const zoneId = env["CLOUDFLARE_ZONE_ID"]; const apiToken = env["CLOUDFLARE_API_TOKEN"]; if (!zoneId) return skippedPurgeResult("CLOUDFLARE_ZONE_ID not set"); if (!apiToken) return skippedPurgeResult("CLOUDFLARE_API_TOKEN not set"); const snapshot = await readBehaviorSnapshot(workspaceRoot, releaseId); const routes = snapshot?.routes ?? []; const urls = collectPurgeUrls(deploymentUrl, routes); return purgeCacheByUrls(zoneId, apiToken, urls); } const FEATURE_SMOKE_MARKERS = ['externalLinkQrEntitled":true', "data-external-link-qr-modal"]; async function runFeatureSmokeCheck( deploymentUrl: string, authHeaders: Record, workspaceRoot: string, releaseId: string | undefined, fetchImpl: typeof fetch = fetch, ): Promise { try { let smokePath = "/"; if (releaseId) { try { const snapshot = await readBehaviorSnapshot(workspaceRoot, releaseId); const routes = snapshot?.routes ?? []; const firstHtmlRoute = routes.find((r) => r.path && r.path !== "/" && !r.redirectTarget); if (firstHtmlRoute?.path) { smokePath = firstHtmlRoute.path; } } catch { // Non-fatal — fall back to "/" } } const base = deploymentUrl.replace(/\/$/, ""); const fetchUrl = `${base}${smokePath}`; const controller = new AbortController(); let timer: ReturnType | undefined; try { timer = setTimeout(() => controller.abort(), 15000); const response = await fetchImpl(fetchUrl, { headers: authHeaders, redirect: "follow", signal: controller.signal, }); if (!response.ok) { return { checked: false, markersFound: [], markersMissing: [], error: `HTTP ${response.status} fetching ${fetchUrl}`, }; } const html = await response.text(); const markersFound: string[] = []; const markersMissing: string[] = []; for (const marker of FEATURE_SMOKE_MARKERS) { if (html.includes(marker)) { markersFound.push(marker); } else { markersMissing.push(marker); } } return { checked: true, markersFound, markersMissing, error: null }; } finally { if (timer) clearTimeout(timer); } } catch (err) { return { checked: false, markersFound: [], markersMissing: [], error: err instanceof Error ? err.message : String(err), }; } } async function runHealthCheckWithRetry( adapter: DeploymentAdapter, systemId: string, deploymentUrl: string, channel: "dev" | "alt" | "main", releaseId: string, workspaceRoot: string, authHeaders: Record = {}, agentFetch?: typeof fetch, ): Promise<{ state: "healthy" | "unhealthy" | "unknown"; checks: HealthCheck[] }> { for (let attempt = 1; attempt <= HEALTH_CHECK_MAX_ATTEMPTS; attempt++) { if (attempt > 1) { await sleep(HEALTH_CHECK_BACKOFF_DELAYS_MS[attempt - 2]); } try { const healthInput: HealthInput = { systemId, deploymentUrl, channel, releaseId, expectedBehaviorSnapshotHash: "", workspaceRoot, authHeaders, agentFetch, }; const result = await adapter.health(healthInput); if (result.state === "healthy") { return { state: "healthy", checks: result.checks ?? [] }; } if (attempt === HEALTH_CHECK_MAX_ATTEMPTS) { return { state: result.state ?? "unhealthy", checks: result.checks ?? [] }; } } catch { if (attempt === HEALTH_CHECK_MAX_ATTEMPTS) { return { state: "unhealthy", checks: [] }; } } } return { state: "unknown", checks: [] }; } export async function executeDeployPhases( ctx: DeployExecutionContext, channel: "dev" | "alt" | "main", io: WorkspaceIO = createDefaultIO().io, ): Promise { const channelConfig = getChannelConfig(ctx.systemConfig, channel); const deploymentUrl = channelConfig.url ?? ""; const now = new Date().toISOString(); const systemState = await readSystemStateSmart(ctx.workspaceRoot, ctx.systemId); let authHeaders = buildAuthHeader(systemState.accessPin); let buildSkipped = false; let buildIdentityPath = ""; let localDistTreeHash = ""; let purgeResult: PurgeResult | undefined; let freshness: FreshnessResult = { verified: false, cdnDistTreeHash: null, localDistTreeHash: "", attempts: 0, }; let healthState: "healthy" | "unhealthy" | "unknown" = "unknown"; let healthChecks: HealthCheck[] = []; let evidenceSynced = false; let evidenceSyncError: string | null = null; let axiomStatus: "pass" | "fail" | "not-run" = "not-run"; let axiomExitCode = 0; let bordbuchCommitted = false; let systemStateUpdated = false; let failingPhase: string | undefined; let releaseSignResult: DeployExecutionResult["releaseSignResult"]; let featureSmokeCheck: FeatureSmokeCheckResult | undefined; let agentContractCheck: AgentContractCheckResult | undefined; let accessReconcile: DeployExecutionResult["accessReconcile"]; const gate = gateForChannel(channel); const _effectRecord = buildEffectRecord( ctx.operationId, ctx.candidateId, gate, channel, ctx.artifactHash, ctx.authResult.ok ? ctx.authResult.outcome.decisionId : "", false, null, "deploying", now, ); try { if (ctx.releaseId) { const releaseDir = path.join(ctx.workspaceRoot, "releases", ctx.releaseId); const distDir = path.join(releaseDir, "dist"); if (!(await io.exists(distDir))) { try { const buildResult = await io.exec("sh", ["-c", "pnpm build"], { cwd: ctx.workspaceRoot, timeoutMs: 600000, }); if (buildResult.exitCode !== 0) { throw new Error( `pnpm build exited ${buildResult.exitCode}: ${buildResult.stderr.slice(-400)}`, ); } } catch (err) { failingPhase = "build"; throw new Error(`Build failed: ${err instanceof Error ? err.message : String(err)}`); } } else { buildSkipped = true; } buildIdentityPath = path.join(distDir, "client", ".well-known", "build-identity.json"); try { if (await io.exists(buildIdentityPath)) { const existing = JSON.parse(await io.readFile(buildIdentityPath)); localDistTreeHash = existing.distTreeHash ?? ""; } if (!localDistTreeHash) { const distTreeHashResult = await fingerprintTree(path.join(distDir, "client"), { mode: "stable", }); localDistTreeHash = distTreeHashResult.value; const identity = { releaseId: ctx.releaseId, distTreeHash: localDistTreeHash, buildTimestamp: now, }; await atomicWriteFile(buildIdentityPath, JSON.stringify(identity, null, 2)); } } catch { // Non-fatal — build-identity is best-effort } } // --- RFC-0931: Signing phase (non-fatal if no key configured) --- if (ctx.releaseId && buildIdentityPath && (await io.exists(buildIdentityPath))) { try { const secretsEnv = ctx.secretsFilePath ? await sourceDotenv(ctx.secretsFilePath) : {}; const signEnv = { ...filterEnv(process.env), ...secretsEnv }; const privateKeyEnv = signEnv["SIGNING_PRIVATE_KEY"]; const privateKeyPath = signEnv["SIGNING_PRIVATE_KEY_PATH"]; const publicKeyUrl = signEnv["PUBLIC_KEY_URL"]; if (privateKeyEnv || privateKeyPath) { let privateKeyBytes: Uint8Array; let privateKeyPem: string; try { if (privateKeyEnv) { privateKeyPem = privateKeyEnv; privateKeyBytes = await loadPrivateKey({ pem: privateKeyEnv }); } else { privateKeyPem = await io.readFile(privateKeyPath!); privateKeyBytes = await loadPrivateKey({ filePath: privateKeyPath!, encoding: "pem", }); } } catch (err) { failingPhase = "release-sign"; throw new Error( `Release signing failed (key load): ${err instanceof Error ? err.message : String(err)}`, ); } const distDir = path.join(ctx.workspaceRoot, "releases", ctx.releaseId, "dist"); const signResult = await signBuildIdentity({ buildIdentityPath, privateKeyBytes, publicKeyUrl, }); await writeReleasePublicKey({ distDir, publicKeyHex: signResult.publicKeyHex, }); try { await signLatestBuildArtifacts({ cwd: ctx.cacheCloneDir, privateKeyPem, publicKeyUrl, }); } catch { // Non-fatal: signed-manifest.json is best-effort in deploy pipeline } releaseSignResult = { buildIdentitySigned: true, signatureHex: signResult.signatureHex, publicKeyUrl: signResult.publicKeyUrl, reusedExistingSignature: signResult.reusedExistingSignature, }; } } catch (err) { failingPhase = "release-sign"; throw new Error( `Release signing failed: ${err instanceof Error ? err.message : String(err)}`, ); } } // --- RFC-1138: DNS upsert phase --- // Moved out of build.prepare — validate pipelines must not mutate external // state. DNS records are upserted here, in the deploy-bound path, before // the wrangler deploy so records exist when the site goes live. try { const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel"); const dnsResult = (await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "dns.record.upsert", argv: [`--site=${ctx.systemId}`], })) as { exitCode?: number; summary?: string }; if (dnsResult.exitCode !== 0) { failingPhase = "dns-upsert"; throw new Error(`dns.record.upsert failed: ${dnsResult.summary ?? "unknown error"}`); } } catch (err) { if (failingPhase !== "dns-upsert") failingPhase = "dns-upsert"; throw err; } const distPath = ctx.releaseId ? path.join(ctx.workspaceRoot, "releases", ctx.releaseId, "dist") : ""; const propagateInput: PropagateInput = { systemId: ctx.systemId, releaseId: ctx.releaseId ?? "", url: deploymentUrl, channel, distPath, workerName: channelConfig.workerName, secretsFilePath: ctx.secretsFilePath, expectedBehaviorSnapshotHash: localDistTreeHash || "", }; let propagateResult: PropagationResult; try { propagateResult = await ctx.adapter.propagate(propagateInput); } catch (err) { failingPhase = "wrangler-deploy"; throw new Error( `Wrangler deploy failed: ${err instanceof Error ? err.message : String(err)}`, ); } if (propagateResult.state !== "succeeded") { failingPhase = "wrangler-deploy"; throw new Error( `Deploy failed: adapter '${ctx.adapter.name}' reported state=${propagateResult.state} (see adapter stderr above)`, ); } const actualDeploymentUrl = propagateResult.deploymentUrl || deploymentUrl; // --- RFC-1284: access-pin reconcile --- // A crashed certify (or failed resume) can leave ACCESS_PIN deleted while // accessPin still declares protection. Re-assert the secret on dev/alt // immediately after a successful propagation — before purge/health/smoke // phases that can throw — so every deploy converges the projection to the // desired state. Fresh state re-read: a concurrent certify may have opened // a suspension during the build/deploy window. The cache clone owns the // marker — non-smart read, same store as the access commands (Q-6). // Reconcile failure is a non-fatal diagnostic — ACCESS-01 in // sternsystem.validate is the backstop. if (channel !== "main") { try { let freshState = await readSystemState(ctx.workspaceRoot, ctx.systemId); // RFC-1325: a provisional workers.dev birth has no pre-declared pin — // the first dev/alt deploy creates protection via access.protect // (which writes the secret itself, so the re-assert below is skipped). if ((freshState?.accessPin ?? null) === null && isProvisionalHost(actualDeploymentUrl)) { const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel"); const protectResult = (await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "leitstand.access.protect", argv: [`--site=${ctx.systemId}`], })) as { exitCode?: number; summary?: string; data?: { pin?: string } }; // undefined exitCode is the already-protected no-op — still success. if ((protectResult.exitCode ?? 0) === 0) { freshState = await readSystemState(ctx.workspaceRoot, ctx.systemId); // The pin the protect call just issued now gates the dev/alt // workers — the pre-deploy headers are stale and would 401 the // health/freshness probes below. authHeaders = buildAuthHeader(freshState?.accessPin); accessReconcile = { ok: true, pinIssued: protectResult.data?.pin }; } else { accessReconcile = { ok: false, error: `leitstand.access.protect failed: ${protectResult.summary ?? "unknown error"}`, }; } } const desiredPin = freshState?.accessPin ?? null; const suspended = (freshState?.accessPinSuspended ?? null) !== null; if (accessReconcile === undefined && desiredPin !== null && !suspended) { const secretsEnv = ctx.secretsFilePath ? await sourceDotenv(ctx.secretsFilePath) : {}; const pinEnv = { ...filterEnv(process.env as Record), ...secretsEnv, }; const putResult = await runWranglerSecretPut( channelConfig.workerName, "ACCESS_PIN", desiredPin, pinEnv, ctx.workspaceRoot, ); accessReconcile = putResult.exitCode === 0 ? { ok: true } : { ok: false, error: putResult.stderr.trim() || putResult.stdout.trim() || `exit code ${putResult.exitCode}`, }; } } catch (err) { accessReconcile = { ok: false, error: err instanceof Error ? err.message : String(err) }; } } if (channel !== "dev" || !isDevWorkersUrl(actualDeploymentUrl)) { if (ctx.adapter.purgeCapable()) { try { purgeResult = await runPurgeStep( ctx.workspaceRoot, ctx.releaseId ?? "", actualDeploymentUrl, ctx.secretsFilePath, ); if (channel !== "dev" && !purgeResult.success) { failingPhase = "cache-purge"; throw new Error(`CDN cache purge failed: ${purgeResult.error ?? "unknown"}`); } } catch (err) { if (channel !== "dev") throw err; // Dev channel: purge failure is non-fatal } } } // RFC-1188: all agent-directed probes of this channel go through one // outbound resolution — health, freshness, and feature-smoke share it. const outbound = await resolveOutboundAgentClient(ctx.workspaceRoot, ctx.systemId); if (channel !== "dev" || !isDevWorkersUrl(actualDeploymentUrl)) { if (localDistTreeHash) { freshness = await verifyFreshness( actualDeploymentUrl, localDistTreeHash, noopLogger, authHeaders, outbound.client.fetch, ); if (!freshness.verified) { failingPhase = "freshness"; throw new Error(`Freshness verification failed: ${freshness.error ?? "unknown"}`); } } } const healthResult = await runHealthCheckWithRetry( ctx.adapter, ctx.systemId, actualDeploymentUrl, channel, ctx.releaseId ?? "", ctx.workspaceRoot, authHeaders, outbound.client.fetch, ); healthState = healthResult.state; healthChecks = healthResult.checks; if (healthState === "unhealthy") { failingPhase = "health-check"; throw new Error("Health check failed — deployment is unhealthy"); } // RFC-1179: blocking smoke gates on every channel — fetch-level status/body // checks (site.smoke.run) then the Playwright runtime gate // (deploy.smoke.run: console errors, pageerror, requestfailed, 4xx/5xx). // Both run unconditionally — dev workers.dev URLs included — so a broken // release never propagates to the next channel. { const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel"); const smokeArgv = [ `--site=${ctx.systemId}`, `--url=${actualDeploymentUrl}`, "--json", ...(ctx.releaseId ? [`--release-id=${ctx.releaseId}`] : []), ...(ctx.commitSha ? [`--commit-sha=${ctx.commitSha}`] : []), ]; const siteSmokeResult = (await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "site.smoke.run", argv: smokeArgv, })) as { exitCode?: number; summary?: string }; if ((siteSmokeResult.exitCode ?? 1) !== 0) { failingPhase = "site-smoke"; throw new Error(`site.smoke.run failed: ${siteSmokeResult.summary ?? "unknown error"}`); } const deploySmokeResult = (await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "deploy.smoke.run", argv: smokeArgv, })) as { exitCode?: number; summary?: string }; if ((deploySmokeResult.exitCode ?? 1) !== 0) { failingPhase = "deploy-smoke"; throw new Error(`deploy.smoke.run failed: ${deploySmokeResult.summary ?? "unknown error"}`); } } // RFC-0948: Post-deploy feature smoke check — non-fatal warning if (channel !== "dev" || !isDevWorkersUrl(actualDeploymentUrl)) { featureSmokeCheck = await runFeatureSmokeCheck( actualDeploymentUrl, authHeaders, ctx.workspaceRoot, ctx.releaseId, outbound.client.fetch, ); } // RFC-0954: Warm the search index — non-fatal on failure try { const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel"); await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "agent.search.warm", argv: [`--site=${ctx.systemId}`], }); } catch { // Non-fatal — search index warm-up is best-effort } // RFC-1116: External agent-surface contract check — non-fatal, recorded as // deploy evidence; certification consumes the result like other smoke data. try { const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel"); const contractResult = (await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "agent.surface.contract.check", argv: [`--url=${actualDeploymentUrl}`], })) as { data?: AgentContractCheckResult }; agentContractCheck = contractResult.data; } catch { // Non-fatal — contract check is best-effort evidence } if (channel === "dev" && ctx.missionId && ctx.commitSha) { try { const missionResult = await runMissionCheckWithResilience( ctx.workspaceRoot, ctx.missionId, actualDeploymentUrl, ctx.commitSha, noopLogger, ); axiomExitCode = missionResult.exitCode; axiomStatus = missionResult.exitCode === 0 ? "pass" : "fail"; if (missionResult.exitCode === 1) { failingPhase = "mission-check"; throw new Error("mission.check failed with content violations (exit 1)"); } } catch (err) { if (err instanceof Error && err.message.includes("mission.check")) { throw err; } // Infrastructure error — non-fatal for pipeline, logged } } if (channel === "alt" && ctx.missionId && ctx.commitSha) { try { const evidenceDir = path.join( ctx.workspaceRoot, "missions", ctx.missionId, "workpiece", "evidence", ); const metadataPath = path.join(evidenceDir, "evidence-metadata.json"); const studyRunPath = path.join(evidenceDir, "study-run.json"); if ((await io.exists(metadataPath)) && (await io.exists(studyRunPath))) { const metadata = JSON.parse(await io.readFile(metadataPath)); const studyRun = JSON.parse(await io.readFile(studyRunPath)); if (metadata.commitSha !== ctx.commitSha || studyRun.missionId !== ctx.missionId) { failingPhase = "axiom-evidence-gate"; throw new Error( `Axiom evidence gate: commitSha or missionId mismatch (expected sha=${ctx.commitSha}, mission=${ctx.missionId})`, ); } } } catch (err) { if (failingPhase === "axiom-evidence-gate") throw err; // Non-fatal — evidence files may not exist } } if (channel === "main") { const altChannelConfig = getChannelConfig(ctx.systemConfig, "alt"); const altUrl = altChannelConfig.url ?? ""; if (altUrl) { const altHealth = await runHealthCheckWithRetry( ctx.adapter, ctx.systemId, altUrl, "alt", ctx.releaseId ?? "", ctx.workspaceRoot, authHeaders, outbound.client.fetch, ); if (altHealth.state !== "healthy") { failingPhase = "alt-health-check"; throw new Error( `Alt channel is not healthy (state=${altHealth.state}) — cannot promote to main`, ); } } } // RFC-1190: evidence archival is fail-closed once axiom evidence exists — // previously failures were swallowed into a field nothing surfaced while // deployState reported "succeeded" (DNA-106). Absent evidence dir or // missing evidence-metadata.json is a clean no-op, not a failure. if (!ctx.skipEvidenceSync && ctx.missionId) { const { resolveMissionDir } = await import("../kernel/mission-resolver.ts"); const missionDir = await resolveMissionDir(ctx.workspaceRoot, ctx.missionId, io); const axiomEvidenceDir = path.join(missionDir, "evidence", "axiom"); const hasAxiomEvidence = (await io.exists(axiomEvidenceDir)) && (await io.exists(path.join(axiomEvidenceDir, "evidence-metadata.json"))); if (hasAxiomEvidence) { try { const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel"); const syncResult = (await executeKernelCommand({ workspaceRoot: ctx.workspaceRoot, commandName: "evidence.sync", argv: [`--site=${ctx.systemId}`, `--mission=${ctx.missionId}`], })) as | { exitCode?: number; summary?: string } | Array<{ exitCode?: number; summary?: string }>; const reports = Array.isArray(syncResult) ? syncResult : [syncResult]; const failed = reports.find((r) => (r?.exitCode ?? 0) !== 0); if (failed) throw new Error(failed?.summary ?? "evidence.sync exited non-zero"); evidenceSynced = true; } catch (err) { evidenceSyncError = err instanceof Error ? err.message : String(err); failingPhase = "evidence-sync"; throw new Error(`EVIDENCE_SYNC_FAILED: ${evidenceSyncError}`); } } } try { await appendAndCommitBordbuch( ctx.workspaceRoot, ctx.systemId, "deployment", `${gate} deployed to ${channel}`, "leitstand", ); bordbuchCommitted = true; } catch { // Non-fatal } try { const state = await readSystemStateSmart(ctx.workspaceRoot, ctx.systemId); if (state) { const channelKey = channel as "dev" | "alt" | "main"; if (!state.lastPropagated) state.lastPropagated = {} as never; (state.lastPropagated as Record)[channelKey] = { releaseId: ctx.releaseId ?? "", at: now, healthy: true, state: "succeeded", operationId: ctx.operationId, leaseExpiresAt: null, workerVersionId: propagateResult.workerVersionId, }; await writeSystemStateSmart(ctx.workspaceRoot, ctx.systemId, state); systemStateUpdated = true; } } catch { // Non-fatal } const finalEffectRecord = buildEffectRecord( ctx.operationId, ctx.candidateId, gate, channel, ctx.artifactHash, ctx.authResult.ok ? ctx.authResult.outcome.decisionId : "", false, null, "succeeded", now, propagateResult.workerVersionId, ctx.releaseId ?? undefined, ); await writeDeploymentEffectRecord( ctx.cacheCloneDir, finalEffectRecord, actualDeploymentUrl, propagateResult.workerVersionId, ctx.releaseId ?? undefined, ); return { deploymentUrl: actualDeploymentUrl, buildSkipped, buildIdentity: { releaseId: ctx.releaseId ?? "", written: await io.exists(buildIdentityPath), path: buildIdentityPath, }, releaseSignResult, freshness, purgeResult, healthState, healthChecks, featureSmokeCheck, agentContractCheck, effectRecord: finalEffectRecord, bordbuchCommitted, systemStateUpdated, evidenceSynced, evidenceSyncError, axiomStatus, axiomExitCode, accessReconcile, missionContext: ctx.missionContext, }; } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); const failedEffectRecord = buildEffectRecord( ctx.operationId, ctx.candidateId, gate, channel, ctx.artifactHash, ctx.authResult.ok ? ctx.authResult.outcome.decisionId : "", false, null, "failed", now, ); await writeDeploymentEffectRecord(ctx.cacheCloneDir, failedEffectRecord, deploymentUrl); return { deploymentUrl, buildSkipped, buildIdentity: { releaseId: ctx.releaseId ?? "", written: await io.exists(buildIdentityPath), path: buildIdentityPath, }, releaseSignResult, freshness, purgeResult, healthState, healthChecks, featureSmokeCheck, agentContractCheck, effectRecord: failedEffectRecord, bordbuchCommitted, systemStateUpdated, evidenceSynced, evidenceSyncError, axiomStatus, axiomExitCode, accessReconcile, missionContext: ctx.missionContext, failingPhase: failingPhase ?? "unknown", errorMessage, }; } }