fix: sanitize agent/run/company labels to RFC 1123 (N4)

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-24 00:00:56 +00:00
parent 8a08e6a6ee
commit 29a4e709d0
4 changed files with 77 additions and 7 deletions
+20 -1
View File
@@ -15,7 +15,7 @@ vi.mock("./k8s-client.js", () => ({
resetCache: vi.fn(),
}));
const { isK8s404, buildPartialRunError, classifyOrphan, describePodTerminatedError, streamPodLogsOnce } = await import("./execute.js");
const { isK8s404, buildPartialRunError, classifyOrphan, describePodTerminatedError, streamPodLogsOnce, execute } = await import("./execute.js");
function makeJob(opts: {
runId?: string;
@@ -261,6 +261,25 @@ describe("describePodTerminatedError", () => {
});
});
describe("execute: all-invalid agent.id (N4)", () => {
it("returns hard error without creating a Job when agent.id sanitizes to null", async () => {
const logs: string[] = [];
const result = await execute({
runId: "run-001",
agent: { id: "@@@", companyId: "co1", name: "Bad Agent", adapterType: "claude_k8s", adapterConfig: {} },
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
config: {},
context: {},
onLog: async (_stream, msg) => { logs.push(msg); },
});
expect(result.errorCode).toBe("k8s_agent_id_invalid");
expect(result.errorMessage).toContain("@@@");
// getSelfPodInfo must NOT have been called (early return before K8s calls)
const { getSelfPodInfo } = await import("./k8s-client.js");
expect(getSelfPodInfo).not.toHaveBeenCalled();
});
});
// Regression: FAR-10 hardening — streamPodLogsOnce must not hang forever when
// the K8s client's logApi.log call never resolves. When stopSignal fires, the
// bail timer must force-return within LOG_STREAM_BAIL_TIMEOUT_MS (3s in the