{"version":3,"file":"execution-state-machine.d.ts","sourceRoot":"","sources":["../../../src/core/long-horizon/execution-state-machine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mission Execution State Machine v1.\n *\n * Governs the lifecycle of one Mission Contract execution through\n * a deterministic, replayable state machine. Every transition is\n * caller-supplied (executionId, transitionId, expectedRevision) and\n * the machine derives the destination state internally.\n *\n * Trusted completion (APPROVE_COMPLETION) requires a genuine\n * contract-bound TrustedValidationContext. The generic CLI cannot\n * mint one and must reject APPROVE_COMPLETION atomically.\n *\n * Full requirement-evidence completion gate deferred to LH-5.\n */\n\nimport { evaluateCompletionReadiness, type ReadinessInput } from \"./adaptive/readiness.js\";\nimport { computeMissionContractDigest } from \"./contract-digest.js\";\nimport { _getBoundContractDigest, type TrustedValidationContext } from \"./trusted-context.js\";\nimport type { MissionContractV1 } from \"./types.js\";\n\n// =============================================================================\n// Schema constants\n// =============================================================================\n\nexport const MISSION_EXECUTION_RECORD_VERSION = 1 as const;\n\n// =============================================================================\n// Mission Execution State\n// =============================================================================\n\nexport type MissionExecutionState =\n\t| \"PLANNING\"\n\t| \"EXECUTION\"\n\t| \"VERIFICATION\"\n\t| \"COMPLETION_REVIEW\"\n\t| \"BLOCKED\"\n\t| \"COMPLETED\"\n\t| \"FAILED\"\n\t| \"CANCELLED\";\n\n/** States from which execution can resume after BLOCKED. */\nexport type ResumableMissionExecutionState = Extract<\n\tMissionExecutionState,\n\t\"PLANNING\" | \"EXECUTION\" | \"VERIFICATION\" | \"COMPLETION_REVIEW\"\n>;\n\n// =============================================================================\n// Terminal states\n// =============================================================================\n\nexport const TERMINAL_STATES: ReadonlySet<MissionExecutionState> = new Set([\"COMPLETED\", \"FAILED\", \"CANCELLED\"]);\n\nexport const RESUABLE_STATES: ReadonlySet<MissionExecutionState> = new Set([\n\t\"PLANNING\",\n\t\"EXECUTION\",\n\t\"VERIFICATION\",\n\t\"COMPLETION_REVIEW\",\n]);\n\n// =============================================================================\n// Transition Kinds\n// =============================================================================\n\nexport type MissionExecutionTransitionKind =\n\t| \"START_EXECUTION\"\n\t| \"REQUEST_VERIFICATION\"\n\t| \"RETURN_TO_EXECUTION\"\n\t| \"REQUEST_COMPLETION_REVIEW\"\n\t| \"RETURN_TO_VERIFICATION\"\n\t| \"APPROVE_COMPLETION\"\n\t| \"BLOCK\"\n\t| \"RESUME\"\n\t| \"FAIL\"\n\t| \"CANCEL\";\n\n// =============================================================================\n// Transition graph — source state → (kind → destination state)\n// =============================================================================\n\nconst TRANSITION_GRAPH: ReadonlyMap<\n\tMissionExecutionState,\n\tReadonlyMap<MissionExecutionTransitionKind, MissionExecutionState>\n> = new Map([\n\t[\n\t\t\"PLANNING\",\n\t\tnew Map([\n\t\t\t[\"START_EXECUTION\", \"EXECUTION\"],\n\t\t\t[\"BLOCK\", \"BLOCKED\"],\n\t\t\t[\"FAIL\", \"FAILED\"],\n\t\t\t[\"CANCEL\", \"CANCELLED\"],\n\t\t]),\n\t],\n\t[\n\t\t\"EXECUTION\",\n\t\tnew Map([\n\t\t\t[\"REQUEST_VERIFICATION\", \"VERIFICATION\"],\n\t\t\t[\"BLOCK\", \"BLOCKED\"],\n\t\t\t[\"FAIL\", \"FAILED\"],\n\t\t\t[\"CANCEL\", \"CANCELLED\"],\n\t\t]),\n\t],\n\t[\n\t\t\"VERIFICATION\",\n\t\tnew Map([\n\t\t\t[\"RETURN_TO_EXECUTION\", \"EXECUTION\"],\n\t\t\t[\"REQUEST_COMPLETION_REVIEW\", \"COMPLETION_REVIEW\"],\n\t\t\t[\"BLOCK\", \"BLOCKED\"],\n\t\t\t[\"FAIL\", \"FAILED\"],\n\t\t\t[\"CANCEL\", \"CANCELLED\"],\n\t\t]),\n\t],\n\t[\n\t\t\"COMPLETION_REVIEW\",\n\t\tnew Map([\n\t\t\t[\"RETURN_TO_EXECUTION\", \"EXECUTION\"],\n\t\t\t[\"RETURN_TO_VERIFICATION\", \"VERIFICATION\"],\n\t\t\t[\"APPROVE_COMPLETION\", \"COMPLETED\"],\n\t\t\t[\"BLOCK\", \"BLOCKED\"],\n\t\t\t[\"FAIL\", \"FAILED\"],\n\t\t\t[\"CANCEL\", \"CANCELLED\"],\n\t\t]),\n\t],\n\t[\n\t\t\"BLOCKED\",\n\t\tnew Map([\n\t\t\t[\"RESUME\", \"BLOCKED\"], // RESUME uses dynamic blockedFromState — validated separately\n\t\t\t[\"FAIL\", \"FAILED\"],\n\t\t\t[\"CANCEL\", \"CANCELLED\"],\n\t\t]),\n\t],\n\t[\"COMPLETED\", new Map()],\n\t[\"FAILED\", new Map()],\n\t[\"CANCELLED\", new Map()],\n]);\n\n// =============================================================================\n// Mission Execution Transition Record v1\n// =============================================================================\n\nexport interface MissionExecutionTransitionRecordV1 {\n\t/** Caller-supplied. Must be unique, non-empty, trimmed. */\n\ttransitionId: string;\n\t/** The kind of transition requested. */\n\tkind: MissionExecutionTransitionKind;\n\t/** The state before this transition was applied. */\n\tfromState: MissionExecutionState;\n\t/** The state after this transition was applied (derived by the machine). */\n\ttoState: MissionExecutionState;\n\t/** The revision before the transition. revisionAfter = revisionBefore + 1. */\n\trevisionBefore: number;\n\t/** The revision after the transition. */\n\trevisionAfter: number;\n}\n\n// =============================================================================\n// Mission Execution Record v1\n// =============================================================================\n\nexport interface MissionExecutionRecordV1 {\n\treadonly executionVersion: 1;\n\t/** Caller-supplied execution identifier. Must be non-empty, trimmed. */\n\treadonly executionId: string;\n\t/** The canonical SHA-256 digest of the bound Mission Contract. */\n\treadonly contractDigest: string;\n\t/** Monotonically increasing revision counter. Starts at 0. */\n\treadonly revision: number;\n\t/** Current execution state. */\n\treadonly state: MissionExecutionState;\n\t/**\n\t * The exact active state when BLOCKED was entered.\n\t * Present only when state === BLOCKED.\n\t */\n\treadonly blockedFromState?: ResumableMissionExecutionState;\n\t/** Append-only transition history. */\n\treadonly transitions: readonly MissionExecutionTransitionRecordV1[];\n}\n\n// =============================================================================\n// Known top-level keys for strict validation\n// =============================================================================\n\nexport const MISSION_EXECUTION_RECORD_KEYS: ReadonlySet<string> = new Set([\n\t\"executionVersion\",\n\t\"executionId\",\n\t\"contractDigest\",\n\t\"revision\",\n\t\"state\",\n\t\"blockedFromState\",\n\t\"transitions\",\n]);\n\nexport const MISSION_EXECUTION_TRANSITION_KEYS: ReadonlySet<string> = new Set([\n\t\"transitionId\",\n\t\"kind\",\n\t\"fromState\",\n\t\"toState\",\n\t\"revisionBefore\",\n\t\"revisionAfter\",\n]);\n\n// =============================================================================\n// Valid states and kinds for fast lookup\n// =============================================================================\n\nconst VALID_STATES: ReadonlySet<string> = new Set([\n\t\"PLANNING\",\n\t\"EXECUTION\",\n\t\"VERIFICATION\",\n\t\"COMPLETION_REVIEW\",\n\t\"BLOCKED\",\n\t\"COMPLETED\",\n\t\"FAILED\",\n\t\"CANCELLED\",\n]);\n\nconst VALID_KINDS: ReadonlySet<string> = new Set([\n\t\"START_EXECUTION\",\n\t\"REQUEST_VERIFICATION\",\n\t\"RETURN_TO_EXECUTION\",\n\t\"REQUEST_COMPLETION_REVIEW\",\n\t\"RETURN_TO_VERIFICATION\",\n\t\"APPROVE_COMPLETION\",\n\t\"BLOCK\",\n\t\"RESUME\",\n\t\"FAIL\",\n\t\"CANCEL\",\n]);\n\nconst RESUABLE_STATES_SET: ReadonlySet<string> = new Set([\n\t\"PLANNING\",\n\t\"EXECUTION\",\n\t\"VERIFICATION\",\n\t\"COMPLETION_REVIEW\",\n]);\n\n// =============================================================================\n// Transition Request v1\n// =============================================================================\n\nexport interface MissionExecutionTransitionRequestV1 {\n\t/** Caller-supplied. Must be unique, non-empty, trimmed. */\n\ttransitionId: string;\n\t/** The expected current revision. Rejected if mismatch. */\n\texpectedRevision: number;\n\t/** The transition kind. */\n\tkind: MissionExecutionTransitionKind;\n}\n\n// =============================================================================\n// Validation Result\n// =============================================================================\n\nexport type MissionExecutionValidationResult =\n\t| { readonly valid: true }\n\t| { readonly valid: false; readonly error: string };\n\nexport type MissionExecutionInspectionResult =\n\t| {\n\t\t\treadonly valid: true;\n\t\t\treadonly executionId: string;\n\t\t\treadonly contractDigest: string;\n\t\t\treadonly state: MissionExecutionState;\n\t\t\treadonly revision: number;\n\t\t\treadonly transitionCount: number;\n\t\t\treadonly blockedFromState?: ResumableMissionExecutionState;\n\t\t\treadonly completionApproved: \"unavailable\";\n\t  }\n\t| {\n\t\t\treadonly valid: false;\n\t\t\treadonly error: string;\n\t  };\n\nexport type MissionExecutionTransitionResult =\n\t| { readonly ok: true; readonly record: MissionExecutionRecordV1 }\n\t| { readonly ok: false; readonly error: string; readonly code: MissionExecutionErrorCode };\n\nexport type MissionExecutionErrorCode =\n\t| \"INVALID_EXECUTION_RECORD\"\n\t| \"INVALID_TRANSITION\"\n\t| \"STALE_REVISION\"\n\t| \"DUPLICATE_TRANSITION_ID\"\n\t| \"TERMINAL_STATE\"\n\t| \"ILLEGAL_TRANSITION\"\n\t| \"SELF_TRANSITION\"\n\t| \"BLOCK_WHILE_BLOCKED\"\n\t| \"RESUME_WHILE_NOT_BLOCKED\"\n\t| \"RESUME_STATE_NOT_RESUMABLE\"\n\t| \"TRUSTED_VALIDATION_CONTEXT_REQUIRED\"\n\t| \"EXECUTION_COMPLETION_CAPABILITY_REQUIRED\"\n\t| \"READINESS_GATE_BLOCKED\"\n\t| \"CONTRACT_DIGEST_MISMATCH\"\n\t| \"UNKNOWN_SEMANTIC_FIELD\"\n\t| \"INTERNAL_ERROR\";\n\n// =============================================================================\n// Execution completion capability\n// =============================================================================\n\nexport const EXECUTION_COMPLETION_CAPABILITY = \"execution:complete\" as const;\n\n// =============================================================================\n// Deterministic validation of a raw execution record\n// =============================================================================\n\n/**\n * Validate a MissionExecutionRecordV1 against its bound Mission Contract.\n * Performs deep structural validation, unknown-field rejection, and\n * full transition-history replay.\n */\nexport function validateMissionExecutionRecord(\n\tcontract: MissionContractV1,\n\trecord: unknown,\n): MissionExecutionValidationResult {\n\tif (!isRecord(record)) {\n\t\treturn { valid: false, error: \"Execution record must be a non-null object\" };\n\t}\n\n\t// Phase 1: Top-level structural validation\n\tconst structErr = validateExecutionRecordStructure(record);\n\tif (structErr) return { valid: false, error: structErr };\n\n\tconst rec = record as Record<string, unknown>;\n\n\t// Phase 2: Contract digest binding\n\tconst contractDigest = computeMissionContractDigest(contract);\n\tif (rec.contractDigest !== contractDigest) {\n\t\treturn { valid: false, error: \"CONTRACT_DIGEST_MISMATCH: record contractDigest does not match contract\" };\n\t}\n\n\t// Phase 3: Transition history validation and replay\n\tconst transitions = rec.transitions as unknown[];\n\tif (!Array.isArray(transitions)) {\n\t\treturn { valid: false, error: \"transitions must be an array\" };\n\t}\n\n\tconst typedTransitions = transitions as unknown[];\n\n\t// Validate each transition entry structurally\n\tconst seenIds = new Set<string>();\n\tfor (let i = 0; i < typedTransitions.length; i++) {\n\t\tconst tx = typedTransitions[i];\n\t\tconst txErr = validateTransitionStructure(tx, i);\n\t\tif (txErr) return { valid: false, error: txErr };\n\n\t\tconst t = tx as Record<string, unknown>;\n\n\t\tconst transitionId = t.transitionId as string;\n\t\tif (seenIds.has(transitionId)) {\n\t\t\treturn { valid: false, error: `transitions[${i}]: duplicate transition ID \"${transitionId}\"` };\n\t\t}\n\t\tseenIds.add(transitionId);\n\n\t\t// Revision continuity\n\t\tconst revisionBefore = t.revisionBefore as number;\n\t\tconst revisionAfter = t.revisionAfter as number;\n\t\tif (revisionAfter !== revisionBefore + 1) {\n\t\t\treturn {\n\t\t\t\tvalid: false,\n\t\t\t\terror: `transitions[${i}]: revisionAfter (${revisionAfter}) must equal revisionBefore + 1 (${revisionBefore + 1})`,\n\t\t\t};\n\t\t}\n\n\t\tif (i > 0) {\n\t\t\tconst prevTx = typedTransitions[i - 1] as Record<string, unknown>;\n\t\t\tif (revisionBefore !== (prevTx.revisionAfter as number)) {\n\t\t\t\treturn {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: `transitions[${i}]: revision discontinuity at transition ${i} (expected ${prevTx.revisionAfter}, got ${revisionBefore})`,\n\t\t\t\t};\n\t\t\t}\n\t\t} else {\n\t\t\t// First transition must have revisionBefore === 0\n\t\t\tif (revisionBefore !== 0) {\n\t\t\t\treturn {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: `transitions[0]: first transition must have revisionBefore 0, got ${revisionBefore}`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// Phase 4: Replay the full history to derive final state\n\tconst replayResult = replayExecutionHistory(typedTransitions);\n\tif (!replayResult.ok) {\n\t\treturn { valid: false, error: replayResult.error! };\n\t}\n\n\t// Phase 5: Verify recorded state matches replayed state\n\tif (rec.state !== replayResult.state) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `state mismatch: record claims \"${rec.state}\" but replayed history produces \"${replayResult.state}\"`,\n\t\t};\n\t}\n\n\t// Phase 6: Verify blockedFromState consistency\n\tif (rec.state === \"BLOCKED\") {\n\t\tif (rec.blockedFromState === undefined) {\n\t\t\treturn { valid: false, error: \"blockedFromState is required when state is BLOCKED\" };\n\t\t}\n\t\tif (rec.blockedFromState !== replayResult.blockedFromState) {\n\t\t\treturn {\n\t\t\t\tvalid: false,\n\t\t\t\terror: `blockedFromState mismatch: record claims \"${rec.blockedFromState}\" but replayed history produces \"${replayResult.blockedFromState}\"`,\n\t\t\t};\n\t\t}\n\t} else {\n\t\tif (rec.blockedFromState !== undefined) {\n\t\t\treturn {\n\t\t\t\tvalid: false,\n\t\t\t\terror: `blockedFromState must not be present when state is \"${rec.state}\"`,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Phase 7: Verify revision equals transition history length\n\tif (rec.revision !== typedTransitions.length) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `revision (${rec.revision}) must equal transition history length (${typedTransitions.length})`,\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n// =============================================================================\n// Initialize a new MissionExecutionRecordV1 in PLANNING state\n// =============================================================================\n\n/**\n * Create a new MissionExecutionRecordV1 with revision 0 and state PLANNING.\n * The executionId and contractDigest are caller-supplied and deterministic.\n */\nexport function initializeMissionExecution(contract: MissionContractV1, executionId: string): MissionExecutionRecordV1 {\n\tif (!executionId || executionId.trim().length === 0) {\n\t\tthrow new Error(\"executionId must be non-empty and trimmed\");\n\t}\n\tif (executionId !== executionId.trim()) {\n\t\tthrow new Error(\"executionId must be trimmed (no leading/trailing whitespace)\");\n\t}\n\n\tconst contractDigest = computeMissionContractDigest(contract);\n\n\treturn freezeRecord({\n\t\texecutionVersion: 1 as const,\n\t\texecutionId: executionId.trim(),\n\t\tcontractDigest,\n\t\trevision: 0,\n\t\tstate: \"PLANNING\" as const,\n\t\ttransitions: Object.freeze([] as MissionExecutionTransitionRecordV1[]),\n\t}) as unknown as MissionExecutionRecordV1;\n}\n\n// =============================================================================\n// Structural inspection (untrusted)\n// =============================================================================\n\n/**\n * Inspect a MissionExecutionRecordV1 structurally without requiring\n * trusted provenance. Never claims completion approval.\n */\nexport function inspectMissionExecution(\n\t_contract: MissionContractV1,\n\trecord: unknown,\n): MissionExecutionInspectionResult {\n\tif (!isRecord(record)) {\n\t\treturn { valid: false, error: \"Execution record must be a non-null object\" };\n\t}\n\n\tconst rec = record as Record<string, unknown>;\n\n\t// Basic structural checks\n\tif (typeof rec.executionId !== \"string\" || rec.executionId.trim().length === 0) {\n\t\treturn { valid: false, error: \"executionId must be a non-empty trimmed string\" };\n\t}\n\tif (typeof rec.contractDigest !== \"string\") {\n\t\treturn { valid: false, error: \"contractDigest must be a string\" };\n\t}\n\tif (typeof rec.revision !== \"number\" || !Number.isSafeInteger(rec.revision) || rec.revision < 0) {\n\t\treturn { valid: false, error: \"revision must be a safe non-negative integer\" };\n\t}\n\tif (typeof rec.state !== \"string\" || !VALID_STATES.has(rec.state)) {\n\t\treturn { valid: false, error: `state must be a valid MissionExecutionState, got \"${rec.state}\"` };\n\t}\n\tif (!Array.isArray(rec.transitions)) {\n\t\treturn { valid: false, error: \"transitions must be an array\" };\n\t}\n\n\tconst state = rec.state as MissionExecutionState;\n\tconst revision = rec.revision as number;\n\n\t// blockedFromState check\n\tlet blockedFromState: ResumableMissionExecutionState | undefined;\n\tif (state === \"BLOCKED\") {\n\t\tif (typeof rec.blockedFromState !== \"string\" || !RESUABLE_STATES_SET.has(rec.blockedFromState)) {\n\t\t\treturn {\n\t\t\t\tvalid: false,\n\t\t\t\terror: `blockedFromState must be a resumable state when state is BLOCKED`,\n\t\t\t};\n\t\t}\n\t\tblockedFromState = rec.blockedFromState as ResumableMissionExecutionState;\n\t} else if (rec.blockedFromState !== undefined) {\n\t\treturn { valid: false, error: `blockedFromState must not be present when state is \"${state}\"` };\n\t}\n\n\treturn {\n\t\tvalid: true,\n\t\texecutionId: rec.executionId as string,\n\t\tcontractDigest: rec.contractDigest as string,\n\t\tstate,\n\t\trevision,\n\t\ttransitionCount: rec.transitions.length,\n\t\tblockedFromState,\n\t\tcompletionApproved: \"unavailable\",\n\t};\n}\n\n// =============================================================================\n// Apply a transition\n// =============================================================================\n\n/**\n * Apply a deterministic transition to a MissionExecutionRecordV1.\n *\n * Every mutation requires expectedRevision, caller-supplied transitionId,\n * and a transition kind. The destination state is derived by the state\n * machine from the current state and the transition kind.\n *\n * APPROVE_COMPLETION requires a genuine contract-bound\n * TrustedValidationContext. Without one, the transition is rejected.\n */\nexport function applyMissionExecutionTransition(\n\tcontract: MissionContractV1,\n\trecord: MissionExecutionRecordV1,\n\trequest: MissionExecutionTransitionRequestV1,\n\toptions?: {\n\t\ttrustedValidationContext?: TrustedValidationContext;\n\t\t/** Adaptive readiness gate. When present and not ready, APPROVE_COMPLETION is blocked. */\n\t\treadinessGate?: ReadinessInput;\n\t},\n): MissionExecutionTransitionResult {\n\t// Phase 0: Validate contract digest\n\tconst contractDigest = computeMissionContractDigest(contract);\n\tif (record.contractDigest !== contractDigest) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"CONTRACT_DIGEST_MISMATCH\",\n\t\t\tcode: \"CONTRACT_DIGEST_MISMATCH\",\n\t\t};\n\t}\n\n\t// Phase 1: Validate request\n\tif (!request.transitionId || request.transitionId.trim().length === 0) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"transitionId must be non-empty and trimmed\",\n\t\t\tcode: \"INVALID_TRANSITION\",\n\t\t};\n\t}\n\tif (request.transitionId !== request.transitionId.trim()) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"transitionId must be trimmed (no leading/trailing whitespace)\",\n\t\t\tcode: \"INVALID_TRANSITION\",\n\t\t};\n\t}\n\n\t// Phase 2: expectedRevision check\n\tif (!Number.isSafeInteger(request.expectedRevision) || request.expectedRevision < 0) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"expectedRevision must be a safe non-negative integer\",\n\t\t\tcode: \"STALE_REVISION\",\n\t\t};\n\t}\n\tif (request.expectedRevision !== record.revision) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `STALE_REVISION: expected revision ${request.expectedRevision} but record is at revision ${record.revision}`,\n\t\t\tcode: \"STALE_REVISION\",\n\t\t};\n\t}\n\n\t// Phase 3: Validate transition kind\n\tif (!VALID_KINDS.has(request.kind)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Unknown transition kind: \"${request.kind}\"`,\n\t\t\tcode: \"INVALID_TRANSITION\",\n\t\t};\n\t}\n\n\t// Phase 4: Terminal state rejection\n\tif (TERMINAL_STATES.has(record.state)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `TERMINAL_STATE: cannot transition from terminal state \"${record.state}\"`,\n\t\t\tcode: \"TERMINAL_STATE\",\n\t\t};\n\t}\n\n\t// Phase 5: RESUME requires BLOCKED\n\tif (request.kind === \"RESUME\" && record.state !== \"BLOCKED\") {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"RESUME: can only resume from BLOCKED state\",\n\t\t\tcode: \"RESUME_WHILE_NOT_BLOCKED\",\n\t\t};\n\t}\n\n\t// Phase 6: BLOCK while already BLOCKED\n\tif (request.kind === \"BLOCK\" && record.state === \"BLOCKED\") {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"BLOCK_WHILE_BLOCKED: already in BLOCKED state\",\n\t\t\tcode: \"BLOCK_WHILE_BLOCKED\",\n\t\t};\n\t}\n\n\tif (\n\t\trecord.state === \"BLOCKED\" &&\n\t\trequest.kind !== \"RESUME\" &&\n\t\trequest.kind !== \"FAIL\" &&\n\t\trequest.kind !== \"CANCEL\"\n\t) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `BLOCKED: only RESUME, FAIL, or CANCEL allowed from BLOCKED, got \"${request.kind}\"`,\n\t\t\tcode: \"ILLEGAL_TRANSITION\",\n\t\t};\n\t}\n\n\t// Phase 7: Derive destination state\n\tconst stateTransitions = TRANSITION_GRAPH.get(record.state);\n\tif (!stateTransitions) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `INTERNAL_ERROR: no transition graph entry for state \"${record.state}\"`,\n\t\t\tcode: \"INTERNAL_ERROR\",\n\t\t};\n\t}\n\n\tlet toState: MissionExecutionState;\n\n\tif (request.kind === \"RESUME\" && record.state === \"BLOCKED\") {\n\t\t// RESUME returns to the exact blockedFromState\n\t\tif (!record.blockedFromState) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: \"INTERNAL_ERROR: BLOCKED state missing blockedFromState\",\n\t\t\t\tcode: \"INTERNAL_ERROR\",\n\t\t\t};\n\t\t}\n\t\tif (!RESUABLE_STATES_SET.has(record.blockedFromState)) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `RESUME_STATE_NOT_RESUMABLE: \"${record.blockedFromState}\" is not a resumable state`,\n\t\t\t\tcode: \"RESUME_STATE_NOT_RESUMABLE\",\n\t\t\t};\n\t\t}\n\t\ttoState = record.blockedFromState;\n\t} else {\n\t\tconst dest = stateTransitions.get(request.kind);\n\t\tif (dest === undefined) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `ILLEGAL_TRANSITION: cannot apply \"${request.kind}\" from \"${record.state}\"`,\n\t\t\t\tcode: \"ILLEGAL_TRANSITION\",\n\t\t\t};\n\t\t}\n\t\ttoState = dest;\n\t}\n\n\t// Phase 8: Self-transition check\n\tif (record.state === toState) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `SELF_TRANSITION: transition from \"${record.state}\" to \"${toState}\" is a no-op`,\n\t\t\tcode: \"SELF_TRANSITION\",\n\t\t};\n\t}\n\n\t// Phase 9: Trusted completion boundary\n\tif (toState === \"COMPLETED\") {\n\t\tconst completionError = checkCompletionAuthorization(contractDigest, options?.trustedValidationContext);\n\t\tif (completionError) return completionError;\n\n\t\t// Adaptive readiness gate: deterministic evidence-backed completion.\n\t\tif (options?.readinessGate) {\n\t\t\tconst readiness = evaluateCompletionReadiness(options.readinessGate);\n\t\t\tif (!readiness.ready) {\n\t\t\t\tconst blockers = readiness.blockers.join(\",\");\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `READINESS_GATE_BLOCKED: ${blockers}`,\n\t\t\t\t\tcode: \"READINESS_GATE_BLOCKED\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// Phase 10: Duplicate transition ID check\n\tfor (const tx of record.transitions) {\n\t\tif (tx.transitionId === request.transitionId) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `DUPLICATE_TRANSITION_ID: transition \"${request.transitionId}\" already exists`,\n\t\t\t\tcode: \"DUPLICATE_TRANSITION_ID\",\n\t\t\t};\n\t\t}\n\t}\n\n\t// Phase 11: Determine blockedFromState for BLOCK transitions\n\tlet blockedFromState: ResumableMissionExecutionState | undefined;\n\tif (request.kind === \"BLOCK\" && record.state !== \"BLOCKED\") {\n\t\tif (RESUABLE_STATES_SET.has(record.state)) {\n\t\t\tblockedFromState = record.state as ResumableMissionExecutionState;\n\t\t}\n\t} else if (request.kind === \"RESUME\") {\n\t\t// Clear blockedFromState on RESUME\n\t\tblockedFromState = undefined;\n\t}\n\n\t// Phase 12: Build transition record from validated primitives (no caller reference retained)\n\tconst transition: MissionExecutionTransitionRecordV1 = Object.freeze({\n\t\ttransitionId: String(request.transitionId),\n\t\tkind: request.kind,\n\t\tfromState: record.state,\n\t\ttoState,\n\t\trevisionBefore: record.revision | 0,\n\t\trevisionAfter: (record.revision + 1) | 0,\n\t});\n\n\t// Phase 13: Build new record (immutable — new object, fully defensive snapshot)\n\t// Every historical transition is reconstructed from validated primitive values\n\t// to prevent caller-owned mutable references from aliasing into the output.\n\tconst snapshotTransitions: readonly MissionExecutionTransitionRecordV1[] = Object.freeze([\n\t\t...record.transitions.map((tx) =>\n\t\t\tObject.freeze({\n\t\t\t\ttransitionId: String(tx.transitionId),\n\t\t\t\tkind: tx.kind,\n\t\t\t\tfromState: tx.fromState,\n\t\t\t\ttoState: tx.toState,\n\t\t\t\trevisionBefore: tx.revisionBefore | 0,\n\t\t\t\trevisionAfter: tx.revisionAfter | 0,\n\t\t\t}),\n\t\t),\n\t\ttransition,\n\t]);\n\tconst newRecord: MissionExecutionRecordV1 = freezeRecord({\n\t\texecutionVersion: 1 as const,\n\t\texecutionId: String(record.executionId),\n\t\tcontractDigest: String(record.contractDigest),\n\t\trevision: record.revision + 1,\n\t\tstate: toState,\n\t\tblockedFromState,\n\t\ttransitions: snapshotTransitions,\n\t}) as unknown as MissionExecutionRecordV1;\n\n\treturn { ok: true, record: newRecord };\n}\n\n// =============================================================================\n// Completion approval gate — trusted boundary\n// =============================================================================\n\nfunction checkCompletionAuthorization(\n\tcontractDigest: string,\n\ttrustedValidationContext: TrustedValidationContext | undefined,\n): MissionExecutionTransitionResult | null {\n\tif (!trustedValidationContext) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"TRUSTED_VALIDATION_CONTEXT_REQUIRED: APPROVE_COMPLETION requires a genuine contract-bound TrustedValidationContext\",\n\t\t\tcode: \"TRUSTED_VALIDATION_CONTEXT_REQUIRED\",\n\t\t};\n\t}\n\n\t// Verify the trusted context is genuinely branded\n\tconst boundDigest = _getBoundContractDigest(trustedValidationContext);\n\tif (boundDigest !== contractDigest) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"TRUSTED_VALIDATION_CONTEXT_REQUIRED: trusted context is not bound to this contract digest\",\n\t\t\tcode: \"TRUSTED_VALIDATION_CONTEXT_REQUIRED\",\n\t\t};\n\t}\n\n\t// Verify the trusted context authorizes the execution:complete capability\n\tif (!trustedValidationContext.verifyCapability(\"completion-operator\", \"operator\", EXECUTION_COMPLETION_CAPABILITY)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"EXECUTION_COMPLETION_CAPABILITY_REQUIRED: trusted context does not authorize execution:complete\",\n\t\t\tcode: \"EXECUTION_COMPLETION_CAPABILITY_REQUIRED\",\n\t\t};\n\t}\n\n\t// LH-5 will implement the full requirement-evidence completion gate here.\n\treturn null;\n}\n\n// =============================================================================\n// Replay execution history to derive final state\n// =============================================================================\n\ninterface ReplayResult {\n\tok: boolean;\n\tstate?: MissionExecutionState;\n\tblockedFromState?: ResumableMissionExecutionState;\n\terror?: string;\n}\n\nfunction replayExecutionHistory(transitions: readonly unknown[]): ReplayResult {\n\tif (transitions.length === 0) {\n\t\treturn { ok: true, state: \"PLANNING\" };\n\t}\n\n\tlet currentState: MissionExecutionState = \"PLANNING\";\n\tlet blockedFromState: ResumableMissionExecutionState | undefined;\n\n\tfor (let i = 0; i < transitions.length; i++) {\n\t\tconst tx = transitions[i] as Record<string, unknown>;\n\t\tconst kind = tx.kind as MissionExecutionTransitionKind;\n\t\tconst fromState = tx.fromState as MissionExecutionState;\n\t\tconst toState = tx.toState as MissionExecutionState;\n\n\t\t// Verify fromState matches current\n\t\tif (fromState !== currentState) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `transitions[${i}]: fromState \"${fromState}\" does not match current state \"${currentState}\"`,\n\t\t\t};\n\t\t}\n\n\t\t// Verify transition kind is valid for current state\n\t\tif (kind === \"RESUME\") {\n\t\t\tif (currentState !== \"BLOCKED\") {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: RESUME only valid from BLOCKED, not \"${currentState}\"`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!blockedFromState) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: RESUME but no blockedFromState recorded`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (toState !== blockedFromState) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: RESUME to \"${toState}\" does not match blockedFromState \"${blockedFromState}\"`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tblockedFromState = undefined;\n\t\t} else if (kind === \"BLOCK\") {\n\t\t\tif (!RESUABLE_STATES_SET.has(currentState)) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: BLOCK only valid from resumable state, not \"${currentState}\"`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tblockedFromState = currentState as ResumableMissionExecutionState;\n\t\t} else {\n\t\t\tconst stateTransitions = TRANSITION_GRAPH.get(currentState);\n\t\t\tif (!stateTransitions) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: no transition graph entry for state \"${currentState}\"`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst expectedDest = stateTransitions.get(kind);\n\t\t\tif (expectedDest === undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: illegal transition \"${kind}\" from \"${currentState}\"`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (toState !== expectedDest) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `transitions[${i}]: toState \"${toState}\" does not match expected destination \"${expectedDest}\" for kind \"${kind}\" from \"${currentState}\"`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Self-transition rejection\n\t\tif (fromState === toState) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `transitions[${i}]: self-transition from \"${fromState}\" to \"${toState}\"`,\n\t\t\t};\n\t\t}\n\n\t\t// Terminal state must not have outgoing transitions\n\t\tif (TERMINAL_STATES.has(currentState) && i > 0) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `transitions[${i}]: transition after terminal state \"${prevTerminal(transitions, i)}\"`,\n\t\t\t};\n\t\t}\n\n\t\tcurrentState = toState;\n\t}\n\n\treturn { ok: true, state: currentState, blockedFromState };\n}\n\nfunction prevTerminal(transitions: readonly unknown[], currentIndex: number): string {\n\tfor (let i = currentIndex - 1; i >= 0; i--) {\n\t\tconst tx = transitions[i] as Record<string, unknown>;\n\t\tif (TERMINAL_STATES.has(tx.toState as MissionExecutionState)) {\n\t\t\treturn tx.toState as string;\n\t\t}\n\t}\n\treturn \"unknown\";\n}\n\n// =============================================================================\n// Transition structure validation\n// =============================================================================\n\nfunction validateTransitionStructure(tx: unknown, index: number): string | null {\n\tif (!isRecord(tx)) {\n\t\treturn `transitions[${index}]: must be a non-null object`;\n\t}\n\n\tconst t = tx as Record<string, unknown>;\n\n\t// Unknown field check\n\tfor (const key of Object.keys(t)) {\n\t\tif (!MISSION_EXECUTION_TRANSITION_KEYS.has(key)) {\n\t\t\treturn `transitions[${index}]: unknown field \"${key}\"`;\n\t\t}\n\t}\n\n\tif (typeof t.transitionId !== \"string\" || t.transitionId.trim().length === 0) {\n\t\treturn `transitions[${index}]: transitionId must be a non-empty trimmed string`;\n\t}\n\tif (t.transitionId !== (t.transitionId as string).trim()) {\n\t\treturn `transitions[${index}]: transitionId must be trimmed (no leading/trailing whitespace)`;\n\t}\n\tif (typeof t.kind !== \"string\" || !VALID_KINDS.has(t.kind)) {\n\t\treturn `transitions[${index}]: kind must be a valid transition kind, got \"${t.kind}\"`;\n\t}\n\tif (typeof t.fromState !== \"string\" || !VALID_STATES.has(t.fromState)) {\n\t\treturn `transitions[${index}]: fromState must be a valid state, got \"${t.fromState}\"`;\n\t}\n\tif (typeof t.toState !== \"string\" || !VALID_STATES.has(t.toState)) {\n\t\treturn `transitions[${index}]: toState must be a valid state, got \"${t.toState}\"`;\n\t}\n\tif (typeof t.revisionBefore !== \"number\" || !Number.isSafeInteger(t.revisionBefore) || t.revisionBefore < 0) {\n\t\treturn `transitions[${index}]: revisionBefore must be a safe non-negative integer`;\n\t}\n\tif (typeof t.revisionAfter !== \"number\" || !Number.isSafeInteger(t.revisionAfter) || t.revisionAfter < 0) {\n\t\treturn `transitions[${index}]: revisionAfter must be a safe non-negative integer`;\n\t}\n\n\t// NaN, Infinity checks (covered by !Number.isSafeInteger but let's be explicit)\n\tif (!Number.isFinite(t.revisionBefore as number) || !Number.isFinite(t.revisionAfter as number)) {\n\t\treturn `transitions[${index}]: revision values must be finite`;\n\t}\n\n\treturn null;\n}\n\n// =============================================================================\n// Execution record structure validation\n// =============================================================================\n\nfunction validateExecutionRecordStructure(record: Record<string, unknown>): string | null {\n\t// Unknown field check\n\tfor (const key of Object.keys(record)) {\n\t\tif (!MISSION_EXECUTION_RECORD_KEYS.has(key)) {\n\t\t\treturn `Unknown field in execution record: \"${key}\"`;\n\t\t}\n\t}\n\n\tif (record.executionVersion !== 1) {\n\t\treturn `executionVersion must be 1, got ${record.executionVersion}`;\n\t}\n\n\tif (typeof record.executionId !== \"string\" || record.executionId.trim().length === 0) {\n\t\treturn \"executionId must be a non-empty trimmed string\";\n\t}\n\tif (record.executionId !== (record.executionId as string).trim()) {\n\t\treturn \"executionId must be trimmed (no leading/trailing whitespace)\";\n\t}\n\n\tif (typeof record.contractDigest !== \"string\" || record.contractDigest.length === 0) {\n\t\treturn \"contractDigest must be a non-empty string\";\n\t}\n\n\tif (\n\t\ttypeof record.revision !== \"number\" ||\n\t\t!Number.isFinite(record.revision) ||\n\t\t!Number.isSafeInteger(record.revision) ||\n\t\trecord.revision < 0\n\t) {\n\t\treturn \"revision must be a safe non-negative integer\";\n\t}\n\n\tif (typeof record.state !== \"string\" || !VALID_STATES.has(record.state)) {\n\t\treturn `state must be a valid MissionExecutionState, got \"${record.state}\"`;\n\t}\n\n\tif (!Array.isArray(record.transitions)) {\n\t\treturn \"transitions must be an array\";\n\t}\n\n\t// blockedFromState validation\n\tif (record.state === \"BLOCKED\") {\n\t\tif (record.blockedFromState === undefined) {\n\t\t\treturn \"blockedFromState is required when state is BLOCKED\";\n\t\t}\n\t\tif (typeof record.blockedFromState !== \"string\" || !RESUABLE_STATES_SET.has(record.blockedFromState)) {\n\t\t\treturn `blockedFromState must be a resumable state when state is BLOCKED, got \"${record.blockedFromState}\"`;\n\t\t}\n\t} else {\n\t\tif (record.blockedFromState !== undefined) {\n\t\t\treturn `blockedFromState must not be present when state is \"${record.state}\"`;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction freezeRecord<T extends Record<string, unknown>>(obj: T): T {\n\treturn Object.freeze(obj) as T;\n}\n"]}