Files
flowgraph/docs/architecture/open-questions.md
glm-5.1 907c33650f 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
2026-05-21 19:40:45 +00:00

364 lines
31 KiB
Markdown

---
status: reviewed
last_updated: 2026-05-22
---
# Open Questions Tracker
Cross-cutting compilation of all unresolved questions across the flowgraph architecture documents, organized by theme. Questions that appear in multiple documents are unified here with cross-references.
## How to Use This Document
- Each question has an **ID** (e.g., OQ-01), **status**, **origin** (which doc(s)), and **priority** assessment
- **Cross-references** link related questions that may conflict or answer each other
- When a question is resolved, update its status to `resolved` and add a resolution note
- Once all questions in a theme are resolved, the theme section can be removed
## ADR-005 Impact
[ADR-005: Event Log as Single Source of Truth](decisions/005-event-log-as-source-of-truth.md) proposes an Execution Event Log pattern that resolves or reframes several open questions. ADR-005 is now **Accepted**. All questions it affects have been resolved:
| Question | ADR-005 Impact | Final Resolution |
|----------|-----------------|-------------------|
| OQ-01 | Reframed → Resolved | Type-compat edges only on `dataFlow: true` edges. Temporal edges bypass type checking. |
| OQ-02 | Reframed → Resolved | Type checking scope narrows to state-transfer edges. Structured mismatch reporting confirmed. |
| OQ-05 | Independent → Resolved | Containers stay transparent. Aggregate status computed as projection from children. |
| OQ-06 | Resolved | The reactive layer bridges to call protocol through the event log. Hub appends events; reactive layer projects them. |
| OQ-07 | Resolved | Call graph and reactive engine are both projections of the event log. Neither owns the other. |
| OQ-08 | Resolved | `depends_on` edges unnecessary. Data dependencies expressed through result projection. |
| OQ-09 | Resolved | Retries are natural append events. New `requestId` per retry. |
| OQ-10 | Reframed → Resolved | Running node failure handling is a projection policy, not a state machine rule. Default: running nodes continue. |
## Theme 1: Edge Semantics and Type Compatibility
### OQ-01: Should `fromSpecs()` add ALL edges or only compatible ones?
- **Origin**: [operation-graph.md](operation-graph.md) Q1
- **Status**: resolved
- **Priority**: high — affects storage size, API surface, and diagnostic value
- **Resolution**: Adopt option (a) for state-transfer edges, option (b) for temporal-only edges. Type-compatibility edges (with `compatible: true/false` attributes) are only added where data flows between operations. The `dataFlow` attribute on `TemplateEdgeAttrs` (resolved in ADR-005) determines which edges need type checking. For edges where `dataFlow: true`, both compatible and incompatible edges provide diagnostic value. For edges where `dataFlow: false`, no type-compat edge is needed — temporal ordering doesn't have type compatibility.
- **Cross-references**: OQ-04
### OQ-02: How granular should type compatibility results be?
- **Origin**: [operation-graph.md](operation-graph.md) Q4, [analysis.md](analysis.md) Q1
- **Status**: resolved
- **Priority**: high — directly shapes the `typeCompat()` return type and `OperationEdgeAttrs`
- **Resolution**: Type compatibility checking only applies to **state-transfer edges** (where A's output flows into B's input), as established by ADR-005's `dataFlow` attribute on `TemplateEdgeAttrs`. Temporal-only edges bypass type checking entirely (their "compatibility" is trivially true). The `typeCompat()` function returns `{ compatible, detail?, mismatches? }` for state-transfer edges only. The schema already has `mismatches?: TypeMismatch[]` in `OperationEdgeAttrs` — this design is confirmed. Remaining detail decisions (recursive depth limits, unknown/union type handling) are implementation concerns, not architecture decisions.
- **Cross-references**: OQ-01
### OQ-03: Should subscription operations be treated differently in type compatibility?
- **Origin**: [operation-graph.md](operation-graph.md) Q3
- **Status**: resolved
- **Priority**: medium — affects operation graph edge semantics for streaming operations
- **Resolution**: For v1, subscriptions are treated identically to queries/mutations in `typeCompat()`. A subscription's `outputSchema` describes a single stream element, and `typeCompat()` checks whether that single element is compatible with the downstream input. This is correct for `Map` (which processes stream elements individually) and may be misleading for direct subscription→operation connections. The `OperationNodeAttrs.type` field is available for consumers that need subscription-aware behavior. A v2 extension could add a `streaming: boolean` flag on edges to capture stream semantics explicitly, but this adds complexity without a current use case.
- **Cross-references**: OQ-01
### OQ-04: Edge type consistency — should `edgeType` be required on ALL edges?
- **Origin**: [schema.md](schema.md) Q1
- **Status**: resolved
- **Priority**: medium — affects serialization format and edge handling across all graph types
- **Resolution**: Option (a) — `edgeType` is required on all edges. The mode-specific attribute schemas (`OperationEdgeAttrs`, `TriggeredEdgeAttrs`, `DependencyEdgeAttrs`) do NOT include `edgeType` — it is stored as a universal attribute alongside the mode-specific attributes in graphology. This ensures consistent serialization/deserialization, uniform graphology queries, and straightforward edge-type filtering across all graph modes. The redundancy for operation graphs (where `edgeType` is always `"typed"`) is a minor ergonomic cost for significant consistency gains. See ADR-006 for the full decision record.
- **Cross-references**: OQ-29
---
## Theme 2: Structural Container Transparency
### OQ-05: Should `Sequential` and `Parallel` be transparent in the graph?
- **Origin**: [workflow-templates.md](workflow-templates.md) Q1, [host-configs.md](host-configs.md) Q1
- **Status**: resolved
- **Priority**: high — fundamental to how the DAG is structured and how the reactive engine computes preconditions
- **Question (merged)**: Currently, structural containers (`Sequential`, `Parallel`, `Conditional`) produce edges but no nodes. The reactive engine then has to reconstruct structural context to compute preconditions. Should they create "virtual" nodes instead?
- **Resolution**: Keep containers transparent (current design). Structural containers do NOT create nodes in the DAG or events in the event log. Their aggregate status can be computed as a projection from their children's statuses:
- A `Sequential` is "completed" when all its children are completed/skipped
- A `Parallel` is "completed" when all its children are completed/skipped
- A `Conditional` is "completed" when its taken branch is completed/skipped
This resolution aligns with ADR-005's projection model: the event log records real call events, and projections derive derived state. Virtual nodes in the event log would pollute it with synthetic events that have no call protocol equivalent. Virtual nodes in the DAG would add structural overhead for what is already computable.
The `parentMap` and `siblingMap` in the `ReactiveContext` remain the mechanism for computing preconditions. These maps are derived from the template structure during rendering, not from the DAG. They provide the structural context that the transparent-DAG approach needs, without requiring container nodes.
- **Cross-references**: OQ-14 (partial re-rendering)
---
## Theme 3: Call Protocol Integration
### OQ-06: How does template instantiation interact with the call protocol?
- **Origin**: [workflow-templates.md](workflow-templates.md) Q4, [host-configs.md](host-configs.md) Q3
- **Status**: resolved by ADR-005
- **Priority**: high — this is a fundamental integration point between flowgraph and the call protocol
- **Question (merged)**: 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? Is it the template instance? The hub coordinator? And how does the `ReactiveHostConfig` bridge to `registry.execute()` or `PendingRequestMap.call()`?
- **ADR-005 resolution**: The reactive layer bridges to the call protocol through the event log. Call protocol events (`call.requested`, `call.responded`, etc.) are appended to the event log. The reactive status projection derives `NodeStatus` from the log. The result projection derives `CallResult` from the log. The hub coordinator appends events; the reactive layer projects them. No callback, no boomerang, no direct signal mutation by the coordinator.
- **Cross-references**: OQ-07, OQ-08
### OQ-07: Should the reactive engine own the call graph?
- **Origin**: [host-configs.md](host-configs.md) Q4
- **Status**: resolved by ADR-005
- **Priority**: high — affects the separation between flowgraph and the call protocol
- **Question**: Currently the call graph (from call-graph.md) and the reactive engine (from reactive-execution.md) are separate concepts. But at runtime, every `<Operation>` in a template becomes a call graph node. Should the reactive engine populate the call graph as a side effect?
- **ADR-005 resolution**: Neither owns the other. Both the call graph and the reactive status/result projections derive from the same event log. They are independent projections of the same source of truth. The call graph projects the structural view (who triggered whom). The reactive engine projects the behavioral view (what's running, what's blocked). You can have one without the other, or both simultaneously.
### OQ-08: Should `depends_on` edges be auto-populated from workflow templates?
- **Origin**: [call-graph.md](call-graph.md) Q2
- **Status**: resolved by ADR-005
- **Priority**: medium — affects how the call graph and template system relate
- **Question**: When a call graph is instantiated from a workflow template, the template's sequential/parallel structure implies data dependencies. Should the template instantiation automatically create `depends_on` edges in the call graph?
- **ADR-005 resolution**: `depends_on` edges are unnecessary as a separate concept. Data dependencies are expressed through the result projection of the event log. If node B needs node A's output, B reads `getResult("A")` from the result projection. The temporal ordering (A before B) is already expressed by template edges. There's no need for a separate edge type to represent data flow — the event log IS the data transport.
---
## Theme 4: Failure and Retry Semantics
### OQ-09: How are retries handled at the signal level?
- **Origin**: [reactive-execution.md](reactive-execution.md) Q2
- **Status**: resolved by ADR-005
- **Priority**: high — affects the core status state machine
- **Question**: If an operation fails and should be retried, the status would need to go `running → failed → ready → running`. But the current state machine marks `failed` as terminal with no exit transitions. How should this work?
- **ADR-005 resolution**: Option (c) is correct, and the event log makes it natural. A retry is not a state mutation — it's a new sequence of events appended to the log. When `call.requested` arrives for the same operation with a new `requestId`, it's a new fact. The old `call.error` event remains in the log as history. The status projection derives the current state by scanning for the most recent event per node. No `retried` status needed; no state machine mutation; the log preserves full history.
- **Cross-references**: OQ-10
### OQ-10: What happens to running nodes when a predecessor fails?
- **Origin**: [reactive-execution.md](reactive-execution.md) Q6
- **Status**: resolved
- **Priority**: high — affects failure propagation correctness
- **Resolution**: This is a **policy configuration** of the status projection, not a hardcoded state machine rule. The event log records failure facts. The projection decides how to handle running nodes that depend on a failed node. The default policy (option a from the original framing): running nodes are NOT affected by a predecessor's failure — only idle/waiting nodes transition to `aborted`. A more aggressive policy could abort running nodes, but this requires explicit configuration. The event log makes both strategies expressible without changing the underlying mechanism — only the projection logic changes. This aligns with ADR-005's principle that projections encode policy while the log records facts.
- **Cross-references**: OQ-09 (retries are new events, not state mutations)
---
## Theme 5: Preconditions and Scheduling
### OQ-11: Should preconditions support OR logic?
- **Origin**: [reactive-execution.md](reactive-execution.md) Q1
- **Status**: resolved
- **Priority**: medium — affects the precondition computation model
- **Resolution**: No for v1. All preconditions use AND logic — a node becomes `ready` only when ALL predecessors have reached a satisfying terminal state (`completed` or `skipped`). OR logic (`anyOf`) would introduce significant complexity (what happens when one predecessor completes but another fails?) and is already partially addressed by `Conditional` (which provides branch-level either/or semantics). For v2, if OR logic becomes necessary, it should be added as a `preconditionMode: "allOf" | "anyOf"` attribute on `Operation` (node-level, not edge-level), defaulting to `"allOf"`. This is a clean extension point that doesn't change the current precondition model.
- **Cross-references**: OQ-12
### OQ-12: How does `maxConcurrency` interact with preconditions?
- **Origin**: [reactive-execution.md](reactive-execution.md) Q4
- **Status**: resolved
- **Priority**: medium — a `Parallel` group with `maxConcurrency: 3` should only start 3 nodes at a time
- **Resolution**: `maxConcurrency` is a `Parallel` prop enforced by the `WorkflowReactiveRoot` via a reactive counting semaphore. When the root initializes signals for nodes in a `Parallel` group with `maxConcurrency: N`, it wraps the precondition logic: a node's effective `ready` transition requires both `preconditions.value === true` AND `runningCount < maxConcurrency`, where `runningCount` is a reactive computed derived from counting sibling nodes currently in the `running` state. This is entirely a reactive-engine concern — the DAG doesn't encode `maxConcurrency` (it's not structural), and the call graph doesn't need to know about it. The `Parallel` component's `maxConcurrency` prop is already part of the template definition; the reactive engine just needs to honor it.
- **Cross-references**: OQ-11, workflow-templates `Parallel` component
### OQ-13: Should `blockedByFailure` be a separate `computed` or derived from `preconditions`?
- **Origin**: [reactive-execution.md](reactive-execution.md) Q5
- **Status**: resolved
- **Priority**: low — implementation detail
- **Resolution**: Keep two separate `computed` values (current design). Two separate computeds are more composable — you can check preconditions independently of failure status, and you can compose different effects for each. A single `computed<NodeReadiness>` would require every consumer to destructure the result, losing the clean `if (preconditions.value) { ... }` pattern. The implementation cost of two effects per node is negligible. The current design is confirmed.
---
## Theme 6: Graph Construction and API Surface
### OQ-14: Should the call graph support unknown `operationId`?
- **Origin**: [call-graph.md](call-graph.md) Q1
- **Status**: resolved
- **Priority**: medium — affects `fromCallEvents()` and `updateFromEvent()` behavior
- **Resolution**: Yes — the call graph records what happened, not what should have happened. Nodes with unknown `operationId` get `status: "pending"` and may later transition to `"failed"` with an `OPERATION_NOT_FOUND` error code. This is consistent with the error-handling doc's existing statement about unknown `operationId`. The behavior is documented explicitly in the `fromCallEvents()` specification: when a `call.requested` event references an `operationId` not in the registry, the node is still created with `status: "pending"` and the given `operationId`. This enables the call graph to serve as a complete audit trail regardless of registry state.
### OQ-15: Should the call graph support multiple graphs simultaneously?
- **Origin**: [call-graph.md](call-graph.md) Q3
- **Status**: resolved
- **Priority**: low — confirmed as correct design, not a deferral
- **Resolution**: No — one `FlowGraph` instance per graph. Multiple concurrent workflows use multiple instances. This design is simpler and matches graphology's model. Subgraphs would require a scoping mechanism and cross-scope queries that add complexity without benefit at current scale. The hub coordinator creates one `WorkflowReactiveRoot` per workflow, so one `FlowGraph` per workflow is consistent. This is a deliberate "no," not a deferral — if future scale demands require multi-workflow queries, a specialized query layer can aggregate across instances.
### OQ-16: Should `filterByStatus` use an index?
- **Origin**: [call-graph.md](call-graph.md) Q4
- **Status**: resolved
- **Priority**: low — premature optimization for small graphs
- **Resolution**: No — O(n) filter is sufficient for expected graph sizes (tens to hundreds of nodes). A status index would add implementation complexity (maintain on every `updateStatus()`) for no measurable benefit at current scale. If performance becomes an issue with very large graphs, a `Map<CallStatus, Set<string>>` index can be added as an optimization later without changing the public API.
### OQ-17: Should `FlowGraph` expose graphology's traversal methods directly?
- **Origin**: [flowgraph-api.md](flowgraph-api.md) Q1
- **Status**: resolved
- **Priority**: medium — affects the public API surface
- **Resolution**: Option (c) — expose the most common traversal methods directly on `FlowGraph`, let `.graph` handle the rest. The directly exposed methods are: `forEachNode()`, `forEachEdge()`, `nodes()`, `edges()`, `order`, `size`, `inNeighbors()`, `outNeighbors()` (already exposed as `predecessors()`/`successors()`). Less common methods (degree, detailed attribute iteration, adjacency queries) remain accessible via `flowGraph.graph`. This is the 80/20 approach: consumers get a clean API for common operations, and power users get the escape hatch.
### OQ-18: Should `addOperation` auto-populate type-compat edges?
- **Origin**: [flowgraph-api.md](flowgraph-api.md) Q2
- **Status**: resolved
- **Priority**: low — affects incremental construction behavior
- **Resolution**: No — `addOperation()` adds a node only. Call `buildTypeEdges()` manually after incremental construction. Auto-population would require O(n) comparisons on every `addOperation()`, which adds complexity for a rare use case (the operation graph is typically built once via `fromSpecs()`). If incremental construction is needed, the consumer can call `buildTypeEdges()` manually after adding operations.
### OQ-28: Should `FlowGraph` share analysis functions across instances?
- **Origin**: [flowgraph-api.md](flowgraph-api.md) Q3
- **Status**: resolved
- **Priority**: low — optimization concern, not blocking
- **Resolution**: No — each `FlowGraph` instance owns its own `DirectedGraph`, and analysis functions are stateless pure functions that take a graph as input. There's nothing to pool or share. The `FlowGraph` convenience methods delegate to these standalone functions. Shared analysis "instances" would only make sense if the functions had internal caches, but they don't. This question conflated "sharing analysis functions" (already done — `typeCompat` is a standalone function) with "sharing graph data" (unnecessary since analysis doesn't cache state).
### OQ-19: Should `parallelGroups` account for resource constraints?
- **Origin**: [analysis.md](analysis.md) Q4
- **Status**: resolved
- **Priority**: low — feature enhancement, not a core concern
- **Resolution**: No for v1 — `parallelGroups()` returns theoretical maximum parallelism. Adding resource constraints would conflate structural analysis with scheduling policy. The `maxConcurrency` prop on `Parallel` is a runtime scheduling concern handled by the reactive engine (see OQ-12), not a structural analysis concern. If consumers need resource-aware scheduling, they can post-process the `parallelGroups()` output with their own constraints. An optional `maxConcurrency` parameter can be added in v2 as a convenience, but the core analysis function stays pure.
### OQ-27: Should `validateTemplate` check runtime preconditions?
- **Origin**: [analysis.md](analysis.md) Q2
- **Status**: resolved
- **Priority**: low — explicitly out of scope for static analysis
- **Resolution**: Explicitly out of scope. `validateTemplate` only checks structural validity and type compatibility. Runtime preconditions (e.g., "operation B requires an API key that operation A doesn't have access to") belong to the access control layer, not the static analysis layer. This is a deliberate scope boundary, not a design gap.
---
## Theme 7: Conditional and Template Semantics
### OQ-29: Should GraphologyHostConfig produce a separate graph per edge type?
- **Origin**: [host-configs.md](host-configs.md) Q2
- **Status**: resolved
- **Priority**: medium — affects implementation of the GraphologyHostConfig
- **Resolution**: No — all edge types share a single graph, with `edgeType` as a universal required attribute on every edge (consistent with OQ-004 resolution). Separate graphs per edge type would add complexity (cross-graph traversal, cache coherence, multi-graph queries) for a marginal performance gain at current scale. Single-graph filtering by `edgeType` is O(n) on edges and negligible for expected graph sizes. If a concrete performance issue arises, a `Map<EdgeType, DirectedGraph>` internal index can be added as an optimization without changing the API. See ADR-006 for the full decision on `edgeType` consistency.
- **Cross-references**: OQ-04
### OQ-20: How should conditional edge conditions be represented?
- **Origin**: [schema.md](schema.md) Q3
- **Status**: resolved
- **Priority**: medium — affects `TemplateEdgeAttrs.condition` type safety
- **Resolution**: `condition: Type.Optional(Type.Unknown())` with documentation describing the two runtime forms. The condition field accepts:
1. **String form** (`string`): A serializable reference to an operation name whose result determines the branch. Survives JSON round-trips.
2. **Function form** (`(results: Record<string, CallResult>) => boolean`): A runtime-evaluated predicate. Does NOT survive JSON serialization.
`@alkdev/typebox`'s `Type.Function()` defines serializable function input/output **schemas** (shapes), but `Conditional.test` predicates are runtime closures — they can't be represented as serializable function schemas. Using `Type.Function()` here would conflate the function's shape schema with the runtime closure itself. `Type.Unknown()` with clear documentation is the pragmatic choice for v1, accepting that JSON serialization only preserves the string form. A dedicated `ConditionSchema` can be introduced in v2 if template interchange needs schema-level condition descriptions, but only if there's a concrete use case for representing conditions as typed data (rather than as code).
- **Known Gap** (from [host-configs.md](host-configs.md)): "Conditional Test Evaluation" — the `Conditional.test` function needs access to the `WorkflowContext`/`ReactiveContext` at runtime to evaluate against predecessor results. This gap is resolved by ADR-005: `Conditional.test` reads from the result projection.
- **Cross-references**: OQ-05, OQ-06
### OQ-21: Should templates support explicit `depends_on` edges?
- **Origin**: [workflow-templates.md](workflow-templates.md) Q3
- **Status**: resolved
- **Priority**: medium — affects template composition expressiveness
- **Resolution**: 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.
- **Cross-references**: OQ-08
---
## Theme 8: Identity and Serialization
### OQ-22: Should `CallNodeAttrs.identity` be a structured type or `Type.Record`?
- **Origin**: [schema.md](schema.md) Q2
- **Status**: resolved
- **Priority**: medium — affects the `@alkdev/operations` peer dependency
- **Resolution**: Option (a) — import the `Identity` type structure from `@alkdev/operations` (peer dependency). Since `@alkdev/operations` is already a peer dependency (for `CallEventMapValue`), adding this type import creates minimal additional coupling. The `CallNodeAttrs.identity` field mirrors the `Identity` interface: `{ id, scopes, resources? }`. Version alignment is handled by semver ranges. The TypeBox schema for `identity` is defined inline in `CallNodeAttrs` to match the shape (not imported as a TypeBox schema from operations, since `Identity` is a TypeScript interface there), but the field semantics match exactly.
### OQ-23: Multiple graphs per `FlowGraph` instance?
- **Origin**: [call-graph.md](call-graph.md) Q3 (same as OQ-15)
- **Status**: resolved (duplicate of OQ-15 — see above)
### OQ-24: Async analysis functions?
- **Origin**: [analysis.md](analysis.md) Q3
- **Status**: resolved
- **Priority**: low — premature for current scale
- **Resolution**: No — synchronous is sufficient for current scale (10-200 nodes). Making functions async would add API complexity (Promise return types, async/await boilerplate) for no current benefit. If large graphs become common, `typeCompat()` and `buildTypeEdges()` can gain async variants alongside the synchronous ones.
---
## Theme 9: Reactive Execution Mechanics
### OQ-25: Should the reactive graph support partial re-rendering?
- **Origin**: [reactive-execution.md](reactive-execution.md) Q3
- **Status**: resolved
- **Priority**: low — blocked on ujsx reconciler, now resolved with clear path
- **Resolution**: Blocked on ujsx reconciler. When the reconciler is implemented, flowgraph gains re-rendering through the standard `prepareUpdate`/`commitUpdate` HostConfig methods. The event log persists across re-renders (ADR-005), so re-rendered nodes pick up where they left off. No special reactive-graph re-rendering logic is needed — the reconciler handles tree diffing, and the HostConfig applies mutations. For v1 (before the reconciler), the reactive tree is built once and torn down via `WorkflowReactiveRoot.dispose()`.
- **Cross-references**: OQ-05, host-configs.md "Known Gaps"
---
## Theme 10: Version and Scale Concerns
### OQ-26: How to handle version conflicts?
- **Origin**: [operation-graph.md](operation-graph.md) Q2
- **Status**: resolved
- **Priority**: low — confirmed as correct design, not a deferral
- **Resolution**: The current design uses `namespace.name` (no version) as the node key, meaning only one version per operation can exist in the graph. This is intentional simplicity. Version conflicts are a niche concern that would add significant complexity (version-aware node keys like `namespace.name@version`, multi-version edges, version compatibility matrices) without a concrete use case. If versioning becomes needed, the node key format could be extended to `namespace.name@version`, but this is a significant change that requires careful consideration. For v1, the one-version-per-operation constraint is sufficient and keeps the key format simple and consistent.
---
### ADR-006: Edge Type Consistency and Single-Graph Architecture
See [decisions/006-edge-type-consistency.md](decisions/006-edge-type-consistency.md) for the full decision record.
---
## Summary Table
| ID | Question | Origin | Priority | Status |
|----|----------|--------|----------|--------|
| OQ-01 | All edges or only compatible edges? | operation-graph | high | resolved |
| OQ-02 | Type compatibility depth and granularity | operation-graph, analysis | high | resolved |
| OQ-03 | Subscription operations in type compat | operation-graph | medium | resolved |
| OQ-04 | `edgeType` on all edges? | schema | medium | resolved |
| OQ-05 | Structural container transparency | workflow-templates, host-configs | high | resolved |
| OQ-06 | Template ↔ call protocol interaction | workflow-templates, host-configs | high | resolved |
| OQ-07 | Should reactive engine own call graph? | host-configs | high | resolved |
| OQ-08 | Auto-populate `depends_on` from templates? | call-graph | medium | resolved |
| OQ-09 | Retries at signal level | reactive-execution | high | resolved |
| OQ-10 | Running nodes when predecessor fails | reactive-execution | high | resolved |
| OQ-11 | OR logic for preconditions | reactive-execution | medium | resolved |
| OQ-12 | `maxConcurrency` interaction with preconditions | reactive-execution | medium | resolved |
| OQ-13 | `blockedByFailure` vs single computed | reactive-execution | low | resolved |
| OQ-14 | Unknown `operationId` in call graph | call-graph | medium | resolved |
| OQ-15 | Multiple graphs per instance | call-graph | low | resolved |
| OQ-16 | `filterByStatus` index | call-graph | low | resolved |
| OQ-17 | Expose graphology traversal directly? | flowgraph-api | medium | resolved |
| OQ-18 | Auto-populate type edges on `addOperation`? | flowgraph-api | low | resolved |
| OQ-19 | `parallelGroups` with resource constraints | analysis | low | resolved |
| OQ-20 | Conditional edge condition representation | schema | medium | resolved |
| OQ-21 | Explicit `depends_on` in templates | workflow-templates | medium | resolved |
| OQ-22 | `CallNodeAttrs.identity` type | schema | medium | resolved |
| OQ-23 | Multiple graphs per instance | call-graph | low | resolved (duplicate of OQ-15) |
| OQ-24 | Async analysis functions | analysis | low | resolved |
| OQ-25 | Partial re-rendering | reactive-execution | low | resolved |
| OQ-26 | Operation version conflicts | operation-graph | low | resolved |
| OQ-27 | Runtime preconditions in validateTemplate? | analysis | low | resolved |
| OQ-28 | Share analysis functions across instances? | flowgraph-api | low | resolved |
| OQ-29 | Separate graph per edge type? | host-configs | medium | resolved |
### All Questions Resolved
All open questions have been resolved. The architecture is now fully specified and ready for implementation decomposition.
### Cross-Cutting Themes
All cross-cutting theme groups have been resolved:
1. **Edge semantics group** (OQ-01, OQ-02, OQ-04): All resolved. Type checking only on `dataFlow: true` edges. `edgeType` is universal on all edges (ADR-006).
2. **Call protocol integration group** (OQ-06, OQ-07, OQ-08): All resolved by ADR-005. Bridge through event log, projections instead of ownership, data flow through result projection.
3. **Failure semantics group** (OQ-09, OQ-10): All resolved by ADR-005. Retries are append events; running node failure is a projection policy.
4. **Scheduling group** (OQ-11, OQ-12): All resolved. AND-only preconditions for v1, `maxConcurrency` via reactive counting semaphore.
5. **Template expressiveness group** (OQ-05, OQ-20, OQ-21): All resolved. Containers stay transparent, `condition` as `Type.Unknown()` with documentation, no explicit `depends_on` for v1.
6. **Graph structure group** (OQ-04, OQ-29): All resolved by ADR-006. Universal `edgeType` on all edges, single shared graph per `FlowGraph`.