{"version":3,"file":"validation.cjs","names":[],"sources":["../../../src/batteries/orchestration/validation.ts"],"sourcesContent":["/**\n * Submit-time validation and the lifecycle machine.\n *\n * @module @nhtio/adk/batteries/orchestration/validation\n *\n * @remarks\n * This module owns the freeze gate: it folds a plan's op log into a `RawPlanView`, runs every\n * submit check over that folded graph, and — only on a clean pass — commits the\n * `editable → reviewable` transition. It is the \"the battery validates, the store commits\" split\n * made concrete: every check here is battery policy a bring-your-own store has no business\n * reimplementing, and the store's `transition` is the only boundary that can atomically move the\n * lifecycle state.\n *\n * The two exported functions are the whole surface:\n *\n * - {@link collectIssues} — the pure-ish validator. Given a folded view and the injected\n *   `FreezeInputs` (the tier-C allowlist and the wired predicate cells), it returns every\n *   `PlanIssue` the graph raises. It never throws on a well-typed-but-invalid plan; a malformed\n *   definition surfaces as an issue, not a crash.\n * - {@link freezePlan} — the lifecycle entry point. It folds the log, runs {@link collectIssues},\n *   and only when no issue is `blocking` calls `store.transition(editable → reviewable,\n *   {expectedDigest})`. The digest is what makes the commit safe rather than racy: content is\n *   validated at digest D and the store commits only if the plan is still at D, so a concurrent\n *   edit invalidates the transition instead of slipping past an already-passed check.\n *\n * Every refusal is a `PlanIssue` with a stable `code`, a model-addressed `message` naming the fix,\n * the `nodeId`/`edgeId` where applicable, and a `severity`. Blocking issues refuse the freeze;\n * advisory issues are surfaced for the author but do not stop the transition.\n *\n * The checks are grouped into three families, each documented at its call site:\n * topology (entry, reachability, acyclicity, the diamond-join rule, id and handle rules),\n * references and dataflow (dangling refs, undeclared fields, join-crossing selections, ambiguous\n * references, taint), and per-node shape (call, transform, branch/select, encodability, scaffold\n * placeholders, unreachable calls).\n */\n\nimport { foldOps } from './ops'\nimport { NodeRef } from './encoding'\nimport { passesSchema } from '../../lib/utils/validation'\nimport { effectiveToolMethods } from './artifact_methods'\nimport { isError, isInstanceOf, isObject } from '../../lib/utils/guards'\nimport {\n  entryNodes,\n  findCycle,\n  handleAppliesTo,\n  immediateDominator,\n  incoming,\n  isValidEdgeId,\n  isValidNodeId,\n  nodeById,\n  outgoing,\n  reachableFrom,\n  routesBetween,\n} from './plan'\nimport type { PlanStore } from './store'\nimport type {\n  FreezeInputs,\n  NodeId,\n  NodeRef as NodeRefType,\n  PlanEdge,\n  PlanIssue,\n  PlanNode,\n  RawPlanView,\n} from './types'\n\n// ── constants ────────────────────────────────────────────────────────────────\n/**\n * The exact string an authoring tool leaves in a field it scaffolded but the model never filled\n * in. The check is deliberately an exact match: cheap, and effective against a model that filled\n * in structure but not intent. A value equal to this string is refused as an unedited scaffold\n * placeholder.\n */\nconst SCAFFOLD_PLACEHOLDER = 'lorem ipsum'\n\n/**\n * Constructor names the encoder round-trips losslessly, and which are therefore inside the\n * `EncodableValue` subset. Anything else that is not a plain object or array is an unregistered\n * custom class and is refused at freeze. `NodeRef`/`ParamRef` are included because they are the\n * IR's own reference classes; luxon values are included because the encoder serialises them.\n */\nconst KNOWN_ENCODABLE_NAMES: ReadonlySet<string> = new Set([\n  'Date',\n  'RegExp',\n  'Map',\n  'Set',\n  'ArrayBuffer',\n  'DataView',\n  'Int8Array',\n  'Uint8Array',\n  'Uint8ClampedArray',\n  'Int16Array',\n  'Uint16Array',\n  'Int32Array',\n  'Uint32Array',\n  'Float32Array',\n  'Float64Array',\n  'BigInt64Array',\n  'BigUint64Array',\n  'DateTime',\n  'Duration',\n  'Interval',\n  'NodeRef',\n  'ParamRef',\n])\n\n// ── small structural helpers ─────────────────────────────────────────────────\n/**\n * True for a PLAIN object — one whose prototype is `Object.prototype` or `null`. Every\n * encoder-owned value (`Date`, `RegExp`, `Map`, `Set`, typed arrays, `ArrayBuffer`, `DataView`,\n * bigint, luxon values, `NodeRef`/`ParamRef` instances) has a non-plain prototype, so this is\n * exactly the set whose keys we are allowed to walk as a record.\n */\nconst isPlainObject = (v: unknown): v is Record<string, unknown> => {\n  if (v === null || typeof v !== 'object') return false\n  const proto = Object.getPrototypeOf(v)\n  return proto === Object.prototype || proto === null\n}\n\n/**\n * Read a field off a definition defensively. A folded definition is well-typed but may still be\n * invalid at runtime (a required field unset), so every required-field check reads through this\n * rather than trusting the type.\n */\nconst readField = (def: unknown, key: string): unknown =>\n  isObject(def) ? (def as Record<string, unknown>)[key] : undefined\n\n/**\n * True when `value` is an instance of a class the encoder round-trips losslessly. Used to tell a\n * legitimate encodable value (a `Date`, a `Map`, a `NodeRef`) apart from an unregistered custom\n * class, which the encoder cannot hydrate.\n */\nconst isKnownEncodable = (v: unknown): boolean => {\n  if (v === null || typeof v !== 'object') return false\n  const name = (v as { constructor?: { name?: string } }).constructor?.name\n  return name !== undefined && KNOWN_ENCODABLE_NAMES.has(name)\n}\n\n/**\n * Collect every `NodeRef` reachable inside a value, depth-first. `NodeRef` is a class, so\n * `NodeRef.isNodeRef` is an `instanceof`-backed guard no look-alike record can satisfy.\n *\n * A seen-set of visited object references guards against a cyclic staged value looping this walk\n * forever (a self-referencing record, array, `Map`, or `Set`). The set is persistent rather than\n * path-scoped: revisiting an already-visited value cannot surface a `NodeRef` that the first visit\n * missed, so we only need to stop re-descending, not to distinguish sibling re-references.\n */\nconst collectRefs = (value: unknown, out: NodeRefType[], seen: Set<object>): void => {\n  if (NodeRef.isNodeRef(value)) {\n    out.push(value)\n    return\n  }\n  if (value === null || typeof value !== 'object') return\n  if (seen.has(value)) return\n  seen.add(value)\n  if (Array.isArray(value)) {\n    for (const v of value) collectRefs(v, out, seen)\n    return\n  }\n  if (isInstanceOf(value, 'Map', Map)) {\n    for (const [k, v] of value) {\n      collectRefs(k, out, seen)\n      collectRefs(v, out, seen)\n    }\n    return\n  }\n  if (isInstanceOf(value, 'Set', Set)) {\n    for (const v of value) collectRefs(v, out, seen)\n    return\n  }\n  for (const key of Object.keys(value)) {\n    collectRefs((value as Record<string, unknown>)[key], out, seen)\n  }\n}\n\n/**\n * The data references a node consumes to produce its output. `call` reads its `args`, `reason`\n * reads its `prompt`, `transform` reads its `source`. `branch`/`select` route rather than produce\n * data, so they contribute nothing here.\n */\nconst dataRefs = (node: PlanNode): NodeRefType[] => {\n  const out: NodeRefType[] = []\n  if (node.kind === 'call') collectRefs(node.definition.args, out, new Set<object>())\n  else if (node.kind === 'reason') collectRefs(node.definition.prompt, out, new Set<object>())\n  else if (node.kind === 'transform') collectRefs(node.definition.source, out, new Set<object>())\n  return out\n}\n\n/**\n * The declared output field paths of a node, for reference-field validation. `reason` nodes carry\n * an encoded `Schema` rather than `DeclaredField[]`, so their fields are not statically\n * enumerable here and the check is skipped for them. A `join`'s output is provenance\n * (`via`/`from`/`branch`), which is a graph constant.\n */\nconst declaredFieldPaths = (node: PlanNode): string[] => {\n  if (node.kind === 'entry') return node.definition.input.map((f) => f.path)\n  if (node.kind === 'call') return node.definition.output.map((f) => f.path)\n  if (node.kind === 'transform') return node.definition.output.map((f) => f.path)\n  if (node.kind === 'join') return ['via', 'from', 'branch']\n  return []\n}\n\n/**\n * True when a reference path is a prefix of (or equal to) a declared field path. A reference may\n * read a sub-path of a declared field (`result.items` from a declared `result`), so the check is\n * prefix-based in both directions.\n */\nconst pathIsDeclared = (paths: readonly string[], path: string): boolean =>\n  paths.some((p) => p === path || p.startsWith(path + '.') || path.startsWith(p + '.'))\n\n/**\n * An independent reachability derivation, written from scratch rather than delegating to\n * `reachableFrom`. Used by the unreachable-`call` check, which is deliberately implemented twice\n * in independent derivations — see that check for why this is not dead code.\n */\nconst manualReachable = (view: RawPlanView, startId: NodeId): Set<NodeId> => {\n  const byFrom = new Map<NodeId, PlanEdge[]>()\n  for (const e of view.edges) {\n    const list = byFrom.get(e.from)\n    if (list) list.push(e)\n    else byFrom.set(e.from, [e])\n  }\n  const seen = new Set<NodeId>([startId])\n  const stack: NodeId[] = [startId]\n  while (stack.length > 0) {\n    const cur = stack.pop()!\n    for (const e of byFrom.get(cur) ?? []) {\n      if (!seen.has(e.to)) {\n        seen.add(e.to)\n        stack.push(e.to)\n      }\n    }\n  }\n  return seen\n}\n\n// ── taint ────────────────────────────────────────────────────────────────────\n/**\n * A tainted node, and which of its output fields carry the taint.\n *\n * @remarks\n * `fields: 'all'` is the ordinary case — a node with no declassification taints everything it\n * produces. A node that declares `declassifies` taints only the fields NOT named there, which is\n * why this cannot collapse to a set of node ids.\n */\ninterface TaintedNode {\n  fields: 'all' | ReadonlySet<string>\n}\n\n/**\n * Compute which node outputs are tainted, by fixpoint, at FIELD granularity.\n *\n * @remarks\n * External input at the `entry` node is tainted and propagates transitively through data\n * references. A node is tainted when any of its data references reaches a tainted OUTPUT FIELD.\n *\n * **Declassification is per FIELD, not per node**, which is the whole reason this returns a map\n * rather than a set of ids. The contract is that \"an output field named in `declassifies` is\n * untainted regardless of its inputs; EVERY OTHER FIELD keeps the taint of the node's inputs\" —\n * so a node declaring `declassifies: ['safe']` while also emitting `unsafe` launders exactly one\n * of them. A node-granular set cannot express that: declaring one safe field would clear the\n * node, and a downstream `call` reading the untouched sibling would be accepted. That was a real\n * gap, and it is the security-relevant direction, since `declassifies` is an author's ASSERTION\n * that a tool sanitised something — an assertion that must bind only to what it actually names.\n *\n * A `reason` node cannot declassify at all: a model is precisely not a sanitiser. `branch`/`select`\n * route rather than produce data, so they neither propagate nor clear taint.\n *\n * @param view - The folded plan.\n * @param entryId - The entry node, the origin of all external taint.\n * @returns A map from node id to which of its output fields are tainted.\n */\nconst computeTainted = (view: RawPlanView, entryId: NodeId): Map<NodeId, TaintedNode> => {\n  const tainted = new Map<NodeId, TaintedNode>([[entryId, { fields: 'all' }]])\n\n  /** Does this reference read a field that is currently tainted? */\n  const readsTainted = (ref: NodeRefType): boolean => {\n    const source = tainted.get(ref.node)\n    if (!source) return false\n    if (source.fields === 'all') return true\n    // A reference with no path reads the whole item, so ANY tainted field taints it.\n    if (ref.path === undefined) return source.fields.size > 0\n    // Otherwise only the named field matters — matched at its root segment, since a dot-path\n    // reaches INTO a declared field and inherits that field's status.\n    const root = ref.path.split('.')[0] ?? ref.path\n    return source.fields.has(root)\n  }\n\n  let changed = true\n  while (changed) {\n    changed = false\n    for (const node of view.nodes) {\n      const existing = tainted.get(node.id)\n      if (existing?.fields === 'all') continue\n      if (!dataRefs(node).some(readsTainted)) continue\n\n      const declassifies =\n        node.kind === 'call' && Array.isArray(node.definition.declassifies)\n          ? node.definition.declassifies\n          : []\n\n      let next: TaintedNode\n      if (declassifies.length === 0) {\n        next = { fields: 'all' }\n      } else {\n        // Only the fields this node did NOT claim to sanitise stay tainted. A declared output\n        // list is what makes that enumerable; with none declared there is nothing left to taint.\n        const declared = node.kind === 'call' ? node.definition.output.map((f) => f.path) : []\n        const remaining = new Set(declared.filter((path) => !declassifies.includes(path)))\n        next = { fields: remaining }\n      }\n\n      // Monotone widening only, so the fixpoint terminates: a node's taint may grow from absent\n      // to some fields to all, never shrink.\n      const sizeOf = (t: TaintedNode): number =>\n        typeof t.fields === 'string' ? Number.POSITIVE_INFINITY : t.fields.size\n      if (existing === undefined || sizeOf(next) > sizeOf(existing)) {\n        tainted.set(node.id, next)\n        changed = true\n      }\n    }\n  }\n  return tainted\n}\n\n// ── encodability ─────────────────────────────────────────────────────────────\n/**\n * Walk a staged value and refuse anything outside the `EncodableValue` subset, plus any cycle\n * inside it. Type membership does not imply encodability: a `Function`, an `Error`, or an\n * unregistered custom class is inside no subset the encoder can hydrate, and a record/array/`Map`/\n * `Set` can be cyclic even though it is well-typed. The walk uses a path-scoped seen-set, so a\n * value referenced twice in sibling branches is fine while a value that reaches itself is refused.\n */\nconst checkEncodable = (\n  value: unknown,\n  seen: Set<unknown>,\n  nodeId: NodeId,\n  issues: PlanIssue[]\n): void => {\n  if (typeof value === 'function') {\n    issues.push({\n      code: 'unencodable_value',\n      message: `Node \"${nodeId}\" stages a Function, which the encoder serialises by source text and cannot hydrate; replace it with a plain encodable value.`,\n      nodeId,\n      severity: 'blocking',\n    })\n    return\n  }\n  if (isError(value)) {\n    issues.push({\n      code: 'unencodable_value',\n      message: `Node \"${nodeId}\" stages an Error, which is outside the encodable value subset; replace it with a plain encodable value.`,\n      nodeId,\n      severity: 'blocking',\n    })\n    return\n  }\n  if (value === null || typeof value !== 'object') return\n  if (seen.has(value)) {\n    issues.push({\n      code: 'cyclic_value',\n      message: `Node \"${nodeId}\" stages a cyclic value, which cannot be encoded; break the cycle.`,\n      nodeId,\n      severity: 'blocking',\n    })\n    return\n  }\n  seen.add(value)\n  if (Array.isArray(value)) {\n    for (const v of value) checkEncodable(v, seen, nodeId, issues)\n  } else if (isInstanceOf(value, 'Map', Map)) {\n    for (const [k, v] of value) {\n      checkEncodable(k, seen, nodeId, issues)\n      checkEncodable(v, seen, nodeId, issues)\n    }\n  } else if (isInstanceOf(value, 'Set', Set)) {\n    for (const v of value) checkEncodable(v, seen, nodeId, issues)\n  } else if (isKnownEncodable(value)) {\n    // Date, RegExp, typed array, ArrayBuffer, DataView, luxon, NodeRef, ParamRef — fine.\n  } else if (isPlainObject(value)) {\n    for (const key of Object.keys(value)) checkEncodable(value[key], seen, nodeId, issues)\n  } else {\n    issues.push({\n      code: 'unencodable_value',\n      message: `Node \"${nodeId}\" stages an unregistered custom class, which the encoder cannot hydrate; replace it with a plain encodable value.`,\n      nodeId,\n      severity: 'blocking',\n    })\n  }\n  seen.delete(value)\n}\n\n// ── scaffold placeholder ─────────────────────────────────────────────────────\n/**\n * Walk a staged value and refuse any string equal to the scaffold placeholder. A seen-set guards\n * against a cyclic value looping this walk (the cycle itself is reported by the encodability\n * check).\n */\nconst checkScaffold = (\n  value: unknown,\n  seen: Set<unknown>,\n  nodeId: NodeId,\n  issues: PlanIssue[]\n): void => {\n  if (typeof value === 'string') {\n    if (value === SCAFFOLD_PLACEHOLDER) {\n      issues.push({\n        code: 'scaffold_placeholder',\n        message: `Node \"${nodeId}\" still carries the unedited scaffold placeholder \"${SCAFFOLD_PLACEHOLDER}\"; replace it with the intended content.`,\n        nodeId,\n        severity: 'blocking',\n      })\n    }\n    return\n  }\n  if (value === null || typeof value !== 'object') return\n  if (seen.has(value)) return\n  seen.add(value)\n  if (Array.isArray(value)) {\n    for (const v of value) checkScaffold(v, seen, nodeId, issues)\n  } else if (isInstanceOf(value, 'Map', Map)) {\n    for (const [k, v] of value) {\n      checkScaffold(k, seen, nodeId, issues)\n      checkScaffold(v, seen, nodeId, issues)\n    }\n  } else if (isInstanceOf(value, 'Set', Set)) {\n    for (const v of value) checkScaffold(v, seen, nodeId, issues)\n  } else if (isPlainObject(value)) {\n    for (const key of Object.keys(value)) checkScaffold(value[key], seen, nodeId, issues)\n  }\n}\n\n// ── collectIssues ───────────────────────────────────────────────────────────\n/**\n * Run every submit check over a folded plan view.\n *\n * The checks are grouped into three families:\n *\n * **Topology** — exactly one `entry` with no incoming edges; every other node reachable from it;\n * acyclicity over every handle (a diamond fan-in is not a cycle); every `join` a diamond (its fork\n * is its immediate dominator, more than one fork→join route, no reconvergence, no nested join);\n * edge ids valid and unique; node ids valid; handle applicability per source kind; a `select`\n * must carry a `default` edge.\n *\n * **References and dataflow** — a `NodeRef` naming a missing node or an undeclared field; a\n * `first`/`last` selection resolved across a `join`; an omitted `branchId` where more than one path\n * reaches the referenced node; taint (a tainted reference may reach a `reason` prompt but not a\n * `call` node's args, with declassification only via a `call` node's `declassifies`); staged\n * values outside the encodable subset and cyclic values inside it.\n *\n * **Per-node shape** — a `call` naming a tool outside the allowlist, with `replaySafe`/\n * `onIndeterminate` unset, or the retry-unsafe-repeat contradiction; a `branch`/`select` with no\n * wired evaluator cell (and `load()`/`validate()` on every wired cell); a `transform` naming a\n * step absent from the source class's effective method set, with args failing the descriptor's\n * schema, or whose source tool's return class is undeclared; a `Media`/`Uint8Array`-returning tool\n * feeding a field-declaring node; an unedited scaffold placeholder; an unreachable `call` node.\n *\n * This function never throws on a well-typed-but-invalid plan. A malformed definition surfaces as\n * an issue rather than a crash, and every evaluator interaction is wrapped so a failing cell\n * reports an issue instead of propagating.\n *\n * @param view - The folded plan content to validate.\n * @param inputs - The injected tier-C allowlist and wired predicate cells.\n * @returns Every issue the graph raises, blocking and advisory.\n */\nexport async function collectIssues(view: RawPlanView, inputs: FreezeInputs): Promise<PlanIssue[]> {\n  const issues: PlanIssue[] = []\n\n  // ── topology: entry ───────────────────────────────────────────────────────\n  const entries = entryNodes(view)\n  if (entries.length === 0) {\n    issues.push({\n      code: 'no_entry',\n      message: 'The plan has no entry node, so nothing can start; add exactly one entry node.',\n      severity: 'blocking',\n    })\n  }\n  if (entries.length > 1) {\n    issues.push({\n      code: 'multiple_entries',\n      message:\n        `The plan has ${entries.length} entry nodes, so the executor cannot tell which to ` +\n        `materialise; keep exactly one entry node.`,\n      severity: 'blocking',\n    })\n  }\n  for (const e of entries) {\n    if (incoming(view, e.id).length > 0) {\n      issues.push({\n        code: 'entry_has_incoming',\n        message: `Entry node \"${e.id}\" has incoming edges; the entry must have no incoming edges.`,\n        nodeId: e.id,\n        severity: 'blocking',\n      })\n    }\n  }\n  const entryId = entries.length === 1 ? entries[0].id : undefined\n\n  // ── topology: acyclicity (over every handle) ─────────────────────────────\n  const cycle = findCycle(view)\n  if (cycle) {\n    issues.push({\n      code: 'cycle',\n      message:\n        `The graph contains a cycle closed by edge \"${cycle.edgeId}\" (${cycle.from} → ${cycle.to}); ` +\n        `remove the cycle — an error or default edge back to an ancestor is still a cycle.`,\n      edgeId: cycle.edgeId,\n      severity: 'blocking',\n    })\n  }\n\n  // ── topology: node ids ───────────────────────────────────────────────────\n  for (const node of view.nodes) {\n    if (!isValidNodeId(node.id)) {\n      issues.push({\n        code: 'invalid_node_id',\n        message:\n          `Node id \"${node.id}\" is not a valid snake_case id (lowercase letters, digits, ` +\n          `underscores, no \"/\" and no leading \".\"); rename it so it cannot be mistaken for a path.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n  }\n\n  // ── topology: edge ids valid and unique ─────────────────────────────────\n  const seenEdgeIds = new Map<string, PlanEdge>()\n  for (const edge of view.edges) {\n    if (!isValidEdgeId(edge.id)) {\n      issues.push({\n        code: 'invalid_edge_id',\n        message:\n          `Edge id \"${edge.id}\" does not match /^[A-Za-z0-9_-]{1,64}$/; rename it so the route ` +\n          `renderer cannot be forged.`,\n        edgeId: edge.id,\n        severity: 'blocking',\n      })\n    }\n    if (seenEdgeIds.has(edge.id)) {\n      issues.push({\n        code: 'duplicate_edge_id',\n        message:\n          `Edge id \"${edge.id}\" is used by more than one edge; rename one of them — edge ids are ` +\n          `identifiers and must be unique.`,\n        edgeId: edge.id,\n        severity: 'blocking',\n      })\n    } else {\n      seenEdgeIds.set(edge.id, edge)\n    }\n  }\n\n  // ── topology: handle applicability ───────────────────────────────────────\n  for (const edge of view.edges) {\n    const src = nodeById(view, edge.from)\n    if (src && !handleAppliesTo(src.kind, edge.handle)) {\n      issues.push({\n        code: 'invalid_handle',\n        message:\n          `Edge \"${edge.id}\" uses handle \"${edge.handle}\" from a ${src.kind} node, which does not ` +\n          `allow that handle; use a handle the source kind permits.`,\n        edgeId: edge.id,\n        severity: 'blocking',\n      })\n    }\n  }\n\n  // ── topology: select must have a default edge ────────────────────────────\n  for (const node of view.nodes) {\n    if (node.kind === 'select') {\n      const hasDefault = outgoing(view, node.id).some((e) => e.handle === 'default')\n      if (!hasDefault) {\n        issues.push({\n          code: 'select_missing_default',\n          message:\n            `Select node \"${node.id}\" has no \"default\" edge; a select must carry a default so an ` +\n            `unmatched case has somewhere to go.`,\n          nodeId: node.id,\n          severity: 'blocking',\n        })\n      }\n    }\n  }\n\n  // ── topology: reachability and the diamond-join rule (need a single entry) ─\n  if (entryId !== undefined) {\n    const reachable = reachableFrom(view, entryId)\n    for (const node of view.nodes) {\n      if (node.id !== entryId && !reachable.has(node.id)) {\n        issues.push({\n          code: 'unreachable_node',\n          message: `Node \"${node.id}\" is not reachable from the entry node; connect it or remove it.`,\n          nodeId: node.id,\n          severity: 'blocking',\n        })\n      }\n    }\n\n    const joins = view.nodes.filter((n) => n.kind === 'join')\n    for (const join of joins) {\n      const fork = immediateDominator(view, entryId, join.id)\n      if (fork === undefined) {\n        issues.push({\n          code: 'join_no_fork',\n          message:\n            `Join \"${join.id}\" has no immediate dominator from the entry; a join must close a ` +\n            `fan-out that a single ancestor opened.`,\n          nodeId: join.id,\n          severity: 'blocking',\n        })\n        continue\n      }\n      const routes = routesBetween(view, fork, join.id)\n      if (routes.length <= 1) {\n        issues.push({\n          code: 'join_not_diamond',\n          message:\n            `Join \"${join.id}\" has only ${routes.length} route(s) from its fork \"${fork}\"; a join ` +\n            `must close more than one distinct fork→join route, so this join should not exist.`,\n          nodeId: join.id,\n          severity: 'blocking',\n        })\n        continue\n      }\n      // The fork→join region: every node on a fork→join route, excluding the fork and the join.\n      const region = new Set<NodeId>()\n      for (const route of routes) {\n        for (let i = 1; i < route.length - 1; i++) region.add(route[i])\n      }\n      for (const rid of region) {\n        const rnode = nodeById(view, rid)\n        if (rnode && rnode.kind === 'join') {\n          issues.push({\n            code: 'nested_join',\n            message:\n              `Join \"${join.id}\" contains a nested join \"${rid}\" inside its fork→join region; a ` +\n              `diamond must not contain another barrier.`,\n            nodeId: join.id,\n            severity: 'blocking',\n          })\n          break\n        }\n        if (incoming(view, rid).length > 1) {\n          issues.push({\n            code: 'join_reconvergence',\n            message:\n              `Join \"${join.id}\" has reconverged inside its diamond at \"${rid}\" (in-degree > 1); ` +\n              `the fork→join region must contain no node with in-degree > 1 other than the join.`,\n            nodeId: join.id,\n            severity: 'blocking',\n          })\n          break\n        }\n      }\n    }\n  }\n\n  // ── references and dataflow ──────────────────────────────────────────────\n  const joins = view.nodes.filter((n) => n.kind === 'join')\n  const allRefs: { ref: NodeRefType; nodeId: NodeId }[] = []\n  for (const node of view.nodes) {\n    const refs: NodeRefType[] = []\n    collectRefs(node.definition, refs, new Set<object>())\n    for (const ref of refs) allRefs.push({ ref, nodeId: node.id })\n  }\n\n  for (const { ref, nodeId } of allRefs) {\n    const target = nodeById(view, ref.node)\n    if (!target) {\n      issues.push({\n        code: 'missing_reference',\n        message: `Node \"${nodeId}\" references node \"${ref.node}\", which does not exist; fix the reference.`,\n        nodeId,\n        severity: 'blocking',\n      })\n      continue\n    }\n    const paths = declaredFieldPaths(target)\n    if (ref.path !== undefined && paths.length > 0 && !pathIsDeclared(paths, ref.path)) {\n      issues.push({\n        code: 'undeclared_field',\n        message:\n          `Node \"${nodeId}\" references path \"${ref.path}\" on node \"${ref.node}\", which does not ` +\n          `declare that field; reference a declared field.`,\n        nodeId,\n        severity: 'blocking',\n      })\n    }\n    if (ref.select === 'first' || ref.select === 'last') {\n      const acrossJoin = joins.some((j) => reachableFrom(view, j.id).has(ref.node))\n      if (acrossJoin) {\n        issues.push({\n          code: 'first_last_across_join',\n          message:\n            `Node \"${nodeId}\" selects \"${ref.select}\" from node \"${ref.node}\", which is reached ` +\n            `across a join; we ship no automatic pairing, so a first/last selection across a join ` +\n            `is refused.`,\n          nodeId,\n          severity: 'blocking',\n        })\n      }\n    }\n    if (ref.branchId === undefined && entryId !== undefined) {\n      const routes = routesBetween(view, entryId, ref.node)\n      if (routes.length > 1) {\n        issues.push({\n          code: 'ambiguous_reference',\n          message:\n            `Node \"${nodeId}\" references node \"${ref.node}\" without a branchId, but more than one ` +\n            `path reaches it; name which execution to read.`,\n          nodeId,\n          severity: 'blocking',\n        })\n      }\n    }\n  }\n\n  // ── taint ────────────────────────────────────────────────────────────────\n  if (entryId !== undefined) {\n    const tainted = computeTainted(view, entryId)\n    for (const node of view.nodes) {\n      if (node.kind !== 'call') continue\n      const declassifies =\n        Array.isArray(node.definition.declassifies) && node.definition.declassifies.length > 0\n      if (declassifies) continue // the sanctioned declassification point\n      const refs: NodeRefType[] = []\n      collectRefs(node.definition.args, refs, new Set<object>())\n      for (const ref of refs) {\n        const source = tainted.get(ref.node)\n        if (source === undefined) continue\n        const root = ref.path === undefined ? undefined : (ref.path.split('.')[0] ?? ref.path)\n        const readsTaintedField =\n          source.fields === 'all'\n            ? true\n            : root === undefined\n              ? source.fields.size > 0\n              : source.fields.has(root)\n        if (readsTaintedField) {\n          issues.push({\n            code: 'tainted_call_arg',\n            message:\n              `Call node \"${node.id}\" passes tainted data from node \"${ref.node}\" into its args; ` +\n              `a tainted reference may reach a reason prompt but not a call node's args. ` +\n              `Declassify via this call node's \"declassifies\" field, or route the value through a ` +\n              `sanitising step first.`,\n            nodeId: node.id,\n            severity: 'blocking',\n          })\n        }\n      }\n    }\n  }\n\n  // ── per-node shape: call ─────────────────────────────────────────────────\n  for (const node of view.nodes) {\n    if (node.kind !== 'call') continue\n    const def = node.definition\n    if (!inputs.invocable.has(def.tool)) {\n      const available = inputs.invocable.names()\n      issues.push({\n        code: 'unknown_tool',\n        message:\n          `Call node \"${node.id}\" names tool \"${def.tool}\", which is not on the allowlist; use one ` +\n          `of the available tools: ${available.join(', ')}.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n    if (readField(def, 'replaySafe') === undefined) {\n      issues.push({\n        code: 'missing_replay_safe',\n        message: `Call node \"${node.id}\" does not set \"replaySafe\"; it is required and has no default.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n    if (readField(def, 'onIndeterminate') === undefined) {\n      issues.push({\n        code: 'missing_on_indeterminate',\n        message: `Call node \"${node.id}\" does not set \"onIndeterminate\"; it is required and has no default.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n    if (def.onIndeterminate === 'retry' && def.replaySafe === false) {\n      issues.push({\n        code: 'retry_unsafe_repeat',\n        message:\n          `Call node \"${node.id}\" sets onIndeterminate \"retry\" with replaySafe false — a ` +\n          `contradiction: it is asserted unsafe to repeat and to be repeated.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n    const ret = inputs.invocable.returns(def.tool)\n    if (ret && (ret.kind === 'media' || ret.kind === 'bytes') && def.output.length > 0) {\n      issues.push({\n        code: 'media_feeds_fields',\n        message:\n          `Call node \"${node.id}\" returns ${ret.kind} but declares output fields; bytes and media ` +\n          `are not pathable, so a field-declaring node cannot consume them.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n  }\n\n  // ── per-node shape: transform ────────────────────────────────────────────\n  for (const node of view.nodes) {\n    if (node.kind !== 'transform') continue\n    const source = node.definition.source\n    const sourceNode = nodeById(view, source.node)\n    if (!sourceNode) continue // missing reference reported above\n    if (sourceNode.kind !== 'call') {\n      issues.push({\n        code: 'transform_source_not_call',\n        message:\n          `Transform \"${node.id}\" reads its source from node \"${source.node}\", which is not a ` +\n          `call node; a transform must read a call node's artifact output.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n      continue\n    }\n    const tool = sourceNode.definition.tool\n    const ret = inputs.invocable.returns(tool)\n    if (ret === undefined) {\n      issues.push({\n        code: 'transform_undeclared_tool',\n        message:\n          `Transform \"${node.id}\" reads from tool \"${tool}\", whose return class is not declared; ` +\n          `declare what the tool returns so the legal step set is known — the battery never guesses ` +\n          `a class.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n      continue\n    }\n    if (ret.kind !== 'artifact') {\n      issues.push({\n        code: 'transform_source_not_artifact',\n        message:\n          `Transform \"${node.id}\" reads from tool \"${tool}\", which does not return an artifact; a ` +\n          `transform needs an artifact class to name its steps.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n      continue\n    }\n    const methods = effectiveToolMethods(ret.artifactClass)\n    for (const step of node.definition.steps) {\n      const desc = methods.find((m) => m.name === step.name)\n      if (!desc) {\n        const legal = methods.map((m) => m.name)\n        issues.push({\n          code: 'transform_unknown_step',\n          message:\n            `Transform \"${node.id}\" names step \"${step.name}\", which is not a method of the source ` +\n            `artifact class; use one of: ${legal.join(', ')}.`,\n          nodeId: node.id,\n          severity: 'blocking',\n        })\n        continue\n      }\n      if (desc.argsSchema && step.args !== undefined && !passesSchema(desc.argsSchema, step.args)) {\n        issues.push({\n          code: 'transform_step_args',\n          message:\n            `Transform \"${node.id}\" step \"${step.name}\" has args that fail that method's schema; ` +\n            `fix the args.`,\n          nodeId: node.id,\n          severity: 'blocking',\n        })\n      }\n    }\n  }\n\n  // ── per-node shape: branch/select evaluator wiring ───────────────────────\n  for (const node of view.nodes) {\n    if (node.kind !== 'branch' && node.kind !== 'select') continue\n    const evaluatorId = node.definition.evaluator\n    const cell = inputs.evaluators.find((e) => e.id === evaluatorId)\n    if (!cell) {\n      issues.push({\n        code: 'unwired_evaluator',\n        message:\n          `${node.kind === 'branch' ? 'Branch' : 'Select'} node \"${node.id}\" names evaluator ` +\n          `\"${evaluatorId}\", which is not wired; supply a cell with that id.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n      continue\n    }\n    try {\n      await cell.load()\n      await cell.validate(node)\n    } catch (err) {\n      issues.push({\n        code: 'evaluator_error',\n        message:\n          `Evaluator \"${evaluatorId}\" failed to load or validate ${node.kind} node \"${node.id}\": ` +\n          `${isError(err) ? err.message : String(err)}.`,\n        nodeId: node.id,\n        severity: 'blocking',\n      })\n    }\n  }\n\n  // ── per-node shape: encodability, scaffold, unreachable call ─────────────\n  for (const node of view.nodes) {\n    checkEncodable(node.definition, new Set<unknown>(), node.id, issues)\n    checkScaffold(node.definition, new Set<unknown>(), node.id, issues)\n  }\n\n  // The unreachable-`call` check is implemented TWICE, in independent derivations, and that is\n  // not dead code: the bug this family guards — a `call` node that can never run because nothing\n  // reaches it — shipped twice in the prior art, each time because a single reachability walk was\n  // subtly wrong (one missed edges over a particular handle, the other mishandled the entry). Two\n  // independent walks cannot share the same blind spot, so a `call` is reported unreachable only\n  // when BOTH derivations agree it is. The first derivation delegates to the plan module's\n  // `reachableFrom`; the second is `manualReachable`, written from scratch above.\n  if (entryId !== undefined) {\n    const reachableA = reachableFrom(view, entryId)\n    const reachableB = manualReachable(view, entryId)\n    for (const node of view.nodes) {\n      if (node.kind !== 'call') continue\n      if (!reachableA.has(node.id) || !reachableB.has(node.id)) {\n        issues.push({\n          code: 'unreachable_call',\n          message:\n            `Call node \"${node.id}\" is unreachable from the entry node, so it can never run; ` +\n            `connect it or remove it.`,\n          nodeId: node.id,\n          severity: 'blocking',\n        })\n      }\n    }\n  }\n\n  return issues\n}\n\n// ── freezePlan ───────────────────────────────────────────────────────────────\n/**\n * Freeze a plan: fold the log, validate, and commit the `editable → reviewable` transition.\n *\n * The op log is folded into a `RawPlanView`, the fold's own issues are carried over (minus the\n * fold's advisory `duplicate_edge_id`, which {@link collectIssues} reports as a blocking check),\n * and {@link collectIssues} runs over the folded graph. Only when no issue is `blocking` is the\n * store's `transition` called, passing the folded digest as `expectedDigest`.\n *\n * The digest is what makes the commit safe rather than racy: content is validated at digest D and\n * the store commits only if the plan is still at D, so a concurrent edit invalidates the\n * transition instead of slipping past an already-passed check. If the transition is rejected\n * (the digest moved or the state is not `editable`), a `transition_rejected` blocking issue is\n * appended and the freeze reports failure.\n *\n * @param store - The plan store holding the plan.\n * @param planId - Identity of the plan to freeze.\n * @param inputs - The fully-resolved tier-C allowlist and wired predicate cells.\n * @returns Whether the freeze succeeded, and every issue the plan raised.\n */\nexport async function freezePlan(\n  store: PlanStore,\n  planId: string,\n  inputs: FreezeInputs\n): Promise<{ ok: boolean; issues: PlanIssue[] }> {\n  const ops = await store.readOps(planId)\n  const provenance = await store.readProvenance(planId)\n  const { view, issues: foldIssues } = foldOps(planId, ops, provenance)\n\n  const collected = await collectIssues(view, inputs)\n  // The fold reports a same-id edge collision as advisory; collectIssues owns it as a blocking\n  // check, so drop the advisory duplicate to avoid double-reporting the same defect.\n  const fold = foldIssues.filter((i) => i.code !== 'duplicate_edge_id')\n  const issues = [...fold, ...collected]\n\n  if (issues.some((i) => i.severity === 'blocking')) {\n    return { ok: false, issues }\n  }\n\n  const result = await store.transition(planId, {\n    from: 'editable',\n    to: 'reviewable',\n    expectedDigest: view.digest,\n  })\n  if (result.ok) return { ok: true, issues }\n\n  return {\n    ok: false,\n    issues: [\n      ...issues,\n      {\n        code: 'transition_rejected',\n        message: `The plan changed while it was being validated (${result.reason}); re-run freeze.`,\n        severity: 'blocking',\n      },\n    ],\n  }\n}\n"],"mappings":"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"}