mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 16:02:11 +00:00
fix(orchestration): route the legacy coordinator gate at the caller's own Run (#11745)
* 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 <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user