From a16c6a23f48f762d422238da98a88602844b6a43 Mon Sep 17 00:00:00 2001 From: UniDesk Codex Date: Mon, 6 Jul 2026 22:04:59 +0800 Subject: [PATCH] fix: rebuild missing env reuse artifacts --- scripts/ci-plan.test.mjs | 35 +++++++++++++++++++++++++++++++++++ scripts/src/ci-plan-lib.mjs | 7 ++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/scripts/ci-plan.test.mjs b/scripts/ci-plan.test.mjs index e55dc62b..d3a9ddb0 100644 --- a/scripts/ci-plan.test.mjs +++ b/scripts/ci-plan.test.mjs @@ -286,6 +286,41 @@ test("v03 planner reuses current env hash tag when registry already has it", asy assert.deepEqual(plan.buildServices, []); }); +test("v03 planner rebuilds when hash metadata drifts and registry lacks reusable env image", async () => { + const repo = await createFixtureRepo(); + const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); + const initialPlan = await createCiPlan({ + repoRoot: repo, + lane: "v03", + baseRef: "HEAD", + targetRef: initialSha, + artifactCatalogPath: "deploy/artifact-catalog.v03.json", + services: ["hwlab-cloud-api"] + }); + const catalog = createCatalogFixture("ready", catalogOptionsFromPlan(initialPlan, { sourceCommitId: initialSha })); + catalog.services.find((service) => service.serviceId === "hwlab-cloud-api").environmentInputHash = "stale-environment-input"; + await writeFile(path.join(repo, "deploy/artifact-catalog.v03.json"), JSON.stringify(catalog, null, 2)); + await git(repo, ["add", "deploy/artifact-catalog.v03.json"]); + await git(repo, ["commit", "-m", "seed stale v03 env catalog"]); + + const plan = await createCiPlan({ + repoRoot: repo, + lane: "v03", + baseRef: "HEAD~1", + targetRef: "HEAD", + artifactCatalogPath: "deploy/artifact-catalog.v03.json", + services: ["hwlab-cloud-api"], + verifyReuseRegistry: true, + reuseRegistryProbe: async () => ({ status: "missing", method: "HEAD", statusCode: 404 }) + }); + const cloudApi = plan.services.find((service) => service.serviceId === "hwlab-cloud-api"); + assert.equal(cloudApi.environmentInputChanged, true); + assert.equal(cloudApi.envChanged, true); + assert.equal(cloudApi.buildRequired, true); + assert.equal(cloudApi.reuseRegistry.status, "missing"); + assert.deepEqual(plan.buildServices, ["hwlab-cloud-api"]); +}); + test("v03 planner rebuilds env image when target registry lacks reused digest", async () => { const repo = await createFixtureRepo(); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); diff --git a/scripts/src/ci-plan-lib.mjs b/scripts/src/ci-plan-lib.mjs index d439d5e1..3202e26e 100644 --- a/scripts/src/ci-plan-lib.mjs +++ b/scripts/src/ci-plan-lib.mjs @@ -256,12 +256,13 @@ export async function createCiPlan(options = {}) { serviceId: model.serviceId, config: runtimeReuseByService.get(model.serviceId) ?? null }); - const affected = runtimeReuseDecision.runtimeReuseHit === true + const reuseArtifactReady = !envReuse || environmentReady === true; + const affected = runtimeReuseDecision.runtimeReuseHit === true && reuseArtifactReady ? false - : runtimeReuseDecision.envReuseHit === true + : runtimeReuseDecision.envReuseHit === true && reuseArtifactReady ? runtimeReuseDecision.sourceIdentityHit === false || runtimeConfigChanged === true : plannedAffected; - const buildRequired = runtimeReuseDecision.skipImageBuild === true ? false : plannedBuildRequired; + const buildRequired = runtimeReuseDecision.skipImageBuild === true && reuseArtifactReady ? false : plannedBuildRequired; services.push({ serviceId: model.serviceId, runtimeKind: model.runtimeKind,