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
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user