fix: honor AgentRun durable dispatch boundary

This commit is contained in:
root
2026-07-10 05:09:09 +02:00
parent 8cc114f730
commit d69573391b
2 changed files with 56 additions and 10 deletions
+49 -10
View File
@@ -85,6 +85,20 @@ const AGENTRUN_PROVIDER_TRACE_WIRE_API = "agentrun-v01-command-result";
const THREAD_CONTINUITY_POLICY = "hwlab-agentrun-v01-reuse-runner-thread";
const SESSION_POLICY_RUN_LOCAL = "hwlab-agentrun-v01-session-runner-reuse";
const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "blocked", "cancelled", "canceled"]);
const AGENTRUN_DURABLE_TRANSIENT_ENV_RESERVED = new Set([
"AGENTRUN_API_KEY",
"AUTH_PASSWORD",
"CODEX_API_KEY",
"CODEX_HOME",
"GH_TOKEN",
"GITHUB_TOKEN",
"HOME",
"OPENAI_API_KEY",
"PROVIDER_TOKEN",
"UNIDESK_AUTH_PASSWORD",
"UNIDESK_PROVIDER_TOKEN",
"UNIDESK_SSH_CLIENT_TOKEN"
]);
const HWLAB_RESOURCE_GIT_BUNDLES = Object.freeze([
Object.freeze({ name: "hwlab-tools", subpath: "tools", target_path: "tools" }),
Object.freeze({ name: "hwlab-agent-skills", subpath: "skills", target_path: ".agents/skills" })
@@ -1599,22 +1613,47 @@ function buildAgentRunDurableDispatchInput({ env, traceId, backendProfile }) {
const runtimeAuthority = requireAgentRunRuntimeAuthority(env);
const baseTransient = buildAgentRunTransientEnv(env, { providerProfile: backendProfile, parentTraceId: traceId });
assertAgentRunTransientRuntimeAuthority(baseTransient, runtimeAuthority);
if (baseTransient.some((entry) => entry?.sensitive === true)) {
throw adapterError("agentrun_durable_dispatch_secret_forbidden", "AgentRun durable dispatch input must not persist sensitive transient environment values.");
}
assertAgentRunDurableTransientEnv(baseTransient);
return {
idempotencyKey: traceId,
namespace: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE, env.AGENTRUN_RUNTIME_NAMESPACE, DEFAULT_RUNNER_NAMESPACE),
...(firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_IMAGE, env.AGENTRUN_RUNNER_IMAGE)
? { image: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_IMAGE, env.AGENTRUN_RUNNER_IMAGE) }
: {}),
...(firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_SERVICE_ACCOUNT, env.AGENTRUN_RUNNER_SERVICE_ACCOUNT)
? { serviceAccountName: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_SERVICE_ACCOUNT, env.AGENTRUN_RUNNER_SERVICE_ACCOUNT) }
: {}),
...(baseTransient.length > 0 ? { transientEnv: baseTransient } : {})
};
}
function assertAgentRunDurableTransientEnv(entries) {
const seen = new Set();
for (const entry of Array.isArray(entries) ? entries : []) {
const name = firstNonEmpty(entry?.name);
const value = firstNonEmpty(entry?.value);
if (!/^[A-Z_][A-Z0-9_]*$/u.test(name) || !value || seen.has(name)) {
throw adapterError("agentrun_durable_dispatch_transient_env_invalid", "AgentRun durable dispatch transient environment must contain unique non-empty environment entries.");
}
if (entry?.sensitive === true) {
throw adapterError("agentrun_durable_dispatch_secret_forbidden", "AgentRun durable dispatch input must not persist sensitive transient environment values.");
}
if (agentRunReservedTransientEnvName(name)) {
throw adapterError("agentrun_durable_dispatch_transient_env_reserved", `AgentRun durable dispatch transient environment cannot override runtime-owned ${name}.`);
}
seen.add(name);
}
}
function agentRunReservedTransientEnvName(name) {
return name.startsWith("AGENTRUN_")
|| name.startsWith("KUBERNETES_")
|| AGENTRUN_DURABLE_TRANSIENT_ENV_RESERVED.has(name)
|| new Set([
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"NO_PROXY",
"GIT_SSH_COMMAND",
"OTEL_SERVICE_NAME",
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"
]).has(name);
}
function requireAgentRunDurableDispatchIntent(value) {
const intent = value && typeof value === "object" && !Array.isArray(value) ? value : null;
const admittedStates = new Set(["pending", "dispatching", "retry", "dispatched"]);
@@ -64,7 +64,11 @@ test("fresh gpt.pika turn returns running only after durable dispatch intent is
assert.equal(commandCall.body.dispatch.kind, "kubernetes-job");
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "commandId"), false);
assert.equal(commandCall.body.dispatch.input.idempotencyKey, traceId);
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "namespace"), false);
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "image"), false);
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "serviceAccountName"), false);
assert.equal(commandCall.body.dispatch.input.transientEnv.every((entry) => entry.sensitive === false), true);
assert.equal(commandCall.body.dispatch.input.transientEnv.every((entry) => !entry.name.startsWith("AGENTRUN_") && !entry.name.startsWith("KUBERNETES_")), true);
assert.equal(JSON.stringify(commandCall.body).includes("hwl_live_must_not_be_persisted"), false);
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
assert.ok(traceStore.snapshot(traceId).events.some((event) => event.label === "agentrun:dispatch-intent:admitted"));
@@ -222,6 +226,9 @@ function agentRunTestEnv() {
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "NC01",
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: sourceCommit,
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "gpt.pika",
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: "caller-controlled-namespace-must-be-ignored",
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_IMAGE: "caller-controlled-image-must-be-ignored",
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_SERVICE_ACCOUNT: "caller-controlled-service-account-must-be-ignored",
HWLAB_RUNTIME_API_URL: "http://hwlab-cloud-api.hwlab-v03.svc.cluster.local:6667",
HWLAB_RUNTIME_WEB_URL: "http://hwlab-cloud-web.hwlab-v03.svc.cluster.local:8080",
HWLAB_RUNTIME_NAMESPACE: "hwlab-v03",