resolve all remaining open questions (OQ-03–OQ-29), add ADR-006
Resolve all 19 remaining open questions across the architecture. Every question now has a documented resolution with rationale: - OQ-004/OQ-029: edgeType is a universal required attribute on all edges, single graph per FlowGraph instance (ADR-006) - OQ-011: No OR preconditions for v1; preconditionMode as v2 extension - OQ-012: maxConcurrency enforced via reactive counting semaphore - OQ-014: Unknown operationId creates node with pending status - OQ-017: Expose common graphology traversal methods on FlowGraph (80/20) - OQ-020: condition as Type.Unknown() with string/function documentation - OQ-022: Identity imported from @alkdev/operations peer dep - All other questions resolved with documented rationale Fix three critical issues found by architecture review: 1. edgeType serialization/validation gap: document two-step validation 2. CallEdgeAttrs runtime discrimination: edgeType as runtime discriminant, depends_on edges clarified as observability-only (not execution) 3. ADR-005 signal mutation inconsistency: explicitly distinguish call-level statuses (event-log-driven) from workflow-derived statuses (signal-mutation) Additional clarifications: - dataFlow inference uses conservative strategy (defaults false) - Conditional.test string resolution: operationName → status === completed - Add negated field to TemplateEdgeAttrs for else-branch conditions - Document edge key priority convention for composite keys - Add maxConcurrency semaphore design to reactive-execution.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-21
|
||||
last_updated: 2026-05-22
|
||||
---
|
||||
|
||||
# Workflow Templates
|
||||
@@ -130,13 +130,26 @@ When rendered to a graphology DAG, `Conditional` creates an edge with `edgeType:
|
||||
|
||||
If the test evaluates to `false` and no `else` branch is provided, the branch nodes transition to `skipped` in `NodeStatus`.
|
||||
|
||||
#### String condition resolution
|
||||
|
||||
When `Conditional.test` is a string (rather than a function), the HostConfig resolves it at render time using the operation registry. The resolution algorithm is:
|
||||
|
||||
- `test: "operationName"` → resolves to `(results) => results["operationName"]?.status === "completed"`, meaning "the then-branch is taken if the referenced operation completed successfully."
|
||||
- If the referenced operation failed or was aborted, the condition evaluates to `false` and the else-branch is taken (or the then-branch is `skipped` if no else-branch).
|
||||
- String conditions can only reference predecessor operations by name. For more complex conditions (checking output fields, combining multiple results, etc.), use the function form.
|
||||
|
||||
This resolution algorithm is deterministic and produces the same behavior regardless of which HostConfig performs the resolution.
|
||||
|
||||
#### Else-branch behavior
|
||||
|
||||
When the `else` prop is provided, the `Conditional` renders two subgraphs:
|
||||
|
||||
**DAG rendering (GraphologyHostConfig)**:
|
||||
- The `then` branch (child) renders with an edge from the conditional's predecessor to the first child, with `edgeType: "conditional"` and `condition: <test>`.
|
||||
- The `else` branch renders as a separate subgraph with `edgeType: "conditional"` and `condition: <negated test>`. The negated condition is derived automatically.
|
||||
- The `else` branch renders as a separate subgraph with `edgeType: "conditional"`, `condition: <test>`, and `negated: true`. The `negated` flag on `TemplateEdgeAttrs` indicates that the condition is logically negated for the else-branch. At render time, the HostConfig resolves the negation differently depending on the condition form:
|
||||
- **String condition**: `condition: "fetch-data"` with `negated: true` resolves to `(results) => results["fetch-data"]?.status !== "completed"`.
|
||||
- **Function condition**: The HostConfig wraps the original function: `condition: (results) => !originalTest(results)`.
|
||||
- This ensures the else-branch is taken when the original condition evaluates to `false`, regardless of condition form.
|
||||
- Both branches share the same predecessor — the `Conditional` node's structural position in the template determines the common starting point.
|
||||
|
||||
**Reactive rendering (ReactiveHostConfig)**:
|
||||
@@ -378,9 +391,9 @@ Not all component combinations are valid. The following rules govern which compo
|
||||
|
||||
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.
|
||||
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 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. 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.
|
||||
3. ~~**Should templates support `depends_on` edges explicitly?**~~ **Resolved (OQ-021)**: No for v1. ADR-005's `dataFlow` inference and the result projection make explicit `depends_on` unnecessary for current use cases. Data dependencies are expressed through the result projection — if B needs A's output, B reads `getResult("A")`. The `dataFlow: true` attribute on edges captures which edges carry data. An explicit `<DependsOn>` component would add template syntax complexity and potentially conflict with structural ordering. If a future use case requires non-adjacent data dependencies that can't be expressed by restructuring the template, `<DependsOn>` can be added as a v2 extension. But v1 intentionally restricts dependencies to follow the structural flow.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user