Files
rose-ash/plans/business-logic-fed-flows.md
giles 3675d059b5 host P0.1: publish-activity contract for federated composition-flows
Business logic as federated composition-flows (plans/business-logic-fed-flows.md). P0.1: the
host describes a published post as a fed-sx activity — host/blog--publish-activity(slug) →
{:type "create" :actor "site" :id <CID> :object {:type "article" :slug :category}} — the
exact shape next/'s trigger machinery consumes (verified: next/tests/triggers_e2e.sh 10/10).
category (drives the flow branch: newsletter suspends / urgent fires / else skip) comes from
the "category" field-value, else the first tag, else "urgent". + host/blog--post-category.

Design decided: activity log = every CID delta (event source); triggers = declared subscriptions
(DefineTrigger); flows hybrid (SX composition for simple via the execute-fold, named Erlang flows
for complex); federated execution = Erlang (next/); the type carries content+relations+behavior.

blog 200/200 (+3: contract, category fallback, missing-post nil).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:55:08 +00:00

4.4 KiB

Business logic as federated composition-flows

Vision: business logic is part of an object's composition — declared on its type, alongside content grammar + allowed relations. State changes federate over fed-sx; federated services execute the business logic as durable flows.

Design (decided 2026-07-02):

  • Activity log = every CID delta — the federated event source of truth. Any content change (new CID, per host/blog--cid-of: content in the record → CID; relations are external → not).
  • Triggers = declared subscriptions — a type declares named triggers (on-publish, on-relate, …); flows fire only on matching ones. (fed-sx DefineTrigger: activity-type → flow-name + guard
    • actor-scope.) Log complete, execution precise.
  • Flows = hybrid — simple logic authored as SX composition (the execute-fold: effect/alt/ each — already live in /workflow-demo); an escape hatch to named Erlang flows (next/flow/*.erl, effect-as-data, deterministic replay, suspend/resume) for the complex.
  • Federated execution = Erlang (next/ fed-sx Milestone-1 kernel: trigger_registry + flow_dispatch + pipeline post-append fan-out). Authoring stays SX; the fed-sx activity is the bridge. flow-on-sx (Scheme, lib/flow) remains for purely-local durable logic.
  • The type carries its whole contract: fields+grammar (content) · allowed relations (external) · triggers+flows (behavior). All composition, all editable in the type-def editor.

Verified baseline: next/tests/triggers_e2e.sh = 10/10 — publish activity → trigger → blog_publish_digest flow (urgent/newsletter-suspend/draft-skip/guard-reject/dedup). This is the reference P0 wires the live host onto.

P0 — publish workflow, end-to-end (spike)

Prove: live host publishes a post → fed-sx activity → on-publish trigger → blog_publish_digest.

  • P0.1 — the publish-activity contract (SX side). host/blog--publish-activity(slug): a post record → the fed-sx activity {:type "create" :actor "site" :id :object {:type "article" :category … :slug …}}. category from field-value "category", else first tag, else "urgent". + host/blog--post-category. blog 200/200 (3 tests). DONE 2026-07-02.
  • P0.2 — the dispatch bridge. On publish (host/blog-edit-submit status→published, or a dedicated publish action), emit the activity into the trigger machinery. RUNTIME CHOICE for P0: in-process — serve.sh loads next/ kernel + registers the on-publish trigger; host emits via the erlang-on-sx bridge (erlang-eval-ast pipeline:apply_triggers). (P3 replaces this with real fed-sx delivery over next/kernel/http_server.erl.)
  • P0.3 — the effect is visible. The flow's digest_sent effect surfaces (a durable record / a DigestSent activity back in the log / shown on the post or a /flows page). Close the local loop.

P1 — types declare behavior (generalize)

  • A Composition field / the type carries a :triggers list (on-publish → flow-name + guard) — edited in the type-def editor, like grammar + relations.
  • A fold turns a type's declared behavior into DefineTrigger + flow registrations at boot.
  • Simple flows authored as SX composition (execute-fold) → compiled/dispatched to the engine; complex ones reference named Erlang flows.

P2 — state-change → activity emission (the CID-delta event source)

  • Every content mutation (new CID) appends a state-change activity to the log. Define the activity envelope (verb, actor, object CID, prev CID, delta summary).
  • Wire the host's write path (put!/set-comp!/edit-submit) to the append.

P3 — federation proper

  • Activities cross peers via next/ delivery (http_server / outbox / follower_graph). A remote service's trigger_registry fires the flow. Everything works over fed-sx.

P4 — close the loop

  • Flow effects mutate objects back durably (a flow's DescribeEffects → host writes / new activities), so business logic can change state, which federates, which triggers more flows.

Progress log (newest first)

  • 2026-07-02 — P0.1 done. host/blog--publish-activity + host/blog--post-category; the publish contract in SX, 200/200. Verified next/ triggers e2e baseline 10/10. Roadmap anchored. NEXT: P0.2 the dispatch bridge (in-process: serve.sh loads next/ kernel + registers the on-publish trigger; host emits the activity via the erlang-on-sx bridge to pipeline:apply_triggers).