{"version":3,"file":"runs.cjs","names":[],"sources":["../../../src/batteries/orchestration/runs.ts"],"sourcesContent":["/**\n * The run-event fold: turn a persisted run log into a `RunProjection`.\n *\n * @module @nhtio/adk/batteries/orchestration/runs\n */\n\nimport { branchKey } from './ops'\nimport type {\n  RunEvent,\n  RunProjection,\n  PendingFrame,\n  JoinState,\n  FrameRef,\n  NodeOutcome,\n  NodeOutput,\n  InterruptionCause,\n  NodeId,\n  EdgeHandle,\n  EncodableValue,\n} from './types'\n\n/**\n * Fold a sequence of run events into a single {@link RunProjection}.\n *\n * The projection is derived entirely from the events themselves — no graph, no store, and no\n * side channel. It is deterministic and total: the same event list always folds to the same\n * projection.\n *\n * @param events - The ordered run events to fold.\n * @returns The projected state of the run.\n * @throws If the first event is not `run_started` (a malformed event list).\n */\nexport function foldRun(events: RunEvent[]): RunProjection {\n  if (events.length === 0 || events[0].kind !== 'run_started') {\n    throw new Error('foldRun: malformed event list; first event must be run_started')\n  }\n\n  const started = events[0]\n  const runId = started.runId\n  const digest = started.digest\n\n  const frameStatus = new Map<string, 'running' | 'done' | 'failed' | 'skipped'>()\n  const nodeStatusById = new Map<NodeId, 'pending' | 'running' | 'done' | 'failed' | 'skipped'>()\n  const outputs: Map<string, NodeOutput> = new Map()\n  const indeterminate: FrameRef[] = []\n  const edgesTaken: { edgeId: string; handle: EdgeHandle; evidence?: EncodableValue }[] = []\n\n  let frontier: { frames: PendingFrame[]; joins: JoinState[] } = { frames: [], joins: [] }\n  let outcome: 'running' | 'completed' | 'halted' | 'aborted' = 'running'\n  let interruption: InterruptionCause | undefined\n\n  const frameKey = (frame: FrameRef): string => `${frame.nodeId}:${branchKey(frame.branchId)}`\n  const nodeIdFromKey = (key: string): NodeId => key.slice(0, key.indexOf(':'))\n\n  for (const event of events) {\n    switch (event.kind) {\n      case 'run_started':\n        // Already handled above; nothing further to fold.\n        break\n\n      case 'node_entered': {\n        const key = frameKey(event.frame)\n        if (!frameStatus.has(key)) {\n          frameStatus.set(key, 'running')\n        }\n        break\n      }\n\n      case 'node_settled': {\n        const key = frameKey(event.frame)\n        frameStatus.set(key, outcomeStatus(event.outcome))\n\n        if (event.outcome.status === 'ok') {\n          outputs.set(key, event.outcome.output)\n        }\n        break\n      }\n\n      case 'edge_taken': {\n        edgesTaken.push({ edgeId: event.edgeId, handle: event.handle, evidence: event.evidence })\n        break\n      }\n\n      case 'frontier_snapshot': {\n        frontier = { frames: event.frames, joins: event.joins }\n        break\n      }\n\n      case 'run_interrupted': {\n        interruption = event.cause\n        break\n      }\n\n      case 'run_settled': {\n        outcome = event.outcome\n        break\n      }\n    }\n  }\n\n  // Advance the frontier past the last snapshot using the events after it: each later\n  // node_settled removes its frame, each later edge_taken adds its `to` frame with the\n  // outputs and artifacts that event carries.\n  const snapshotIndex = lastIndexWhere(events, (e) => e.kind === 'frontier_snapshot')\n  const frames = new Map<string, PendingFrame>()\n  for (const frame of frontier.frames) {\n    frames.set(frameKey(frame.frame), frame)\n  }\n  for (let i = snapshotIndex + 1; i < events.length; i++) {\n    const event = events[i]\n    if (event.kind === 'node_settled') {\n      frames.delete(frameKey(event.frame))\n    } else if (event.kind === 'edge_taken') {\n      frames.set(frameKey(event.to), {\n        frame: event.to,\n        outputs: event.outputs,\n        artifacts: event.artifacts,\n      })\n    }\n  }\n  frontier = { frames: [...frames.values()], joins: frontier.joins }\n\n  // Indeterminate frames: entered-without-settled AND kind === 'call', exactly. No other kind\n  // ever appears — branch/select/transform/reason/join are re-entered unconditionally on resume.\n  const settledKeys = new Set<string>()\n  for (const event of events) {\n    if (event.kind === 'node_settled') {\n      settledKeys.add(frameKey(event.frame))\n    }\n  }\n  const seenIndeterminate = new Set<string>()\n  for (const event of events) {\n    if (event.kind === 'node_entered' && event.frame.kind === 'call') {\n      const key = frameKey(event.frame)\n      if (!settledKeys.has(key) && !seenIndeterminate.has(key)) {\n        seenIndeterminate.add(key)\n        indeterminate.push(event.frame)\n      }\n    }\n  }\n\n  // Roll up per-node status from per-frame status.\n  const framesByNode = new Map<NodeId, ('running' | 'done' | 'failed' | 'skipped')[]>()\n  for (const [key, status] of frameStatus) {\n    const nodeId = nodeIdFromKey(key)\n    const list = framesByNode.get(nodeId) ?? []\n    list.push(status)\n    framesByNode.set(nodeId, list)\n  }\n  for (const [nodeId, statuses] of framesByNode) {\n    nodeStatusById.set(nodeId, rollupNodeStatus(statuses))\n  }\n\n  return {\n    runId,\n    digest,\n    frameStatus,\n    nodeStatusById,\n    outputs,\n    frontier,\n    indeterminate,\n    edgesTaken,\n    outcome,\n    interruption,\n  }\n}\n\nfunction outcomeStatus(outcome: NodeOutcome): 'done' | 'failed' | 'skipped' {\n  switch (outcome.status) {\n    case 'ok':\n      return 'done'\n    case 'failed':\n      return 'failed'\n    case 'skipped':\n      return 'skipped'\n  }\n}\n\nfunction rollupNodeStatus(\n  statuses: ('running' | 'done' | 'failed' | 'skipped')[]\n): 'pending' | 'running' | 'done' | 'failed' | 'skipped' {\n  if (statuses.some((s) => s === 'running')) return 'running'\n  if (statuses.some((s) => s === 'failed')) return 'failed'\n  if (statuses.every((s) => s === 'done' || s === 'skipped')) return 'done'\n  return 'pending'\n}\n\nfunction lastIndexWhere(events: RunEvent[], predicate: (e: RunEvent) => boolean): number {\n  for (let i = events.length - 1; i >= 0; i--) {\n    if (predicate(events[i])) return i\n  }\n  return -1\n}\n"],"mappings":"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"}