{"version":3,"file":"dag-scheduler.mjs","names":[],"sources":["../../../../../../../ai/src/planner/dag-scheduler.ts"],"sourcesContent":["import type { PlannerStep } from \"../contracts/planner/planner-plan.type\";\nimport { PlannerPlanInvalidError } from \"../errors/planner-plan-invalid-error\";\n\n/**\n * One node in the planner's execution DAG — a plan step plus the\n * resolved structural metadata the scheduler needs to order it.\n *\n * `id` is the step's own `id` when present, falling back to the step's\n * array index stringified (exactly as {@link PlannerStep.id} documents).\n * `dependencies` is the resolved set of node ids this step waits on,\n * de-duplicated and self-references dropped.\n */\nexport type DagNode = {\n  /** Stable id — the step's own `id`, or its array index as a string. */\n  id: string;\n  /** 0-based position of the step in the original plan array. */\n  index: number;\n  /** The plan step this node schedules. */\n  step: PlannerStep;\n  /** Resolved ids this step depends on (subset of the DAG's node ids). */\n  dependencies: string[];\n};\n\n/**\n * The built execution DAG — the ordered node list plus the lookups the\n * scheduler walks. Ordering follows the original plan array so a\n * dependency-free plan executes in author order, level by level.\n */\nexport type PlannerDag = {\n  /** Nodes in original plan order. */\n  nodes: DagNode[];\n  /** id → node, for dependency resolution and sink detection. */\n  byId: Map<string, DagNode>;\n  /** id → ids of the nodes that depend on it (reverse edges). */\n  dependents: Map<string, string[]>;\n};\n\n/**\n * Build the execution DAG from a plan's steps.\n *\n * Each step's `id` (falling back to its array index) and its `dependsOn`\n * become an adjacency list. A `dependsOn` that names a step not in the\n * plan, or any dependency cycle, raises a typed\n * {@link PlannerPlanInvalidError} BEFORE any step runs — the same error\n * class `generatePlan` uses for an unusable plan, with forensic context.\n *\n * @throws PlannerPlanInvalidError on a duplicate id, an unknown\n *   `dependsOn` target, or a cycle.\n */\nexport function buildDag(steps: PlannerStep[], plannerName = \"planner\"): PlannerDag {\n  const nodes: DagNode[] = [];\n  const byId = new Map<string, DagNode>();\n\n  // Pass 1 — assign every step a stable id (own id or array index) and\n  // index the nodes. Duplicate explicit ids are a malformed plan.\n  for (let index = 0; index < steps.length; index++) {\n    const step = steps[index] as PlannerStep;\n    const id = step.id ?? String(index);\n\n    if (byId.has(id)) {\n      throw new PlannerPlanInvalidError(\n        `ai.planner(\"${plannerName}\"): duplicate step id \"${id}\" in DAG plan`,\n        { context: { id } },\n      );\n    }\n\n    const node: DagNode = { id, index, step, dependencies: [] };\n    nodes.push(node);\n    byId.set(id, node);\n  }\n\n  // Pass 2 — resolve dependencies against the id set; reject unknowns,\n  // dedupe, and drop self-references (a no-op edge, never a cycle).\n  const dependents = new Map<string, string[]>();\n\n  for (const node of nodes) {\n    const seen = new Set<string>();\n\n    for (const dependency of node.step.dependsOn ?? []) {\n      if (dependency === node.id) {\n        continue;\n      }\n\n      if (!byId.has(dependency)) {\n        throw new PlannerPlanInvalidError(\n          `ai.planner(\"${plannerName}\"): step \"${node.id}\" depends on unknown step \"${dependency}\"`,\n          { context: { id: node.id, dependency } },\n        );\n      }\n\n      if (seen.has(dependency)) {\n        continue;\n      }\n\n      seen.add(dependency);\n      node.dependencies.push(dependency);\n\n      const reverse = dependents.get(dependency) ?? [];\n      reverse.push(node.id);\n      dependents.set(dependency, reverse);\n    }\n  }\n\n  assertAcyclic(nodes, byId, plannerName);\n\n  return { nodes, byId, dependents };\n}\n\n/**\n * Compute the next ready set: nodes not yet done whose every dependency\n * is in `completed`. Preserves original plan order so a level dispatches\n * deterministically. A node whose dependency is `unreachable` (a failed\n * or skipped ancestor) is NOT ready — it never becomes ready and is\n * recorded skipped by the caller.\n */\nexport function readyNodes(\n  dag: PlannerDag,\n  completed: ReadonlySet<string>,\n  done: ReadonlySet<string>,\n): DagNode[] {\n  return dag.nodes.filter(\n    (node) =>\n      !done.has(node.id) &&\n      node.dependencies.every((dependency) => completed.has(dependency)),\n  );\n}\n\n/**\n * The topological sink(s) — nodes nothing depends on. Used to define the\n * \"final output\" under parallelism: with an `output` schema set, a\n * single sink is the unambiguous final step; multiple sinks are a\n * convergence error the caller surfaces.\n */\nexport function sinkNodes(dag: PlannerDag): DagNode[] {\n  return dag.nodes.filter((node) => (dag.dependents.get(node.id) ?? []).length === 0);\n}\n\n/**\n * Depth-first cycle detection over the dependency edges. A back-edge to\n * a node on the current recursion stack means a cycle — raised as a\n * typed {@link PlannerPlanInvalidError} naming the offending node.\n */\nfunction assertAcyclic(\n  nodes: DagNode[],\n  byId: Map<string, DagNode>,\n  plannerName: string,\n): void {\n  const VISITING = 1;\n  const DONE = 2;\n  const state = new Map<string, number>();\n\n  const visit = (node: DagNode): void => {\n    const current = state.get(node.id);\n\n    if (current === DONE) {\n      return;\n    }\n\n    if (current === VISITING) {\n      throw new PlannerPlanInvalidError(\n        `ai.planner(\"${plannerName}\"): dependency cycle detected at step \"${node.id}\"`,\n        { context: { id: node.id } },\n      );\n    }\n\n    state.set(node.id, VISITING);\n\n    for (const dependency of node.dependencies) {\n      visit(byId.get(dependency) as DagNode);\n    }\n\n    state.set(node.id, DONE);\n  };\n\n  for (const node of nodes) {\n    visit(node);\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiDA,SAAgB,SAAS,OAAsB,cAAc,WAAuB;CAClF,MAAM,QAAmB,CAAC;CAC1B,MAAM,uBAAO,IAAI,IAAqB;CAItC,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;EACjD,MAAM,OAAO,MAAM;EACnB,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK;EAElC,IAAI,KAAK,IAAI,EAAE,GACb,MAAM,IAAI,wBACR,eAAe,YAAY,yBAAyB,GAAG,gBACvD,EAAE,SAAS,EAAE,GAAG,EAAE,CACpB;EAGF,MAAM,OAAgB;GAAE;GAAI;GAAO;GAAM,cAAc,CAAC;EAAE;EAC1D,MAAM,KAAK,IAAI;EACf,KAAK,IAAI,IAAI,IAAI;CACnB;CAIA,MAAM,6BAAa,IAAI,IAAsB;CAE7C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,uBAAO,IAAI,IAAY;EAE7B,KAAK,MAAM,cAAc,KAAK,KAAK,aAAa,CAAC,GAAG;GAClD,IAAI,eAAe,KAAK,IACtB;GAGF,IAAI,CAAC,KAAK,IAAI,UAAU,GACtB,MAAM,IAAI,wBACR,eAAe,YAAY,YAAY,KAAK,GAAG,6BAA6B,WAAW,IACvF,EAAE,SAAS;IAAE,IAAI,KAAK;IAAI;GAAW,EAAE,CACzC;GAGF,IAAI,KAAK,IAAI,UAAU,GACrB;GAGF,KAAK,IAAI,UAAU;GACnB,KAAK,aAAa,KAAK,UAAU;GAEjC,MAAM,UAAU,WAAW,IAAI,UAAU,KAAK,CAAC;GAC/C,QAAQ,KAAK,KAAK,EAAE;GACpB,WAAW,IAAI,YAAY,OAAO;EACpC;CACF;CAEA,cAAc,OAAO,MAAM,WAAW;CAEtC,OAAO;EAAE;EAAO;EAAM;CAAW;AACnC;;;;;;;;AASA,SAAgB,WACd,KACA,WACA,MACW;CACX,OAAO,IAAI,MAAM,QACd,SACC,CAAC,KAAK,IAAI,KAAK,EAAE,KACjB,KAAK,aAAa,OAAO,eAAe,UAAU,IAAI,UAAU,CAAC,CACrE;AACF;;;;;;;AAQA,SAAgB,UAAU,KAA4B;CACpD,OAAO,IAAI,MAAM,QAAQ,UAAU,IAAI,WAAW,IAAI,KAAK,EAAE,KAAK,CAAC,EAAC,CAAE,WAAW,CAAC;AACpF;;;;;;AAOA,SAAS,cACP,OACA,MACA,aACM;CACN,MAAM,WAAW;CACjB,MAAM,OAAO;CACb,MAAM,wBAAQ,IAAI,IAAoB;CAEtC,MAAM,SAAS,SAAwB;EACrC,MAAM,UAAU,MAAM,IAAI,KAAK,EAAE;EAEjC,IAAI,YAAY,MACd;EAGF,IAAI,YAAY,UACd,MAAM,IAAI,wBACR,eAAe,YAAY,yCAAyC,KAAK,GAAG,IAC5E,EAAE,SAAS,EAAE,IAAI,KAAK,GAAG,EAAE,CAC7B;EAGF,MAAM,IAAI,KAAK,IAAI,QAAQ;EAE3B,KAAK,MAAM,cAAc,KAAK,cAC5B,MAAM,KAAK,IAAI,UAAU,CAAY;EAGvC,MAAM,IAAI,KAAK,IAAI,IAAI;CACzB;CAEA,KAAK,MAAM,QAAQ,OACjB,MAAM,IAAI;AAEd"}