- C-01: fix broken README link (call-graph-runtime.md → call-graph.md)
- C-02: add CallEdgeAttrs union type alias in schema.md
- C-03/W-12: rename TypedEdgeAttrs → OperationEdgeAttrs for consistent
{GraphType}EdgeAttrs naming pattern, update all references
- W-01: standardize terminology — prerequisites=structural/graph,
preconditions=reactive/computed, rename WorkflowNode.prerequisites
to preconditions, rename computePrerequisites to computePreconditions
- W-09: update ADR-001/002/003 status from Proposed to Accepted
- W-10: clarify call graph mutation API — addCall creates triggered
edges automatically, addDependency creates depends_on edges
- update review checklist with resolved items
69 lines
3.8 KiB
Markdown
69 lines
3.8 KiB
Markdown
# ADR-001: ujsx Trees as Workflow Template IR
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
Flowgraph needs a way to define workflow templates — reusable sequences of operations with conditional branching and parallel execution. The templates must be:
|
|
|
|
1. **Declarative** — defining *what* should happen, not *how*
|
|
2. **Composable** — nesting sequential, parallel, and conditional flows
|
|
3. **Serializable** — store in JSON, transmit over APIs, version in git
|
|
4. **Validatable** — check against an operation graph before execution
|
|
5. **Renderable to multiple targets** — structural validation (DAG) and runtime execution (reactive)
|
|
|
|
The obvious approach is a custom template format: an array of step objects with type discriminators:
|
|
|
|
```typescript
|
|
const template = [
|
|
{ type: "operation", name: "architect" },
|
|
{ type: "sequential", steps: [...] },
|
|
];
|
|
```
|
|
|
|
This works but has limitations:
|
|
- Custom format requires a custom parser, serializer, and validator
|
|
- No composition primitives — `sequential` and `parallel` are just types in an array
|
|
- No host switching — a separate compiler is needed for each target (DAG, execution engine)
|
|
- No incremental updates — changing a step requires rebuilding the entire structure
|
|
|
|
## Decision
|
|
|
|
Use ujsx `UNode` trees as the workflow template intermediate representation. Workflow components (`Operation`, `Sequential`, `Parallel`, `Conditional`) are `UComponent` functions that produce `UElement` nodes. The template is rendered to different targets through ujsx `HostConfig` implementations.
|
|
|
|
```typescript
|
|
const template = h(Sequential, {},
|
|
h(Operation, { name: "architect" }),
|
|
h(Operation, { name: "reviewer" }),
|
|
);
|
|
```
|
|
|
|
## Rationale
|
|
|
|
1. **No new format** — ujsx already defines `UNode`, `UElement`, `URoot`, type guards, and serialization. We don't need to design, implement, and maintain a template format.
|
|
|
|
2. **Composition is structural** — `<Sequential>` and `<Parallel>` compose naturally as parent-child structure in a tree. Array-of-objects requires custom merging logic.
|
|
|
|
3. **Host target switching** — the same `UNode` tree renders to a graphology DAG (for validation) or a reactive engine (for execution) by swapping the `HostConfig`. No template-specific compiler needed.
|
|
|
|
4. **Incremental updates** — when the ujsx reconciler is implemented, template changes (add/remove/reorder steps) can be applied incrementally without rebuilding the entire DAG. Array-of-objects requires full diffing and rebuilding.
|
|
|
|
5. **Reactive props** — `@preact/signals-core` enables signal-driven prop updates. An `Operation` node's `name` could be a `signal<string>`, enabling dynamic workflow modification at runtime.
|
|
|
|
6. **Serialization for free** — `UNode` trees are plain JSON. `JSON.stringify(template)` works. No custom serializer needed.
|
|
|
|
## Consequences
|
|
|
|
- **Direct dependency on `@alkdev/ujsx`** — flowgraph imports `h`, `createRoot`, `HostConfig`, `ReactiveRoot`, and type definitions from ujsx. This is a direct dependency, not a peer dependency.
|
|
- **Function props don't serialize** — `Conditional.test` can be a function `(results) => boolean`, which doesn't survive JSON round-trips. Templates with conditional branches need to provide `test` at render time or use expression strings.
|
|
- **Template components must follow ujsx component contract** — `(props) => UNode`. This is a minimal contract but it means components are synchronous functions that return a tree.
|
|
- **The template IS the tree** — there is no separate compilation step between the ujsx tree and the render target. The `HostConfig.render()` call IS the compilation.
|
|
|
|
## References
|
|
|
|
- ujsx architecture: `@alkdev/ujsx/docs/architecture/README.md`
|
|
- ujsx HostConfig: `@alkdev/ujsx/docs/architecture/host-config.md`
|
|
- Workflow templates: [workflow-templates.md](../workflow-templates.md)
|
|
- Host configs: [host-configs.md](../host-configs.md) |