diff --git a/plans/relations-as-posts.md b/plans/relations-as-posts.md index 0f6dd931..8299cdbc 100644 --- a/plans/relations-as-posts.md +++ b/plans/relations-as-posts.md @@ -65,6 +65,66 @@ So the complete picture: the metamodel expresses **structure + validation** of t platform's domain model uniformly; **behaviour composes from the substrate loops**; **integrations stay referenced services**. It's the convergence point of every loop in the repo. +## Behaviour as data — lifecycles + ECA over an effect vocabulary (DESIGN — Slice 9) + +Structure is inert; "place an order / ship goods" is the dynamic part. The principle: +**behaviour is data-defined orchestration over a small fixed vocabulary of effects.** Only two +layers stay code — the **effect primitives** (the irreducible ops that touch the world) and the +**interpreter** that runs the data. Everything between is editable posts. The system defines its +own behaviour down to the effect boundary (`[[feedback_runtime_control]]`). + +**Shape.** A type declares a **lifecycle** (a state machine) as data, plus standalone **ECA +rules** for reactions that aren't state transitions: + +``` +Order: cart --place--> placed [guard: stock-available ∧ total>0] [effects: reserve-stock] + placed --pay--> paid [guard: payment-ok] [effects: charge-card, confirm-stock] + paid --ship--> shipped [guard: address-valid] [effects: create-shipment, notify] +ECA: when stock(product) < threshold => notify(buyer:owner, "restock") +``` + +- **States/transitions/rules/effect-invocations are all posts** — meta-circular: `Lifecycle`, + `Transition`, `Rule`, `Effect` are themselves types in the metamodel; a behaviour is instances + you edit in the same UI as the schema. A transition = `{from, to, on-event, guard, [effects]}`. +- **Guards are PURE** — predicates over the instance's attributes/relations, i.e. type-system + queries (Datalog). No side effects, analysable, you can diagram a lifecycle. +- **Runs on `[[project_flow_on_sx]]`** because it's durable + long-running: `placed→paid` waits + for a SumUp webhook, `paid→shipped` waits days. flow's suspend/resume IS this. Failures → + compensation (saga) — `commerce-on-sx` already does "refund as a flow". +- "Place an order" / "ship" = *attempt transition T*; the button/webhook just fires the event. + +### The effect vocabulary (sketch — store + events) + +An effect is a named, parameterised op (itself an `Effect` post: name + params + binding). +Behaviours reference effects by name with args bound to instance/context. Four tiers: + +| Tier | Effect | Notes | +|------|--------|-------| +| **Pure guard** (read-only, not an effect) | `is-a? / attr-cmp / count / relation-exists?` | type-system queries (Datalog); compose the transition guards | +| **Data** (internal, transactional on the graph) | `create(type, attrs)`, `set-attr`, `set-state`, `relate / unrelate`, `incr / decr`, `append-ledger(entry)` | the durable post-graph mutations; `decr` stock is atomic-with-check | +| **Domain** (composed from data, named for atomicity/meaning) | `reserve-stock`, `release-stock`, `confirm-reservation`, `book-seat`, `issue-ticket` | small compositions the vocabulary blesses; `events-on-sx` has the capacity-safe versions | +| **Integration** (external services — the code edge) | `charge-card`, `refund` (SumUp), `create-shipment` / `track`, `notify(recipient, template, data)`, `federate(activity)` (ActivityPub), `process-media(asset)` (artdag) | the irreducible primitives; keep this list SMALL and composable (artdag's S-expression effects is the model) | +| **Control** (durable orchestration — flow primitives) | `wait-for(event)`, `wait-until(time) / after(dur)`, `emit(event)`, `transition(instance, state)` | `wait-for` = the SumUp webhook / shipment-delivered; `after` = reservation-expiry / event-reminder; `emit` chains ECA rules | + +So `place order` = guard `stock-available ∧ total>0` → effects `reserve-stock`, `set-state placed`, +`emit order-placed`; the webhook later fires `pay` → `charge-card`, `confirm-reservation`, +`set-state paid`. Events reuse the same machinery: ticket `reserved →(after 15m, no pay)→ released`, +event `--remind(after)--> notify` digests. Almost all of it is the same vocabulary. + +### The one fork (same shape as the type-system line) + +- **Declarative core** — lifecycles + ECA + the effect vocabulary: safe, analysable, diagrammable, + editable by non-programmers, verifiable. Covers ~95%. +- **Guarded code escape-hatch** — a `Scheme`/`Smalltalk` snippet stored on a post and `eval`'d for + the rare bespoke guard/effect (`[[project_content_on_sx]]` is Smalltalk message-passing, + `[[project_flow_on_sx]]` is guest Scheme — the homoiconic door exists). Turing-complete, unsafe, + fenced — exactly the decidable-core / fenced-frontier split we drew for types. + +**Where to start:** pin down the effect vocabulary above (the real design artifact), build the +generic interpreter on flow-on-sx with pure (Datalog) guards, and **lift `commerce-on-sx` / +`events-on-sx` from guest-code into lifecycle+effect DATA** — they already implement exactly this, +just not editably. + ## Why (the wrinkle that started this) Candidates for `is-a`/`subtype-of` were `instances-of("type")` — the *instances* that are