import assert from "node:assert/strict"; import test from "node:test"; import { execFile } from "node:child_process"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { buildLockAnnotations, classifyDeployLock, deployLockHeldFailure, parseDeployLock, runDevCdApply, verifyDevLive } from "./dev-cd-apply.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); function iso(offsetMs = 0) { return new Date(Date.parse("2026-05-23T08:00:00.000Z") + offsetMs).toISOString(); } function leaseFromLock(lock) { return { apiVersion: "coordination.k8s.io/v1", kind: "Lease", metadata: { name: "hwlab-dev-cd-lock", namespace: "hwlab-dev", resourceVersion: "1", annotations: buildLockAnnotations(lock) }, spec: { holderIdentity: `${lock.ownerTaskId}/${lock.transactionId}`, leaseDurationSeconds: lock.ttlSeconds, acquireTime: lock.startedAt, renewTime: lock.updatedAt } }; } function parsePatch(args) { const index = args.indexOf("-p"); assert.notEqual(index, -1, "kubectl patch must include -p"); return JSON.parse(args[index + 1]); } async function makeRepo(options = {}) { const commitId = options.commitId ?? "abc1234"; const repoRoot = await fsTempDir(); await mkdir(path.join(repoRoot, "deploy/k8s/base"), { recursive: true }); await mkdir(path.join(repoRoot, "reports/dev-gate"), { recursive: true }); await writeFile( path.join(repoRoot, "deploy/deploy.json"), `${JSON.stringify({ manifestVersion: "v1", environment: "dev", namespace: "hwlab-dev", endpoint: "http://74.48.78.17:16667", commitId, services: [ { serviceId: "hwlab-cloud-api", image: `127.0.0.1:5000/hwlab/hwlab-cloud-api:${commitId.slice(0, 7)}`, namespace: "hwlab-dev", profile: "dev", env: { HWLAB_COMMIT_ID: commitId.slice(0, 7) } }, { serviceId: "hwlab-cloud-web", image: `127.0.0.1:5000/hwlab/hwlab-cloud-web:${commitId.slice(0, 7)}`, namespace: "hwlab-dev", profile: "dev", env: { HWLAB_COMMIT_ID: commitId.slice(0, 7) } } ] }, null, 2)}\n` ); await writeFile( path.join(repoRoot, "deploy/artifact-catalog.dev.json"), `${JSON.stringify({ catalogVersion: "v1", kind: "hwlab-artifact-catalog", environment: "dev", profile: "dev", namespace: "hwlab-dev", endpoint: "http://74.48.78.17:16667", commitId, artifactState: "published", publish: { ciPublished: true, registryVerified: true, provenance: "reports/dev-gate/dev-artifacts.json" }, services: [ { serviceId: "hwlab-cloud-api", commitId, image: `127.0.0.1:5000/hwlab/hwlab-cloud-api:${commitId.slice(0, 7)}`, imageTag: commitId.slice(0, 7), digest: "sha256:b3713aa07164a9ff79a436bd583552427de42922835a5baff50484f53d98f296", publishState: "published", profile: "dev", namespace: "hwlab-dev", artifactRequired: true }, { serviceId: "hwlab-cloud-web", commitId, image: `127.0.0.1:5000/hwlab/hwlab-cloud-web:${commitId.slice(0, 7)}`, imageTag: commitId.slice(0, 7), digest: "sha256:3be80fcfa769d68b0a7ec44e9215b93546d01bf23411533c5cb4888438f2bd84", publishState: "published", profile: "dev", namespace: "hwlab-dev", artifactRequired: true } ] }, null, 2)}\n` ); await writeFile( path.join(repoRoot, "reports/dev-gate/dev-artifacts.json"), `${JSON.stringify({ reportVersion: "v1", taskId: "dev-artifact-publish", status: "published", commitId, artifactPublish: { status: "published", mode: "publish", sourceCommitId: commitId, registryPrefix: "127.0.0.1:5000/hwlab", serviceCount: 2, requiredServiceCount: 2, publishedCount: 2, generatedAt: iso(), services: [ { serviceId: "hwlab-cloud-api", status: "published", artifactRequired: true, image: `127.0.0.1:5000/hwlab/hwlab-cloud-api:${commitId.slice(0, 7)}`, digest: "sha256:b3713aa07164a9ff79a436bd583552427de42922835a5baff50484f53d98f296" }, { serviceId: "hwlab-cloud-web", status: "published", artifactRequired: true, image: `127.0.0.1:5000/hwlab/hwlab-cloud-web:${commitId.slice(0, 7)}`, digest: "sha256:3be80fcfa769d68b0a7ec44e9215b93546d01bf23411533c5cb4888438f2bd84" } ] } }, null, 2)}\n` ); await writeFile(path.join(repoRoot, "deploy/k8s/base/workloads.yaml"), `# fixture ${commitId}\n`); return repoRoot; } async function fsTempDir() { return await import("node:fs/promises").then((fs) => fs.mkdtemp(path.join(os.tmpdir(), "hwlab-cd-"))); } function makeHttpGetJson() { return async (url) => { if (url.includes("16666")) { return { statusCode: 200, json: { serviceId: "hwlab-cloud-web", environment: "dev", status: "ok", revision: "abc1234", image: { reference: "127.0.0.1:5000/hwlab/hwlab-cloud-web:abc1234", tag: "abc1234" }, observedAt: iso() } }; } return { statusCode: 200, json: { serviceId: "hwlab-cloud-api", environment: "dev", status: "degraded", ready: false, commit: { id: "abc1234", source: "runtime-env" }, image: { reference: "127.0.0.1:5000/hwlab/hwlab-cloud-api:abc1234", tag: "abc1234" }, blockerCodes: ["runtime_durable_adapter_auth_blocked"], observedAt: iso() } }; }; } const publishedCommit = "abc1234abc1234abc1234abc1234abc1234abc1"; const laterControlCommit = "def5678def5678def5678def5678def5678def5"; const artifactMergeCommit = "fedcba9fedcba9fedcba9fedcba9fedcba9fedc"; const artifactReportCommit = "a77efa1a77efa1a77efa1a77efa1a77efa1a77e"; function makeRunCommand({ heldLock = null, commandLog = [], targetCommitId = publishedCommit, headCommitId = targetCommitId, extraGitRefs = {}, commitParents = {}, latestArtifactMergeCommit = null, changedPaths = [ "deploy/artifact-catalog.dev.json", "deploy/deploy.json", "deploy/k8s/base/workloads.yaml", "reports/dev-gate/dev-artifacts.json" ], desiredStatePromotionStatus = "pass", runtimeJobLogSuffix = "", kubeContext = "d601", kubeServer = "https://d601:6443", kubeNodes = ["d601"], bareKubeContext = null, bareKubeServer = null, bareKubeNodes = null, bareHwlabDevNamespace = false, secretRefs = { "hwlab-cloud-api-dev-db": ["database-url"], "hwlab-cloud-api-dev-db-admin": ["admin-url"], "hwlab-code-agent-provider": ["openai-api-key"] } } = {}) { let lease = heldLock ? leaseFromLock(heldLock) : null; const assertLeaseMicroTime = (value) => { assert.match(value, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/u); }; return async (command, args, options = {}) => { commandLog.push({ command, args, env: options.env ?? {}, input: options.input ?? "" }); if (command === "git" && args[0] === "rev-parse" && args.includes("origin/main^{commit}")) { return { code: 0, stdout: `${targetCommitId}\n`, stderr: "" }; } if (command === "git" && args[0] === "rev-parse" && args.includes("--short=7")) { const ref = String(args.at(-1) ?? ""); const commitId = extraGitRefs[ref] ?? ref; return { code: 0, stdout: `${commitId.slice(0, 7)}\n`, stderr: "" }; } if (command === "git" && args[0] === "rev-parse" && args.includes("HEAD^{commit}")) { return { code: 0, stdout: `${headCommitId}\n`, stderr: "" }; } if (command === "git" && args[0] === "rev-parse" && args[1] === "--verify") { const ref = String(args[2] ?? "").replace(/\^\{commit\}$/u, ""); const commitId = extraGitRefs[ref]; if (commitId) return { code: 0, stdout: `${commitId}\n`, stderr: "" }; return { code: 1, stdout: "", stderr: `fatal: ambiguous argument '${ref}'` }; } if (command === "git" && args[0] === "log" && args.includes("--first-parent")) { return latestArtifactMergeCommit ? { code: 0, stdout: `${latestArtifactMergeCommit}\n`, stderr: "" } : { code: 1, stdout: "", stderr: "no matching commits" }; } if (command === "git" && args[0] === "rev-list" && args.includes("--parents")) { const ref = String(args.at(-1) ?? ""); const parents = commitParents[ref] ?? []; return { code: 0, stdout: `${[ref, ...parents].join(" ")}\n`, stderr: "" }; } if (command === "git" && args[0] === "diff-tree") { return { code: 0, stdout: `${changedPaths.join("\n")}\n`, stderr: "" }; } if (command === "which" && args[0] === "kubectl") { return { code: 0, stdout: "/usr/local/bin/kubectl\n", stderr: "" }; } if (command.includes("kubectl") && args[0] === "config" && args[1] === "current-context") { const hasKubeconfig = Object.hasOwn(options.env ?? {}, "KUBECONFIG"); return { code: 0, stdout: `${hasKubeconfig ? kubeContext : (bareKubeContext ?? kubeContext)}\n`, stderr: "" }; } if (command.includes("kubectl") && args[0] === "config" && args[1] === "view") { const hasKubeconfig = Object.hasOwn(options.env ?? {}, "KUBECONFIG"); return { code: 0, stdout: `${hasKubeconfig ? kubeServer : (bareKubeServer ?? kubeServer)}\n`, stderr: "" }; } if (command.includes("kubectl") && args[0] === "get" && args[1] === "nodes") { const hasKubeconfig = Object.hasOwn(options.env ?? {}, "KUBECONFIG"); return { code: 0, stdout: `${(hasKubeconfig ? kubeNodes : (bareKubeNodes ?? kubeNodes)).join(" ")}\n`, stderr: "" }; } if (command.includes("kubectl") && args[0] === "get" && args[1] === "namespace") { return bareHwlabDevNamespace ? { code: 0, stdout: "namespace/hwlab-dev\n", stderr: "" } : { code: 1, stdout: "", stderr: "Error from server (NotFound): namespaces \"hwlab-dev\" not found" }; } if (command.includes("kubectl") && args.includes("get") && args.includes("secret")) { const name = args[args.indexOf("secret") + 1]; if (!secretRefs[name]) { return { code: 1, stdout: "", stderr: `Error from server (NotFound): secrets "${name}" not found` }; } return { code: 0, stdout: `secret/${name}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("describe") && args.includes("secret")) { const name = args[args.indexOf("secret") + 1]; const keys = secretRefs[name]; if (!keys) { return { code: 1, stdout: "", stderr: `Error from server (NotFound): secrets "${name}" not found` }; } return { code: 0, stdout: `Name: ${name}\nNamespace: hwlab-dev\nType: Opaque\n\nData\n====\n${keys.map((key) => `${key}: 16 bytes`).join("\n")}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("get") && args.includes("lease")) { if (!lease) return { code: 1, stdout: "", stderr: "Error from server (NotFound): leases.coordination.k8s.io \"hwlab-dev-cd-lock\" not found" }; return { code: 0, stdout: `${JSON.stringify(lease)}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("create")) { lease = JSON.parse(options.input); assertLeaseMicroTime(lease.spec.acquireTime); assertLeaseMicroTime(lease.spec.renewTime); lease.metadata.resourceVersion = "2"; return { code: 0, stdout: `${JSON.stringify(lease)}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("replace")) { const nextLease = JSON.parse(options.input); assert.equal(nextLease.metadata?.resourceVersion, lease?.metadata?.resourceVersion); if (nextLease.spec?.acquireTime) assertLeaseMicroTime(nextLease.spec.acquireTime); if (nextLease.spec?.renewTime) assertLeaseMicroTime(nextLease.spec.renewTime); lease = nextLease; lease.metadata.resourceVersion = String(Number(lease.metadata.resourceVersion ?? "2") + 1); return { code: 0, stdout: `${JSON.stringify(lease)}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("apply") && args.includes("-f") && args.includes("-")) { const manifest = JSON.parse(options.input); assert.equal(manifest.kind, "Job"); assert.equal(manifest.metadata.namespace, "hwlab-dev"); assert.ok(manifest.metadata.name.startsWith("hwlab-runtime-")); assert.equal(manifest.spec.template.spec.containers[0].env.some((entry) => entry.name === "HWLAB_CLOUD_DB_URL" && entry.valueFrom?.secretKeyRef?.name === "hwlab-cloud-api-dev-db"), true); assert.equal(manifest.spec.template.spec.containers[0].env.some((entry) => entry.name === "HWLAB_CLOUD_DB_ADMIN_URL" && entry.valueFrom?.secretKeyRef?.name === "hwlab-cloud-api-dev-db-admin"), true); assert.equal(JSON.stringify(manifest).includes("database-url"), true); assert.equal(JSON.stringify(manifest).includes("postgresql://"), false); return { code: 0, stdout: JSON.stringify(manifest), stderr: "" }; } if (command.includes("kubectl") && args.includes("wait") && args.some((arg) => String(arg).startsWith("job/hwlab-runtime-"))) { return { code: 0, stdout: "job.batch/hwlab-runtime complete\n", stderr: "" }; } if (command.includes("kubectl") && args.includes("logs") && args.some((arg) => String(arg).startsWith("job/hwlab-runtime-provision"))) { return { code: 0, stdout: `${runtimeJobLogSuffix}${JSON.stringify({ conclusion: { status: "ready" }, safety: { secretValuesPrinted: false } }, null, 2)}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("logs") && args.some((arg) => String(arg).startsWith("job/hwlab-runtime-migrate"))) { return { code: 0, stdout: `${runtimeJobLogSuffix}${JSON.stringify({ conclusion: { status: "ready" }, safety: { secretValuesPrinted: false } }, null, 2)}\n`, stderr: "" }; } if (command.includes("kubectl") && args.includes("patch")) { assert.ok(lease, "lease must exist before patch"); const patch = parsePatch(args); lease.metadata.annotations = { ...lease.metadata.annotations, ...(patch.metadata?.annotations ?? {}) }; lease.spec = { ...lease.spec, ...(patch.spec ?? {}) }; lease.metadata.resourceVersion = String(Number(lease.metadata.resourceVersion ?? "2") + 1); return { code: 0, stdout: `${JSON.stringify(lease)}\n`, stderr: "" }; } if ((command === process.execPath || command.endsWith("/node")) && args.includes("scripts/deploy-desired-state-plan.mjs") && args.includes("--promotion-commit")) { const promotionCommit = args[args.indexOf("--promotion-commit") + 1]; const status = desiredStatePromotionStatus; return { code: status === "pass" ? 0 : 1, stdout: `${JSON.stringify({ status, summary: { desiredCommitId: promotionCommit.slice(0, 7), artifactState: "published", ciPublished: true, registryVerified: true, blockers: status === "pass" ? 0 : 1 }, target: { convergence: { state: status === "pass" ? "already_promoted" : "promotion_mismatch" } }, diagnostics: status === "pass" ? [] : [{ code: "promotion_commit_mismatch", path: "desired-state", message: "fixture mismatch" }] })}\n`, stderr: "" }; } if (command === process.execPath || command.endsWith("/node")) { return { code: 0, stdout: JSON.stringify({ ok: true, command: args.join(" ") }), stderr: "" }; } return { code: 0, stdout: "", stderr: "" }; }; } function assertNoReadOnlySideEffects(commandLog) { const mutatingKubectlVerbs = new Set(["apply", "create", "replace", "patch", "delete", "rollout", "wait", "logs"]); assert.equal(commandLog.some((entry) => entry.command === "ps"), false); assert.equal(commandLog.some((entry) => entry.command === "kill"), false); assert.equal(commandLog.some((entry) => entry.command.includes("kubectl") && entry.args.some((arg) => mutatingKubectlVerbs.has(arg)) ), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/refresh-artifact-catalog.mjs")), false); } function assertNoWriteSideEffects(commandLog) { const writeKubectlVerbs = new Set(["apply", "create", "replace", "patch", "delete", "rollout", "wait", "logs"]); assert.equal(commandLog.some((entry) => entry.command.includes("kubectl") && entry.args.some((arg) => writeKubectlVerbs.has(arg)) ), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/refresh-artifact-catalog.mjs")), false); } test("lock classifier reports held and stale states with retryAfterSeconds", () => { const lock = { ownerTaskId: "task-a", transactionId: "tx-a", promotionCommit: "abc1234", deployJsonHash: "sha256:a", phase: "applying", startedAt: iso(0), updatedAt: iso(0), ttlSeconds: 300, targetNamespace: "hwlab-dev" }; const parsed = parseDeployLock(leaseFromLock(lock)); const held = classifyDeployLock(parsed, new Date(iso(120000))); assert.equal(held.held, true); assert.equal(held.stale, false); assert.equal(held.retryAfterSeconds, 180); const stale = classifyDeployLock(parsed, new Date(iso(301000))); assert.equal(stale.held, false); assert.equal(stale.stale, true); assert.equal(stale.retryAfterSeconds, 0); }); test("dev-cd status and dry-run are read-only compact JSON", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--dry-run", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); assert.equal(code, 0); assert.equal(status.mode, "dry-run"); assert.equal(status.mutationAttempted, false); assert.equal(status.prodTouched, false); assert.equal(status.lock.status, "absent"); assert.equal(status.deployJson.manifest, undefined); assert.equal(status.deployJson.matchesTarget, true); assert.equal(status.live.summary.checked, 2); assertNoReadOnlySideEffects(commandLog); await assert.rejects( readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"), { code: "ENOENT" } ); }); test("dev-cd default status is read-only compact JSON", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); assert.equal(code, 0); assert.equal(status.mode, "status"); assert.equal(status.mutationAttempted, false); assert.equal(status.prodTouched, false); assert.equal(status.target.shortCommitId, "abc1234"); assert.equal(status.deployJson.hash.startsWith("sha256:"), true); assert.equal(status.deployJson.matchesTarget, true); assert.equal(status.lock.status, "absent"); assertNoReadOnlySideEffects(commandLog); }); test("dev-cd status accepts a published deploy-json promotion under a later control HEAD", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); const commandLog = []; let output = ""; const code = await runDevCdApply(["--status", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml"], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, targetCommitId: laterControlCommit, headCommitId: laterControlCommit, extraGitRefs: { abc1234: publishedCommit }, latestArtifactMergeCommit: artifactMergeCommit, commitParents: { [artifactMergeCommit]: [publishedCommit, artifactReportCommit], [artifactReportCommit]: [publishedCommit] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); assert.equal(code, 0); assert.equal(status.status, "pass"); assert.equal(status.target.promotionCommit, publishedCommit); assert.equal(status.target.shortCommitId, "abc1234"); assert.equal(status.target.promotionSource, "deploy-json"); assert.equal(status.target.publishRequired, false); assert.equal(status.target.controlRef.shortCommitId, "def5678"); assert.equal(status.target.artifactBoundary.status, "pass"); assert.equal(status.target.artifactBoundary.artifactMergeCommit.shortCommitId, "fedcba9"); assert.equal(status.target.artifactBoundary.artifactMergeCommit.promotionParentMatches, true); assert.equal(status.target.artifactBoundary.artifactCommit.shortCommitId, "a77efa1"); assert.equal(status.target.artifactBoundary.artifactCommit.promotionParentMatches, true); assert.equal(status.artifactCatalog.commitId, "abc1234"); assert.equal(status.artifactCatalog.digestCounts.sha256, 2); assert.equal(status.artifactReport.sourceCommitId, "abc1234"); assert.equal(status.liveDelta.status, "pass"); assert.equal(status.liveDelta.expectedCommit, "abc1234"); assert.equal(status.target.headMatchesTarget, true); assert.equal(status.target.desiredStateCheck.status, "pass"); assert.equal(status.deployJson.matchesTarget, true); assert.match(status.nextActions[0], /without republishing HEAD/u); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/deploy-desired-state-plan.mjs") && entry.args.includes("--promotion-commit") && entry.args.includes(publishedCommit) ), true); assertNoReadOnlySideEffects(commandLog); }); test("dev-cd status exposes moving origin/main when checkout is behind a deploy-json promotion control ref", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); const commandLog = []; let output = ""; const code = await runDevCdApply(["--status", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml"], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, targetCommitId: laterControlCommit, headCommitId: publishedCommit, extraGitRefs: { abc1234: publishedCommit }, latestArtifactMergeCommit: artifactMergeCommit, commitParents: { [artifactMergeCommit]: [publishedCommit, artifactReportCommit], [artifactReportCommit]: [publishedCommit] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); assert.equal(code, 0); assert.equal(status.status, "degraded"); assert.equal(status.target.promotionCommit, publishedCommit); assert.equal(status.target.controlRef.shortCommitId, "def5678"); assert.equal(status.target.headCommitId, publishedCommit); assert.equal(status.target.headMatchesTarget, false); assert.match(status.nextActions.join("\n"), /Checkout or fast-forward/u); assertNoReadOnlySideEffects(commandLog); }); test("apply stops before lock acquisition when checkout is behind deploy-json promotion control ref", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-moving-main", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, targetCommitId: laterControlCommit, headCommitId: publishedCommit, extraGitRefs: { abc1234: publishedCommit }, latestArtifactMergeCommit: artifactMergeCommit, commitParents: { [artifactMergeCommit]: [publishedCommit, artifactReportCommit], [artifactReportCommit]: [publishedCommit] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.target.controlRef.shortCommitId, "def5678"); assert.equal(summary.blockers.some((blocker) => blocker.scope === "target-ref-head"), true); assert.equal(commandLog.some((entry) => entry.command.includes("kubectl")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false); }); test("dev-cd status degrades artifact merge provenance when catalog/report do not match deploy-json promotion", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); await writeFile( path.join(repoRoot, "deploy/artifact-catalog.dev.json"), `${JSON.stringify({ kind: "hwlab-artifact-catalog", commitId: "9999999", artifactState: "published", publish: { ciPublished: true, registryVerified: true, provenance: "reports/dev-gate/dev-artifacts.json" }, services: [] }, null, 2)}\n` ); const commandLog = []; let output = ""; const code = await runDevCdApply(["--status", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml"], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, targetCommitId: laterControlCommit, headCommitId: laterControlCommit, extraGitRefs: { abc1234: publishedCommit }, latestArtifactMergeCommit: artifactMergeCommit, commitParents: { [artifactMergeCommit]: [publishedCommit, artifactReportCommit], [artifactReportCommit]: [publishedCommit] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); assert.equal(code, 0); assert.equal(status.status, "degraded"); assert.equal(status.target.promotionCommit, publishedCommit); assert.equal(status.target.artifactBoundary.status, "degraded"); assert.equal(status.target.artifactBoundary.desiredState.deployCommitMatches, true); assert.equal(status.target.artifactBoundary.desiredState.catalogCommitMatches, false); assert.equal(status.target.artifactBoundary.desiredState.reportCommitMatches, true); assertNoReadOnlySideEffects(commandLog); }); test("apply stops before lock acquisition when artifact merge provenance mismatches deploy-json promotion", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); await writeFile( path.join(repoRoot, "reports/dev-gate/dev-artifacts.json"), `${JSON.stringify({ status: "published", commitId: "9999999", artifactPublish: { status: "published", sourceCommitId: "9999999", serviceCount: 0, requiredServiceCount: 0, publishedCount: 0, services: [] } }, null, 2)}\n` ); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-provenance", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, targetCommitId: laterControlCommit, headCommitId: laterControlCommit, extraGitRefs: { abc1234: publishedCommit }, latestArtifactMergeCommit: artifactMergeCommit, commitParents: { [artifactMergeCommit]: [publishedCommit, artifactReportCommit], [artifactReportCommit]: [publishedCommit] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.blockers.some((blocker) => blocker.scope === "artifact-boundary"), true); assert.equal(commandLog.some((entry) => entry.command.includes("kubectl")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false); }); test("dev-cd status summarizes held, released, and stale locks without unsafe takeover hints", async () => { const cases = [ { name: "held", phase: "applying", now: iso(100000), expectedStatus: "held", expectedRetryAfterSeconds: 200 }, { name: "released", phase: "released", releasedAt: iso(50000), releaseStatus: "pass", now: iso(100000), expectedStatus: "released", expectedRetryAfterSeconds: 0 }, { name: "stale", phase: "verifying", now: iso(301000), expectedStatus: "stale", expectedRetryAfterSeconds: 0 } ]; for (const item of cases) { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply(["--status", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml"], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, heldLock: { promotionCommit: "abc1234", deployJsonHash: "sha256:a", ownerTaskId: `task-${item.name}`, transactionId: `tx-${item.name}`, phase: item.phase, startedAt: iso(0), updatedAt: iso(0), ttlSeconds: 300, liveBefore: { status: "pass" }, targetNamespace: "hwlab-dev", targetRef: "origin/main", lockBackend: "Lease", ...(item.releasedAt ? { releasedAt: item.releasedAt } : {}), ...(item.releaseStatus ? { releaseStatus: item.releaseStatus } : {}) } }), httpGetJson: makeHttpGetJson(), now: () => new Date(item.now), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); assert.equal(code, 0); assert.equal(status.lock.status, item.expectedStatus); assert.equal(status.lock.lockName, "hwlab-dev-cd-lock"); assert.equal(status.lock.current.ownerTaskId, `task-${item.name}`); assert.equal(status.lock.retryAfterSeconds, item.expectedRetryAfterSeconds); assert.equal(JSON.stringify(status).includes("force-interrupt"), false); assert.equal(JSON.stringify(status.nextActions).includes("force"), false); if (item.expectedStatus === "stale") { assert.match(status.lock.allowedLockAction.breakStale, /--break-stale-lock/u); } assertNoReadOnlySideEffects(commandLog); } }); test("dev-cd status reports unavailable kubectl with redacted reason", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const runCommand = async (command, args, options = {}) => { commandLog.push({ command, args, env: options.env ?? {}, input: options.input ?? "" }); if (command === "git" && args[0] === "rev-parse" && args.includes("origin/main^{commit}")) { return { code: 0, stdout: "abc1234abc1234abc1234abc1234abc1234abc1\n", stderr: "" }; } if (command === "git" && args[0] === "rev-parse" && args.includes("--short=7")) { return { code: 0, stdout: `${String(args.at(-1)).slice(0, 7)}\n`, stderr: "" }; } if (command === "git" && args[0] === "rev-parse" && args.includes("HEAD^{commit}")) { return { code: 0, stdout: "abc1234abc1234abc1234abc1234abc1234abc1\n", stderr: "" }; } if (command === "git" && args[0] === "log" && args.includes("--first-parent")) { return { code: 1, stdout: "", stderr: "no matching commits" }; } if (command === "git" && args[0] === "rev-list" && args.includes("--parents")) { const ref = String(args.at(-1) ?? ""); return { code: 0, stdout: `${ref}\n`, stderr: "" }; } if (command === "git" && args[0] === "diff-tree") { return { code: 0, stdout: "", stderr: "" }; } if (command === "which" && args[0] === "kubectl") { return { code: 1, stdout: "", stderr: "kubectl missing" }; } if (command === "kubectl") { return { code: 1, stdout: "", stderr: "connection failed token=SECRET_TOKEN password=SECRET_PASSWORD postgresql://user:SECRET_DB@db" }; } return { code: 0, stdout: "", stderr: "" }; }; const code = await runDevCdApply(["--status"], { repoRoot, env: {}, runCommand, httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const status = JSON.parse(output); const text = JSON.stringify(status); assert.equal(code, 0); assert.equal(status.status, "degraded"); assert.equal(status.lock.status, "unavailable"); assert.equal(status.lock.reason, "kubectl or DEV kubeconfig unavailable; command output is redacted"); assert.equal(text.includes("SECRET_TOKEN"), false); assert.equal(text.includes("SECRET_PASSWORD"), false); assert.equal(text.includes("SECRET_DB"), false); assert.match(status.lock.redactedReason, //u); assertNoReadOnlySideEffects(commandLog); }); test("force-interrupt option is rejected before process discovery or lease mutation", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply(["--force-interrupt-lock"], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const failure = JSON.parse(output); assert.equal(code, 2); assert.equal(failure.error, "unknown-argument"); assert.equal(failure.mutationAttempted, false); assert.equal(failure.prodTouched, false); assert.deepEqual(commandLog, []); }); test("deploy-lock-held failure is structured before publish/apply", () => { const failure = deployLockHeldFailure({ ownerTaskId: "task-a", transactionId: "tx-a", promotionCommit: "abc1234", deployJsonHash: "sha256:a", phase: "applying", updatedAt: iso(0), ttlSeconds: 300, targetNamespace: "hwlab-dev", lockName: "hwlab-dev-cd-lock", lockBackend: "Lease" }, new Date(iso(100000))); assert.equal(failure.ok, false); assert.equal(failure.error, "deploy-lock-held"); assert.equal(failure.holder, "task-a"); assert.equal(failure.promotionCommit, "abc1234"); assert.equal(failure.phase, "applying"); assert.equal(failure.retryAfterSeconds, 200); assert.equal(failure.mutationAttempted, false); }); test("second transaction exits deploy-lock-held without running side effects", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-b", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, heldLock: { promotionCommit: "abc1234", deployJsonHash: "sha256:a", ownerTaskId: "task-a", transactionId: "tx-a", phase: "publishing", startedAt: iso(0), updatedAt: iso(0), ttlSeconds: 300, liveBefore: { status: "pass" }, targetNamespace: "hwlab-dev", targetRef: "origin/main", lockBackend: "Lease" } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const failure = JSON.parse(output); assert.equal(code, 2); assert.equal(failure.error, "deploy-lock-held"); assert.equal(failure.holder, "task-a"); assert.equal(failure.retryAfterSeconds, 200); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false); }); test("stale lock requires explicit break-stale-lock confirmation", async () => { const repoRoot = await makeRepo(); let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-b", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml" ], { repoRoot, env: {}, runCommand: makeRunCommand({ heldLock: { promotionCommit: "abc1234", deployJsonHash: "sha256:a", ownerTaskId: "task-a", transactionId: "tx-a", phase: "verifying", startedAt: iso(0), updatedAt: iso(0), ttlSeconds: 300, liveBefore: { status: "pass" }, targetNamespace: "hwlab-dev", targetRef: "origin/main", lockBackend: "Lease" } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(301000)), stdout: { write: (chunk) => { output += chunk; } } }); const failure = JSON.parse(output); assert.equal(code, 2); assert.equal(failure.error, "deploy-lock-held"); assert.equal(failure.stale, true); assert.equal(failure.requiresBreakStaleLock, true); }); test("apply preflight passes with D601 native k3s and required SecretRef keys before mutation", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-preflight-pass", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report", "--full-output" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const report = JSON.parse(output); const preflight = report.devCdApply.preflight; assert.equal(code, 0); assert.equal(preflight.status, "pass"); assert.equal(preflight.kubeconfig.required, "/etc/rancher/k3s/k3s.yaml"); assert.equal(preflight.kubeconfig.enforcedEnv, true); assert.deepEqual(preflight.controlPlane.forced.nodeNames, ["d601"]); assert.deepEqual( preflight.secretRefs.map((ref) => `${ref.secretName}/${ref.secretKey}:${ref.status}`), [ "hwlab-cloud-api-dev-db/database-url:present", "hwlab-cloud-api-dev-db-admin/admin-url:present", "hwlab-code-agent-provider/openai-api-key:present" ] ); assert.equal(preflight.safety.secretValuesRead, false); assert.equal(preflight.safety.secretValuesPrinted, false); const firstWriteIndex = commandLog.findIndex((entry) => entry.command.includes("kubectl") && ["create", "replace", "apply", "patch", "delete", "rollout", "wait", "logs"].some((verb) => entry.args.includes(verb)) ); const secretDescribeIndexes = commandLog .map((entry, index) => ({ entry, index })) .filter(({ entry }) => entry.command.includes("kubectl") && entry.args.includes("describe") && entry.args.includes("secret")) .map(({ index }) => index); assert.ok(secretDescribeIndexes.length > 0); assert.ok(secretDescribeIndexes.every((index) => index < firstWriteIndex)); }); test("apply preflight blocks missing SecretRef before Lease or side-effect commands", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-missing-secret", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, secretRefs: { "hwlab-cloud-api-dev-db": ["database-url"], "hwlab-cloud-api-dev-db-admin": ["admin-url"] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.preflight.status, "blocked"); assert.equal(summary.preflight.blockerCount, 1); assert.equal(summary.blockers.some((blocker) => blocker.scope === "secretref:hwlab-code-agent-provider/openai-api-key"), true); assert.equal(writtenReport.devCdApply.preflight.safety.writeSideEffectsAttempted, false); assert.equal(writtenReport.devCdApply.preflight.safety.secretValuesRead, false); assert.equal(JSON.stringify(writtenReport).includes("SECRET"), false); assertNoWriteSideEffects(commandLog); }); test("apply preflight blocks non-D601 kubeconfig path before kubectl mutation", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-wrong-path", "--kubeconfig", "/tmp/kubeconfig" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.preflight.status, "blocked"); assert.equal(summary.blockers.some((blocker) => blocker.scope === "d601-kubeconfig-path"), true); assertNoWriteSideEffects(commandLog); }); test("apply preflight blocks docker-desktop and 127.0.0.1 control-plane signals before mutation", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-docker-desktop", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, kubeContext: "docker-desktop", kubeServer: "https://127.0.0.1:11700", kubeNodes: ["desktop-control-plane"] }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.blockers.some((blocker) => blocker.scope === "d601-native-k3s-context"), true); assert.equal(summary.blockers.some((blocker) => blocker.scope === "d601-native-k3s-server"), true); assert.equal(summary.blockers.some((blocker) => blocker.scope === "d601-native-k3s-nodes"), true); assertNoWriteSideEffects(commandLog); }); test("apply preflight blocks second hwlab-dev control plane visible through bare kubectl", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-second-control-plane", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, bareKubeContext: "docker-desktop", bareKubeServer: "https://127.0.0.1:11700", bareKubeNodes: ["desktop-control-plane"], bareHwlabDevNamespace: true }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.blockers.some((blocker) => blocker.scope === "bare-kubectl-context"), true); assert.equal(summary.blockers.some((blocker) => blocker.scope === "bare-kubectl-server"), true); assert.equal(summary.blockers.some((blocker) => blocker.scope === "second-hwlab-dev-control-plane"), true); assertNoWriteSideEffects(commandLog); }); test("transaction runs phases, allows internal side-effect env, releases lock, and reports live verify", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--break-stale-lock", "--owner-task-id", "task-b", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report", "--full-output" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, heldLock: { promotionCommit: "old1234", deployJsonHash: "sha256:old", ownerTaskId: "task-a", transactionId: "tx-a", phase: "verifying", startedAt: iso(0), updatedAt: iso(0), ttlSeconds: 300, liveBefore: { status: "pass" }, targetNamespace: "hwlab-dev", targetRef: "origin/main", lockBackend: "Lease" } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(301000)), stdout: { write: (chunk) => { output += chunk; } } }); const report = JSON.parse(output); assert.equal(code, 0); assert.equal(report.status, "pass"); assert.deepEqual( report.transaction.phases.map((phase) => phase.phase), ["publishing", "applying", "verifying", "released"] ); assert.equal(report.devCdApply.lock.staleBreak.ownerTaskId, "task-a"); assert.equal(report.transaction.release.status, "released"); assert.equal(report.devCdApply.liveVerify.status, "pass"); assert.equal(report.devCdApply.liveVerify.summary.checked, 2); assert.equal(report.devCdApply.liveVerify.endpoints.some((endpoint) => endpoint.id === "cloud-web-16666"), true); assert.equal(report.devCdApply.liveVerify.endpoints.some((endpoint) => endpoint.id === "cloud-api-16667"), true); assert.equal(report.devCdApply.reportPaths.transaction, "reports/dev-gate/dev-cd-apply.json"); assert.equal(report.devCdApply.reportPaths.runtimeProvisioning, "reports/dev-gate/dev-runtime-provisioning-report.json"); assert.equal(report.devCdApply.reportPaths.runtimeMigration, "reports/dev-gate/dev-runtime-migration-report.json"); assert.equal(report.devCdApply.reportPaths.runtimePostflight, "reports/dev-gate/dev-runtime-postflight-report.json"); const publishCall = commandLog.find((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")); const applyCall = commandLog.find((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")); const provisioningCall = commandLog.find((entry) => entry.args.includes("scripts/dev-runtime-provisioning.mjs")); const migrationCall = commandLog.find((entry) => entry.args.includes("scripts/dev-runtime-migration.mjs")); const postflightCall = commandLog.find((entry) => entry.args.includes("scripts/dev-runtime-postflight.mjs")); const refreshCall = commandLog.find((entry) => entry.args.includes("scripts/refresh-artifact-catalog.mjs")); assert.ok(publishCall?.env.HWLAB_CD_TRANSACTION_ID); assert.equal(applyCall?.env.HWLAB_CD_TRANSACTION_ID, publishCall.env.HWLAB_CD_TRANSACTION_ID); const provisioningJobApply = commandLog.find((entry) => entry.command.includes("kubectl") && entry.args.includes("apply") && entry.input.includes("hwlab-runtime-provision") ); const migrationJobApply = commandLog.find((entry) => entry.command.includes("kubectl") && entry.args.includes("apply") && entry.input.includes("hwlab-runtime-migrate") ); assert.ok(provisioningJobApply); assert.ok(migrationJobApply); assert.equal(JSON.parse(provisioningJobApply.input).spec.template.spec.containers[0].args[0], "scripts/dev-runtime-provisioning.mjs"); assert.equal(JSON.parse(migrationJobApply.input).spec.template.spec.containers[0].args[0], "scripts/dev-runtime-migration.mjs"); assert.equal(postflightCall?.env.HWLAB_CD_TRANSACTION_ID, publishCall.env.HWLAB_CD_TRANSACTION_ID); assert.deepEqual(postflightCall.args.slice(0, 4), ["scripts/dev-runtime-postflight.mjs", "--live", "--confirm-dev", "--confirmed-non-production"]); assert.equal(refreshCall.args[refreshCall.args.indexOf("--target-ref") + 1], "abc1234abc1234abc1234abc1234abc1234abc1"); assert.ok( commandLog.findIndex((entry) => entry.input.includes("hwlab-runtime-provision")) < commandLog.findIndex((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")) ); assert.ok( commandLog.findIndex((entry) => entry.input.includes("hwlab-runtime-migrate")) < commandLog.findIndex((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")) ); const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); assert.equal(writtenReport.devCdApply.liveVerify.summary.checked, 2); }); test("runtime maintenance job reports are parsed after redacting secret-like log output", async () => { const repoRoot = await makeRepo(); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-redaction", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report", "--full-output" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, runtimeJobLogSuffix: "debug postgresql://hwlab:SECRET_DB@db.example:5432/hwlab password=SECRET_PASSWORD token=SECRET_TOKEN\n" }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const report = JSON.parse(output); const serialized = JSON.stringify(report); assert.equal(code, 0); assert.equal(report.status, "pass"); assert.equal(serialized.includes("SECRET_DB"), false); assert.equal(serialized.includes("SECRET_PASSWORD"), false); assert.equal(serialized.includes("SECRET_TOKEN"), false); assert.equal(report.devCdApply.steps.find((step) => step.id === "runtime-db-provisioning").stdoutJson.safety.secretValuesPrinted, false); assert.equal(report.devCdApply.steps.find((step) => step.id === "runtime-db-migration").stdoutJson.safety.secretValuesPrinted, false); }); test("transaction can apply an already published deploy-json promotion without republishing control HEAD", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); const commandLog = []; let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-promotion", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report", "--full-output" ], { repoRoot, env: {}, runCommand: makeRunCommand({ commandLog, targetCommitId: laterControlCommit, headCommitId: laterControlCommit, extraGitRefs: { abc1234: publishedCommit }, latestArtifactMergeCommit: artifactMergeCommit, commitParents: { [artifactMergeCommit]: [publishedCommit, artifactReportCommit], [artifactReportCommit]: [publishedCommit] } }), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const report = JSON.parse(output); assert.equal(code, 0); assert.equal(report.status, "pass"); assert.equal(report.devCdApply.target.promotionCommit, publishedCommit); assert.equal(report.devCdApply.target.promotionSource, "deploy-json"); assert.equal(report.devCdApply.target.publishRequired, false); assert.equal(report.devCdApply.target.artifactBoundary.status, "pass"); assert.equal(report.devCdApply.target.artifactBoundary.artifactMergeCommit.shortCommitId, "fedcba9"); assert.deepEqual( report.devCdApply.steps.map((step) => step.id), [ "desired-state-check-existing-promotion", "artifact-catalog-validate", "runtime-db-provisioning", "runtime-db-migration", "dev-deploy-apply", "runtime-durable-postflight" ] ); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/refresh-artifact-catalog.mjs")), false); assert.equal(commandLog.some((entry) => entry.args.includes("scripts/deploy-desired-state-plan.mjs") && entry.args.includes("--promotion-commit") && entry.args.includes(publishedCommit) ), true); const applyCall = commandLog.find((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")); assert.ok(applyCall?.env.HWLAB_CD_TRANSACTION_ID); assert.equal(applyCall.env.HWLAB_CD_TRANSACTION_OWNER, "task-promotion"); assert.equal(report.devCdApply.liveVerify.status, "pass"); assert.equal(report.devCdApply.liveVerify.summary.commitMatches, 2); }); test("apply stdout is concise by default while write-report keeps the full report", async () => { const repoRoot = await makeRepo(); let output = ""; const code = await runDevCdApply([ "--apply", "--confirm-dev", "--confirmed-non-production", "--owner-task-id", "task-c", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", "--write-report" ], { repoRoot, env: {}, runCommand: makeRunCommand(), httpGetJson: makeHttpGetJson(), now: () => new Date(iso(100000)), stdout: { write: (chunk) => { output += chunk; } } }); const summary = JSON.parse(output); assert.equal(code, 0); assert.equal(summary.status, "pass"); assert.equal(summary.devCdApply, undefined); assert.equal(summary.fullOutputHint.includes("--full-output"), true); assert.equal(summary.lock.acquired, true); assert.equal(summary.steps.some((step) => step.id === "dev-deploy-apply"), true); const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); assert.equal(writtenReport.devCdApply.deployJson.before.manifest.environment, "dev"); }); test("live verify summarizes 16666 and 16667 health identity", async () => { const result = await verifyDevLive({ now: () => new Date(iso()), httpGetJson: makeHttpGetJson() }, { expectedCommit: "abc1234" }); assert.equal(result.status, "pass"); assert.equal(result.summary.checked, 2); assert.deepEqual(result.endpoints.map((endpoint) => endpoint.url), [ "http://74.48.78.17:16666/health/live", "http://74.48.78.17:16667/health/live" ]); assert.equal(result.endpoints.every((endpoint) => endpoint.commitMatches), true); }); test("legacy artifact publish CLI rejects side effects without transaction env", async () => { await assert.rejects( execFileAsync(process.execPath, ["scripts/dev-artifact-publish.mjs", "--publish", "--no-report"], { cwd: repoRoot, env: { ...process.env, HWLAB_CD_TRANSACTION_ID: "" } }), (error) => { assert.equal(error.code, 2); const payload = JSON.parse(error.stdout); assert.equal(payload.error, "cd-transaction-required"); assert.equal(payload.script, "scripts/dev-artifact-publish.mjs"); assert.equal(payload.mutationAttempted, false); return true; } ); });