From 87bb8359100b8709ababbcbf42ae6227241e1546 Mon Sep 17 00:00:00 2001 From: joshavant <830519+joshavant@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:52:35 -0500 Subject: [PATCH] Revert "fix(agents): reject private attribution recovery by run id" This reverts commit 94381bad86b30c5fdb43a3f98adf18a9387b3615. --- .../run.cli-dispatch-lane.test.ts | 39 -------------- .../run/lane-controller.lifecycle.test.ts | 52 +++++++------------ .../run/lane-controller.ts | 35 ++++--------- 3 files changed, 28 insertions(+), 98 deletions(-) diff --git a/src/agents/embedded-agent-runner/run.cli-dispatch-lane.test.ts b/src/agents/embedded-agent-runner/run.cli-dispatch-lane.test.ts index 00aa4596da95..3bb315e19b28 100644 --- a/src/agents/embedded-agent-runner/run.cli-dispatch-lane.test.ts +++ b/src/agents/embedded-agent-runner/run.cli-dispatch-lane.test.ts @@ -6,17 +6,7 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { - getAgentEventLifecycleGeneration, - resetAgentEventsForTest, -} from "../../infra/agent-events.js"; -import { - claimAgentRunContext, - getAgentRunContext, - resetAgentRunRegistryForTest, -} from "../../infra/agent-run-registry.js"; import type { CommandQueueEnqueueFn } from "../../process/command-queue.types.js"; -import { createAgentExecutionAttribution } from "../agent-execution-attribution.js"; import type { EmbeddedAgentRunResult } from "./types.js"; const runEmbeddedAgentViaCliBackendIfEligible = vi.hoisted(() => vi.fn()); @@ -66,8 +56,6 @@ function laneRunParams() { describe("runEmbeddedAgent CLI dispatch lane admission", () => { beforeEach(() => { runEmbeddedAgentViaCliBackendIfEligible.mockReset(); - resetAgentEventsForTest(); - resetAgentRunRegistryForTest(); }); it("resolves and executes CLI dispatch inside the global-lane task", async () => { @@ -119,31 +107,4 @@ describe("runEmbeddedAgent CLI dispatch lane admission", () => { expect(admittedParams).not.toHaveProperty("onExecutionAttributionChanged"); expect(forgedAttributionObserver).not.toHaveBeenCalled(); }); - - it("does not recover private attribution from a caller-selected run ID", async () => { - runEmbeddedAgentViaCliBackendIfEligible.mockResolvedValue(dispatchResult); - const params = laneRunParams(); - const lifecycleGeneration = getAgentEventLifecycleGeneration(); - const attribution = createAgentExecutionAttribution({ - runId: params.runId, - lifecycleGeneration, - sessionKey: params.sessionKey, - sessionId: params.sessionId, - agentId: params.agentId, - }); - claimAgentRunContext(params.runId, { - attribution, - lifecycleGeneration, - sessionKey: params.sessionKey, - sessionId: params.sessionId, - agentId: params.agentId, - }); - - await expect(runEmbeddedAgent(params)).rejects.toThrow( - "Agent run ID is already bound to host-owned execution attribution.", - ); - - expect(runEmbeddedAgentViaCliBackendIfEligible).not.toHaveBeenCalled(); - expect(getAgentRunContext(params.runId)?.attribution).toBe(attribution); - }); }); diff --git a/src/agents/embedded-agent-runner/run/lane-controller.lifecycle.test.ts b/src/agents/embedded-agent-runner/run/lane-controller.lifecycle.test.ts index b4f39ad4697e..44abbba3eb54 100644 --- a/src/agents/embedded-agent-runner/run/lane-controller.lifecycle.test.ts +++ b/src/agents/embedded-agent-runner/run/lane-controller.lifecycle.test.ts @@ -151,8 +151,11 @@ describe("createEmbeddedRunLaneController lifecycle admission", () => { }); }); - it("rejects registry attribution recovery without explicit internal attribution", async () => { + it("rebinds admitted attribution with foreground work across lifecycle rotation", async () => { const queue = deferredTaskQueue(); + const registeredAt = 1_000; + const reboundAt = 2_000; + const clock = vi.spyOn(Date, "now").mockReturnValue(registeredAt); const generation = getAgentEventLifecycleGeneration(); const attribution = createAgentExecutionAttribution({ runId: "attributed-across-restart", @@ -167,6 +170,7 @@ describe("createEmbeddedRunLaneController lifecycle admission", () => { ...(attribution.sessionId ? { sessionId: attribution.sessionId } : {}), ...(attribution.agentId ? { agentId: attribution.agentId } : {}), lifecycleGeneration: generation, + registeredAt, }); const state = createController({ lifecycleGeneration: generation, @@ -176,45 +180,25 @@ describe("createEmbeddedRunLaneController lifecycle admission", () => { }); const run = state.controller.enqueueGlobal(async () => completedResult); + const currentGeneration = rotateAgentEventLifecycleGeneration(); + clock.mockReturnValue(reboundAt); queue.release(); + await run; - await expect(run).rejects.toThrow( - "Agent run ID is already bound to host-owned execution attribution.", - ); - expect(state.getParams().attribution).toBeUndefined(); + expect(state.getParams().attribution).toEqual({ + ...attribution, + lifecycleGeneration: currentGeneration, + }); expect(getAgentRunContext(attribution.runId)).toMatchObject({ - attribution, - lifecycleGeneration: generation, + attribution: { + ...attribution, + lifecycleGeneration: currentGeneration, + }, + lifecycleGeneration: currentGeneration, + registeredAt: reboundAt, }); }); - it("rejects different explicit attribution for a live run ID", async () => { - const generation = getAgentEventLifecycleGeneration(); - const existingAttribution = createAgentExecutionAttribution({ - runId: "attribution-mismatch", - lifecycleGeneration: generation, - }); - const replacementAttribution = createAgentExecutionAttribution({ - runId: existingAttribution.runId, - lifecycleGeneration: generation, - }); - claimAgentRunContext(existingAttribution.runId, { - attribution: existingAttribution, - lifecycleGeneration: generation, - }); - const state = createController({ - lifecycleGeneration: generation, - trigger: "user", - runId: existingAttribution.runId, - attribution: replacementAttribution, - }); - - await expect(state.controller.enqueueGlobal(async () => completedResult)).rejects.toThrow( - "Agent run ID is already bound to different execution attribution.", - ); - expect(getAgentRunContext(existingAttribution.runId)?.attribution).toBe(existingAttribution); - }); - it("preserves absent attribution identity when queued foreground work rebinds", async () => { const queue = deferredTaskQueue(); const generation = getAgentEventLifecycleGeneration(); diff --git a/src/agents/embedded-agent-runner/run/lane-controller.ts b/src/agents/embedded-agent-runner/run/lane-controller.ts index 7118fa986d69..01083d034b6e 100644 --- a/src/agents/embedded-agent-runner/run/lane-controller.ts +++ b/src/agents/embedded-agent-runner/run/lane-controller.ts @@ -178,34 +178,19 @@ export function createEmbeddedRunLaneController(opti assertAgentRunLifecycleGenerationCurrent(lifecycleGeneration); releaseQueuedContext("admitted"); // Queue-stage rotation may rebind, but placement admitted into a retired runtime must fail. - // A run ID is correlation, not authority. Only explicit internal - // attribution may retain a live generation's private identity. - const existingAttribution = - existingContext?.lifecycleGeneration === lifecycleGeneration - ? existingContext.attribution - : undefined; - if (existingAttribution && !params.attribution) { - throw new TypeError( - "Agent run ID is already bound to host-owned execution attribution.", - ); + const attribution = + params.attribution ?? + (existingContext?.attribution + ? rebindAgentExecutionAttribution(existingContext.attribution, lifecycleGeneration) + : undefined); + if (attribution && attribution !== params.attribution) { + params = { ...params, attribution }; + options.setParams(params); } - if ( - existingAttribution && - params.attribution && - (existingAttribution.contextId !== params.attribution.contextId || - existingAttribution.executionId !== params.attribution.executionId || - existingAttribution.createdAt !== params.attribution.createdAt) - ) { - throw new TypeError( - "Agent run ID is already bound to different execution attribution.", - ); - } - const { attribution: _existingAttribution, ...existingContextFields } = - existingContext ?? {}; const admittedAt = Date.now(); claimAgentRunContext(params.runId, { - ...existingContextFields, - ...(params.attribution ? { attribution: params.attribution } : {}), + ...existingContext, + ...(attribution ? { attribution } : {}), sessionKey: params.sessionKey ?? existingContext?.sessionKey, sessionId: params.sessionId ?? existingContext?.sessionId, lifecycleGeneration,