mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
fix(orchestration): attest coordinator RPC callers
This commit is contained in:
@@ -11891,7 +11891,7 @@
|
||||
"providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"],
|
||||
"coveredPlatforms": ["macos"],
|
||||
"coveredProviders": ["local", "daemon", "ssh"],
|
||||
"coverageNotes": "A deterministic isolated runtime/SQLite/RPC harness covers Run and Dispatch ownership, exact handle and remint-stable pane recipients, fixed Delivery replay and acknowledgment, restart, concurrent pollers, filtered waiters, and built-CLI parity with direct DB state. Schema v28 durably records every coordinator handle through SQLite triggers, backfills current handles on open, recreates routing triggers after old table rebuilds, and atomically canonicalizes coordinator-addressed inserts to run:<id>. Schema v31 adds coordinator process incarnation, execution-host scope, and an authority revision; tests refuse live and unverifiable replacement, require runtime-attested restored-process continuity before backfilling a migrated revision -1 row, fence attestation loss during that bind, preserve generation and pending Delivery identity for the same process after handle/pane remint, permit only owning-host-proven exit takeover, fence a stale concurrent takeover observation, and clear stale authority when an older runtime changes only v30 binding columns. A 125-row oracle and 151-row reconciliation suite cover paged old-handle backlogs, commit-without-notify recovery, duplicate Dispatch owners, cancellation, and lifecycle fencing. Federation tests use real databases, dropped acknowledgments, restart/reconnect, and protocol v1/v2 peers; duplicate read rows do not wake again. Daemon and SSH pointer settlement is deterministic, with SSH bounded at 60 seconds beyond the 20-second mux health window. RPC changes are additive optional response fields; no stream opcode changed. Live headed/headless paired-runtime, physical SSH, WSL, Linux, and Windows acceptance remain gaps.",
|
||||
"coverageNotes": "A deterministic isolated runtime/SQLite/RPC harness covers Run and Dispatch ownership, exact handle and remint-stable pane recipients, fixed Delivery replay and acknowledgment, restart, concurrent pollers, filtered waiters, and built-CLI parity with direct DB state. Schema v28 durably records every coordinator handle through SQLite triggers, backfills current handles on open, recreates routing triggers after old table rebuilds, and atomically canonicalizes coordinator-addressed inserts to run:<id>. Schema v31 adds coordinator process incarnation, execution-host scope, and an authority revision; tests refuse live and unverifiable replacement, reject an identity-less caller that copies the live coordinator handle, require runtime-attested restored-process continuity before backfilling a migrated revision -1 row, fence attestation loss during that bind, preserve generation and pending Delivery identity for the same process after handle/pane remint, permit only owning-host-proven exit takeover, fence a stale concurrent takeover observation, and clear stale authority when an older runtime changes only v30 binding columns. A 125-row oracle and 151-row reconciliation suite cover paged old-handle backlogs, commit-without-notify recovery, duplicate Dispatch owners, cancellation, and lifecycle fencing. Federation tests use real databases, dropped acknowledgments, restart/reconnect, and protocol v1/v2 peers; duplicate read rows do not wake again. Daemon and SSH pointer settlement is deterministic, with SSH bounded at 60 seconds beyond the 20-second mux health window. RPC changes are additive optional response fields; no stream opcode changed. Live headed/headless paired-runtime, physical SSH, WSL, Linux, and Windows acceptance remain gaps.",
|
||||
"motivatingLinks": [
|
||||
"https://linear.app/stably/issue/STA-5729",
|
||||
"https://linear.app/stably/issue/STA-4325",
|
||||
@@ -11903,7 +11903,7 @@
|
||||
"commands": [
|
||||
"pnpm run build:cli && pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-message-delivery-identity.test.ts --reporter=dot --testTimeout=5000",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/run-coordinator-authority-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts src/main/runtime/rpc/methods/orchestration-runs.test.ts src/cli/handlers/orchestration-run-cli.test.ts src/main/ipc/pty-controller-process-inventory.test.ts src/cli/handlers/orchestration-gate-cli.test.ts src/main/runtime/rpc/methods/orchestration-gates.test.ts src/main/runtime/rpc/methods/orchestration-gate-run-authorization.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/run-coordinator-authority-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts src/main/runtime/rpc/methods/orchestration-runs.test.ts src/main/runtime/rpc/methods/orchestration-caller-attestation.test.ts src/cli/handlers/orchestration-run-cli.test.ts src/main/ipc/pty-controller-process-inventory.test.ts src/cli/handlers/orchestration-gate-cli.test.ts src/main/runtime/rpc/methods/orchestration-gates.test.ts src/main/runtime/rpc/methods/orchestration-gate-run-authorization.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/main/runtime/rpc/methods/orchestration-runs.test.ts src/main/runtime/rpc/methods/orchestration-send.test.ts src/main/runtime/rpc/methods/orchestration-check.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/federation-sync.test.ts src/main/runtime/rpc/methods/orchestration-federation.test.ts src/main/runtime/rpc/methods/orchestration-federation-lifecycle-settlement.test.ts --reporter=dot"
|
||||
],
|
||||
@@ -11931,6 +11931,7 @@
|
||||
"src/main/daemon/degraded-daemon-pty-provider.test.ts",
|
||||
"src/main/runtime/orca-runtime.test.ts",
|
||||
"src/main/runtime/terminal-send-stale-leaf-liveness.test.ts",
|
||||
"src/main/runtime/rpc/methods/orchestration-caller-attestation.test.ts",
|
||||
"src/main/runtime/rpc/methods/orchestration-runs.test.ts",
|
||||
"src/main/runtime/rpc/methods/orchestration-send.test.ts",
|
||||
"src/main/runtime/rpc/methods/orchestration-check.test.ts",
|
||||
@@ -11963,6 +11964,12 @@
|
||||
"explicit task listing discloses when the caller is not the current consumer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "src/main/runtime/rpc/methods/orchestration-caller-attestation.test.ts",
|
||||
"assertions": [
|
||||
"rejects an identity-less caller that copies the current coordinator handle with no effects and agent-readable recovery steps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "src/main/runtime/orchestration/run-coordinator-authority-migration.test.ts",
|
||||
"assertions": [
|
||||
@@ -12112,10 +12119,10 @@
|
||||
"date": "2026-08-27",
|
||||
"runner": "local",
|
||||
"platform": "macos",
|
||||
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/run-coordinator-authority-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts src/main/runtime/rpc/methods/orchestration-runs.test.ts src/cli/handlers/orchestration-run-cli.test.ts src/main/ipc/pty-controller-process-inventory.test.ts src/cli/handlers/orchestration-gate-cli.test.ts src/main/runtime/rpc/methods/orchestration-gates.test.ts src/main/runtime/rpc/methods/orchestration-gate-run-authorization.test.ts",
|
||||
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/run-coordinator-authority-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts src/main/runtime/rpc/methods/orchestration-runs.test.ts src/main/runtime/rpc/methods/orchestration-caller-attestation.test.ts src/cli/handlers/orchestration-run-cli.test.ts src/main/ipc/pty-controller-process-inventory.test.ts src/cli/handlers/orchestration-gate-cli.test.ts src/main/runtime/rpc/methods/orchestration-gates.test.ts src/main/runtime/rpc/methods/orchestration-gate-run-authorization.test.ts",
|
||||
"result": "passed",
|
||||
"durationSeconds": 5.42,
|
||||
"summary": "The final focused Run-authority gate passed 95/95 across schema migration and mixed-version writes, restored-process attestation and race fencing, Delivery fencing, owning-host liveness and deadlines, coordinator lifecycle, and CLI recovery inspection. Nine authority regressions failed deterministically before their fixes; the broader orchestration, RPC, and CLI suite passed 2,405 tests with one skipped."
|
||||
"summary": "The final focused Run-authority gate passed 96/96 across schema migration and mixed-version writes, caller and restored-process attestation, race and Delivery fencing, owning-host liveness and deadlines, coordinator lifecycle, and CLI recovery inspection. Ten authority regressions failed deterministically before their fixes; the final strict-attestation orchestration/RPC/CLI slice passed 1,255 tests, after an earlier broader branch gate passed 2,405 tests with one skipped."
|
||||
},
|
||||
{
|
||||
"date": "2026-08-13",
|
||||
@@ -12164,7 +12171,7 @@
|
||||
},
|
||||
"redGreenEvidence": {
|
||||
"status": "complete",
|
||||
"evidence": "The byte-identical five-test STA-4325 oracle (SHA-256 76a2d672d27e04e5b1db2f52df3b303468bdf006be7cfd003bf443618a856c35) failed 5/5 on reported v1.4.180 commit 0b62333cf179 and latest origin/main 9bb8836bb6, then passed 5/5 on its final candidate. For STA-5729, nine added regressions failed before implementation: same-pane process replacement mutated, an incarnation-less live coordinator was reported exited, an unbound migrated Run was stranded, a concurrent binding waiter was not cancelled, explicit headless Run inspection failed, targeted-local plus aggregate-local provider inventory dropped caller deadlines, and a migrated exact-handle row accepted replacement or stale restored authority; all nine passed after the authority, liveness, waiter, CLI, deadline, provenance, and revalidation fixes, followed by the 2,405-test orchestration suite with one skip. Live topology coverage remains a separate promotion gap."
|
||||
"evidence": "The byte-identical five-test STA-4325 oracle (SHA-256 76a2d672d27e04e5b1db2f52df3b303468bdf006be7cfd003bf443618a856c35) failed 5/5 on reported v1.4.180 commit 0b62333cf179 and latest origin/main 9bb8836bb6, then passed 5/5 on its final candidate. For STA-5729, ten added regressions failed before implementation: same-pane process replacement mutated, an incarnation-less live coordinator was reported exited, an unbound migrated Run was stranded, a concurrent binding waiter was not cancelled, explicit headless Run inspection failed, targeted-local plus aggregate-local provider inventory dropped caller deadlines, a migrated exact-handle row accepted replacement or stale restored authority, and an identity-less caller could copy the live coordinator handle; all ten passed after the authority, liveness, waiter, CLI, deadline, provenance, attestation, and revalidation fixes. The final strict-attestation slice passed 1,255 tests after an earlier broader branch gate passed 2,405 tests with one skip. Live topology coverage remains a separate promotion gap."
|
||||
},
|
||||
"performanceBudget": {
|
||||
"required": true,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { OrchestrationCompatibilityEvidence } from '../../../shared/orchestration-compatibility-evidence'
|
||||
import type { RpcRequest } from './core'
|
||||
|
||||
const UNATTESTED_CALLER = Object.freeze({ paneKey: '', launchToken: '' })
|
||||
|
||||
export function resolveRpcCallerEvidence(
|
||||
request: RpcRequest,
|
||||
params: unknown,
|
||||
legacyCoordinatorAuthority?: unknown
|
||||
): OrchestrationCompatibilityEvidence | undefined {
|
||||
if (
|
||||
request.orchestrationCompatibilityEvidence ||
|
||||
legacyCoordinatorAuthority ||
|
||||
!request.method.startsWith('orchestration.')
|
||||
) {
|
||||
return request.orchestrationCompatibilityEvidence
|
||||
}
|
||||
const values = params as Record<string, unknown>
|
||||
const terminalHandle = [values.callerTerminalHandle, values.from, values.terminal].find(
|
||||
(value): value is string => typeof value === 'string'
|
||||
)
|
||||
return { terminalHandle: terminalHandle ?? '', ...UNATTESTED_CALLER }
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import { parseRpcRequestParams } from './dispatcher-request-parsing'
|
||||
import { routeDispatcherClientHostedBrowserRpc } from './dispatcher-client-browser-routing'
|
||||
import { needsLocalCallerFingerprint } from './dispatcher-caller-fingerprint'
|
||||
import { createDispatcherStreamingFeatureEmitter } from './dispatcher-streaming-feature-emitter'
|
||||
import { resolveRpcCallerEvidence } from './dispatcher-orchestration-caller-evidence'
|
||||
|
||||
export type DispatcherOptions = { runtime: OrcaRuntimeService; methods?: readonly RpcAnyMethod[] }
|
||||
|
||||
@@ -108,6 +109,7 @@ export class RpcDispatcher {
|
||||
request,
|
||||
compatibility.legacyCoordinatorAuthority
|
||||
)
|
||||
const callerEvidence = resolveRpcCallerEvidence(request, effectiveParams, legacyCoordinator)
|
||||
const authenticatedCallerFingerprint =
|
||||
options?.authenticatedCallerFingerprint ??
|
||||
(needsLocalCallerFingerprint(request, effectiveParams)
|
||||
@@ -129,7 +131,7 @@ export class RpcDispatcher {
|
||||
revalidateLegacyCoordinator: legacyCoordinator?.revalidate,
|
||||
orchestrationCompatibilityCallerAuthority:
|
||||
compatibility.orchestrationCompatibilityCallerAuthority,
|
||||
orchestrationCompatibilityEvidence: request.orchestrationCompatibilityEvidence
|
||||
orchestrationCompatibilityEvidence: callerEvidence
|
||||
})
|
||||
}
|
||||
const result = await this.orchestrationMutations.run(
|
||||
@@ -217,6 +219,7 @@ export class RpcDispatcher {
|
||||
request,
|
||||
compatibility.legacyCoordinatorAuthority
|
||||
)
|
||||
const callerEvidence = resolveRpcCallerEvidence(request, effectiveParams, legacyCoordinator)
|
||||
const authenticatedCallerFingerprint =
|
||||
options?.authenticatedCallerFingerprint ??
|
||||
(needsLocalCallerFingerprint(request, effectiveParams)
|
||||
@@ -247,7 +250,7 @@ export class RpcDispatcher {
|
||||
revalidateLegacyCoordinator: legacyCoordinator?.revalidate,
|
||||
orchestrationCompatibilityCallerAuthority:
|
||||
compatibility.orchestrationCompatibilityCallerAuthority,
|
||||
orchestrationCompatibilityEvidence: request.orchestrationCompatibilityEvidence
|
||||
orchestrationCompatibilityEvidence: callerEvidence
|
||||
})
|
||||
}
|
||||
const result = await this.orchestrationMutations.run(
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { ORCHESTRATION_CONTRACT_VERSION } from '../../../../shared/protocol-version'
|
||||
import { RpcDispatcher } from '../dispatcher'
|
||||
import { createOrchestrationRpcHarness } from './orchestration-rpc-test-harness'
|
||||
import { ORCHESTRATION_METHODS } from './orchestration'
|
||||
|
||||
describe('orchestration coordinator caller attestation', () => {
|
||||
const harness = createOrchestrationRpcHarness()
|
||||
|
||||
afterEach(() => harness.cleanup())
|
||||
|
||||
it('rejects an identity-less caller that names the current coordinator handle', async () => {
|
||||
const { db, runtime } = harness.setup()
|
||||
const dispatcher = new RpcDispatcher({ runtime, methods: ORCHESTRATION_METHODS })
|
||||
const response = await dispatcher.dispatch({
|
||||
id: 'rpc_identity-less-coordinator',
|
||||
authToken: 'test-token',
|
||||
method: 'orchestration.taskCreate',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
params: {
|
||||
spec: 'impersonated coordinator mutation',
|
||||
run: db.getCurrentRunForPane(harness.coordinatorPaneKey)!.id,
|
||||
callerTerminalHandle: 'term_coord'
|
||||
}
|
||||
})
|
||||
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'consumer_fenced',
|
||||
message: expect.stringContaining('authenticated identity from a live Orca agent terminal'),
|
||||
data: {
|
||||
effectsApplied: false,
|
||||
nextSteps: expect.arrayContaining([
|
||||
expect.stringContaining('Omit --from'),
|
||||
expect.stringContaining('copied terminal handle does not grant mutation authority')
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -56,7 +56,11 @@ describe('orchestration RPC methods', () => {
|
||||
exitCode: null
|
||||
})
|
||||
vi.mocked(runtime.getTerminalProcessIncarnation).mockImplementation((handle) =>
|
||||
handle === 'term_worker' ? 'runtime_test:term_worker:1' : null
|
||||
handle === 'term_worker'
|
||||
? 'runtime_test:term_worker:1'
|
||||
: handle === 'term_coord'
|
||||
? 'runtime_test:term_coord:1'
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'getTerminalOrchestrationCliCommand').mockReturnValue('orca')
|
||||
vi.spyOn(runtime, 'sendTerminalAgentPrompt').mockResolvedValue({
|
||||
@@ -101,7 +105,7 @@ describe('orchestration RPC methods', () => {
|
||||
expect(db.getDispatchContext(task.id)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('deliberately permits present but unverifiable restored-terminal evidence', async () => {
|
||||
it('rejects restored-terminal evidence that names a different caller', async () => {
|
||||
setup()
|
||||
mockCurrentWorkerStart()
|
||||
// Restored/adopted terminals have no launch token, so verification returns null; this
|
||||
@@ -115,14 +119,14 @@ describe('orchestration RPC methods', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = (await call('orchestration.workerStart', {
|
||||
task: task.id,
|
||||
from: 'term_coord',
|
||||
agent: 'codex'
|
||||
})) as { state: string }
|
||||
|
||||
expect(result.state).toBe('ready')
|
||||
expect(db.getDispatchContext(task.id)).toBeDefined()
|
||||
await expect(
|
||||
call('orchestration.workerStart', {
|
||||
task: task.id,
|
||||
from: 'term_coord',
|
||||
agent: 'codex'
|
||||
})
|
||||
).rejects.toMatchObject({ code: 'consumer_fenced' })
|
||||
expect(db.getDispatchContext(task.id)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('starts a fresh agent in the coordinator current worktree', async () => {
|
||||
|
||||
@@ -11,7 +11,10 @@ import type { OrchestrationEnvironmentTransport } from '../../orchestration/envi
|
||||
import { waitForFederatedLifecycleSettlement } from '../../orchestration/federation-lifecycle-settlement'
|
||||
import { RpcDispatcher } from '../dispatcher'
|
||||
import { ORCHESTRATION_METHODS } from './orchestration'
|
||||
import { createFederationWorkerStartRequest as startRequest } from './orchestration-federation-test-request'
|
||||
import {
|
||||
createFederationWorkerStartRequest as startRequest,
|
||||
mockFederationCoordinatorAttestation
|
||||
} from './orchestration-federation-test-request'
|
||||
|
||||
describe('orchestration federation lifecycle settlement', () => {
|
||||
let homeDb: OrchestrationDb
|
||||
@@ -72,9 +75,7 @@ describe('orchestration federation lifecycle settlement', () => {
|
||||
})
|
||||
homeRuntime.setOrchestrationDb(homeDb)
|
||||
homeDispatcher = new RpcDispatcher({ runtime: homeRuntime, methods: ORCHESTRATION_METHODS })
|
||||
vi.spyOn(homeRuntime, 'getTerminalPaneKey').mockReturnValue(
|
||||
'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
|
||||
)
|
||||
mockFederationCoordinatorAttestation(homeRuntime)
|
||||
configureWorkerRuntime()
|
||||
})
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ import type { OrchestrationEnvironmentTransport } from '../../orchestration/envi
|
||||
import type { RpcRequest } from '../core'
|
||||
import { RpcDispatcher } from '../dispatcher'
|
||||
import { ORCHESTRATION_METHODS } from './orchestration'
|
||||
import {
|
||||
FEDERATION_COORDINATOR_EVIDENCE,
|
||||
mockFederationCoordinatorAttestation
|
||||
} from './orchestration-federation-test-request'
|
||||
|
||||
describe('orchestration federated worker output', () => {
|
||||
const databases: OrchestrationDb[] = []
|
||||
@@ -73,9 +77,7 @@ describe('orchestration federated worker output', () => {
|
||||
runtime: homeRuntime,
|
||||
methods: ORCHESTRATION_METHODS
|
||||
})
|
||||
vi.spyOn(homeRuntime, 'getTerminalPaneKey').mockImplementation((handle) =>
|
||||
handle === 'term_coord' ? 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' : null
|
||||
)
|
||||
mockFederationCoordinatorAttestation(homeRuntime)
|
||||
configureWorkerRuntime(workerRuntime)
|
||||
})
|
||||
|
||||
@@ -101,6 +103,7 @@ describe('orchestration federated worker output', () => {
|
||||
authToken: 'coordinator-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: 'request_windows_worker',
|
||||
orchestrationCompatibilityEvidence: FEDERATION_COORDINATOR_EVIDENCE,
|
||||
method: 'orchestration.workerStart',
|
||||
params: {
|
||||
task: taskId,
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
import { vi } from 'vitest'
|
||||
import { ORCHESTRATION_CONTRACT_VERSION } from '../../../../shared/protocol-version'
|
||||
import type { OrchestrationCompatibilityEvidence } from '../../../../shared/orchestration-compatibility-evidence'
|
||||
import type { OrcaRuntimeService } from '../../orca-runtime'
|
||||
import type { RpcRequest } from '../core'
|
||||
|
||||
export const FEDERATION_COORDINATOR_EVIDENCE = {
|
||||
terminalHandle: 'term_coord',
|
||||
paneKey: 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
|
||||
launchToken: 'coordinator-launch-token'
|
||||
} as const satisfies OrchestrationCompatibilityEvidence
|
||||
|
||||
export function mockFederationCoordinatorAttestation(runtime: OrcaRuntimeService): void {
|
||||
vi.spyOn(runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
|
||||
handle === FEDERATION_COORDINATOR_EVIDENCE.terminalHandle
|
||||
? FEDERATION_COORDINATOR_EVIDENCE.paneKey
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'verifyOrchestrationCompatibilityCaller').mockImplementation((evidence) =>
|
||||
evidence?.terminalHandle === FEDERATION_COORDINATOR_EVIDENCE.terminalHandle &&
|
||||
evidence.paneKey === FEDERATION_COORDINATOR_EVIDENCE.paneKey
|
||||
? {
|
||||
terminalHandle: evidence.terminalHandle,
|
||||
paneKey: evidence.paneKey,
|
||||
processIncarnation: 'home_runtime:coordinator:1',
|
||||
hostScope: { kind: 'local', hostId: 'local' },
|
||||
launchTokenHash: 'test-token-hash',
|
||||
terminalProvenance: 'current_runtime'
|
||||
}
|
||||
: null
|
||||
)
|
||||
}
|
||||
|
||||
export function createFederationWorkerStartRequest(
|
||||
taskId: string,
|
||||
overrides: Record<string, unknown> = {}
|
||||
@@ -10,6 +40,7 @@ export function createFederationWorkerStartRequest(
|
||||
authToken: 'coordinator-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: 'request_windows_worker',
|
||||
orchestrationCompatibilityEvidence: FEDERATION_COORDINATOR_EVIDENCE,
|
||||
method: 'orchestration.workerStart',
|
||||
params: {
|
||||
task: taskId,
|
||||
|
||||
@@ -9,7 +9,11 @@ import { OrchestrationDb } from '../../orchestration/db'
|
||||
import type { OrchestrationEnvironmentTransport } from '../../orchestration/environment-transport'
|
||||
import { RpcDispatcher } from '../dispatcher'
|
||||
import { ORCHESTRATION_METHODS } from './orchestration'
|
||||
import { createFederationWorkerStartRequest as startRequest } from './orchestration-federation-test-request'
|
||||
import {
|
||||
createFederationWorkerStartRequest as startRequest,
|
||||
FEDERATION_COORDINATOR_EVIDENCE,
|
||||
mockFederationCoordinatorAttestation
|
||||
} from './orchestration-federation-test-request'
|
||||
|
||||
describe('orchestration federation', () => {
|
||||
const databases: OrchestrationDb[] = []
|
||||
@@ -75,9 +79,7 @@ describe('orchestration federation', () => {
|
||||
runtime: homeRuntime,
|
||||
methods: ORCHESTRATION_METHODS
|
||||
})
|
||||
vi.spyOn(homeRuntime, 'getTerminalPaneKey').mockImplementation((handle) =>
|
||||
handle === 'term_coord' ? 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' : null
|
||||
)
|
||||
mockFederationCoordinatorAttestation(homeRuntime)
|
||||
configureWorkerRuntime(workerRuntime)
|
||||
})
|
||||
|
||||
@@ -415,6 +417,7 @@ describe('orchestration federation', () => {
|
||||
authToken: 'coordinator-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: 'home_reply_request',
|
||||
orchestrationCompatibilityEvidence: FEDERATION_COORDINATOR_EVIDENCE,
|
||||
method: 'orchestration.reply',
|
||||
params: {
|
||||
id: question!.id,
|
||||
@@ -472,6 +475,7 @@ describe('orchestration federation', () => {
|
||||
authToken: 'coordinator-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: 'home_late_reply_request',
|
||||
orchestrationCompatibilityEvidence: FEDERATION_COORDINATOR_EVIDENCE,
|
||||
method: 'orchestration.reply',
|
||||
params: { id: questionId, body: 'yes', from: 'term_coord' }
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('orchestration gate Run authorization', () => {
|
||||
|
||||
it('G2 refuses an unattested caller creating a gate on another Run task', async () => {
|
||||
const harness = createHarness()
|
||||
const runA = bindCurrentCoordinatorRun(harness)
|
||||
bindCurrentCoordinatorRun(harness)
|
||||
const before = adoptedTaskState(harness)
|
||||
|
||||
const response = await harness.dispatcher.dispatch(
|
||||
@@ -78,8 +78,8 @@ describe('orchestration gate Run authorization', () => {
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'task_not_found',
|
||||
message: `Task ${harness.taskId} was not found in Run ${runA}.`
|
||||
code: 'consumer_fenced',
|
||||
data: { effectsApplied: false }
|
||||
}
|
||||
})
|
||||
expect(adoptedTaskState(harness)).toEqual(before)
|
||||
@@ -214,7 +214,10 @@ describe('orchestration gate Run authorization', () => {
|
||||
)
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({ ok: false, error: { message: `Gate not found: ${gate.id}` } })
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
expect(harness.db.getGate(gate.id)?.status).toBe('pending')
|
||||
expect(harness.db.getTask(harness.taskId)?.status).toBe('blocked')
|
||||
})
|
||||
|
||||
@@ -19,6 +19,7 @@ export function createOrchestrationRpcHarness() {
|
||||
let dbOpen = false
|
||||
let runtime: OrcaRuntimeService
|
||||
let activeRunId: string | undefined
|
||||
let defaultCtx: RpcContext
|
||||
|
||||
const coordinatorPaneKey = COORDINATOR_PANE_KEY
|
||||
|
||||
@@ -36,6 +37,28 @@ export function createOrchestrationRpcHarness() {
|
||||
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((handle) =>
|
||||
handle.startsWith('term_') ? `runtime_test:${handle}:1` : null
|
||||
)
|
||||
vi.spyOn(runtime, 'verifyOrchestrationCompatibilityCaller').mockImplementation((evidence) => {
|
||||
const terminalHandle = evidence?.terminalHandle
|
||||
const paneKey = terminalHandle ? runtime.getTerminalPaneKey(terminalHandle) : null
|
||||
const terminalAuthority = terminalHandle
|
||||
? runtime.getOrchestrationDispatchAuthority(terminalHandle)
|
||||
: null
|
||||
const processIncarnation = terminalHandle
|
||||
? (terminalAuthority?.processIncarnation ??
|
||||
runtime.getTerminalProcessIncarnation(terminalHandle))
|
||||
: null
|
||||
if (!terminalHandle || !paneKey || !processIncarnation || evidence.paneKey !== paneKey) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
terminalHandle,
|
||||
paneKey,
|
||||
processIncarnation,
|
||||
launchTokenHash: 'test-launch-token-hash',
|
||||
hostScope: terminalAuthority?.hostScope ?? { kind: 'local', hostId: 'local' },
|
||||
terminalProvenance: 'current_runtime'
|
||||
}
|
||||
})
|
||||
if (withBoundRun) {
|
||||
activeRunId = db.createRun({
|
||||
objective: 'Test Run',
|
||||
@@ -51,7 +74,8 @@ export function createOrchestrationRpcHarness() {
|
||||
} else {
|
||||
activeRunId = undefined
|
||||
}
|
||||
return { db, runtime, ctx: { runtime }, activeRunId }
|
||||
defaultCtx = { runtime }
|
||||
return { db, runtime, ctx: defaultCtx, activeRunId }
|
||||
}
|
||||
|
||||
function cleanup(): void {
|
||||
@@ -96,7 +120,27 @@ export function createOrchestrationRpcHarness() {
|
||||
}
|
||||
}
|
||||
const parsed = method.params ? method.params.parse(scopedParams) : undefined
|
||||
return method.handler(parsed, ctx)
|
||||
const callerHandle =
|
||||
typeof scopedParams.callerTerminalHandle === 'string'
|
||||
? scopedParams.callerTerminalHandle
|
||||
: typeof scopedParams.from === 'string'
|
||||
? scopedParams.from
|
||||
: typeof scopedParams.terminal === 'string'
|
||||
? scopedParams.terminal
|
||||
: undefined
|
||||
const callerPaneKey = callerHandle ? runtime.getTerminalPaneKey(callerHandle) : null
|
||||
const effectiveCtx =
|
||||
ctx === defaultCtx && callerHandle && callerPaneKey
|
||||
? {
|
||||
...ctx,
|
||||
orchestrationCompatibilityEvidence: {
|
||||
terminalHandle: callerHandle,
|
||||
paneKey: callerPaneKey,
|
||||
launchToken: 'test-launch-token'
|
||||
}
|
||||
}
|
||||
: ctx
|
||||
return method.handler(parsed, effectiveCtx)
|
||||
}
|
||||
|
||||
return { coordinatorPaneKey, setup, cleanup, findMethod, call }
|
||||
|
||||
@@ -22,13 +22,40 @@ export type RunScopeParams = {
|
||||
export function assertCallerHandleMatchesEvidence(
|
||||
runtime: OrcaRuntimeService,
|
||||
callerTerminalHandle: string,
|
||||
callerEvidence?: OrchestrationCompatibilityEvidence
|
||||
callerEvidence?: OrchestrationCompatibilityEvidence,
|
||||
options: {
|
||||
callerAuthority?: OrchestrationCompatibilityCallerAuthority
|
||||
allowLegacyAuthority?: boolean
|
||||
} = {}
|
||||
): void {
|
||||
if (!callerEvidence) {
|
||||
if (callerEvidence && callerEvidence.terminalHandle !== callerTerminalHandle) {
|
||||
throw new OrchestrationError(
|
||||
'consumer_fenced',
|
||||
`This terminal is attested as ${callerEvidence.terminalHandle} and cannot act as ${callerTerminalHandle}.`,
|
||||
{ effectsApplied: false }
|
||||
)
|
||||
}
|
||||
// Why: trusted in-process handlers omit evidence; public RPCs receive an invalid sentinel.
|
||||
if (!callerEvidence || options.allowLegacyAuthority) {
|
||||
return
|
||||
}
|
||||
const attested = runtime.verifyOrchestrationCompatibilityCaller(callerEvidence)
|
||||
if (attested && attested.terminalHandle !== callerTerminalHandle) {
|
||||
const attested =
|
||||
options.callerAuthority ?? runtime.verifyOrchestrationCompatibilityCaller(callerEvidence)
|
||||
if (!attested) {
|
||||
throw new OrchestrationError(
|
||||
'consumer_fenced',
|
||||
'Coordinator mutations require authenticated identity from a live Orca agent terminal. No effects were applied.',
|
||||
{
|
||||
effectsApplied: false,
|
||||
nextSteps: [
|
||||
'Run the command inside the coordinator agent terminal with the version-matched Orca CLI.',
|
||||
'Omit --from; Orca resolves and attests the invoking agent terminal automatically.',
|
||||
'Use explicit Run reads for inspection; a copied terminal handle does not grant mutation authority.'
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
if (attested.terminalHandle !== callerTerminalHandle) {
|
||||
throw new OrchestrationError(
|
||||
'consumer_fenced',
|
||||
`This terminal is attested as ${attested.terminalHandle} and cannot act as ${callerTerminalHandle}.`,
|
||||
@@ -65,7 +92,9 @@ export function resolveOrchestrationCaller(
|
||||
params: OrchestrationCallerParams
|
||||
): string | null {
|
||||
if (!params.evidenceAssertedByCaller) {
|
||||
assertCallerHandleMatchesEvidence(runtime, params.callerTerminalHandle, params.callerEvidence)
|
||||
assertCallerHandleMatchesEvidence(runtime, params.callerTerminalHandle, params.callerEvidence, {
|
||||
callerAuthority: params.callerAuthority
|
||||
})
|
||||
}
|
||||
const paneKey =
|
||||
params.callerAuthority?.terminalHandle === params.callerTerminalHandle
|
||||
@@ -98,10 +127,13 @@ export function resolveRunScope(runtime: OrcaRuntimeService, params: RunScopePar
|
||||
orchestrationSkillRecoveryData()
|
||||
)
|
||||
}
|
||||
assertCallerHandleMatchesEvidence(runtime, params.callerTerminalHandle, params.callerEvidence)
|
||||
if (explicit && params.legacyCoordinatorRunId === explicit.id) {
|
||||
assertCallerHandleMatchesEvidence(runtime, params.callerTerminalHandle, params.callerEvidence, {
|
||||
allowLegacyAuthority: true
|
||||
})
|
||||
return explicit
|
||||
}
|
||||
assertCallerHandleMatchesEvidence(runtime, params.callerTerminalHandle, params.callerEvidence)
|
||||
const paneKey = params.callerPaneKey ?? runtime.getTerminalPaneKey(params.callerTerminalHandle)
|
||||
if (!paneKey) {
|
||||
throw new OrchestrationError(
|
||||
|
||||
@@ -88,7 +88,11 @@ describe('orchestration RPC methods', () => {
|
||||
|
||||
it('requires runtime-observed stable pane identity for binding', async () => {
|
||||
setup(false)
|
||||
vi.spyOn(runtime, 'getTerminalPaneKey').mockReturnValue(null)
|
||||
const transientPaneKey = 'tab_stale:33333333-3333-4333-8333-333333333333'
|
||||
vi.spyOn(runtime, 'getTerminalPaneKey')
|
||||
.mockReturnValueOnce(transientPaneKey)
|
||||
.mockReturnValueOnce(transientPaneKey)
|
||||
.mockReturnValue(null)
|
||||
|
||||
await expect(
|
||||
call('orchestration.runCreate', { objective: 'No pane', from: 'term_stale' })
|
||||
|
||||
@@ -126,7 +126,10 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [
|
||||
{ effectsApplied: false }
|
||||
)
|
||||
}
|
||||
assertCallerHandleMatchesEvidence(runtime, params.from, orchestrationCompatibilityEvidence)
|
||||
assertCallerHandleMatchesEvidence(runtime, params.from, orchestrationCompatibilityEvidence, {
|
||||
callerAuthority,
|
||||
allowLegacyAuthority: Boolean(legacyCoordinatorAuthority)
|
||||
})
|
||||
const db = runtime.getOrchestrationDb()
|
||||
const targetRun = db.getRun(params.id)
|
||||
const identity = resolveRunCoordinatorIdentity(runtime, params.from, paneKey)
|
||||
@@ -173,7 +176,10 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [
|
||||
requireStablePane: true,
|
||||
evidenceAssertedByCaller: true
|
||||
})
|
||||
assertCallerHandleMatchesEvidence(runtime, params.from, orchestrationCompatibilityEvidence)
|
||||
assertCallerHandleMatchesEvidence(runtime, params.from, orchestrationCompatibilityEvidence, {
|
||||
callerAuthority,
|
||||
allowLegacyAuthority: Boolean(legacyCoordinatorAuthority)
|
||||
})
|
||||
const currentIdentity = resolveRunCoordinatorIdentity(runtime, params.from, currentPaneKey)
|
||||
const revalidatedCaller = orchestrationCompatibilityEvidence
|
||||
? runtime.verifyOrchestrationCompatibilityCaller(
|
||||
|
||||
@@ -105,7 +105,23 @@ async function createPromptContractHarness(
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((candidate) =>
|
||||
candidate === handle ? `runtime_test:${handle}:1` : null
|
||||
candidate === handle
|
||||
? `runtime_test:${handle}:1`
|
||||
: candidate === 'term_coord'
|
||||
? 'runtime_test:term_coord:1'
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'verifyOrchestrationCompatibilityCaller').mockImplementation((evidence) =>
|
||||
evidence?.terminalHandle === 'term_coord' && evidence.paneKey === COORDINATOR_PANE_KEY
|
||||
? {
|
||||
terminalHandle: evidence.terminalHandle,
|
||||
paneKey: evidence.paneKey,
|
||||
processIncarnation: 'runtime_test:term_coord:1',
|
||||
hostScope: { kind: 'local', hostId: 'local' },
|
||||
launchTokenHash: 'test-token-hash',
|
||||
terminalProvenance: 'current_runtime'
|
||||
}
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {})
|
||||
vi.spyOn(runtime, 'showTerminal').mockResolvedValue({
|
||||
@@ -139,6 +155,11 @@ async function createPromptContractHarness(
|
||||
authToken: 'caller-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: `${REQUEST_ID}_${outcome}`,
|
||||
orchestrationCompatibilityEvidence: {
|
||||
terminalHandle: 'term_coord',
|
||||
paneKey: COORDINATOR_PANE_KEY,
|
||||
launchToken: 'coordinator-launch-token'
|
||||
},
|
||||
method: 'orchestration.workerStart',
|
||||
params: {
|
||||
task: task.id,
|
||||
|
||||
@@ -34,7 +34,23 @@ describe('orchestration new-worktree workers', () => {
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((handle) =>
|
||||
handle === 'term_worker' ? 'runtime_test:term_worker:1' : null
|
||||
handle === 'term_worker'
|
||||
? 'runtime_test:term_worker:1'
|
||||
: handle === 'term_coord'
|
||||
? 'runtime_test:term_coord:1'
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'verifyOrchestrationCompatibilityCaller').mockImplementation((evidence) =>
|
||||
evidence?.terminalHandle === 'term_coord' && evidence.paneKey === coordinatorPaneKey
|
||||
? {
|
||||
terminalHandle: evidence.terminalHandle,
|
||||
paneKey: evidence.paneKey,
|
||||
processIncarnation: 'runtime_test:term_coord:1',
|
||||
hostScope: { kind: 'local', hostId: 'local' },
|
||||
launchTokenHash: 'test-token-hash',
|
||||
terminalProvenance: 'current_runtime'
|
||||
}
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {})
|
||||
vi.spyOn(runtime, 'showTerminal').mockResolvedValue({
|
||||
@@ -537,6 +553,11 @@ describe('orchestration new-worktree workers', () => {
|
||||
authToken: 'caller-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: 'worker_start_request',
|
||||
orchestrationCompatibilityEvidence: {
|
||||
terminalHandle: 'term_coord',
|
||||
paneKey: coordinatorPaneKey,
|
||||
launchToken: 'coordinator-launch-token'
|
||||
},
|
||||
method: 'orchestration.workerStart',
|
||||
params: {
|
||||
task: task.id,
|
||||
@@ -604,6 +625,11 @@ describe('orchestration new-worktree workers', () => {
|
||||
authToken: 'caller-token',
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: 'worker_start_request',
|
||||
orchestrationCompatibilityEvidence: {
|
||||
terminalHandle: 'term_coord',
|
||||
paneKey: coordinatorPaneKey,
|
||||
launchToken: 'coordinator-launch-token'
|
||||
},
|
||||
method: 'orchestration.workerStart',
|
||||
params: {
|
||||
task: task.id,
|
||||
@@ -632,6 +658,20 @@ describe('orchestration new-worktree workers', () => {
|
||||
? `tab_coord_reminted:${coordinatorPaneKey.split(':')[1]}`
|
||||
: null
|
||||
)
|
||||
vi.spyOn(restartedRuntime, 'verifyOrchestrationCompatibilityCaller').mockImplementation(
|
||||
(evidence) =>
|
||||
evidence?.terminalHandle === 'term_coord_reminted' &&
|
||||
evidence.paneKey === `tab_coord_reminted:${coordinatorPaneKey.split(':')[1]}`
|
||||
? {
|
||||
terminalHandle: evidence.terminalHandle,
|
||||
paneKey: evidence.paneKey,
|
||||
processIncarnation: 'runtime_test:term_coord:1',
|
||||
hostScope: { kind: 'local', hostId: 'local' },
|
||||
launchTokenHash: 'test-token-hash',
|
||||
terminalProvenance: 'restored'
|
||||
}
|
||||
: null
|
||||
)
|
||||
const recreateWorktree = vi
|
||||
.spyOn(restartedRuntime, 'createManagedWorktree')
|
||||
.mockRejectedValue(new Error('replay recreated the worktree'))
|
||||
@@ -646,6 +686,11 @@ describe('orchestration new-worktree workers', () => {
|
||||
...request,
|
||||
id: 'rpc_worker_start_retry',
|
||||
authToken: 'caller-token-after-restart',
|
||||
orchestrationCompatibilityEvidence: {
|
||||
terminalHandle: 'term_coord_reminted',
|
||||
paneKey: `tab_coord_reminted:${coordinatorPaneKey.split(':')[1]}`,
|
||||
launchToken: 'coordinator-launch-token-after-restart'
|
||||
},
|
||||
params: { ...(request.params as Record<string, unknown>), from: 'term_coord_reminted' }
|
||||
})
|
||||
|
||||
|
||||
@@ -137,7 +137,10 @@ describe('#11745 H1 — gate methods authorize the caller Run', () => {
|
||||
)
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({ ok: false, error: { code: 'task_not_found' } })
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
expect(adoptedGraph(harness)).toEqual(before)
|
||||
})
|
||||
|
||||
@@ -175,7 +178,10 @@ describe('#11745 H1 — gate methods authorize the caller Run', () => {
|
||||
)
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({ ok: false, error: { message: `Gate not found: ${gate.id}` } })
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
expect(harness.db.getGate(gate.id)).toMatchObject({ status: 'pending', resolution: null })
|
||||
expect(adoptedGraph(harness)).toEqual(before)
|
||||
expect(before.task).toBe('blocked')
|
||||
@@ -558,7 +564,7 @@ describe('#11745 H3 — adopted Run claimed by the legacy coordinator', () => {
|
||||
})
|
||||
|
||||
describe('#11745 H4 — unattested caller on a taken-over adopted Run', () => {
|
||||
it('DOCUMENTS the accepted behaviour: the binding alone grants authority', async () => {
|
||||
it('requires attestation after a current coordinator takes over an adopted Run', async () => {
|
||||
const harness = createHarness()
|
||||
await legacyClaimsAdoptedRun(harness, 'v-h4')
|
||||
await currentCoordinatorTakesOver(harness, 'v-h4')
|
||||
@@ -577,8 +583,8 @@ describe('#11745 H4 — unattested caller on a taken-over adopted Run', () => {
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({
|
||||
ok: true,
|
||||
result: { task: { run_id: harness.adoptedRunId } }
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('legacy coordinator fence jurisdiction', () => {
|
||||
expect(harness.db.listTasks({ runId: harness.adoptedRunId })).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('lets the post-takeover owner operate the adopted Run without attestation', async () => {
|
||||
it('requires attestation from the post-takeover owner', async () => {
|
||||
const harness = createHarness()
|
||||
await claimAdoptedRunAsLegacyCoordinator(harness)
|
||||
await takeoverAdoptedRun(harness)
|
||||
@@ -181,8 +181,8 @@ describe('legacy coordinator fence jurisdiction', () => {
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({
|
||||
ok: true,
|
||||
result: { task: { run_id: harness.adoptedRunId, spec: 'owner assignment' } }
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -279,7 +279,12 @@ describe('legacy question takeover compatibility', () => {
|
||||
from: CURRENT_COORDINATOR_HANDLE,
|
||||
run: harness.adoptedRunId
|
||||
},
|
||||
'answer-after-takeover'
|
||||
'answer-after-takeover',
|
||||
{
|
||||
terminalHandle: CURRENT_COORDINATOR_HANDLE,
|
||||
paneKey: CURRENT_COORDINATOR_PANE,
|
||||
launchToken: 'current-coordinator-token'
|
||||
}
|
||||
)
|
||||
)
|
||||
).resolves.toMatchObject({ ok: true })
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('legacy coordinator gate run routing', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('routes an unnamed Run to the caller binding even when attestation fails', async () => {
|
||||
it('does not route an unnamed Run from an unattested caller', async () => {
|
||||
const harness = createHarness()
|
||||
const run = harness.db.createRun({
|
||||
objective: 'current work',
|
||||
@@ -64,9 +64,10 @@ describe('legacy coordinator gate run routing', () => {
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({
|
||||
ok: true,
|
||||
result: { task: { run_id: run.id, spec: 'fresh assignment' } }
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
expect(harness.db.listTasks({ runId: run.id })).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('lets a current coordinator rebind the unclaimed adopted Run with actionable guidance', async () => {
|
||||
|
||||
@@ -217,10 +217,10 @@ describe('legacy coordinator delivery targets after takeover', () => {
|
||||
)
|
||||
)
|
||||
|
||||
// It still owns the Run binding, so it reads its own Run rather than being fenced read-only.
|
||||
// Binding metadata alone is not caller identity after the coordinator pane changes.
|
||||
expect(response).toMatchObject({
|
||||
ok: true,
|
||||
result: { runId: harness.adoptedRunId, legacyReadOnly: false }
|
||||
ok: false,
|
||||
error: { code: 'consumer_fenced', data: { effectsApplied: false } }
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -242,6 +242,18 @@ function createHarness(): Harness {
|
||||
}
|
||||
return null
|
||||
})
|
||||
vi.spyOn(runtime, 'verifyOrchestrationCompatibilityCaller').mockImplementation((evidence) =>
|
||||
evidence?.terminalHandle === COORDINATOR_HANDLE && evidence.paneKey === COORDINATOR_PANE
|
||||
? {
|
||||
terminalHandle: COORDINATOR_HANDLE,
|
||||
paneKey: COORDINATOR_PANE,
|
||||
processIncarnation: 'runtime:coordinator:1',
|
||||
hostScope: { kind: 'local', hostId: 'local' },
|
||||
launchTokenHash: 'test-token-hash',
|
||||
terminalProvenance: 'current_runtime'
|
||||
}
|
||||
: null
|
||||
)
|
||||
const runId = db.createRun({
|
||||
objective: 'Enforce Task/Dispatch state',
|
||||
coordinatorHandle: COORDINATOR_HANDLE,
|
||||
@@ -369,7 +381,12 @@ function request(method: string, params: Record<string, unknown>): RpcRequest {
|
||||
method,
|
||||
params,
|
||||
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
|
||||
orchestrationRequestId: `task_dispatch_invariant_${requestSequence}`
|
||||
orchestrationRequestId: `task_dispatch_invariant_${requestSequence}`,
|
||||
orchestrationCompatibilityEvidence: {
|
||||
terminalHandle: COORDINATOR_HANDLE,
|
||||
paneKey: COORDINATOR_PANE,
|
||||
launchToken: 'test-launch-token'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user