{"version":3,"file":"child-execution-port.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/child-execution-port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAwB,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAEpG,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AAEzF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,KAAK,EACX,+BAA+B,EAC/B,kCAAkC,EAClC,kCAAkC,EAClC,iCAAiC,EAGjC,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,wCAAwC;IACxD,yGAAyG;IACzG,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,kBAAkB,CAAC;IAC1B,gFAAgF;IAChF,SAAS,EAAE,uBAAuB,CAAC;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC1C,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,qBAAa,iCAAkC,YAAW,+BAA+B;IACxF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0B;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,OAAO,EAAE,wCAAwC,EAO5D;IAED;;;;;;;;;OASG;IACG,YAAY,CAAC,OAAO,EAAE,kCAAkC,GAAG,OAAO,CAAC,kCAAkC,CAAC,CA4D3G;IAED;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,kCAAkC,GAAG,OAAO,CAAC,iCAAiC,CAAC,CAiDzG;IAED,OAAO,CAAC,OAAO;YAID,SAAS;YAKT,YAAY;CAM1B","sourcesContent":["/**\n * Scheduler/Worker child execution authority port (2.14.0).\n *\n * Adapter implementation of `OrchestrationChildExecutionPort`. It wraps the\n * existing control services and never launches anything itself:\n *\n *   - `SchedulerControlService` — durable scheduling intent. `executeChild`\n *     enqueues at most one intent per child mission (the scheduler's intent id\n *     is deterministic per mission, so repeated requests reuse the entry).\n *   - `WorkerControlService` — status/terminal polling only. The port calls\n *     the worker read model (`status`) and nothing else: never `start`, never\n *     `runOnce`, never a launch path.\n *\n * Actual execution remains owned by Scheduler -> Assignment -> Worker\n * (`SchedulerControlService.runTick` -> `AssignmentControlService` ->\n * `WorkerControlService.runOnce`).\n *\n * Authority is verified, never defaulted: the durable parent mission names the\n * child execution authority (`MissionRequest.orchestrationExecution.childExecutionAuthority`)\n * and this port accepts a child only when that named authority — and the named\n * orchestration — match this port's `authority` exactly.\n *\n * `childStatus` is the port's read-only polling half of the\n * `OrchestrationChildExecutionPort` contract: it reports mission state,\n * terminal outcome, scheduling intent, and worker read-model snapshots, and\n * is the only method the parent lifecycle executor may use to observe a\n * child without requesting execution.\n */\nimport type { DurableMissionRecord, DurableMissionStore } from \"../mission-domain/durable-store.js\";\nimport { isTerminalMissionState, type MissionState } from \"../mission-domain/mission-state.js\";\nimport type { SchedulerControlService } from \"../scheduler/scheduler-control-service.js\";\nimport { SchedulingError } from \"../scheduler/scheduler-types.js\";\nimport type { WorkerControlService } from \"../worker-daemon/worker-control-service.js\";\nimport type {\n\tOrchestrationChildExecutionPort,\n\tOrchestrationChildExecutionReceipt,\n\tOrchestrationChildExecutionRequest,\n\tOrchestrationChildExecutionStatus,\n\tOrchestrationChildWorkerStatus,\n\tOrchestrationPlanDocument,\n\tOrchestrationStore,\n} from \"./types.js\";\n\nexport interface SchedulerWorkerChildExecutionPortOptions {\n\t/** Stable identity of this child execution authority (matched against the parent's durable contract). */\n\tauthority: string;\n\t/** Durable mission store for child/parent verification and terminal polling. */\n\tmissions: DurableMissionStore;\n\t/** Orchestration plan store for node verification and scheduling input. */\n\tstore: OrchestrationStore;\n\t/** Scheduler used to enqueue (and idempotently reuse) the scheduling intent. */\n\tscheduler: SchedulerControlService;\n\t/**\n\t * Workers consulted for status/terminal polling only. The port never\n\t * starts a worker or triggers execution through it.\n\t */\n\tworkers?: readonly WorkerControlService[];\n\t/** Clock for deterministic enqueue timestamps. */\n\tnow?: () => number;\n}\n\nexport class SchedulerWorkerChildExecutionPort implements OrchestrationChildExecutionPort {\n\tprivate readonly _missions: DurableMissionStore;\n\tprivate readonly _store: OrchestrationStore;\n\tprivate readonly _scheduler: SchedulerControlService;\n\tprivate readonly _workers: readonly WorkerControlService[];\n\tprivate readonly _now: () => number;\n\treadonly authority: string;\n\n\tconstructor(options: SchedulerWorkerChildExecutionPortOptions) {\n\t\tthis.authority = options.authority;\n\t\tthis._missions = options.missions;\n\t\tthis._store = options.store;\n\t\tthis._scheduler = options.scheduler;\n\t\tthis._workers = options.workers ?? [];\n\t\tthis._now = options.now ?? (() => Date.now());\n\t}\n\n\t/**\n\t * Authorize execution of a materialized orchestration child.\n\t *\n\t * Verifies, in order: the plan exists and names the node; the node is\n\t * materialized and its durable identity matches the request; the child\n\t * mission's orchestration metadata matches; the parent's durable contract\n\t * names exactly this authority for this orchestration; the child is not\n\t * terminal. Only then does it enqueue the scheduling intent. It never\n\t * launches: execution proceeds through Scheduler -> Assignment -> Worker.\n\t */\n\tasync executeChild(request: OrchestrationChildExecutionRequest): Promise<OrchestrationChildExecutionReceipt> {\n\t\tconst planDocument = await this._loadPlan(request.orchestrationId);\n\t\tif (!planDocument) return this.decline(`ORCHESTRATION_NOT_FOUND: no plan for ${request.orchestrationId}`);\n\t\tconst plan = planDocument.plan;\n\t\tconst node = plan.nodes.find((candidate) => candidate.nodeId === request.nodeId);\n\t\tif (!node) return this.decline(`NODE_NOT_FOUND: ${request.orchestrationId} has no node ${request.nodeId}`);\n\t\tif (!node.childMissionId)\n\t\t\treturn this.decline(`CHILD_NOT_MATERIALIZED: node ${request.nodeId} has no materialized child mission`);\n\t\tif (\n\t\t\tnode.childMissionId !== request.childMissionId ||\n\t\t\tnode.childSessionId !== request.childSessionId ||\n\t\t\tnode.workspaceAccess !== request.workspaceAccess\n\t\t)\n\t\t\treturn this.decline(\n\t\t\t\t`CHILD_IDENTITY_MISMATCH: node ${request.nodeId} is materialized as ` +\n\t\t\t\t\t`${node.childMissionId}/${node.childSessionId ?? \"-\"}/${node.workspaceAccess}, ` +\n\t\t\t\t\t`request is ${request.childMissionId}/${request.childSessionId}/${request.workspaceAccess}`,\n\t\t\t);\n\n\t\tconst child = await this._loadMission(request.childMissionId);\n\t\tif (!child) return this.decline(`CHILD_NOT_MATERIALIZED: mission ${request.childMissionId} is missing`);\n\t\tconst metadata = child.request.orchestration;\n\t\tif (!metadata || metadata.orchestrationId !== request.orchestrationId || metadata.nodeId !== request.nodeId)\n\t\t\treturn this.decline(\n\t\t\t\t`CHILD_METADATA_MISMATCH: mission ${request.childMissionId} carries orchestration metadata ` +\n\t\t\t\t\t`${metadata ? `${metadata.orchestrationId}/${metadata.nodeId}` : \"none\"}`,\n\t\t\t);\n\n\t\tconst parent = await this._loadMission(plan.parentMissionId);\n\t\tif (!parent) return this.decline(`PARENT_MISSION_MISSING: ${plan.parentMissionId} required to verify authority`);\n\t\tconst execution = parent.request.orchestrationExecution;\n\t\tif (!execution)\n\t\t\treturn this.decline(\n\t\t\t\t`AUTHORITY_MISMATCH: parent mission ${plan.parentMissionId} names no child execution authority`,\n\t\t\t);\n\t\tif (execution.orchestrationId !== request.orchestrationId)\n\t\t\treturn this.decline(\n\t\t\t\t`AUTHORITY_MISMATCH: parent mission ${plan.parentMissionId} names orchestration ` +\n\t\t\t\t\t`'${execution.orchestrationId}', not '${request.orchestrationId}'`,\n\t\t\t);\n\t\tif (execution.childExecutionAuthority !== this.authority)\n\t\t\treturn this.decline(\n\t\t\t\t`AUTHORITY_MISMATCH: parent mission ${plan.parentMissionId} names authority ` +\n\t\t\t\t\t`'${execution.childExecutionAuthority}', not '${this.authority}'`,\n\t\t\t);\n\t\tif (isTerminalMissionState(child.state))\n\t\t\treturn this.decline(`MISSION_TERMINAL: child ${request.childMissionId} is ${child.state}`);\n\n\t\ttry {\n\t\t\tawait this._scheduler.enqueueIntent(request.childMissionId, {\n\t\t\t\trequirements: node.requirements,\n\t\t\t\tpriority: node.priority ?? 0,\n\t\t\t\tnow: this._now(),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (error instanceof SchedulingError)\n\t\t\t\treturn this.decline(`SCHEDULER_DECLINED: ${error.code}: ${error.message}`);\n\t\t\tthrow error;\n\t\t}\n\t\treturn { accepted: true, authority: this.authority };\n\t}\n\n\t/**\n\t * Status/terminal polling for a materialized child, read from the existing\n\t * stores (durable mission store, scheduling intent store) and the worker\n\t * read model. Read-only: this never enqueues, assigns, or launches.\n\t */\n\tasync childStatus(request: OrchestrationChildExecutionRequest): Promise<OrchestrationChildExecutionStatus> {\n\t\tconst child = await this._loadMission(request.childMissionId);\n\t\tconst missionState: MissionState | \"MISSING\" = child ? child.state : \"MISSING\";\n\t\tconst terminal = child ? isTerminalMissionState(child.state) : false;\n\t\tconst success = terminal && child?.result?.success === true;\n\t\tconst verificationStatus = child?.result?.verification.status;\n\t\tconst completionDecision = child?.result?.completionDecision;\n\t\tconst verificationSummary = child?.result?.verification.summary;\n\n\t\tlet intent: OrchestrationChildExecutionStatus[\"intent\"];\n\t\ttry {\n\t\t\tconst detail = await this._scheduler.getIntentForMission(request.childMissionId);\n\t\t\tintent = {\n\t\t\t\tintentId: detail.intentId,\n\t\t\t\tstate: detail.state,\n\t\t\t\tassignmentId: detail.assignmentId,\n\t\t\t\tunschedulableReason: detail.unschedulableReason,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof SchedulingError)) throw error;\n\t\t\tintent = undefined;\n\t\t}\n\n\t\tconst workers: OrchestrationChildWorkerStatus[] = [];\n\t\tfor (const worker of this._workers) {\n\t\t\tconst status = await worker.status();\n\t\t\tconst executing = status.currentAssignment?.missionId === request.childMissionId;\n\t\t\tworkers.push({\n\t\t\t\tworkerId: status.identity.workerId,\n\t\t\t\tdaemonState: status.daemonState,\n\t\t\t\tactivity: status.activity,\n\t\t\t\t...(executing ? { executingChildMissionId: request.childMissionId } : {}),\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\torchestrationId: request.orchestrationId,\n\t\t\tnodeId: request.nodeId,\n\t\t\tchildMissionId: request.childMissionId,\n\t\t\tchildSessionId: request.childSessionId,\n\t\t\tmissionState,\n\t\t\tterminal,\n\t\t\tsuccess,\n\t\t\t...(verificationStatus ? { verificationStatus } : {}),\n\t\t\t...(completionDecision ? { completionDecision } : {}),\n\t\t\t...(verificationSummary ? { verificationSummary } : {}),\n\t\t\t...(intent ? { intent } : {}),\n\t\t\tworkers,\n\t\t};\n\t}\n\n\tprivate decline(reason: string): OrchestrationChildExecutionReceipt {\n\t\treturn { accepted: false, authority: this.authority, reason };\n\t}\n\n\tprivate async _loadPlan(orchestrationId: string): Promise<OrchestrationPlanDocument | undefined> {\n\t\tconst loaded = await this._store.load(orchestrationId);\n\t\treturn loaded.status === \"ok\" ? loaded.document : undefined;\n\t}\n\n\tprivate async _loadMission(missionId: string): Promise<DurableMissionRecord | undefined> {\n\t\tconst loaded = await this._missions.load(missionId);\n\t\tif (loaded.status === \"corrupt\")\n\t\t\tthrow new Error(`CHILD_MISSION_CORRUPT: mission ${missionId} is corrupt: ${loaded.diagnostic}`);\n\t\treturn loaded.status === \"ok\" ? loaded.record : undefined;\n\t}\n}\n"]}