resolve architecture review round 2: criticals, warnings, suggestions

- 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
This commit is contained in:
2026-05-19 13:05:35 +00:00
parent 1dbaccbde3
commit eaeba38e71
13 changed files with 1489 additions and 57 deletions

View File

@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-05-19
last_updated: 2026-05-20
---
# Analysis Functions
@@ -23,6 +23,46 @@ All analysis functions are pure: they don't mutate the graph, they don't depend
## Type Compatibility
### Compatibility Contract
The `typeCompat` function defines a clear contract for what each result means:
| Result | Meaning | What the consumer should do |
|--------|---------|-----------------------------|
| `{ compatible: true }` | Output schema is a subtype of input schema | Allow the edge; data can flow from source to target without transformation |
| `{ compatible: true, detail }` | Compatible with notes | Allow the edge; the `detail` string describes why (e.g., "output has extra fields beyond input requirements") |
| `{ compatible: false, mismatches }` | Structural incompatibility | Reject the edge or add a transformation step; `mismatches` lists specific field-level problems |
| No edge at all | Unknown compatibility (one or both schemas are `Type.Unknown()`) | Neither compatible nor incompatible; no edge is created |
### Depth of Compatibility Checking
`typeCompat` performs **deep recursive structural comparison**:
1. **Top-level fields** — all required fields in `inputSchema` must be present in `outputSchema`
2. **Nested objects** — recursively compared. If `inputSchema` requires `{ address: { city: string } }`, `outputSchema` providing `{ address: { city: string, zip: string } }` is compatible (output is a superset)
3. **Arrays** — element types are compared. If `inputSchema` requires `string[]`, `outputSchema` providing `(string | number)[]` is **not** compatible (output could produce non-string elements)
4. **Optional fields** — if `inputSchema` marks a field as optional (`Type.Optional()`), it's not required in `outputSchema`. If `outputSchema` omits it, compatibility is still `true`.
5. **Union types** — if `inputSchema` accepts `string | number`, `outputSchema` providing just `string` is compatible (string is a subtype of string | number). The reverse (input requires `string`, output provides `string | number`) is **not** compatible.
The `mismatches` array provides field-level diagnostics for incompatible results:
```typescript
interface TypeMismatch {
path: string; // JSON path to the mismatched field (e.g., "/address/city")
expected: string; // What input requires (e.g., "string")
actual: string; // What output provides (e.g., "number")
}
```
### Compatibility Rules Summary
| Output \ Input | Exact match | Superset of input | Subset of input | Unknown |
|---------------|-------------|-------------------|-----------------|---------|
| Exact match | ✅ compatible | ✅ compatible | ❌ incompatible | No edge |
| Superset | ✅ compatible | ✅ compatible | ❌ incompatible | No edge |
| Subset | ❌ incompatible | ❌ incompatible | Depends on which fields | No edge |
| Unknown | No edge | No edge | No edge | No edge |
### `typeCompat(outputSchema, inputSchema)`
```typescript
@@ -99,7 +139,7 @@ function topologicalOrder(graph: FlowGraph): string[]
Returns node keys in topological order (prerequisites before dependents). Uses `graphology-dag`'s `topologicalSort` algorithm.
Throws `CircularDependencyError` if the graph contains cycles, with `cycles` populated by `findCycles()`.
Throws `CycleError` if the graph contains cycles, with `cycles` populated by `findCycles()`.
### `parallelGroups(graph)`
@@ -239,13 +279,54 @@ This pattern enables:
- **Testing** — standalone functions are easier to test in isolation
- **Composition** — consumers can chain analysis functions without creating intermediate `FlowGraph` instances
## Performance Characteristics
Analysis functions are pure and operate on the graph in memory. Their complexity is:
| Function | Complexity | Notes |
|----------|-----------|-------|
| `topologicalOrder()` | O(V + E) | Linear in nodes + edges. Single traversal. |
| `parallelGroups()` | O(V + E) | Same as topological sort. One pass. |
| `criticalPath()` | O(V + E) | Longest path in DAG. Single traversal with path tracking. |
| `reachableFrom()` | O(V + E) | BFS/DFS from starting nodes. |
| `ancestors()` | O(V + E) | Backward traversal from target. |
| `descendants()` | O(V + E) | Forward traversal from target. |
| `hasCycles()` | O(V + E) | DFS-based cycle detection. Always `false` after validated construction. |
| `findCycles()` | O(V + E) | Johnson's algorithm for finding all elementary cycles. |
| `typeCompat()` | O(depth) | Depends on schema depth. Schemas are typically shallow (5-10 fields). Fast for realistic schemas. |
| `buildTypeEdges()` | O(V²) | Pairwise comparison of all operations. For 50 operations: 2,500 comparisons. For 200: 40,000. Each comparison is `O(depth)`. |
| `validateTemplate()` | O(V + E) | Template traversal plus DAG validation. |
| `validatePreconditions()` | O(V × E) | For each node, check all predecessors. |
| `validateGraph()` | O(V + E) | Cycle detection + edge validation + orphan detection. |
### Practical Performance
For expected graph sizes (10-200 nodes):
- `buildTypeEdges()`: 0.5-5ms for 50 operations, 5-50ms for 200 operations
- `topologicalOrder()`: <1ms for any realistic graph
- `typeCompat()`: <0.01ms per comparison
- All query functions: <1ms for any realistic graph
These are in-memory operations with no I/O. The dominant cost is `buildTypeEdges()` which scales quadratically with the number of operations. For very large registries (>500 operations), consider lazy edge construction or caching.
### Optimization Opportunities
1. **Lazy edge construction**`buildTypeEdges()` currently compares all pairs. For large registries, edges could be computed on demand: when `typeCompat(A, B)` is queried, compute and cache the result. This trades startup time for query-time cost.
2. **Type compatibility caching**`typeCompat()` results could be cached by schema hash. Identical schemas always produce the same result. This helps when the same operation appears in multiple templates.
3. **Incremental graph updates** — when a single operation is added to the registry, only compute edges for the new node (O(V) instead of O(V²)).
4. **Parallel group scheduling**`parallelGroups()` is useful for the hub coordinator to determine max parallelism. An optional `maxConcurrency` parameter could be added to limit group sizes for realistic scheduling.
## Constraints
- **Analysis functions are pure** — they don't mutate the graph, don't depend on external state, and don't throw on validation failures (they return error arrays)
- **Type compatibility is structural, not semantic** — `typeCompat()` checks schema shapes, not whether the data makes sense. "Age as number" is compatible with "count as number" even though they're semantically different.
- **Template validation is advisory** — warnings are not errors. A template with an unknown operation is a warning, not a validation failure (the operation might be added to the registry later).
- **Analysis functions work on the underlying `DirectedGraph`** — they're thin wrappers around graphology and graphology-dag functions, following the same pattern as taskgraph
- **`topologicalOrder()` throws on cycles** — unlike `validateGraph()` which returns errors, `topologicalOrder()` throws `CircularDependencyError` because it cannot produce a valid ordering from a cyclic graph
- **`topologicalOrder()` throws on cycles** — unlike `validateGraph()` which returns errors, `topologicalOrder()` throws `CycleError` because it cannot produce a valid ordering from a cyclic graph
## Open Questions
@@ -260,6 +341,6 @@ This pattern enables:
## References
- Schema: [schema.md](schema.md) — `TypeCompatResult`, `TypeMismatch`, `ValidationError`
- Error handling: [error-handling.md](error-handling.md) — `CircularDependencyError`, `TypeIncompatError`
- Error handling: [error-handling.md](error-handling.md) — `CycleError`, `TypeIncompatError`
- Taskgraph analysis pattern: `@alkdev/taskgraph_ts/src/analysis/`
- TypeBox Value utilities: `@alkdev/typebox/value`