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:
@@ -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`
|
||||
Reference in New Issue
Block a user