Skip to content

Commit 52ff050

Browse files
jmanhypeclaude
andcommitted
Implement OTP supervision tree and public API for wf_substrate
This commit implements the complete OTP supervision tree and public API for wf_substrate as specified in item 012. The implementation provides a production-grade process architecture for workflow case management with proper fault containment and supervision. Key changes: **New Modules:** - wf_case_runner: Per-case gen_statem managing execution lifecycle with explicit states (initializing, running, waiting_effect, waiting_signal, cancelled, done). Executes bytecode in quanta, handles signals, and supports state timeout for case timeout. - wf_case_sup: Dynamic supervisor using simple_one_for_one strategy to manage per-case runner processes. Cases are temporary (not restarted on normal termination) to avoid replaying execution state. - Updated wf_substrate_sup: Added wf_case_sup as permanent child in the supervision tree alongside existing singletons (wf_governance, wf_budget, wf_approval). - Updated wf_substrate: Implemented complete public API including: * new_case/3: Create workflow case (accepts bytecode directly until item 004) * signal/2: Send external signal to running case * cancel/1, cancel_region/2: Cancel case or specific region * await/2: Block until case completion with monitor/timeout * status/1: Query current case status * trace/3: Configure trace level and sink * validate/2: Stub returning ok (item 013 will implement) **Testing:** - wf_case_runner_tests: State machine transition tests - wf_supervision_tree_tests: Supervisor behavior, dynamic children, restart strategies, name conflicts - wf_substrate_api_tests: Complete API coverage including error cases - wf_exec_tests.hrl: Shared mock bytecode generators **Examples:** - examples/basic_usage.erl: Demonstrates sequential and parallel workflows with tracing and signal delivery **Configuration:** - Updated wf_substrate.app.src: registered includes wf_case_sup, modules list includes all new modules The supervision tree structure: wf_substrate_sup (one_for_one) ├── wf_case_sup (simple_one_for_one) │ └── wf_case_runner (temporary, per case) ├── wf_governance (permanent) ├── wf_budget (permanent) └── wf_approval (permanent) All modules compile without warnings and follow existing code patterns. Process registration uses Erlang registry (no external dependencies). TODO comments added for item 004 (wf_compile) and item 013 (wf_validate). Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
1 parent 0f0951e commit 52ff050

11 files changed

Lines changed: 1051 additions & 27 deletions

.wreckit/items/012-otp-supervision-tree/item.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
"schema_version": 1,
33
"id": "012-otp-supervision-tree",
44
"title": "Implement OTP supervision tree and public API",
5-
"state": "researched",
6-
"overview": "Implement the OTP supervision tree and public API for wf_substrate.\n\nSupervision hierarchy:\n- wf_substrate_sup: top-level supervisor (one_for_one), starts wf_case_sup and optional singletons.\n- wf_case_sup: dynamic supervisor (simple_one_for_one) for per-case runner processes.\n- wf_effect_sup (optional): dynamic supervisor for effect handler processes if effects are executed in-process.\n- wf_trace_sink: optional gen_server that collects trace events from all cases (for centralized logging/monitoring).\n\nPer-case runner as gen_statem with states: {initializing, running, waiting_effect, waiting_signal, cancelled, done}. The runner holds the exec_state, steps in configurable bursts (N reductions then yield to scheduler), handles incoming signals (external events that feed into XOR choices or task completions), effect responses, and cancel requests. Uses state_timeout for overall case timeout.\n\nPublic API module wf_substrate.erl exports:\n- new_case/3 :: (CaseId, wf_term(), Ctx) -> {ok, Pid} \u2014 compile term, create runner.\n- signal/2 :: (CaseId, Signal) -> ok \u2014 send external signal to running case.\n- cancel/1 :: (CaseId) -> ok \u2014 cancel entire case.\n- cancel_region/2 :: (CaseId, ScopeId) -> ok \u2014 cancel a region within a case.\n- await/2 :: (CaseId, Timeout) -> {ok, Result} | {error, Reason} \u2014 block until case completes.\n- status/1 :: (CaseId) -> Status \u2014 get current case status.\n- trace/3 :: (CaseId, Level, Sink) -> ok \u2014 configure tracing for a case.\n- validate/2 :: (wf_term(), Options) -> ok | {error, Issues} \u2014 static validation without execution.\n\nInclude wf_substrate_app.erl (application behaviour) that starts wf_substrate_sup.",
5+
"state": "implementing",
6+
"overview": "Implement the OTP supervision tree and public API for wf_substrate.\n\nSupervision hierarchy:\n- wf_substrate_sup: top-level supervisor (one_for_one), starts wf_case_sup and optional singletons.\n- wf_case_sup: dynamic supervisor (simple_one_for_one) for per-case runner processes.\n- wf_effect_sup (optional): dynamic supervisor for effect handler processes if effects are executed in-process.\n- wf_trace_sink: optional gen_server that collects trace events from all cases (for centralized logging/monitoring).\n\nPer-case runner as gen_statem with states: {initializing, running, waiting_effect, waiting_signal, cancelled, done}. The runner holds the exec_state, steps in configurable bursts (N reductions then yield to scheduler), handles incoming signals (external events that feed into XOR choices or task completions), effect responses, and cancel requests. Uses state_timeout for overall case timeout.\n\nPublic API module wf_substrate.erl exports:\n- new_case/3 :: (CaseId, wf_term(), Ctx) -> {ok, Pid} — compile term, create runner.\n- signal/2 :: (CaseId, Signal) -> ok — send external signal to running case.\n- cancel/1 :: (CaseId) -> ok — cancel entire case.\n- cancel_region/2 :: (CaseId, ScopeId) -> ok — cancel a region within a case.\n- await/2 :: (CaseId, Timeout) -> {ok, Result} | {error, Reason} — block until case completes.\n- status/1 :: (CaseId) -> Status — get current case status.\n- trace/3 :: (CaseId, Level, Sink) -> ok — configure tracing for a case.\n- validate/2 :: (wf_term(), Options) -> ok | {error, Issues} — static validation without execution.\n\nInclude wf_substrate_app.erl (application behaviour) that starts wf_substrate_sup.",
77
"branch": null,
88
"pr_url": null,
99
"pr_number": null,
1010
"last_error": null,
1111
"created_at": "2026-02-10T00:00:00.000Z",
12-
"updated_at": "2026-02-10T00:00:00.000Z"
13-
}
12+
"updated_at": "2026-02-11T18:49:05.771Z"
13+
}

examples/basic_usage.erl

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
%%%-------------------------------------------------------------------
2+
%%% @doc Basic usage examples for wf_substrate
3+
%%% @end
4+
%%%-------------------------------------------------------------------
5+
-module(basic_usage).
6+
-export([run_simple_workflow/0, run_parallel_workflow/0]).
7+
8+
%% @doc Run a simple sequential workflow
9+
run_simple_workflow() ->
10+
%% Define bytecode for sequence: Task A -> Task B -> Done
11+
Bytecode = [
12+
{'SEQ_ENTER', 0},
13+
{'TASK_EXEC', task_a},
14+
{'SEQ_NEXT', 3},
15+
{'TASK_EXEC', task_b},
16+
{'DONE'}
17+
],
18+
19+
%% Start application
20+
{ok, _Apps} = application:ensure_all_started(wf_substrate),
21+
22+
%% Create new case
23+
{ok, Pid} = wf_substrate:new_case(simple_seq, Bytecode, #{}),
24+
25+
%% Await completion
26+
Result = wf_substrate:await(simple_seq, 5000),
27+
28+
%% Clean up
29+
application:stop(wf_substrate),
30+
31+
Result.
32+
33+
%% @doc Run a parallel workflow
34+
run_parallel_workflow() ->
35+
%% Define bytecode for parallel: Task A || Task B
36+
Bytecode = [
37+
{'PAR_FORK', [1, 3]},
38+
{'TASK_EXEC', task_a},
39+
{'DONE'},
40+
{'TASK_EXEC', task_b},
41+
{'DONE'},
42+
{'JOIN_WAIT', all}
43+
],
44+
45+
%% Start application
46+
{ok, _Apps} = application:ensure_all_started(wf_substrate),
47+
48+
%% Create new case with tracing enabled
49+
{ok, Pid} = wf_substrate:new_case(parallel_flow, Bytecode, #{}),
50+
51+
%% Configure tracing
52+
ok = wf_substrate:trace(parallel_flow, min, {ets, wf_trace_events}),
53+
54+
%% Send signal during execution
55+
ok = wf_substrate:signal(parallel_flow, {user_event, data}),
56+
57+
%% Check status
58+
{ok, StatusInfo} = wf_substrate:status(parallel_flow),
59+
60+
%% Await completion
61+
Result = wf_substrate:await(parallel_flow, 10000),
62+
63+
%% Clean up
64+
application:stop(wf_substrate),
65+
66+
Result.

0 commit comments

Comments
 (0)