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:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
status: draft
|
||||
status: reviewed
|
||||
last_updated: 2026-05-22
|
||||
---
|
||||
|
||||
@@ -509,6 +509,38 @@ function callStatusToNodeStatus(callStatus: CallStatus): NodeStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### Aggregate Status
|
||||
|
||||
For consumers that need to check whether a workflow has completed, the `WorkflowReactiveRoot` provides convenience methods:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Returns true when all nodes have reached a terminal state
|
||||
* (completed, failed, aborted, or skipped).
|
||||
* Useful for checking workflow completion without manually
|
||||
* iterating statusMap.
|
||||
*/
|
||||
isComplete(): boolean
|
||||
|
||||
/**
|
||||
* Returns an aggregate status summary for the workflow.
|
||||
* Useful for observability and completion tracking.
|
||||
*/
|
||||
getAggregateStatus(): {
|
||||
completed: number;
|
||||
failed: number;
|
||||
aborted: number;
|
||||
skipped: number;
|
||||
running: number;
|
||||
waiting: number;
|
||||
ready: number;
|
||||
idle: number;
|
||||
total: number;
|
||||
}
|
||||
```
|
||||
|
||||
These methods derive from the `statusMap` and align with ADR-005's projection model — they read signal values rather than scanning the event log directly, since the signals are already projections of the log.
|
||||
|
||||
## Event-Driven Execution
|
||||
|
||||
Under ADR-005, the hub coordinator's responsibility shifts from directly setting signal values to **appending events to the log**. The reactive layer drives execution via `effect()`s that watch projections and invoke calls when preconditions are met.
|
||||
@@ -634,13 +666,18 @@ The reactive execution layer has three levels of error handling, each with disti
|
||||
|
||||
### Level 1: Signal-level errors (per-node)
|
||||
|
||||
When a call fails, the hub coordinator sets the node's status to `"failed"`:
|
||||
When a call fails, the hub coordinator appends a `call.error` event to the event log:
|
||||
|
||||
```typescript
|
||||
status.value = "failed"; // Individual node failure
|
||||
workflowRoot.append({
|
||||
type: "call.error",
|
||||
requestId,
|
||||
error: { code: error.code, message: error.message },
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
```
|
||||
|
||||
This triggers `blockedByFailure` in all downstream dependents, causing them to transition to `"aborted"`. The failure propagates through the signal graph reactively — no manual error handling is needed.
|
||||
The status projection derives `NodeStatus.failed` from this event. The `blockedByFailure` computed in all downstream dependents automatically re-evaluates, causing them to transition to `"aborted"`. The failure propagates through the signal graph reactively — no manual error handling is needed.
|
||||
|
||||
### Level 2: Conditional error boundaries (branch-level)
|
||||
|
||||
@@ -700,7 +737,7 @@ The `WorkflowErrorBoundary` catches errors that escape the signal graph (e.g., a
|
||||
|
||||
| Error type | Scope | Mechanism | Recovery |
|
||||
|------------|-------|-----------|----------|
|
||||
| Call failure | Single node | `status.value = "failed"` | Cascades to dependents via `blockedByFailure` |
|
||||
| Call failure | Single node | `workflowRoot.append({ type: "call.error", ... })` | Cascades to dependents via `blockedByFailure` |
|
||||
| Caught by Conditional | Branch | `Conditional.test` evaluates against failed status | Redirect to else-branch, downstream sees `completed` |
|
||||
| Uncaught cascade | Downstream chain | `blockedByFailure` effects | Downstream nodes transition to `aborted` |
|
||||
| System failure | Entire workflow | `abortAll()` | All non-terminal nodes to `aborted` |
|
||||
|
||||
Reference in New Issue
Block a user