refactor: remove placeholder join step

This commit is contained in:
lda
2026-09-05 17:47:57 +07:00 Verified
parent 043bda6a34
commit cccdbc0a47
42 changed files with 88 additions and 309 deletions
@@ -14,9 +14,10 @@ without making multiple matching edges silently mean broadcast.
The scheduler, ready queue, blocked frames, lineage-local state views, and
reducer-aware barrier commits already support concurrent foreach and native
subgraphs. They do not yet define general graph-level fork/gather. The existing
`JoinNode` is only a day-one marker: it immediately emits `done` and neither
waits nor merges. Renaming it would falsely preserve semantics it never had.
subgraphs. They do not yet define general graph-level fork/gather. A former
`JoinNode` day-one marker immediately emitted `done`; it was removed after a
census found no real persisted workflows using it. `GatherNode` therefore does
not inherit placeholder semantics or a compatibility burden.
A cross-system semantics review supported keeping `NodeResult.output` separate
from its named domain `outcome`, keeping operational failure outside that
@@ -135,11 +136,10 @@ feature would require an operational signal to the owning foreach and an
explicit policy for already admitted concurrent items. No `BreakNode` is added
without that use case and policy.
The current `JoinNode` will not be silently upgraded. Before implementation we
will verify whether real persisted artifacts use it. With no real compatibility
obligation, remove it and introduce `GatherNode` cleanly. If persisted callers
exist, define an explicit migration rather than assigning barrier semantics to
old `join` payloads.
The placeholder `JoinNode` was removed rather than silently upgraded. The
repository has no real persisted artifacts outside tests, so `GatherNode` can
be introduced with its actual rendezvous and merge contract and no legacy wire
alias.
## Considered Options
@@ -169,8 +169,8 @@ ordinary back-edge to the owning foreach already expresses item return in the
canonical graph. Break and race semantics remain separate future policies
rather than additional meanings assigned to ordinary outcomes.
**Reuse or rename `JoinNode`.** Rejected as the default because the existing
node is a pass-through marker with no barrier contract.
**Reuse or rename the placeholder `JoinNode`.** Rejected and removed because
the node was a pass-through marker with no barrier contract.
## Consequences
@@ -206,7 +206,6 @@ node is a pass-through marker with no barrier contract.
continuation frame in each topology shape.
- The trace representation for waiting and merging without excessive internal
scheduler noise.
- Whether any real persisted artifact requires migration from `JoinNode`.
This ADR extends the lineage and barrier direction established by
[ADR-0002](0002-concurrent-foreach-policy-and-barrier-commits.md). It remains
+17 -15
View File
@@ -40,23 +40,12 @@ author -> validate -> save artifact -> deploy -> run -> inspect or resume
## Active runtime sequence
The next three slices build on the foreach control-region, scheduler, lineage,
and barrier foundations in this order.
The next three slices build on the foreach control-region, structured-context,
scheduler, lineage, and barrier foundations in this order.
### 1. Review and merge structured runtime context
### 1. Add a persisted run step budget
The implementation plan is ready and its feature branch is under review:
- [`structured runtime context design`](superpowers/specs/2026-09-04-structured-runtime-context-design.md)
- [`structured runtime context implementation plan`](historical/superpowers/plans/2026-09-04-structured-runtime-context.md)
This slice gives runtime code, expressions, validation, and authoring references
one model for run data and same-scope foreach activations. Subgraphs continue to
cross an explicit input boundary rather than inheriting a parent's context.
### 2. Add a persisted run step budget
After structured context is stable, implement the proposed run-wide limit:
Implement the proposed run-wide limit:
- [`run step budget design`](superpowers/specs/2026-09-04-run-step-budget-design.md)
@@ -64,6 +53,13 @@ The budget must cover every frame and subgraph scope in one run, survive
checkpoint and resume, and stop valid but non-terminating graph cycles with a
clear runtime failure.
### 2. Consolidate runtime identity resolution
Introduce one internal resolver for a frame, lineage, runtime scope, and
foreach activation environment. The resolver should validate the canonical
identity chain once so fork/gather code does not pass related identifiers
independently or repeat ownership walks.
### 3. Implement explicit fork and gather
Reuse the scheduler, activation, lineage, and reducer-aware barrier machinery:
@@ -140,12 +136,18 @@ The active sequence can assume these foundations:
- Native subgraph scopes and durable return to the parent node
- Concurrent foreach with activation barriers and reducer-aware lineage merges
- Validated foreach back-edges with one static control region per node use
- Structured runtime context shared by execution, expressions, validation, and
authoring references
- Removal of the pass-through `JoinNode`; future `GatherNode` starts with its
actual synchronization contract and no placeholder compatibility
- Durable stopped-run inspection and resume
- Python client reconstruction of capabilities, artifacts, deployments, and
runs through the API
The current foreach return contract is
[`foreach back-edge design`](superpowers/specs/2026-09-04-foreach-back-edge-design.md).
The current context contract is
[`structured runtime context`](superpowers/specs/2026-09-04-structured-runtime-context-design.md).
## Historical entry points
@@ -112,7 +112,6 @@ Exactly one step-kind key must be present. Allowed step-kind keys are:
- `use`
- `foreach`
- `interrupt`
- `join`
Zero kind keys or multiple kind keys are validation errors.
@@ -243,19 +242,6 @@ This lowers to the current core `ForeachNode`.
This lowers to the current core `InterruptNode`.
### `join`
```json
{
"join": {}
}
```
This lowers to the current core `JoinNode`.
`join` is not a reverse branch. It remains reserved for actual join/frame
semantics.
## Routes
Most ordinary edges should be authored through `routes`:
@@ -337,7 +323,8 @@ node_c.unreachable
-> runtime_error
```
This is not `join`. It is compressed declaration of several ordinary edges.
This is not synchronization. It is compressed declaration of several ordinary
edges.
Possible later surfaces:
@@ -366,7 +353,7 @@ That should be handled as its own pass, not smuggled into this MCP draft change.
Potential later core work:
- true graph-as-node / subgraph support
- meaningful join semantics
- explicit fork/gather semantics
- future START-edge support if `Workflow.start` changes
### Draft `route` Sugar
@@ -157,7 +157,6 @@ Example:
"SubgraphNode",
"ConditionNode",
"ForeachNode",
"JoinNode",
"EndNode",
"InterruptNode"
]
@@ -85,8 +85,8 @@ semantic operation produces one patch and consumes one revision.
clients do not need to know about the internal service split.
The service boundary is intentionally not capability-only. The current draft
model also represents `end`, `condition`, `interrupt`, `foreach`, `join`,
`when`, `choose`, and `match` steps, and core may gain more step kinds. This
model also represents `end`, `condition`, `interrupt`, `foreach`, `when`,
`choose`, `match`, and subgraph steps, and core may gain more step kinds. This
slice adds semantic operations only where required, but new step-kind helpers
belong in `WorkflowDraftAuthoringApi` rather than a parallel authoring system.
@@ -132,7 +132,6 @@ Node presentation distinguishes the existing core node kinds:
- condition;
- interrupt;
- foreach;
- join;
- end and other control nodes.
Each node shows its stable id and concise semantic label. Selecting a node opens
@@ -226,4 +225,3 @@ The slice is complete when:
7. stale requests and partial failures cannot corrupt newer selections;
8. the `lda_report_workflow` lifecycle is readable without scrolling raw JSON;
9. frontend tests, typecheck, production build, and optional live smoke pass.
@@ -56,7 +56,6 @@ deliberate authoring vocabulary that is later lowered by
- `DraftUseStep`
- `DraftForeachStep`
- `DraftInterruptStep`
- `DraftJoinStep`
- `DraftEndStep`
- `DraftWhenStep`
- `DraftChooseStep`
@@ -149,7 +148,6 @@ Declared top-level outcomes are:
- `foreach`: `loop`, `done`, plus `completed_with_errors` when the item-error
policy is `skip` or `collect`;
- `interrupt`: `interrupt.outcomes`;
- `join`: `done`;
- `subgraph`: `subgraph.outcomes`.
The capability helper remains distinct because it resolves a capability,
@@ -175,7 +173,7 @@ reject malformed or ambiguous step objects before dispatching to the API.
The Python RPC client implements the same method on `WorkflowApi`. Client and
server serialize steps with aliases so fields such as foreach `as` and when
`if` retain their canonical wire names. Round-trip tests cover all nine step
`if` retain their canonical wire names. Round-trip tests cover all eight step
variants, including interrupt schemas and subgraph workflow references.
## CLI Shape
@@ -186,7 +184,6 @@ Register a focused Typer application beneath `wf draft`:
wf draft add capability
wf draft add interrupt
wf draft add foreach
wf draft add join
wf draft add end
wf draft add when
wf draft add choose
@@ -210,7 +207,6 @@ Variant-specific options are:
`--resume LOCAL_SOURCE=STATE_TARGET`, and repeatable `--outcome`;
- `foreach`: `--over`, `--as`, `--mode`, `--item-error`, optional
`--collect-to`, `--max-active`, and `--max-outstanding`;
- `join`: no variant-specific options;
- `end`: `--outcome` and no `--route`;
- `when`: `--condition-file`, `--then`, and `--otherwise`;
- `choose`: `--clauses-file` containing the ordered clause array and
@@ -269,7 +265,7 @@ modules need them; avoid a broad CLI refactor.
### CLI
- `wf draft add --help` lists all nine commands.
- `wf draft add --help` lists all eight commands.
- Per-command help exposes only relevant options.
- Every command builds the expected `DraftStep`, incoming source, and routes.
- Invalid flag combinations fail before calling the API.
@@ -28,7 +28,7 @@ The remaining graph-authoring work is split into independently useful slices:
1. selected-step input and output dataflow;
2. workflow Input, State, and Outcomes contract projections;
3. explicit End authoring and a typed Add step palette;
4. typed interrupt, control, subgraph, foreach, and join forms; and
4. typed interrupt, control, subgraph, and foreach forms; and
5. direct graph gestures lowered through the same canonical mutations.
This document specifies only the first item.
@@ -56,7 +56,7 @@ Slice 6 includes:
Slice 6 excludes:
- explicit End-node creation;
- typed creation forms for interrupt, condition, subgraph, foreach, or join;
- typed creation forms for interrupt, condition, subgraph, or foreach;
- graph gesture binding by drawing edges;
- arbitrary schema inference from runtime values;
- renaming existing step ids;
@@ -140,7 +140,7 @@ independent source of truth once the operation is available.
Standard frame context currently includes values such as prior outcome, active
incoming edge, scope id, lineage id, and parent lineage id. Foreach iteration
frames additionally expose loop item, loop index, and the configured foreach
alias. Future fork/join features may add branch-scoped context through the same
alias. Future fork/gather features may add branch-scoped context through the same
inventory without changing high-level clients.
Context availability is computed by core/API code using workflow graph and
@@ -36,7 +36,7 @@ They are runtime policy, not workflow graph semantics.
**Step Attempt** is one admitted attempt to execute a selected workflow `Step`
in one frame. Node uses, conditions, foreach controllers, subgraph boundaries,
interrupt nodes, joins, and explicit end nodes all count.
interrupt nodes, and explicit end nodes all count.
**Step Number** is the one-based ordinal assigned to an admitted step attempt
within a run.
@@ -256,7 +256,7 @@ with the run.
### Core counting
- A budget of one admits exactly one step and denies the second.
- Node, condition, foreach, subgraph, interrupt, join, and explicit end steps
- Node, condition, foreach, subgraph, interrupt, and explicit end steps
count.
- A transition to legacy `END` does not create an extra attempt.
- Handler failure still consumes its admitted step.
+1 -1
View File
@@ -444,7 +444,7 @@ Use `set-route` separately for outcome routing.
Use `wf draft add` to add one typed step to an existing draft:
```text
capability interrupt foreach join end
capability interrupt foreach end
when choose match subgraph
```
+5 -16
View File
@@ -399,8 +399,8 @@ are part of the graph definition:
```
Static values are not path mappings. Use `{"target": ..., "value": ...}` for
literal JSON values. Invalid draft step shapes are rejected instead of silently
compiling to `join`.
literal JSON values. Invalid draft step shapes are rejected instead of being
silently replaced with a placeholder step.
Generated MCP tool wrappers are intentionally naive. They normally expose both
`ok` and `error` outcomes, because MCP tool calls can report transport/provider
@@ -513,16 +513,6 @@ Declares an explicit workflow terminal outcome.
Use explicit `end` steps for non-`ok` workflow outcomes. The legacy `__end__`
destination remains the shorthand for public workflow outcome `ok`.
### `join`
Joins control flow.
```json
{
"join": {}
}
```
### `when`
Creates one boolean decision step. The condition uses the same JSON shape as
@@ -643,10 +633,9 @@ interrupt, an end step, or a subgraph rather than a capability:
```bash
wf draft create report_ws --name report_workflow
wf draft add join report_ws --revision 1 --step gate --route done=finish
wf draft set-start report_ws --revision 2 --step gate
wf draft add end report_ws --revision 3 --step finish --outcome error
wf draft set-contract report_ws --revision 4 --outcome error
wf draft add end report_ws --revision 1 --step finish --outcome error
wf draft set-start report_ws --revision 2 --step finish
wf draft set-contract report_ws --revision 3 --outcome error
wf draft validate report_ws
```