Files
pikasTech-HWLAB/scripts/src/dev-cd-apply.test.mjs
T
2026-05-24 06:10:48 +00:00

1632 lines
62 KiB
JavaScript

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), "../..");
const tempReportRoot = "/tmp/hwlab-dev-gate";
const artifactReportFixturePath = path.join(tempReportRoot, "dev-artifacts.json");
function tempReport(name) {
return path.join(tempReportRoot, name);
}
function stateReport(repoRoot, name) {
return path.join(repoRoot, ".state/test-reports", name);
}
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(tempReportRoot, { 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: "ci-artifact: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(
artifactReportFixturePath,
`${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",
"ci-artifact:dev-artifacts.json"
],
desiredStatePromotionStatus = "pass",
runtimeJobLogSuffix = "",
runtimePostflightReport = null,
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")) {
if (args.includes("scripts/dev-runtime-postflight.mjs")) {
const report = runtimePostflightReport ?? {
conclusion: { status: "pass" },
blockers: [],
safety: { secretValuesPrinted: false },
apiHealth: { runtimeDurableReadiness: { ready: true } },
apiV1: { runtimeDurableReadiness: { ready: true } },
m3: {
persistenceContract: { ready: true },
operationCount: 4
}
};
return {
code: report.conclusion?.status === "pass" ? 0 : 1,
stdout: `${JSON.stringify(report, null, 2)}\n`,
stderr: ""
};
}
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",
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
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(stateReport(repoRoot, "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: "ci-artifact: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(
artifactReportFixturePath,
`${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, /<redacted>/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",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--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",
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
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(stateReport(repoRoot, "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",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--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, stateReport(repoRoot, "dev-cd-apply.json"));
assert.equal(report.devCdApply.reportPaths.runtimeProvisioning, tempReport("dev-runtime-provisioning-report.json"));
assert.equal(report.devCdApply.reportPaths.runtimeMigration, tempReport("dev-runtime-migration-report.json"));
assert.equal(report.devCdApply.reportPaths.runtimePostflight, tempReport("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("cmd/hwlab-cloud-api/provision.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);
const provisioningJob = JSON.parse(provisioningJobApply.input);
const migrationJob = JSON.parse(migrationJobApply.input);
assert.equal(provisioningJob.spec.template.spec.containers[0].args[0], "cmd/hwlab-cloud-api/provision.mjs");
assert.equal(migrationJob.spec.template.spec.containers[0].args[0], "cmd/hwlab-cloud-api/migrate.mjs");
assert.equal(
provisioningJob.spec.template.spec.containers[0].env.some((entry) => entry.name === "HWLAB_CD_TRANSACTION_ID"),
true
);
assert.equal(
migrationJob.spec.template.spec.containers[0].env.some((entry) => entry.name === "HWLAB_CD_TRANSACTION_ID"),
true
);
assert.equal(provisioningCall, undefined);
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(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
assert.equal(writtenReport.devCdApply.liveVerify.summary.checked, 2);
});
test("transaction can skip runtime postflight while preserving publish apply and live verify", async () => {
const repoRoot = await makeRepo();
const commandLog = [];
let output = "";
const code = await runDevCdApply([
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--owner-task-id",
"task-skip-postflight",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--full-output",
"--skip-runtime-postflight"
], {
repoRoot,
env: {},
runCommand: makeRunCommand({ commandLog }),
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.steps.some((step) => step.id === "artifact-publish"), true);
assert.equal(report.devCdApply.steps.some((step) => step.id === "dev-deploy-apply"), true);
assert.equal(report.devCdApply.steps.some((step) => step.id === "runtime-durable-postflight"), false);
assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-runtime-postflight.mjs")), false);
assert.equal(report.devCdApply.liveVerify.status, "pass");
assert.equal(report.devCdApply.liveVerify.summary.checked, 2);
});
test("transaction blocks with structured runtime postflight blocker when M3 evidence is not persisted", async () => {
const repoRoot = await makeRepo();
const commandLog = [];
let output = "";
const runtimePostflightReport = {
conclusion: {
status: "blocked",
blockerCount: 1
},
apiHealth: {
runtimeDurableReadiness: {
ready: true,
classification: "runtime_durable_ready"
}
},
apiV1: {
runtimeDurableReadiness: {
ready: true,
classification: "runtime_durable_ready"
}
},
m3: {
persistenceContract: {
ready: false,
sequence: [
{ id: "write-do1-true", persisted: true, idsPresent: true },
{ id: "read-di1-true", persisted: false, idsPresent: true }
]
},
operationCount: 4
},
blockers: [
{
type: "runtime_blocker",
scope: "m3-durable-postflight",
status: "open",
reason: "M3 DO1 true/false durable postflight did not prove persisted operation/audit/evidence for every step.",
impact: "A later rollout could appear ready while losing evidence persistence.",
safeNextAction: "Repair durable audit/evidence persistence, then rerun DEV runtime postflight.",
retryable: true
}
],
safety: {
secretValuesPrinted: false,
readsKubernetesSecrets: false
}
};
const code = await runDevCdApply([
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--owner-task-id",
"task-postflight-blocked",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
repoRoot,
env: {},
runCommand: makeRunCommand({ commandLog, runtimePostflightReport }),
httpGetJson: makeHttpGetJson(),
now: () => new Date(iso(100000)),
stdout: { write: (chunk) => { output += chunk; } }
});
const summary = JSON.parse(output);
const report = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
const blocker = report.blockers.find((item) => item.scope === "runtime-durable-postflight");
assert.equal(code, 2);
assert.equal(summary.status, "blocked");
assert.equal(summary.devCdApply, undefined);
assert.equal(output.length < 12000, true);
assert.equal(report.status, "blocked");
assert.equal(report.devCdApply.steps.at(-1).id, "runtime-durable-postflight");
assert.equal(report.devCdApply.steps.at(-1).status, "blocked");
assert.ok(blocker);
assert.equal(blocker.reason, runtimePostflightReport.blockers[0].reason);
assert.equal(blocker.impact, runtimePostflightReport.blockers[0].impact);
assert.equal(blocker.safeNextAction, runtimePostflightReport.blockers[0].safeNextAction);
assert.equal(blocker.retryable, true);
assert.equal(blocker.evidence.blockerScope, "m3-durable-postflight");
assert.equal(report.devCdApply.liveVerify.status, "not_run");
assert.equal(JSON.stringify(report).includes("postgresql://"), false);
assert.equal(JSON.stringify(report).includes("SECRET"), false);
assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-runtime-postflight.mjs")), true);
});
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",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--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",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--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",
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
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(stateReport(repoRoot, "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("artifact publish CLI rejects side effects without CI or transaction identity", async () => {
await assert.rejects(
execFileAsync(process.execPath, ["scripts/dev-artifact-publish.mjs", "--publish", "--no-report"], {
cwd: repoRoot,
env: {
HWLAB_CD_TRANSACTION_ID: "",
HWLAB_CI_ARTIFACT_RUN_ID: ""
}
}),
(error) => {
assert.equal(error.code, 2);
const payload = JSON.parse(error.stdout);
assert.equal(payload.error, "artifact-publish-identity-required");
assert.equal(payload.script, "scripts/dev-artifact-publish.mjs");
assert.equal(payload.cdLockRequired, false);
assert.equal(payload.rolloutAttempted, false);
assert.equal(payload.mutationAttempted, false);
return true;
}
);
});