{"version":3,"file":"assignment-control-service.d.ts","sourceRoot":"","sources":["../../../src/core/assignment/assignment-control-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Assignment Control Service (2.11.0).\n *\n * The authoritative application/operator boundary over the durable mission ↔\n * executor assignment domain. It never schedules, never auto-selects executors,\n * and never starts mission work merely by creating an assignment.\n *\n *   - `assignMission`            designates a mission for an executor.\n *   - `reassignMission`          explicitly supersedes the current designation.\n *   - `releaseAssignment`        removes a designation without cancelling work.\n *   - `acceptAssignment`         a current runtime claims the assignment.\n *   - `beginAssignedExecution`   persists the EXECUTING intent + owner correlation.\n *   - `startAssignedMission`     drives the existing fenced child-resume path.\n *\n * Execution ownership (`ExecutionLease`/`fencingToken`) and executor runtime\n * incarnation (`runtimeInstanceId`/`runtimeEpoch`) remain separate authority\n * domains; the assignment only records logical designation and audit metadata.\n */\n\nimport {\n\ttype BuiltChildResume,\n\tbuildChildResumeExecutor,\n\tresolveChildSessionForResume,\n} from \"../durable-child-session/child-session-restore.js\";\nimport { ExecutorRegistryError } from \"../executor-registry/executor-registry-types.js\";\nimport type { ExecutorControlService, ExecutorDetail, ExecutorRuntimeProof } from \"../executor-registry/index.js\";\nimport {\n\tDurableMissionCoordinator,\n\ttype DurableMissionCoordinatorOptions,\n} from \"../mission-domain/durable-coordinator.js\";\nimport type { DurableMissionRecord, DurableMissionStore } from \"../mission-domain/durable-store.js\";\nimport { isExecutionLeaseActive, newExecutorOwnerId } from \"../mission-domain/execution-lease.js\";\nimport { isTerminalMissionState } from \"../mission-domain/mission-state.js\";\nimport type { ProcessMissionVerifier } from \"../mission-domain/process-mission-executor.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type { AssignmentStore } from \"./assignment-store.js\";\nimport { createAssignmentRecord } from \"./assignment-store.js\";\nimport {\n\ttype AcceptAssignmentOutcome,\n\ttype AssignabilityResult,\n\ttype AssignMissionInput,\n\ttype AssignMissionOutcome,\n\ttype AssignmentDetail,\n\tAssignmentError,\n\ttype AssignmentListOptions,\n\ttype AssignmentListResult,\n\ttype AssignmentRecord,\n\ttype AssignmentState,\n\ttype AssignmentSummary,\n\ttype BeginAssignedExecutionOutcome,\n\ttype CompleteAssignmentInput,\n\ttype CompleteAssignmentOutcome,\n\ttype ExecutionOwnerIdentity,\n\tisSafeAssignmentId,\n\ttype MissionRequirements,\n\tnewAssignmentId,\n\ttype ReassignMissionInput,\n\ttype StartAssignedMissionOutcome,\n} from \"./assignment-types.js\";\nimport { evaluateAssignability, mergeExecutorCapabilities } from \"./compatibility.js\";\n\n/** Maps a resume to the concrete child CLI launch (mirrors Mission Control). */\nexport type BuildAssignedResumeLaunch = (input: {\n\trequest: DurableMissionRecord[\"request\"];\n\tresumePrompt: string;\n\tchildSessionId: string;\n}) => {\n\tcommand: string;\n\targs: readonly string[];\n\tcwd: string;\n\tenv?: Record<string, string | undefined>;\n};\n\n/**\n * Builds the executor for a resolved durable child session. Defaults to the\n * local `ProcessMissionExecutor`; a remote worker injects a builder that\n * constructs a `RemoteMissionExecutor` bound to the same resolved session.\n */\nexport type BuildAssignedExecutor = (input: {\n\trecord: DurableMissionRecord;\n\tsessionManager: SessionManager;\n\tchildSessionId: string;\n\tassignmentId: string;\n}) => BuiltChildResume;\n\nexport interface AssignmentControlServiceOptions {\n\tstore: AssignmentStore;\n\tmissions: DurableMissionStore;\n\texecutors: ExecutorControlService;\n\tnow?: () => number;\n\tassignmentIdFactory?: () => string;\n\townerIdFactory?: () => string;\n\tsessionDir?: string;\n\tleaseDurationMs?: number;\n\theartbeatIntervalMs?: number;\n\trenewalSafetyMarginMs?: number;\n\tattemptIdFactory?: () => string;\n\tleaseIdFactory?: () => string;\n}\n\nexport class AssignmentControlService {\n\tprivate readonly _store: AssignmentStore;\n\tprivate readonly _missions: DurableMissionStore;\n\tprivate readonly _executors: ExecutorControlService;\n\tprivate readonly _now: () => number;\n\tprivate readonly _assignmentIdFactory: () => string;\n\tprivate readonly _ownerIdFactory: () => string;\n\tprivate readonly _sessionDir?: string;\n\tprivate readonly _coordinatorOptions: DurableMissionCoordinatorOptions;\n\n\tconstructor(options: AssignmentControlServiceOptions) {\n\t\tthis._store = options.store;\n\t\tthis._missions = options.missions;\n\t\tthis._executors = options.executors;\n\t\tthis._now = options.now ?? (() => Date.now());\n\t\tthis._assignmentIdFactory = options.assignmentIdFactory ?? (() => newAssignmentId());\n\t\tthis._ownerIdFactory = options.ownerIdFactory ?? (() => newExecutorOwnerId());\n\t\tthis._sessionDir = options.sessionDir;\n\t\tthis._coordinatorOptions = {\n\t\t\tnow: options.now,\n\t\t\tleaseDurationMs: options.leaseDurationMs,\n\t\t\theartbeatIntervalMs: options.heartbeatIntervalMs,\n\t\t\trenewalSafetyMarginMs: options.renewalSafetyMarginMs,\n\t\t\tattemptIdFactory: options.attemptIdFactory,\n\t\t\tleaseIdFactory: options.leaseIdFactory,\n\t\t};\n\t}\n\n\tget store(): AssignmentStore {\n\t\treturn this._store;\n\t}\n\n\t// =========================================================================\n\t// Read model\n\t// =========================================================================\n\n\tasync listAssignments(options: AssignmentListOptions = {}): Promise<AssignmentListResult> {\n\t\tconst { records, corrupt } = await this._store.listRecords();\n\n\t\tlet entries = records.map((record) => this._toSummary(record));\n\t\tconst filter = options.filter;\n\t\tif (filter) {\n\t\t\tentries = entries.filter((entry) => {\n\t\t\t\tif (filter.missionId !== undefined && entry.missionId !== filter.missionId) return false;\n\t\t\t\tif (filter.executorId !== undefined && entry.executorId !== filter.executorId) return false;\n\t\t\t\tif (filter.state !== undefined && entry.state !== filter.state) return false;\n\t\t\t\tif (filter.current !== undefined && entry.current !== filter.current) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\t\t}\n\n\t\tconst sort = options.sort ?? \"createdAtMs\";\n\t\tconst direction = options.direction ?? \"asc\";\n\t\tentries.sort((a, b) => {\n\t\t\tlet cmp: number;\n\t\t\tif (sort === \"assignmentId\")\n\t\t\t\tcmp = a.assignmentId < b.assignmentId ? -1 : a.assignmentId > b.assignmentId ? 1 : 0;\n\t\t\telse if (sort === \"createdAtMs\") {\n\t\t\t\tcmp = a.createdAtMs - b.createdAtMs || (a.assignmentId < b.assignmentId ? -1 : 1);\n\t\t\t} else cmp = a.updatedAtMs - b.updatedAtMs || (a.assignmentId < b.assignmentId ? -1 : 1);\n\t\t\treturn direction === \"desc\" ? -cmp : cmp;\n\t\t});\n\n\t\tconst offset = options.offset ?? 0;\n\t\tif (offset > 0) entries = entries.slice(offset);\n\t\tif (options.limit !== undefined) entries = entries.slice(0, options.limit);\n\n\t\treturn { entries, corrupt };\n\t}\n\n\tasync getAssignment(assignmentId: string): Promise<AssignmentDetail> {\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\treturn this._toDetail(record);\n\t}\n\n\tasync getCurrentForMission(missionId: string): Promise<AssignmentDetail | undefined> {\n\t\tconst { records } = await this._store.listRecords();\n\t\tconst current = records.filter((record) => record.missionId === missionId && record.current);\n\t\tif (current.length > 1) {\n\t\t\tthrow new AssignmentError(\"ASSIGNMENT_CORRUPT\", `Mission ${missionId} has multiple current assignments`, {\n\t\t\t\tmissionId,\n\t\t\t\tassignmentIds: current.map((record) => record.assignmentId),\n\t\t\t});\n\t\t}\n\t\treturn current.length === 1 ? this._toDetail(current[0]) : undefined;\n\t}\n\n\tasync listForMission(missionId: string): Promise<AssignmentSummary[]> {\n\t\tconst { records } = await this._store.listRecords();\n\t\treturn records\n\t\t\t.filter((record) => record.missionId === missionId)\n\t\t\t.sort((a, b) => a.createdAtMs - b.createdAtMs || (a.assignmentId < b.assignmentId ? -1 : 1))\n\t\t\t.map((record) => this._toSummary(record));\n\t}\n\n\tasync listForExecutor(executorId: string): Promise<AssignmentSummary[]> {\n\t\tconst { records } = await this._store.listRecords();\n\t\treturn records\n\t\t\t.filter((record) => record.executorId === executorId)\n\t\t\t.sort((a, b) => a.createdAtMs - b.createdAtMs || (a.assignmentId < b.assignmentId ? -1 : 1))\n\t\t\t.map((record) => this._toSummary(record));\n\t}\n\n\t/** Current designation per mission (for Mission Control / Executor Control views). */\n\tasync listCurrentAssignments(): Promise<Map<string, AssignmentRecord>> {\n\t\tconst { records } = await this._store.listRecords();\n\t\tconst map = new Map<string, AssignmentRecord>();\n\t\tfor (const record of records) {\n\t\t\tif (!record.current) continue;\n\t\t\tmap.set(record.missionId, record);\n\t\t}\n\t\treturn map;\n\t}\n\n\t/** Read-only deterministic compatibility/assignability for a mission↔executor pair. */\n\tasync evaluateCompatibility(\n\t\tmissionId: string,\n\t\texecutorId: string,\n\t\trequirements?: MissionRequirements,\n\t): Promise<{\n\t\tmissionId: string;\n\t\texecutorId: string;\n\t\trequirements: MissionRequirements | undefined;\n\t\tassignability: AssignabilityResult;\n\t}> {\n\t\tawait this._requireMission(missionId);\n\t\tconst detail = await this._executorDetail(executorId);\n\t\tlet effectiveRequirements = requirements;\n\t\tif (effectiveRequirements === undefined) {\n\t\t\tconst current = await this.getCurrentForMission(missionId);\n\t\t\teffectiveRequirements = current?.requirementsSnapshot;\n\t\t}\n\t\tconst assignability = this._evaluateAssignability(detail, effectiveRequirements);\n\t\treturn { missionId, executorId, requirements: effectiveRequirements, assignability };\n\t}\n\n\t// =========================================================================\n\t// Explicit designation mutations\n\t// =========================================================================\n\n\tasync assignMission(input: AssignMissionInput): Promise<AssignMissionOutcome> {\n\t\tconst mission = await this._requireMission(input.missionId);\n\t\tif (isTerminalMissionState(mission.state)) {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"MISSION_NOT_ASSIGNABLE\",\n\t\t\t\t`Mission ${input.missionId} is terminal (${mission.state})`,\n\t\t\t\t{\n\t\t\t\t\tmissionId: input.missionId,\n\t\t\t\t\tstate: mission.state,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst assignmentId = input.assignmentId ?? this._assignmentIdFactory();\n\t\tif (!isSafeAssignmentId(assignmentId)) {\n\t\t\tthrow new AssignmentError(\"INVALID_ASSIGNMENT_ID\", `Unsafe assignment id: ${assignmentId}`, { assignmentId });\n\t\t}\n\n\t\tconst detail = await this._executorDetail(input.executorId);\n\t\tconst assignability = this._evaluateAssignability(detail, input.requirements);\n\t\tthis._assertAssignable(input.executorId, assignability);\n\n\t\tconst now = this._now();\n\t\tconst record = createAssignmentRecord({\n\t\t\tassignmentId,\n\t\t\tmissionId: input.missionId,\n\t\t\texecutorId: input.executorId,\n\t\t\tnow,\n\t\t\tassignedBy: input.assignedBy,\n\t\t\trequirementsSnapshot: input.requirements,\n\t\t\tcompatibilitySnapshot: assignability.compatibility,\n\t\t\texecutionMode: input.executionMode,\n\t\t\tremoteTargetId: input.remoteTargetId,\n\t\t\texecutorRuntimeAtAssignment: {\n\t\t\t\truntimeInstanceId: detail.runtime?.runtimeInstanceId,\n\t\t\t\truntimeEpoch: detail.runtimeEpoch,\n\t\t\t\tstatus: detail.status,\n\t\t\t\tobservedAtMs: now,\n\t\t\t},\n\t\t});\n\n\t\tconst result = await this._store.mutate<AssignMissionOutcome>(input.missionId, (index) => {\n\t\t\tif (index.current) {\n\t\t\t\tthrow new AssignmentError(\n\t\t\t\t\t\"MISSION_ALREADY_ASSIGNED\",\n\t\t\t\t\t`Mission ${input.missionId} already has a current assignment`,\n\t\t\t\t\t{\n\t\t\t\t\t\tmissionId: input.missionId,\n\t\t\t\t\t\tcurrentAssignmentId: index.current.assignmentId,\n\t\t\t\t\t\tcurrentExecutorId: index.current.executorId,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: [...index.records, record],\n\t\t\t\tvalue: {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: input.missionId,\n\t\t\t\t\texecutorId: input.executorId,\n\t\t\t\t\tstate: \"ASSIGNED\" as AssignmentState,\n\t\t\t\t\trecord,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, input.missionId);\n\t}\n\n\tasync reassignMission(\n\t\tmissionId: string,\n\t\tnewExecutorId: string,\n\t\tinput: Omit<ReassignMissionInput, \"missionId\" | \"executorId\"> = {},\n\t): Promise<AssignMissionOutcome> {\n\t\tconst mission = await this._requireMission(missionId);\n\t\tif (isTerminalMissionState(mission.state)) {\n\t\t\tthrow new AssignmentError(\"MISSION_NOT_ASSIGNABLE\", `Mission ${missionId} is terminal (${mission.state})`, {\n\t\t\t\tmissionId,\n\t\t\t\tstate: mission.state,\n\t\t\t});\n\t\t}\n\t\tif (mission.lease && isExecutionLeaseActive(mission.lease, this._now())) {\n\t\t\tthrow new AssignmentError(\"MISSION_ACTIVE\", `Mission ${missionId} has an active execution owner`, {\n\t\t\t\tmissionId,\n\t\t\t\townerId: mission.lease.ownerId,\n\t\t\t});\n\t\t}\n\n\t\tconst detail = await this._executorDetail(newExecutorId);\n\t\tconst assignability = this._evaluateAssignability(detail, input.requirements);\n\t\tthis._assertAssignable(newExecutorId, assignability);\n\n\t\tconst assignmentId = input.assignmentId ?? this._assignmentIdFactory();\n\t\tif (!isSafeAssignmentId(assignmentId)) {\n\t\t\tthrow new AssignmentError(\"INVALID_ASSIGNMENT_ID\", `Unsafe assignment id: ${assignmentId}`, { assignmentId });\n\t\t}\n\n\t\tconst now = this._now();\n\t\tconst next = createAssignmentRecord({\n\t\t\tassignmentId,\n\t\t\tmissionId,\n\t\t\texecutorId: newExecutorId,\n\t\t\tnow,\n\t\t\tassignedBy: input.assignedBy,\n\t\t\trequirementsSnapshot: input.requirements,\n\t\t\tcompatibilitySnapshot: assignability.compatibility,\n\t\t\texecutionMode: input.executionMode,\n\t\t\tremoteTargetId: input.remoteTargetId,\n\t\t\texecutorRuntimeAtAssignment: {\n\t\t\t\truntimeInstanceId: detail.runtime?.runtimeInstanceId,\n\t\t\t\truntimeEpoch: detail.runtimeEpoch,\n\t\t\t\tstatus: detail.status,\n\t\t\t\tobservedAtMs: now,\n\t\t\t},\n\t\t});\n\t\tnext.supersedesAssignmentId = undefined;\n\n\t\tconst result = await this._store.mutate<AssignMissionOutcome>(missionId, (index) => {\n\t\t\tconst current = index.current;\n\t\t\tif (!current) {\n\t\t\t\tthrow new AssignmentError(\n\t\t\t\t\t\"ASSIGNMENT_NOT_CURRENT\",\n\t\t\t\t\t`Mission ${missionId} has no current assignment to reassign`,\n\t\t\t\t\t{ missionId },\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (current.state === \"EXECUTING\") {\n\t\t\t\tthrow new AssignmentError(\"MISSION_ACTIVE\", `Mission ${missionId} is executing under its assignment`, {\n\t\t\t\t\tmissionId,\n\t\t\t\t\tassignmentId: current.assignmentId,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (current.executorId === newExecutorId) {\n\t\t\t\tthrow new AssignmentError(\n\t\t\t\t\t\"MISSION_ALREADY_ASSIGNED\",\n\t\t\t\t\t`Mission ${missionId} is already assigned to executor ${newExecutorId}`,\n\t\t\t\t\t{ missionId, assignmentId: current.assignmentId, executorId: newExecutorId },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst superseded: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"SUPERSEDED\",\n\t\t\t\tcurrent: false,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\tsupersededByAssignmentId: assignmentId,\n\t\t\t\treason: `superseded by reassignment to ${newExecutorId}`,\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\tconst withSupersedes: AssignmentRecord = { ...next, supersedesAssignmentId: current.assignmentId };\n\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: [\n\t\t\t\t\t...index.records.map((r) => (r.assignmentId === current.assignmentId ? superseded : r)),\n\t\t\t\t\twithSupersedes,\n\t\t\t\t],\n\t\t\t\tvalue: {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId,\n\t\t\t\t\texecutorId: newExecutorId,\n\t\t\t\t\tstate: \"ASSIGNED\" as AssignmentState,\n\t\t\t\t\trecord: withSupersedes,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, missionId);\n\t}\n\n\tasync releaseAssignment(assignmentId: string): Promise<AssignmentRecord> {\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\tconst mission = await this._requireMission(record.missionId);\n\t\tif (mission.lease && isExecutionLeaseActive(mission.lease, this._now())) {\n\t\t\tthrow new AssignmentError(\"MISSION_ACTIVE\", `Mission ${record.missionId} has an active execution owner`, {\n\t\t\t\tmissionId: record.missionId,\n\t\t\t\tassignmentId,\n\t\t\t\townerId: mission.lease.ownerId,\n\t\t\t});\n\t\t}\n\n\t\tconst now = this._now();\n\t\tconst result = await this._store.mutate<AssignmentRecord>(record.missionId, (index) => {\n\t\t\tif (index.current?.assignmentId !== assignmentId) {\n\t\t\t\tthrow new AssignmentError(\n\t\t\t\t\t\"ASSIGNMENT_NOT_CURRENT\",\n\t\t\t\t\t`Assignment ${assignmentId} is not the current assignment for mission ${record.missionId}`,\n\t\t\t\t\t{ assignmentId, missionId: record.missionId },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst current = index.current;\n\t\t\tif (current.state === \"EXECUTING\") {\n\t\t\t\tthrow new AssignmentError(\"MISSION_ACTIVE\", `Assignment ${assignmentId} is executing`, {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: record.missionId,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst released: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"RELEASED\",\n\t\t\t\tcurrent: false,\n\t\t\t\treleasedAtMs: now,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\treason: \"assignment released\",\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: index.records.map((r) => (r.assignmentId === assignmentId ? released : r)),\n\t\t\t\tvalue: released,\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, record.missionId);\n\t}\n\n\t// =========================================================================\n\t// Acceptance + execution boundary\n\t// =========================================================================\n\n\tasync acceptAssignment(assignmentId: string, proof: ExecutorRuntimeProof): Promise<AcceptAssignmentOutcome> {\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\tif (record.state !== \"ASSIGNED\") {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"ASSIGNMENT_NOT_CURRENT\",\n\t\t\t\t`Assignment ${assignmentId} is ${record.state}; only ASSIGNED can be accepted`,\n\t\t\t\t{ assignmentId, state: record.state },\n\t\t\t);\n\t\t}\n\t\tawait this._assertCurrentRuntime(record, proof);\n\t\tawait this._requireAcceptableMission(record.missionId);\n\n\t\tconst now = this._now();\n\t\tconst result = await this._store.mutate<AcceptAssignmentOutcome>(record.missionId, (index) => {\n\t\t\tif (index.current?.assignmentId !== assignmentId) {\n\t\t\t\tthrow new AssignmentError(\"ASSIGNMENT_NOT_CURRENT\", `Assignment ${assignmentId} is no longer current`, {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: record.missionId,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst current = index.current;\n\t\t\tconst accepted: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"ACCEPTED\",\n\t\t\t\tacceptedAtMs: now,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\treason: `accepted by runtime ${proof.runtimeInstanceId} (epoch ${proof.runtimeEpoch})`,\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: index.records.map((r) => (r.assignmentId === assignmentId ? accepted : r)),\n\t\t\t\tvalue: { assignmentId, state: \"ACCEPTED\" as AssignmentState, record: accepted },\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, record.missionId);\n\t}\n\n\tasync beginAssignedExecution(\n\t\tassignmentId: string,\n\t\tproof: ExecutorRuntimeProof,\n\t): Promise<BeginAssignedExecutionOutcome> {\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\tif (record.state !== \"ASSIGNED\" && record.state !== \"ACCEPTED\") {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"ASSIGNMENT_NOT_CURRENT\",\n\t\t\t\t`Assignment ${assignmentId} is ${record.state}; cannot begin execution`,\n\t\t\t\t{ assignmentId, state: record.state },\n\t\t\t);\n\t\t}\n\t\tawait this._assertCurrentRuntime(record, proof);\n\t\tawait this._requireExecutableMission(record.missionId);\n\n\t\tconst detail = await this._executorDetail(record.executorId);\n\t\tconst assignability = this._evaluateAssignability(detail, record.requirementsSnapshot);\n\t\tthis._assertAssignable(record.executorId, assignability);\n\n\t\tconst ownerId = this._ownerIdFactory();\n\t\tconst executionOwnerIdentity: ExecutionOwnerIdentity = {\n\t\t\texecutorId: record.executorId,\n\t\t\truntimeInstanceId: proof.runtimeInstanceId,\n\t\t\truntimeEpoch: proof.runtimeEpoch,\n\t\t\townerId,\n\t\t};\n\t\tconst now = this._now();\n\n\t\tconst result = await this._store.mutate<BeginAssignedExecutionOutcome>(record.missionId, (index) => {\n\t\t\tif (index.current?.assignmentId !== assignmentId) {\n\t\t\t\tthrow new AssignmentError(\"ASSIGNMENT_NOT_CURRENT\", `Assignment ${assignmentId} is no longer current`, {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: record.missionId,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst current = index.current;\n\t\t\tconst executing: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"EXECUTING\",\n\t\t\t\texecutionStartedAtMs: now,\n\t\t\t\texecutionOwnerIdentity,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\treason: `execution started by runtime ${proof.runtimeInstanceId} (epoch ${proof.runtimeEpoch})`,\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: index.records.map((r) => (r.assignmentId === assignmentId ? executing : r)),\n\t\t\t\tvalue: {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: record.missionId,\n\t\t\t\t\texecutorId: record.executorId,\n\t\t\t\t\tstate: \"EXECUTING\" as AssignmentState,\n\t\t\t\t\texecutionOwnerIdentity,\n\t\t\t\t\trecord: executing,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, record.missionId);\n\t}\n\n\tasync startAssignedMission(\n\t\tassignmentId: string,\n\t\tproof: ExecutorRuntimeProof,\n\t\toptions: {\n\t\t\tbuildResumeLaunch: BuildAssignedResumeLaunch;\n\t\t\tsignal?: AbortSignal;\n\t\t\tverifier?: ProcessMissionVerifier;\n\t\t\t/** Optional executor builder (remote); defaults to local ProcessMissionExecutor. */\n\t\t\tbuildExecutor?: BuildAssignedExecutor;\n\t\t},\n\t): Promise<StartAssignedMissionOutcome> {\n\t\t// Preflight without mutating: resolve the durable child session BEFORE the\n\t\t// assignment is marked EXECUTING, so a non-child/unresolvable mission is\n\t\t// surfaced structurally instead of leaving a stale EXECUTING assignment.\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\tawait this._assertCurrentRuntime(record, proof);\n\t\tawait this._requireExecutableMission(record.missionId);\n\t\tconst resolved = await resolveChildSessionForResume({\n\t\t\tstore: this._missions,\n\t\t\tmissionId: record.missionId,\n\t\t\tsessionDir: this._sessionDir,\n\t\t});\n\n\t\tconst built = options.buildExecutor\n\t\t\t? options.buildExecutor({\n\t\t\t\t\trecord: resolved.record,\n\t\t\t\t\tsessionManager: resolved.sessionManager,\n\t\t\t\t\tchildSessionId: resolved.childSessionId,\n\t\t\t\t\tassignmentId,\n\t\t\t\t})\n\t\t\t: buildChildResumeExecutor({\n\t\t\t\t\trecord: resolved.record,\n\t\t\t\t\tsessionManager: resolved.sessionManager,\n\t\t\t\t\tchildSessionId: resolved.childSessionId,\n\t\t\t\t\tbuildResumeLaunch: options.buildResumeLaunch,\n\t\t\t\t\texecutorId: record.executorId,\n\t\t\t\t\tverifier: options.verifier,\n\t\t\t\t});\n\n\t\tconst begun = await this.beginAssignedExecution(assignmentId, proof);\n\t\tconst coordinator = new DurableMissionCoordinator(this._missions, built.executor, {\n\t\t\t...this._coordinatorOptions,\n\t\t\townerId: begun.executionOwnerIdentity.ownerId,\n\t\t\tassignmentId,\n\t\t});\n\n\t\ttry {\n\t\t\tconst terminal = await coordinator.resume(begun.missionId, { signal: options.signal });\n\t\t\tconst lastAttempt = terminal.attempts[terminal.attempts.length - 1];\n\t\t\tconst completed = await this.completeAssignment(assignmentId, {\n\t\t\t\tresultState: terminal.state,\n\t\t\t\tattemptId: lastAttempt?.attemptId,\n\t\t\t\texecutionId: terminal.resultExecutionId ?? lastAttempt?.executionId,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tassignmentId,\n\t\t\t\tmissionId: begun.missionId,\n\t\t\t\tassignmentState: completed.state,\n\t\t\t\tmissionState: terminal.state,\n\t\t\t\tattemptId: completed.record.consumedByAttemptId,\n\t\t\t\texecutionId: completed.record.consumedByExecutionId,\n\t\t\t\tsuccess: terminal.state === \"SUCCEEDED\",\n\t\t\t\tassignment: completed.record,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tawait this._failAssignment(assignmentId, error).catch(() => undefined);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync completeAssignment(assignmentId: string, input: CompleteAssignmentInput): Promise<CompleteAssignmentOutcome> {\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\tconst now = this._now();\n\n\t\tconst result = await this._store.mutate<CompleteAssignmentOutcome>(record.missionId, (index) => {\n\t\t\tif (index.current?.assignmentId !== assignmentId) {\n\t\t\t\tthrow new AssignmentError(\"ASSIGNMENT_NOT_CURRENT\", `Assignment ${assignmentId} is no longer current`, {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: record.missionId,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst current = index.current;\n\t\t\tif (current.state !== \"EXECUTING\") {\n\t\t\t\tthrow new AssignmentError(\n\t\t\t\t\t\"ASSIGNMENT_NOT_CURRENT\",\n\t\t\t\t\t`Assignment ${assignmentId} is ${current.state}; only EXECUTING can be completed`,\n\t\t\t\t\t{ assignmentId, state: current.state },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst completed: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"COMPLETED\",\n\t\t\t\tcurrent: false,\n\t\t\t\tcompletedAtMs: now,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\tterminalMissionState: input.resultState,\n\t\t\t\tconsumedByAttemptId: input.attemptId ?? current.consumedByAttemptId,\n\t\t\t\tconsumedByExecutionId: input.executionId ?? current.consumedByExecutionId,\n\t\t\t\treason: input.reason ?? `mission terminal ${input.resultState}`,\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: index.records.map((r) => (r.assignmentId === assignmentId ? completed : r)),\n\t\t\t\tvalue: {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tstate: \"COMPLETED\" as AssignmentState,\n\t\t\t\t\tterminalMissionState: input.resultState,\n\t\t\t\t\trecord: completed,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, record.missionId);\n\t}\n\n\t/**\n\t * Honest interruption of a previously-EXECUTING assignment after a worker\n\t * restart reconciliation. The mission (owned by the mission domain) is\n\t * reconciled separately to INTERRUPTED; this marks the assignment's\n\t * execution attempt FAILED so it is never silently re-executed and never\n\t * reported as a fabricated completion.\n\t */\n\tasync interruptExecution(assignmentId: string, reason: string): Promise<AssignmentRecord> {\n\t\tconst record = await this._requireAssignment(assignmentId);\n\t\tconst now = this._now();\n\n\t\tconst result = await this._store.mutate<AssignmentRecord>(record.missionId, (index) => {\n\t\t\tif (index.current?.assignmentId !== assignmentId) {\n\t\t\t\tthrow new AssignmentError(\"ASSIGNMENT_NOT_CURRENT\", `Assignment ${assignmentId} is no longer current`, {\n\t\t\t\t\tassignmentId,\n\t\t\t\t\tmissionId: record.missionId,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst current = index.current;\n\t\t\tif (current.state !== \"EXECUTING\") {\n\t\t\t\tthrow new AssignmentError(\n\t\t\t\t\t\"ASSIGNMENT_NOT_CURRENT\",\n\t\t\t\t\t`Assignment ${assignmentId} is ${current.state}; only EXECUTING can be interrupted`,\n\t\t\t\t\t{ assignmentId, state: current.state },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"FAILED\",\n\t\t\t\tcurrent: false,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\treason,\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: index.records.map((r) => (r.assignmentId === assignmentId ? failed : r)),\n\t\t\t\tvalue: failed,\n\t\t\t};\n\t\t});\n\n\t\treturn this._unwrap(result, record.missionId);\n\t}\n\n\t// =========================================================================\n\t// Internals\n\t// =========================================================================\n\n\tprivate _toSummary(record: AssignmentRecord): AssignmentSummary {\n\t\treturn {\n\t\t\tassignmentId: record.assignmentId,\n\t\t\tmissionId: record.missionId,\n\t\t\texecutorId: record.executorId,\n\t\t\tstate: record.state,\n\t\t\tcurrent: record.current,\n\t\t\tcreatedAtMs: record.createdAtMs,\n\t\t\tupdatedAtMs: record.updatedAtMs,\n\t\t\tassignedBy: record.assignedBy,\n\t\t\tacceptedAtMs: record.acceptedAtMs,\n\t\t\texecutionStartedAtMs: record.executionStartedAtMs,\n\t\t\tcompletedAtMs: record.completedAtMs,\n\t\t\treleasedAtMs: record.releasedAtMs,\n\t\t\tsupersededByAssignmentId: record.supersededByAssignmentId,\n\t\t\tconsumedByAttemptId: record.consumedByAttemptId,\n\t\t\tconsumedByExecutionId: record.consumedByExecutionId,\n\t\t\tterminalMissionState: record.terminalMissionState,\n\t\t\treason: record.reason,\n\t\t\texecutionMode: record.executionMode,\n\t\t\tremoteTargetId: record.remoteTargetId,\n\t\t};\n\t}\n\n\tprivate _toDetail(record: AssignmentRecord): AssignmentDetail {\n\t\treturn {\n\t\t\t...this._toSummary(record),\n\t\t\trequirementsSnapshot: record.requirementsSnapshot,\n\t\t\tcompatibilitySnapshot: record.compatibilitySnapshot,\n\t\t\texecutorRuntimeAtAssignment: record.executorRuntimeAtAssignment,\n\t\t\texecutionOwnerIdentity: record.executionOwnerIdentity,\n\t\t};\n\t}\n\n\tprivate _unwrap<T>(\n\t\tresult: { status: \"ok\"; value: T } | { status: \"corrupt\"; diagnostic: string },\n\t\tmissionId: string,\n\t): T {\n\t\tif (result.status === \"corrupt\") {\n\t\t\tthrow new AssignmentError(\"ASSIGNMENT_CORRUPT\", `Assignment store is corrupt for mission ${missionId}`, {\n\t\t\t\tmissionId,\n\t\t\t\tdiagnostic: result.diagnostic,\n\t\t\t});\n\t\t}\n\t\treturn result.value;\n\t}\n\n\tprivate async _requireAssignment(assignmentId: string): Promise<AssignmentRecord> {\n\t\tif (!isSafeAssignmentId(assignmentId)) {\n\t\t\tthrow new AssignmentError(\"INVALID_ASSIGNMENT_ID\", `Unsafe assignment id: ${assignmentId}`, { assignmentId });\n\t\t}\n\t\tconst loaded = await this._store.load(assignmentId);\n\t\tif (loaded.status === \"missing\") {\n\t\t\tthrow new AssignmentError(\"ASSIGNMENT_NOT_FOUND\", `Assignment not found: ${assignmentId}`, { assignmentId });\n\t\t}\n\t\tif (loaded.status === \"corrupt\") {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"ASSIGNMENT_CORRUPT\",\n\t\t\t\t`Assignment ${assignmentId} is corrupt: ${loaded.diagnostic}`,\n\t\t\t\t{ assignmentId, diagnostic: loaded.diagnostic },\n\t\t\t);\n\t\t}\n\t\treturn loaded.record;\n\t}\n\n\tprivate async _requireMission(missionId: string): Promise<DurableMissionRecord> {\n\t\tconst loaded = await this._missions.load(missionId);\n\t\tif (loaded.status === \"missing\") {\n\t\t\tthrow new AssignmentError(\"MISSION_NOT_FOUND\", `Mission not found: ${missionId}`, { missionId });\n\t\t}\n\t\tif (loaded.status === \"corrupt\") {\n\t\t\tthrow new AssignmentError(\"MISSION_NOT_ASSIGNABLE\", `Mission ${missionId} is corrupt: ${loaded.diagnostic}`, {\n\t\t\t\tmissionId,\n\t\t\t\tdiagnostic: loaded.diagnostic,\n\t\t\t});\n\t\t}\n\t\treturn loaded.record;\n\t}\n\n\tprivate async _executorDetail(executorId: string): Promise<ExecutorDetail> {\n\t\ttry {\n\t\t\treturn await this._executors.getExecutor(executorId);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ExecutorRegistryError && error.code === \"EXECUTOR_NOT_FOUND\") {\n\t\t\t\tthrow new AssignmentError(\"EXECUTOR_NOT_FOUND\", `Executor not found: ${executorId}`, { executorId });\n\t\t\t}\n\t\t\tif (error instanceof ExecutorRegistryError && error.code === \"EXECUTOR_CORRUPT\") {\n\t\t\t\tthrow new AssignmentError(\"EXECUTOR_NOT_ASSIGNABLE\", `Executor ${executorId} is corrupt`, { executorId });\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate _evaluateAssignability(\n\t\tdetail: ExecutorDetail,\n\t\trequirements: MissionRequirements | undefined,\n\t): AssignabilityResult {\n\t\tconst capabilities = mergeExecutorCapabilities(\n\t\t\tdetail.configuredCapabilities,\n\t\t\tdetail.runtime?.advertisedCapabilities,\n\t\t);\n\t\treturn evaluateAssignability({\n\t\t\trequirements,\n\t\t\tcapabilities,\n\t\t\tstatus: detail.status,\n\t\t\tretired: detail.retired,\n\t\t});\n\t}\n\n\tprivate _assertAssignable(executorId: string, assignability: AssignabilityResult): void {\n\t\tif (assignability.assignable) return;\n\t\tif (!assignability.compatible) {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"EXECUTOR_INCOMPATIBLE\",\n\t\t\t\t`Executor ${executorId} is incompatible with mission requirements`,\n\t\t\t\t{ executorId, unsatisfied: assignability.compatibility.unsatisfied },\n\t\t\t);\n\t\t}\n\t\tif (assignability.status === \"RETIRED\") {\n\t\t\tthrow new AssignmentError(\"EXECUTOR_RETIRED\", `Executor ${executorId} is retired`, { executorId });\n\t\t}\n\t\tthrow new AssignmentError(\n\t\t\t\"EXECUTOR_OFFLINE\",\n\t\t\t`Executor ${executorId} is not presently assignable (${assignability.status})`,\n\t\t\t{ executorId, status: assignability.status },\n\t\t);\n\t}\n\n\tprivate async _assertCurrentRuntime(assignment: AssignmentRecord, proof: ExecutorRuntimeProof): Promise<void> {\n\t\tif (proof.executorId !== assignment.executorId) {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"ASSIGNMENT_RUNTIME_MISMATCH\",\n\t\t\t\t`Runtime proof executor ${proof.executorId} does not match assignment executor ${assignment.executorId}`,\n\t\t\t\t{\n\t\t\t\t\tassignmentId: assignment.assignmentId,\n\t\t\t\t\tproofExecutorId: proof.executorId,\n\t\t\t\t\texecutorId: assignment.executorId,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (typeof proof.runtimeInstanceId !== \"string\" || proof.runtimeInstanceId.length === 0) {\n\t\t\tthrow new AssignmentError(\"ASSIGNMENT_RUNTIME_MISMATCH\", \"runtimeInstanceId is required\", {\n\t\t\t\tassignmentId: assignment.assignmentId,\n\t\t\t});\n\t\t}\n\t\tif (!Number.isSafeInteger(proof.runtimeEpoch) || proof.runtimeEpoch < 1) {\n\t\t\tthrow new AssignmentError(\"ASSIGNMENT_RUNTIME_MISMATCH\", \"runtimeEpoch must be a positive integer\", {\n\t\t\t\tassignmentId: assignment.assignmentId,\n\t\t\t});\n\t\t}\n\n\t\tconst detail = await this._executorDetail(proof.executorId);\n\t\tif (detail.retired) {\n\t\t\tthrow new AssignmentError(\"EXECUTOR_RETIRED\", `Executor ${proof.executorId} is retired`, {\n\t\t\t\texecutorId: proof.executorId,\n\t\t\t});\n\t\t}\n\t\tif (!detail.runtime) {\n\t\t\tthrow new AssignmentError(\"EXECUTOR_OFFLINE\", `Executor ${proof.executorId} has no active runtime`, {\n\t\t\t\texecutorId: proof.executorId,\n\t\t\t});\n\t\t}\n\t\tif (detail.runtime.runtimeInstanceId !== proof.runtimeInstanceId || detail.runtimeEpoch !== proof.runtimeEpoch) {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"STALE_EXECUTOR_INSTANCE\",\n\t\t\t\t`Runtime proof is not authoritative for executor ${proof.executorId}`,\n\t\t\t\t{\n\t\t\t\t\texecutorId: proof.executorId,\n\t\t\t\t\tauthoritativeInstanceId: detail.runtime.runtimeInstanceId,\n\t\t\t\t\tauthoritativeEpoch: detail.runtimeEpoch,\n\t\t\t\t\tstaleInstanceId: proof.runtimeInstanceId,\n\t\t\t\t\tstaleEpoch: proof.runtimeEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (detail.status !== \"ONLINE\") {\n\t\t\tthrow new AssignmentError(\"EXECUTOR_OFFLINE\", `Executor ${proof.executorId} is not ONLINE`, {\n\t\t\t\texecutorId: proof.executorId,\n\t\t\t\tstatus: detail.status,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async _requireNonTerminalMission(missionId: string): Promise<DurableMissionRecord> {\n\t\tconst record = await this._requireMission(missionId);\n\t\tif (isTerminalMissionState(record.state)) {\n\t\t\tthrow new AssignmentError(\"MISSION_NOT_ASSIGNABLE\", `Mission ${missionId} is terminal (${record.state})`, {\n\t\t\t\tmissionId,\n\t\t\t\tstate: record.state,\n\t\t\t});\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate async _requireAcceptableMission(missionId: string): Promise<DurableMissionRecord> {\n\t\tconst record = await this._requireNonTerminalMission(missionId);\n\t\tif (record.lease && isExecutionLeaseActive(record.lease, this._now())) {\n\t\t\tthrow new AssignmentError(\"MISSION_ACTIVE\", `Mission ${missionId} has an active execution owner`, {\n\t\t\t\tmissionId,\n\t\t\t\townerId: record.lease.ownerId,\n\t\t\t});\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate async _requireExecutableMission(missionId: string): Promise<DurableMissionRecord> {\n\t\tconst record = await this._requireAcceptableMission(missionId);\n\t\tif (record.state !== \"CREATED\" && record.state !== \"INTERRUPTED\") {\n\t\t\tthrow new AssignmentError(\n\t\t\t\t\"MISSION_NOT_ASSIGNABLE\",\n\t\t\t\t`Mission ${missionId} is ${record.state}; reconcile first`,\n\t\t\t\t{\n\t\t\t\t\tmissionId,\n\t\t\t\t\tstate: record.state,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate async _failAssignment(assignmentId: string, error: unknown): Promise<void> {\n\t\tconst loaded = await this._store.load(assignmentId);\n\t\tif (loaded.status !== \"ok\") return;\n\t\tconst record = loaded.record;\n\t\tconst now = this._now();\n\t\tawait this._store.mutate<void>(record.missionId, (index) => {\n\t\t\tif (index.current?.assignmentId !== assignmentId) return { kind: \"noop\", value: undefined };\n\t\t\tconst current = index.current;\n\t\t\tif (current.state !== \"EXECUTING\") return { kind: \"noop\", value: undefined };\n\t\t\tconst failed: AssignmentRecord = {\n\t\t\t\t...current,\n\t\t\t\tstate: \"FAILED\",\n\t\t\t\tcurrent: false,\n\t\t\t\tupdatedAtMs: now,\n\t\t\t\treason: `assigned execution failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\trevision: current.revision + 1,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"write\",\n\t\t\t\trecords: index.records.map((r) => (r.assignmentId === assignmentId ? failed : r)),\n\t\t\t\tvalue: undefined,\n\t\t\t};\n\t\t});\n\t}\n}\n"]}