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
+3 -2
View File
@@ -895,8 +895,9 @@ _Avoid_: Job, invocation
- Missing reducers mean replace only within a single serial lineage. At a - Missing reducers mean replace only within a single serial lineage. At a
**Barrier**, multiple writes to the same path without a reducer are conflicts, **Barrier**, multiple writes to the same path without a reducer are conflicts,
not last-writer-wins replacements. not last-writer-wins replacements.
- Current `JoinNode` should not silently become a **Barrier**. It may be - The pass-through `JoinNode` was removed. Future **Gather** behavior starts
repurposed later only through an explicit design pass. from an explicit barrier contract rather than inheriting placeholder
semantics.
- The first **Scheduler Foundation** implementation should create ready/block/wake - The first **Scheduler Foundation** implementation should create ready/block/wake
seams only. **Lineage Isolation** and **Barrier** merge behavior are future seams only. **Lineage Isolation** and **Barrier** merge behavior are future
concurrent semantics, not first-pass behavior. concurrent semantics, not first-pass behavior.
-14
View File
@@ -1312,17 +1312,6 @@
], ],
"type": "object" "type": "object"
}, },
"DraftJoinStep": {
"additionalProperties": false,
"description": "Draft step that emits the current core join node.",
"properties": {
"join": {
"additionalProperties": true,
"type": "object"
}
},
"type": "object"
},
"DraftMatchCase": { "DraftMatchCase": {
"additionalProperties": false, "additionalProperties": false,
"description": "One ordered equality case in a draft match decision.", "description": "One ordered equality case in a draft match decision.",
@@ -6209,9 +6198,6 @@
{ {
"$ref": "#/components/schemas/DraftInterruptStep" "$ref": "#/components/schemas/DraftInterruptStep"
}, },
{
"$ref": "#/components/schemas/DraftJoinStep"
},
{ {
"$ref": "#/components/schemas/DraftEndStep" "$ref": "#/components/schemas/DraftEndStep"
}, },
@@ -14,9 +14,10 @@ without making multiple matching edges silently mean broadcast.
The scheduler, ready queue, blocked frames, lineage-local state views, and The scheduler, ready queue, blocked frames, lineage-local state views, and
reducer-aware barrier commits already support concurrent foreach and native reducer-aware barrier commits already support concurrent foreach and native
subgraphs. They do not yet define general graph-level fork/gather. The existing subgraphs. They do not yet define general graph-level fork/gather. A former
`JoinNode` is only a day-one marker: it immediately emits `done` and neither `JoinNode` day-one marker immediately emitted `done`; it was removed after a
waits nor merges. Renaming it would falsely preserve semantics it never had. 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 A cross-system semantics review supported keeping `NodeResult.output` separate
from its named domain `outcome`, keeping operational failure outside that 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 explicit policy for already admitted concurrent items. No `BreakNode` is added
without that use case and policy. without that use case and policy.
The current `JoinNode` will not be silently upgraded. Before implementation we The placeholder `JoinNode` was removed rather than silently upgraded. The
will verify whether real persisted artifacts use it. With no real compatibility repository has no real persisted artifacts outside tests, so `GatherNode` can
obligation, remove it and introduce `GatherNode` cleanly. If persisted callers be introduced with its actual rendezvous and merge contract and no legacy wire
exist, define an explicit migration rather than assigning barrier semantics to alias.
old `join` payloads.
## Considered Options ## 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 canonical graph. Break and race semantics remain separate future policies
rather than additional meanings assigned to ordinary outcomes. rather than additional meanings assigned to ordinary outcomes.
**Reuse or rename `JoinNode`.** Rejected as the default because the existing **Reuse or rename the placeholder `JoinNode`.** Rejected and removed because
node is a pass-through marker with no barrier contract. the node was a pass-through marker with no barrier contract.
## Consequences ## Consequences
@@ -206,7 +206,6 @@ node is a pass-through marker with no barrier contract.
continuation frame in each topology shape. continuation frame in each topology shape.
- The trace representation for waiting and merging without excessive internal - The trace representation for waiting and merging without excessive internal
scheduler noise. scheduler noise.
- Whether any real persisted artifact requires migration from `JoinNode`.
This ADR extends the lineage and barrier direction established by This ADR extends the lineage and barrier direction established by
[ADR-0002](0002-concurrent-foreach-policy-and-barrier-commits.md). It remains [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 ## Active runtime sequence
The next three slices build on the foreach control-region, scheduler, lineage, The next three slices build on the foreach control-region, structured-context,
and barrier foundations in this order. 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: Implement the proposed run-wide limit:
- [`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:
- [`run step budget design`](superpowers/specs/2026-09-04-run-step-budget-design.md) - [`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 checkpoint and resume, and stop valid but non-terminating graph cycles with a
clear runtime failure. 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 ### 3. Implement explicit fork and gather
Reuse the scheduler, activation, lineage, and reducer-aware barrier machinery: 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 - Native subgraph scopes and durable return to the parent node
- Concurrent foreach with activation barriers and reducer-aware lineage merges - Concurrent foreach with activation barriers and reducer-aware lineage merges
- Validated foreach back-edges with one static control region per node use - 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 - Durable stopped-run inspection and resume
- Python client reconstruction of capabilities, artifacts, deployments, and - Python client reconstruction of capabilities, artifacts, deployments, and
runs through the API runs through the API
The current foreach return contract is The current foreach return contract is
[`foreach back-edge design`](superpowers/specs/2026-09-04-foreach-back-edge-design.md). [`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 ## Historical entry points
@@ -112,7 +112,6 @@ Exactly one step-kind key must be present. Allowed step-kind keys are:
- `use` - `use`
- `foreach` - `foreach`
- `interrupt` - `interrupt`
- `join`
Zero kind keys or multiple kind keys are validation errors. 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`. 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 ## Routes
Most ordinary edges should be authored through `routes`: Most ordinary edges should be authored through `routes`:
@@ -337,7 +323,8 @@ node_c.unreachable
-> runtime_error -> 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: 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: Potential later core work:
- true graph-as-node / subgraph support - true graph-as-node / subgraph support
- meaningful join semantics - explicit fork/gather semantics
- future START-edge support if `Workflow.start` changes - future START-edge support if `Workflow.start` changes
### Draft `route` Sugar ### Draft `route` Sugar
@@ -157,7 +157,6 @@ Example:
"SubgraphNode", "SubgraphNode",
"ConditionNode", "ConditionNode",
"ForeachNode", "ForeachNode",
"JoinNode",
"EndNode", "EndNode",
"InterruptNode" "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. clients do not need to know about the internal service split.
The service boundary is intentionally not capability-only. The current draft The service boundary is intentionally not capability-only. The current draft
model also represents `end`, `condition`, `interrupt`, `foreach`, `join`, model also represents `end`, `condition`, `interrupt`, `foreach`, `when`,
`when`, `choose`, and `match` steps, and core may gain more step kinds. This `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 slice adds semantic operations only where required, but new step-kind helpers
belong in `WorkflowDraftAuthoringApi` rather than a parallel authoring system. belong in `WorkflowDraftAuthoringApi` rather than a parallel authoring system.
@@ -132,7 +132,6 @@ Node presentation distinguishes the existing core node kinds:
- condition; - condition;
- interrupt; - interrupt;
- foreach; - foreach;
- join;
- end and other control nodes. - end and other control nodes.
Each node shows its stable id and concise semantic label. Selecting a node opens 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; 7. stale requests and partial failures cannot corrupt newer selections;
8. the `lda_report_workflow` lifecycle is readable without scrolling raw JSON; 8. the `lda_report_workflow` lifecycle is readable without scrolling raw JSON;
9. frontend tests, typecheck, production build, and optional live smoke pass. 9. frontend tests, typecheck, production build, and optional live smoke pass.
@@ -56,7 +56,6 @@ deliberate authoring vocabulary that is later lowered by
- `DraftUseStep` - `DraftUseStep`
- `DraftForeachStep` - `DraftForeachStep`
- `DraftInterruptStep` - `DraftInterruptStep`
- `DraftJoinStep`
- `DraftEndStep` - `DraftEndStep`
- `DraftWhenStep` - `DraftWhenStep`
- `DraftChooseStep` - `DraftChooseStep`
@@ -149,7 +148,6 @@ Declared top-level outcomes are:
- `foreach`: `loop`, `done`, plus `completed_with_errors` when the item-error - `foreach`: `loop`, `done`, plus `completed_with_errors` when the item-error
policy is `skip` or `collect`; policy is `skip` or `collect`;
- `interrupt`: `interrupt.outcomes`; - `interrupt`: `interrupt.outcomes`;
- `join`: `done`;
- `subgraph`: `subgraph.outcomes`. - `subgraph`: `subgraph.outcomes`.
The capability helper remains distinct because it resolves a capability, 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 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 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. variants, including interrupt schemas and subgraph workflow references.
## CLI Shape ## CLI Shape
@@ -186,7 +184,6 @@ Register a focused Typer application beneath `wf draft`:
wf draft add capability wf draft add capability
wf draft add interrupt wf draft add interrupt
wf draft add foreach wf draft add foreach
wf draft add join
wf draft add end wf draft add end
wf draft add when wf draft add when
wf draft add choose wf draft add choose
@@ -210,7 +207,6 @@ Variant-specific options are:
`--resume LOCAL_SOURCE=STATE_TARGET`, and repeatable `--outcome`; `--resume LOCAL_SOURCE=STATE_TARGET`, and repeatable `--outcome`;
- `foreach`: `--over`, `--as`, `--mode`, `--item-error`, optional - `foreach`: `--over`, `--as`, `--mode`, `--item-error`, optional
`--collect-to`, `--max-active`, and `--max-outstanding`; `--collect-to`, `--max-active`, and `--max-outstanding`;
- `join`: no variant-specific options;
- `end`: `--outcome` and no `--route`; - `end`: `--outcome` and no `--route`;
- `when`: `--condition-file`, `--then`, and `--otherwise`; - `when`: `--condition-file`, `--then`, and `--otherwise`;
- `choose`: `--clauses-file` containing the ordered clause array and - `choose`: `--clauses-file` containing the ordered clause array and
@@ -269,7 +265,7 @@ modules need them; avoid a broad CLI refactor.
### CLI ### CLI
- `wf draft add --help` lists all nine commands. - `wf draft add --help` lists all eight commands.
- Per-command help exposes only relevant options. - Per-command help exposes only relevant options.
- Every command builds the expected `DraftStep`, incoming source, and routes. - Every command builds the expected `DraftStep`, incoming source, and routes.
- Invalid flag combinations fail before calling the API. - 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; 1. selected-step input and output dataflow;
2. workflow Input, State, and Outcomes contract projections; 2. workflow Input, State, and Outcomes contract projections;
3. explicit End authoring and a typed Add step palette; 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. 5. direct graph gestures lowered through the same canonical mutations.
This document specifies only the first item. This document specifies only the first item.
@@ -56,7 +56,7 @@ Slice 6 includes:
Slice 6 excludes: Slice 6 excludes:
- explicit End-node creation; - 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; - graph gesture binding by drawing edges;
- arbitrary schema inference from runtime values; - arbitrary schema inference from runtime values;
- renaming existing step ids; - 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 Standard frame context currently includes values such as prior outcome, active
incoming edge, scope id, lineage id, and parent lineage id. Foreach iteration incoming edge, scope id, lineage id, and parent lineage id. Foreach iteration
frames additionally expose loop item, loop index, and the configured foreach 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. inventory without changing high-level clients.
Context availability is computed by core/API code using workflow graph and 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` **Step Attempt** is one admitted attempt to execute a selected workflow `Step`
in one frame. Node uses, conditions, foreach controllers, subgraph boundaries, 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 **Step Number** is the one-based ordinal assigned to an admitted step attempt
within a run. within a run.
@@ -256,7 +256,7 @@ with the run.
### Core counting ### Core counting
- A budget of one admits exactly one step and denies the second. - 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. count.
- A transition to legacy `END` does not create an extra attempt. - A transition to legacy `END` does not create an extra attempt.
- Handler failure still consumes its admitted step. - 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: Use `wf draft add` to add one typed step to an existing draft:
```text ```text
capability interrupt foreach join end capability interrupt foreach end
when choose match subgraph 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 Static values are not path mappings. Use `{"target": ..., "value": ...}` for
literal JSON values. Invalid draft step shapes are rejected instead of silently literal JSON values. Invalid draft step shapes are rejected instead of being
compiling to `join`. silently replaced with a placeholder step.
Generated MCP tool wrappers are intentionally naive. They normally expose both Generated MCP tool wrappers are intentionally naive. They normally expose both
`ok` and `error` outcomes, because MCP tool calls can report transport/provider `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__` Use explicit `end` steps for non-`ok` workflow outcomes. The legacy `__end__`
destination remains the shorthand for public workflow outcome `ok`. destination remains the shorthand for public workflow outcome `ok`.
### `join`
Joins control flow.
```json
{
"join": {}
}
```
### `when` ### `when`
Creates one boolean decision step. The condition uses the same JSON shape as 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 ```bash
wf draft create report_ws --name report_workflow wf draft create report_ws --name report_workflow
wf draft add join report_ws --revision 1 --step gate --route done=finish wf draft add end report_ws --revision 1 --step finish --outcome error
wf draft set-start report_ws --revision 2 --step gate wf draft set-start report_ws --revision 2 --step finish
wf draft add end report_ws --revision 3 --step finish --outcome error wf draft set-contract report_ws --revision 3 --outcome error
wf draft set-contract report_ws --revision 4 --outcome error
wf draft validate report_ws wf draft validate report_ws
``` ```
@@ -255,7 +255,7 @@ an unexpected extra argument because it is not attached to its own flag.
Adds any typed `DraftStep` with optional incoming and outgoing route wiring in Adds any typed `DraftStep` with optional incoming and outgoing route wiring in
one revision. The CLI exposes one command per kind under `wf draft add`: one revision. The CLI exposes one command per kind under `wf draft add`:
`interrupt`, `foreach`, `join`, `end`, `when`, `choose`, `match`, and `interrupt`, `foreach`, `end`, `when`, `choose`, `match`, and
`subgraph`. Decision targets are embedded and reject `--route`. Interrupts `subgraph`. Decision targets are embedded and reject `--route`. Interrupts
and subgraphs preserve JSON Schema boundary contracts. Invalid intermediate and subgraphs preserve JSON Schema boundary contracts. Invalid intermediate
drafts remain saveable in the workspace but must pass `wf draft validate` drafts remain saveable in the workspace but must pass `wf draft validate`
-3
View File
@@ -16,7 +16,6 @@ from wf_artifacts.drafts.models import (
DraftEndStep, DraftEndStep,
DraftForeachStep, DraftForeachStep,
DraftInterruptStep, DraftInterruptStep,
DraftJoinStep,
DraftMatchStep, DraftMatchStep,
DraftStep, DraftStep,
DraftSubgraphStep, DraftSubgraphStep,
@@ -353,8 +352,6 @@ class WorkflowDraftAuthoringApi:
return outcomes return outcomes
if isinstance(step, DraftInterruptStep): if isinstance(step, DraftInterruptStep):
return set(step.interrupt.outcomes) return set(step.interrupt.outcomes)
if isinstance(step, DraftJoinStep):
return {"done"}
if isinstance(step, DraftSubgraphStep): if isinstance(step, DraftSubgraphStep):
return set(step.subgraph.outcomes) return set(step.subgraph.outcomes)
if isinstance( if isinstance(
-2
View File
@@ -11,7 +11,6 @@ from .models import (
DraftEndStep, DraftEndStep,
DraftForeachStep, DraftForeachStep,
DraftInterruptStep, DraftInterruptStep,
DraftJoinStep,
DraftMatchCase, DraftMatchCase,
DraftMatchStep, DraftMatchStep,
DraftSubgraphStep, DraftSubgraphStep,
@@ -27,7 +26,6 @@ __all__ = [
"DraftEndStep", "DraftEndStep",
"DraftForeachStep", "DraftForeachStep",
"DraftInterruptStep", "DraftInterruptStep",
"DraftJoinStep",
"DraftMatchCase", "DraftMatchCase",
"DraftMatchStep", "DraftMatchStep",
"DraftSubgraphStep", "DraftSubgraphStep",
+1 -6
View File
@@ -4,7 +4,7 @@ from typing import Any
from wf_authoring import WorkflowBuilder from wf_authoring import WorkflowBuilder
from wf_authoring.dsl import PathExpr from wf_authoring.dsl import PathExpr
from wf_core import JoinNode, SubgraphNode, Workflow from wf_core import SubgraphNode, Workflow
from wf_core.paths import GraphSourcePath from wf_core.paths import GraphSourcePath
from .models import ( from .models import (
@@ -12,7 +12,6 @@ from .models import (
DraftEndStep, DraftEndStep,
DraftForeachStep, DraftForeachStep,
DraftInterruptStep, DraftInterruptStep,
DraftJoinStep,
DraftMatchStep, DraftMatchStep,
DraftStep, DraftStep,
DraftSubgraphStep, DraftSubgraphStep,
@@ -84,10 +83,6 @@ def _add_step(builder: WorkflowBuilder, step_id: str, step: DraftStep):
return builder.interrupt( return builder.interrupt(
**interrupt_kwargs, **interrupt_kwargs,
) )
if isinstance(step, DraftJoinStep):
node = JoinNode(id=step_id, type="join")
builder.nodes.append(node)
return node
if isinstance(step, DraftEndStep): if isinstance(step, DraftEndStep):
return builder.end(step.end.outcome, id=step_id) return builder.end(step.end.outcome, id=step_id)
if isinstance(step, DraftWhenStep): if isinstance(step, DraftWhenStep):
-9
View File
@@ -220,14 +220,6 @@ class DraftSubgraphStep(BaseModel):
subgraph: DraftSubgraphPayload subgraph: DraftSubgraphPayload
class DraftJoinStep(BaseModel):
"""Draft step that emits the current core join node."""
model_config = ConfigDict(extra="forbid")
join: JsonObject = Field(default_factory=dict)
class DraftEndPayload(BaseModel): class DraftEndPayload(BaseModel):
"""Payload for one explicit workflow terminal outcome.""" """Payload for one explicit workflow terminal outcome."""
@@ -323,7 +315,6 @@ DraftStep = (
DraftUseStep DraftUseStep
| DraftForeachStep | DraftForeachStep
| DraftInterruptStep | DraftInterruptStep
| DraftJoinStep
| DraftEndStep | DraftEndStep
| DraftWhenStep | DraftWhenStep
| DraftChooseStep | DraftChooseStep
+1 -9
View File
@@ -8,7 +8,6 @@ from wf_core import (
EndNode, EndNode,
ForeachNode, ForeachNode,
InterruptNode, InterruptNode,
JoinNode,
NodeUse, NodeUse,
SubgraphNode, SubgraphNode,
) )
@@ -16,14 +15,7 @@ from wf_core import (
from ..nodes import NodeSpec from ..nodes import NodeSpec
StepRef: TypeAlias = ( StepRef: TypeAlias = (
str str | NodeUse | SubgraphNode | ConditionNode | ForeachNode | InterruptNode | EndNode
| NodeUse
| SubgraphNode
| ConditionNode
| ForeachNode
| InterruptNode
| JoinNode
| EndNode
) )
"""A reference to a step, which can be either a string id or a node object """A reference to a step, which can be either a string id or a node object
that should be auto-used.""" that should be auto-used."""
-38
View File
@@ -16,7 +16,6 @@ from wf_artifacts.drafts.models import (
DraftForeachStep, DraftForeachStep,
DraftInterruptPayload, DraftInterruptPayload,
DraftInterruptStep, DraftInterruptStep,
DraftJoinStep,
DraftMatchCase, DraftMatchCase,
DraftMatchPayload, DraftMatchPayload,
DraftMatchStep, DraftMatchStep,
@@ -425,43 +424,6 @@ def add_foreach_step(
) )
@app.command("join")
def add_join_step(
ctx: typer.Context,
workspace_id: Annotated[str, typer.Argument(help="Draft workspace id.")],
revision: Annotated[
int, typer.Option("--revision", min=1, help="Expected workspace revision.")
],
step_id: Annotated[str, typer.Option("--step", help="New draft step id.")],
from_step: Annotated[
str | None, typer.Option("--from-step", help="Incoming step id.")
] = None,
from_outcome: Annotated[
str | None,
typer.Option("--from-outcome", help="Outcome on --from-step (default: ok)."),
] = None,
route: Annotated[
list[str] | None,
typer.Option("--route", help="Route mapping OUTCOME=TARGET. Repeat as needed."),
] = None,
) -> None:
"""Add a join step.
Example: `wf draft add join WS --revision 1 --step joined --route done=__end__`.
Run `wf draft validate WS` after editing.
"""
_submit_step(
ctx,
workspace_id=workspace_id,
revision=revision,
step_id=step_id,
step=DraftJoinStep(join={}),
from_step=from_step,
from_outcome=from_outcome,
routes=_parse_route_flags(route) or None,
)
@app.command("end") @app.command("end")
def add_end_step( def add_end_step(
ctx: typer.Context, ctx: typer.Context,
+1 -1
View File
@@ -56,7 +56,7 @@ def write_json_file(path: Path, payload: Any, *, force: bool) -> None:
output.write("\n") output.write("\n")
except FileExistsError as exc: except FileExistsError as exc:
raise CliInputError( raise CliInputError(
f"file {path!s} already exists; use --force to replace it" f"file already exists: {path!s}; use --force to replace it"
) from exc ) from exc
except OSError as exc: except OSError as exc:
raise CliInputError(f"could not write file {path!s}: {exc}") from exc raise CliInputError(f"could not write file {path!s}: {exc}") from exc
-2
View File
@@ -11,7 +11,6 @@ from .models import (
InputPathBinding, InputPathBinding,
InputValueBinding, InputValueBinding,
InterruptNode, InterruptNode,
JoinNode,
JsonValue, JsonValue,
LiteralExpression, LiteralExpression,
NodeDef, NodeDef,
@@ -89,7 +88,6 @@ __all__ = [
"InputValueBinding", "InputValueBinding",
"InterruptRequest", "InterruptRequest",
"InterruptRoute", "InterruptRoute",
"JoinNode",
"JsonValue", "JsonValue",
"LiteralExpression", "LiteralExpression",
"NodeDef", "NodeDef",
-2
View File
@@ -30,7 +30,6 @@ from wf_core.models.steps import (
ForeachItemErrorPolicy, ForeachItemErrorPolicy,
ForeachNode, ForeachNode,
InterruptNode, InterruptNode,
JoinNode,
NodeUse, NodeUse,
Step, Step,
SubgraphNode, SubgraphNode,
@@ -54,7 +53,6 @@ __all__ = [
"InputExpressionBinding", "InputExpressionBinding",
"InputPathBinding", "InputPathBinding",
"InputValueBinding", "InputValueBinding",
"JoinNode",
"JsonValue", "JsonValue",
"LiteralExpression", "LiteralExpression",
"LiteralOperand", "LiteralOperand",
+1 -14
View File
@@ -293,13 +293,6 @@ class ForeachNode(BaseModel):
return GraphSourcePath("context", ("foreach", self.id, "index")) return GraphSourcePath("context", ("foreach", self.id, "index"))
class JoinNode(BaseModel):
"""Control-flow step that marks a branch or frame as joined."""
id: str
type: Literal["join"]
class EndNode(BaseModel): class EndNode(BaseModel):
"""Explicit workflow terminal that sets the workflow-level outcome. """Explicit workflow terminal that sets the workflow-level outcome.
@@ -411,13 +404,7 @@ class InterruptNode(BaseModel):
Step = Annotated[ Step = Annotated[
NodeUse NodeUse | SubgraphNode | ConditionNode | ForeachNode | EndNode | InterruptNode,
| SubgraphNode
| ConditionNode
| ForeachNode
| JoinNode
| EndNode
| InterruptNode,
Field(discriminator="type"), Field(discriminator="type"),
] ]
"""Discriminated union of all executable workflow graph steps.""" """Discriminated union of all executable workflow graph steps."""
-9
View File
@@ -41,15 +41,6 @@ def handle_condition_step(
) )
def handle_join_step() -> StepExecutionResult:
return StepExecutionResult(
outcome="done",
resolved_input={},
output={},
state_changes={},
)
def handle_interrupt_step( def handle_interrupt_step(
run: RunState, run: RunState,
step: InterruptNode, step: InterruptNode,
-6
View File
@@ -10,7 +10,6 @@ from wf_core.models.steps import (
EndNode, EndNode,
ForeachNode, ForeachNode,
InterruptNode, InterruptNode,
JoinNode,
NodeUse, NodeUse,
SubgraphNode, SubgraphNode,
) )
@@ -22,7 +21,6 @@ from wf_core.runtime.ops.foreach import step_foreach
from wf_core.runtime.ops.handlers import ( from wf_core.runtime.ops.handlers import (
handle_condition_step, handle_condition_step,
handle_interrupt_step, handle_interrupt_step,
handle_join_step,
) )
from wf_core.runtime.ops.index import WorkflowIndex, build_workflow_index from wf_core.runtime.ops.index import WorkflowIndex, build_workflow_index
from wf_core.runtime.ops.merges import ReducerDefinition from wf_core.runtime.ops.merges import ReducerDefinition
@@ -149,8 +147,6 @@ def step_workflow(
raise raise
elif isinstance(step, ConditionNode): elif isinstance(step, ConditionNode):
step_result = handle_condition_step(run, step) step_result = handle_condition_step(run, step)
elif isinstance(step, JoinNode):
step_result = handle_join_step()
elif isinstance(step, EndNode): elif isinstance(step, EndNode):
return complete_end_step( return complete_end_step(
run=run, run=run,
@@ -264,8 +260,6 @@ async def step_workflow_async(
raise raise
elif isinstance(step, ConditionNode): elif isinstance(step, ConditionNode):
step_result = handle_condition_step(run, step) step_result = handle_condition_step(run, step)
elif isinstance(step, JoinNode):
step_result = handle_join_step()
elif isinstance(step, EndNode): elif isinstance(step, EndNode):
return complete_end_step( return complete_end_step(
run=run, run=run,
-2
View File
@@ -19,8 +19,6 @@ def declared_outcomes_for_step(step: Step, node_defs: dict[str, NodeDef]) -> set
if step.item_error.action in {"skip", "collect"}: if step.item_error.action in {"skip", "collect"}:
outcomes.add("completed_with_errors") outcomes.add("completed_with_errors")
return outcomes return outcomes
if step.type == "join":
return {"done"}
if isinstance(step, EndNode): if isinstance(step, EndNode):
return set() return set()
if isinstance(step, InterruptNode): if isinstance(step, InterruptNode):
+1 -1
View File
@@ -226,7 +226,7 @@ def test_draft_step_requires_exactly_one_kind_key() -> None:
assert isinstance(steps, dict) assert isinstance(steps, dict)
echo = steps["echo"] echo = steps["echo"]
assert isinstance(echo, dict) assert isinstance(echo, dict)
echo["join"] = {} echo["end"] = {"outcome": "ok"}
with pytest.raises(ValidationError) as exc_info: with pytest.raises(ValidationError) as exc_info:
WorkflowDraft.model_validate(draft) WorkflowDraft.model_validate(draft)
+5 -6
View File
@@ -675,7 +675,7 @@ async def test_update_capability_step_rejects_wrong_kind_and_invalid_input_atomi
register_echo=True, register_echo=True,
) )
draft = _echo_draft() draft = _echo_draft()
draft["steps"]["joined"] = {"join": ["echo"]} draft["steps"]["joined"] = {"end": {}}
await draft_api.create_draft_workspace(workspace_id="echo", draft=draft) await draft_api.create_draft_workspace(workspace_id="echo", draft=draft)
before = await draft_api.get_draft_workspace( before = await draft_api.get_draft_workspace(
workspace_id="echo", workspace_id="echo",
@@ -3136,7 +3136,7 @@ async def test_set_step_output_bindings_rejects_non_capability_step_without_muta
await draft_api.patch_draft_workspace( await draft_api.patch_draft_workspace(
workspace_id="non-capability-output-step", workspace_id="non-capability-output-step",
revision=1, revision=1,
patch=[{"op": "replace", "path": "/steps/render", "value": {"join": {}}}], patch=[{"op": "replace", "path": "/steps/render", "value": {"end": {}}}],
) )
before = await draft_api.get_draft_workspace( before = await draft_api.get_draft_workspace(
workspace_id="non-capability-output-step", workspace_id="non-capability-output-step",
@@ -3246,7 +3246,7 @@ async def test_set_step_output_bindings_stale_revision_precedes_non_capability_s
await draft_api.patch_draft_workspace( await draft_api.patch_draft_workspace(
workspace_id="stale-output-non-capability", workspace_id="stale-output-non-capability",
revision=1, revision=1,
patch=[{"op": "replace", "path": "/steps/render", "value": {"join": {}}}], patch=[{"op": "replace", "path": "/steps/render", "value": {"end": {}}}],
) )
before = await draft_api.get_draft_workspace( before = await draft_api.get_draft_workspace(
workspace_id="stale-output-non-capability", workspace_id="stale-output-non-capability",
@@ -3504,7 +3504,7 @@ async def test_set_step_input_bindings_rejects_non_capability_step_without_mutat
register_echo=True, register_echo=True,
) )
draft = _structured_report_draft() draft = _structured_report_draft()
draft["steps"]["report"] = {"join": {}} draft["steps"]["report"] = {"end": {}}
await draft_api.create_draft_workspace(workspace_id="non_capability", draft=draft) await draft_api.create_draft_workspace(workspace_id="non_capability", draft=draft)
api = WorkflowApi(authoring.context, drafts=True) api = WorkflowApi(authoring.context, drafts=True)
before = await draft_api.get_draft_workspace( before = await draft_api.get_draft_workspace(
@@ -3956,7 +3956,6 @@ async def test_add_step_from_capability_rejects_existing_step_id(
"interrupt", "interrupt",
{"interrupt": {"kind": "review", "outcomes": ["submitted"]}}, {"interrupt": {"kind": "review", "outcomes": ["submitted"]}},
), ),
("join", {"join": {}}),
("end", {"end": {"outcome": "ok"}}), ("end", {"end": {"outcome": "ok"}}),
( (
"when", "when",
@@ -4067,7 +4066,7 @@ async def test_add_step_stale_revision_wins_over_content_preflight(
before = await draft_api.get_draft_workspace( before = await draft_api.get_draft_workspace(
workspace_id="draft_ws", include_draft=True workspace_id="draft_ws", include_draft=True
) )
step = TypeAdapter(DraftStep).validate_python({"join": {}}) step = TypeAdapter(DraftStep).validate_python({"end": {}})
result = await api.add_step( result = await api.add_step(
workspace_id="draft_ws", workspace_id="draft_ws",
+5 -34
View File
@@ -824,7 +824,6 @@ def test_wf_draft_add_help_lists_typed_step_commands_and_removes_flat_command()
"capability", "capability",
"interrupt", "interrupt",
"foreach", "foreach",
"join",
"end", "end",
"when", "when",
"choose", "choose",
@@ -1531,7 +1530,7 @@ def test_wf_draft_add_foreach_builds_concurrent_policy(monkeypatch) -> None:
assert calls[1]["step"].foreach.concurrent.max_outstanding == 20 assert calls[1]["step"].foreach.concurrent.max_outstanding == 20
def test_wf_draft_add_join_and_end_build_concrete_steps(monkeypatch) -> None: def test_wf_draft_add_end_builds_concrete_step(monkeypatch) -> None:
calls: list[dict[str, Any]] = [] calls: list[dict[str, Any]] = []
class FakeHandlers: class FakeHandlers:
@@ -1544,25 +1543,6 @@ def test_wf_draft_add_join_and_end_build_concrete_steps(monkeypatch) -> None:
"wf_cli.commands.draft_add.load_cli_context", lambda _ctx: context "wf_cli.commands.draft_add.load_cli_context", lambda _ctx: context
) )
join_result = runner.invoke(
app,
[
"draft",
"add",
"join",
"workspace",
"--revision",
"1",
"--step",
"joined",
"--from-step",
"each_issue",
"--from-outcome",
"done",
"--route",
"done=finish",
],
)
end_result = runner.invoke( end_result = runner.invoke(
app, app,
[ [
@@ -1571,7 +1551,7 @@ def test_wf_draft_add_join_and_end_build_concrete_steps(monkeypatch) -> None:
"end", "end",
"workspace", "workspace",
"--revision", "--revision",
"2", "1",
"--step", "--step",
"finish", "finish",
"--outcome", "--outcome",
@@ -1579,12 +1559,9 @@ def test_wf_draft_add_join_and_end_build_concrete_steps(monkeypatch) -> None:
], ],
) )
assert join_result.exit_code == 0, join_result.output
assert end_result.exit_code == 0, end_result.output assert end_result.exit_code == 0, end_result.output
assert calls[0]["step"].model_dump(mode="json") == {"join": {}} assert calls[0]["step"].model_dump(mode="json") == {"end": {"outcome": "completed"}}
assert calls[0]["routes"] == {"done": "finish"} assert calls[0]["routes"] is None
assert calls[1]["step"].model_dump(mode="json") == {"end": {"outcome": "completed"}}
assert calls[1]["routes"] is None
def test_wf_draft_add_control_commands_reject_invalid_input_before_api_call( def test_wf_draft_add_control_commands_reject_invalid_input_before_api_call(
@@ -1788,12 +1765,9 @@ def test_wf_draft_add_control_commands_reject_invalid_input_before_api_call(
def test_wf_draft_add_control_command_help_is_type_specific() -> None: def test_wf_draft_add_control_command_help_is_type_specific() -> None:
interrupt = runner.invoke(app, ["draft", "add", "interrupt", "--help"]) interrupt = runner.invoke(app, ["draft", "add", "interrupt", "--help"])
foreach = runner.invoke(app, ["draft", "add", "foreach", "--help"]) foreach = runner.invoke(app, ["draft", "add", "foreach", "--help"])
join = runner.invoke(app, ["draft", "add", "join", "--help"])
end = runner.invoke(app, ["draft", "add", "end", "--help"]) end = runner.invoke(app, ["draft", "add", "end", "--help"])
assert ( assert interrupt.exit_code == foreach.exit_code == end.exit_code == 0
interrupt.exit_code == foreach.exit_code == join.exit_code == end.exit_code == 0
)
assert "--request-schema-file" in interrupt.output assert "--request-schema-file" in interrupt.output
assert "--resume-schema-file" in interrupt.output assert "--resume-schema-file" in interrupt.output
assert "--request" in interrupt.output assert "--request" in interrupt.output
@@ -1806,9 +1780,6 @@ def test_wf_draft_add_control_command_help_is_type_specific() -> None:
assert "--item-error" in foreach.output assert "--item-error" in foreach.output
assert "--collect-to" in foreach.output assert "--collect-to" in foreach.output
assert "--request-schema-file" not in foreach.output assert "--request-schema-file" not in foreach.output
assert "--from-step" in join.output
assert "--route" in join.output
assert "--request-schema-file" not in join.output
assert "--outcome" in end.output assert "--outcome" in end.output
assert "--route" not in end.output assert "--route" not in end.output
+8 -21
View File
@@ -771,14 +771,14 @@ def test_wf_remote_capability_free_draft_lifecycle(monkeypatch, tmp_path) -> Non
[ [
"draft", "draft",
"add", "add",
"join", "end",
"control_ws", "control_ws",
"--revision", "--revision",
"1", "1",
"--step", "--step",
"gate", "finish",
"--route", "--outcome",
"done=finish", "error",
], ],
[ [
"draft", "draft",
@@ -787,26 +787,14 @@ def test_wf_remote_capability_free_draft_lifecycle(monkeypatch, tmp_path) -> Non
"--revision", "--revision",
"2", "2",
"--step", "--step",
"gate",
],
[
"draft",
"add",
"end",
"control_ws",
"--revision",
"3",
"--step",
"finish", "finish",
"--outcome",
"error",
], ],
[ [
"draft", "draft",
"set-contract", "set-contract",
"control_ws", "control_ws",
"--revision", "--revision",
"4", "3",
"--outcome", "--outcome",
"error", "error",
], ],
@@ -824,10 +812,10 @@ def test_wf_remote_capability_free_draft_lifecycle(monkeypatch, tmp_path) -> Non
assert '"status": "valid"' in results[-1].output assert '"status": "valid"' in results[-1].output
assert inspected.exit_code == 0, inspected.output assert inspected.exit_code == 0, inspected.output
payload = json.loads(inspected.output) payload = json.loads(inspected.output)
assert payload["revision"] == 5 assert payload["revision"] == 4
assert payload["draft"]["start"] == "gate" assert payload["draft"]["start"] == "finish"
assert payload["draft"]["outcomes"] == ["error"] assert payload["draft"]["outcomes"] == ["error"]
assert set(payload["draft"]["steps"]) == {"gate", "finish"} assert set(payload["draft"]["steps"]) == {"finish"}
def test_wf_draft_export_uses_remote_get_and_writes_only_draft( def test_wf_draft_export_uses_remote_get_and_writes_only_draft(
@@ -2409,7 +2397,6 @@ def test_wf_draft_add_control_steps_use_generic_rpc_target(
"completed_with_errors": "__end__", "completed_with_errors": "__end__",
}, },
), ),
("join", ["--route", "done=__end__"], {}, {"done": "__end__"}),
("end", ["--outcome", "ok"], {"outcome": "ok"}, None), ("end", ["--outcome", "ok"], {"outcome": "ok"}, None),
( (
"when", "when",
-1
View File
@@ -67,7 +67,6 @@ def test_schema_compact_alias_is_json_outline_without_refs() -> None:
"SubgraphNode", "SubgraphNode",
"ConditionNode", "ConditionNode",
"ForeachNode", "ForeachNode",
"JoinNode",
"EndNode", "EndNode",
"InterruptNode", "InterruptNode",
] ]
+1 -1
View File
@@ -92,7 +92,7 @@ def test_generates_the_complete_real_workflow_contract() -> None:
assert len(manifest["operations"]) == 72 assert len(manifest["operations"]) == 72
assert len({operation["method"] for operation in manifest["operations"]}) == 72 assert len({operation["method"] for operation in manifest["operations"]}) == 72
assert len(schemas) == 142 assert len(schemas) == 141
assert len(manifest["components"]["errors"]) == 1 assert len(manifest["components"]["errors"]) == 1
assert all( assert all(
set(operation["result"]["schema"]) == {"$ref"} set(operation["result"]["schema"]) == {"$ref"}
+2 -2
View File
@@ -2090,7 +2090,7 @@ def test_add_draft_step_params_reject_invalid_kind_and_route_source() -> None:
"workspace_id": "ws", "workspace_id": "ws",
"revision": 1, "revision": 1,
"step_id": "bad", "step_id": "bad",
"step": {"use": "demo.echo", "join": {}}, "step": {"use": "demo.echo", "end": {}},
} }
) )
@@ -2104,7 +2104,7 @@ def test_add_draft_step_params_reject_invalid_kind_and_route_source() -> None:
"workspace_id": "ws", "workspace_id": "ws",
"revision": 1, "revision": 1,
"step_id": "new", "step_id": "new",
"step": {"join": {}}, "step": {"end": {}},
"incoming": incoming, "incoming": incoming,
} }
) )
+13 -26
View File
@@ -13,7 +13,6 @@ from wf_api.surface import RouteSource, WorkflowDraftSurface
from wf_artifacts.drafts.models import ( from wf_artifacts.drafts.models import (
DraftEndPayload, DraftEndPayload,
DraftEndStep, DraftEndStep,
DraftJoinStep,
DraftStep, DraftStep,
) )
from wf_core import END from wf_core import END
@@ -573,32 +572,25 @@ async def test_rpc_client_builds_capability_free_draft_lifecycle(tmp_path) -> No
workspace_id="control_first", workspace_id="control_first",
name="control_first", name="control_first",
) )
joined = await client.add_step(
workspace_id="control_first",
revision=created["revision"],
step_id="gate",
step=DraftJoinStep(join={}),
routes={"done": "finish"},
)
started = await client.set_draft_start(
workspace_id="control_first",
revision=joined["revision"],
step_id="gate",
)
ended = await client.add_step( ended = await client.add_step(
workspace_id="control_first", workspace_id="control_first",
revision=started["revision"], revision=created["revision"],
step_id="finish", step_id="finish",
step=DraftEndStep(end=DraftEndPayload(outcome="error")), step=DraftEndStep(end=DraftEndPayload(outcome="error")),
) )
contracted = await client.set_draft_contract( started = await client.set_draft_start(
workspace_id="control_first", workspace_id="control_first",
revision=ended["revision"], revision=ended["revision"],
step_id="finish",
)
contracted = await client.set_draft_contract(
workspace_id="control_first",
revision=started["revision"],
outcomes=("error",), outcomes=("error",),
) )
stale = await client.set_draft_start( stale = await client.set_draft_start(
workspace_id="control_first", workspace_id="control_first",
revision=ended["revision"], revision=started["revision"],
step_id="finish", step_id="finish",
) )
validated = await client.validate_draft_workspace(workspace_id="control_first") validated = await client.validate_draft_workspace(workspace_id="control_first")
@@ -609,22 +601,18 @@ async def test_rpc_client_builds_capability_free_draft_lifecycle(tmp_path) -> No
) )
assert created["revision"] == 1 assert created["revision"] == 1
assert joined["revision"] == 2 assert ended["revision"] == 2
assert started["revision"] == 3 assert started["revision"] == 3
assert ended["revision"] == 4 assert contracted["revision"] == 4
assert contracted["revision"] == 5
assert stale["status"] == "conflict" assert stale["status"] == "conflict"
assert stale["diagnostics"][0]["code"] == "revision_conflict" assert stale["diagnostics"][0]["code"] == "revision_conflict"
assert validated["status"] == "valid" assert validated["status"] == "valid"
assert "compiled_plan" in compiled assert "compiled_plan" in compiled
assert compiled["compiled_plan"]["start"] == "gate" assert compiled["compiled_plan"]["start"] == "finish"
draft = inspected.get("draft") draft = inspected.get("draft")
assert draft is not None assert draft is not None
assert draft["start"] == "gate" assert draft["start"] == "finish"
assert draft["steps"] == { assert draft["steps"] == {"finish": {"end": {"outcome": "error"}}}
"gate": {"join": {}},
"finish": {"end": {"outcome": "error"}},
}
def test_rpc_client_satisfies_draft_surface_static_shape() -> None: def test_rpc_client_satisfies_draft_surface_static_shape() -> None:
@@ -1226,7 +1214,6 @@ async def test_rpc_client_preserves_nested_local_path_strings() -> None:
"outcomes": ["submitted"], "outcomes": ["submitted"],
}, },
), ),
("join", TypeAdapter(DraftStep).validate_python({"join": {}}), {}),
("end", TypeAdapter(DraftStep).validate_python({"end": {}}), {"outcome": "ok"}), ("end", TypeAdapter(DraftStep).validate_python({"end": {}}), {"outcome": "ok"}),
( (
"when", "when",
@@ -31,8 +31,6 @@ const nodeColor = (data: WorkflowGraphNodeData): string => {
return "#ef4444"; return "#ef4444";
case "foreach": case "foreach":
return "#8b5cf6"; return "#8b5cf6";
case "join":
return "#10b981";
case "end": case "end":
return "#6b7280"; return "#6b7280";
default: default:
@@ -7,7 +7,6 @@ export type WorkflowGraphNodeKind =
| "condition" | "condition"
| "interrupt" | "interrupt"
| "foreach" | "foreach"
| "join"
| "end" | "end"
| "unsupported"; | "unsupported";
@@ -94,8 +93,6 @@ const mapNodeKind = (type: unknown): WorkflowGraphNodeKind => {
return "interrupt"; return "interrupt";
case "foreach": case "foreach":
return "foreach"; return "foreach";
case "join":
return "join";
case "end": case "end":
return "end"; return "end";
default: default:
@@ -119,7 +116,6 @@ const buildLabel = (
return typeof node.kind === "string" ? node.kind : "Interrupt"; return typeof node.kind === "string" ? node.kind : "Interrupt";
} }
if (type === "foreach") return "For Each"; if (type === "foreach") return "For Each";
if (type === "join") return "Join";
if (type === "subgraph") { if (type === "subgraph") {
const workflowRef = typeof node.workflow === "string" ? node.workflow : undefined; const workflowRef = typeof node.workflow === "string" ? node.workflow : undefined;
if (workflowRef) { if (workflowRef) {
@@ -46,7 +46,6 @@ describe("architectureCatalog", () => {
"NodeUse", "NodeUse",
"Condition", "Condition",
"Foreach", "Foreach",
"Join",
"Subgraph", "Subgraph",
"Interrupt", "Interrupt",
"End", "End",
@@ -524,7 +524,6 @@ export const architectureCatalog: FigureCatalogDefinition = defineFigureCatalog(
"node-use": { x: 0, y: 0 }, "node-use": { x: 0, y: 0 },
"condition": { x: 300, y: 0 }, "condition": { x: 300, y: 0 },
"foreach": { x: 600, y: 0 }, "foreach": { x: 600, y: 0 },
"join": { x: 900, y: 0 },
"subgraph": { x: 150, y: 210 }, "subgraph": { x: 150, y: 210 },
"interrupt": { x: 450, y: 210 }, "interrupt": { x: 450, y: 210 },
"end": { x: 750, y: 210 }, "end": { x: 750, y: 210 },
@@ -542,7 +541,6 @@ export const architectureCatalog: FigureCatalogDefinition = defineFigureCatalog(
}, },
{ id: "condition", label: "Condition", summary: "Route true or false", kind: "decision", icon: "branch", evidencePointer: "src/wf_core/models/steps.py" }, { id: "condition", label: "Condition", summary: "Route true or false", kind: "decision", icon: "branch", evidencePointer: "src/wf_core/models/steps.py" },
{ id: "foreach", label: "Foreach", summary: "Create item frames and barrier", kind: "loop", icon: "repeat", evidencePointer: "src/wf_core/models/steps.py" }, { id: "foreach", label: "Foreach", summary: "Create item frames and barrier", kind: "loop", icon: "repeat", evidencePointer: "src/wf_core/models/steps.py" },
{ id: "join", label: "Join", summary: "Close a branch or frame", kind: "operation", icon: "workflow", evidencePointer: "src/wf_core/models/steps.py" },
{ id: "subgraph", label: "Subgraph", summary: "Enter a prepared child workflow", kind: "boundary", icon: "layers", evidencePointer: "src/wf_core/models/steps.py" }, { id: "subgraph", label: "Subgraph", summary: "Enter a prepared child workflow", kind: "boundary", icon: "layers", evidencePointer: "src/wf_core/models/steps.py" },
{ id: "interrupt", label: "Interrupt", summary: "Persist request and wait", kind: "boundary", icon: "pause", evidencePointer: "src/wf_core/models/steps.py", childFigureId: "interrupt-contract-detail" }, { id: "interrupt", label: "Interrupt", summary: "Persist request and wait", kind: "boundary", icon: "pause", evidencePointer: "src/wf_core/models/steps.py", childFigureId: "interrupt-contract-detail" },
{ id: "end", label: "End", summary: "Project a terminal outcome", kind: "terminal", icon: "stop", evidencePointer: "src/wf_core/models/steps.py" }, { id: "end", label: "End", summary: "Project a terminal outcome", kind: "terminal", icon: "stop", evidencePointer: "src/wf_core/models/steps.py" },
@@ -57,7 +57,6 @@ const stepKind = (step: JsonRecord): string => {
"choose", "choose",
"match", "match",
"foreach", "foreach",
"join",
"end", "end",
]) { ]) {
if (kind in step) return kind; if (kind in step) return kind;
@@ -464,7 +464,6 @@ export interface WorkflowContractMap {
| DraftUseStep | DraftUseStep
| DraftForeachStep | DraftForeachStep
| DraftInterruptStep | DraftInterruptStep
| DraftJoinStep
| DraftEndStep | DraftEndStep
| DraftWhenStep | DraftWhenStep
| DraftChooseStep | DraftChooseStep
@@ -1867,17 +1866,6 @@ export interface SchemaRef {
type?: string | string[] | null; type?: string | string[] | null;
[k: string]: unknown; [k: string]: unknown;
} }
/**
* Draft step that emits the current core join node.
*
* This interface was referenced by `WorkflowContractMap`'s JSON-Schema
* via the `definition` "DraftJoinStep".
*/
export interface DraftJoinStep {
join?: {
[k: string]: unknown;
};
}
/** /**
* Draft step that lowers to core `EndNode`. * Draft step that lowers to core `EndNode`.
* *