From a47c38ef222e501f1ca54cd686f944dbf4c2016f Mon Sep 17 00:00:00 2001 From: Code Queue Review Date: Sun, 24 May 2026 07:29:51 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A0=A1=E9=AA=8C=20Code=20Agent=20Code?= =?UTF-8?q?x=20home=20=E6=8C=82=E8=BD=BD=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/cloud/code-agent-contract.mjs | 18 ++++++++- scripts/deploy-desired-state-plan.test.mjs | 43 +++++++++++++++++++++- scripts/src/deploy-desired-state-plan.mjs | 4 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/internal/cloud/code-agent-contract.mjs b/internal/cloud/code-agent-contract.mjs index f1c6ec61..1257ab87 100644 --- a/internal/cloud/code-agent-contract.mjs +++ b/internal/cloud/code-agent-contract.mjs @@ -98,6 +98,12 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE if (deployEnv?.CODEX_HOME !== contract.codexHome.path || workloadInspection.codexHome.present !== true) { missingCodexHomeContract.push(`CODEX_HOME must be ${contract.codexHome.path}`); } + if (workloadInspection.codexHome.writableHomeMountPresent !== true) { + missingCodexHomeContract.push(`${contract.codexHome.path} must be a writable volumeMount`); + } + if (workloadInspection.codexHome.emptyDirPresent !== true) { + missingCodexHomeContract.push(`${contract.codexHome.path} must be backed by emptyDir`); + } if (workloadInspection.codexHome.configMapPresent !== true) { missingCodexHomeContract.push(`${contract.codexHome.configMapName}/${contract.codexHome.configKey}`); } @@ -424,6 +430,13 @@ function inspectCodexHomeWorkloadMounts(workloadEnv = {}) { const volumeMounts = Array.isArray(workloadEnv.__volumeMounts) ? workloadEnv.__volumeMounts : []; const volumes = Array.isArray(workloadEnv.__volumes) ? workloadEnv.__volumes : []; const homeMount = volumeMounts.find((entry) => entry?.name === "hwlab-code-agent-codex-home" && entry?.mountPath === contract.codexHome.path); + const writableHomeMount = homeMount && homeMount.readOnly !== true; + const homeVolume = volumes.find((entry) => + entry?.name === "hwlab-code-agent-codex-home" && + entry?.emptyDir && + typeof entry.emptyDir === "object" && + !Array.isArray(entry.emptyDir) + ); const configMount = volumeMounts.find((entry) => entry?.name === "hwlab-code-agent-codex-config" && entry?.mountPath === `${contract.codexHome.path}/${contract.codexHome.configKey}` && @@ -448,8 +461,9 @@ function inspectCodexHomeWorkloadMounts(workloadEnv = {}) { ); return { path: contract.codexHome.path, - present: Boolean(homeMount), - writableHomeMountPresent: Boolean(homeMount), + present: Boolean(writableHomeMount && homeVolume), + writableHomeMountPresent: Boolean(writableHomeMount), + emptyDirPresent: Boolean(homeVolume), configMapPresent: Boolean(configMount && configVolume), authSecretPresent: Boolean(authMount && authVolume), configMapName: contract.codexHome.configMapName, diff --git a/scripts/deploy-desired-state-plan.test.mjs b/scripts/deploy-desired-state-plan.test.mjs index 33ffe528..32a7b0ac 100644 --- a/scripts/deploy-desired-state-plan.test.mjs +++ b/scripts/deploy-desired-state-plan.test.mjs @@ -39,6 +39,8 @@ async function makeFixture({ workloadEnvMirrors = true, workloadSkillsCommitId = null, workloadCodeAgentProviderEnv = true, + workloadCodexHomeVolumeMounts = true, + workloadCodexHomeVolumes = true, workloadDbEnv = true, workloadDbSslMode = "disable" } = {}) { @@ -184,7 +186,7 @@ async function makeFixture({ ...workloadProviderEnv ] : [], - ...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv + ...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadCodexHomeVolumeMounts ? { volumeMounts: [ { name: "hwlab-code-agent-codex-home", mountPath: "/codex-home" }, @@ -195,7 +197,7 @@ async function makeFixture({ : {}) } ], - ...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv + ...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadCodexHomeVolumes ? { volumes: [ { name: "hwlab-code-agent-codex-home", emptyDir: {} }, @@ -248,11 +250,48 @@ test("passes when cloud-api preserves provider env and DEV egress contract", asy assert.equal(plan.codeAgentProvider.secretRef.present, true); assert.equal(plan.codeAgentProvider.egress.deployMatchesDevProxy, true); assert.equal(plan.codeAgentProvider.codexHome.present, true); + assert.equal(plan.codeAgentProvider.codexHome.writableHomeMountPresent, true); + assert.equal(plan.codeAgentProvider.codexHome.emptyDirPresent, true); assert.equal(plan.codeAgentProvider.codexHome.configMapPresent, true); assert.equal(plan.codeAgentProvider.codexHome.authSecretPresent, true); assert.deepEqual(plan.diagnostics, []); }); +test("blocks when cloud-api CODEX_HOME mount is missing its writable emptyDir volume", async () => { + const repoRoot = await makeFixture({ workloadCodexHomeVolumes: false }); + const plan = await buildDesiredStatePlan({ repoRoot }); + const diagnostic = plan.diagnostics.find((entry) => entry.code === "code_agent_provider_contract_mismatch"); + + assert.equal(plan.status, "blocked"); + assert.equal(plan.codeAgentProvider.ready, false); + assert.equal(plan.codeAgentProvider.codexHome.present, false); + assert.equal(plan.codeAgentProvider.codexHome.writableHomeMountPresent, true); + assert.equal(plan.codeAgentProvider.codexHome.emptyDirPresent, false); + assert.ok(diagnostic); + assert.ok(diagnostic.actual.missingCodexHomeContract.includes("/codex-home must be backed by emptyDir")); +}); + +test("blocks when cloud-api CODEX_HOME mount is read-only", async () => { + const repoRoot = await makeFixture(); + const workloadsPath = path.join(repoRoot, "deploy/k8s/base/workloads.yaml"); + const workloads = JSON.parse(await readFile(workloadsPath, "utf8")); + workloads.items[0].spec.template.spec.containers[0].volumeMounts.find((entry) => + entry.name === "hwlab-code-agent-codex-home" + ).readOnly = true; + await writeFile(workloadsPath, `${JSON.stringify(workloads, null, 2)}\n`); + + const plan = await buildDesiredStatePlan({ repoRoot }); + const diagnostic = plan.diagnostics.find((entry) => entry.code === "code_agent_provider_contract_mismatch"); + + assert.equal(plan.status, "blocked"); + assert.equal(plan.codeAgentProvider.ready, false); + assert.equal(plan.codeAgentProvider.codexHome.present, false); + assert.equal(plan.codeAgentProvider.codexHome.writableHomeMountPresent, false); + assert.equal(plan.codeAgentProvider.codexHome.emptyDirPresent, true); + assert.ok(diagnostic); + assert.ok(diagnostic.actual.missingCodexHomeContract.includes("/codex-home must be a writable volumeMount")); +}); + test("blocks when cloud-api DB SSL mode drifts back to require", async () => { const repoRoot = await makeFixture({ deployDbSslMode: "require", diff --git a/scripts/src/deploy-desired-state-plan.mjs b/scripts/src/deploy-desired-state-plan.mjs index 3462d087..6f5f3b04 100644 --- a/scripts/src/deploy-desired-state-plan.mjs +++ b/scripts/src/deploy-desired-state-plan.mjs @@ -304,7 +304,9 @@ function inspectCodeAgentProviderDesiredState(ctx, deployService, workloadRecord deployMismatches: inspection.deployMismatches.map((entry) => entry.name), workloadMismatches: inspection.workloadMismatches.map((entry) => entry.name), missingSecretRefs: inspection.missingSecretRefs, - missingEgressContract: inspection.missingEgressContract + missingEgressContract: inspection.missingEgressContract, + missingCodexHomeContract: inspection.missingCodexHomeContract, + missingNoProxyContract: inspection.missingNoProxyContract }, secretMaterialRead: false, valuesRedacted: true