import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { mkdtemp, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { promisify } from "node:util"; import { SERVICE_IDS } from "../internal/protocol/index.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); const V02_SERVICE_IDS = Object.freeze([ "hwlab-cloud-api", "hwlab-cloud-web", "hwlab-agent-mgr", "hwlab-agent-worker", "hwlab-device-pod", "hwlab-gateway", "hwlab-edge-proxy", "hwlab-agent-skills" ]); async function git(args) { const result = await execFileAsync("git", args, { cwd: repoRoot, timeout: 5000, maxBuffer: 1024 * 1024 }); return result.stdout.trim(); } function digestFor(index) { return `sha256:${String(index + 1).padStart(64, "0")}`; } test("refresh accepts an absolute publish report path", async () => { const commitId = await git(["rev-parse", "HEAD"]); const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]); const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-")); const reportPath = path.join(tempDir, "dev-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", taskId: "g14-artifact-publish", commitId: shortCommitId, artifactPublish: { status: "published", mode: "publish", sourceCommitId: commitId, registryPrefix: "127.0.0.1:5000/hwlab", serviceCount: SERVICE_IDS.length, requiredServiceCount: SERVICE_IDS.length, disabledServiceCount: 0, publishedCount: SERVICE_IDS.length, publishPlan: { version: "v2", services: SERVICE_IDS.map((serviceId, index) => ({ serviceId, required: true, digest: digestFor(index), image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`, imageTag: shortCommitId })) }, services: SERVICE_IDS.map((serviceId, index) => ({ serviceId, status: "published", artifactRequired: true, image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`, imageTag: shortCommitId, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`, buildCreatedAt: "2026-05-24T00:00:00.000Z", buildSource: `pikasTech/HWLAB@${commitId}`, componentCommitId: commitId, componentInputHash: `${String(index + 1).padStart(64, "a")}`, dockerfileHash: `${String(index + 1).padStart(64, "b")}`, buildArgsHash: `${String(index + 1).padStart(64, "c")}`, ciAffected: index === 0, ciReason: index === 0 ? ["component-path-changed"] : ["component-inputs-unchanged"] })) } }, null, 2)}\n`); const result = await execFileAsync(process.execPath, [ "scripts/refresh-artifact-catalog.mjs", "--target-ref", "HEAD", "--publish-report", reportPath, "--no-write" ], { cwd: repoRoot, timeout: 15000, maxBuffer: 10 * 1024 * 1024 }); const payload = JSON.parse(result.stdout); assert.equal(payload.status, "published"); assert.equal(payload.publishedCount, SERVICE_IDS.length); assert.deepEqual(payload.wrote, []); assert.equal(payload.services[0].componentCommitId, commitId); assert.equal(payload.services[0].ciAffected, true); assert.equal(payload.services[0].componentInputHash, `${String(1).padStart(64, "a")}`); }); test("refresh is read-only by default", async () => { const commitId = await git(["rev-parse", "HEAD"]); const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]); const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-default-readonly-")); const reportPath = path.join(tempDir, "dev-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", taskId: "g14-artifact-publish", commitId: shortCommitId, artifactPublish: { status: "published", mode: "publish", sourceCommitId: commitId, registryPrefix: "127.0.0.1:5000/hwlab", serviceCount: SERVICE_IDS.length, requiredServiceCount: SERVICE_IDS.length, disabledServiceCount: 0, publishedCount: SERVICE_IDS.length, publishPlan: { version: "v2", services: SERVICE_IDS.map((serviceId, index) => ({ serviceId, required: true, digest: digestFor(index), image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`, imageTag: shortCommitId })) }, services: SERVICE_IDS.map((serviceId, index) => ({ serviceId, status: "published", artifactRequired: true, image: `127.0.0.1:5000/hwlab/${serviceId}:${shortCommitId}`, imageTag: shortCommitId, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}` })) } }, null, 2)}\n`); const result = await execFileAsync(process.execPath, [ "scripts/refresh-artifact-catalog.mjs", "--target-ref", "HEAD", "--publish-report", reportPath ], { cwd: repoRoot, timeout: 15000, maxBuffer: 10 * 1024 * 1024 }); const payload = JSON.parse(result.stdout); assert.equal(payload.status, "published"); assert.deepEqual(payload.wrote, []); assert.equal(payload.deployTruthPolicy, "deploy.json is human-authored runtime config; artifact promotion must not rewrite it"); }); test("refresh keeps reused service image tags from the publish report", async () => { const commitId = await git(["rev-parse", "HEAD"]); const shortCommitId = await git(["rev-parse", "--short=7", "HEAD"]); const reusedTag = "d42c77d"; const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-reused-")); const reportPath = path.join(tempDir, "dev-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", taskId: "g14-artifact-publish", commitId: shortCommitId, artifactPublish: { status: "published", mode: "publish", sourceCommitId: commitId, registryPrefix: "127.0.0.1:5000/hwlab", serviceCount: SERVICE_IDS.length, requiredServiceCount: SERVICE_IDS.length, disabledServiceCount: 0, publishedCount: 1, reusedCount: SERVICE_IDS.length - 1, publishPlan: { version: "v2", services: SERVICE_IDS.map((serviceId, index) => ({ serviceId, required: true, status: index === 0 ? "published" : "reused", digest: digestFor(index), image: `127.0.0.1:5000/hwlab/${serviceId}:${index === 0 ? shortCommitId : reusedTag}`, imageTag: index === 0 ? shortCommitId : reusedTag })) }, services: SERVICE_IDS.map((serviceId, index) => ({ serviceId, status: index === 0 ? "published" : "reused", artifactRequired: true, commitId: index === 0 ? shortCommitId : reusedTag, sourceCommitId: index === 0 ? commitId : reusedTag, image: `127.0.0.1:5000/hwlab/${serviceId}:${index === 0 ? shortCommitId : reusedTag}`, imageTag: index === 0 ? shortCommitId : reusedTag, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`, buildCreatedAt: "2026-05-24T00:00:00.000Z", buildSource: index === 0 ? `pikasTech/HWLAB@${commitId}` : `pikasTech/HWLAB@${reusedTag}`, componentCommitId: index === 0 ? commitId : reusedTag, componentInputHash: `${String(index + 1).padStart(64, "d")}`, dockerfileHash: `${String(index + 1).padStart(64, "e")}`, buildArgsHash: `${String(index + 1).padStart(64, "f")}`, ciAffected: index === 0, ciReason: index === 0 ? ["component-path-changed"] : ["component-inputs-unchanged"], reusedFrom: index === 0 ? null : `catalog:${reusedTag}` })) } }, null, 2)}\n`); const result = await execFileAsync(process.execPath, [ "scripts/refresh-artifact-catalog.mjs", "--target-ref", "HEAD", "--publish-report", reportPath, "--no-write" ], { cwd: repoRoot, timeout: 15000, maxBuffer: 10 * 1024 * 1024 }); const payload = JSON.parse(result.stdout); assert.equal(payload.status, "published"); assert.equal(payload.publishedCount, 1); assert.equal(payload.reusedCount, SERVICE_IDS.length - 1); assert.equal(payload.services[0].imageTag, shortCommitId); assert.equal(payload.services[1].imageTag, reusedTag); assert.equal(payload.services[1].publishState, "reused"); assert.equal(payload.services[1].reusedFrom, `catalog:${reusedTag}`); }); test("v02 refresh accepts env-reuse device-pod image tag from environment hash", async () => { const commitId = await git(["rev-parse", "HEAD"]); const environmentInputHash = "14847b0fe65d3d99a8bad2553c67c8660e24fba7e065eb122dba0de83e471823"; const envTag = `env-${environmentInputHash.slice(0, 12)}`; const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-env-reuse-")); const reportPath = path.join(tempDir, "v02-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", taskId: "v02-artifact-publish", commitId, artifactPublish: { status: "published", mode: "publish", sourceCommitId: commitId, registryPrefix: "127.0.0.1:5000/hwlab", serviceCount: V02_SERVICE_IDS.length, requiredServiceCount: V02_SERVICE_IDS.length, disabledServiceCount: 0, publishedCount: V02_SERVICE_IDS.length, reusedCount: 0, serviceInventory: { version: "v2", serviceCount: V02_SERVICE_IDS.length, requiredServiceCount: V02_SERVICE_IDS.length, disabledServiceCount: 0, requiredServiceIds: V02_SERVICE_IDS, disabledServiceIds: [], services: V02_SERVICE_IDS.map((serviceId) => ({ serviceId, publishEnabled: true, artifactRequired: true, artifactScope: "required", runtimeKind: serviceId === "hwlab-device-pod" ? "env-reuse" : "node-command", implementationState: "repo-entrypoint", sourceState: "source-present", entrypoint: `cmd/${serviceId}/main.ts`, disabledReason: null })) }, publishPlan: { version: "v2", services: V02_SERVICE_IDS.map((serviceId, index) => ({ serviceId, required: true, digest: digestFor(index), image: serviceId === "hwlab-device-pod" ? `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}` : `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`, imageTag: serviceId === "hwlab-device-pod" ? envTag : commitId })) }, services: V02_SERVICE_IDS.map((serviceId, index) => { if (serviceId !== "hwlab-device-pod") { return { serviceId, status: "published", artifactRequired: true, commitId, sourceCommitId: commitId, image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`, imageTag: commitId, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}` }; } return { serviceId, status: "published", artifactRequired: true, commitId, sourceCommitId: commitId, image: `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`, imageTag: envTag, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/hwlab-device-pod-env@${digestFor(index)}`, runtimeMode: "env-reuse-git-mirror-checkout", envReuse: true, environmentImage: `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`, environmentDigest: digestFor(index), environmentInputHash, codeInputHash: "96cd701e01ab9fab8d4daafd30b2206f65f5e72d23adb72dc4ad23fd9310f97e", bootRepo: "git@github.com:pikasTech/HWLAB.git", bootCommit: commitId, bootSh: "deploy/runtime/boot/hwlab-device-pod.sh" }; }) } }, null, 2)}\n`); const result = await execFileAsync(process.execPath, [ "scripts/refresh-artifact-catalog.mjs", "--lane", "v02", "--image-tag-mode", "full", "--target-ref", "HEAD", "--catalog-path", path.join(tempDir, "artifact-catalog.v02.json"), "--publish-report", reportPath, "--no-write" ], { cwd: repoRoot, timeout: 15000, maxBuffer: 10 * 1024 * 1024 }); const payload = JSON.parse(result.stdout); const devicePod = payload.services.find((service) => service.serviceId === "hwlab-device-pod"); assert.equal(payload.status, "published"); assert.equal(devicePod.imageTag, envTag); assert.equal(devicePod.runtimeMode, "env-reuse-git-mirror-checkout"); assert.equal(devicePod.environmentInputHash, environmentInputHash); assert.equal(devicePod.bootCommit, commitId); assert.equal(devicePod.bootSh, "deploy/runtime/boot/hwlab-device-pod.sh"); }); test("v02 refresh reports code-only env-reuse records as reused", async () => { const commitId = await git(["rev-parse", "HEAD"]); const previousCommitId = "1234567890abcdef1234567890abcdef12345678"; const environmentInputHash = "3e03261df109abfc804a4d776b6e8d59d5c71d84268adecf97ee2a420f2747c9"; const codeInputHash = "dbc39fd148d0cb27551400eccb2f7027a614de3ce3c9a0054c407c26478a92d1"; const envTag = `env-${environmentInputHash.slice(0, 12)}`; const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-code-only-reuse-")); const reportPath = path.join(tempDir, "v02-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", taskId: "v02-artifact-publish", commitId, artifactPublish: { status: "published", mode: "publish", sourceCommitId: commitId, registryPrefix: "127.0.0.1:5000/hwlab", serviceCount: V02_SERVICE_IDS.length, requiredServiceCount: V02_SERVICE_IDS.length, disabledServiceCount: 0, publishedCount: 0, reusedCount: V02_SERVICE_IDS.length, serviceInventory: { version: "v2", serviceCount: V02_SERVICE_IDS.length, requiredServiceCount: V02_SERVICE_IDS.length, disabledServiceCount: 0, requiredServiceIds: V02_SERVICE_IDS, disabledServiceIds: [], services: V02_SERVICE_IDS.map((serviceId) => ({ serviceId, publishEnabled: true, artifactRequired: true, artifactScope: "required", runtimeKind: serviceId === "hwlab-device-pod" ? "env-reuse" : "node-command", implementationState: "repo-entrypoint", sourceState: "source-present", entrypoint: `cmd/${serviceId}/main.ts`, disabledReason: null })) }, publishPlan: { version: "v2", services: V02_SERVICE_IDS.map((serviceId, index) => ({ serviceId, required: true, status: "reused", digest: digestFor(index), image: serviceId === "hwlab-device-pod" ? `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}` : `127.0.0.1:5000/hwlab/${serviceId}:${previousCommitId}`, imageTag: serviceId === "hwlab-device-pod" ? envTag : previousCommitId })) }, services: V02_SERVICE_IDS.map((serviceId, index) => { if (serviceId !== "hwlab-device-pod") { return { serviceId, status: "reused", artifactRequired: true, commitId: previousCommitId, sourceCommitId: previousCommitId, image: `127.0.0.1:5000/hwlab/${serviceId}:${previousCommitId}`, imageTag: previousCommitId, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/${serviceId}@${digestFor(index)}`, buildBackend: "reused-catalog", reusedFrom: `catalog:${previousCommitId}` }; } return { serviceId, status: "reused", artifactRequired: true, commitId: envTag, sourceCommitId: commitId, image: `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`, imageTag: envTag, digest: digestFor(index), repositoryDigest: `127.0.0.1:5000/hwlab/hwlab-device-pod-env@${digestFor(index)}`, buildBackend: "reused-env-catalog", runtimeMode: "env-reuse-git-mirror-checkout", envReuse: true, envChanged: false, codeChanged: true, environmentImage: `127.0.0.1:5000/hwlab/hwlab-device-pod-env:${envTag}`, environmentDigest: digestFor(index), environmentInputHash, codeInputHash, bootRepo: "git@github.com:pikasTech/HWLAB.git", bootCommit: commitId, bootSh: "deploy/runtime/boot/hwlab-device-pod.sh", reusedFrom: `env-catalog:${envTag}` }; }) } }, null, 2)}\n`); const result = await execFileAsync(process.execPath, [ "scripts/refresh-artifact-catalog.mjs", "--lane", "v02", "--image-tag-mode", "full", "--target-ref", "HEAD", "--catalog-path", path.join(tempDir, "artifact-catalog.v02.json"), "--publish-report", reportPath, "--no-write" ], { cwd: repoRoot, timeout: 15000, maxBuffer: 10 * 1024 * 1024 }); const payload = JSON.parse(result.stdout); const devicePod = payload.services.find((service) => service.serviceId === "hwlab-device-pod"); assert.equal(payload.status, "published"); assert.equal(payload.publishedCount, 0); assert.equal(payload.reusedCount, V02_SERVICE_IDS.length); assert.equal(devicePod.publishState, "reused"); assert.equal(devicePod.buildBackend, "reused-env-catalog"); assert.equal(devicePod.sourceCommitId, commitId); assert.equal(devicePod.imageTag, envTag); assert.equal(devicePod.runtimeMode, "env-reuse-git-mirror-checkout"); assert.equal(devicePod.environmentInputHash, environmentInputHash); assert.equal(devicePod.codeInputHash, codeInputHash); assert.equal(devicePod.bootCommit, commitId); });