- C-05: Add flowgraph-api.md with complete public API surface - C-06: Document <Map> component in workflow-templates.md - C-07: Specify Conditional else-branch behavior - C-08: Add lifecycle/ownership section to reactive-execution.md - C-09: Add consumer-integration.md end-to-end walkthrough - W-02: Add reactive error boundary semantics (3 levels) - W-03: Complete ReactiveContext interface definition - W-04: Add template composition rules (8 rules) - W-05: Document removeChild for both HostConfigs - W-06: Document signal/effect disposal lifecycle - W-07: Add ADR-004 (no schema version field) - W-08: Add type compatibility depth/contract to analysis.md - W-11: Add performance characteristics section - S-01: Getting Started merged into consumer-integration.md - S-02: Add flow diagrams for template rendering pipeline - S-03: Add node status state machine diagram - S-04: Add testing strategy section - S-06: Validate source structure cross-references Review round 2 fixes: - Define TemplateNodeAttrs as alias for OperationNodeAttrs - Document CallEventMapValue and CallResult types in schema.md - Standardize CycleError naming (replace CircularDependencyError) - Add function form to Map.over type definition - Define Map aggregate completion/failure semantics - Fix immutability claim for fromCallEvents - Clarify edgeType storage alongside OperationEdgeAttrs - Clarify WorkflowNode.status === statusMap (same Signal) - Add component-to-tag mapping for WorkflowTag
9.8 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-05-20 |
Error Handling
FlowgraphError hierarchy, validation error collection, and error boundaries.
Design Principle
Flowgraph follows taskgraph's error handling pattern:
- Programmer errors throw — invalid arguments, duplicate node IDs, cycles where acyclicity is enforced
- Operational conditions return structured results — validation errors, type mismatches, unreachable nodes
- Graph mutations throw on constraint violations — adding a duplicate node throws
DuplicateNodeError, adding a cycle-creating edge throwsCycleError
This means validation functions (validateGraph(), validateSchema(), validateTemplate()) never throw — they collect issues and return them. But construction functions (fromSpecs(), addNode(), addEdge()) do throw on constraint violations.
Error Hierarchy
FlowgraphError # Base class for all flowgraph errors
├── ConstructionError # Errors during graph construction
│ ├── DuplicateNodeError # Duplicate node key
│ ├── DuplicateEdgeError # Duplicate edge key
│ ├── NodeNotFoundError # Referenced node doesn't exist
│ └── CycleError # Adding an edge would create a cycle
├── ValidationError # Schema validation failed (single field)
├── GraphValidationError # Graph-level validation issue
│ ├── CycleValidationError # Cycle detected in the graph
│ └── DanglingReferenceError # Edge references non-existent node
├── TypeIncompatError # Type compatibility check failed
└── InvalidTransitionError # Invalid call status transition
FlowgraphError
class FlowgraphError extends Error {
constructor(message: string) {
super(message);
this.name = "FlowgraphError";
}
}
Base class. All flowgraph errors inherit from this.
ConstructionError
class ConstructionError extends FlowgraphError {
constructor(message: string) {
super(message);
this.name = "ConstructionError";
}
}
Base class for errors that occur during graph construction (fromSpecs(), addNode(), addEdge(), etc.).
DuplicateNodeError
class DuplicateNodeError extends ConstructionError {
constructor(public readonly key: string) {
super(`Node with key "${key}" already exists`);
this.name = "DuplicateNodeError";
}
}
Thrown when adding a node with a key that already exists in the graph.
DuplicateEdgeError
class DuplicateEdgeError extends ConstructionError {
constructor(
public readonly source: string,
public readonly target: string,
) {
super(`Edge "${source} -> ${target}" already exists`);
this.name = "DuplicateEdgeError";
}
}
Thrown when adding an edge between two nodes that already have an edge between them.
NodeNotFoundError
class NodeNotFoundError extends ConstructionError {
constructor(public readonly key: string) {
super(`Node "${key}" not found in graph`);
this.name = "NodeNotFoundError";
}
}
Thrown when referencing a node that doesn't exist (e.g., adding an edge with a non-existent endpoint).
CycleError
class CycleError extends ConstructionError {
constructor(public readonly cycles: string[][]) {
super(`Adding this edge would create a cycle: ${JSON.stringify(cycles)}`);
this.name = "CycleError";
}
}
Thrown when adding an edge would create a cycle. The cycles field contains the cycle paths that would be created.
Note: CycleError is flowgraph's cycle error, thrown by addEdge() during construction. Taskgraph uses a different error name (CircularDependencyError, thrown by topologicalOrder()). The two are distinct errors for distinct contexts — flowgraph prevents cycles at construction time, taskgraph allows cycles and detects them later.
ValidationError
interface ValidationError {
type: "schema";
nodeKey: string;
field: string;
message: string;
value?: unknown;
}
Returned by validateSchema() when a node's attributes don't match the TypeBox schema. This is a structured result, not a thrown error.
GraphValidationError
interface GraphValidationError {
type: "graph";
category: "cycle" | "dangling-reference" | "orphan-node" | "status-inconsistency";
details: unknown;
}
Returned by validateGraph() for graph-level issues:
| Category | Meaning | Details |
|---|---|---|
cycle |
The graph contains cycles | { cycles: string[][] } |
dangling-reference |
An edge references a non-existent node | { source: string, target: string } |
orphan-node |
A node has no incoming or outgoing edges | { nodeKey: string } |
status-inconsistency |
A call node has incompatible status with its parent (e.g., parent completed but child still running) | { nodeKey: string, parentKey: string, nodeStatus: string, parentStatus: string } |
InvalidTransitionError
class InvalidTransitionError extends FlowgraphError {
constructor(
public readonly requestId: string,
public readonly from: CallStatus,
public readonly to: CallStatus,
) {
super(`Invalid status transition for call ${requestId}: ${from} → ${to}`);
this.name = "InvalidTransitionError";
}
}
Thrown when updateNodeStatus() is called with an invalid transition (e.g., completed → running).
TypeIncompatError
interface TypeIncompatError {
type: "type-compat";
sourceKey: string;
targetKey: string;
compatible: false;
mismatches: TypeMismatch[];
}
interface TypeMismatch {
path: string;
expected: string;
actual: string;
}
Returned by validateTemplate() and analyzeTypeCompat() when an edge between operations has incompatible type schemas. This is a structured result, not a thrown error.
Error Collection
Validation functions collect all issues into an array and return them. They do not throw on the first error:
const errors = graph.validate();
// errors is AnyValidationError[], which may be empty
for (const error of errors) {
if (error.type === "schema") {
console.log(`Node ${error.nodeKey} has invalid field ${error.field}: ${error.message}`);
} else if (error.type === "graph" && error.category === "cycle") {
console.log(`Graph has cycles: ${error.details.cycles}`);
}
}
This "collect all errors" pattern allows consumers to see all issues at once, rather than fixing them one at a time.
AnyValidationError
type AnyValidationError = ValidationError | GraphValidationError | TypeIncompatError;
Union type for all validation errors. Consumers use the type discriminator to handle each category:
switch (error.type) {
case "schema": // ValidationError
case "graph": // GraphValidationError
case "type-compat": // TypeIncompatError
}
Throwing vs. Returning
The distinction between thrown errors and returned errors:
| Function | Behavior | Rationale |
|---|---|---|
addNode(key, attrs) |
Throws DuplicateNodeError on duplicate key |
Adding a duplicate is a programmer error |
addEdge(source, target) |
Throws NodeNotFoundError on missing endpoint |
Edge without endpoints is invalid |
addEdge(source, target) |
Throws CycleError if edge creates cycle |
DAG invariant must be maintained |
updateNodeStatus(id, status) |
Throws InvalidTransitionError on invalid transition |
State machine must be enforced |
validateSchema() |
Returns ValidationError[] |
Schema issues are validations, not crashes |
validateGraph() |
Returns GraphValidationError[] |
Graph issues are validations, not crashes |
validateTemplate() |
Returns AnyValidationError[] |
Template issues are validations, not crashes |
analyzeTypeCompat() |
Returns TypeCompatResult (includes mismatches) |
Type incompatibility is advisory, not blocking |
topologicalOrder() |
Throws CycleError on cycles |
No valid ordering exists from a cyclic graph |
This matches taskgraph's pattern: construction enforces invariants (throwing on violations), validation reports issues (returning error arrays).
Error Boundaries in the Call Graph
The call graph has an additional error boundary: updateFromEvent(). Call events arrive from the pub/sub layer and may reference unknown operations or have invalid transitions. The error boundary handles these gracefully:
- Unknown
requestIdin acall.respondedevent → log warning, ignore the event (the call may have been created by a different process) - Invalid status transition → log warning, ignore the event (the call may have transitioned in a different order)
- Unknown
operationId→ create the node anyway withstatus: "pending"(the operation may be registered later)
This makes updateFromEvent() resilient to out-of-order, duplicate, and partial events. Errors are logged but don't crash the process.
Constraints
- Validation functions never throw — they collect errors and return them. This is a contract.
- Construction functions throw on invariant violations — adding a cycle-creating edge is a programming error, not a validation finding.
- All errors have structured data —
CycleErrorincludes cycle paths,InvalidTransitionErrorincludes from/to status,TypeIncompatErrorincludes mismatch details. - Error messages are descriptive — errors include enough context to diagnose the problem without additional lookups.
- Error classes follow the taskgraph pattern — naming, structure, and behavior match
@alkdev/taskgraph_ts/src/error/.
References
- Taskgraph errors:
@alkdev/taskgraph_ts/src/error/ - Call protocol events:
@alkdev/operations/src/call.ts - Schema: schema.md