plan: behaviour as data — lifecycles + ECA over an effect vocabulary (Slice 9)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s

Capture the behaviour layer. Principle: behaviour is data-defined orchestration over a
small fixed vocabulary of effects; only the effect primitives + the interpreter stay code,
everything between is editable posts (meta-circular — Lifecycle/Transition/Rule/Effect are
themselves types). Guards are pure type-system (Datalog) queries; runs on flow-on-sx
(durable: wait-for webhook, after timer; saga compensation). 'Place order'/'ship' = attempt
transition T.

Sketches the effect vocabulary in four tiers — pure guards / data (graph mutations) /
domain (reserve-stock, book-seat) / integration (charge-card, create-shipment, notify,
federate; the code edge, kept small per artdag's S-expr effects) / control (wait-for, after,
emit, transition; flow primitives) — worked through store + events. The fork: declarative
core + guarded code escape-hatch (Scheme/Smalltalk on a post). Start by pinning the
vocabulary + a generic interpreter, and lift commerce-on-sx/events-on-sx from guest-code
into lifecycle+effect DATA (they already implement exactly this, just not editably).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 09:43:13 +00:00
parent 82c0978da6
commit 4e968426c1

View File

@@ -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