# events-on-sx: Calendar, ticketing & notification delivery on Datalog > **DRAFT outline.** The events vertical + the shared notification-delivery edge. > Depends on `persist-on-sx` (bookings ledger) and `flow-on-sx` (reminders, retrying > delivery). Pairs with `commerce-on-sx` for paid tickets. rose-ash's `events` domain is calendar + ticketing: recurring events, availability, capacity, bookings. Scheduling is constraint reasoning — "is this slot free given recurrence, capacity, and the attendee's other bookings?" — which is rule evaluation over facts. Datalog expresses availability, recurrence expansion, and capacity as rules; a booking is a transaction; reminders and digests are durable `flow`s. Notification *delivery* (email/push) — needed here and by `feed/notify` — is folded in as an injected transport, extractable later. End-state: a Datalog-on-SX events layer with recurrence expansion, availability + capacity rules, transactional booking, and a flow-driven notification dispatcher (reminders, digests, retries) over an injected transport. ## Status (rolling) `bash lib/events/conformance.sh` → **53/53** (Phase 1: calendar + availability) ## Ground rules - **Scope:** only `lib/events/**` and `plans/events-on-sx.md`. May **import** from `lib/datalog/`, and (once they exist) `lib/persist/` + `lib/flow/`. Do not edit substrates. - **Architecture:** events/availability/capacity are Datalog facts + rules; recurrence expands to occurrence facts within a window; a booking checks rules then appends a `persist` event (idempotent, capacity-safe). Notifications are flows that suspend on transport IO and retry on failure. - **Determinism:** recurrence expansion + availability must be reproducible for a fixed window + ruleset; capacity checks must be race-safe (no overbooking). - **Commits:** one feature per commit. Progress log + tick boxes. ## Architecture sketch ``` Event + booking Result event(id,start,rrule,capacity) {:booked | :full | :conflict} + reminders │ ▲ ▼ │ lib/events/calendar.sx lib/events/availability.sx — event facts, recurrence (RRULE) — free/busy + capacity rules (Datalog) — expand occurrences in window │ │ ▲ ▼ │ lib/events/booking.sx lib/events/notify.sx (flow) — transactional, capacity-safe — reminders / digests, retry on fail — bookings → persist ledger — injected transport (email/push) │ │ ▼ ▼ lib/events/api.sx ── (events/schedule) (events/book) (events/agenda) ──────┘ ``` ## Phase 1 — Calendar + recurrence - [x] `calendar.sx` — event facts, RRULE expansion in a window (DAILY/WEEKLY) - [x] `calendar.sx` — MONTHLY (bymonthday + nth-weekday byday) - [x] `availability.sx` — free/busy rules (busy/conflict/busy_in on Datalog) - [ ] `availability.sx` — next-free slot search (same rules, different bindings) - [ ] `api.sx` + tests + scoreboard + conformance.sh ## Phase 2 — Ticketing + booking - [ ] capacity rules; transactional booking → `persist` (no overbooking) - [ ] paid tickets compose with `commerce` order flow - [ ] tests: capacity edge, double-book guard, conflict detection ## Phase 3 — Notification delivery (flow) - [ ] `notify.sx` — reminder/digest flows over injected transport - [ ] retry/backoff on transport failure (flow suspend/resume) - [ ] tests: delivery success, retry path, idempotent re-send - [ ] NOTE: shared with `feed/notify` — candidate for later extraction to a `delivery-on-sx` once a second consumer is real ## Phase 4 — Federation - [ ] cross-instance events (peer calendar) — trust-gated stub - [ ] tests: federated agenda merge ## Progress log - 2026-06-07 — `availability.sx`: free/busy + conflict detection as forward- chained Datalog over `occurrence`/`booking` EDB. Rules `busy(A,S,E)`, `conflict(A,O1,O2)` (canonical `O1day-number conversion, DAILY/WEEKLY RRULE expansion in a bounded (start,end) window with INTERVAL, COUNT (window- independent), UNTIL, BYDAY (weekly). `ev-expand-all` merges + sorts. Wired conformance harness (conf + thin wrapper reusing `lib/guest/conformance.sh`), scoreboard. 24/24 green. MONTHLY deferred to next commit. ## Blockers - None. Substrates present: `lib/datalog` (276/276), `lib/persist`, `lib/flow` all exist — Phase 2/3 unblocked when reached.