/* RFC-1284: Tests for the post-propagate ACCESS_PIN reconcile in executeDeployPhases — re-asserts the declared pin on dev/alt channels after a successful deploy, skips suspended/unprotected/main, and degrades failures to a non-fatal diagnostic on the result. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-1284: initial — deploy-time ACCESS_PIN reconcile coverage (AC-7). 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-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-1284 */ import { afterEach, beforeEach, test, expect, vi } from "vitest"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { executeDeployPhases } from "../deploy-execution.ts"; import { verifyFreshness } from "../leitstand-commands.ts"; import type { DeployExecutionContext } from "../deploy-execution.ts"; import type { DeploymentAdapter, PropagateInput, RollbackInput, RollbackResult, HealthInput, } from "../adapter.ts"; import type { PropagationResult, HealthCheck } from "@warpgogol/werkstatt-engine/schemas"; import type { AuthorizeResult } from "../deploy-helpers.ts"; import type { DeploymentStaticConfig } from "@warpgogol/werkstatt-engine/schemas"; import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint"; let deploySystemState: Record = {}; let wranglerSecretPutCalls: Array<{ workerName: string; secretName: string; value: string }> = []; let wranglerSecretPutExitCode = 0; vi.mock("../leitstand-commands.ts", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, verifyFreshness: vi.fn().mockResolvedValue({ verified: false, cdnDistTreeHash: null, }), }; }); // RFC-1138: dns.record.upsert runs via executeKernelCommand — mock so the tmp // workspace deploy reaches propagate. vi.mock("@warpgogol/werkstatt-engine/kernel", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, executeKernelCommand: vi.fn().mockResolvedValue({ exitCode: 0, summary: "ok" }), }; }); vi.mock("../../sternsystem/registry-io.ts", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, // Reconcile reads the cache-clone store (non-smart) — REVIEW-CODE-2026-10-15-02 F1/Q-6. readSystemState: vi.fn(async () => deploySystemState), readSystemStateSmart: vi.fn(async () => deploySystemState), writeSystemStateSmart: vi.fn().mockResolvedValue(undefined), }; }); vi.mock("../adapters/wrangler-secrets.ts", () => ({ runWranglerSecretPut: vi.fn(async (workerName: string, secretName: string, value: string) => { wranglerSecretPutCalls.push({ workerName, secretName, value }); return { exitCode: wranglerSecretPutExitCode, stdout: "", stderr: "" }; }), runWranglerSecretDelete: vi.fn().mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" }), })); function makeAdapter(): DeploymentAdapter { return { name: "cloudflare-workers", async propagate(input: PropagateInput): Promise { return { systemId: input.systemId, releaseId: input.releaseId, state: "succeeded", deploymentUrl: input.url, startedAt: new Date().toISOString(), completedAt: new Date().toISOString(), healthChecks: [], }; }, async rollback(input: RollbackInput): Promise { return { systemId: input.systemId, channel: input.channel, state: "succeeded", workerName: input.workerName, startedAt: new Date().toISOString(), completedAt: new Date().toISOString(), stdout: "", stderr: "", }; }, async health( _input: HealthInput, ): Promise<{ state: "healthy" | "unhealthy" | "unknown"; checks: HealthCheck[] }> { return { state: "healthy", checks: [] }; }, getLimits() { return { maxTotalSize: 100 * 1024 * 1024, maxFileSize: 10 * 1024 * 1024 }; }, purgeCapable() { return false; }, bootSmokeCapable() { return false; }, }; } const systemConfig: DeploymentStaticConfig = { adapter: "cloudflare-workers", channels: { // RFC-1325: custom-domain dev channel — a workers.dev URL would trigger provisional auto-protect. dev: { workerName: "test-dev", url: "https://dev.example.com" }, alt: { workerName: "test-alt", url: "https://alt.example.com" }, main: { workerName: "test-main", url: "https://main.example.com" }, }, }; const authResult: AuthorizeResult = { ok: true, outcome: { ok: true, authorized: true, gate: "dev-deploy", channel: "dev", candidateId: "test-sys", decisionId: "gd-000001", requiresMainVerification: false, requiresDurableSync: false, }, gateDecision: { schema: "werkstatt/gate-decision@1", decisionId: "gd-000001", candidateId: "test-sys", policyBundleRoot: "sha256:0000000000000000000000000000000000000000000000000000000000000000" as Sha256Digest, gate: "dev", evaluationCut: 1, selectedEvidence: [], status: "pass", coverage: { schema: "werkstatt/coverage-report@1", totalRequirements: 9, coveredRequirements: 9, uncoveredRequirements: [], }, reasons: [], actionPackRef: null, decidedAt: new Date().toISOString(), }, }; function makeCtx(adapter: DeploymentAdapter, workspaceRoot: string): DeployExecutionContext { return { systemId: "test-sys", releaseId: "r000001", candidateId: "test-sys", artifactHash: "sha256:abc" as Sha256Digest, authResult, workspaceRoot, cacheCloneDir: join(workspaceRoot, "..", "systems-cache", "test-sys"), systemConfig, adapter, operationId: "op-test", gateDecisionPath: "/tmp/gd.json", secretsFilePath: undefined, skipEvidenceSync: true, forceBuild: false, }; } function makeWorkspace(): string { const tmp = mkdtempSync(join(tmpdir(), "leitstand-reconcile-test-")); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); writeFileSync(join(tmp, "package.json"), JSON.stringify({ version: "1.0.0" }) + "\n"); const distDir = join(tmp, "releases", "r000001", "dist", "client"); mkdirSync(distDir, { recursive: true }); return tmp; } beforeEach(() => { vi.clearAllMocks(); wranglerSecretPutCalls = []; wranglerSecretPutExitCode = 0; deploySystemState = { schemaVersion: "1.0.0", systemId: "test-sys", currentMission: null, lastRelease: null, lastPropagated: {}, accessPin: null, accessPinSuspended: null, passportRequired: false, ownershipRequired: false, }; // Same kernel-env leakage guard as deploy-execution-purge.test.ts. vi.stubEnv("SIGNING_PRIVATE_KEY", ""); vi.stubEnv("SIGNING_PRIVATE_KEY_PATH", ""); vi.stubEnv("PUBLIC_KEY_URL", ""); }); afterEach(() => { vi.unstubAllEnvs(); }); test("RFC-1284: dev deploy reconciles ACCESS_PIN when protection is desired and not suspended", async () => { const tmp = makeWorkspace(); try { deploySystemState = { ...deploySystemState, accessPin: "4092" }; const result = await executeDeployPhases(makeCtx(makeAdapter(), tmp), "dev"); expect( wranglerSecretPutCalls, "successful dev deploy must re-assert ACCESS_PIN from the declared accessPin (AC-7)", ).toHaveLength(1); expect(wranglerSecretPutCalls[0]).toMatchObject({ workerName: "test-dev", secretName: "ACCESS_PIN", value: "4092", }); expect(result.accessReconcile).toEqual({ ok: true }); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } }); test("RFC-1284: reconcile is skipped while a suspension marker is open", async () => { const tmp = makeWorkspace(); try { deploySystemState = { ...deploySystemState, accessPin: "4092", accessPinSuspended: { at: "2026-10-15T10:00:00.000Z", by: "leitstand.certify", operationId: null, }, }; const result = await executeDeployPhases(makeCtx(makeAdapter(), tmp), "dev"); expect(wranglerSecretPutCalls).toHaveLength(0); expect(result.accessReconcile).toBeUndefined(); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } }); test("RFC-1284: reconcile is skipped when accessPin is null (deliberately unprotected)", async () => { const tmp = makeWorkspace(); try { const result = await executeDeployPhases(makeCtx(makeAdapter(), tmp), "dev"); expect(wranglerSecretPutCalls).toHaveLength(0); expect(result.accessReconcile).toBeUndefined(); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } }); test("RFC-1284: reconcile never runs on main — production is never protected", async () => { const tmp = makeWorkspace(); try { deploySystemState = { ...deploySystemState, accessPin: "4092" }; const result = await executeDeployPhases(makeCtx(makeAdapter(), tmp), "main"); expect(wranglerSecretPutCalls).toHaveLength(0); expect(result.accessReconcile).toBeUndefined(); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } }); test("RFC-1284: alt deploy reconciles ACCESS_PIN", async () => { const tmp = makeWorkspace(); try { deploySystemState = { ...deploySystemState, accessPin: "4092" }; const result = await executeDeployPhases(makeCtx(makeAdapter(), tmp), "alt"); expect(wranglerSecretPutCalls).toHaveLength(1); expect(wranglerSecretPutCalls[0].workerName).toBe("test-alt"); expect(result.accessReconcile).toEqual({ ok: true }); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } }); test("RFC-1284: reconcile re-reads state AFTER propagation — a suspension opened mid-deploy is respected", async () => { const tmp = makeWorkspace(); try { // Desired pin set, no suspension yet — a naive pre-deploy read would // re-assert the pin. Simulate a concurrent certify opening a suspension // while the adapter propagates. deploySystemState = { ...deploySystemState, accessPin: "4092" }; const adapter = makeAdapter(); const originalPropagate = adapter.propagate; adapter.propagate = async (input) => { const res = await originalPropagate.call(adapter, input); deploySystemState = { ...deploySystemState, accessPinSuspended: { at: new Date().toISOString(), by: "leitstand.certify", operationId: "op-concurrent", }, }; return res; }; const result = await executeDeployPhases(makeCtx(adapter, tmp), "dev"); expect( wranglerSecretPutCalls, "reconcile must read fresh state — the mid-deploy suspension suppresses the re-assert", ).toHaveLength(0); expect(result.accessReconcile).toBeUndefined(); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } }); test("RFC-1284: reconcile failure is non-fatal — result records the diagnostic, deploy continues", async () => { const tmp = makeWorkspace(); try { deploySystemState = { ...deploySystemState, accessPin: "4092" }; wranglerSecretPutExitCode = 1; // Custom-domain dev channel runs the freshness gate — verify it passes so // the reconcile failure is the only diagnostic on the result. vi.mocked(verifyFreshness).mockResolvedValueOnce({ verified: true, cdnDistTreeHash: "hash", localDistTreeHash: "hash", attempts: 1, }); const result = await executeDeployPhases(makeCtx(makeAdapter(), tmp), "dev"); expect(result.accessReconcile?.ok).toBe(false); expect( result.accessReconcile?.error, "reconcile failure must surface on the result — ACCESS-01 is the backstop", ).toBeDefined(); // Non-fatal: the deploy itself still reports healthy phases. expect(result.effectRecord.state).toBe("succeeded"); } finally { rmSync(tmp, { recursive: true, force: true }); rmSync(resolve(tmp, "..", "systems-cache"), { recursive: true, force: true }); } });