/** * Graph Execution Engine v2 — Join (Fan-In) Evaluator * * Version: 2.0 * Date: 2026-07-24 * * Pure graph-theoretic fan-in mechanism. A convergence node collects the * {@link EdgePayload}s arriving along its incoming edges and activates once * the declared join strategy is satisfied. The accumulated upstream results * stay exposed on `node.upstreamResults` for consumers to inspect (e.g. * `approval-payload.ts` builds the human-facing approval context from them) — * this module only enforces the join and records the upstream payloads. * * Design references: * - `.rolebox/design/graph-model.md` §3 (join semantics) * - `.rolebox/design/orchestration-patterns.md` §1.1 (fan-in aggregation) * * All three join strategies are implemented at runtime: `all`, `any`, and * `quorum:N`. {@link joinSatisfied} exposes the compact boolean contract * (`true` iff satisfied); {@link evaluateJoin} exposes the full discriminated * verdict (`satisfied` | `failed` | `waiting`) with per-verdict reasons for * downstream diagnostics and cancellation decisions. */ import type { EdgeDeclaration, JoinConfig } from "../../types.graph-v2.ts"; import type { EngineState, NodeRuntimeState, ResolvedJoinStrategy } from "../../types.engine-v2.ts"; import type { EdgePayload } from "../../types.engine-v2.ts"; export type { ResolvedJoinStrategy }; /** * Pure resolver that projects a node's declared {@link JoinConfig} into the * runtime join-strategy shape. Absent `join` (or `strategy: all`) resolves to * "all". * * `any` resolves to the `JoinStrategy.Any` string value; `quorum:N` resolves to * `{ quorum: N }` (the required count lives on the {@link JoinConfig}, not the * strategy string). The default quorum is `1`. * * This is THE single source of truth for the join-strategy shape: it is used * both to populate the runtime field in {@link registerNode} and, via * {@link getJoinStrategy}, to drive {@link evaluateJoin} / {@link joinSatisfied} * — so evaluation and the runtime field can never diverge. */ export declare function resolveJoinStrategy(join?: JoinConfig): ResolvedJoinStrategy; /** * The required answer count of a resolved join strategy, or `undefined` for * the strategies that carry no count (`"all"` / `"any"`). * * Single reader for the quorum branch (C1): {@link evaluateJoin} and the * approval cancellation gate (`approval-handler.ts` `shouldCancel`) both read * the count through this function, so the two consumers cannot interpret the * same `{ quorum: N }` value differently. */ export declare function readQuorum(strategy: ResolvedJoinStrategy): number | undefined; /** * The declared join strategy for a node, read from its {@link JoinConfig} in * the graph declaration. * * Prefers the node's cached runtime `joinStrategy` field — populated at * provision time by {@link registerNode} from the very same * {@link resolveJoinStrategy} resolver and validated on persisted-state load — * so the per-call O(V) declaration lookup is skipped. Falls back to locating * the node's declared config and delegating to {@link resolveJoinStrategy} * only when the cache is absent (nodes hydrated from older persisted state). * Both paths resolve through the same resolver, so they stay in lockstep. */ export declare function getJoinStrategy(state: EngineState, node: NodeRuntimeState): ResolvedJoinStrategy; /** * Whether an edge is a revision back-edge: an `on_signal` edge whose filter * names `revise_needed`. These edges route revision feedback backward within * a loop group and must be excluded from in-degree computations that determine * graph roots (otherwise a loop's entry node whose only incoming edge is a * revise back-edge would never be discovered as a root, deadlocking the graph). * * Design references: * - `.rolebox/design/graph-model.md` §5.2 (signal-routed edges) * - `.rolebox/design/orchestration-patterns.md` §1.6 (bounded-cycle loop groups) */ export declare function isReviseBackEdge(edge: EdgeDeclaration): boolean; /** * Distinct node IDs that feed `node` via its incoming edges. * * This is a pure topological fact derived from the graph declaration — every * edge with `to === node.nodeId` contributes its `from` source. A node with no * incoming edges returns an empty set (a graph root, satisfied immediately). * * @param opts.excludeReviseBackEdges — when true, `revise_needed` back-edges * are skipped and do not count as upstream sources. Default `false` * preserves byte-identical behavior for all existing callers (cascade-cancel, * approval-handler, signal-propagation). */ export declare function getUpstreamNodeIds(state: EngineState, node: NodeRuntimeState, opts?: { excludeReviseBackEdges?: boolean; }): string[]; /** * Discriminated verdict of a join evaluation, produced by {@link evaluateJoin}. * * - `satisfied` — the join strategy's threshold has been met; the convergence * node may activate and run its agent+prompt on the merged fan-in context. * - `failed` — the join cannot be (further) satisfied given the signals * already received; the convergence node must propagate the worst observed * upstream signal upward (see failure-resilience.md §1.5). * - `waiting` — neither satisfied nor failed; the node keeps accumulating * upstream results until a future signal tips the balance. */ export type JoinVerdict = { kind: "satisfied"; reasons: string[]; } | { kind: "failed"; reasons: string[]; } | { kind: "waiting"; reasons: string[]; }; /** * Evaluate a node's join strategy against its accumulated upstream results, * returning a full discriminated verdict (satisfied / failed / waiting). * * This is the evaluation half of fan-in, driven by the same signal counts and * escalation-lattice rules as the design references: * - `.rolebox/design/graph-model.md` §3.1 (join strategies) * - `.rolebox/design/failure-resilience.md` §1.5 (join-failure: partial failure * at a convergence point) * - `.rolebox/design/orchestration-patterns.md` §1.1 (fan-in aggregation) * * Strategy semantics: * - No upstream edges → a graph root; satisfied immediately. * - `all` → satisfied when every upstream records `answer`; fails as soon as * any upstream records a non-`answer` terminating signal (the worst signal is * propagated); otherwise waiting. * - `any` → satisfied on the first upstream `answer`; fails only when every * upstream has emitted a non-`answer` terminating signal before any `answer` * arrived; otherwise waiting (a pending upstream may still answer). * - `quorum:N` → satisfied when `answer_count >= N`; fails when the quorum * becomes impossible, i.e. `answer_count + pending_count < N`; otherwise * waiting. */ export declare function evaluateJoin(state: EngineState, node: NodeRuntimeState): JoinVerdict; /** * Whether a node's join strategy is satisfied — i.e. the required upstream * results have been received under the declared strategy (all / any / quorum:N). * * This is a thin boolean projection of {@link evaluateJoin}: it returns `true` * exactly when the verdict is `satisfied`, and `false` for both `failed` and * `waiting`. The boolean contract is intentionally preserved so existing * callers (e.g. {@link collectUpstreamResults} caching `node.joinSatisfied`) * keep working unchanged; use {@link evaluateJoin} when the failed-vs-waiting * distinction matters. * * - No upstream edges → immediately satisfied (`true`). * - `all` → satisfied only when every upstream source recorded `answer`. * - `any` → satisfied on the first upstream `answer`. * - `quorum:N` → satisfied when at least N upstream sources recorded `answer`. * * Always returns a consistent `boolean` (strict `true`/`false`, never truthy). */ export declare function joinSatisfied(state: EngineState, node: NodeRuntimeState): boolean; /** * Record an upstream {@link EdgePayload} into the node's accumulated results, * keyed by the source node ID (`edgePayload.fromNode`). After recording, the * node's cached `joinSatisfied` flag is recomputed so callers can rely on the * field without re-deriving topology each time. * * This is the aggregation half of fan-in: the advance engine calls this when an * `answer` signal arrives along an incoming edge, then queries * `node.joinSatisfied` to decide whether to activate the node. */ export declare function collectUpstreamResults(state: EngineState, node: NodeRuntimeState, edgePayload: EdgePayload): void; //# sourceMappingURL=join-evaluator.d.ts.map