The post-append fan-out that fires durable flows from arriving
activities (fed-sx-triggers-loop.md Phases 2+3), native into next/flow
— no cross-guest FFI.
- pipeline.erl: apply_triggers/3 runs AFTER the kernel append (rejected
activities never reach it). It looks the activity's type up in the
trigger registry, drops specs whose guard/actor-scope fails or whose
{activity_cid, trigger_cid} pair already fired (federation can deliver
the same activity twice — dedup is keyed on that pair, read from the
actor's :triggers_fired), and dispatches the rest. Returns the audit
triples for the kernel to fold into :triggers_fired + its projection.
Must not be called inside a `try` (it does gen_server:calls, which
deadlock the scheduler inside a try); running post-append in its own
step satisfies that.
- flow_dispatch.erl: bridges a matched trigger to flow_store:start, with
the activity bound into the flow's input env. guard_passes/3 gates on
actor-scope + guard. Failures (unknown flow, crashing first step) come
back as {error, _}, never raised — one flow can't take down the rest.
- flow_store.erl: drive wrapped in try (the drive is pure, so the try is
safe) so a flow whose step raises yields {error, {flow_crashed, _}}
instead of crashing the store.
Tests: flow_dispatch.sh (12), pipeline_triggers.sh (10). lib/erlang
771/771, next/flow 34/34.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A native Erlang-on-SX durable workflow engine, so the fed-sx kernel can
fan activities out into business flows in its own runtime — no cross-
guest FFI to the Scheme lib/flow, no marshalling, no Scheme dependency.
The seed of a real engine (chosen over bridging Scheme flow) that can
later supersede it for substrate use.
- flow.erl — the deterministic-replay driver. Same durability model as
the Scheme engine (re-run from the top; effects go through suspend;
the replay log is plain [{Tag,Value}] data, restart-ready), but
adapted to three hard runtime constraints: no re-enterable
continuation, no process dictionary, and a blocking receive inside a
`try` deadlocks the cooperative scheduler. Resolution: thread the log
through a railway-style context and make suspend SHORT-CIRCUIT (like a
fail value) instead of throwing — purely functional, sidesteps all
three. Ctx = {flow_cont,V,Log} | {flow_susp,Tag,Log}.
- flow_spec.erl — combinator algebra mirrored from lib/flow/spec.sx:
leaves, sequence/parallel/map_flow, flow_while/flow_until, branch,
railway fail/recover/attempt, tap, try_catch/retry.
- flow_store.erl — durable gen_server: named-flow registry + instance
table + start/resume/status. Drives the pure flow from handle_call,
so no gen_server:call is ever inside the replay try-path.
Gate: next/flow/conformance.sh — 34/34. lib/erlang untouched (771/771).
See next/flow/README.md for the model + why railway threading.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>