fed-sx-types Phase 8: blog-publish-digest e2e + flow:wait
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 58s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 58s
The motivating end-to-end demonstration (fed-sx-triggers-loop.md Phase 4): one trigger arriving in the pipeline drives a multi-step business flow with a branch, a timer suspension, an injected effect, and a follow-up activity emit — all in the kernel's own runtime. - flow.erl: flow:wait/1 — a timer-style suspend that PRESERVES the value on resume (vs flow:suspend/1, which returns the logged result), so a "wait until morning" step lets the env flow through to later steps. - next/flow/flows/blog_publish_digest.erl: the flow. Branches on the article :category (newsletter -> wait-until-morning -> send + emit; urgent -> send + emit now; else -> skip), fetches followers (injected), builds a digest email per follower, and emits a DigestSent activity OBJECT. Effect-as-data: a flow can't call kernel gen_servers from inside the drive (a blocking call there deadlocks the scheduler), so it returns the emails + DigestSent object for a driver to dispatch and append — which can then trigger downstream flows, closing the loop. Test: triggers_e2e.sh (10) — urgent completes in one cycle with 3 emails + a DigestSent object; newsletter suspends on the morning timer, then resumes to the same on "advancing the clock"; draft takes the else branch (no emails); a non-Article note is rejected by the guard; a duplicate activity fires once. flow:wait covered in next/flow (36/36). plans/fed-sx-design.md §13.10 documents the trigger fan-out as a kernel convention. lib/erlang 771/771. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1296,6 +1296,32 @@ inbox + pull from outbox. SSE is convenience, not protocol.
|
||||
unknown verbs are stored-but-not-projected — safe by default, with explicit
|
||||
operator control over what extensions load.
|
||||
|
||||
### 13.10 Activity-driven flow triggers (kernel convention)
|
||||
|
||||
Beyond projections (which fold an activity into read-model state), the kernel
|
||||
supports firing **durable business flows** off arriving activities — the
|
||||
"something happened → here is what we DO about it" half of the model. The
|
||||
convention (substrate landed in `loops/fed-sx-types`, Phases 5–8):
|
||||
|
||||
- A `DefineTrigger{activity-type, flow-name, guard?, actor-scope?}` activity binds
|
||||
an activity-type to a named flow. `trigger_registry` hydrates from a fold over
|
||||
these (restart-safe, same content-addressing as `define-registry`).
|
||||
- Fan-out runs **after** the kernel append, as the last pipeline step (§14):
|
||||
`envelope → signature → activity-type schema → object schema → append → trigger
|
||||
fan-out`. Only accepted activities fire flows; rejected ones never trigger.
|
||||
- Fan-out is deduped per `{activity-cid, trigger-cid}` (federation can deliver the
|
||||
same activity twice via different peers) using the actor's `:triggers_fired`
|
||||
field, and is failure-isolated: one flow's failure never blocks the append or
|
||||
the other flows.
|
||||
- Flows run on **flow-on-erlang** (`next/flow/`), a native Erlang-on-SX durable
|
||||
workflow engine (deterministic-replay suspend/resume; combinator algebra
|
||||
mirrored from the Scheme `lib/flow`). It runs in the kernel's own runtime, so
|
||||
the fan-out is a direct call — no cross-guest bridge. Because a flow runs inside
|
||||
the engine's drive (where a blocking kernel call would deadlock the cooperative
|
||||
scheduler), flows are **pure and describe effects as data** (their output, or a
|
||||
`suspend`); a driver outside the flow performs IO and appends any follow-up
|
||||
activity — which can in turn trigger further flows.
|
||||
|
||||
## 14. Validation pipeline
|
||||
|
||||
Every activity entering the substrate (whether published locally or received from a
|
||||
|
||||
Reference in New Issue
Block a user