From d34bbd7917b3afde4c164ee7338dae4bf1e5818a Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 31 Jul 2026 02:48:07 -0700 Subject: [PATCH] fix(orchestration): route the legacy coordinator gate at the caller's own Run (#11745) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(orchestration): route the legacy gate at the caller's own Run The retained-legacy-coordinator gate treated an unnamed Run as the adopted Run, so callers with no relation to it were fenced with legacy_read_only, and the adopted Run's NULL coordinator made the owner escape hatch unreachable. Resolve the caller's bound Run first and keep the adopted Run only as the unbound fallback, and treat an unclaimed adopted Run as free — the same rule bindingMatches() already applies 100 lines down. * refactor(orchestration): pass the open db handle into boundRunId Co-authored-by: Orca --------- Co-authored-by: Orca --- ...hestration-legacy-coordinator-authority.ts | 23 ++- .../orchestration-legacy-run-routing.test.ts | 146 ++++++++++++++++++ 2 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 src/main/runtime/rpc/orchestration-legacy-run-routing.test.ts diff --git a/src/main/runtime/rpc/orchestration-legacy-coordinator-authority.ts b/src/main/runtime/rpc/orchestration-legacy-coordinator-authority.ts index a6223ba073a..782460c891b 100644 --- a/src/main/runtime/rpc/orchestration-legacy-coordinator-authority.ts +++ b/src/main/runtime/rpc/orchestration-legacy-coordinator-authority.ts @@ -1,4 +1,5 @@ import type { OrcaRuntimeService, OrchestrationCompatibilityCallerAuthority } from '../orca-runtime' +import type { OrchestrationDb } from '../orchestration/db' import type { LegacyCompatibilityPrincipalRow } from '../orchestration/types' import type { LegacyCoordinatorAuthorityProof, RpcRequest } from './core' import { @@ -20,8 +21,12 @@ export class LegacyCoordinatorAuthority { ): LegacyCoordinatorAuthorityProof | undefined { const db = this.runtime.getOrchestrationDb() const adoption = db.getLegacyAdoption() - const requestedRun = requestedRunId ?? adoption?.adopted_run_id - if (!adoption || requestedRun !== adoption.adopted_run_id) { + if (!adoption) { + return undefined + } + // Why: an unnamed Run means the caller's own binding; only an unbound caller can still mean the adopted Run. + const requestedRun = requestedRunId ?? boundRunId(db, request) ?? adoption.adopted_run_id + if (requestedRun !== adoption.adopted_run_id) { return undefined } const candidate = db.resolveLegacyCoordinatorCandidate({ @@ -31,10 +36,17 @@ export class LegacyCoordinatorAuthority { }) if (!candidate) { if (request.orchestrationCompatibilityEvidence) { + const run = db.getRun(adoption.adopted_run_id) + // Why: an unclaimed adopted Run has no coordinator to fence — same rule as bindingMatches below. + if ( + !run?.coordinator_pane_key && + !db.getLegacyCoordinatorPrincipal(adoption.adopted_run_id) + ) { + return undefined + } const caller = this.runtime.verifyOrchestrationCompatibilityCaller( request.orchestrationCompatibilityEvidence ) - const run = db.getRun(adoption.adopted_run_id) if ( caller && run?.coordinator_handle === caller.terminalHandle && @@ -168,3 +180,8 @@ export class LegacyCoordinatorAuthority { ) } } + +function boundRunId(db: OrchestrationDb, request: RpcRequest): string | undefined { + const paneKey = request.orchestrationCompatibilityEvidence?.paneKey + return paneKey ? db.getCurrentRunForPane(paneKey)?.id : undefined +} diff --git a/src/main/runtime/rpc/orchestration-legacy-run-routing.test.ts b/src/main/runtime/rpc/orchestration-legacy-run-routing.test.ts new file mode 100644 index 00000000000..49e2b78d6ae --- /dev/null +++ b/src/main/runtime/rpc/orchestration-legacy-run-routing.test.ts @@ -0,0 +1,146 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { + cleanupLegacyCompatibilityDispatcherHarnesses, + COORDINATOR_HANDLE, + createHarness, + currentEvidence, + CURRENT_COORDINATOR_HANDLE, + CURRENT_COORDINATOR_PANE, + evidence, + invoke, + request +} from './orchestration-legacy-compatibility-dispatcher-test-fixture' + +afterEach(() => { + cleanupLegacyCompatibilityDispatcherHarnesses() +}) + +describe('legacy coordinator gate run routing', () => { + it.each(['dispatch', 'websocket'] as const)( + '%s asks an unbound caller to bind a Run instead of fencing it as a legacy coordinator', + async (transport) => { + const harness = createHarness() + + const response = await invoke( + harness.dispatcher, + request( + 'orchestration.taskCreate', + { + spec: 'fresh assignment', + callerTerminalHandle: CURRENT_COORDINATOR_HANDLE + }, + currentEvidence('coordinator'), + `unbound-task-create-${transport}` + ), + transport + ) + + expect(response).toMatchObject({ + ok: false, + error: { code: 'run_required' } + }) + expect(harness.db.listTasks({ runId: harness.adoptedRunId })).toHaveLength(1) + } + ) + + it('routes an unnamed Run to the caller binding even when attestation fails', async () => { + const harness = createHarness() + const run = harness.db.createRun({ + objective: 'current work', + coordinatorHandle: CURRENT_COORDINATOR_HANDLE, + coordinatorPaneKey: CURRENT_COORDINATOR_PANE + }) + + const response = await harness.dispatcher.dispatch( + request( + 'orchestration.taskCreate', + { + spec: 'fresh assignment', + callerTerminalHandle: CURRENT_COORDINATOR_HANDLE + }, + { ...currentEvidence('coordinator'), launchToken: '' }, + 'unattested-bound-task-create' + ) + ) + + expect(response).toMatchObject({ + ok: true, + result: { task: { run_id: run.id, spec: 'fresh assignment' } } + }) + }) + + it('lets a current coordinator rebind the unclaimed adopted Run with actionable guidance', async () => { + const harness = createHarness() + + const response = await harness.dispatcher.dispatch( + request( + 'orchestration.runUse', + { id: harness.adoptedRunId, from: CURRENT_COORDINATOR_HANDLE }, + currentEvidence('coordinator'), + 'unclaimed-adopted-run-use' + ) + ) + + expect(response).toMatchObject({ + ok: false, + error: { + code: 'consumer_fenced', + data: { + recoveryCommand: `orca orchestration run-use --id ${harness.adoptedRunId} --takeover-legacy` + } + } + }) + }) + + it('still routes the retained legacy coordinator to the adopted Run without --run', async () => { + const harness = createHarness() + + const response = await harness.dispatcher.dispatch( + request( + 'orchestration.taskCreate', + { + spec: 'retained assignment', + callerTerminalHandle: COORDINATOR_HANDLE + }, + evidence('coordinator'), + 'retained-adopted-task-create' + ) + ) + + expect(response).toMatchObject({ + ok: true, + result: { + task: { run_id: harness.adoptedRunId, spec: 'retained assignment' } + } + }) + }) + + it('still fences a stale legacy coordinator once the adopted Run is claimed', async () => { + const harness = createHarness() + harness.db.bindRun({ + runId: harness.adoptedRunId, + coordinatorHandle: CURRENT_COORDINATOR_HANDLE, + coordinatorPaneKey: CURRENT_COORDINATOR_PANE, + takeoverLegacy: true + }) + + const response = await harness.dispatcher.dispatch( + request( + 'orchestration.taskCreate', + { + spec: 'stale assignment', + run: harness.adoptedRunId, + callerTerminalHandle: COORDINATOR_HANDLE + }, + evidence('coordinator'), + 'claimed-adopted-task-create' + ) + ) + + expect(response).toMatchObject({ + ok: false, + error: { code: 'legacy_read_only' } + }) + expect(harness.db.listTasks({ runId: harness.adoptedRunId })).toHaveLength(1) + }) +})