/** * Single-recipient handoff flow — `idle → locked → CAS → commit | revert`. * * See session-hierarchy.md §6.1 (single-recipient flow) and §6.4 (concurrent * CAS). Function-based per Convention #9 — dependencies arrive as an * explicit `deps` envelope so tests can inject mocks and production code * composes flows without a class hierarchy. * * Flow (pattern doc §5.1 + §6.1): * 1. Load source session; verify tenant. * 2. Check status (must be `idle`); no non-terminal turns. * 3. Validate capacity (depth + width). * 4. Transition source `idle → locked` with CAS on `ownerVersion`. * 5. Emit `onLocked`. * 6. Spawn recipient sub-session + provision isolated workspace. * 7. On any step 6 failure: compensating revert (`locked → idle`, version * unchanged, dispose any partial worktree), emit `onUnlocked`, rethrow. * 8. On success: commit `locked → idle` with updated `currentActor` + * appended `previousActors` + bumped `ownerVersion`. * 9. Emit `onCommitted` with the new version. */ import { type TopicManagerDependency } from '../../manager/topic/dependency.js'; import type { SessionId, TenantId } from '../../types/ids/index.js'; import type { SessionStore } from '../../types/session/store.js'; import type { WorkspaceBackendRegistry } from '../workspace/registry.js'; import type { HandoffAssignment, HandoffOutcome } from './assignment.js'; import type { CapacityValidator } from './capacity.js'; import type { HandoffEventSink } from './events.js'; /** * Minimal surface the handoff flow queries for turn fan-in status. A turn is * "blocking" when it is in any non-terminal status that prevents the source * session from transitioning to `locked` (session-hierarchy.md §5.1). * * The flow injects the resolver so it stays decoupled from how turns are * stored. Production reads the session index; tests stub. */ export interface TurnStatusResolver { /** * Returns the reason the session has a non-terminal turn, or `null` when * all its turns are terminal and the lock is allowed. */ blockingTurn(sessionId: SessionId, tenantId: TenantId): Promise<{ reason: 'active_turn' | 'pending_hitl' | 'pending_subsession'; } | null>; } interface SingleHandoffBaseDeps { store: SessionStore; workspaceRegistry: WorkspaceBackendRegistry; capacity: CapacityValidator; events: HandoffEventSink; /** * Required. A caller that genuinely wants no fan-in check supplies their * own always-null resolver, deliberately — the default that used to sit * here answered `null` for every session, which is a check that cannot * fail dressed as a check that ran. */ turnStatus: TurnStatusResolver; } /** Dependencies for a single-recipient handoff. */ export type SingleHandoffDeps = SingleHandoffBaseDeps & TopicManagerDependency; /** * Executes a single-recipient handoff against `deps.store`. Throws * {@link HandoffLockRejected}, {@link HandoffVersionConflict}, * {@link TenantIsolationError}, or {@link DelegationCapacityExceeded} on * invariant violations. Workspace provisioning failures surface as * {@link WorkspaceBackendError} after the compensating revert. */ export declare function executeSingleHandoff(deps: SingleHandoffDeps, assignment: HandoffAssignment, tenantId: TenantId): Promise; export {}; //# sourceMappingURL=single.d.ts.map