{"version":3,"file":"durable-delegation.d.ts","sourceRoot":"","sources":["../../../src/core/durable-delegation/durable-delegation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable Delegation (2.5.0).\n *\n * Production orchestration seam that turns a delegated child into a durable\n * first-class mission BEFORE external execution begins. This module composes\n * the canonical mission domain (DurableMissionCoordinator + MissionExecutor)\n * with a shared DurableMissionStore; it does not import process, provider,\n * CLI, or UI machinery — the execution mechanism arrives behind the\n * MissionExecutor seam.\n *\n * Invariants:\n *   - A child mission exists durably at CREATED before any executor.launch.\n *   - The coordinator owns every authoritative lifecycle transition\n *     (CREATED → QUEUED → LAUNCHING + attemptId → RUNNING + executionId →\n *     terminal); the caller never drives the executor directly.\n *   - Parent identity is structured (`missionId` + `depth`), never PID-derived.\n *   - Recovery (restart reconciliation) never auto-runs child work; it is a\n *     read-only store pass that marks lost ownership INTERRUPTED.\n */\n\nimport {\n\tDurableMissionCoordinator,\n\ttype DurableMissionCoordinatorOptions,\n\ttype DurableRecoveryReport,\n} from \"../mission-domain/durable-coordinator.js\";\nimport type { DurableMissionRecord, DurableMissionStore } from \"../mission-domain/durable-store.js\";\nimport type { HeartbeatScheduler, HeartbeatTelemetry } from \"../mission-domain/execution-heartbeat.js\";\nimport {\n\ttype ExecutionLease,\n\ttype ExecutionLeaseProof,\n\tnewExecutorOwnerId,\n} from \"../mission-domain/execution-lease.js\";\nimport type { MissionExecutor } from \"../mission-domain/mission-executor.js\";\nimport { createMissionRequest, type MissionRequest } from \"../mission-domain/mission-request.js\";\nimport type { MissionResult } from \"../mission-domain/mission-result.js\";\n\n/** Structured parent mission identity (never PID-derived). */\nexport interface DelegationParentIdentity {\n\tmissionId: string;\n\tdepth: number;\n}\n\n/** The durable + canonical outcome of one delegated child. */\nexport interface DurableDelegationChildOutcome {\n\tmissionId: string;\n\tparentMissionId?: string;\n\tdepth: number;\n\t/** Terminal durable record (authoritative lifecycle history). */\n\trecord: DurableMissionRecord;\n\t/** Canonical terminal result (the parent consumes this, not prose). */\n\tresult: MissionResult;\n\t/** Durable attempt identity allocated before launch. */\n\tattemptId: string;\n\t/** Executor execution identity attached after launch (when launch succeeded). */\n\texecutionId?: string;\n\t/** Fencing epoch of the winning execution (ownership authority epoch). */\n\tfencingToken: number;\n\t/** Latest heartbeat telemetry for this execution (observability). */\n\theartbeatTelemetry?: HeartbeatTelemetry;\n}\n\nexport interface DurableMissionDelegatorOptions {\n\tstore: DurableMissionStore;\n\t/** Now override for deterministic construction (tests). */\n\tnow?: () => number;\n\t/** Attempt identity factory (tests). */\n\tattemptIdFactory?: () => string;\n\t/** Executor owner identity (defaults to a fresh host+UUID identity). */\n\townerId?: string;\n\t/** Execution lease lifetime override. */\n\tleaseDurationMs?: number;\n\t/** Lease identity factory (tests). */\n\tleaseIdFactory?: () => string;\n\t/** Heartbeat renewal cadence override (defaults to leaseDurationMs / 3). */\n\theartbeatIntervalMs?: number;\n\t/** Heartbeat safety margin override (defaults to leaseDurationMs / 6). */\n\trenewalSafetyMarginMs?: number;\n\t/** Injectable timer scheduler for deterministic heartbeat tests. */\n\theartbeatScheduler?: HeartbeatScheduler;\n}\n\n/**\n * Deterministic, session-scoped, non-PID identity for the transitional\n * root-delegation anchor used when no active first-class/reliability mission\n * exists. It is a safe path component and never derived from process state.\n */\nexport function rootDelegationMissionId(sessionId: string): string {\n\treturn `delegation-root-${sessionId}`;\n}\n\n/**\n * Resolve the delegation parent identity from the active reliability mission\n * id when present; otherwise fall back to the deterministic session-scoped\n * root-delegation anchor. Depth is always 0 for the root parent.\n */\nexport function parentIdentityFor(activeMissionId: string | undefined, sessionId: string): DelegationParentIdentity {\n\treturn { missionId: activeMissionId ?? rootDelegationMissionId(sessionId), depth: 0 };\n}\n\n/** Executor used only for read/recovery passes that never launch work. */\nconst NOOP_EXECUTOR: MissionExecutor = {\n\texecutorId: \"noop\",\n\tasync launch(): Promise<never> {\n\t\tthrow new Error(\"noop executor cannot launch\");\n\t},\n\tasync awaitResult(): Promise<never> {\n\t\tthrow new Error(\"noop executor cannot await\");\n\t},\n\tasync cancel(): Promise<void> {},\n};\n\nexport class DurableMissionDelegator {\n\tprivate readonly _store: DurableMissionStore;\n\tprivate readonly _coordinatorOptions: DurableMissionCoordinatorOptions;\n\tprivate readonly _ownerId: string;\n\n\tconstructor(options: DurableMissionDelegatorOptions) {\n\t\tthis._store = options.store;\n\t\tthis._ownerId = options.ownerId ?? newExecutorOwnerId();\n\t\tthis._coordinatorOptions = {\n\t\t\tnow: options.now,\n\t\t\tattemptIdFactory: options.attemptIdFactory,\n\t\t\townerId: this._ownerId,\n\t\t\tleaseDurationMs: options.leaseDurationMs,\n\t\t\tleaseIdFactory: options.leaseIdFactory,\n\t\t\theartbeatIntervalMs: options.heartbeatIntervalMs,\n\t\t\trenewalSafetyMarginMs: options.renewalSafetyMarginMs,\n\t\t\theartbeatScheduler: options.heartbeatScheduler,\n\t\t};\n\t}\n\n\t/** The underlying persistence port (tests/load paths). */\n\tget store(): DurableMissionStore {\n\t\treturn this._store;\n\t}\n\n\t/** Stable executor owner identity used for lease acquisition. */\n\tget ownerId(): string {\n\t\treturn this._ownerId;\n\t}\n\n\t/** Build a coordinator bound to one child's execution mechanism. */\n\tprivate coordinator(executor: MissionExecutor): DurableMissionCoordinator {\n\t\treturn new DurableMissionCoordinator(this._store, executor, this._coordinatorOptions);\n\t}\n\n\t/**\n\t * Ensure the transitional root-delegation anchor exists durably at CREATED.\n\t * Idempotent across calls (the request is fully deterministic so a re-create\n\t * with the same identity is recognized, never a conflict).\n\t */\n\tasync ensureRootDelegationMission(sessionId: string): Promise<DelegationParentIdentity> {\n\t\tconst missionId = rootDelegationMissionId(sessionId);\n\t\tconst request = createMissionRequest({\n\t\t\tmissionId,\n\t\t\tobjective: \"Root delegation anchor for this session\",\n\t\t\tagent: \"root-delegation\",\n\t\t\texecutionMode: \"execute\",\n\t\t\tacceptanceCriteria: [],\n\t\t\tnow: 0,\n\t\t});\n\t\tawait this.coordinator(NOOP_EXECUTOR).createMission(request);\n\t\treturn { missionId, depth: 0 };\n\t}\n\n\t/**\n\t * Persist a child mission at CREATED. Does not launch anything. The caller\n\t * supplies the execution mechanism so launch can never precede durable\n\t * creation — the coordinator persists first and only then invokes it.\n\t */\n\tasync createChild(request: MissionRequest, executor: MissionExecutor): Promise<DurableMissionRecord> {\n\t\treturn this.coordinator(executor).createMission(request);\n\t}\n\n\t/**\n\t * Create + run one child to a terminal durable state. The child is persisted\n\t * at CREATED before `executor.launch`; then QUEUED, LAUNCHING (attemptId),\n\t * RUNNING (executionId) and finally a terminal result are persisted around\n\t * the executor call. The caller never drives the executor directly.\n\t */\n\tasync executeChild(\n\t\trequest: MissionRequest,\n\t\texecutor: MissionExecutor,\n\t\toptions: { signal?: AbortSignal } = {},\n\t): Promise<DurableDelegationChildOutcome> {\n\t\tconst coordinator = this.coordinator(executor);\n\t\tawait coordinator.createMission(request);\n\t\tconst terminal = await coordinator.resume(request.missionId, { signal: options.signal });\n\t\treturn this._outcome(terminal, coordinator.heartbeatTelemetry(request.missionId));\n\t}\n\n\t/**\n\t * Explicitly resume an interrupted child to terminal state. Does NOT create\n\t * the mission (it must already exist durably). Allocates a NEW execution\n\t * attempt while preserving the prior attempt history and the immutable\n\t * mission/session identity.\n\t */\n\tasync resumeMission(\n\t\tmissionId: string,\n\t\texecutor: MissionExecutor,\n\t\toptions: { signal?: AbortSignal } = {},\n\t): Promise<DurableDelegationChildOutcome> {\n\t\tconst coordinator = this.coordinator(executor);\n\t\tconst terminal = await coordinator.resume(missionId, { signal: options.signal });\n\t\treturn this._outcome(terminal, coordinator.heartbeatTelemetry(missionId));\n\t}\n\n\tprivate _outcome(\n\t\tterminal: DurableMissionRecord,\n\t\theartbeatTelemetry?: HeartbeatTelemetry,\n\t): DurableDelegationChildOutcome {\n\t\tconst result = terminal.result;\n\t\tif (!result) {\n\t\t\tthrow new Error(`Mission ${terminal.missionId} did not reach a terminal result`);\n\t\t}\n\t\tconst lastAttempt = terminal.attempts[terminal.attempts.length - 1];\n\t\treturn {\n\t\t\tmissionId: terminal.missionId,\n\t\t\tparentMissionId: terminal.parentMissionId,\n\t\t\tdepth: terminal.depth,\n\t\t\trecord: terminal,\n\t\t\tresult,\n\t\t\tattemptId: lastAttempt?.attemptId ?? \"\",\n\t\t\texecutionId: terminal.resultExecutionId ?? lastAttempt?.executionId,\n\t\t\tfencingToken: terminal.fencingToken,\n\t\t\theartbeatTelemetry,\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Read + recovery (never launch work)\n\t// =========================================================================\n\n\t/** Load a mission record, or `undefined` when missing. Corrupt records throw. */\n\tasync getMission(missionId: string): Promise<DurableMissionRecord | undefined> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).getMission(missionId);\n\t}\n\n\tasync listMissions(): Promise<string[]> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).listMissions();\n\t}\n\n\tasync listNonterminalMissions(): Promise<string[]> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).listNonterminalMissions();\n\t}\n\n\tasync listChildren(parentMissionId: string): Promise<string[]> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).listChildren(parentMissionId);\n\t}\n\n\t/**\n\t * Restart reconciliation: mark lost executor ownership INTERRUPTED. Never\n\t * launches work and never fabricates success. See DurableMissionCoordinator.\n\t */\n\tasync recover(options: { now?: number } = {}): Promise<DurableRecoveryReport> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).recover(options);\n\t}\n\n\t// =========================================================================\n\t// Ownership control (no executor work)\n\t// =========================================================================\n\n\t/** Atomically acquire execution ownership without invoking the executor. */\n\tasync acquireOwnership(missionId: string): Promise<{ record: DurableMissionRecord; lease: ExecutionLease }> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).acquireOwnership(missionId);\n\t}\n\n\t/** Renew a live lease (heartbeat). Does not change the fencing token. */\n\tasync renewOwnership(\n\t\tmissionId: string,\n\t\tproof: ExecutionLeaseProof,\n\t\toptions: { now?: number } = {},\n\t): Promise<{ lease: ExecutionLease; record: DurableMissionRecord }> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).renewOwnership(missionId, proof, options);\n\t}\n\n\t/** Release a live lease without reaching a terminal state. */\n\tasync releaseOwnership(\n\t\tmissionId: string,\n\t\tproof: ExecutionLeaseProof,\n\t\toptions: { now?: number } = {},\n\t): Promise<DurableMissionRecord> {\n\t\treturn this.coordinator(NOOP_EXECUTOR).releaseOwnership(missionId, proof, options);\n\t}\n}\n"]}