From cccdbc0a476d2ed343a7c2aa01d24b5e6f5e2317 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 5 Sep 2026 17:47:57 +0700 Subject: [PATCH] refactor: remove placeholder join step --- CONTEXT.md | 5 ++- contracts/workflow-api.manifest.json | 14 ------- ...xplicit-fork-and-topology-driven-gather.md | 21 +++++----- docs/current_roadmap.md | 32 ++++++++------- ...026-05-18-workflow-draft-surface-design.md | 19 ++------- .../2026-06-22-wf-schema-command-design.md | 1 - ...06-27-draft-semantic-authoring-boundary.md | 4 +- ...-02-workflow-console-lifecycle-explorer.md | 2 - ...026-07-20-generic-draft-add-step-design.md | 8 +--- ...w-console-selected-step-dataflow-design.md | 2 +- ...-workflow-console-contract-graph-design.md | 4 +- .../2026-09-04-run-step-budget-design.md | 4 +- docs/wf_cli.md | 2 +- docs/workflow_drafts.md | 21 +++------- .../references/draft-workspaces.md | 2 +- src/wf_api/draft_authoring.py | 3 -- src/wf_artifacts/drafts/__init__.py | 2 - src/wf_artifacts/drafts/adapter.py | 7 +--- src/wf_artifacts/drafts/models.py | 9 ----- src/wf_authoring/builder/refs.py | 10 +---- src/wf_cli/commands/draft_add.py | 38 ------------------ src/wf_cli/io.py | 2 +- src/wf_core/__init__.py | 2 - src/wf_core/models/__init__.py | 2 - src/wf_core/models/steps.py | 15 +------ src/wf_core/runtime/ops/handlers.py | 9 ----- src/wf_core/runtime/step.py | 6 --- src/wf_core/validation/outcomes.py | 2 - tests/artifacts/test_draft_models.py | 2 +- tests/wf_api/test_drafts_service.py | 11 +++--- tests/wf_cli/test_app.py | 39 +++---------------- tests/wf_cli/test_remote_target.py | 29 ++++---------- tests/wf_cli/test_schema.py | 1 - tests/wf_contract_manifest/test_generate.py | 2 +- tests/wf_transport_rpc_http/test_app.py | 4 +- tests/wf_transport_rpc_http/test_client.py | 39 +++++++------------ web/apps/console/src/graph/WorkflowGraph.tsx | 2 - web/apps/console/src/graph/graph-model.ts | 4 -- .../figures/architecture-catalog.test.ts | 1 - .../figures/architecture-catalog.ts | 2 - .../workspace/authoring/authoring-graph.ts | 1 - .../rpc/src/generated/workflow-contract.ts | 12 ------ 42 files changed, 88 insertions(+), 309 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index af84fa11..0abd7d5d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -895,8 +895,9 @@ _Avoid_: Job, invocation - Missing reducers mean replace only within a single serial lineage. At a **Barrier**, multiple writes to the same path without a reducer are conflicts, not last-writer-wins replacements. -- Current `JoinNode` should not silently become a **Barrier**. It may be - repurposed later only through an explicit design pass. +- The pass-through `JoinNode` was removed. Future **Gather** behavior starts + from an explicit barrier contract rather than inheriting placeholder + semantics. - The first **Scheduler Foundation** implementation should create ready/block/wake seams only. **Lineage Isolation** and **Barrier** merge behavior are future concurrent semantics, not first-pass behavior. diff --git a/contracts/workflow-api.manifest.json b/contracts/workflow-api.manifest.json index 152450cb..7f808f6d 100644 --- a/contracts/workflow-api.manifest.json +++ b/contracts/workflow-api.manifest.json @@ -1312,17 +1312,6 @@ ], "type": "object" }, - "DraftJoinStep": { - "additionalProperties": false, - "description": "Draft step that emits the current core join node.", - "properties": { - "join": { - "additionalProperties": true, - "type": "object" - } - }, - "type": "object" - }, "DraftMatchCase": { "additionalProperties": false, "description": "One ordered equality case in a draft match decision.", @@ -6209,9 +6198,6 @@ { "$ref": "#/components/schemas/DraftInterruptStep" }, - { - "$ref": "#/components/schemas/DraftJoinStep" - }, { "$ref": "#/components/schemas/DraftEndStep" }, diff --git a/docs/adr/0006-explicit-fork-and-topology-driven-gather.md b/docs/adr/0006-explicit-fork-and-topology-driven-gather.md index 370a55cf..661da577 100644 --- a/docs/adr/0006-explicit-fork-and-topology-driven-gather.md +++ b/docs/adr/0006-explicit-fork-and-topology-driven-gather.md @@ -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 diff --git a/docs/current_roadmap.md b/docs/current_roadmap.md index 63290863..22f29c95 100644 --- a/docs/current_roadmap.md +++ b/docs/current_roadmap.md @@ -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 diff --git a/docs/superpowers/specs/2026-05-18-workflow-draft-surface-design.md b/docs/superpowers/specs/2026-05-18-workflow-draft-surface-design.md index b61bedfc..5a1623a3 100644 --- a/docs/superpowers/specs/2026-05-18-workflow-draft-surface-design.md +++ b/docs/superpowers/specs/2026-05-18-workflow-draft-surface-design.md @@ -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 diff --git a/docs/superpowers/specs/2026-06-22-wf-schema-command-design.md b/docs/superpowers/specs/2026-06-22-wf-schema-command-design.md index b3eb5d59..6861c601 100644 --- a/docs/superpowers/specs/2026-06-22-wf-schema-command-design.md +++ b/docs/superpowers/specs/2026-06-22-wf-schema-command-design.md @@ -157,7 +157,6 @@ Example: "SubgraphNode", "ConditionNode", "ForeachNode", - "JoinNode", "EndNode", "InterruptNode" ] diff --git a/docs/superpowers/specs/2026-06-27-draft-semantic-authoring-boundary.md b/docs/superpowers/specs/2026-06-27-draft-semantic-authoring-boundary.md index 9dd750d8..df792de4 100644 --- a/docs/superpowers/specs/2026-06-27-draft-semantic-authoring-boundary.md +++ b/docs/superpowers/specs/2026-06-27-draft-semantic-authoring-boundary.md @@ -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. diff --git a/docs/superpowers/specs/2026-07-02-workflow-console-lifecycle-explorer.md b/docs/superpowers/specs/2026-07-02-workflow-console-lifecycle-explorer.md index 02a855af..703f6a5b 100644 --- a/docs/superpowers/specs/2026-07-02-workflow-console-lifecycle-explorer.md +++ b/docs/superpowers/specs/2026-07-02-workflow-console-lifecycle-explorer.md @@ -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. - diff --git a/docs/superpowers/specs/2026-07-20-generic-draft-add-step-design.md b/docs/superpowers/specs/2026-07-20-generic-draft-add-step-design.md index d6d8c6b6..43b461aa 100644 --- a/docs/superpowers/specs/2026-07-20-generic-draft-add-step-design.md +++ b/docs/superpowers/specs/2026-07-20-generic-draft-add-step-design.md @@ -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. diff --git a/docs/superpowers/specs/2026-08-09-workflow-console-selected-step-dataflow-design.md b/docs/superpowers/specs/2026-08-09-workflow-console-selected-step-dataflow-design.md index 1c373214..ec0227fa 100644 --- a/docs/superpowers/specs/2026-08-09-workflow-console-selected-step-dataflow-design.md +++ b/docs/superpowers/specs/2026-08-09-workflow-console-selected-step-dataflow-design.md @@ -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. diff --git a/docs/superpowers/specs/2026-08-14-workflow-console-contract-graph-design.md b/docs/superpowers/specs/2026-08-14-workflow-console-contract-graph-design.md index 7f15e97e..79da1f1d 100644 --- a/docs/superpowers/specs/2026-08-14-workflow-console-contract-graph-design.md +++ b/docs/superpowers/specs/2026-08-14-workflow-console-contract-graph-design.md @@ -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 diff --git a/docs/superpowers/specs/2026-09-04-run-step-budget-design.md b/docs/superpowers/specs/2026-09-04-run-step-budget-design.md index 749495e4..edeb056d 100644 --- a/docs/superpowers/specs/2026-09-04-run-step-budget-design.md +++ b/docs/superpowers/specs/2026-09-04-run-step-budget-design.md @@ -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. diff --git a/docs/wf_cli.md b/docs/wf_cli.md index 1c1b9d4d..a4f152e9 100644 --- a/docs/wf_cli.md +++ b/docs/wf_cli.md @@ -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 ``` diff --git a/docs/workflow_drafts.md b/docs/workflow_drafts.md index 7ee93220..0954239d 100644 --- a/docs/workflow_drafts.md +++ b/docs/workflow_drafts.md @@ -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 ``` diff --git a/skills/wf-workflow/references/draft-workspaces.md b/skills/wf-workflow/references/draft-workspaces.md index 3516914b..dbe6ec6c 100644 --- a/skills/wf-workflow/references/draft-workspaces.md +++ b/skills/wf-workflow/references/draft-workspaces.md @@ -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 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 and subgraphs preserve JSON Schema boundary contracts. Invalid intermediate drafts remain saveable in the workspace but must pass `wf draft validate` diff --git a/src/wf_api/draft_authoring.py b/src/wf_api/draft_authoring.py index d16a38b3..eaedba9a 100644 --- a/src/wf_api/draft_authoring.py +++ b/src/wf_api/draft_authoring.py @@ -16,7 +16,6 @@ from wf_artifacts.drafts.models import ( DraftEndStep, DraftForeachStep, DraftInterruptStep, - DraftJoinStep, DraftMatchStep, DraftStep, DraftSubgraphStep, @@ -353,8 +352,6 @@ class WorkflowDraftAuthoringApi: return outcomes if isinstance(step, DraftInterruptStep): return set(step.interrupt.outcomes) - if isinstance(step, DraftJoinStep): - return {"done"} if isinstance(step, DraftSubgraphStep): return set(step.subgraph.outcomes) if isinstance( diff --git a/src/wf_artifacts/drafts/__init__.py b/src/wf_artifacts/drafts/__init__.py index 9ba0a986..a514b46a 100644 --- a/src/wf_artifacts/drafts/__init__.py +++ b/src/wf_artifacts/drafts/__init__.py @@ -11,7 +11,6 @@ from .models import ( DraftEndStep, DraftForeachStep, DraftInterruptStep, - DraftJoinStep, DraftMatchCase, DraftMatchStep, DraftSubgraphStep, @@ -27,7 +26,6 @@ __all__ = [ "DraftEndStep", "DraftForeachStep", "DraftInterruptStep", - "DraftJoinStep", "DraftMatchCase", "DraftMatchStep", "DraftSubgraphStep", diff --git a/src/wf_artifacts/drafts/adapter.py b/src/wf_artifacts/drafts/adapter.py index e9a69671..b755ac06 100644 --- a/src/wf_artifacts/drafts/adapter.py +++ b/src/wf_artifacts/drafts/adapter.py @@ -4,7 +4,7 @@ from typing import Any from wf_authoring import WorkflowBuilder 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 .models import ( @@ -12,7 +12,6 @@ from .models import ( DraftEndStep, DraftForeachStep, DraftInterruptStep, - DraftJoinStep, DraftMatchStep, DraftStep, DraftSubgraphStep, @@ -84,10 +83,6 @@ def _add_step(builder: WorkflowBuilder, step_id: str, step: DraftStep): return builder.interrupt( **interrupt_kwargs, ) - if isinstance(step, DraftJoinStep): - node = JoinNode(id=step_id, type="join") - builder.nodes.append(node) - return node if isinstance(step, DraftEndStep): return builder.end(step.end.outcome, id=step_id) if isinstance(step, DraftWhenStep): diff --git a/src/wf_artifacts/drafts/models.py b/src/wf_artifacts/drafts/models.py index 0ec4d1e5..7182bdac 100644 --- a/src/wf_artifacts/drafts/models.py +++ b/src/wf_artifacts/drafts/models.py @@ -220,14 +220,6 @@ class DraftSubgraphStep(BaseModel): 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): """Payload for one explicit workflow terminal outcome.""" @@ -323,7 +315,6 @@ DraftStep = ( DraftUseStep | DraftForeachStep | DraftInterruptStep - | DraftJoinStep | DraftEndStep | DraftWhenStep | DraftChooseStep diff --git a/src/wf_authoring/builder/refs.py b/src/wf_authoring/builder/refs.py index d5203c0c..416af230 100644 --- a/src/wf_authoring/builder/refs.py +++ b/src/wf_authoring/builder/refs.py @@ -8,7 +8,6 @@ from wf_core import ( EndNode, ForeachNode, InterruptNode, - JoinNode, NodeUse, SubgraphNode, ) @@ -16,14 +15,7 @@ from wf_core import ( from ..nodes import NodeSpec StepRef: TypeAlias = ( - str - | NodeUse - | SubgraphNode - | ConditionNode - | ForeachNode - | InterruptNode - | JoinNode - | EndNode + str | NodeUse | SubgraphNode | ConditionNode | ForeachNode | InterruptNode | EndNode ) """A reference to a step, which can be either a string id or a node object that should be auto-used.""" diff --git a/src/wf_cli/commands/draft_add.py b/src/wf_cli/commands/draft_add.py index 10c523a6..ac615477 100644 --- a/src/wf_cli/commands/draft_add.py +++ b/src/wf_cli/commands/draft_add.py @@ -16,7 +16,6 @@ from wf_artifacts.drafts.models import ( DraftForeachStep, DraftInterruptPayload, DraftInterruptStep, - DraftJoinStep, DraftMatchCase, DraftMatchPayload, 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") def add_end_step( ctx: typer.Context, diff --git a/src/wf_cli/io.py b/src/wf_cli/io.py index 176a423d..39bb0e2a 100644 --- a/src/wf_cli/io.py +++ b/src/wf_cli/io.py @@ -56,7 +56,7 @@ def write_json_file(path: Path, payload: Any, *, force: bool) -> None: output.write("\n") except FileExistsError as exc: 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 except OSError as exc: raise CliInputError(f"could not write file {path!s}: {exc}") from exc diff --git a/src/wf_core/__init__.py b/src/wf_core/__init__.py index 2915214e..b8a681bc 100644 --- a/src/wf_core/__init__.py +++ b/src/wf_core/__init__.py @@ -11,7 +11,6 @@ from .models import ( InputPathBinding, InputValueBinding, InterruptNode, - JoinNode, JsonValue, LiteralExpression, NodeDef, @@ -89,7 +88,6 @@ __all__ = [ "InputValueBinding", "InterruptRequest", "InterruptRoute", - "JoinNode", "JsonValue", "LiteralExpression", "NodeDef", diff --git a/src/wf_core/models/__init__.py b/src/wf_core/models/__init__.py index 24f9d5f0..2839bb89 100644 --- a/src/wf_core/models/__init__.py +++ b/src/wf_core/models/__init__.py @@ -30,7 +30,6 @@ from wf_core.models.steps import ( ForeachItemErrorPolicy, ForeachNode, InterruptNode, - JoinNode, NodeUse, Step, SubgraphNode, @@ -54,7 +53,6 @@ __all__ = [ "InputExpressionBinding", "InputPathBinding", "InputValueBinding", - "JoinNode", "JsonValue", "LiteralExpression", "LiteralOperand", diff --git a/src/wf_core/models/steps.py b/src/wf_core/models/steps.py index 0f9b78f7..ea948cdd 100644 --- a/src/wf_core/models/steps.py +++ b/src/wf_core/models/steps.py @@ -293,13 +293,6 @@ class ForeachNode(BaseModel): 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): """Explicit workflow terminal that sets the workflow-level outcome. @@ -411,13 +404,7 @@ class InterruptNode(BaseModel): Step = Annotated[ - NodeUse - | SubgraphNode - | ConditionNode - | ForeachNode - | JoinNode - | EndNode - | InterruptNode, + NodeUse | SubgraphNode | ConditionNode | ForeachNode | EndNode | InterruptNode, Field(discriminator="type"), ] """Discriminated union of all executable workflow graph steps.""" diff --git a/src/wf_core/runtime/ops/handlers.py b/src/wf_core/runtime/ops/handlers.py index 31df0da9..ada236b4 100644 --- a/src/wf_core/runtime/ops/handlers.py +++ b/src/wf_core/runtime/ops/handlers.py @@ -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( run: RunState, step: InterruptNode, diff --git a/src/wf_core/runtime/step.py b/src/wf_core/runtime/step.py index 1da1491c..3bd505cf 100644 --- a/src/wf_core/runtime/step.py +++ b/src/wf_core/runtime/step.py @@ -10,7 +10,6 @@ from wf_core.models.steps import ( EndNode, ForeachNode, InterruptNode, - JoinNode, NodeUse, SubgraphNode, ) @@ -22,7 +21,6 @@ from wf_core.runtime.ops.foreach import step_foreach from wf_core.runtime.ops.handlers import ( handle_condition_step, handle_interrupt_step, - handle_join_step, ) from wf_core.runtime.ops.index import WorkflowIndex, build_workflow_index from wf_core.runtime.ops.merges import ReducerDefinition @@ -149,8 +147,6 @@ def step_workflow( raise elif isinstance(step, ConditionNode): step_result = handle_condition_step(run, step) - elif isinstance(step, JoinNode): - step_result = handle_join_step() elif isinstance(step, EndNode): return complete_end_step( run=run, @@ -264,8 +260,6 @@ async def step_workflow_async( raise elif isinstance(step, ConditionNode): step_result = handle_condition_step(run, step) - elif isinstance(step, JoinNode): - step_result = handle_join_step() elif isinstance(step, EndNode): return complete_end_step( run=run, diff --git a/src/wf_core/validation/outcomes.py b/src/wf_core/validation/outcomes.py index 75dfc699..1c4b8196 100644 --- a/src/wf_core/validation/outcomes.py +++ b/src/wf_core/validation/outcomes.py @@ -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"}: outcomes.add("completed_with_errors") return outcomes - if step.type == "join": - return {"done"} if isinstance(step, EndNode): return set() if isinstance(step, InterruptNode): diff --git a/tests/artifacts/test_draft_models.py b/tests/artifacts/test_draft_models.py index 22d2b5c5..59b9730e 100644 --- a/tests/artifacts/test_draft_models.py +++ b/tests/artifacts/test_draft_models.py @@ -226,7 +226,7 @@ def test_draft_step_requires_exactly_one_kind_key() -> None: assert isinstance(steps, dict) echo = steps["echo"] assert isinstance(echo, dict) - echo["join"] = {} + echo["end"] = {"outcome": "ok"} with pytest.raises(ValidationError) as exc_info: WorkflowDraft.model_validate(draft) diff --git a/tests/wf_api/test_drafts_service.py b/tests/wf_api/test_drafts_service.py index 5d6a0391..350941df 100644 --- a/tests/wf_api/test_drafts_service.py +++ b/tests/wf_api/test_drafts_service.py @@ -675,7 +675,7 @@ async def test_update_capability_step_rejects_wrong_kind_and_invalid_input_atomi register_echo=True, ) draft = _echo_draft() - draft["steps"]["joined"] = {"join": ["echo"]} + draft["steps"]["joined"] = {"end": {}} await draft_api.create_draft_workspace(workspace_id="echo", draft=draft) before = await draft_api.get_draft_workspace( 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( workspace_id="non-capability-output-step", 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( 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( workspace_id="stale-output-non-capability", 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( 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, ) draft = _structured_report_draft() - draft["steps"]["report"] = {"join": {}} + draft["steps"]["report"] = {"end": {}} await draft_api.create_draft_workspace(workspace_id="non_capability", draft=draft) api = WorkflowApi(authoring.context, drafts=True) before = await draft_api.get_draft_workspace( @@ -3956,7 +3956,6 @@ async def test_add_step_from_capability_rejects_existing_step_id( "interrupt", {"interrupt": {"kind": "review", "outcomes": ["submitted"]}}, ), - ("join", {"join": {}}), ("end", {"end": {"outcome": "ok"}}), ( "when", @@ -4067,7 +4066,7 @@ async def test_add_step_stale_revision_wins_over_content_preflight( before = await draft_api.get_draft_workspace( workspace_id="draft_ws", include_draft=True ) - step = TypeAdapter(DraftStep).validate_python({"join": {}}) + step = TypeAdapter(DraftStep).validate_python({"end": {}}) result = await api.add_step( workspace_id="draft_ws", diff --git a/tests/wf_cli/test_app.py b/tests/wf_cli/test_app.py index 84f00bf8..16eb70df 100644 --- a/tests/wf_cli/test_app.py +++ b/tests/wf_cli/test_app.py @@ -824,7 +824,6 @@ def test_wf_draft_add_help_lists_typed_step_commands_and_removes_flat_command() "capability", "interrupt", "foreach", - "join", "end", "when", "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 -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]] = [] 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 ) - 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( app, [ @@ -1571,7 +1551,7 @@ def test_wf_draft_add_join_and_end_build_concrete_steps(monkeypatch) -> None: "end", "workspace", "--revision", - "2", + "1", "--step", "finish", "--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 calls[0]["step"].model_dump(mode="json") == {"join": {}} - assert calls[0]["routes"] == {"done": "finish"} - assert calls[1]["step"].model_dump(mode="json") == {"end": {"outcome": "completed"}} - assert calls[1]["routes"] is None + assert calls[0]["step"].model_dump(mode="json") == {"end": {"outcome": "completed"}} + assert calls[0]["routes"] is None 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: interrupt = runner.invoke(app, ["draft", "add", "interrupt", "--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"]) - assert ( - interrupt.exit_code == foreach.exit_code == join.exit_code == end.exit_code == 0 - ) + assert interrupt.exit_code == foreach.exit_code == end.exit_code == 0 assert "--request-schema-file" in interrupt.output assert "--resume-schema-file" 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 "--collect-to" 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 "--route" not in end.output diff --git a/tests/wf_cli/test_remote_target.py b/tests/wf_cli/test_remote_target.py index 4233b54a..256f4100 100644 --- a/tests/wf_cli/test_remote_target.py +++ b/tests/wf_cli/test_remote_target.py @@ -771,14 +771,14 @@ def test_wf_remote_capability_free_draft_lifecycle(monkeypatch, tmp_path) -> Non [ "draft", "add", - "join", + "end", "control_ws", "--revision", "1", "--step", - "gate", - "--route", - "done=finish", + "finish", + "--outcome", + "error", ], [ "draft", @@ -787,26 +787,14 @@ def test_wf_remote_capability_free_draft_lifecycle(monkeypatch, tmp_path) -> Non "--revision", "2", "--step", - "gate", - ], - [ - "draft", - "add", - "end", - "control_ws", - "--revision", - "3", - "--step", "finish", - "--outcome", - "error", ], [ "draft", "set-contract", "control_ws", "--revision", - "4", + "3", "--outcome", "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 inspected.exit_code == 0, inspected.output payload = json.loads(inspected.output) - assert payload["revision"] == 5 - assert payload["draft"]["start"] == "gate" + assert payload["revision"] == 4 + assert payload["draft"]["start"] == "finish" 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( @@ -2409,7 +2397,6 @@ def test_wf_draft_add_control_steps_use_generic_rpc_target( "completed_with_errors": "__end__", }, ), - ("join", ["--route", "done=__end__"], {}, {"done": "__end__"}), ("end", ["--outcome", "ok"], {"outcome": "ok"}, None), ( "when", diff --git a/tests/wf_cli/test_schema.py b/tests/wf_cli/test_schema.py index e7499709..2f6de4cd 100644 --- a/tests/wf_cli/test_schema.py +++ b/tests/wf_cli/test_schema.py @@ -67,7 +67,6 @@ def test_schema_compact_alias_is_json_outline_without_refs() -> None: "SubgraphNode", "ConditionNode", "ForeachNode", - "JoinNode", "EndNode", "InterruptNode", ] diff --git a/tests/wf_contract_manifest/test_generate.py b/tests/wf_contract_manifest/test_generate.py index d8177cfd..deadfd28 100644 --- a/tests/wf_contract_manifest/test_generate.py +++ b/tests/wf_contract_manifest/test_generate.py @@ -92,7 +92,7 @@ def test_generates_the_complete_real_workflow_contract() -> None: assert len(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 all( set(operation["result"]["schema"]) == {"$ref"} diff --git a/tests/wf_transport_rpc_http/test_app.py b/tests/wf_transport_rpc_http/test_app.py index f250a06a..90e4f422 100644 --- a/tests/wf_transport_rpc_http/test_app.py +++ b/tests/wf_transport_rpc_http/test_app.py @@ -2090,7 +2090,7 @@ def test_add_draft_step_params_reject_invalid_kind_and_route_source() -> None: "workspace_id": "ws", "revision": 1, "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", "revision": 1, "step_id": "new", - "step": {"join": {}}, + "step": {"end": {}}, "incoming": incoming, } ) diff --git a/tests/wf_transport_rpc_http/test_client.py b/tests/wf_transport_rpc_http/test_client.py index 44d34f62..9766dc2a 100644 --- a/tests/wf_transport_rpc_http/test_client.py +++ b/tests/wf_transport_rpc_http/test_client.py @@ -13,7 +13,6 @@ from wf_api.surface import RouteSource, WorkflowDraftSurface from wf_artifacts.drafts.models import ( DraftEndPayload, DraftEndStep, - DraftJoinStep, DraftStep, ) 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", 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( workspace_id="control_first", - revision=started["revision"], + revision=created["revision"], step_id="finish", step=DraftEndStep(end=DraftEndPayload(outcome="error")), ) - contracted = await client.set_draft_contract( + started = await client.set_draft_start( workspace_id="control_first", revision=ended["revision"], + step_id="finish", + ) + contracted = await client.set_draft_contract( + workspace_id="control_first", + revision=started["revision"], outcomes=("error",), ) stale = await client.set_draft_start( workspace_id="control_first", - revision=ended["revision"], + revision=started["revision"], step_id="finish", ) 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 joined["revision"] == 2 + assert ended["revision"] == 2 assert started["revision"] == 3 - assert ended["revision"] == 4 - assert contracted["revision"] == 5 + assert contracted["revision"] == 4 assert stale["status"] == "conflict" assert stale["diagnostics"][0]["code"] == "revision_conflict" assert validated["status"] == "valid" assert "compiled_plan" in compiled - assert compiled["compiled_plan"]["start"] == "gate" + assert compiled["compiled_plan"]["start"] == "finish" draft = inspected.get("draft") assert draft is not None - assert draft["start"] == "gate" - assert draft["steps"] == { - "gate": {"join": {}}, - "finish": {"end": {"outcome": "error"}}, - } + assert draft["start"] == "finish" + assert draft["steps"] == {"finish": {"end": {"outcome": "error"}}} 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"], }, ), - ("join", TypeAdapter(DraftStep).validate_python({"join": {}}), {}), ("end", TypeAdapter(DraftStep).validate_python({"end": {}}), {"outcome": "ok"}), ( "when", diff --git a/web/apps/console/src/graph/WorkflowGraph.tsx b/web/apps/console/src/graph/WorkflowGraph.tsx index 6e2013d1..b051f103 100644 --- a/web/apps/console/src/graph/WorkflowGraph.tsx +++ b/web/apps/console/src/graph/WorkflowGraph.tsx @@ -31,8 +31,6 @@ const nodeColor = (data: WorkflowGraphNodeData): string => { return "#ef4444"; case "foreach": return "#8b5cf6"; - case "join": - return "#10b981"; case "end": return "#6b7280"; default: diff --git a/web/apps/console/src/graph/graph-model.ts b/web/apps/console/src/graph/graph-model.ts index 15fa33db..c9576670 100644 --- a/web/apps/console/src/graph/graph-model.ts +++ b/web/apps/console/src/graph/graph-model.ts @@ -7,7 +7,6 @@ export type WorkflowGraphNodeKind = | "condition" | "interrupt" | "foreach" - | "join" | "end" | "unsupported"; @@ -94,8 +93,6 @@ const mapNodeKind = (type: unknown): WorkflowGraphNodeKind => { return "interrupt"; case "foreach": return "foreach"; - case "join": - return "join"; case "end": return "end"; default: @@ -119,7 +116,6 @@ const buildLabel = ( return typeof node.kind === "string" ? node.kind : "Interrupt"; } if (type === "foreach") return "For Each"; - if (type === "join") return "Join"; if (type === "subgraph") { const workflowRef = typeof node.workflow === "string" ? node.workflow : undefined; if (workflowRef) { diff --git a/web/apps/console/src/presentation/figures/architecture-catalog.test.ts b/web/apps/console/src/presentation/figures/architecture-catalog.test.ts index d0b06ebd..4cf9af08 100644 --- a/web/apps/console/src/presentation/figures/architecture-catalog.test.ts +++ b/web/apps/console/src/presentation/figures/architecture-catalog.test.ts @@ -46,7 +46,6 @@ describe("architectureCatalog", () => { "NodeUse", "Condition", "Foreach", - "Join", "Subgraph", "Interrupt", "End", diff --git a/web/apps/console/src/presentation/figures/architecture-catalog.ts b/web/apps/console/src/presentation/figures/architecture-catalog.ts index 94d8a538..b628238b 100644 --- a/web/apps/console/src/presentation/figures/architecture-catalog.ts +++ b/web/apps/console/src/presentation/figures/architecture-catalog.ts @@ -524,7 +524,6 @@ export const architectureCatalog: FigureCatalogDefinition = defineFigureCatalog( "node-use": { x: 0, y: 0 }, "condition": { x: 300, y: 0 }, "foreach": { x: 600, y: 0 }, - "join": { x: 900, y: 0 }, "subgraph": { x: 150, y: 210 }, "interrupt": { x: 450, 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: "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: "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" }, diff --git a/web/apps/console/src/workspace/authoring/authoring-graph.ts b/web/apps/console/src/workspace/authoring/authoring-graph.ts index b76d0f2d..2be7a7a9 100644 --- a/web/apps/console/src/workspace/authoring/authoring-graph.ts +++ b/web/apps/console/src/workspace/authoring/authoring-graph.ts @@ -57,7 +57,6 @@ const stepKind = (step: JsonRecord): string => { "choose", "match", "foreach", - "join", "end", ]) { if (kind in step) return kind; diff --git a/web/packages/rpc/src/generated/workflow-contract.ts b/web/packages/rpc/src/generated/workflow-contract.ts index 058be46d..df9596b9 100644 --- a/web/packages/rpc/src/generated/workflow-contract.ts +++ b/web/packages/rpc/src/generated/workflow-contract.ts @@ -464,7 +464,6 @@ export interface WorkflowContractMap { | DraftUseStep | DraftForeachStep | DraftInterruptStep - | DraftJoinStep | DraftEndStep | DraftWhenStep | DraftChooseStep @@ -1867,17 +1866,6 @@ export interface SchemaRef { type?: string | string[] | null; [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`. *