fix(orchestration): file federated worker mail under the coordinator Run (#19542)

This commit is contained in:
Jinwoo Hong
2026-09-08 04:03:07 -04:00
committed by GitHub
parent 53852c9ca4
commit 98fdbc4ade
100 changed files with 1599 additions and 546 deletions
+9
View File
@@ -13658,6 +13658,7 @@
"invariant": "Starting a worker in the coordinator's current workspace must materialize one inactive terminal tab before worker-start returns, preserve coordinator focus, and remain exactly once after workspace re-entry. After an app update or restart, an exact live legacy worker must fence automatic provider resume, adopt its original PTY into its original background pane, retain readable output, and clear the resume record without spawning, writing, signalling, interrupting, replacing, or focusing the worker. A current-contract worker whose renderer graph identity is temporarily absent must retain its Dispatch capability and settle exactly once from exact hook-attested handle, pane, and process evidence; otherwise only an exact attested coordinator may take over. A worker_done caller may report success only after the owning runtime returns an explicit lifecycle verdict or authoritative reads prove that the exact Task, Dispatch, and worker report receipt settled the expected outcome. Federated terminal settlement must remain replay-eligible until the worker durably acknowledges it, and identical same-outcome retries must converge idempotently. Independently updated clients and worker servers must preserve the negotiated protocol: current peers use Run-home lifecycle settlement, while protocol v1/v2 peers retain their legacy completion path without receiving newer-only fields. A federated worker may accept only the authority defined by its negotiated protocol. An exact existing target workspace must receive a discoverable tab without stealing coordinator focus; if renderer reveal fails, worker-start must expose that the live worker remains background-only. Run and Dispatch checks must resolve through the caller's stable pane identity when a terminal handle is reminted, while a live handle outranks mismatched pane metadata. A nested worker's creator edge requires the current creator pane, process incarnation, and owning Run generation; reminting and rebinding that pane to another Run must remove the stale edge. Explicit legacy terminal inspection remains handle-scoped, and remote or headless worker presentation remains background-only.",
"oracle": "Drive Run create, Task create, and worker-start through production Electron runtimes with a deterministic Codex fixture. Require append-only ledgers with one still-live PID and no interruption, a visible inactive worker tab while the coordinator stays active, Run delivery through stable pane identity, and stable PTY/incarnation, tab, leaf, worktree, Task, and Dispatch across workspace re-entry. In a restart journey, retain the original daemon PTY and PID, remove renderer ownership, retain sleeping-session evidence, mark the Dispatch legacy, relaunch, and require exact inactive tab adoption, readable ACK output, cleared resume state, one spawn, and no resume argv or Conversation interrupted text after another workspace round trip. The service oracle removes renderer lookup identity from current-contract callers while retaining real restored-PTY and hook commitments, replays authenticated completion and takeover across fresh runtimes, and requires one Task, Dispatch, terminal authority, message, mutation, ordinary-mail delivery, remote process fencing, and unchanged fixture marker bytes while foreign pane evidence remains rejected. Unit tests separately remint a creator pane and process from Run A into Run B, require the nested Run A worker to fall back to its current coordinator, require indexed query plans, and bound 300 Task reads with 50,000 retained Runs. They also assert authority-specific legacy affordances, exact identity and owner matching, retained-output fallback, pane-stable routing, federated non-activation, and SSH fallback parity.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 npx vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/orchestration/messaging/check-worker-federated-attachment.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/cli/handlers/orchestration.test.ts src/cli/handlers/orchestration-check-identity.test.ts src/cli/handlers/orchestration-worker-cli.test.ts src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts src/main/runtime/rpc/methods/orchestration/messaging/send.test.ts src/main/ssh/ssh-remote-orca-cli.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/cli/handlers/orchestration-lifecycle-rejection.test.ts src/cli/handlers/orchestration-lifecycle-json-rejection.test.ts src/cli/handlers/orchestration-migration.test.ts",
@@ -13672,6 +13673,7 @@
"pnpm run build:cli && SKIP_BUILD=1 pnpm exec playwright test tests/e2e/orchestration-worker-settlement-release-cli.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1"
],
"testFiles": [
"src/main/runtime/rpc/methods/orchestration/messaging/check-worker-federated-attachment.test.ts",
"src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts",
"src/main/runtime/orchestration/formatter.test.ts",
"src/main/runtime/orchestration/orchestration-legacy-worker-terminal-recovery.test.ts",
@@ -13695,6 +13697,13 @@
"tests/e2e/orchestration-worker-settlement-release-cli.spec.ts"
],
"assertionRefs": [
{
"file": "src/main/runtime/rpc/methods/orchestration/messaging/check-worker-federated-attachment.test.ts",
"assertions": [
"replays the coordinator instruction and takes its ack after the app restarts",
"files loopback mail once under the local Dispatch Run without replacing its owner"
]
},
{
"file": "src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts",
"assertions": [
@@ -168,9 +168,10 @@ describe('orchestration kernel', () => {
expect(kernel).toContain(
'`projection.attention` categories, `projection.attention.requiresAction`, and literal `projection.nextAction` argv'
)
expect(kernel).toContain(
'An `inspect` `nextAction` on a `live` row with `attention.requiresAction` false is informational, not a command to re-run: keep waiting with `check --wait`'
)
// Unverifiable workers can still owe release; the guide must explain the action itself.
expect(kernel).toContain('A `none` `nextAction` has no argv to run')
expect(kernel).toContain('read `liveness.reason` and keep waiting with `check --wait`')
expect(kernel).toContain('Absence never earns an argv; settlement and pending work still do')
expect(kernel).toContain('choose `worker-stop` or `worker-abandon`')
})
+2 -2
View File
@@ -137,8 +137,8 @@ After three consecutive empty waits, stop waiting blindly and enumerate with
`ORCA orchestration worker-list --include-remote --json` (defaults to the bound
Run; `--run <run_id>` overrides; the receipt's `scope` names which), acting on
each row's `projection.attention` categories, `projection.attention.requiresAction`, and literal `projection.nextAction` argv.
An `inspect` `nextAction` on a `live` row with `attention.requiresAction` false
is informational, not a command to re-run: keep waiting with `check --wait`.
A `none` `nextAction` has no argv to run: read `liveness.reason` and keep waiting
with `check --wait`. Absence never earns an argv; settlement and pending work still do.
Leave the wait only on positive proof the agent stopped: `exited` liveness, the
worker's own observation of process exit, or a transcript whose final agent turn
sent no `worker_done`. Then load `references/recovery-and-cleanup.md` and choose
File diff suppressed because one or more lines are too long
@@ -145,7 +145,7 @@ export class OrcaRuntimeWithSubscribeToTerminalResize extends OrcaRuntimeWithApp
exitCause: cause,
handle
}),
...(recipient.runId ? { runId: recipient.runId } : {})
runId: dispatch.run_id
})
this.notifyMessageArrived(escalation.to_handle, escalation.type)
} catch (error) {
@@ -95,7 +95,10 @@ describe('OrcaRuntimeService', () => {
return [name, createRootDispatch(db, task.id, handles[name], paneKey(name))]
})
)
const legacyTask = db.createTask({ spec: 'legacy worker' })
const legacyTask = db.createTask({
runId: 'run_legacy_local',
spec: 'legacy worker'
})
const legacyDispatch = createRootDispatch(
db,
legacyTask.id,
@@ -216,7 +216,10 @@ describe('OrcaRuntimeService', () => {
runtime as unknown as {
leaves: Map<
string,
{ lastAgentStatus: string | null; lastAgentStatusObservedLive: boolean }
{
lastAgentStatus: string | null
lastAgentStatusObservedLive: boolean
}
>
}
).leaves.values()
@@ -415,7 +418,12 @@ describe('OrcaRuntimeService', () => {
const [terminal] = (await runtime.listTerminals()).terminals
runtime.onPtyData('pty-1', '\x1b]0;Codex working\x07', 100)
db.insertMessage({ from: 'term_worker', to: terminal.handle, subject: 'pending' })
db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: terminal.handle,
subject: 'pending'
})
runtime.notifyMessageArrived(terminal.handle, 'status')
db.close()
@@ -56,7 +56,10 @@ describe('OrcaRuntimeService', () => {
)
const db = new OrchestrationDb(':memory:')
try {
const task = db.createTask({ spec: 'continue after missing worker recovery' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'continue after missing worker recovery'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -163,7 +166,10 @@ describe('OrcaRuntimeService', () => {
)
const db = new OrchestrationDb(':memory:')
try {
const task = db.createTask({ spec: 'retry missing worker recovery' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'retry missing worker recovery'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -7,9 +7,13 @@ type PointerTarget = { ptyId: string; processIncarnation: string }
// The slice of the mailbox store the pointer batch selector depends on.
type PointerStore = {
insertMessage(message: { from: string; to: string; subject: string; type?: MessageType }): {
id: string
}
insertMessage(message: {
runId: string
from: string
to: string
subject: string
type?: MessageType
}): { id: string }
stageMailboxPointerEnter(ids: string[], target: PointerTarget): boolean
markMailboxPointerWriteAttempted(ids: string[], target: PointerTarget): boolean
getUndeliveredUnreadMessages(
@@ -32,7 +36,12 @@ describe.each(STORES)('mailbox pointer reservations (%s)', (_name, createStore)
it('refuses a claim another flight already holds', () => {
const store = createStore()
const message = store.insertMessage({ from: 'a', to: 'run:run-1', subject: 'contended' })
const message = store.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'contended'
})
expect(store.stageMailboxPointerEnter([message.id], rival)).toBe(true)
expect(store.stageMailboxPointerEnter([message.id], mine)).toBe(false)
@@ -41,8 +50,18 @@ describe.each(STORES)('mailbox pointer reservations (%s)', (_name, createStore)
it('rolls the whole batch back when one row is already claimed', () => {
const store = createStore()
const free = store.insertMessage({ from: 'a', to: 'run:run-1', subject: 'free' })
const taken = store.insertMessage({ from: 'a', to: 'run:run-1', subject: 'taken' })
const free = store.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'free'
})
const taken = store.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'taken'
})
expect(store.stageMailboxPointerEnter([taken.id], rival)).toBe(true)
expect(store.stageMailboxPointerEnter([free.id, taken.id], mine)).toBe(false)
@@ -52,8 +71,19 @@ describe.each(STORES)('mailbox pointer reservations (%s)', (_name, createStore)
it('applies the exclusion and limit the pointer batch selector relies on', () => {
const store = createStore()
store.insertMessage({ from: 'a', to: 'run:run-1', subject: 'reserved', type: 'escalation' })
const kept = store.insertMessage({ from: 'a', to: 'run:run-1', subject: 'kept' })
store.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'reserved',
type: 'escalation'
})
const kept = store.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'kept'
})
expect(
store
@@ -12,13 +12,14 @@ describe('coordinator decision-gate authority', () => {
it('opens a gate only for the sender-owned active Dispatch', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'owned gate target' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'owned gate target' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', 'tab_owner:leaf_owner')
const logs: string[] = []
openDecisionGateFromMessage(
db,
db.insertMessage({
runId: 'run_legacy_local',
from: 'term_owner',
to: 'term_coordinator',
subject: 'Need approval',
@@ -41,20 +42,27 @@ describe('coordinator decision-gate authority', () => {
it('rejects a gate targeting another active Dispatch without mutating either Task', () => {
db = new OrchestrationDb(':memory:')
const attackerTask = db.createTask({ spec: 'attacker assignment' })
const attackerTask = db.createTask({
runId: 'run_legacy_local',
spec: 'attacker assignment'
})
const attacker = createRootDispatch(
db,
attackerTask.id,
'term_attacker',
'tab_attacker:leaf_attacker'
)
const victimTask = db.createTask({ spec: 'victim assignment' })
const victimTask = db.createTask({
runId: 'run_legacy_local',
spec: 'victim assignment'
})
const victim = createRootDispatch(db, victimTask.id, 'term_victim', 'tab_victim:leaf_victim')
const logs: string[] = []
openDecisionGateFromMessage(
db,
db.insertMessage({
runId: 'run_legacy_local',
from: 'term_attacker',
to: 'term_coordinator',
subject: 'Block the victim',
@@ -79,12 +87,16 @@ describe('coordinator decision-gate authority', () => {
it('accepts the canonical sender of an imported federated Dispatch', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'remote gate target' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'remote gate target'
})
const dispatch = createRootDispatch(db, task.id, 'remote-worker')
openDecisionGateFromMessage(
db,
db.insertMessage({
runId: 'run_legacy_local',
from: `dispatch:${dispatch.id}`,
to: 'term_coordinator',
subject: 'Remote approval required',
@@ -66,7 +66,7 @@ describe('coordinator dispatch with an unobserved prompt', () => {
it('never re-pastes a preamble whose turn start was not observed', async () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'do the work' })
const runtime = createRuntime(new Error('agent_prompt_stalled'))
const logs: string[] = []
@@ -88,7 +88,7 @@ describe('coordinator dispatch with an unobserved prompt', () => {
it('lets a late worker report settle a dispatch whose prompt was unobserved', async () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'do the work' })
await dispatch(createRuntime(new Error('agent_prompt_stalled')), task.id, [])
const dispatchId = db.getDispatchContext(task.id)!.id
const minted = db.mintDispatchCapability({
@@ -119,7 +119,7 @@ describe('coordinator dispatch with an unobserved prompt', () => {
it('still fails the dispatch when the prompt was never delivered', async () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'do the work' })
const runtime = createRuntime(new Error('terminal_not_writable'))
await expect(dispatch(runtime, task.id, [])).rejects.toThrow('terminal_not_writable')
@@ -44,8 +44,8 @@ describe('Coordinator drift probe coalescing', () => {
: { base: 'origin/main', behind: 0, recentSubjects: [] }
}
}
const first = db.createTask({ spec: 'first task' })
const second = db.createTask({ spec: 'second task' })
const first = db.createTask({ runId: 'run_legacy_local', spec: 'first task' })
const second = db.createTask({ runId: 'run_legacy_local', spec: 'second task' })
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
coordinatorHandle: 'coord',
@@ -65,6 +65,7 @@ describe('Coordinator drift probe coalescing', () => {
throw new Error(`missing dispatch for ${task.id}`)
}
db.insertMessage({
runId: 'run_legacy_local',
from: dispatch.assignee_handle,
to: 'coord',
subject: 'Done',
@@ -105,8 +106,14 @@ describe('Coordinator drift probe coalescing', () => {
}
}
}
const refused = db.createTask({ spec: 'requires a current base' })
const allowed = db.createTask({ spec: 'can use stale base\nallow-stale-base: true' })
const refused = db.createTask({
runId: 'run_legacy_local',
spec: 'requires a current base'
})
const allowed = db.createTask({
runId: 'run_legacy_local',
spec: 'can use stale base\nallow-stale-base: true'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
coordinatorHandle: 'coord',
@@ -12,20 +12,21 @@ describe('coordinator escalation authority', () => {
it('rejects an escalation targeting another active Dispatch', () => {
db = new OrchestrationDb(':memory:')
const attackerTask = db.createTask({ spec: 'attacker assignment' })
const attackerTask = db.createTask({ runId: 'run_legacy_local', spec: 'attacker assignment' })
const attacker = createRootDispatch(
db,
attackerTask.id,
'term_attacker',
'tab_attacker:leaf_attacker'
)
const victimTask = db.createTask({ spec: 'victim assignment' })
const victimTask = db.createTask({ runId: 'run_legacy_local', spec: 'victim assignment' })
const victim = createRootDispatch(db, victimTask.id, 'term_victim')
const logs: string[] = []
applyEscalationToDispatch(
db,
db.insertMessage({
runId: 'run_legacy_local',
from: 'term_attacker',
to: 'term_coordinator',
subject: 'Fail the victim',
@@ -43,12 +44,13 @@ describe('coordinator escalation authority', () => {
it('accepts the canonical sender of an imported federated Dispatch', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'remote escalation target' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'remote escalation target' })
const dispatch = createRootDispatch(db, task.id, 'remote-worker')
applyEscalationToDispatch(
db,
db.insertMessage({
runId: 'run_legacy_local',
from: `dispatch:${dispatch.id}`,
to: 'term_coordinator',
subject: 'Remote worker failed',
@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest'
import { parseAllowStaleBaseFromSpec } from './coordinator-stale-base-flag'
describe('parseAllowStaleBaseFromSpec', () => {
it('matches canonical form on its own line and strips it', () => {
const spec = `Do the work
allow-stale-base: true`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(true)
expect(strippedSpec).toBe('Do the work\n')
expect(strippedSpec).not.toContain('allow-stale-base')
})
it('matches case-insensitively', () => {
const spec = `Do the work
Allow-Stale-Base: TRUE`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(true)
expect(strippedSpec).not.toMatch(/[Aa]llow-[Ss]tale-[Bb]ase/)
})
it('does not match allow-stale-base: false', () => {
const spec = `Do the work
allow-stale-base: false`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(false)
expect(strippedSpec).toBe(spec)
})
it('does not match allow-stale-base: truthy', () => {
const spec = `Do the work
allow-stale-base: truthy`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(false)
expect(strippedSpec).toBe(spec)
})
it('does not match the flag embedded inside a sentence', () => {
const spec = 'we allow-stale-base: true sometimes'
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(false)
expect(strippedSpec).toBe(spec)
})
it('handles the flag as the last line with no trailing newline', () => {
const spec = 'line 1\nallow-stale-base: true'
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(true)
expect(strippedSpec).toBe('line 1\n')
expect(strippedSpec.endsWith('allow-stale-base: true')).toBe(false)
})
})
@@ -3,12 +3,11 @@ import { OrchestrationDb } from './db'
import { reconcileLifecycleMessage } from './lifecycle-reconciliation'
import { Coordinator } from './coordinator'
import type { CoordinatorRuntime } from './coordinator-runtime-contract'
import {
DISPATCH_STALE_THRESHOLD,
parseAllowStaleBaseFromSpec
} from './coordinator-stale-base-flag'
import { DISPATCH_STALE_THRESHOLD } from './coordinator-stale-base-flag'
import { createRootDispatch } from './db/root-dispatch-test-fixture'
const runId = 'run_legacy_local'
type DriftResult = {
base: string
behind: number
@@ -92,6 +91,7 @@ function insertWorkerDone(
}
const from = params.from ?? dispatch?.assignee_handle ?? 'term_unknown'
db.insertMessage({
runId,
from,
to: params.to ?? 'coord',
subject: 'Done',
@@ -131,7 +131,10 @@ describe('Coordinator', () => {
runtime.cliCommand = 'orca-ide'
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
const task = db.createTask({ spec: 'implement feature' })
const task = db.createTask({
runId,
spec: 'implement feature'
})
// Simulate worker_done arriving after dispatch
const coordinator = new Coordinator(db, runtime, {
@@ -166,7 +169,10 @@ describe('Coordinator', () => {
getTerminalPaneKey: (handle: string) => (handle === 'term_a' ? 'tab_a:leaf_a' : null)
})
const task = db.createTask({ spec: 'implement feature' })
const task = db.createTask({
runId,
spec: 'implement feature'
})
const coordinator = new Coordinator(db, withPaneLookup, {
spec: 'build it',
coordinatorHandle: 'coord',
@@ -198,7 +204,10 @@ describe('Coordinator', () => {
}
: null
})
const task = db.createTask({ spec: 'implement feature' })
const task = db.createTask({
runId,
spec: 'implement feature'
})
const coordinator = new Coordinator(db, withAuthority, {
spec: 'build it',
coordinatorHandle: 'coord',
@@ -223,9 +232,13 @@ describe('Coordinator', () => {
db = new OrchestrationDb(':memory:')
const runtime = createMockRuntime()
const task = db.createTask({ spec: 'send-driven completion' })
const task = db.createTask({
runId,
spec: 'send-driven completion'
})
const dispatch = createRootDispatch(db, task.id, 'term_a')
const msg = db.insertMessage({
runId,
from: 'term_a',
to: 'coord',
subject: 'Done',
@@ -250,7 +263,10 @@ describe('Coordinator', () => {
db = new OrchestrationDb(':memory:')
const runtime = createMockRuntime()
const task = db.createTask({ spec: 'duplicate completion' })
const task = db.createTask({
runId,
spec: 'duplicate completion'
})
const dispatch = createRootDispatch(db, task.id, 'term_a')
const payload = JSON.stringify({
taskId: task.id,
@@ -258,6 +274,7 @@ describe('Coordinator', () => {
outcome: 'succeeded'
})
const first = db.insertMessage({
runId,
from: 'term_a',
to: 'coord',
subject: 'Done',
@@ -265,6 +282,7 @@ describe('Coordinator', () => {
payload
})
db.insertMessage({
runId,
from: 'term_a',
to: 'coord',
subject: 'Done again',
@@ -289,7 +307,7 @@ describe('Coordinator', () => {
db = new OrchestrationDb(':memory:')
const runtime = createMockRuntime()
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId, spec: 'work' })
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -321,7 +339,10 @@ describe('Coordinator', () => {
{ handle: 'term_b', worktreeId: 'wt1', connected: true, writable: true }
]
const task = db.createTask({ spec: 'risky work' })
const task = db.createTask({
runId,
spec: 'risky work'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -339,6 +360,7 @@ describe('Coordinator', () => {
const dispatch = db.getDispatchContext(task.id)
expect(dispatch).toBeDefined()
db.insertMessage({
runId,
from: dispatch?.assignee_handle ?? 'missing-worker',
to: 'coord',
subject: `Failed attempt ${i + 1}`,
@@ -360,7 +382,10 @@ describe('Coordinator', () => {
throw new Error('terminal_not_writable')
}
const task = db.createTask({ spec: 'cannot dispatch' })
const task = db.createTask({
runId,
spec: 'cannot dispatch'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
coordinatorHandle: 'coord',
@@ -379,7 +404,10 @@ describe('Coordinator', () => {
const runtime = createMockRuntime()
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
const task = db.createTask({ spec: 'needs approval' })
const task = db.createTask({
runId,
spec: 'needs approval'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -398,6 +426,7 @@ describe('Coordinator', () => {
const dispatch = db.getDispatchContext(task.id)
expect(dispatch).toBeDefined()
db.insertMessage({
runId,
from: 'term_a',
to: 'coord',
subject: 'Need approval',
@@ -441,8 +470,12 @@ describe('Coordinator', () => {
const runtime = createMockRuntime()
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
const t1 = db.createTask({ spec: 'first' })
const t2 = db.createTask({ spec: 'second', deps: [t1.id] })
const t1 = db.createTask({ runId, spec: 'first' })
const t2 = db.createTask({
runId,
spec: 'second',
deps: [t1.id]
})
expect(t2.status).toBe('pending')
@@ -492,9 +525,9 @@ describe('Coordinator', () => {
{ handle: 'term_c', worktreeId: 'wt1', connected: true, writable: true }
]
const t1 = db.createTask({ spec: 'one' })
const t2 = db.createTask({ spec: 'two' })
const t3 = db.createTask({ spec: 'three' })
const t1 = db.createTask({ runId, spec: 'one' })
const t2 = db.createTask({ runId, spec: 'two' })
const t3 = db.createTask({ runId, spec: 'three' })
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -530,7 +563,7 @@ describe('Coordinator', () => {
const runtime = createMockRuntime()
// No terminals available so dispatchReadyTasks creates one and we can
// drive the stale-scan deterministically via SQL backdating.
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(db, task.id, 'term_stale')
// Backdate dispatched_at and last_heartbeat_at beyond the 10-min threshold
@@ -569,7 +602,7 @@ describe('Coordinator', () => {
const runtime = createMockRuntime()
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(db, task.id, 'term_a')
const coordinator = new Coordinator(db, runtime, {
@@ -581,6 +614,7 @@ describe('Coordinator', () => {
const runPromise = coordinator.run()
db.insertMessage({
runId,
from: 'term_a',
to: 'coord',
subject: 'alive',
@@ -606,12 +640,16 @@ describe('Coordinator', () => {
const runtime = createMockRuntime()
const logs: string[] = []
const task = db.createTask({ spec: 'retry-sensitive work' })
const task = db.createTask({
runId,
spec: 'retry-sensitive work'
})
const staleCtx = createRootDispatch(db, task.id, 'term_old')
db.failDispatch(staleCtx.id, 'retry elsewhere')
const activeCtx = createRootDispatch(db, task.id, 'term_current')
db.insertMessage({
runId,
from: 'term_old',
to: 'coord',
subject: 'Late done',
@@ -663,11 +701,15 @@ describe('Coordinator', () => {
const runtime = createMockRuntime()
const logs: string[] = []
const task = db.createTask({ spec: 'owned work' })
const task = db.createTask({
runId,
spec: 'owned work'
})
const leafId = '11111111-1111-4111-8111-111111111111'
const ctx = createRootDispatch(db, task.id, 'term_owner', `tab_before:${leafId}`)
db.insertMessage({
runId,
from: 'term_reminted',
to: 'coord',
subject: 'Done after restart',
@@ -693,7 +735,7 @@ describe('Coordinator', () => {
it('can be stopped', async () => {
db = new OrchestrationDb(':memory:')
const runtime = createMockRuntime()
db.createTask({ spec: 'never finishes' })
db.createTask({ runId, spec: 'never finishes' })
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -723,7 +765,10 @@ describe('Coordinator', () => {
recentSubjects: ['fix A', 'fix B', 'fix C']
})
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({
runId,
spec: 'do the work'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -759,7 +804,10 @@ describe('Coordinator', () => {
recentSubjects: ['fix A']
})
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({
runId,
spec: 'do the work'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -799,7 +847,7 @@ describe('Coordinator', () => {
const spec = `Investigate issue #42
allow-stale-base: true`
const task = db.createTask({ spec })
const task = db.createTask({ runId, spec })
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -832,7 +880,10 @@ allow-stale-base: true`
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
runtime.setProbeDrift(null)
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({
runId,
spec: 'do the work'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -861,7 +912,10 @@ allow-stale-base: true`
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
const logs: string[] = []
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({
runId,
spec: 'do the work'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -892,7 +946,10 @@ allow-stale-base: true`
runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }]
runtime.throwProbeDrift = new Error('boom')
const task = db.createTask({ spec: 'do the work' })
const task = db.createTask({
runId,
spec: 'do the work'
})
const coordinator = new Coordinator(db, runtime, {
spec: 'go',
@@ -915,53 +972,3 @@ allow-stale-base: true`
})
})
})
describe('parseAllowStaleBaseFromSpec', () => {
it('matches canonical form on its own line and strips it', () => {
const spec = `Do the work
allow-stale-base: true`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(true)
expect(strippedSpec).toBe('Do the work\n')
expect(strippedSpec).not.toContain('allow-stale-base')
})
it('matches case-insensitively', () => {
const spec = `Do the work
Allow-Stale-Base: TRUE`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(true)
expect(strippedSpec).not.toMatch(/[Aa]llow-[Ss]tale-[Bb]ase/)
})
it('does not match allow-stale-base: false', () => {
const spec = `Do the work
allow-stale-base: false`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(false)
expect(strippedSpec).toBe(spec)
})
it('does not match allow-stale-base: truthy', () => {
const spec = `Do the work
allow-stale-base: truthy`
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(false)
expect(strippedSpec).toBe(spec)
})
it('does not match the flag embedded inside a sentence', () => {
const spec = 'we allow-stale-base: true sometimes'
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(false)
expect(strippedSpec).toBe(spec)
})
it('handles the flag as the last line with no trailing newline', () => {
const spec = 'line 1\nallow-stale-base: true'
const { allowStale, strippedSpec } = parseAllowStaleBaseFromSpec(spec)
expect(allowStale).toBe(true)
expect(strippedSpec).toBe('line 1\n')
expect(strippedSpec.endsWith('allow-stale-base: true')).toBe(false)
})
})
@@ -64,7 +64,7 @@ describe('orchestration empty-dispatch short-circuit (benchmark)', () => {
it('still runs the fan-out once a dispatch exists (correctness preserved)', () => {
const db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
createRootDispatch(db, task.id, 'term_5')
const handles = Array.from({ length: 10 }, (_, i) => `term_${i}`)
@@ -76,7 +76,11 @@ describe('orchestration empty-dispatch short-circuit (benchmark)', () => {
it('predicate lifecycle: false when empty, true after dispatch (even completed), false after reset', () => {
const db = new OrchestrationDb(':memory:')
expect(db.hasAnyDispatchContexts()).toBe(false)
const ctx = createRootDispatch(db, db.createTask({ spec: 'work' }).id, 'term_worker')
const ctx = createRootDispatch(
db,
db.createTask({ runId: 'run_legacy_local', spec: 'work' }).id,
'term_worker'
)
expect(db.hasAnyDispatchContexts()).toBe(true)
// Completed rows still count — recent-completed lookups must stay valid.
db.completeDispatch(ctx.id)
@@ -13,7 +13,7 @@ afterEach(() => {
function seedHeartbeatedDispatch(): { d: OrchestrationDb; dispatchId: string } {
const d = new OrchestrationDb(':memory:')
db = d
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(d, task.id, 'term_worker')
d.recordHeartbeat(dispatch.id, '2026-05-03T00:00:00.000Z')
return { d, dispatchId: dispatch.id }
@@ -8,7 +8,12 @@ describe('orchestration message timestamps', () => {
it('exposes SQLite timestamps with an explicit UTC designator', () => {
db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'b', subject: 'timestamped' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'b',
subject: 'timestamped'
})
expect(message.created_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/)
db.markAsDelivered([message.id])
@@ -0,0 +1,194 @@
import { afterEach, describe, expect, it } from 'vitest'
import type Database from '../../sqlite/sync-database'
import { OrchestrationDb, type MessageType } from './db'
const runId = 'run_legacy_local'
describe('OrchestrationDb', () => {
let db: OrchestrationDb | undefined
afterEach(() => {
db?.close()
})
function createDb(): OrchestrationDb {
db = new OrchestrationDb(':memory:')
return db
}
describe('messages', () => {
it('inserts and retrieves a message', () => {
const d = createDb()
const msg = d.insertMessage({
runId,
from: 'term_a',
to: 'term_b',
subject: 'hello',
body: 'world'
})
expect(msg.id).toMatch(/^msg_/)
expect(msg.from_handle).toBe('term_a')
expect(msg.to_handle).toBe('term_b')
expect(msg.subject).toBe('hello')
expect(msg.body).toBe('world')
expect(msg.type).toBe('status')
expect(msg.priority).toBe('normal')
expect(msg.read).toBe(0)
expect(msg.sequence).toBeGreaterThan(0)
})
it('returns unread messages in sequence order', () => {
const d = createDb()
d.insertMessage({ runId, from: 'a', to: 'b', subject: 'first' })
d.insertMessage({ runId, from: 'a', to: 'b', subject: 'second' })
d.insertMessage({ runId, from: 'a', to: 'c', subject: 'other' })
const unread = d.getUnreadMessages('b')
expect(unread).toHaveLength(2)
expect(unread[0].subject).toBe('first')
expect(unread[1].subject).toBe('second')
})
it('filters unread by type', () => {
const d = createDb()
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'status msg',
type: 'status'
})
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'done msg',
type: 'worker_done'
})
const filtered = d.getUnreadMessages('b', ['worker_done'])
expect(filtered).toHaveLength(1)
expect(filtered[0].type).toBe('worker_done')
})
it('excludes already-delivered rows from getUndeliveredUnreadMessages', () => {
const d = createDb()
const m1 = d.insertMessage({ runId, from: 'a', to: 'b', subject: 'one' })
const m2 = d.insertMessage({ runId, from: 'a', to: 'b', subject: 'two' })
d.markAsDelivered([m1.id])
// Push delivery query: only undelivered, unread.
const pending = d.getUndeliveredUnreadMessages('b')
expect(pending).toHaveLength(1)
expect(pending[0].id).toBe(m2.id)
// Explicit `check` still sees both (they are still unread).
const unread = d.getUnreadMessages('b')
expect(unread).toHaveLength(2)
})
it('creates the undelivered inbox index used by push delivery', () => {
const d = createDb()
const sqlite = (d as unknown as { db: Database.Database }).db
const indexes = sqlite
.prepare(
`SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'messages' AND name = 'idx_messages_undelivered_inbox'`
)
.all()
expect(indexes).toHaveLength(1)
})
it('filters getUndeliveredUnreadMessages by type', () => {
const d = createDb()
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 's',
type: 'status'
})
const wd = d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'd',
type: 'worker_done'
})
const filtered = d.getUndeliveredUnreadMessages('b', ['worker_done'])
expect(filtered).toHaveLength(1)
expect(filtered[0].id).toBe(wd.id)
})
it('marks messages as read', () => {
const d = createDb()
const m1 = d.insertMessage({ runId, from: 'a', to: 'b', subject: 'one' })
const m2 = d.insertMessage({ runId, from: 'a', to: 'b', subject: 'two' })
d.markAsRead([m1.id])
const unread = d.getUnreadMessages('b')
expect(unread).toHaveLength(1)
expect(unread[0].id).toBe(m2.id)
})
it('stores typed payload and thread_id', () => {
const d = createDb()
const payload = JSON.stringify({ taskId: 'task_abc', filesModified: ['src/a.ts'] })
const msg = d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'done',
type: 'worker_done',
priority: 'high',
threadId: 'thread_1',
payload
})
expect(msg.type).toBe('worker_done')
expect(msg.priority).toBe('high')
expect(msg.thread_id).toBe('thread_1')
expect(msg.payload).toBe(payload)
})
it('rejects invalid message type', () => {
const d = createDb()
expect(() =>
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'bad',
type: 'invalid' as MessageType
})
).toThrow()
})
it('getInbox returns all messages across recipients', () => {
const d = createDb()
d.insertMessage({ runId, from: 'a', to: 'b', subject: 'one' })
d.insertMessage({ runId, from: 'a', to: 'c', subject: 'two' })
d.insertMessage({ runId, from: 'b', to: 'a', subject: 'three' })
const inbox = d.getInbox(10)
expect(inbox).toHaveLength(3)
})
it('getMessageById returns the correct message', () => {
const d = createDb()
const msg = d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'test'
})
const found = d.getMessageById(msg.id)
expect(found?.subject).toBe('test')
expect(d.getMessageById('msg_nonexistent')).toBeUndefined()
})
})
})
@@ -33,12 +33,16 @@ describe('task creation dependency readiness', () => {
it('creates a late dependent as ready when every dependency is completed', () => {
const db = createDb()
const first = db.createTask({ spec: 'first' })
const second = db.createTask({ spec: 'second' })
const first = db.createTask({ runId: 'run_legacy_local', spec: 'first' })
const second = db.createTask({ runId: 'run_legacy_local', spec: 'second' })
db.updateTaskStatus(first.id, 'completed')
db.updateTaskStatus(second.id, 'completed')
const child = db.createTask({ spec: 'child', deps: [first.id, second.id] })
const child = db.createTask({
runId: 'run_legacy_local',
spec: 'child',
deps: [first.id, second.id]
})
expect(child.status).toBe('ready')
})
@@ -49,7 +53,7 @@ describe('task creation dependency readiness', () => {
const path = join(directory, 'orchestration.db')
const db = createDb(path)
const concurrent = createDb(path)
const dependency = db.createTask({ spec: 'dependency' })
const dependency = db.createTask({ runId: 'run_legacy_local', spec: 'dependency' })
const sqlite = (db as unknown as OrchestrationDbAccess).db
const prepare = sqlite.prepare.bind(sqlite)
let injected = false
@@ -61,7 +65,7 @@ describe('task creation dependency readiness', () => {
return prepare(sql)
})
const child = db.createTask({ spec: 'child', deps: [dependency.id] })
const child = db.createTask({ runId: 'run_legacy_local', spec: 'child', deps: [dependency.id] })
expect(injected).toBe(true)
expect(child.status).toBe('ready')
@@ -69,10 +73,14 @@ describe('task creation dependency readiness', () => {
it('promotes only after every dependency completes', () => {
const db = createDb()
const first = db.createTask({ spec: 'first' })
const second = db.createTask({ spec: 'second' })
const first = db.createTask({ runId: 'run_legacy_local', spec: 'first' })
const second = db.createTask({ runId: 'run_legacy_local', spec: 'second' })
db.updateTaskStatus(first.id, 'completed')
const child = db.createTask({ spec: 'child', deps: [first.id, second.id] })
const child = db.createTask({
runId: 'run_legacy_local',
spec: 'child',
deps: [first.id, second.id]
})
expect(child.status).toBe('pending')
db.updateTaskStatus(second.id, 'completed')
@@ -83,11 +91,15 @@ describe('task creation dependency readiness', () => {
'does not unlock a dependent whose dependency is %s',
(status) => {
const db = createDb()
const terminal = db.createTask({ spec: 'terminal dependency' })
const completing = db.createTask({ spec: 'completing dependency' })
const terminal = db.createTask({ runId: 'run_legacy_local', spec: 'terminal dependency' })
const completing = db.createTask({ runId: 'run_legacy_local', spec: 'completing dependency' })
db.updateTaskStatus(terminal.id, status)
const child = db.createTask({ spec: 'child', deps: [terminal.id, completing.id] })
const child = db.createTask({
runId: 'run_legacy_local',
spec: 'child',
deps: [terminal.id, completing.id]
})
expect(child.status).toBe('pending')
db.updateTaskStatus(completing.id, 'completed')
@@ -98,9 +110,9 @@ describe('task creation dependency readiness', () => {
it('rejects missing dependencies without inserting a task', () => {
const db = createDb()
expect(() => db.createTask({ spec: 'child', deps: ['task_missing'] })).toThrow(
'Dependency task task_missing must belong to run'
)
expect(() =>
db.createTask({ runId: 'run_legacy_local', spec: 'child', deps: ['task_missing'] })
).toThrow('Dependency task task_missing must belong to run')
expect(db.listTasks()).toEqual([])
})
@@ -109,7 +121,7 @@ describe('task creation dependency readiness', () => {
const sqlite = (db as unknown as OrchestrationDbAccess).db
sqlite.exec('BEGIN IMMEDIATE')
const task = db.createTask({ spec: 'transactional child' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'transactional child' })
sqlite.exec('ROLLBACK')
expect(db.getTask(task.id)).toBeUndefined()
@@ -120,11 +132,19 @@ describe('task creation dependency readiness', () => {
directories.push(directory)
const path = join(directory, 'orchestration.db')
const before = createDb(path)
const completed = before.createTask({ spec: 'completed' })
const open = before.createTask({ spec: 'open' })
const completed = before.createTask({ runId: 'run_legacy_local', spec: 'completed' })
const open = before.createTask({ runId: 'run_legacy_local', spec: 'open' })
before.updateTaskStatus(completed.id, 'completed')
const ready = before.createTask({ spec: 'ready', deps: [completed.id] })
const pending = before.createTask({ spec: 'pending', deps: [completed.id, open.id] })
const ready = before.createTask({
runId: 'run_legacy_local',
spec: 'ready',
deps: [completed.id]
})
const pending = before.createTask({
runId: 'run_legacy_local',
spec: 'pending',
deps: [completed.id, open.id]
})
before.close()
databases.splice(databases.indexOf(before), 1)
@@ -30,9 +30,17 @@ describe('Task/Dispatch invariant transactions', () => {
'allows a dependency-blocked pending Task to become %s',
(status) => {
const { db } = createDatabase()
const dependency = db.createTask({ spec: 'unresolved dependency' })
const task = db.createTask({ spec: 'manual resolution', deps: [dependency.id] })
const dependent = db.createTask({ spec: 'downstream work', deps: [task.id] })
const dependency = db.createTask({ runId: 'run_legacy_local', spec: 'unresolved dependency' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'manual resolution',
deps: [dependency.id]
})
const dependent = db.createTask({
runId: 'run_legacy_local',
spec: 'downstream work',
deps: [task.id]
})
expect(task.status).toBe('pending')
const updated = db.updateTaskStatus(task.id, status, 'manual resolution')
@@ -45,7 +53,7 @@ describe('Task/Dispatch invariant transactions', () => {
it('surfaces invalid Task lifecycle edges instead of returning the unchanged row', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'invalid lifecycle edge' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'invalid lifecycle edge' })
db.updateTaskStatus(task.id, 'blocked')
expect(() =>
@@ -67,8 +75,12 @@ describe('Task/Dispatch invariant transactions', () => {
'rolls back a %s Task when Dispatch settlement fails',
(status) => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'atomic work' })
const dependent = db.createTask({ spec: 'dependent work', deps: [task.id] })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'atomic work' })
const dependent = db.createTask({
runId: 'run_legacy_local',
spec: 'dependent work',
deps: [task.id]
})
const dispatch = createRootDispatch(db, task.id, 'term_worker')
const capability = db.mintDispatchCapability({
dispatchId: dispatch.id,
@@ -111,7 +123,10 @@ describe('Task/Dispatch invariant transactions', () => {
it('does not commit a caller-owned transaction', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'outer transaction work' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'outer transaction work'
})
const dispatch = createRootDispatch(db, task.id, 'term_worker')
const sqlite = sqliteFor(db)
@@ -135,7 +150,10 @@ describe('Task/Dispatch invariant transactions', () => {
it('keeps Dispatch creation inside a caller-owned transaction', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'outer transaction dispatch' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'outer transaction dispatch'
})
const sqlite = sqliteFor(db)
sqlite.exec('BEGIN IMMEDIATE')
@@ -152,7 +170,10 @@ describe('Task/Dispatch invariant transactions', () => {
'settles every active Dispatch left by a pre-fix split when the Task becomes %s',
(status) => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'legacy split work' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'legacy split work'
})
const first = createRootDispatch(db, task.id, 'term_first')
sqliteFor(db).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const second = createRootDispatch(db, task.id, 'term_second')
@@ -169,17 +190,31 @@ describe('Task/Dispatch invariant transactions', () => {
expect(db.getActiveDispatchForTerminal('term_first')).toBeUndefined()
expect(db.getActiveDispatchForTerminal('term_second')).toBeUndefined()
expect(() =>
createRootDispatch(db, db.createTask({ spec: 'first later work' }).id, 'term_first')
createRootDispatch(
db,
db.createTask({ runId: 'run_legacy_local', spec: 'first later work' }).id,
'term_first'
)
).not.toThrow()
expect(() =>
createRootDispatch(db, db.createTask({ spec: 'second later work' }).id, 'term_second')
createRootDispatch(
db,
db.createTask({
runId: 'run_legacy_local',
spec: 'second later work'
}).id,
'term_second'
)
).not.toThrow()
}
)
it('does not requeue a legacy split Task while another Dispatch remains active', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'legacy split retry' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'legacy split retry'
})
const first = createRootDispatch(db, task.id, 'term_first')
sqliteFor(db).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const second = createRootDispatch(db, task.id, 'term_second')
@@ -193,7 +228,10 @@ describe('Task/Dispatch invariant transactions', () => {
it('does not block a legacy split Task while another Dispatch remains active', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'legacy split release' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'legacy split release'
})
const first = createRootDispatch(db, task.id, 'term_first')
sqliteFor(db).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const second = createRootDispatch(db, task.id, 'term_second')
@@ -211,7 +249,10 @@ describe('Task/Dispatch invariant transactions', () => {
'rejects moving a Task to %s while a Dispatch remains active',
(status) => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'guarded work' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'guarded work'
})
const dispatch = createRootDispatch(db, task.id, 'term_worker')
expect(() => db.updateTaskStatus(task.id, status, 'must not persist')).toThrowError(
@@ -227,7 +268,10 @@ describe('Task/Dispatch invariant transactions', () => {
it('rejects moving a Task to dispatched without an active Dispatch', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'unassigned work' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'unassigned work'
})
expect(() => db.updateTaskStatus(task.id, 'dispatched')).toThrowError(
expect.objectContaining({
@@ -241,7 +285,10 @@ describe('Task/Dispatch invariant transactions', () => {
it('rejects a Dispatch when failure wins after readiness was observed', () => {
const first = createDatabase()
const concurrent = createDatabase(first.path)
const task = first.db.createTask({ spec: 'interleaved work' })
const task = first.db.createTask({
runId: 'run_legacy_local',
spec: 'interleaved work'
})
const sqlite = sqliteFor(first.db)
const prepare = sqlite.prepare.bind(sqlite)
let injected = false
@@ -264,8 +311,14 @@ describe('Task/Dispatch invariant transactions', () => {
it('atomically rejects a same-pane Dispatch that loses the occupancy race', () => {
const first = createDatabase()
const concurrent = createDatabase(first.path)
const firstTask = first.db.createTask({ spec: 'first terminal claimant' })
const secondTask = first.db.createTask({ spec: 'second terminal claimant' })
const firstTask = first.db.createTask({
runId: 'run_legacy_local',
spec: 'first terminal claimant'
})
const secondTask = first.db.createTask({
runId: 'run_legacy_local',
spec: 'second terminal claimant'
})
const sqlite = sqliteFor(first.db)
const prepare = sqlite.prepare.bind(sqlite)
let winnerId: string | undefined
@@ -303,14 +356,20 @@ describe('Task/Dispatch invariant transactions', () => {
it('rejects worker authority when another Dispatch owns the pane', () => {
const { db } = createDatabase()
const ownerTask = db.createTask({ spec: 'current pane owner' })
const ownerTask = db.createTask({
runId: 'run_legacy_local',
spec: 'current pane owner'
})
const owner = createRootDispatch(
db,
ownerTask.id,
'term_owner',
'tab_old:cccccccc-cccc-4ccc-8ccc-cccccccccccc'
)
const workerTask = db.createTask({ spec: 'competing supervised worker' })
const workerTask = db.createTask({
runId: 'run_legacy_local',
spec: 'competing supervised worker'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -346,7 +405,10 @@ describe('Task/Dispatch invariant transactions', () => {
'rejects a %s Task update while its supervised worker remains active',
(status) => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'supervised lifecycle' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'supervised lifecycle'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -394,7 +456,10 @@ describe('Task/Dispatch invariant transactions', () => {
it('keeps a federated late start authoritative after rejecting Task failure', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'federated lifecycle' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'federated lifecycle'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -29,7 +29,7 @@ afterEach(() => {
describe('Task/Dispatch lifecycle guards', () => {
it('rejects a worker report while another supervised Dispatch is active', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'legacy supervised split' })
const task = database.createTask({ runId: 'run_legacy_local', spec: 'legacy supervised split' })
const first = startWorker(database, task.id, 'first')
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const second = startWorker(database, task.id, 'second')
@@ -55,7 +55,7 @@ describe('Task/Dispatch lifecycle guards', () => {
'settles context-only legacy siblings after a %s worker report',
(outcome) => {
const database = createDatabase()
const task = database.createTask({ spec: 'legacy mixed split' })
const task = database.createTask({ runId: 'run_legacy_local', spec: 'legacy mixed split' })
const contextOnly = createRootDispatch(database, task.id, 'term_context')
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const worker = startWorker(database, task.id, 'reporter')
@@ -78,7 +78,10 @@ describe('Task/Dispatch lifecycle guards', () => {
expect(() =>
createRootDispatch(
database,
database.createTask({ spec: 'later context work' }).id,
database.createTask({
runId: 'run_legacy_local',
spec: 'later context work'
}).id,
'term_context'
)
).not.toThrow()
@@ -87,7 +90,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('settles a newer context-only legacy sibling after a worker report', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'reversed legacy mixed split' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'reversed legacy mixed split'
})
const worker = startWorker(database, task.id, 'reversed_reporter')
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const contextOnly = createRootDispatch(database, task.id, 'term_reversed_context')
@@ -112,7 +118,10 @@ describe('Task/Dispatch lifecycle guards', () => {
'treats abandon of an already %s worker as stale without a lifecycle conflict',
(state) => {
const database = createDatabase()
const task = database.createTask({ spec: `already ${state}` })
const task = database.createTask({
runId: 'run_legacy_local',
spec: `already ${state}`
})
const worker = startWorker(database, task.id, `already_${state}`)
if (state === 'failed') {
database.failDispatch(worker.dispatchId, 'process exited', { workerProcessExited: true })
@@ -130,7 +139,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('rejects generic failure while a supervised worker remains active', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'supervised failure guard' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'supervised failure guard'
})
const worker = startWorker(database, task.id, 'guarded')
expect(() => database.failDispatch(worker.dispatchId, 'unsafe retry')).toThrowError(
@@ -151,7 +163,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('atomically settles worker state when a proven process exit fails its Dispatch', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'exited worker' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'exited worker'
})
const worker = startWorker(database, task.id, 'exited')
expect(
@@ -168,7 +183,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('settles a stop-unknown worker when a positive PTY exit arrives', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'stop-unknown exited worker' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'stop-unknown exited worker'
})
const worker = startWorker(database, task.id, 'stop_unknown_exited')
expect(database.beginWorkerStop(worker.dispatchId, 'runtime_test').disposition).toBe('stopping')
@@ -198,7 +216,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('keeps a Task dispatched when missing-terminal recovery leaves another worker active', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'legacy missing-terminal split' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'legacy missing-terminal split'
})
const missing = startWorker(database, task.id, 'missing')
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const live = startWorker(database, task.id, 'live')
@@ -225,7 +246,10 @@ describe('Task/Dispatch lifecycle guards', () => {
'keeps a Task dispatched when a %s worker start fails beside a live worker',
(kind) => {
const database = createDatabase()
const task = database.createTask({ spec: `${kind} split start failure` })
const task = database.createTask({
runId: 'run_legacy_local',
spec: `${kind} split start failure`
})
const failed = database.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -342,7 +366,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('rolls back federated start uncertainty when the Task transition cannot commit', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'atomic federated uncertainty' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'atomic federated uncertainty'
})
const started = database.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -383,7 +410,10 @@ describe('Task/Dispatch lifecycle guards', () => {
'%s releases the last context-only sibling after a newer worker start fails',
(operation) => {
const database = createDatabase()
const task = database.createTask({ spec: `${operation} historical sibling` })
const task = database.createTask({
runId: 'run_legacy_local',
spec: `${operation} historical sibling`
})
const contextOnly = createRootDispatch(database, task.id, `term_${operation}`)
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const failed = database.createStartingWorkerDispatch({
@@ -411,7 +441,10 @@ describe('Task/Dispatch lifecycle guards', () => {
expect(() =>
createRootDispatch(
database,
database.createTask({ spec: `${operation} later work` }).id,
database.createTask({
runId: 'run_legacy_local',
spec: `${operation} later work`
}).id,
`term_${operation}`
)
).not.toThrow()
@@ -422,7 +455,10 @@ describe('Task/Dispatch lifecycle guards', () => {
'%s records guarded receipts for context-only Dispatch and Task release',
(operation) => {
const database = createDatabase()
const task = database.createTask({ spec: `${operation} receipt release` })
const task = database.createTask({
runId: 'run_legacy_local',
spec: `${operation} receipt release`
})
const contextOnly = createRootDispatch(database, task.id, `term_${operation}`)
const released =
@@ -445,7 +481,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('rolls back both context-only projections when the Task transition fails', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'context-only atomic receipt' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'context-only atomic receipt'
})
const contextOnly = createRootDispatch(database, task.id, 'term_context')
sqliteFor(database).exec(`
CREATE TRIGGER reject_context_release_task_block
@@ -470,7 +509,10 @@ describe('Task/Dispatch lifecycle guards', () => {
'%s preserves a live worker sibling and lets it report',
(operation) => {
const database = createDatabase()
const task = database.createTask({ spec: `${operation} legacy worker split` })
const task = database.createTask({
runId: 'run_legacy_local',
spec: `${operation} legacy worker split`
})
const live = startWorker(database, task.id, `${operation}_live`)
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const released = startWorker(database, task.id, `${operation}_released`)
@@ -502,7 +544,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('blocks a Task when an interleaved stop settles its final active Dispatch', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'interleaved legacy worker release' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'interleaved legacy worker release'
})
const stopping = startWorker(database, task.id, 'interleaved_stopping')
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const abandoned = startWorker(database, task.id, 'interleaved_abandoned')
@@ -521,7 +566,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('restores a live sibling after stopping an uncertain worker start', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'uncertain legacy worker split' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'uncertain legacy worker split'
})
const live = startWorker(database, task.id, 'uncertain_live')
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const uncertain = database.createStartingWorkerDispatch({
@@ -552,7 +600,10 @@ describe('Task/Dispatch lifecycle guards', () => {
'restores a live sibling after an uncertain worker start fails through %s',
(recovery) => {
const database = createDatabase()
const task = database.createTask({ spec: `${recovery} uncertain sibling` })
const task = database.createTask({
runId: 'run_legacy_local',
spec: `${recovery} uncertain sibling`
})
const live = startWorker(database, task.id, `${recovery}_live`)
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const uncertain = database.createStartingWorkerDispatch({
@@ -589,7 +640,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('rejects gate creation while a supervised worker remains active', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'worker gate guard' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'worker gate guard'
})
const worker = startWorker(database, task.id, 'gate')
expect(() => database.createGate({ taskId: task.id, question: 'Proceed?' })).toThrowError(
@@ -607,7 +661,10 @@ describe('Task/Dispatch lifecycle guards', () => {
it('rolls back gate resolution when an active Dispatch blocks readiness', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'corrupt gated task' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'corrupt gated task'
})
const gate = database.createGate({ taskId: task.id, question: 'Proceed?' })
sqliteFor(database).prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(task.id)
const dispatch = createRootDispatch(database, task.id, 'term_worker')
@@ -29,7 +29,10 @@ describe('Task/Dispatch concurrency', () => {
it('reads a concurrent Task result before applying an explicit status correction', () => {
const first = createDatabase()
const concurrent = createDatabase(first.path)
const task = first.db.createTask({ spec: 'concurrent status winner' })
const task = first.db.createTask({
runId: 'run_legacy_local',
spec: 'concurrent status winner'
})
const sqlite = sqliteFor(first.db)
const exec = sqlite.exec.bind(sqlite)
let concurrentWon = false
@@ -57,7 +60,7 @@ describe('Task/Dispatch concurrency', () => {
it('holds the Task status writer reservation through its lifecycle reads', () => {
const first = createDatabase()
const concurrent = createDatabase(first.path)
const task = first.db.createTask({ spec: 'reserved status winner' })
const task = first.db.createTask({ runId: 'run_legacy_local', spec: 'reserved status winner' })
const sqlite = sqliteFor(first.db)
const exec = sqlite.exec.bind(sqlite)
sqliteFor(concurrent.db).pragma('busy_timeout = 0')
@@ -86,7 +89,7 @@ describe('Task/Dispatch concurrency', () => {
it('rolls back Dispatch failure when Task requeue fails', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'atomic retry failure' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'atomic retry failure' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
sqliteFor(db).exec(`
CREATE TRIGGER reject_task_requeue
@@ -113,7 +116,10 @@ describe('Task/Dispatch concurrency', () => {
it('does not let stale failure overwrite a completed worker report', () => {
const first = createDatabase()
const concurrent = createDatabase(first.path)
const task = first.db.createTask({ spec: 'worker completion wins' })
const task = first.db.createTask({
runId: 'run_legacy_local',
spec: 'worker completion wins'
})
const started = first.db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -177,7 +183,10 @@ describe('Task/Dispatch concurrency', () => {
it('keeps nested dispatch failure atomic with its caller transaction', () => {
const { db } = createDatabase()
const task = db.createTask({ spec: 'nested atomic failure' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'nested atomic failure'
})
const dispatch = createRootDispatch(db, task.id, 'term_worker')
const sqlite = sqliteFor(db)
@@ -198,8 +207,14 @@ describe('Task/Dispatch concurrency', () => {
it('serializes reminted-pane worker authority claims', () => {
const first = createDatabase()
const concurrent = createDatabase(first.path)
const losingTask = first.db.createTask({ spec: 'losing worker' })
const winningTask = first.db.createTask({ spec: 'winning worker' })
const losingTask = first.db.createTask({
runId: 'run_legacy_local',
spec: 'losing worker'
})
const winningTask = first.db.createTask({
runId: 'run_legacy_local',
spec: 'winning worker'
})
const loser = first.db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -8,10 +8,20 @@ describe('undelivered orchestration mailboxes', () => {
it('lists only mailboxes with undelivered unread messages', () => {
db = new OrchestrationDb(':memory:')
const delivered = db.insertMessage({ from: 'a', to: 'delivered', subject: 'done' })
const read = db.insertMessage({ from: 'a', to: 'read', subject: 'seen' })
db.insertMessage({ from: 'a', to: 'pending', subject: 'first' })
db.insertMessage({ from: 'a', to: 'pending', subject: 'second' })
const delivered = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'delivered',
subject: 'done'
})
const read = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'read',
subject: 'seen'
})
db.insertMessage({ runId: 'run_legacy_local', from: 'a', to: 'pending', subject: 'first' })
db.insertMessage({ runId: 'run_legacy_local', from: 'a', to: 'pending', subject: 'second' })
db.markAsDelivered([delivered.id])
db.markAsRead([read.id])
@@ -20,7 +30,12 @@ describe('undelivered orchestration mailboxes', () => {
it('persists and settles a pending pointer Enter independently of delivery', () => {
db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'run:run_1', subject: 'staged' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run_1',
subject: 'staged'
})
expect(
db.stageMailboxPointerEnter([message.id], {
+110 -210
View File
@@ -4,9 +4,10 @@ import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import Database from '../../sqlite/sync-database'
import { LEGACY_RUN_ID, OrchestrationDb } from './db'
import type { MessageType } from './db'
import { createRootDispatch } from './db/root-dispatch-test-fixture'
const runId = 'run_legacy_local'
// Overwrites the datetime('now')-seeded timestamps with explicit fixture values
// so stale-detection assertions stay deterministic (no wall clock).
function setDispatchTimes(
@@ -33,154 +34,10 @@ describe('OrchestrationDb', () => {
return db
}
describe('messages', () => {
it('inserts and retrieves a message', () => {
const d = createDb()
const msg = d.insertMessage({
from: 'term_a',
to: 'term_b',
subject: 'hello',
body: 'world'
})
expect(msg.id).toMatch(/^msg_/)
expect(msg.from_handle).toBe('term_a')
expect(msg.to_handle).toBe('term_b')
expect(msg.subject).toBe('hello')
expect(msg.body).toBe('world')
expect(msg.type).toBe('status')
expect(msg.priority).toBe('normal')
expect(msg.read).toBe(0)
expect(msg.sequence).toBeGreaterThan(0)
})
it('returns unread messages in sequence order', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 'first' })
d.insertMessage({ from: 'a', to: 'b', subject: 'second' })
d.insertMessage({ from: 'a', to: 'c', subject: 'other' })
const unread = d.getUnreadMessages('b')
expect(unread).toHaveLength(2)
expect(unread[0].subject).toBe('first')
expect(unread[1].subject).toBe('second')
})
it('filters unread by type', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 'status msg', type: 'status' })
d.insertMessage({ from: 'a', to: 'b', subject: 'done msg', type: 'worker_done' })
const filtered = d.getUnreadMessages('b', ['worker_done'])
expect(filtered).toHaveLength(1)
expect(filtered[0].type).toBe('worker_done')
})
it('excludes already-delivered rows from getUndeliveredUnreadMessages', () => {
const d = createDb()
const m1 = d.insertMessage({ from: 'a', to: 'b', subject: 'one' })
const m2 = d.insertMessage({ from: 'a', to: 'b', subject: 'two' })
d.markAsDelivered([m1.id])
// Push delivery query: only undelivered, unread.
const pending = d.getUndeliveredUnreadMessages('b')
expect(pending).toHaveLength(1)
expect(pending[0].id).toBe(m2.id)
// Explicit `check` still sees both (they are still unread).
const unread = d.getUnreadMessages('b')
expect(unread).toHaveLength(2)
})
it('creates the undelivered inbox index used by push delivery', () => {
const d = createDb()
const sqlite = (d as unknown as { db: Database.Database }).db
const indexes = sqlite
.prepare(
`SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'messages' AND name = 'idx_messages_undelivered_inbox'`
)
.all()
expect(indexes).toHaveLength(1)
})
it('filters getUndeliveredUnreadMessages by type', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 's', type: 'status' })
const wd = d.insertMessage({ from: 'a', to: 'b', subject: 'd', type: 'worker_done' })
const filtered = d.getUndeliveredUnreadMessages('b', ['worker_done'])
expect(filtered).toHaveLength(1)
expect(filtered[0].id).toBe(wd.id)
})
it('marks messages as read', () => {
const d = createDb()
const m1 = d.insertMessage({ from: 'a', to: 'b', subject: 'one' })
const m2 = d.insertMessage({ from: 'a', to: 'b', subject: 'two' })
d.markAsRead([m1.id])
const unread = d.getUnreadMessages('b')
expect(unread).toHaveLength(1)
expect(unread[0].id).toBe(m2.id)
})
it('stores typed payload and thread_id', () => {
const d = createDb()
const payload = JSON.stringify({ taskId: 'task_abc', filesModified: ['src/a.ts'] })
const msg = d.insertMessage({
from: 'a',
to: 'b',
subject: 'done',
type: 'worker_done',
priority: 'high',
threadId: 'thread_1',
payload
})
expect(msg.type).toBe('worker_done')
expect(msg.priority).toBe('high')
expect(msg.thread_id).toBe('thread_1')
expect(msg.payload).toBe(payload)
})
it('rejects invalid message type', () => {
const d = createDb()
expect(() =>
d.insertMessage({
from: 'a',
to: 'b',
subject: 'bad',
type: 'invalid' as MessageType
})
).toThrow()
})
it('getInbox returns all messages across recipients', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 'one' })
d.insertMessage({ from: 'a', to: 'c', subject: 'two' })
d.insertMessage({ from: 'b', to: 'a', subject: 'three' })
const inbox = d.getInbox(10)
expect(inbox).toHaveLength(3)
})
it('getMessageById returns the correct message', () => {
const d = createDb()
const msg = d.insertMessage({ from: 'a', to: 'b', subject: 'test' })
const found = d.getMessageById(msg.id)
expect(found?.subject).toBe('test')
expect(d.getMessageById('msg_nonexistent')).toBeUndefined()
})
})
describe('tasks', () => {
it('creates a task with no deps as ready', () => {
const d = createDb()
const task = d.createTask({ spec: 'do something' })
const task = d.createTask({ runId, spec: 'do something' })
expect(task.id).toMatch(/^task_/)
expect(task.status).toBe('ready')
expect(task.deps).toBe('[]')
@@ -191,6 +48,7 @@ describe('OrchestrationDb', () => {
it('persists explicit task display metadata', () => {
const d = createDb()
const task = d.createTask({
runId,
spec: 'full details',
taskTitle: 'Checkout race',
displayName: 'Fix checkout race'
@@ -204,6 +62,7 @@ describe('OrchestrationDb', () => {
it('persists the creating terminal handle for task-created worktrees', () => {
const d = createDb()
const task = d.createTask({
runId,
spec: 'spawn related workspace',
createdByTerminalHandle: 'term_creator'
})
@@ -214,16 +73,16 @@ describe('OrchestrationDb', () => {
it('creates a task with deps as pending', () => {
const d = createDb()
const parent = d.createTask({ spec: 'parent' })
const child = d.createTask({ spec: 'child', deps: [parent.id] })
const parent = d.createTask({ runId, spec: 'parent' })
const child = d.createTask({ runId, spec: 'child', deps: [parent.id] })
expect(child.status).toBe('pending')
expect(JSON.parse(child.deps)).toEqual([parent.id])
})
it('promotes pending tasks when deps complete', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second', deps: [t1.id] })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second', deps: [t1.id] })
expect(d.getTask(t2.id)?.status).toBe('pending')
@@ -234,9 +93,9 @@ describe('OrchestrationDb', () => {
it('does not promote task until ALL deps complete', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'a' })
const t2 = d.createTask({ spec: 'b' })
const t3 = d.createTask({ spec: 'c', deps: [t1.id, t2.id] })
const t1 = d.createTask({ runId, spec: 'a' })
const t2 = d.createTask({ runId, spec: 'b' })
const t3 = d.createTask({ runId, spec: 'c', deps: [t1.id, t2.id] })
d.updateTaskStatus(t1.id, 'completed')
expect(d.getTask(t3.id)?.status).toBe('pending')
@@ -247,7 +106,7 @@ describe('OrchestrationDb', () => {
it('sets completed_at on completion', () => {
const d = createDb()
const task = d.createTask({ spec: 'do it' })
const task = d.createTask({ runId, spec: 'do it' })
const updated = d.updateTaskStatus(task.id, 'completed', '{"result": true}')
expect(updated?.completed_at).toBeTruthy()
expect(updated?.result).toBe('{"result": true}')
@@ -255,7 +114,7 @@ describe('OrchestrationDb', () => {
it('completing a task frees its active dispatch context', () => {
const d = createDb()
const task = d.createTask({ spec: 'do it' })
const task = d.createTask({ runId, spec: 'do it' })
createRootDispatch(d, task.id, 'term_a')
d.updateTaskStatus(task.id, 'completed')
@@ -266,8 +125,8 @@ describe('OrchestrationDb', () => {
it('listTasks filters by status', () => {
const d = createDb()
d.createTask({ spec: 'ready task' })
const t2 = d.createTask({ spec: 'another' })
d.createTask({ runId, spec: 'ready task' })
const t2 = d.createTask({ runId, spec: 'another' })
d.updateTaskStatus(t2.id, 'completed')
expect(d.listTasks({ status: 'ready' })).toHaveLength(1)
@@ -277,15 +136,15 @@ describe('OrchestrationDb', () => {
it('listTasks returns all when no filter', () => {
const d = createDb()
d.createTask({ spec: 'one' })
d.createTask({ spec: 'two' })
d.createTask({ runId, spec: 'one' })
d.createTask({ runId, spec: 'two' })
expect(d.listTasks()).toHaveLength(2)
})
it('listTasksWithDispatch joins active dispatch metadata', () => {
const d = createDb()
const ready = d.createTask({ spec: 'ready task' })
const dispatched = d.createTask({ spec: 'active task' })
const ready = d.createTask({ runId, spec: 'ready task' })
const dispatched = d.createTask({ runId, spec: 'active task' })
const ctx = createRootDispatch(d, dispatched.id, 'term_worker')
const rows = d.listTasksWithDispatch()
@@ -300,7 +159,7 @@ describe('OrchestrationDb', () => {
it('listTasksWithDispatch does not surface completed dispatches', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
createRootDispatch(d, task.id, 'term_worker')
d.updateTaskStatus(task.id, 'completed')
@@ -314,8 +173,8 @@ describe('OrchestrationDb', () => {
it('supports parent_id for task decomposition', () => {
const d = createDb()
const parent = d.createTask({ spec: 'parent' })
const child = d.createTask({ spec: 'child', parentId: parent.id })
const parent = d.createTask({ runId, spec: 'parent' })
const child = d.createTask({ runId, spec: 'child', parentId: parent.id })
expect(child.parent_id).toBe(parent.id)
})
})
@@ -323,7 +182,7 @@ describe('OrchestrationDb', () => {
describe('dispatch contexts', () => {
it('creates a dispatch context and marks task as dispatched', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(d, task.id, 'term_worker')
expect(ctx.id).toMatch(/^ctx_/)
@@ -335,8 +194,8 @@ describe('OrchestrationDb', () => {
it('rejects dispatch for non-ready tasks', () => {
const d = createDb()
const parent = d.createTask({ spec: 'parent' })
const child = d.createTask({ spec: 'child', deps: [parent.id] })
const parent = d.createTask({ runId, spec: 'parent' })
const child = d.createTask({ runId, spec: 'child', deps: [parent.id] })
expect(() => createRootDispatch(d, child.id, 'term_worker')).toThrow(
/only ready tasks can be dispatched/
@@ -345,8 +204,8 @@ describe('OrchestrationDb', () => {
it('rejects dispatch to an occupied terminal', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second' })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second' })
createRootDispatch(d, t1.id, 'term_worker')
expect(() => createRootDispatch(d, t2.id, 'term_worker')).toThrow(
@@ -361,8 +220,8 @@ describe('OrchestrationDb', () => {
it('rejects dispatch to a reminted handle on a pane with an active dispatch', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second' })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second' })
createRootDispatch(d, t1.id, 'term_old', `tab_1:${LEAF_A}`)
expect(() => createRootDispatch(d, t2.id, 'term_new', `tab_1:${LEAF_A}`)).toThrow(
@@ -372,8 +231,8 @@ describe('OrchestrationDb', () => {
it('rejects dispatch when pane keys share a leaf after break-out', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second' })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second' })
createRootDispatch(d, t1.id, 'term_old', `tab_1:${LEAF_A}`)
expect(() => createRootDispatch(d, t2.id, 'term_new', `tab_2:${LEAF_A}`)).toThrow(
@@ -383,8 +242,8 @@ describe('OrchestrationDb', () => {
it('allows concurrent dispatches to different panes', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second' })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second' })
createRootDispatch(d, t1.id, 'term_a', `tab_1:${LEAF_A}`)
expect(() => createRootDispatch(d, t2.id, 'term_b', `tab_1:${LEAF_B}`)).not.toThrow()
@@ -392,8 +251,8 @@ describe('OrchestrationDb', () => {
it('falls back to handle lock when pane keys are missing', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second' })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second' })
createRootDispatch(d, t1.id, 'term_worker')
// New dispatch has a pane key but the active row is legacy (no pane key):
@@ -403,8 +262,8 @@ describe('OrchestrationDb', () => {
it('allows dispatch to a terminal after previous dispatch completes', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'first' })
const t2 = d.createTask({ spec: 'second' })
const t1 = d.createTask({ runId, spec: 'first' })
const t2 = d.createTask({ runId, spec: 'second' })
const ctx1 = createRootDispatch(d, t1.id, 'term_worker')
d.completeDispatch(ctx1.id)
@@ -414,7 +273,7 @@ describe('OrchestrationDb', () => {
it('getDispatchContext returns latest for a task', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(d, task.id, 'term_a')
const found = d.getDispatchContext(task.id)
expect(found?.id).toBe(ctx.id)
@@ -422,7 +281,7 @@ describe('OrchestrationDb', () => {
it('getDispatchContext uses insertion order when timestamps tie', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx1 = createRootDispatch(d, task.id, 'term_a')
d.failDispatch(ctx1.id, 'retry')
const ctx2 = createRootDispatch(d, task.id, 'term_a')
@@ -432,7 +291,7 @@ describe('OrchestrationDb', () => {
it('getActiveDispatchForTerminal returns active dispatch', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
createRootDispatch(d, task.id, 'term_a')
const active = d.getActiveDispatchForTerminal('term_a')
@@ -442,10 +301,16 @@ describe('OrchestrationDb', () => {
it('getLatestDispatchForTerminal returns the most recent completed dispatch', () => {
const d = createDb()
const firstTask = d.createTask({ spec: 'first' })
const firstTask = d.createTask({
runId,
spec: 'first'
})
const first = createRootDispatch(d, firstTask.id, 'term_a')
d.completeDispatch(first.id)
const secondTask = d.createTask({ spec: 'second' })
const secondTask = d.createTask({
runId,
spec: 'second'
})
const second = createRootDispatch(d, secondTask.id, 'term_a')
d.completeDispatch(second.id)
@@ -457,7 +322,7 @@ describe('OrchestrationDb', () => {
it('circuit breaker trips after 3 failures', () => {
const d = createDb()
const task = d.createTask({ spec: 'flaky' })
const task = d.createTask({ runId, spec: 'flaky' })
const ctx = createRootDispatch(d, task.id, 'term_a')
const after1 = d.failDispatch(ctx.id, 'timeout')
@@ -480,7 +345,7 @@ describe('OrchestrationDb', () => {
it('completeDispatch sets completed_at', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(d, task.id, 'term_a')
d.completeDispatch(ctx.id)
@@ -493,7 +358,10 @@ describe('OrchestrationDb', () => {
describe('decision gates', () => {
it('creates a gate and blocks the task', () => {
const d = createDb()
const task = d.createTask({ spec: 'needs approval' })
const task = d.createTask({
runId,
spec: 'needs approval'
})
createRootDispatch(d, task.id, 'term_a')
const gate = d.createGate({
taskId: task.id,
@@ -513,7 +381,7 @@ describe('OrchestrationDb', () => {
it('resolves a gate and unblocks the task', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const gate = d.createGate({ taskId: task.id, question: 'ok?' })
const resolved = d.resolveGate(gate.id, 'yes')
@@ -526,7 +394,7 @@ describe('OrchestrationDb', () => {
it('times out a gate', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const gate = d.createGate({ taskId: task.id, question: 'ok?' })
const timedOut = d.timeoutGate(gate.id)
@@ -535,8 +403,8 @@ describe('OrchestrationDb', () => {
it('lists gates with filters', () => {
const d = createDb()
const t1 = d.createTask({ spec: 'a' })
const t2 = d.createTask({ spec: 'b' })
const t1 = d.createTask({ runId, spec: 'a' })
const t2 = d.createTask({ runId, spec: 'b' })
d.createGate({ taskId: t1.id, question: 'q1' })
const g2 = d.createGate({ taskId: t2.id, question: 'q2' })
d.resolveGate(g2.id, 'done')
@@ -599,8 +467,13 @@ describe('OrchestrationDb', () => {
describe('lifecycle', () => {
it('resetAll clears all tables', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 'test' })
d.createTask({ spec: 'work' })
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'test'
})
d.createTask({ runId, spec: 'work' })
d.resetAll()
@@ -610,8 +483,13 @@ describe('OrchestrationDb', () => {
it('resetMessages clears only messages', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 'test' })
d.createTask({ spec: 'work' })
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'test'
})
d.createTask({ runId, spec: 'work' })
d.resetMessages()
@@ -621,8 +499,13 @@ describe('OrchestrationDb', () => {
it('resetTasks clears tasks and dispatch contexts', () => {
const d = createDb()
d.insertMessage({ from: 'a', to: 'b', subject: 'test' })
const task = d.createTask({ spec: 'work' })
d.insertMessage({
runId,
from: 'a',
to: 'b',
subject: 'test'
})
const task = d.createTask({ runId, spec: 'work' })
createRootDispatch(d, task.id, 'term_a')
d.resetTasks()
@@ -636,6 +519,7 @@ describe('OrchestrationDb', () => {
it('insertMessage accepts type = heartbeat', () => {
const d = createDb()
const msg = d.insertMessage({
runId,
from: 'worker',
to: 'coord',
subject: 'alive',
@@ -647,7 +531,7 @@ describe('OrchestrationDb', () => {
it('recordHeartbeat updates last_heartbeat_at on dispatched rows', () => {
const d = createDb()
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(d, task.id, 'term_a')
d.recordHeartbeat(ctx.id, '2026-05-04T00:00:00.000Z')
@@ -662,10 +546,10 @@ describe('OrchestrationDb', () => {
// (b) dispatched, heartbeated 12 min ago → STALE (expected result)
// (c) dispatched, never heartbeated, dispatched 30s ago → not stale (grace)
// (d) completed, heartbeated 30 min ago → not stale (status filter)
const taskA = d.createTask({ spec: 'a' })
const taskB = d.createTask({ spec: 'b' })
const taskC = d.createTask({ spec: 'c' })
const taskD = d.createTask({ spec: 'd' })
const taskA = d.createTask({ runId, spec: 'a' })
const taskB = d.createTask({ runId, spec: 'b' })
const taskC = d.createTask({ runId, spec: 'c' })
const taskD = d.createTask({ runId, spec: 'd' })
const ctxA = createRootDispatch(d, taskA.id, 'term_a')
const ctxB = createRootDispatch(d, taskB.id, 'term_b')
const ctxC = createRootDispatch(d, taskC.id, 'term_c')
@@ -710,15 +594,19 @@ describe('OrchestrationDb', () => {
// Fresh worker: dispatched 12:00, heartbeat 12:05 (space-format), both
// after the 11:55 threshold → NOT stale.
const fresh = createRootDispatch(d, d.createTask({ spec: 'fresh' }).id, 'term_fresh')
const fresh = createRootDispatch(d, d.createTask({ runId, spec: 'fresh' }).id, 'term_fresh')
setDispatchTimes(d, fresh.id, '2026-07-12 12:00:00', '2026-07-12 12:05:00')
// Legacy ISO-format fresh row (mixed-format table) stays fresh too.
const legacy = createRootDispatch(d, d.createTask({ spec: 'legacy' }).id, 'term_legacy')
const legacy = createRootDispatch(
d,
d.createTask({ runId, spec: 'legacy' }).id,
'term_legacy'
)
setDispatchTimes(d, legacy.id, '2026-07-12T12:00:00.000Z', '2026-07-12T12:05:00.000Z')
// Genuinely hung: dispatched + heartbeated at 10:00, ~2h before threshold.
const hung = createRootDispatch(d, d.createTask({ spec: 'hung' }).id, 'term_hung')
const hung = createRootDispatch(d, d.createTask({ runId, spec: 'hung' }).id, 'term_hung')
setDispatchTimes(d, hung.id, '2026-07-12 10:00:00', '2026-07-12 10:00:00')
const stale = d.getStaleDispatches('2026-07-12T11:55:00.000Z')
@@ -730,7 +618,7 @@ describe('OrchestrationDb', () => {
// Space-format dispatched_at one minute after the threshold, no heartbeat
// yet → still inside the grace window, must not be flagged.
const ctx = createRootDispatch(d, d.createTask({ spec: 'x' }).id, 'term_x')
const ctx = createRootDispatch(d, d.createTask({ runId, spec: 'x' }).id, 'term_x')
setDispatchTimes(d, ctx.id, '2026-07-12 12:00:00')
const stale = d.getStaleDispatches('2026-07-12T11:59:00.000Z')
@@ -742,7 +630,11 @@ describe('OrchestrationDb', () => {
it('getStaleDispatches keeps a fresh row just after a UTC-midnight threshold (#8452)', () => {
const d = createDb()
const ctx = createRootDispatch(d, d.createTask({ spec: 'midnight' }).id, 'term_midnight')
const ctx = createRootDispatch(
d,
d.createTask({ runId, spec: 'midnight' }).id,
'term_midnight'
)
setDispatchTimes(d, ctx.id, '2026-05-04 00:04:00')
const stale = d.getStaleDispatches('2026-05-04T00:00:00.000Z')
@@ -755,7 +647,7 @@ describe('OrchestrationDb', () => {
it('getStaleDispatches keeps a live worker with a fresh space-format heartbeat (#8452)', () => {
const d = createDb()
const ctx = createRootDispatch(d, d.createTask({ spec: 'live' }).id, 'term_live')
const ctx = createRootDispatch(d, d.createTask({ runId, spec: 'live' }).id, 'term_live')
setDispatchTimes(d, ctx.id, '2026-07-12 10:00:00', '2026-07-12 11:59:00')
const stale = d.getStaleDispatches('2026-07-12T11:55:00.000Z')
@@ -765,6 +657,7 @@ describe('OrchestrationDb', () => {
it('getThreadMessagesFor returns only same-thread replies to a handle', () => {
const d = createDb()
const outbound = d.insertMessage({
runId,
from: 'worker',
to: 'coord',
subject: 'Question',
@@ -773,6 +666,7 @@ describe('OrchestrationDb', () => {
})
// Reply in the same thread addressed to the worker
const reply = d.insertMessage({
runId,
from: 'coord',
to: 'worker',
subject: 'Re: Question',
@@ -781,6 +675,7 @@ describe('OrchestrationDb', () => {
})
// Distractor: different thread, same recipient
d.insertMessage({
runId,
from: 'coord',
to: 'worker',
subject: 'other',
@@ -789,6 +684,7 @@ describe('OrchestrationDb', () => {
})
// Distractor: same thread but not addressed to worker
d.insertMessage({
runId,
from: 'coord',
to: 'someone_else',
subject: 'cc',
@@ -902,6 +798,7 @@ describe('OrchestrationDb', () => {
// (a) INSERT type='heartbeat' now succeeds
expect(() =>
d.insertMessage({
runId,
from: 'w',
to: 'c',
subject: 'alive',
@@ -911,7 +808,7 @@ describe('OrchestrationDb', () => {
).not.toThrow()
// (b) last_heartbeat_at column exists on dispatch_contexts
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(d, task.id, 'term_a')
d.recordHeartbeat(ctx.id, '2026-05-04T00:00:00.000Z')
expect(d.getDispatchContext(task.id)?.last_heartbeat_at).toBe('2026-05-04T00:00:00.000Z')
@@ -942,11 +839,12 @@ describe('OrchestrationDb', () => {
const d = new OrchestrationDb(path)
db = d
const task = d.createTask({ spec: 'work' })
const task = d.createTask({ runId, spec: 'work' })
const ctx = createRootDispatch(d, task.id, 'term_a', 'tab_1:leaf_1')
expect(d.getDispatchContextById(ctx.id)?.assignee_pane_key).toBe('tab_1:leaf_1')
const msg = d.insertMessage({
runId,
from: 'w',
to: 'c',
subject: 'done',
@@ -960,6 +858,7 @@ describe('OrchestrationDb', () => {
const path = createV1Snapshot()
const first = new OrchestrationDb(path)
first.insertMessage({
runId,
from: 'w',
to: 'c',
subject: 'alive',
@@ -972,6 +871,7 @@ describe('OrchestrationDb', () => {
db = second
expect(() =>
second.insertMessage({
runId,
from: 'w',
to: 'c',
subject: 'again',
@@ -48,7 +48,7 @@ describe('durable Attempt observation and outcome projection', () => {
function createAttempt(): { taskId: string; dispatchId: string } {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'observe outcome' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'observe outcome' })
const dispatch = createRootDispatch(db, task.id, 'term_observed')
return { taskId: task.id, dispatchId: dispatch.id }
}
@@ -162,7 +162,10 @@ describe('durable Attempt observation and outcome projection', () => {
const path = join(dir, 'orchestration.sqlite')
try {
db = new OrchestrationDb(path)
const task = db.createTask({ spec: 'durable observation' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'durable observation'
})
const dispatch = createRootDispatch(db, task.id, 'term_durable')
db.recordAttemptObservation(
fact(dispatch.id, {
@@ -189,7 +192,10 @@ describe('durable Attempt observation and outcome projection', () => {
it('keeps worker_done settlement as the atomic success fast path', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'worker_done fast path' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'worker_done fast path'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -7,4 +7,4 @@ export const LEGACY_CONTRACT_VERSION = 0
export const CURRENT_CONTRACT_VERSION = ORCHESTRATION_CONTRACT_VERSION
// Schema versions: v2 'heartbeat'+last_heartbeat_at, v3 delivered_at, v4 task-creator terminal, v5 task_title/display_name, v6 pane identity, v7 lightweight Runs, v8 crash-safe Run deliveries, v9 durable question threads, v10 Dispatch capabilities, v11 durable mutation receipts, v12 composed worker state, v18 post-v6 version-skew repair, v19 adopted legacy Runs and compatibility receipts, v20 legacy question backfill, v21 legacy scheduler-loss provenance, v22 dispatch assignee lookup, v23 worker terminal resource ownership, v24 creator-incarnation authority, v25 active Dispatch handle lookup, v26 indexed mutation receipt capacity, v27 durable federation acknowledgments, v28 durable local mutation caller identity, v31 dispatch/resource identity links, v32 bounded worker-terminal recovery metadata, v33 durable mailbox pointer Enter state, v34 role-addressed mailbox deliveries, v35 mailbox delivery default and index-predicate repair, v36 dispatch mailbox consumer generation, v37 recorded dispatch creator identity, v39 structured session journal archives.
export const SCHEMA_VERSION = 39
export const SCHEMA_VERSION = 40
@@ -9,7 +9,7 @@ describe('decision-gate lifecycle transitions', () => {
it('blocks the dispatched Task when creating a gate', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'gate blocks task' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'gate blocks task' })
createRootDispatch(db, task.id, 'term_gate')
expect(db.getTask(task.id)?.status).toBe('dispatched')
@@ -20,7 +20,7 @@ describe('decision-gate lifecycle transitions', () => {
it('rolls back the gate row when the Task transition cannot commit', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'atomic gate creation' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'atomic gate creation' })
const dispatch = createRootDispatch(db, task.id, 'term_gate')
db.db.exec(`
CREATE TRIGGER reject_gate_task_block
@@ -1,6 +1,5 @@
import type { DecisionGateRow, DispatchContextRow, GateStatus } from '../../types'
import { OrchestrationError } from '../../orchestration-error'
import { LEGACY_RUN_ID } from '../contract-constants'
import { generateId } from '../generated-id'
import type { OrchestrationDb } from '../orchestration-db'
import { transitionLifecycleWithDb } from '../lifecycle-transition'
@@ -18,6 +17,16 @@ export function createGate(
): DecisionGateRow {
this.db.exec('SAVEPOINT create_gate')
try {
const task = this.getTask(gate.taskId)
if (!task) {
throw new OrchestrationError(
'lifecycle_not_found',
`Task ${gate.taskId} was not found while creating a decision gate.`,
{ taskId: gate.taskId }
)
}
const runId = task.run_id
this.requireRun(runId)
const active = this.db
.prepare(
`SELECT * FROM dispatch_contexts
@@ -65,22 +74,8 @@ export function createGate(
.prepare(
'INSERT INTO decision_gates (id, run_id, task_id, question, options) VALUES (?, ?, ?, ?, ?)'
)
.run(
id,
this.getTask(gate.taskId)?.run_id ?? LEGACY_RUN_ID,
gate.taskId,
gate.question,
optionsJson
)
.run(id, runId, gate.taskId, gate.question, optionsJson)
this.completeActiveDispatchesForTask(gate.taskId)
const task = this.getTask(gate.taskId)
if (!task) {
throw new OrchestrationError(
'lifecycle_not_found',
`Task ${gate.taskId} was not found while creating a decision gate.`,
{ taskId: gate.taskId }
)
}
transitionLifecycleWithDb(this.db, {
entity: 'task',
id: gate.taskId,
@@ -16,7 +16,7 @@ describe('nested worker depth', () => {
function coordinatorDispatchesWorker(maxDepth = UNCAPPED) {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'root task' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'root task' })
const worker = db.createDispatchContext({
taskId: task.id,
assigneeHandle: 'term_worker',
@@ -33,7 +33,7 @@ describe('nested worker depth', () => {
it('refuses a worker dispatching a sub-worker at the default cap', () => {
coordinatorDispatchesWorker()
const nested = db.createTask({ spec: 'nested task' })
const nested = db.createTask({ runId: 'run_legacy_local', spec: 'nested task' })
expect(() =>
db.createDispatchContext({
taskId: nested.id,
@@ -51,7 +51,7 @@ describe('nested worker depth', () => {
it('tells the refused worker to complete the task itself', () => {
coordinatorDispatchesWorker()
const nested = db.createTask({ spec: 'nested task' })
const nested = db.createTask({ runId: 'run_legacy_local', spec: 'nested task' })
expect(() =>
db.createDispatchContext({
taskId: nested.id,
@@ -64,7 +64,7 @@ describe('nested worker depth', () => {
it('permits one more generation when the cap is raised, and records depth 2', () => {
coordinatorDispatchesWorker()
const nested = db.createTask({ spec: 'nested task' })
const nested = db.createTask({ runId: 'run_legacy_local', spec: 'nested task' })
const sub = db.createDispatchContext({
taskId: nested.id,
assigneeHandle: 'term_sub',
@@ -113,9 +113,9 @@ describe('nested worker depth', () => {
db.db
.prepare(
`INSERT INTO remote_dispatch_attachments
(dispatch_id, task_id, home_peer_fingerprint, protocol_version, runtime_epoch,
(dispatch_id, task_id, home_run_id, home_peer_fingerprint, protocol_version, runtime_epoch,
pane_key, process_incarnation, state, depth)
VALUES (?, ?, 'peer', 1, 'epoch', ?, ?, ?, ?)`
VALUES (?, ?, 'run_home', 'peer', 1, 'epoch', ?, ?, ?, ?)`
)
.run(`ctx_${state}_${depth}_${paneKey}_${inc}`, 'task_remote', paneKey, inc, state, depth)
}
@@ -182,7 +182,10 @@ describe('nested worker depth', () => {
it('takes the maximum when a process holds both a local and a remote role', () => {
// Query order must not decide the answer: the deeper role governs.
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'local role' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'local role'
})
db.createDispatchContext({
taskId: task.id,
assigneeHandle: 'term_both',
@@ -220,13 +223,19 @@ describe('nested worker depth', () => {
it('stamps depth 1 for a root coordinator', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'root work' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'root work'
})
expect(startWorker(task.id, SYSTEM, UNCAPPED).dispatch.depth).toBe(1)
})
it('refuses a worker starting a sub-worker at the default cap', () => {
coordinatorDispatchesWorker()
const nested = db.createTask({ spec: 'nested work' })
const nested = db.createTask({
runId: 'run_legacy_local',
spec: 'nested work'
})
expect(() =>
startWorker(
nested.id,
@@ -238,7 +247,10 @@ describe('nested worker depth', () => {
it('refuses a worker retrying into a sub-worker at the default cap', () => {
coordinatorDispatchesWorker()
const nested = db.createTask({ spec: 'nested retry work' })
const nested = db.createTask({
runId: 'run_legacy_local',
spec: 'nested retry work'
})
const first = startWorker(nested.id, SYSTEM, UNCAPPED)
db.failWorkerStart(first.dispatch.id, 'accepted', 'first attempt failed')
expect(() =>
@@ -257,7 +269,10 @@ describe('nested worker depth', () => {
// Context-only dispatch stores null on purpose; requiring an incarnation
// locally would silently drop real parents and fail open.
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'context only' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'context only'
})
const row = db.createDispatchContext({
taskId: task.id,
assigneeHandle: 'term_ctx',
@@ -22,7 +22,7 @@ describe('dispatch mailbox consumer fencing', () => {
afterEach(() => db.close())
function dispatchWithMail(subjects: string[]): { id: string; runId: string } {
const task = db.createTask({ spec: 'fenced worker work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'fenced worker work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker', PANE_A)
for (const subject of subjects) {
db.insertMessage({
@@ -116,7 +116,10 @@ describe('dispatch mailbox consumer fencing', () => {
})
it('bumps and fences on the worker-start attach path', () => {
const task = db.createTask({ spec: 'worker-start attach' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'worker-start attach'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -149,6 +152,7 @@ describe('dispatch mailbox consumer fencing', () => {
it('gives a federated attachment its own generation on the worker host', () => {
const dispatchId = 'ctx_remote_fence'
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: 'task_remote',
homePeerFingerprint: 'home-peer',
@@ -187,7 +191,10 @@ describe('dispatch mailbox consumer fencing', () => {
})
it('starts a retry Dispatch on a fresh mailbox address rather than sharing the old one', () => {
const task = db.createTask({ spec: 'work that fails once' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'work that fails once'
})
const first = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -49,8 +49,8 @@ const STARTING_DISPATCH_CONTEXT_SQL = `INSERT INTO dispatch_contexts (
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', datetime('now'))`
const REMOTE_DISPATCH_ATTACHMENT_SQL = `INSERT INTO remote_dispatch_attachments (
dispatch_id, task_id, home_peer_fingerprint, protocol_version, runtime_epoch, depth
) VALUES (?, ?, ?, ?, ?, ?)`
dispatch_id, home_run_id, task_id, home_peer_fingerprint, protocol_version, runtime_epoch, depth
) VALUES (?, ?, ?, ?, ?, ?, ?)`
/** Last line of defence: a row that reached here unstamped would read as a root. */
function assertStampedDepth(depth: number): void {
@@ -140,6 +140,7 @@ export function insertRemoteDispatchAttachmentRow(
db: Database.Database,
params: {
dispatchId: string
runId: string
taskId: string
homePeerFingerprint: string
protocolVersion: number
@@ -151,6 +152,7 @@ export function insertRemoteDispatchAttachmentRow(
assertStampedDepth(params.depth)
db.prepare(REMOTE_DISPATCH_ATTACHMENT_SQL).run(
params.dispatchId,
params.runId,
params.taskId,
params.homePeerFingerprint,
params.protocolVersion,
@@ -8,7 +8,10 @@ describe('federated Dispatch observation fence', () => {
it('rejects out-of-order epochs and observations captured before release', () => {
const database = (db = new OrchestrationDb(':memory:'))
const task = database.createTask({ spec: 'fenced federated observation' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'fenced federated observation'
})
const started = database.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -8,6 +8,7 @@ export function createRemoteDispatchAttachment(
this: OrchestrationDb,
params: {
dispatchId: string
runId: string
taskId: string
homePeerFingerprint: string
protocolVersion: number
@@ -43,6 +44,16 @@ export function createRemoteDispatchAttachment(
`Remote attachment request ${params.mutationReceipt.requestId} already exists.`
)
}
if (!params.runId?.trim()) {
throw new OrchestrationError('invalid_argument', 'Missing Run ID')
}
this.db
.prepare(
`INSERT OR IGNORE INTO runs (id, objective, home_database, consumer_generation, legacy)
VALUES (?, ?, 'remote', 0, 0)`
)
.run(params.runId, `Coordinated from ${params.homePeerFingerprint}`)
this.requireRun(params.runId)
ensureMutationReceiptCapacity(this.db)
this.db
.prepare(
@@ -59,6 +70,7 @@ export function createRemoteDispatchAttachment(
)
insertRemoteDispatchAttachmentRow(this.db, {
dispatchId: params.dispatchId,
runId: params.runId,
taskId: params.taskId,
homePeerFingerprint: params.homePeerFingerprint,
protocolVersion: params.protocolVersion,
@@ -16,6 +16,7 @@ describe('the remote attachment release guard', () => {
function settledAttachment(dispatchId: string): void {
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: `task_${dispatchId}`,
homePeerFingerprint: 'home-peer',
@@ -8,7 +8,7 @@ describe('guarded lifecycle transitions', () => {
it('rejects a stale prior state without changing the projection', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'guarded transition' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'guarded transition' })
expect(() =>
db!.transitionLifecycle({
@@ -23,7 +23,7 @@ describe('guarded lifecycle transitions', () => {
it('composes its projection into the caller-owned transaction', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'caller-owned rollback' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'caller-owned rollback' })
db.db.exec('SAVEPOINT lifecycle_test')
expect(
@@ -49,7 +49,7 @@ describe('guarded lifecycle transitions', () => {
['completed', 'blocked']
] as const)('preserves public task updates from %s to %s', (from, to) => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'manual status correction' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'manual status correction' })
db.db.prepare('UPDATE tasks SET status = ? WHERE id = ?').run(from, task.id)
expect(db.updateTaskStatus(task.id, to)?.status).toBe(to)
@@ -1,5 +1,4 @@
import type { MessageType, MessagePriority, MessageDeliveryContract, MessageRow } from '../../types'
import { LEGACY_RUN_ID } from '../contract-constants'
import { generateId } from '../generated-id'
import { exposeMessageTimestamps } from '../utc-timestamp'
import type { OrchestrationDb } from '../orchestration-db'
@@ -26,7 +25,10 @@ export type MessageInsert = {
}
export function insertMessage(this: OrchestrationDb, msg: MessageInsert): MessageRow {
const runId = msg.runId ?? LEGACY_RUN_ID
const runId = msg.runId
if (!runId) {
throw new Error('Run is required')
}
const deliveryContract = msg.deliveryContract ?? 'current_delivery'
this.requireRun(runId)
const id = msg.id ?? generateId('msg')
@@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS federated_dispatches (
);
CREATE TABLE IF NOT EXISTS remote_dispatch_attachments (
home_run_id TEXT NOT NULL,
dispatch_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
home_peer_fingerprint TEXT NOT NULL,
@@ -0,0 +1,26 @@
import { afterEach, describe, expect, it } from 'vitest'
import { OrchestrationDb } from '../orchestration-db'
import { migrateV40 } from './migrate-v40'
import { importFederatedControlMessage } from '../../federation-control-message'
describe('federated home Run migration', () => {
const db = new OrchestrationDb(':memory:')
afterEach(() => db.close())
it('adds the home Run column and refuses mail for a development placeholder', () => {
db.db.exec('ALTER TABLE remote_dispatch_attachments DROP COLUMN home_run_id')
db.db.exec(`INSERT INTO remote_dispatch_attachments
(dispatch_id, task_id, home_peer_fingerprint, runtime_epoch)
VALUES ('ctx_old', 'task_old', 'home', 'epoch')`)
migrateV40.call(db, 39)
expect(db.getRemoteDispatchAttachment('ctx_old')?.home_run_id).toBe('')
expect(() =>
importFederatedControlMessage(db, {
dispatchId: 'ctx_old',
messageId: 'message_old',
payload: JSON.stringify({ from: 'home', subject: 'Instruction', body: '', type: 'message' })
})
).toThrow('Run not found:')
expect(db.getMessageById('message_old')).toBeUndefined()
})
})
@@ -0,0 +1,11 @@
import type { OrchestrationDb } from '../orchestration-db'
export function migrateV40(this: OrchestrationDb, current: number): void {
if (current >= 40 || this.hasColumn('remote_dispatch_attachments', 'home_run_id')) {
return
}
// Federation is unreleased; any development-only rows fail Run validation until reattached.
this.db.exec(
"ALTER TABLE remote_dispatch_attachments ADD COLUMN home_run_id TEXT NOT NULL DEFAULT ''"
)
}
@@ -10,6 +10,7 @@ import { migrateV36 } from './migrate-v36'
import { migrateV37 } from './migrate-v37'
import { migrateV38 } from './migrate-v38'
import { migrateV39 } from './migrate-v39'
import { migrateV40 } from './migrate-v40'
// Why: CREATE TABLE IF NOT EXISTS won't alter existing DBs; migrate in a txn that bumps user_version only on success (atomic all-or-nothing).
export function migrate(this: OrchestrationDb): void {
@@ -30,6 +31,7 @@ export function migrate(this: OrchestrationDb): void {
migrateV37.call(this, current)
migrateV38.call(this, current)
migrateV39.call(this, current)
migrateV40.call(this, current)
this.createMailboxDeliveryIndexesIfPossible()
this.db.pragma(`user_version = ${SCHEMA_VERSION}`)
this.db.exec('COMMIT')
@@ -1,7 +1,6 @@
import type Database from '../../../../sqlite/sync-database'
import type { TaskStatus, TaskRow } from '../../types'
import { buildOrchestrationTaskDisplayMetadata } from '../../../../../shared/orchestration-task-display'
import { LEGACY_RUN_ID } from '../contract-constants'
import { generateId } from '../generated-id'
import type { TaskRuntimeLineageRow } from '../run-list-page'
import type { OrchestrationDb } from '../orchestration-db'
@@ -25,7 +24,10 @@ export function createTask(
runId?: string
}
): TaskRow {
const runId = task.runId ?? LEGACY_RUN_ID
const runId = task.runId
if (!runId) {
throw new Error('Run is required')
}
this.requireRun(runId)
if (task.parentId) {
const parent = this.getTask(task.parentId)
@@ -0,0 +1,40 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { OrchestrationDb } from './orchestration-db'
describe('writers require a Run', () => {
let db: OrchestrationDb
beforeEach(() => {
db = new OrchestrationDb(':memory:')
})
afterEach(() => db.close())
it('rejects a message without a Run instead of using the legacy Run', () => {
expect(() => db.insertMessage({ from: 'sender', to: 'worker', subject: 'mail' })).toThrow(
'Run is required'
)
expect(db.db.prepare('SELECT id FROM messages').all()).toEqual([])
})
it('rejects a Task without a Run instead of using the legacy Run', () => {
expect(() => db.createTask({ spec: 'work' })).toThrow('Run is required')
expect(db.listTasks()).toEqual([])
})
it('rejects a decision gate whose Task has no Run', () => {
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
vi.spyOn(db, 'getTask').mockReturnValue({ ...task, run_id: undefined } as never)
expect(() => db.createGate({ taskId: task.id, question: 'Proceed?' })).toThrow()
expect(db.listGates()).toEqual([])
})
it('rejects a decision gate without a Task before writing', () => {
db.db.exec(`
CREATE TRIGGER reject_gate_insert BEFORE INSERT ON decision_gates
BEGIN SELECT RAISE(ABORT, 'gate insert reached'); END;
`)
expect(() => db.createGate({ taskId: 'missing', question: 'Proceed?' })).toThrow(
'Task missing was not found while creating a decision gate.'
)
expect(db.listGates()).toEqual([])
})
})
@@ -6,7 +6,7 @@ import { createRootDispatch } from './db/root-dispatch-test-fixture'
describe('dispatch failure idempotency', () => {
it('counts an active dispatch failure only once', () => {
const db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
expect(db.failDispatch(dispatch.id, 'exit')?.failure_count).toBe(1)
@@ -19,7 +19,7 @@ describe('dispatch failure idempotency', () => {
it('does not overwrite a completed dispatch', () => {
const db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
db.completeDispatch(dispatch.id)
@@ -33,7 +33,7 @@ describe('dispatch failure idempotency', () => {
it('rolls back the dispatch when the task update fails', () => {
const db = new OrchestrationDb(':memory:')
const sqlite = (db as unknown as { db: Database.Database }).db
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
sqlite.exec(`
CREATE TRIGGER reject_task_failure_update
@@ -17,7 +17,7 @@ describe('a start that fails before authority still owns the terminal it created
adoption?: Parameters<OrchestrationDb['failWorkerStart']>[3]
): { db: OrchestrationDb; dispatchId: string } {
const d = (db = new OrchestrationDb(':memory:'))
const task = d.createTask({ spec: 'residual terminal' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'residual terminal' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -122,7 +122,7 @@ describe('a start that fails before authority still owns the terminal it created
const first = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
taskId: d.createTask({ spec: 'owner' }).id,
taskId: d.createTask({ runId: 'run_legacy_local', spec: 'owner' }).id,
startOptions: {}
})
d.prepareStartingWorkerAuthority({
@@ -138,7 +138,7 @@ describe('a start that fails before authority still owns the terminal it created
const second = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
taskId: d.createTask({ spec: 'claimant' }).id,
taskId: d.createTask({ runId: 'run_legacy_local', spec: 'claimant' }).id,
startOptions: {}
})
d.recordWorkerStage({
@@ -14,6 +14,7 @@ describe('federation acknowledgment integrity', () => {
db = new OrchestrationDb(':memory:')
const dispatchId = `ctx_protocol_${protocolVersion}`
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: `task_protocol_${protocolVersion}`,
homePeerFingerprint: 'home_peer',
@@ -58,11 +58,20 @@ export function importFederatedControlMessage(
payload: string
}
): { imported: boolean; type: MessageType } {
const attachment = db.getRemoteDispatchAttachment(params.dispatchId)
if (!attachment) {
throw new OrchestrationError(
'dispatch_not_found',
`Remote Dispatch ${params.dispatchId} was not found.`
)
}
db.requireRun(attachment.home_run_id)
const message = parseFederatedControlMessage(params.payload)
const recipient = `dispatch:${params.dispatchId}`
const existing = db.getMessageById(params.messageId)
if (existing) {
if (
existing.run_id !== attachment.home_run_id ||
existing.to_handle !== recipient ||
existing.from_handle !== message.from ||
existing.subject !== message.subject ||
@@ -81,6 +90,7 @@ export function importFederatedControlMessage(
}
db.insertMessage({
id: params.messageId,
runId: attachment.home_run_id,
from: message.from,
to: recipient,
subject: message.subject,
@@ -109,7 +109,10 @@ describe('lifecycle graph against its callers', () => {
it('settles a stopping worker whose PTY exits during the stop', () => {
const database = createDatabase()
const task = database.createTask({ spec: 'stopping exited worker' })
const task = database.createTask({
runId: 'run_legacy_local',
spec: 'stopping exited worker'
})
const dispatchId = startWorker(database, task.id, 'stopping_exited')
expect(database.beginWorkerStop(dispatchId, 'runtime_test').disposition).toBe('stopping')
@@ -127,9 +130,18 @@ describe('lifecycle graph against its callers', () => {
it('still lets a coordinator reopen or overturn a settled Task', () => {
const database = createDatabase()
const reopened = database.createTask({ spec: 'reopen me' })
const overturned = database.createTask({ spec: 'overturn me' })
const retried = database.createTask({ spec: 'retry me' })
const reopened = database.createTask({
runId: 'run_legacy_local',
spec: 'reopen me'
})
const overturned = database.createTask({
runId: 'run_legacy_local',
spec: 'overturn me'
})
const retried = database.createTask({
runId: 'run_legacy_local',
spec: 'retry me'
})
database.updateTaskStatus(reopened.id, 'completed', 'first result')
database.updateTaskStatus(overturned.id, 'completed', 'wrong result')
database.updateTaskStatus(retried.id, 'failed', 'boom')
@@ -10,10 +10,11 @@ describe('lifecycle reconciliation', () => {
it('rejects handle churn when neither side has stable pane identity', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_before_restart')
const logs: string[] = []
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_after_restart',
to: 'term_coordinator',
subject: 'Done',
@@ -37,9 +38,10 @@ describe('lifecycle reconciliation', () => {
it('completes worker_done from the dispatched pane after a handle remint', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_before_restart', `tab_w:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_after_restart',
to: 'term_coordinator',
subject: 'Done',
@@ -54,7 +56,7 @@ describe('lifecycle reconciliation', () => {
it('completes an exact-authority worker_done after an uncertain worker start', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -82,6 +84,7 @@ describe('lifecycle reconciliation', () => {
).toEqual({ valid: true })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Done after reconnect',
@@ -106,9 +109,10 @@ describe('lifecycle reconciliation', () => {
it('fails both the dispatch and task from an authenticated failed worker report', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker', `tab_w:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Failed: tests cannot start',
@@ -139,7 +143,7 @@ describe('lifecycle reconciliation', () => {
it('keeps worker report settlement nested in its caller transaction', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
db.db.exec('BEGIN IMMEDIATE')
@@ -160,19 +164,16 @@ describe('lifecycle reconciliation', () => {
it('replays an identical terminal outcome without mutating settled state', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
const makeMessage = () =>
db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Done',
type: 'worker_done',
payload: JSON.stringify({
taskId: task.id,
dispatchId: dispatch.id,
outcome: 'succeeded'
})
payload: JSON.stringify({ taskId: task.id, dispatchId: dispatch.id, outcome: 'succeeded' })
})
expect(reconcileLifecycleMessage(db, makeMessage()).action).toBe('completed')
@@ -199,6 +200,7 @@ describe('lifecycle reconciliation', () => {
])('rejects malformed worker reports with $code', ({ payload, code }) => {
db = new OrchestrationDb(':memory:')
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Done',
@@ -215,11 +217,12 @@ describe('lifecycle reconciliation', () => {
it('completes worker_done from the same leaf after a pane break-out changed the tab half', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
// Dispatch recorded the post-break-out pane key; the worker shell still
// holds the spawn-time key with the old tab id.
const dispatch = createRootDispatch(db, task.id, 'term_before_restart', `tab_new:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_after_restart',
to: 'term_coordinator',
subject: 'Done',
@@ -234,9 +237,10 @@ describe('lifecycle reconciliation', () => {
it('rejects mismatched opaque pane keys instead of treating them as legacy', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', `tab_w:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_reminted',
to: 'term_coordinator',
subject: 'Done',
@@ -251,9 +255,10 @@ describe('lifecycle reconciliation', () => {
it('rejects worker_done from a foreign pane that claims the assignee handle', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', `tab_w1:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_owner',
to: 'term_coordinator',
subject: 'Done',
@@ -294,9 +299,10 @@ describe('lifecycle reconciliation', () => {
it('does not let a caller-supplied rejection marker turn completion into success', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker', `tab_w:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Done',
@@ -323,9 +329,10 @@ describe('lifecycle reconciliation', () => {
it('rejects a coordinator completion for a pane-bound dispatch', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker', `tab_w:${LEAF_A}`)
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_coordinator',
to: 'term_coordinator',
subject: 'Done',
@@ -342,9 +349,13 @@ describe('lifecycle reconciliation', () => {
it('uses exact handle equality only for a legacy dispatch without a pane key', () => {
db = new OrchestrationDb(':memory:')
const acceptedTask = db.createTask({ spec: 'legacy work' })
const acceptedTask = db.createTask({
runId: 'run_legacy_local',
spec: 'legacy work'
})
const acceptedDispatch = createRootDispatch(db, acceptedTask.id, 'term_legacy')
const accepted = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy',
to: 'term_coordinator',
subject: 'Done',
@@ -357,9 +368,13 @@ describe('lifecycle reconciliation', () => {
})
expect(reconcileLifecycleMessage(db, accepted).action).toBe('completed')
const rejectedTask = db.createTask({ spec: 'other legacy work' })
const rejectedTask = db.createTask({
runId: 'run_legacy_local',
spec: 'other legacy work'
})
const rejectedDispatch = createRootDispatch(db, rejectedTask.id, 'term_other_legacy')
const rejected = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_foreign',
to: 'term_coordinator',
subject: 'Done',
@@ -379,8 +394,12 @@ describe('lifecycle reconciliation', () => {
it('does not release a dependent when a foreign completion wins the arrival race', () => {
db = new OrchestrationDb(':memory:')
const parent = db.createTask({ spec: 'parent' })
const child = db.createTask({ spec: 'child', deps: [parent.id] })
const parent = db.createTask({ runId: 'run_legacy_local', spec: 'parent' })
const child = db.createTask({
runId: 'run_legacy_local',
spec: 'child',
deps: [parent.id]
})
const dispatch = createRootDispatch(db, parent.id, 'term_worker', `tab_w:${LEAF_A}`)
const payload = JSON.stringify({
taskId: parent.id,
@@ -389,6 +408,7 @@ describe('lifecycle reconciliation', () => {
})
const foreign = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_coordinator',
to: 'term_coordinator',
subject: 'Done',
@@ -403,6 +423,7 @@ describe('lifecycle reconciliation', () => {
expect(db.getTask(child.id)?.status).toBe('pending')
const owner = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker_reminted',
to: 'term_coordinator',
subject: 'Done',
@@ -416,7 +437,7 @@ describe('lifecycle reconciliation', () => {
it('does not let a foreign replay overwrite an authorized completion', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker', `tab_w:${LEAF_A}`)
const payload = JSON.stringify({
taskId: task.id,
@@ -424,6 +445,7 @@ describe('lifecycle reconciliation', () => {
outcome: 'succeeded'
})
const owner = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Done',
@@ -435,6 +457,7 @@ describe('lifecycle reconciliation', () => {
const result = db.getTask(task.id)?.result
const replay = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_foreign',
to: 'term_coordinator',
subject: 'Forged replay',
@@ -451,10 +474,11 @@ describe('lifecycle reconciliation', () => {
it('surfaces worker_done sent from a different pane as rejected', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', `tab_w1:${LEAF_A}`)
const logs: string[] = []
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_other_worker',
to: 'term_coordinator',
subject: 'Done',
@@ -474,9 +498,10 @@ describe('lifecycle reconciliation', () => {
it('surfaces a heartbeat sent from a different pane without recording liveness', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', `tab_w1:${LEAF_A}`)
const heartbeat = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_other_worker',
to: 'term_coordinator',
subject: 'alive',
@@ -508,9 +533,10 @@ describe('lifecycle reconciliation', () => {
it('surfaces a foreign heartbeat that claims the assignee handle', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', `tab_w1:${LEAF_A}`)
const heartbeat = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_owner',
to: 'term_coordinator',
subject: 'alive',
@@ -528,9 +554,10 @@ describe('lifecycle reconciliation', () => {
it('records a heartbeat whose pane key drifted only in the tab half', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_owner', `tab_new:${LEAF_A}`)
const heartbeat = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_owner',
to: 'term_coordinator',
subject: 'alive',
@@ -548,12 +575,16 @@ describe('lifecycle reconciliation', () => {
it('suppresses same-dispatch heartbeats once worker_done is reconciled', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' })
const dispatch = createRootDispatch(db, task.id, 'term_worker')
const otherTask = db.createTask({ spec: 'other work' })
const otherTask = db.createTask({
runId: 'run_legacy_local',
spec: 'other work'
})
const otherDispatch = createRootDispatch(db, otherTask.id, 'term_other')
const insertHeartbeat = (dispatchId: string, from: string) =>
db.insertMessage({
runId: 'run_legacy_local',
from,
to: 'term_coordinator',
subject: 'alive',
@@ -565,6 +596,7 @@ describe('lifecycle reconciliation', () => {
reconcileLifecycleMessage(db, staleHeartbeat)
reconcileLifecycleMessage(db, otherHeartbeat)
const done = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coordinator',
subject: 'Done',
@@ -412,7 +412,7 @@ describe('STA-4604 worker PTY exit escalation reaches the coordinator', () => {
}
})
it('falls back to the legacy gate when the dispatch owning Run row is gone', async () => {
it('preserves the dispatch Run when legacy coordinator routing is used', async () => {
const { runtime, workerHandle, coordinatorHandle } = makeRuntimeWithTwoPanes()
const insertMessage = vi.fn((message: { to: string }) => ({
...message,
@@ -435,8 +435,7 @@ describe('STA-4604 worker PTY exit escalation reaches the coordinator', () => {
expect(insertMessage).toHaveBeenCalledWith(
expect.objectContaining({ to: coordinatorHandle, type: 'escalation' })
)
// An orphaned dispatch has no Run mailbox to address, so it must not invent one.
expect(insertMessage.mock.calls[0]?.[0]).not.toHaveProperty('runId')
expect(insertMessage.mock.calls[0]?.[0]).toHaveProperty('runId', 'run-that-no-longer-exists')
})
it('still reaches the Run mailbox when the Run has no bound coordinator', async () => {
@@ -15,9 +15,9 @@ const MAILBOX = 'dispatch:d1'
function seeded(): OrchestrationDb {
const db = new OrchestrationDb(':memory:')
db.insertMessages([
{ from: 'coordinator', to: MAILBOX, subject: 'a', type: 'status' },
{ from: 'coordinator', to: MAILBOX, subject: 'b', type: 'question' },
{ from: 'coordinator', to: MAILBOX, subject: 'c', type: 'status' }
{ runId: 'run_legacy_local', from: 'coordinator', to: MAILBOX, subject: 'a', type: 'status' },
{ runId: 'run_legacy_local', from: 'coordinator', to: MAILBOX, subject: 'b', type: 'question' },
{ runId: 'run_legacy_local', from: 'coordinator', to: MAILBOX, subject: 'c', type: 'status' }
])
return db
}
@@ -59,7 +59,12 @@ function stageArgs(db: OrchestrationDb, state: OrchestrationMailboxPointerState)
describe('mailbox pointer staging watermark', () => {
it('leaves no watermark when the reservation claim is lost', () => {
const db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 's' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 's'
})
// A concurrent flight already owns the reservation, so this claim cannot succeed.
expect(
db.stageMailboxPointerEnter([message.id], { ptyId: 'other-pty', processIncarnation: 'inc-x' })
@@ -79,7 +84,12 @@ describe('mailbox pointer staging watermark', () => {
it('leaves no watermark when the reservation write throws', () => {
const db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 's' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 's'
})
const throwing = new Proxy(db, {
get(target, prop, receiver) {
if (prop === 'markMailboxPointerWriteAttempted') {
@@ -107,7 +117,12 @@ describe('mailbox pointer staging watermark', () => {
it('keeps the watermark for the flight that owns the reservation', () => {
const db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 's' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 's'
})
const state = new OrchestrationMailboxPointerState()
const args = stageArgs(db, state)
stageOrchestrationMailboxPointer({
@@ -122,7 +137,12 @@ describe('mailbox pointer staging watermark', () => {
it('drains a delivery parked behind the watermark when the write is refused', () => {
const db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 's' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 's'
})
const state = new OrchestrationMailboxPointerState()
const args = stageArgs(db, state)
const redrive = vi.fn()
@@ -148,7 +168,7 @@ describe('mailbox pointer staging watermark', () => {
it('still points new mail after a delivery lost its reservation claim', async () => {
const db = new OrchestrationDb(':memory:')
db.insertMessage({ from: 'a', to: 'run:run-1', subject: 'first' })
db.insertMessage({ runId: 'run_legacy_local', from: 'a', to: 'run:run-1', subject: 'first' })
let stealNextClaim = true
const contended = new Proxy(db, {
get(target, prop, receiver) {
@@ -172,7 +192,7 @@ describe('mailbox pointer staging watermark', () => {
expect(writePty).not.toHaveBeenCalled()
// Newer mail must still reach the agent; a leaked watermark used to park it forever.
db.insertMessage({ from: 'a', to: 'run:run-1', subject: 'second' })
db.insertMessage({ runId: 'run_legacy_local', from: 'a', to: 'run:run-1', subject: 'second' })
delivery.deliver(LEAF, { mailboxHandle: 'run:run-1', skipAbsenceProbe: true })
await new Promise((resolve) => setImmediate(resolve))
@@ -14,7 +14,12 @@ import type { WriteSettlement } from '../../../shared/pty-write-settlement'
describe('orchestration mailbox pointer submit', () => {
it('does not settle a replacement reservation after an old Enter write resolves', async () => {
const db = new OrchestrationDb(':memory:')
const message = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 'staged' })
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'staged'
})
const ptyId = 'pty-reused'
const oldReservation = { ptyId, processIncarnation: 'inc-old' }
const replacementReservation = { ptyId, processIncarnation: 'inc-new' }
@@ -86,8 +91,18 @@ describe('orchestration mailbox pointer submit', () => {
it('does not overwrite a message already reserved by another pointer flight', () => {
const db = new OrchestrationDb(':memory:')
const first = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 'first' })
const second = db.insertMessage({ from: 'a', to: 'run:run-1', subject: 'second' })
const first = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'first'
})
const second = db.insertMessage({
runId: 'run_legacy_local',
from: 'a',
to: 'run:run-1',
subject: 'second'
})
const original = { ptyId: 'pty-a', processIncarnation: 'inc-a' }
const replacement = { ptyId: 'pty-b', processIncarnation: 'inc-b' }
@@ -108,8 +108,20 @@ describe('message batch atomicity', () => {
expect(() =>
db?.insertMessages([
{ id: 'inner_first', from: 'sender', to: 'recipient', subject: 'first' },
{ id: 'inner_second', from: 'sender', to: 'recipient', subject: 'second' }
{
runId: 'run_legacy_local',
id: 'inner_first',
from: 'sender',
to: 'recipient',
subject: 'first'
},
{
runId: 'run_legacy_local',
id: 'inner_second',
from: 'sender',
to: 'recipient',
subject: 'second'
}
])
).toThrow('blocked')
sqlite.exec('COMMIT')
@@ -133,6 +145,7 @@ describe('message batch atomicity', () => {
expect(() =>
db?.commitWorkerDoneMessageMutation(() => {
db?.insertMessage({
runId: 'run_legacy_local',
id: 'inner',
from: 'worker',
to: 'coordinator',
@@ -34,6 +34,7 @@ describe('nested worker depth migration (v30)', () => {
const oldDb = new Database(dbPath)
oldDb.exec('ALTER TABLE dispatch_contexts DROP COLUMN depth')
oldDb.exec('ALTER TABLE remote_dispatch_attachments DROP COLUMN depth')
oldDb.exec('ALTER TABLE remote_dispatch_attachments DROP COLUMN home_run_id')
oldDb.pragma('user_version = 29')
oldDb
.prepare(
@@ -78,7 +79,7 @@ describe('nested worker depth migration (v30)', () => {
)
.run()
const task = db.createTask({ spec: 'post-upgrade nesting attempt' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'post-upgrade nesting attempt' })
expect(() =>
db!.createDispatchContext({
taskId: task.id,
@@ -47,11 +47,13 @@ function createAdoptedFixture(options: { settleWork: boolean }): AdoptedFixture
const before = new OrchestrationDb(dbPath)
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: LEGACY_COORDINATOR_HANDLE
})
const dispatch = createRootDispatch(before, task.id, LEGACY_WORKER_HANDLE, LEGACY_WORKER_PANE)
const recovery = before.insertMessage({
runId: 'run_legacy_local',
from: LEGACY_WORKER_HANDLE,
to: LEGACY_COORDINATOR_HANDLE,
subject: 'recovered worker outcome',
@@ -36,7 +36,12 @@ describe('orchestration migration from every prior version stamp', () => {
expect(reopened.db.pragma('user_version', { simple: true }), `reopen v${version}`).toBe(
SCHEMA_VERSION
)
expect(() => reopened.createTask({ spec: `migration v${version}` })).not.toThrow()
expect(() =>
reopened.createTask({
runId: 'run_legacy_local',
spec: `migration v${version}`
})
).not.toThrow()
reopened.close()
}
})
@@ -103,6 +103,7 @@ describe('OrchestrationDb bounded mutation receipts', () => {
insertMutationReceipts(db, MUTATION_RECEIPT_MAX_ROWS, 'completed')
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId: 'ctx_remote_pruned',
taskId: 'task_remote_pruned',
homePeerFingerprint: 'caller',
@@ -131,6 +132,7 @@ describe('OrchestrationDb bounded mutation receipts', () => {
expect(() =>
db!.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId: 'ctx_remote_overflow',
taskId: 'task_remote_overflow',
homePeerFingerprint: 'caller',
@@ -151,7 +153,7 @@ describe('OrchestrationDb bounded mutation receipts', () => {
it('guards atomic worker acceptance without changing task state', () => {
db = new OrchestrationDb(':memory:')
const task = db.createTask({ spec: 'capacity check' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'capacity check' })
insertMutationReceipts(db, MUTATION_RECEIPT_MAX_ROWS, 'pending')
expect(() =>
@@ -226,7 +228,10 @@ describe('OrchestrationDb dispatch assignee index migration', () => {
tempDir = mkdtempSync(join(tmpdir(), 'orca-dispatch-index-migration-'))
const dbPath = join(tempDir, 'orchestration.db')
db = new OrchestrationDb(dbPath)
const task = db.createTask({ spec: 'indexed lookup' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'indexed lookup'
})
const dispatch = createRootDispatch(db, task.id, 'term_worker')
db.close()
db = undefined
@@ -245,7 +250,9 @@ describe('OrchestrationDb dispatch assignee index migration', () => {
db = new OrchestrationDb(dbPath)
const sqlite = sqliteFor(db)
expect(sqlite.pragma('user_version', { simple: true })).toBe(SCHEMA_VERSION)
expect(db.getDispatchContextById(dispatch.id)).toMatchObject({ assignee_handle: 'term_worker' })
expect(db.getDispatchContextById(dispatch.id)).toMatchObject({
assignee_handle: 'term_worker'
})
expect(db.getTask(task.id)).toMatchObject({
created_by_pane_key: null,
created_by_process_incarnation: null,
@@ -0,0 +1,97 @@
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { LEGACY_RUN_ID, OrchestrationDb } from './db'
import { SCHEMA_VERSION } from './db/contract-constants'
import { resolveOrchestrationMigrationStartVersion } from './orchestration-schema-version-skew'
describe('federated mailbox legacy-adoption probe', () => {
let db: OrchestrationDb | undefined
let directory: string | undefined
afterEach(() => {
db?.close()
if (directory) {
rmSync(directory, { recursive: true, force: true })
}
})
function seedMailbox(handle: string, kind: 'message' | 'delivery'): string {
directory = mkdtempSync(join(tmpdir(), 'orca-federated-legacy-probe-'))
const path = join(directory, 'orchestration.db')
db = new OrchestrationDb(path)
db.db.exec(`
INSERT INTO remote_dispatch_attachments (
dispatch_id, task_id, home_peer_fingerprint, home_run_id, runtime_epoch, state
) VALUES ('ctx_remote', 'task_remote', 'peer_home', 'run_home', 'epoch', 'ready');
`)
if (kind === 'message') {
db.db
.prepare(
`INSERT INTO messages (
id, run_id, delivery_contract, from_handle, to_handle, subject, type
) VALUES ('msg_probe', ?, 'current_delivery', 'term_home', ?, 'continue', 'dispatch')`
)
.run(LEGACY_RUN_ID, handle)
} else {
db.db
.prepare(
`INSERT INTO deliveries (id, run_id, mailbox_handle, consumer_generation, message_ids)
VALUES ('delivery_probe', ?, ?, 0, '[]')`
)
.run(LEGACY_RUN_ID, handle)
}
return path
}
it.each(['message', 'delivery'] as const)(
'does not replay adoption for a misfiled federated %s',
(kind) => {
const path = seedMailbox('dispatch:ctx_remote', kind)
expect(
resolveOrchestrationMigrationStartVersion(db!.db, SCHEMA_VERSION, SCHEMA_VERSION)
).toBe(SCHEMA_VERSION)
db!.close()
db = new OrchestrationDb(path)
expect(db.getLegacyAdoption()).toBeUndefined()
if (kind === 'message') {
expect(db.getMessageById('msg_probe')).toMatchObject({
run_id: LEGACY_RUN_ID,
delivery_contract: 'current_delivery'
})
} else {
expect(
db.db.prepare("SELECT status FROM deliveries WHERE id = 'delivery_probe'").get()
).toEqual({
status: 'outstanding'
})
}
}
)
it.each(['message', 'delivery'] as const)(
'still replays adoption for a genuine legacy %s',
(kind) => {
const path = seedMailbox('term_legacy_coordinator', kind)
expect(
resolveOrchestrationMigrationStartVersion(db!.db, SCHEMA_VERSION, SCHEMA_VERSION)
).toBe(6)
db!.close()
db = new OrchestrationDb(path)
expect(db.getLegacyAdoption()).toBeDefined()
if (kind === 'message') {
expect(db.getMessageById('msg_probe')).toMatchObject({
run_id: db.getLegacyAdoption()!.adopted_run_id,
delivery_contract: 'legacy_direct'
})
} else {
expect(
db.db.prepare("SELECT status FROM deliveries WHERE id = 'delivery_probe'").get()
).toEqual({
status: 'fenced'
})
}
}
)
})
@@ -54,6 +54,7 @@ export function createLegacyStorageCutoverFixture(): {
})
const legacyTask = first.createTask({
runId: 'run_legacy_local',
spec: 'legacy',
createdByTerminalHandle: 'term_legacy_coord'
})
@@ -76,16 +77,19 @@ export function createLegacyStorageCutoverFixture(): {
)
const legacyMessages = [
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_coord',
to: 'term_legacy_worker',
subject: 'read worker mail'
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'read coordinator mail'
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_coord',
to: 'term_legacy_worker',
subject: 'second worker page'
@@ -99,6 +103,7 @@ export function createLegacyStorageCutoverFixture(): {
question: 'Retained question?'
})
const rejection = first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Rejected heartbeat',
@@ -106,6 +111,7 @@ export function createLegacyStorageCutoverFixture(): {
payload: JSON.stringify({ _orcaLifecycleRejection: { code: 'migration', reason: 'cutover' } })
})
const lookalike = first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Ordinary legacy mail',
@@ -115,36 +121,42 @@ export function createLegacyStorageCutoverFixture(): {
})
const malformedRejections = [
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Invalid JSON marker',
payload: '{"_orcaLifecycleRejection":'
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Array marker',
payload: JSON.stringify({ _orcaLifecycleRejection: [] })
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'String marker',
payload: JSON.stringify({ _orcaLifecycleRejection: 'migration' })
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Incomplete marker',
payload: JSON.stringify({ _orcaLifecycleRejection: { code: 'migration' } })
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Non-string marker fields',
payload: JSON.stringify({ _orcaLifecycleRejection: { code: 19, reason: false } })
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'Array root',
@@ -153,6 +165,7 @@ export function createLegacyStorageCutoverFixture(): {
])
}),
first.insertMessage({
runId: 'run_legacy_local',
from: 'term_legacy_worker',
to: 'term_legacy_coord',
subject: 'String root',
@@ -77,6 +77,7 @@ describe('OrchestrationDb mutation and question state', () => {
it('accepts a question message in the fresh canonical schema', () => {
const d = createDb()
const message = d.insertMessage({
runId: 'run_legacy_local',
from: 'worker',
to: 'run:run_1',
subject: 'Need input',
@@ -42,7 +42,8 @@ const VERSIONED_POST_V6_COLUMNS = [
{ version: 36, table: 'dispatch_contexts', column: 'consumer_generation' },
{ version: 36, table: 'remote_dispatch_attachments', column: 'consumer_generation' },
{ version: 37, table: 'dispatch_contexts', column: 'creator_handle' },
{ version: 37, table: 'dispatch_contexts', column: 'creator_pane_key' }
{ version: 37, table: 'dispatch_contexts', column: 'creator_pane_key' },
{ version: 40, table: 'remote_dispatch_attachments', column: 'home_run_id' }
] as const
// Why: v34 shipped without these two, so a v34 stamp proves nothing about them; v35 repairs both
@@ -122,15 +123,22 @@ function messagesAllowQuestions(db: Database.Database): boolean {
function hasConsistentLegacyAdoption(db: Database.Database): boolean {
const sourceRunId = 'run_legacy_local'
// Misfiled federated mail is not evidence of a pre-Runs database.
const notFederatedMailbox = (handle: string): string =>
`NOT EXISTS (SELECT 1 FROM remote_dispatch_attachments AS attachment
WHERE 'dispatch:' || attachment.dispatch_id = ${handle})`
const deliveryFilter = hasOrchestrationColumn(db, 'deliveries', 'mailbox_handle')
? ` AND ${notFederatedMailbox('mailbox_handle')}`
: ''
const sourceGraph = db
.prepare(
`SELECT 1
WHERE EXISTS(SELECT 1 FROM tasks WHERE run_id = ?)
OR EXISTS(SELECT 1 FROM dispatch_contexts WHERE run_id = ?)
OR EXISTS(SELECT 1 FROM decision_gates WHERE run_id = ?)
OR EXISTS(SELECT 1 FROM messages WHERE run_id = ?)
OR EXISTS(SELECT 1 FROM messages WHERE run_id = ? AND ${notFederatedMailbox('to_handle')})
OR EXISTS(SELECT 1 FROM question_threads WHERE run_id = ?)
OR EXISTS(SELECT 1 FROM deliveries WHERE run_id = ?)`
OR EXISTS(SELECT 1 FROM deliveries WHERE run_id = ?${deliveryFilter})`
)
.get(sourceRunId, sourceRunId, sourceRunId, sourceRunId, sourceRunId, sourceRunId)
const adoption = db
@@ -13,7 +13,7 @@ describe('settled worker terminal resume fence rows', () => {
function createReadyWorker(): { db: OrchestrationDb; taskId: string; dispatchId: string } {
const d = new OrchestrationDb(':memory:')
db = d
const task = d.createTask({ spec: 'settled worker' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'settled worker' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -389,7 +389,10 @@ describe('OrchestrationDb version-skew migration', () => {
tempDir = mkdtempSync(join(tmpdir(), 'orca-db-version-skew-v30-reset-'))
const dbPath = join(tempDir, 'orchestration.db')
db = new OrchestrationDb(dbPath)
const task = db.createTask({ spec: 'reset by an older writer' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'reset by an older writer'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -15,7 +15,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('creates and activates a composed worker Dispatch transactionally', () => {
const d = createDb()
const task = d.createTask({ spec: 'worker' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'worker' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -82,7 +82,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('retains an active supervised worker terminal', () => {
const d = createDb()
const task = d.createTask({ spec: 'retain active worker' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'retain active worker' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -114,7 +114,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('requeues an active Task before settling a worker whose terminal is missing', () => {
const d = createDb()
const task = d.createTask({ spec: 'recover missing worker' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'recover missing worker' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -153,7 +153,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('commits worker-start mutation acceptance with the starting Dispatch', () => {
const d = createDb()
const task = d.createTask({ spec: 'atomic acceptance' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'atomic acceptance' })
const mutationReceipt = {
callerFingerprint: 'caller_fingerprint',
requestId: 'worker_start_request',
@@ -207,7 +207,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('fails a composed start without losing residual resource receipts', () => {
const d = createDb()
const task = d.createTask({ spec: 'worker' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'worker' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -232,7 +232,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('allows retry only from the Task current terminal Dispatch', () => {
const d = createDb()
const task = d.createTask({ spec: 'retry current' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'retry current' })
const first = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -272,7 +272,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('treats abandon of a superseded Dispatch as a no-op', () => {
const d = createDb()
const task = d.createTask({ spec: 'stale abandon' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'stale abandon' })
const first = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -317,7 +317,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('lets the stop fence win before a late worker completion', () => {
const d = createDb()
const task = d.createTask({ spec: 'race' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'race' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -350,7 +350,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('allows explicit stop recovery from uncertain local and remote starts', () => {
const d = createDb()
const task = d.createTask({ spec: 'uncertain local start' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'uncertain local start' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -365,6 +365,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
})
d.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId: 'ctx_remote_unknown',
taskId: 'task_remote_unknown',
homePeerFingerprint: 'home_peer',
@@ -398,6 +399,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
const paneKey = 'tab_remote:11111111-1111-4111-8111-111111111111'
const attach = (dispatchId: string): void => {
d.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: `task_${dispatchId}`,
homePeerFingerprint: 'home_peer',
@@ -452,6 +454,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
const leafId = '11111111-1111-4111-8111-111111111111'
const attach = (dispatchId: string, paneKey: string): void => {
d.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: `task_${dispatchId}`,
homePeerFingerprint: 'home_peer',
@@ -499,7 +502,7 @@ describe('OrchestrationDb worker Dispatch state', () => {
it('returns already-settled when completion wins before stop', () => {
const d = createDb()
const task = d.createTask({ spec: 'race' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'race' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -27,7 +27,7 @@ describe('R1 identity migration', () => {
tempDir = mkdtempSync(join(tmpdir(), 'orca-r1-identity-'))
const dbPath = join(tempDir, 'orchestration.db')
db = new OrchestrationDb(dbPath)
const task = db.createTask({ spec: 'legacy supervised worker' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'legacy supervised worker' })
const started = db.createStartingWorkerDispatch({
taskId: task.id,
startOptions: { worktree: 'folder:/workspace' },
+1
View File
@@ -190,6 +190,7 @@ export type FederatedDispatchRow = {
}
export type RemoteDispatchAttachmentRow = {
home_run_id: string
dispatch_id: string
task_id: string
home_peer_fingerprint: string
@@ -6,7 +6,7 @@ const INCARNATION = 'runtime_test:term_worker:1'
let db: OrchestrationDb
function startWorker(spec: string): { taskId: string; dispatchId: string; capability: string } {
const task = db.createTask({ spec })
const task = db.createTask({ runId: 'run_legacy_local', spec })
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -25,6 +25,7 @@ describe('orchestration federated message targeting', () => {
vi.spyOn(runtime, 'getTerminalPaneKey').mockReturnValue(paneKey)
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockReturnValue(processIncarnation)
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: 'task_remote_targeting',
homePeerFingerprint: 'home_peer',
@@ -152,6 +152,7 @@ describe('federated worker release ownership', () => {
function createAttachment(dispatchId: string, terminalOwnership?: 'created' | 'external'): void {
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: `task_${dispatchId}`,
homePeerFingerprint: HOME_FINGERPRINT,
@@ -162,6 +162,7 @@ export async function startFederatedWorker(args: {
server.environmentId,
'orchestration.federationAttachStart',
{
runId,
dispatchId: started.dispatch.id,
taskId: taskForRemote.id,
taskSpec: taskForRemote.spec,
@@ -56,6 +56,7 @@ describe('federated worker agent launch', () => {
const result = (await method.handler(
method.params!.parse({
runId: 'run-home',
dispatchId: 'ctx_remote',
taskId: 'task_remote',
taskSpec: 'remote cursor worker',
@@ -107,6 +107,7 @@ describe('orchestration federation control mail', () => {
homeDb.markWorkerDispatchReady(dispatchId)
workerDb.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: task.id,
homePeerFingerprint: homeFingerprint,
@@ -27,6 +27,7 @@ describe('orchestration federated folder placement', () => {
await expect(
method.handler(
method.params!.parse({
runId: 'run-home',
dispatchId: 'ctx_folder',
taskId: 'task_folder',
taskSpec: 'work in folder',
@@ -445,6 +445,7 @@ describe('orchestration federation lifecycle settlement', () => {
const dispatchId = `ctx_persisted_protocol_${protocolVersion}`
const taskId = `task_persisted_protocol_${protocolVersion}`
workerDb.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId,
homePeerFingerprint: 'run-home-device-token',
@@ -58,6 +58,7 @@ describe('federation host liveness verdicts', () => {
status: 'exited'
} as never)
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId: DISPATCH_ID,
taskId: 'task_remote',
homePeerFingerprint: HOME_FINGERPRINT,
@@ -112,6 +113,7 @@ describe('federation host liveness verdicts', () => {
throw new Error('Expected the real runtime PTY to be listed')
}
hostDb.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId: DISPATCH_ID,
taskId: 'task_remote',
homePeerFingerprint: HOME_FINGERPRINT,
@@ -49,6 +49,7 @@ describe('orchestration federated setup evidence', () => {
}
]
db.createRemoteDispatchAttachment({
runId: 'run-home',
dispatchId,
taskId: 'task_remote_setup',
homePeerFingerprint: 'home_peer',
@@ -29,6 +29,7 @@ describe('federation attach-start prompt budget', () => {
await expect(
method.handler(
method.params!.parse({
runId: 'run-home',
dispatchId: 'ctx_oversized_remote',
taskId: 'task_oversized_remote',
taskSpec: 'x'.repeat(8 * 1024 * 1024),
@@ -3,6 +3,7 @@ import { OptionalFiniteNumber, OptionalString, requiredString } from '../../../s
import { OptionalWorkerLaunchPreference } from '../worker/worker-start-schema'
export const FederationAttachStartParams = z.object({
runId: requiredString('Missing Run ID'),
dispatchId: requiredString('Missing Dispatch ID'),
taskId: requiredString('Missing Task ID'),
taskSpec: requiredString('Missing Task spec'),
@@ -65,6 +65,7 @@ export const ORCHESTRATION_FEDERATION_ATTACH_METHODS: RpcMethod[] = [
const db = runtime.getOrchestrationDb()
db.createRemoteDispatchAttachment({
runId: params.runId,
dispatchId: params.dispatchId,
taskId: params.taskId,
homePeerFingerprint: orchestrationMutation.callerFingerprint,
@@ -0,0 +1,188 @@
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { ORCHESTRATION_METHODS } from '../../orchestration'
import type { RpcContext } from '../../../core'
import { OrchestrationDb } from '../../../../orchestration/db'
import { OrcaRuntimeService } from '../../../../orca-runtime'
import {
encodeFederatedControlMessage,
importFederatedControlMessage
} from '../../../../orchestration/federation-control-message'
const DISPATCH_ID = 'ctx_federated_worker_1'
const WORKER_HANDLE = 'term_federated_worker'
const WORKER_PANE = 'tab_w:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee'
const INCARNATION = 'runtime_test:term_federated_worker:1'
type CheckResult = {
runId: string
deliveryId: string | null
messages: { id: string; subject: string }[]
count: number
replayed: boolean
acknowledged: string | null
}
describe('orchestration.check on a federated attachment across a restart', () => {
let directory: string | undefined
let db: OrchestrationDb | undefined
afterEach(() => {
db?.close()
db = undefined
if (directory) {
rmSync(directory, { recursive: true, force: true })
directory = undefined
}
})
function launch(path: string): RpcContext {
db = new OrchestrationDb(path)
const runtime = new OrcaRuntimeService()
runtime.setOrchestrationDb(db)
vi.spyOn(runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
handle === WORKER_HANDLE ? WORKER_PANE : null
)
vi.spyOn(runtime, 'getLiveTerminalPaneKey').mockImplementation((handle) =>
runtime.getTerminalPaneKey(handle)
)
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((handle) =>
handle === WORKER_HANDLE ? INCARNATION : null
)
return { runtime }
}
function check(ctx: RpcContext, params: Record<string, unknown> = {}): Promise<CheckResult> {
const method = ORCHESTRATION_METHODS.find((entry) => entry.name === 'orchestration.check')
if (!method) {
throw new Error('orchestration.check is not registered')
}
const parsed = method.params
? method.params.parse({ terminal: WORKER_HANDLE, ...params })
: undefined
return method.handler(parsed, ctx) as Promise<CheckResult>
}
function attach(store: OrchestrationDb, dispatchId: string, runId: string): void {
store.createRemoteDispatchAttachment({
dispatchId,
runId,
taskId: 'task_federated_1',
homePeerFingerprint: 'peer_fp',
protocolVersion: 1,
runtimeEpoch: 'epoch_1',
mutationReceipt: {
callerFingerprint: 'peer_fp',
requestId: 'attach_1',
method: 'orchestration.federationAttachStart',
payloadHash: 'attach_payload'
}
})
expect(store.getRunRaw(runId)).toBeDefined()
store.prepareRemoteAttachmentAuthority({
dispatchId,
paneKey: WORKER_PANE,
processIncarnation: INCARNATION,
worktreeId: 'folder_workspace',
terminalHandle: WORKER_HANDLE,
setupState: 'not_applicable',
effects: []
})
store.markRemoteAttachmentReady(dispatchId)
}
it('replays the coordinator instruction and takes its ack after the app restarts', async () => {
directory = mkdtempSync(join(tmpdir(), 'orca-federated-check-'))
const path = join(directory, 'orchestration.db')
const first = launch(path)
attach(db as OrchestrationDb, DISPATCH_ID, 'run_coordinator')
importFederatedControlMessage(db as OrchestrationDb, {
dispatchId: DISPATCH_ID,
messageId: 'msg_federated_1',
payload: encodeFederatedControlMessage({
from: 'term_coord',
subject: 'continue the task',
body: 'the plan changed',
type: 'dispatch',
priority: 'normal',
threadId: null,
payload: null
})
})
const delivered = await check(first)
expect(delivered.messages.map((message) => message.id)).toEqual(['msg_federated_1'])
expect(delivered.runId).toBe('run_coordinator')
expect(delivered.replayed).toBe(false)
const deliveryId = delivered.deliveryId as string
expect(deliveryId).not.toBeNull()
;(db as OrchestrationDb).close()
// The worker's process outlives the app; its instruction is still unacknowledged.
const second = launch(path)
const replayed = await check(second)
expect(replayed.deliveryId).toBe(deliveryId)
expect(replayed.replayed).toBe(true)
expect(replayed.messages.map((message) => message.id)).toEqual(['msg_federated_1'])
const acknowledged = await check(second, { ack: deliveryId })
expect(acknowledged.acknowledged).toBe(deliveryId)
expect(acknowledged.count).toBe(0)
})
it('files loopback mail once under the local Dispatch Run without replacing its owner', async () => {
const ctx = launch(':memory:')
const store = db as OrchestrationDb
const run = store.createRun({
objective: 'loopback coordinator',
coordinatorHandle: 'term_coord',
coordinatorPaneKey: 'tab_coord:pane_coord'
})
const task = store.createTask({ runId: run.id, spec: 'loopback task' })
const { dispatch } = store.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
taskId: task.id,
startOptions: {}
})
attach(store, dispatch.id, run.id)
expect(store.getRemoteDispatchAttachment(dispatch.id)?.home_run_id).toBe(dispatch.run_id)
expect(store.getRun(run.id)).toEqual(run)
const message = {
dispatchId: dispatch.id,
messageId: 'msg_loopback',
payload: encodeFederatedControlMessage({
from: 'term_coord',
subject: 'continue',
body: 'loopback instruction',
type: 'dispatch',
priority: 'normal',
threadId: null,
payload: null
})
}
expect(importFederatedControlMessage(store, message).imported).toBe(true)
expect(importFederatedControlMessage(store, message).imported).toBe(false)
expect(store.getMessageById(message.messageId)?.run_id).toBe(run.id)
const delivered = await check(ctx)
expect(delivered.runId).toBe(run.id)
expect(delivered.messages.map((entry) => entry.id)).toEqual([message.messageId])
expect((await check(ctx, { ack: delivered.deliveryId })).count).toBe(0)
})
it('refuses an attachment with no home Run before writing a Delivery', async () => {
const ctx = launch(':memory:')
const store = db as OrchestrationDb
attach(store, DISPATCH_ID, 'run_coordinator')
const attachment = store.getRemoteDispatchAttachment(DISPATCH_ID)!
vi.spyOn(store, 'findActiveRemoteAttachmentForPane').mockReturnValue({
...attachment,
home_run_id: undefined
} as never)
await expect(check(ctx)).rejects.toThrow()
expect(store.db.prepare('SELECT id FROM deliveries').all()).toEqual([])
})
})
@@ -3,7 +3,6 @@ import type { OrcaRuntimeService } from '../../../../orca-runtime'
import { OrchestrationError } from '../../../../orchestration/orchestration-error'
import { formatMessageBanner } from '../../../../orchestration/formatter'
import { exposeMessages } from './mailbox-message-receipt'
import { ORCHESTRATION_LEGACY_RUN_ID } from '../../../../../../shared/orchestration-rpc-contract'
import { routeAllMailboxPages } from '../schemas'
import { asDispatchFence, callerHoldsDispatchPane, dispatchFenced } from './dispatch-mailbox-fence'
import type { CheckParams } from '../schemas'
@@ -46,13 +45,15 @@ export async function checkWorkerMailbox(args: {
: remoteAttachment
? {
dispatchId: remoteAttachment.dispatch_id,
runId: undefined,
runId: remoteAttachment.home_run_id,
generation: remoteAttachment.consumer_generation
}
: undefined
if (!workerMailbox) {
return undefined
}
const deliveryRunId = workerMailbox.runId
db.requireRun(deliveryRunId)
const address = `dispatch:${workerMailbox.dispatchId}`
// Why: a federated worker host has no dispatch_contexts row, so its generation lives on the
// remote_dispatch_attachments row instead.
@@ -164,7 +165,6 @@ export async function checkWorkerMailbox(args: {
}
}
await revalidateWorkerMailbox()
const deliveryRunId = workerMailbox.runId ?? ORCHESTRATION_LEGACY_RUN_ID
let acknowledged
try {
acknowledged = params.ack
@@ -31,7 +31,7 @@ describe('orchestration migration behavior', () => {
it('lists an explicitly selected legacy Run without binding or mutation', async () => {
const { db, runtime } = createRuntime()
const task = db.createTask({ spec: 'pre-upgrade work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'pre-upgrade work' })
const taskList = ORCHESTRATION_METHODS.find(
(method) => method.name === 'orchestration.taskList'
)!
@@ -55,6 +55,7 @@ describe('orchestration migration behavior', () => {
it('formats legacy terminal inspection as read-only without consuming mail', async () => {
const { db, runtime } = createRuntime()
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coord',
subject: 'still working',
@@ -79,6 +80,7 @@ describe('orchestration migration behavior', () => {
// A consuming check refuses a handle with no live pane before it reads any mail.
vi.spyOn(runtime, 'getTerminalPaneKey').mockReturnValue('tab_legacy:leaf_legacy')
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coord',
subject: 'still working'
@@ -98,6 +100,7 @@ describe('orchestration migration behavior', () => {
it('rejects replies to legacy mail without marking or inserting rows', async () => {
const { db, runtime } = createRuntime()
const message = db.insertMessage({
runId: 'run_legacy_local',
from: 'term_worker',
to: 'term_coord',
subject: 'legacy question'
@@ -131,7 +131,7 @@ describe('failed worker-start receipt for a residual terminal', () => {
function failStart(residual: boolean): { recovery?: string } {
const d = (db = new OrchestrationDb(':memory:'))
const task = d.createTask({ spec: 'residual receipt' })
const task = d.createTask({ runId: 'run_legacy_local', spec: 'residual receipt' })
const started = d.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -117,6 +117,6 @@ describe('pre-v3 dispatch rows in worker-list', () => {
})
expect(worker.projection.attention.categories).toContain('unverifiable')
expect(worker.projection.attention.requiresAction).toBe(true)
expect(worker.projection.nextAction.kind).toBe('inspect')
expect(worker.projection.nextAction).toEqual({ kind: 'none', argv: [] })
})
})
@@ -242,7 +242,13 @@ describe('manual Dispatch observation', () => {
const result = (await workerListMethod.handler(
workerListMethod.params?.parse({ run: run.id }),
{ runtime }
)) as { workers: { dispatchId: string; workerState: string; terminalState: string | null }[] }
)) as {
workers: {
dispatchId: string
workerState: string
terminalState: string | null
}[]
}
expect(result.workers).toEqual([
expect.objectContaining({
@@ -262,7 +268,10 @@ describe('manual Dispatch observation', () => {
const runtime = new OrcaRuntimeService()
runtime.setOrchestrationDb(db)
const closeTerminal = vi.spyOn(runtime, 'closeTerminal')
const task = db.createTask({ spec: 'operator-owned lane' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'operator-owned lane'
})
const dispatch = createRootDispatch(
db,
task.id,
@@ -62,7 +62,10 @@ describe('worker-stop on a structured worker this runtime cannot reach', () => {
worktreeId: WORKTREE,
hostScope: { kind: 'local', hostId: 'local' }
})
const task = db.createTask({ spec: 'stop a structured worker' })
const task = db.createTask({
runId: 'run_legacy_local',
spec: 'stop a structured worker'
})
const started = db.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -642,7 +642,11 @@ function createAdoptedDb(options: { settleWork: boolean }): {
const dbPath = join(dir, 'orchestration.db')
const before = new OrchestrationDb(dbPath)
const task = before.createTask({ spec: 'legacy assignment', createdByTerminalHandle: 'term_old' })
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: 'term_old'
})
createRootDispatch(
before,
task.id,
@@ -650,6 +654,7 @@ function createAdoptedDb(options: { settleWork: boolean }): {
'tab_old:33333333-3333-4333-8333-333333333333'
)
const recovery = before.insertMessage({
runId: 'run_legacy_local',
from: 'term_old_worker',
to: 'term_old',
subject: 'recovered worker outcome',
@@ -53,6 +53,7 @@ export function createHarness(): LegacyCompatibilityDispatcherHarness {
const dbPath = join(dir, 'orchestration.db')
const before = new OrchestrationDb(dbPath)
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: COORDINATOR_HANDLE
})
@@ -43,6 +43,7 @@ function createHarness(): Harness {
const dbPath = join(dir, 'orchestration.db')
const before = new OrchestrationDb(dbPath)
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: COORDINATOR_HANDLE
})
@@ -40,12 +40,14 @@ function createHarness(options?: { seedCutoverQuestion?: boolean; seedCutoverAns
const dbPath = join(dir, 'orchestration.db')
const before = new OrchestrationDb(dbPath)
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: COORDINATOR_HANDLE
})
const dispatch = createRootDispatch(before, task.id, WORKER_HANDLE, WORKER_PANE)
const cutoverQuestion = options?.seedCutoverQuestion
? before.insertMessage({
runId: 'run_legacy_local',
from: WORKER_HANDLE,
to: COORDINATOR_HANDLE,
subject: 'Question',
@@ -60,6 +62,7 @@ function createHarness(options?: { seedCutoverQuestion?: boolean; seedCutoverAns
const cutoverAnswer =
cutoverQuestion && options?.seedCutoverAnswer
? before.insertMessage({
runId: 'run_legacy_local',
from: COORDINATOR_HANDLE,
to: WORKER_HANDLE,
subject: 'Re: Question',
@@ -308,7 +311,10 @@ describe('legacy question takeover compatibility', () => {
resumed as {
result: {
legacyCompatibility: {
answerAcknowledgement: { questionId: string; answerMessageId: string }
answerAcknowledgement: {
questionId: string
answerMessageId: string
}
}
}
}
@@ -51,6 +51,7 @@ function createHarness(): Harness {
const dbPath = join(dir, 'orchestration.db')
const before = new OrchestrationDb(dbPath)
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: COORDINATOR_HANDLE
})
@@ -47,6 +47,7 @@ function createHarness(): Harness {
const dbPath = join(dir, 'orchestration.db')
const before = new OrchestrationDb(dbPath)
const task = before.createTask({
runId: 'run_legacy_local',
spec: 'legacy assignment',
createdByTerminalHandle: COORDINATOR_HANDLE
})
@@ -44,7 +44,7 @@ describe('durable orchestration mutation ledger', () => {
const runtime = new OrcaRuntimeService()
runtime.setOrchestrationDb(db)
const effect = vi.fn((subject: string) =>
db.insertMessage({ from: 'caller', to: 'recipient', subject })
db.insertMessage({ runId: 'run_legacy_local', from: 'caller', to: 'recipient', subject })
)
const dispatcher = new RpcDispatcher({
runtime,
@@ -299,7 +299,10 @@ describe('durable orchestration mutation ledger', () => {
const db = new OrchestrationDb(':memory:')
const runtime = new OrcaRuntimeService()
runtime.setOrchestrationDb(db)
const params = { from: 'term_coord', task: db.createTask({ spec: 'restart' }).id }
const params = {
from: 'term_coord',
task: db.createTask({ runId: 'run_legacy_local', spec: 'restart' }).id
}
const callerFingerprint = db.getOrCreateLocalMutationCallerFingerprint()
const payloadHash = createHash('sha256')
.update(JSON.stringify({ method: 'orchestration.workerStart', params }))
@@ -22,7 +22,7 @@ function createHarness() {
const runtime = new OrcaRuntimeService()
runtime.setOrchestrationDb(db)
const effect = vi.fn((subject: string) =>
db.insertMessage({ from: 'caller', to: 'recipient', subject })
db.insertMessage({ runId: 'run_legacy_local', from: 'caller', to: 'recipient', subject })
)
const dispatcher = new RpcDispatcher({
runtime,
@@ -62,6 +62,7 @@ function createUpdateHarness(): Harness {
const oldRuntimeDb = new OrchestrationDb(dbPath)
const task = oldRuntimeDb.createTask({
runId: 'run_legacy_local',
spec: 'finish work across an app update',
createdByTerminalHandle: COORDINATOR_HANDLE
})
@@ -60,7 +60,7 @@ describe('settled worker automatic-resume fence persistence', () => {
getWorkspaceSessionHostIds: () => [LOCAL_EXECUTION_HOST_ID],
flushOrThrow: vi.fn()
} as unknown as RuntimeStore
const task = orchestrationDb.createTask({ spec: 'fence me' })
const task = orchestrationDb.createTask({ runId: 'run_legacy_local', spec: 'fence me' })
const started = orchestrationDb.createStartingWorkerDispatch({
creator: { kind: 'system' },
maxDepth: Number.MAX_SAFE_INTEGER,
@@ -92,9 +92,19 @@ describe('OrcaRuntimeRpcServer', () => {
}
try {
db.insertMessage({ from: 'worker', to: 'coordinator', subject: 'before reset' })
db.insertMessage({
runId: 'run_legacy_local',
from: 'worker',
to: 'coordinator',
subject: 'before reset'
})
const first = await resetMessages('reset-first', firstDevice.token)
db.insertMessage({ from: 'worker', to: 'coordinator', subject: 'after reset' })
db.insertMessage({
runId: 'run_legacy_local',
from: 'worker',
to: 'coordinator',
subject: 'after reset'
})
const replay = await resetMessages('reset-replay', firstDevice.token)
expect(first).toMatchObject({
@@ -129,6 +139,7 @@ describe('OrcaRuntimeRpcServer', () => {
const device = server['deviceRegistry']!.addDevice('existing-cli', 'runtime')
const existingFingerprint = createHash('sha256').update(device.token).digest('hex')
db.createRemoteDispatchAttachment({
runId: 'run_home',
dispatchId: 'ctx_existing_remote',
taskId: 'task_existing_remote',
homePeerFingerprint: existingFingerprint,
@@ -137,7 +137,7 @@ describe('orchestration fleet projection', () => {
host: { kind: 'local' },
liveness: { verdict: 'unverifiable', reason: 'missing_status' },
resource: { state: 'absent', reason: 'unsupervised' },
nextAction: { kind: 'inspect' }
nextAction: { kind: 'none' }
})
})
@@ -229,6 +229,7 @@ describe('orchestration fleet projection', () => {
expect(second.workers.at(-1)?.id).toBe('dispatch-109')
})
// Cleanup still earns a command when liveness is unverifiable.
it('suggests release only for reclaimable ownership', () => {
const result = projectOrchestrationFleet({
workers: [worker('done', { terminalState: 'reclaimable' })],
@@ -236,6 +237,7 @@ describe('orchestration fleet projection', () => {
now: 1
})
expect(result.workers[0]?.liveness.verdict).toBe('unverifiable')
expect(result.workers[0]?.nextAction).toEqual({
kind: 'release',
argv: ['orchestration', 'worker-release', '--dispatch', 'done']
@@ -487,7 +489,8 @@ describe('fleet liveness and attention after a host verdict', () => {
verdict: 'unverifiable',
reason: 'missing_status'
})
expect(projected.workers[0]!.nextAction.kind).toBe('inspect')
// `recover` is reserved for a proven exit; worker-show would only restate this row.
expect(projected.workers[0]!.nextAction).toEqual({ kind: 'none', argv: [] })
})
it('certifies a process_exited stage whose exit was observed', () => {
@@ -519,15 +522,19 @@ describe('fleet liveness and attention after a host verdict', () => {
expect(projected.workers[0]!.nextAction).toEqual({ kind: 'none', argv: [] })
})
it('keeps an unverifiable worker on inspect: absence is never authority to stop', () => {
// worker-show repeats this projection, so inspecting again would loop.
it('asks nothing of an unverifiable worker instead of looping on worker-show', () => {
const now = 10 * AGENT_STATUS_STALE_AFTER_MS
const projected = projectOrchestrationFleet({
workers: [worker('1')],
statuses: [status('1', now - AGENT_STATUS_STALE_AFTER_MS - 60_000)],
now
})
expect(projected.workers[0]!.liveness.verdict).toBe('unverifiable')
expect(projected.workers[0]!.nextAction.kind).toBe('inspect')
expect(projected.workers[0]!.liveness).toMatchObject({
verdict: 'unverifiable',
reason: 'stale_status'
})
expect(projected.workers[0]!.nextAction).toEqual({ kind: 'none', argv: [] })
})
it('leaves a worker blocked on a question inspectable rather than recoverable', () => {
@@ -539,6 +546,22 @@ describe('fleet liveness and attention after a host verdict', () => {
expect(projected.workers[0]!.nextAction.kind).toBe('inspect')
})
it.each([{ pendingInput: true }, { pendingApproval: true }])(
'keeps an unverifiable worker with %o inspectable',
(pending) => {
const projected = projectOrchestrationFleet({
workers: [worker('1', pending)],
statuses: [],
now: 10_000
})
expect(projected.workers[0]!.liveness.verdict).toBe('unverifiable')
expect(projected.workers[0]!.nextAction).toEqual({
kind: 'inspect',
argv: ['orchestration', 'worker-show', '--dispatch', '1']
})
}
)
// The live worker-list row from a stopped worker: the same receipt proved the exit,
// called it absence, and pointed back at the command that reported the settlement.
it('never contradicts a proven exit on a stopped worker still owning its terminal', () => {
@@ -176,6 +176,10 @@ export function projectFleetNextAction(
) {
return { kind: 'none', argv: [] }
}
// worker-show repeats this projection; absence alone cannot earn another command.
if (liveness.verdict === 'unverifiable' && !worker.pendingInput && !worker.pendingApproval) {
return { kind: 'none', argv: [] }
}
return {
kind: 'inspect',
argv: ['orchestration', 'worker-show', '--dispatch', worker.dispatchId]