fix: architecture review - address 5 critical issues, 6 warnings, 3 suggestions

Critical fixes:
- C1: Create standalone ADR-006 file (edge type consistency),
  extract from open-questions.md inline content
- C2: Convert CallResult from plain interface to TypeBox schema,
  aligning with 'TypeBox as single source of truth' constraint
- C3: Add fromJSON() cycle detection specification - enforce
  ADR-002 DAG invariant even on deserialized input
- C4: Rewrite consumer-integration.md Phase 4 to use ADR-005
  event-append pattern instead of direct signal mutation
- C5: Fix operator precedence bug in consumer-integration.md
  (missing parentheses around OR condition)

Warnings addressed:
- W1: Fix immutability claim - operation graph is 'conventionally
  immutable', not prevented by API
- W2: Add EventLogProjection to reactive exports map
- W3: Add CallResult/CallResultSchema to schema exports map
- W4: Fix reactive-execution.md Level 1 error handling to use
  event-append pattern instead of direct signal mutation
- W5: Remove duplicate dataFlow inference description in schema.md
- W6: Clarify ADR-006 project context (flowgraph vs taskgraph)

Suggestions implemented:
- S1: Add 'reviewed' document lifecycle status between draft/stable,
  update all docs to reviewed status
- S2: Add carve-out note for analysis result types in schema.md
  constraints (they are ephemeral, not serialized)
- S3: Add isComplete() and getAggregateStatus() convenience methods
  to WorkflowReactiveRoot specification
This commit is contained in:
2026-05-21 19:40:45 +00:00
parent f3e084d02f
commit 907c33650f
14 changed files with 189 additions and 85 deletions

View File

@@ -1,5 +1,5 @@
---
status: draft
status: reviewed
last_updated: 2026-05-22
---
@@ -87,6 +87,8 @@ static fromJSON(data: FlowGraphSerialized<NodeAttrs, EdgeAttrs>): FlowGraph<Node
Deserializes from graphology native JSON format. Validates against the appropriate schema (`OperationGraphSerialized` or `CallGraphSerialized`). Throws `InvalidInputError` on validation failure.
After schema validation, `fromJSON()` validates DAG invariants by running cycle detection on the deserialized graph. If cycles are found, it throws `CycleError` with the cycle paths. This enforces ADR-002's DAG-only invariant even for externally-provided data — a corrupted or adversarial JSON input cannot produce a cyclic graph that would violate downstream assumptions (e.g., that `topologicalOrder()` always succeeds).
Round-trip guarantee: `fromSpecs()``export()``fromJSON()` is lossless.
## Mutation Methods
@@ -266,7 +268,7 @@ This is an escape hatch. Direct graph mutation bypasses flowgraph's validation (
| `validate()`, `hasCycles()` | No — reads | Validation results |
| `typeCompat()` | No — reads | `TypeCompatResult` |
**Key invariant**: The operation graph produced by `fromSpecs()` is immutable after construction — no mutation methods are exposed. If the registry changes, rebuild the graph. The call graph produced by `fromCallEvents()` supports incremental mutation via `addCall`, `updateStatus`, and `addDependency`. The initial events populate the graph, and subsequent events update it.
**Key invariant**: The operation graph produced by `fromSpecs()` is conventionally immutable — consumers should not call mutation methods on it after construction. The mutation methods (`addNode`, `addEdge`, etc.) are available for incremental construction via `new FlowGraph()` + `addOperation()`/`addTypedEdge()`. If the registry changes, rebuild the graph. The call graph produced by `fromCallEvents()` supports incremental mutation via `addCall`, `updateStatus`, and `addDependency`. The initial events populate the graph, and subsequent events update it.
## Exports Map
@@ -275,10 +277,10 @@ This is an escape hatch. Direct graph mutation bypasses flowgraph's validation (
| `@alkdev/flowgraph` | `FlowGraph`, all public types |
| `@alkdev/flowgraph/graph` | `FlowGraph`, `FlowGraphOptions` |
| `@alkdev/flowgraph/analysis` | `typeCompat`, `buildTypeEdges`, `validateGraph`, `validateTemplate`, `topologicalOrder`, `parallelGroups`, `criticalPath`, `reachableFrom` |
| `@alkdev/flowgraph/schema` | `OperationNodeAttrs`, `CallNodeAttrs`, `OperationEdgeAttrs`, `CallEdgeAttrs`, `TemplateEdgeAttrs`, `CallStatus`, `NodeStatus`, `EdgeType` |
| `@alkdev/flowgraph/schema` | `OperationNodeAttrs`, `CallNodeAttrs`, `OperationEdgeAttrs`, `CallEdgeAttrs`, `TemplateEdgeAttrs`, `CallResultSchema`, `CallResult`, `CallStatus`, `NodeStatus`, `EdgeType` |
| `@alkdev/flowgraph/component` | `Operation`, `Sequential`, `Parallel`, `Conditional`, `Map` |
| `@alkdev/flowgraph/host` | `GraphologyHostConfig`, `ReactiveHostConfig` |
| `@alkdev/flowgraph/reactive` | `WorkflowReactiveRoot`, `WorkflowNode`, `ReactiveContext` |
| `@alkdev/flowgraph/reactive` | `WorkflowReactiveRoot`, `WorkflowNode`, `ReactiveContext`, `EventLogProjection` |
| `@alkdev/flowgraph/error` | `FlowgraphError`, `ConstructionError`, `CycleError`, `ValidationError`, `TypeIncompatError`, `InvalidTransitionError` |
## Constraints
@@ -288,7 +290,7 @@ This is an escape hatch. Direct graph mutation bypasses flowgraph's validation (
- **No parallel edges** — `addEdge` throws `DuplicateEdgeError` if an edge already exists between the same (source, target) pair.
- **No self-loops** — enforced at the graphology level (`allowSelfLoops: false`).
- **Edge keys are deterministic** — `${source}->${target}` format. No user-specified edge keys.
- **Operation graph is immutable after construction** — no mutation methods are exposed after `fromSpecs()`. If the registry changes, rebuild the graph.
- **Operation graph is conventionally immutable after construction** — `fromSpecs()` produces a graph that consumers should treat as immutable. Mutation methods (`addOperation`, `addTypedEdge`) are available for incremental construction but should not be used on factory-produced graphs. If the registry changes, rebuild the graph.
- **Call graph supports incremental mutation** — `addCall`, `updateStatus`, `addDependency` are the primary mutation paths.
## Open Questions