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
---
@@ -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` |