From 82e2349030881b4d76f43830f010b884fa45a851 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 08:24:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=BE=E5=BC=80=20transientEnv=20?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/reference/spec-v01-hwlab-manual-dispatch.md | 2 +- docs/reference/spec-v01-secret-distribution.md | 2 +- src/mgr/kubernetes-runner-job.ts | 1 - src/selftest/cases/20-runner-k8s-job.ts | 11 ++++++++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/reference/spec-v01-hwlab-manual-dispatch.md b/docs/reference/spec-v01-hwlab-manual-dispatch.md index e2f251f..ad9defa 100644 --- a/docs/reference/spec-v01-hwlab-manual-dispatch.md +++ b/docs/reference/spec-v01-hwlab-manual-dispatch.md @@ -71,7 +71,7 @@ AgentRun `v0.1` 承接 HWLAB v0.2 时,只吸收原有 Code Agent 的通用执 响应必须短返回 JSON,不等待完整模型 turn,至少包含:`runId`、`commandId`、`attemptId`、`jobName`、`namespace`、`runnerId`、`logPath` 或 `podIdentity`、后续 `commands show` 与 `events` 轮询入口。重复提交若 payload 不同,必须结构化失败,不能创建第二个同名业务 attempt。 -`transientEnv` 是 runner-job 层的临时执行上下文,不是 AgentRun run 的 durable fact。manager 只能校验 env name、数量和 value 长度;payload hash 只保存 value hash,response、event、dry-run manifest 和错误详情不得输出明文 value。业务授权仍由 HWLAB 自己负责,AgentRun 只把调度方明确提供的短期 env 交给本次 runner。 +`transientEnv` 是 runner-job 层的临时执行上下文,不是 AgentRun run 的 durable fact。manager 不对条目数量设固定上限,只校验数组形态、env name 合法且唯一、value 非空和单值长度;payload hash 只保存 value hash,response、event、dry-run manifest 和错误详情不得输出明文 value。业务授权仍由 HWLAB 自己负责,AgentRun 只把调度方明确提供的短期 env 交给本次 runner。 ## Run / Command 映射 diff --git a/docs/reference/spec-v01-secret-distribution.md b/docs/reference/spec-v01-secret-distribution.md index b09ba37..43f6be2 100644 --- a/docs/reference/spec-v01-secret-distribution.md +++ b/docs/reference/spec-v01-secret-distribution.md @@ -121,7 +121,7 @@ Run 的 `executionPolicy.secretScope` 只能包含引用,不包含值。provid 规则: - `transientEnv` 只能出现在 `POST /api/v1/runs/:runId/runner-jobs` 请求中;不得写入 `CreateRunInput`、command payload、event payload 或 result envelope 的 value 明文。 -- manager 只校验 env name、数量和 value 长度;runner job payload hash 只纳入 env name 与 value hash。 +- manager 不对 `transientEnv` 条目数量设固定上限,只校验数组形态、env name 合法且唯一、value 非空和单值长度;runner job payload hash 只纳入 env name 与 value hash。 - response、runner job status、event 和 dry-run manifest 只能展示 env name、count 和 `valuesPrinted=false`;dry-run manifest 中的 transient env value 必须显示为 `REDACTED`。 - 正式 Kubernetes Job manifest 会把 value 注入到本次 runner container env;该 token 必须由调度方控制 TTL、权限和业务授权范围。 - AgentRun 不解释 HWLAB device-pod 权限,也不把业务鉴权做成通用 policy;AgentRun 只负责不持久化、不回显、不扩散这类短期 env value。 diff --git a/src/mgr/kubernetes-runner-job.ts b/src/mgr/kubernetes-runner-job.ts index 575592b..4c6f7fc 100644 --- a/src/mgr/kubernetes-runner-job.ts +++ b/src/mgr/kubernetes-runner-job.ts @@ -159,7 +159,6 @@ export async function createKubernetesRunnerJob(options: { store: AgentRunStore; function transientEnvField(value: unknown): RunnerTransientEnv[] { if (value === undefined) return []; if (!Array.isArray(value)) throw new AgentRunError("schema-invalid", "transientEnv must be an array", { httpStatus: 400 }); - if (value.length > 8) throw new AgentRunError("schema-invalid", "transientEnv must contain at most 8 entries", { httpStatus: 400 }); const seen = new Set(); return value.map((entry, index) => { if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new AgentRunError("schema-invalid", `transientEnv[${index}] must be an object`, { httpStatus: 400 }); diff --git a/src/selftest/cases/20-runner-k8s-job.ts b/src/selftest/cases/20-runner-k8s-job.ts index e61d013..5c66b9c 100644 --- a/src/selftest/cases/20-runner-k8s-job.ts +++ b/src/selftest/cases/20-runner-k8s-job.ts @@ -98,15 +98,24 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin transientEnv: [ { name: "HWLAB_DEVICE_POD_SESSION_TOKEN", value: "test-token-material", sensitive: true }, { name: "HWLAB_CLOUD_API_URL", value: "http://cloud.test", sensitive: true }, + { name: "HWLAB_DEVICE_POD_API_URL", value: "http://device-pod.test", sensitive: true }, + { name: "HWLAB_RUNTIME_API_URL", value: "http://runtime-api.test", sensitive: true }, + { name: "HWLAB_RUNTIME_WEB_URL", value: "http://runtime-web.test", sensitive: true }, + { name: "HWLAB_RUNTIME_NAMESPACE", value: "hwlab-v02", sensitive: true }, + { name: "HWLAB_RUNTIME_LANE", value: "v02", sensitive: true }, + { name: "HWLAB_RUNTIME_ENDPOINT_SOURCE", value: "runtime-namespace", sensitive: true }, + { name: "HWLAB_RUNTIME_ENDPOINT_LOCKED", value: "1", sensitive: true }, + { name: "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", value: "1", sensitive: true }, ], }); assert.equal((created as { mutation?: unknown }).mutation, true); assert.equal(((created as JsonRecord).retention as JsonRecord).ttlSecondsAfterFinished, 86_400); - assert.deepEqual((((created as JsonRecord).transientEnv as JsonRecord).names) as string[], ["HWLAB_DEVICE_POD_SESSION_TOKEN", "HWLAB_CLOUD_API_URL"]); + assert.deepEqual((((created as JsonRecord).transientEnv as JsonRecord).names) as string[], ["HWLAB_DEVICE_POD_SESSION_TOKEN", "HWLAB_CLOUD_API_URL", "HWLAB_DEVICE_POD_API_URL", "HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_SOURCE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME"]); const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord; assert.equal((manifest.spec as JsonRecord).ttlSecondsAfterFinished, 86_400); assert.equal(runnerEnvValue(manifest, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "test-token-material"); assert.equal(runnerEnvValue(manifest, "HWLAB_CLOUD_API_URL"), "http://cloud.test"); + assert.equal(runnerEnvValue(manifest, "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME"), "1"); assertRunnerJobUsesToolCredential({ manifest, toolCredentials: (created as JsonRecord).toolCredentials } as JsonRecord, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN"); assertNoSecretLeak(created); } finally {