fix: resolve dev cd artifact promotion boundary

This commit is contained in:
Code Queue Review
2026-05-23 12:51:02 +00:00
parent ff91f77427
commit 83d98ebf07
3 changed files with 667 additions and 38 deletions
+342 -2
View File
@@ -50,7 +50,8 @@ function parsePatch(args) {
async function makeRepo(options = {}) {
const commitId = options.commitId ?? "abc1234";
const repoRoot = await fsTempDir();
await mkdir(path.join(repoRoot, "deploy"), { recursive: true });
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({
@@ -58,9 +59,103 @@ async function makeRepo(options = {}) {
environment: "dev",
namespace: "hwlab-dev",
endpoint: "http://74.48.78.17:16667",
commitId
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;
}
@@ -101,6 +196,8 @@ function makeHttpGetJson() {
const publishedCommit = "abc1234abc1234abc1234abc1234abc1234abc1";
const laterControlCommit = "def5678def5678def5678def5678def5678def5";
const artifactMergeCommit = "fedcba9fedcba9fedcba9fedcba9fedcba9fedc";
const artifactReportCommit = "a77efa1a77efa1a77efa1a77efa1a77efa1a77e";
function makeRunCommand({
heldLock = null,
@@ -108,6 +205,14 @@ function makeRunCommand({
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 = ""
} = {}) {
@@ -120,6 +225,11 @@ function makeRunCommand({
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: "" };
}
@@ -129,6 +239,19 @@ function makeRunCommand({
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: "" };
}
@@ -333,6 +456,11 @@ test("dev-cd status accepts a published deploy-json promotion under a later cont
headCommitId: laterControlCommit,
extraGitRefs: {
abc1234: publishedCommit
},
latestArtifactMergeCommit: artifactMergeCommit,
commitParents: {
[artifactMergeCommit]: [publishedCommit, artifactReportCommit],
[artifactReportCommit]: [publishedCommit]
}
}),
httpGetJson: makeHttpGetJson(),
@@ -348,6 +476,16 @@ test("dev-cd status accepts a published deploy-json promotion under a later cont
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);
@@ -362,6 +500,188 @@ test("dev-cd status accepts a published deploy-json promotion under a later cont
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", "/tmp/kubeconfig"], {
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",
"/tmp/kubeconfig"
], {
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", "/tmp/kubeconfig"], {
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",
"/tmp/kubeconfig"
], {
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 = [
{
@@ -444,9 +764,22 @@ test("dev-cd status reports unavailable kubectl with redacted reason", async ()
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" };
}
@@ -770,6 +1103,11 @@ test("transaction can apply an already published deploy-json promotion without r
headCommitId: laterControlCommit,
extraGitRefs: {
abc1234: publishedCommit
},
latestArtifactMergeCommit: artifactMergeCommit,
commitParents: {
[artifactMergeCommit]: [publishedCommit, artifactReportCommit],
[artifactReportCommit]: [publishedCommit]
}
}),
httpGetJson: makeHttpGetJson(),
@@ -783,6 +1121,8 @@ test("transaction can apply an already published deploy-json promotion without r
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),
[