ADR-005 accepted: resolve all open consequences, update cascading docs

Resolve the three open consequences from ADR-005 (Event Log as Single
Source of Truth) and transition from Proposed to Accepted:

1. Event log IS the call protocol event stream — not a separate type,
   but an EventLogProjection interface (append/getStatus/getResult/
   getEvents) over CallEventMapValue[] with an append-only contract.

2. Event log persists across template re-renders — projections recompute
   against the new DAG; orphaned events stay in log for audit but don't
   affect active projections.

3. Edges get dataFlow: boolean attribute on TemplateEdgeAttrs — inferred
   (not manual) by GraphologyHostConfig from template expressions.
   typeCompat() only runs on dataFlow: true edges. Inference rules are
   precisely specified for Conditional.test, Map.over, and Operation.input.

Also resolve OQ-05 (structural containers stay transparent; aggregate
status is a projection from children) and OQ-10 (running node failure
is a FailurePolicy configuration, default continues-running).

Cascading updates to:
- reactive-execution.md: add hybrid status model (event-log-driven vs
  projection-driven vs signal-mutation), EventLogProjection interface,
  result projection respecting retries, FailurePolicy type
- host-configs.md: ReactiveContext now includes resultProjection and
  computed results; resolved Q1/Q3/Q4
- schema.md: dataFlow attribute on TemplateEdgeAttrs with inference
  rules and type checking implications
- workflow-templates.md: edge creation rules with dataFlow, result
  projection in Conditional/Map, resolved Q1/Q4
- open-questions.md: all ADR-005 questions marked resolved, updated
  summary table and cross-cutting themes, removed duplicate OQ-07

7 files changed, 464 insertions, 139 deletions
This commit is contained in:
2026-05-21 07:44:28 +00:00
parent 2c1b2d1a15
commit c76be7f689
7 changed files with 463 additions and 138 deletions

View File

@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-05-20
last_updated: 2026-05-21
---
# Workflow Templates
@@ -126,7 +126,7 @@ const Conditional: UComponent<{
}>;
```
When rendered to a graphology DAG, `Conditional` creates an edge with `edgeType: "conditional"` and `condition` attribute. When rendered to the reactive engine, the condition is evaluated as a `computed` that depends on the referenced step's status and output.
When rendered to a graphology DAG, `Conditional` creates an edge with `edgeType: "conditional"` and `dataFlow: true` (conditional edges always carry data — the test reads a predecessor's result). When rendered to the reactive engine, the condition is evaluated as a `computed` that depends on the result projection (from the event log per ADR-005).
If the test evaluates to `false` and no `else` branch is provided, the branch nodes transition to `skipped` in `NodeStatus`.
@@ -143,6 +143,7 @@ When the `else` prop is provided, the `Conditional` renders two subgraphs:
- When `test` evaluates to `true`: `then`-branch nodes become `ready` (preconditions met). `else`-branch nodes transition to `skipped`. Their `preconditions` are satisfied by the `skipped` state — downstream nodes see the `Conditional` as completed regardless of which branch was taken.
- When `test` evaluates to `false`: `else`-branch nodes become `ready`. `then`-branch nodes transition to `skipped`. Downstream nodes after the `Conditional` see all branches as resolved.
- When no `else` prop is provided: the `false` branch simply doesn't exist. Nodes after the `Conditional` that depend on it still see it as `completed` because the `Conditional` itself resolves regardless of which path is taken.
- The `test` function receives its data from the **result projection** (ADR-005). `results["nodeName"]` reads from `getResult("nodeName")`, which derives from the event log. This ensures retries are reflected — if a node is retried, its result updates when the retry's `call.responded` event arrives.
This means a `Conditional` with an `else` branch acts as a **complete error boundary** — downstream nodes are insulated from the branch choice. The `Conditional` is `completed` whether the `then` or `else` branch executed.
@@ -170,8 +171,8 @@ The `<Map>` component dynamically replicates its child template for each element
**Reactive rendering (ReactiveHostConfig)**:
- For each item in `over`, creates a `WorkflowNode` with its own `signal<NodeStatus>` and `computed` preconditions.
- All mapped nodes' preconditions are identical: the `Map`'s predecessor must be `completed` (same as `Parallel`).
- Each mapped node's `output` signal holds the result of its corresponding call.
- The `Map` result is available as an aggregated signal containing all mapped nodes' outputs.
- Each mapped node's result is available from the **result projection** (ADR-005). `getResult(nodeKey)` derives from the event log.
- The `Map` result is available as an aggregated computed containing all mapped nodes' results from the result projection.
**Example**:
@@ -239,9 +240,9 @@ The HostConfig maps ujsx component types to graphology operations:
### Edge creation rules
- **Sequential**: For children C1, C2, ..., Cn, edges C1→C2, C2→C3, ..., C(n-1)→Cn are added. Within a sequential group, children have implicit `depends_on` edges.
- **Sequential**: For children C1, C2, ..., Cn, edges C1→C2, C2→C3, ..., C(n-1)→Cn are added. Each edge carries `edgeType: "sequential"`. If the downstream node references the upstream node's result (via `Conditional.test`, `Map.over`, or `Operation.input`), the edge also carries `dataFlow: true`. Otherwise, `dataFlow: false` (temporal ordering only).
- **Parallel**: No edges between children. All children have the same preconditions as the parallel group itself.
- **Conditional**: Edge from the conditional node's prerequisite to the first child of the branch, with `edgeType: "conditional"` and `condition` attribute.
- **Conditional**: Edge from the conditional node's prerequisite to the first child of the branch, with `edgeType: "conditional"` and `dataFlow: true` (conditional edges always carry data — the condition reads a predecessor's result).
- **Nested**: A `Sequential` inside a `Parallel` has its own internal edges. A `Parallel` inside a `Sequential` creates a subgraph where all parallel children share the same predecessor.
### Root node handling
@@ -375,13 +376,13 @@ Not all component combinations are valid. The following rules govern which compo
## Open Questions
1. **Should `Sequential` and `Parallel` be transparent in the graph?** Currently they produce edges, not nodes. An alternative is to create "virtual" grouping nodes (like a "parallel gateway" in BPMN). This would make the graph structure richer but adds complexity.
1. ~~**Should `Sequential` and `Parallel` be transparent in the graph?**~~ **Resolved (OQ-05)**: Containers stay transparent. No nodes for `Sequential`, `Parallel`, or `Conditional` in the DAG. Aggregate status for containers is computed as a projection from children's statuses. The `parentMap` and `siblingMap` in `ReactiveContext` provide the structural context for precondition computation.
2. ~~**Should templates support loops?**~~ **Resolved**: The `<Map>` component provides array iteration — one child per array element. It does NOT support general loops (while, do-while). For repeated execution with conditional exit, use `Conditional` inside a `Sequential` group. General-purpose loops with arbitrary termination conditions are not supported because they would require cycle-supporting templates, which conflicts with the DAG-only invariant.
3. **Should templates support `depends_on` edges explicitly?** Currently dependencies are inferred from structure (sequential implies dependency). An explicit `<DependsOn target="operation-name" />` component would make data dependencies visible in the template without relying on sequential ordering.
3. **Should templates support `depends_on` edges explicitly?** Currently dependencies are inferred from structure (sequential implies dependency). An explicit `<DependsOn target="operation-name" />` component would make data dependencies visible in the template without relying on sequential ordering. With ADR-005's `dataFlow` attribute, data dependencies are now inferable from template expressions — `Conditional.test` and `Map.over` that reference predecessor results set `dataFlow: true` on the corresponding edge. Explicit `depends_on` edges would add manual annotation capability, but the `dataFlow` inference may be sufficient for v1.
4. **How does template instantiation interact with the call protocol?** When a template is instantiated as a call graph, each `<Operation>` becomes a call. But the call protocol's `call.requested` events include `parentRequestId` — who is the parent? The template itself? The hub coordinator? This needs a clear answer.
4. ~~**How does template instantiation interact with the call protocol?**~~ **Resolved (ADR-005)**: The template bridges to the call protocol through the event log. The hub coordinator appends call protocol events; the reactive layer projects them. Each `<Operation>` node's `requestId` maps to call protocol events via the `nodeKeyToRequestId` map. No callback, no boomerang — the event log is the bridge.
## References