{"version":3,"file":"raw.cjs","names":[],"sources":["../../../src/batteries/orchestration/raw.ts"],"sourcesContent":["/**\n * The machine-readable reading surface for a plan's op log.\n *\n * @module @nhtio/adk/batteries/orchestration/raw\n *\n * @remarks\n * The prose views (`render`, `outline`) are for humans and for model re-consumption: they adapt\n * their audience, frame trust, and narrate. A UI showing \"here is what changed and what will be\n * applied\" needs machine-readable state instead, and the op log makes that well-defined. This\n * module is that surface.\n *\n * Three properties this view has that the prose views deliberately do NOT:\n *\n * 1. **It is NOT audience-adapted.** It is data — no narrative, no trust framing, no prose. A\n *    `RawPlanView` is the folded content of the log, and nothing more.\n * 2. **It is STABLE.** A given revision folds to the same bytes forever, which is what makes a\n *    digest meaningful. The fold is a pure function of the op set (see `foldOps`), so the same\n *    prefix always yields the same view and the same digest.\n * 3. **It is reachable both ways.** As a plain exported function, for a UI talking to a\n *    `PlanStore` directly and never going through an agent; and later as a tool, so a model can\n *    read the same data through the same seam.\n *\n * **`RawPlanView` carries NO lifecycle `state`, and that is deliberate.** Lifecycle state is not\n * a `PlanOp`, so it cannot be folded from the log, and a historical revision therefore has no\n * recoverable state to report. A view at revision 7 answers \"what did the CONTENT look like\n * then\", not \"what state was it in then\". `state` is a property of the plan NOW — read it from\n * `store.readState()`. Do not add a `state` field.\n */\n\nimport { foldOps } from './ops'\nimport { isObject } from '../../lib/utils/guards'\nimport type { PlanStore } from './store'\nimport type { ArgValue, PlanDiff, PlanEdge, PlanNode, PlanOp, RawPlanView } from './types'\n\n/**\n * Fold the plan's op log up to a revision into a `RawPlanView`.\n *\n * @remarks\n * The plan IS the fold of its op log, so this is the canonical way to read plan CONTENT. With no\n * `revision`, it folds the plan's present log. With `{revision: N}`, it serves a HISTORICAL view:\n * it reads the op prefix `throughRevision: N` and folds exactly that prefix, so the result is\n * what the content looked like at revision N. A revision the log never reached is REJECTED by\n * the store (which throws rather than silently returning everything) — this function never\n * guesses or truncates.\n *\n * `provenance` (clone/template lineage) is read from `store.readProvenance(planId)` and carried\n * into the view, where it is covered by the digest.\n *\n * The returned view carries NO lifecycle `state` — see the module doc. `state` is a property of\n * the plan NOW and lives on `store.readState()`, not here.\n *\n * @param store - The plan store to read from.\n * @param planId - The plan to read.\n * @param opts - Optional revision selector.\n * @param opts.revision - Fold only the op prefix through this revision. Omitted folds the present\n *   log.\n * @returns The folded content view at the requested revision.\n */\nexport const rawPlan = async (\n  store: PlanStore,\n  planId: string,\n  opts?: { revision?: number }\n): Promise<RawPlanView> => {\n  const ops = await store.readOps(planId, { throughRevision: opts?.revision })\n  const provenance = await store.readProvenance(planId)\n  return foldOps(planId, ops, provenance).view\n}\n\n/**\n * Read the plan's raw op log.\n *\n * @remarks\n * The op log is the source of truth the fold reads; this returns the ops themselves, optionally\n * filtered. `sinceLamport` filters by clock (a Lamport value is not a revision selector — use\n * `throughRevision` to bound by revision). `throughRevision` bounds the result to a REVISION\n * PREFIX, which is what `rawPlan({revision})` and `rawDiff(a, b)` need. A revision the log never\n * reached is rejected by the store rather than silently returning everything.\n *\n * @param store - The plan store to read from.\n * @param planId - The plan to read.\n * @param opts - Optional filters.\n * @param opts.sinceLamport - Only ops with `lamport >= sinceLamport`.\n * @param opts.throughRevision - Only ops up to and including this revision.\n * @returns The matching ops, in the store's order.\n */\nexport const rawOps = async (\n  store: PlanStore,\n  planId: string,\n  opts?: { sinceLamport?: number; throughRevision?: number }\n): Promise<PlanOp[]> => {\n  return store.readOps(planId, opts)\n}\n\n/**\n * A STRUCTURAL delta between two folded states of a plan.\n *\n * @remarks\n * `rawDiff(a, b)` compares two folded states, NOT an event log. It resolves each side to a\n * revision (`'current'` means the plan's present revision), folds both prefixes, and compares the\n * resulting content structurally:\n *\n * - `nodesAdded` / `nodesRemoved` are keyed by node id.\n * - `nodesChanged` names the CHANGED FIELD PATHS with before/after values, typed `ArgValue` — a\n *   changed field may hold a `NodeRef`, so a narrower type would make a legitimate change\n *   unrepresentable.\n * - `edgesAdded` / `edgesRemoved` are keyed by edge id; an edge whose endpoints or handle changed\n *   counts as removed-then-added, since its identity is its id.\n * - Both digests are carried so a UI can label either side.\n *\n * Because this is a FINAL-STATE diff, a node edited and then reverted across the compared span\n * produces NO row — the two states are identical at that node, and there is nothing to report.\n *\n * @param store - The plan store to read from.\n * @param planId - The plan to diff.\n * @param a - The \"from\" side: a revision number or `'current'`.\n * @param b - The \"to\" side: a revision number or `'current'`.\n * @returns The structural delta between the two folded states.\n */\nexport const rawDiff = async (\n  store: PlanStore,\n  planId: string,\n  a: number | 'current',\n  b: number | 'current'\n): Promise<PlanDiff> => {\n  const resolve = async (side: number | 'current'): Promise<number> => {\n    if (side === 'current') {\n      const state = await store.readState(planId)\n      return state.revision\n    }\n    return side\n  }\n\n  const [revA, revB] = await Promise.all([resolve(a), resolve(b)])\n\n  const [opsA, opsB] = await Promise.all([\n    store.readOps(planId, { throughRevision: revA }),\n    store.readOps(planId, { throughRevision: revB }),\n  ])\n\n  const viewA = foldOps(planId, opsA).view\n  const viewB = foldOps(planId, opsB).view\n\n  const nodesA = new Map(viewA.nodes.map((n) => [n.id, n]))\n  const nodesB = new Map(viewB.nodes.map((n) => [n.id, n]))\n\n  const nodesAdded: PlanNode[] = []\n  const nodesRemoved: PlanNode[] = []\n  const nodesChanged: PlanDiff['nodesChanged'] = []\n\n  for (const node of viewB.nodes) {\n    if (!nodesA.has(node.id)) {\n      nodesAdded.push(node)\n    }\n  }\n  for (const node of viewA.nodes) {\n    if (!nodesB.has(node.id)) {\n      nodesRemoved.push(node)\n    }\n  }\n\n  for (const [id, nodeB] of nodesB) {\n    const nodeA = nodesA.get(id)\n    if (!nodeA) continue\n    const fields = diffNode(nodeA, nodeB)\n    if (fields.length > 0) {\n      nodesChanged.push({ nodeId: id, fields })\n    }\n  }\n\n  const edgesA = new Map(viewA.edges.map((e) => [e.id, e]))\n  const edgesB = new Map(viewB.edges.map((e) => [e.id, e]))\n\n  const edgesAdded: PlanEdge[] = []\n  const edgesRemoved: PlanEdge[] = []\n\n  for (const [id, edgeB] of edgesB) {\n    const edgeA = edgesA.get(id)\n    if (!edgeA || !sameEdge(edgeA, edgeB)) {\n      edgesAdded.push(edgeB)\n    }\n  }\n  for (const [id, edgeA] of edgesA) {\n    const edgeB = edgesB.get(id)\n    if (!edgeB || !sameEdge(edgeA, edgeB)) {\n      edgesRemoved.push(edgeA)\n    }\n  }\n\n  return {\n    from: { revision: revA, digest: viewA.digest },\n    to: { revision: revB, digest: viewB.digest },\n    nodesAdded,\n    nodesRemoved,\n    nodesChanged,\n    edgesAdded,\n    edgesRemoved,\n  }\n}\n\n/**\n * Compare two node definitions structurally and return the changed field paths with before/after\n * values. A node's `id` and `phase` are compared too, but only definition changes are reported as\n * field paths; a changed `phase` is reported under the `phase` path.\n */\nconst diffNode = (\n  a: PlanNode,\n  b: PlanNode\n): { path: string; before: ArgValue; after: ArgValue }[] => {\n  const fields: { path: string; before: ArgValue; after: ArgValue }[] = []\n  const walk = (path: string, before: unknown, after: unknown): void => {\n    if (isObject(before) && isObject(after)) {\n      const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n      for (const key of keys) {\n        const childPath = path ? `${path}.${key}` : key\n        walk(\n          childPath,\n          (before as Record<string, unknown>)[key],\n          (after as Record<string, unknown>)[key]\n        )\n      }\n      return\n    }\n    if (Array.isArray(before) && Array.isArray(after)) {\n      if (before.length !== after.length) {\n        fields.push({ path, before: before as ArgValue, after: after as ArgValue })\n        return\n      }\n      for (const [i, element] of before.entries()) {\n        walk(`${path}.${i}`, element, after[i])\n      }\n      return\n    }\n    if (before !== after) {\n      fields.push({ path, before: before as ArgValue, after: after as ArgValue })\n    }\n  }\n  walk('', a.definition, b.definition)\n  if (a.phase !== b.phase) {\n    fields.push({ path: 'phase', before: a.phase as ArgValue, after: b.phase as ArgValue })\n  }\n  return fields\n}\n\n/**\n * Whether two edges are the same edge — same id, endpoints and handle. An edge whose endpoints or\n * handle changed is treated as removed-then-added, since its identity is its id.\n */\nconst sameEdge = (a: PlanEdge, b: PlanEdge): boolean =>\n  a.from === b.from && a.to === b.to && a.handle === b.handle\n"],"mappings":"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"}