Merge loops/events into architecture: events-on-sx cross-event conflict-checked booking (311 tests, 12 suites)
ev/book-checked! prevents an attendee double-booking themselves across different events by consulting their persist-derived availability for the occurrence window (:time-conflict on overlap; same-occurrence re-book stays idempotent).
This commit is contained in:
@@ -249,3 +249,29 @@
|
|||||||
(fn
|
(fn
|
||||||
(b store actor ws we)
|
(b store actor ws we)
|
||||||
(> (len (ev/conflicts-p b store actor ws we)) 0)))
|
(> (len (ev/conflicts-p b store actor ws we)) 0)))
|
||||||
|
|
||||||
|
;; ---- conflict-checked booking ----
|
||||||
|
;; Capacity is per-event, but an attendee should not be double-booked against
|
||||||
|
;; THEMSELVES across different events. Would booking `actor` into `occ` overlap
|
||||||
|
;; an existing booking of theirs elsewhere? (Derived from persist availability;
|
||||||
|
;; an existing booking into `occ` itself is excluded — that's idempotent.)
|
||||||
|
(define
|
||||||
|
ev/would-time-conflict?
|
||||||
|
(fn
|
||||||
|
(b store actor occ)
|
||||||
|
(and
|
||||||
|
(not (ev-actor-booked? b (ev-occ-key occ) actor))
|
||||||
|
(not (ev/free-p? b store actor (get occ :start) (get occ :end))))))
|
||||||
|
|
||||||
|
;; Book `actor` into `occ` only if it doesn't clash with their other bookings.
|
||||||
|
;; Re-booking the same occurrence is idempotent (:already); a clash returns
|
||||||
|
;; :time-conflict; otherwise the normal ev/book-occ! result (:booked / :full).
|
||||||
|
(define
|
||||||
|
ev/book-checked!
|
||||||
|
(fn
|
||||||
|
(b store actor occ)
|
||||||
|
(cond
|
||||||
|
((ev-actor-booked? b (ev-occ-key occ) actor) (ev/book-occ! b store actor occ))
|
||||||
|
((ev/would-time-conflict? b store actor occ)
|
||||||
|
{:status :time-conflict :actor actor :occ-key (ev-occ-key occ)})
|
||||||
|
(else (ev/book-occ! b store actor occ)))))
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"lang": "events",
|
"lang": "events",
|
||||||
"total_passed": 303,
|
"total_passed": 311,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 303,
|
"total": 311,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"calendar","passed":51,"failed":0,"total":51},
|
{"name":"calendar","passed":51,"failed":0,"total":51},
|
||||||
{"name":"timezone","passed":17,"failed":0,"total":17},
|
{"name":"timezone","passed":17,"failed":0,"total":17},
|
||||||
{"name":"availability","passed":22,"failed":0,"total":22},
|
{"name":"availability","passed":22,"failed":0,"total":22},
|
||||||
{"name":"api","passed":24,"failed":0,"total":24},
|
{"name":"api","passed":32,"failed":0,"total":32},
|
||||||
{"name":"booking","passed":82,"failed":0,"total":82},
|
{"name":"booking","passed":82,"failed":0,"total":82},
|
||||||
{"name":"booking-notify","passed":11,"failed":0,"total":11},
|
{"name":"booking-notify","passed":11,"failed":0,"total":11},
|
||||||
{"name":"ticket","passed":31,"failed":0,"total":31},
|
{"name":"ticket","passed":31,"failed":0,"total":31},
|
||||||
@@ -16,5 +16,5 @@
|
|||||||
{"name":"federation","passed":29,"failed":0,"total":29},
|
{"name":"federation","passed":29,"failed":0,"total":29},
|
||||||
{"name":"integration","passed":8,"failed":0,"total":8}
|
{"name":"integration","passed":8,"failed":0,"total":8}
|
||||||
],
|
],
|
||||||
"generated": "2026-06-07T11:51:43+00:00"
|
"generated": "2026-06-07T13:59:09+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# events scoreboard
|
# events scoreboard
|
||||||
|
|
||||||
**303 / 303 passing** (0 failure(s)).
|
**311 / 311 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
| calendar | 51 | 51 | ok |
|
| calendar | 51 | 51 | ok |
|
||||||
| timezone | 17 | 17 | ok |
|
| timezone | 17 | 17 | ok |
|
||||||
| availability | 22 | 22 | ok |
|
| availability | 22 | 22 | ok |
|
||||||
| api | 24 | 24 | ok |
|
| api | 32 | 32 | ok |
|
||||||
| booking | 82 | 82 | ok |
|
| booking | 82 | 82 | ok |
|
||||||
| booking-notify | 11 | 11 | ok |
|
| booking-notify | 11 | 11 | ok |
|
||||||
| ticket | 31 | 31 | ok |
|
| ticket | 31 | 31 | ok |
|
||||||
|
|||||||
@@ -259,6 +259,66 @@
|
|||||||
20))
|
20))
|
||||||
true))))))))))))
|
true))))))))))))
|
||||||
|
|
||||||
|
;; ---- conflict-checked booking ----
|
||||||
|
(define
|
||||||
|
ev-api-cf-run-all!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((b (persist/open))
|
||||||
|
(store
|
||||||
|
(ev/schedule
|
||||||
|
(ev/schedule
|
||||||
|
(ev/schedule (ev/empty) (quote a) (ev-dt 2026 6 1 9 0) 60 nil 10)
|
||||||
|
(quote bb)
|
||||||
|
(ev-dt 2026 6 1 9 30)
|
||||||
|
60
|
||||||
|
nil
|
||||||
|
10)
|
||||||
|
(quote c)
|
||||||
|
(ev-dt 2026 6 1 11 0)
|
||||||
|
60
|
||||||
|
nil
|
||||||
|
10)))
|
||||||
|
(let
|
||||||
|
((oa (ev-occ (quote a) (ev-dt 2026 6 1 9 0) 60))
|
||||||
|
(ob (ev-occ (quote bb) (ev-dt 2026 6 1 9 30) 60))
|
||||||
|
(oc (ev-occ (quote c) (ev-dt 2026 6 1 11 0) 60)))
|
||||||
|
(do
|
||||||
|
(ev-api-check!
|
||||||
|
"first checked booking succeeds"
|
||||||
|
(get (ev/book-checked! b store (quote nia) oa) :status)
|
||||||
|
:booked)
|
||||||
|
(ev-api-check!
|
||||||
|
"overlapping different-event booking is a time conflict"
|
||||||
|
(get (ev/book-checked! b store (quote nia) ob) :status)
|
||||||
|
:time-conflict)
|
||||||
|
(ev-api-check!
|
||||||
|
"the clashing booking did not land on the roster"
|
||||||
|
(ev/roster-occ b ob)
|
||||||
|
(list))
|
||||||
|
(ev-api-check!
|
||||||
|
"a non-overlapping booking is allowed"
|
||||||
|
(get (ev/book-checked! b store (quote nia) oc) :status)
|
||||||
|
:booked)
|
||||||
|
(ev-api-check!
|
||||||
|
"re-booking the same occurrence is idempotent, not a conflict"
|
||||||
|
(get (ev/book-checked! b store (quote nia) oa) :status)
|
||||||
|
:already)
|
||||||
|
;; a different actor is unaffected by nia's bookings
|
||||||
|
(ev-api-check!
|
||||||
|
"another actor may take the overlapping slot"
|
||||||
|
(get (ev/book-checked! b store (quote ola) ob) :status)
|
||||||
|
:booked)
|
||||||
|
(ev-api-check!
|
||||||
|
"would-time-conflict? predicate agrees"
|
||||||
|
(ev/would-time-conflict? b store (quote nia) ob)
|
||||||
|
true)
|
||||||
|
(ev-api-check!
|
||||||
|
"would-time-conflict? false for a free slot"
|
||||||
|
(ev/would-time-conflict? b store (quote zed) ob)
|
||||||
|
false))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
ev-api-tests-run!
|
ev-api-tests-run!
|
||||||
(fn
|
(fn
|
||||||
@@ -268,4 +328,5 @@
|
|||||||
(set! ev-api-fail 0)
|
(set! ev-api-fail 0)
|
||||||
(set! ev-api-failures (list))
|
(set! ev-api-failures (list))
|
||||||
(ev-api-run-all!)
|
(ev-api-run-all!)
|
||||||
|
(ev-api-cf-run-all!)
|
||||||
{:failures ev-api-failures :total (+ ev-api-pass ev-api-fail) :passed ev-api-pass :failed ev-api-fail})))
|
{:failures ev-api-failures :total (+ ev-api-pass ev-api-fail) :passed ev-api-pass :failed ev-api-fail})))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ capacity rules, transactional booking, and a flow-driven notification dispatcher
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/events/conformance.sh` → **303/303** (Phases 1-4 + 9 ext: fed f/b, waitlist, EXDATE/RDATE, overrides, booking/reschedule-notify, fed transport, timezones+DST, e2e delivery pipeline)
|
`bash lib/events/conformance.sh` → **311/311** (Phases 1-4 + 10 ext: …timezones+DST, e2e delivery pipeline, cross-event conflict-checked booking)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -88,6 +88,13 @@ lib/events/api.sx ── (events/schedule) (events/book) (events/agenda) ──
|
|||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-07 — Cross-event conflict-checked booking (extension). Capacity is
|
||||||
|
per-event, but `ev/book-checked!` also prevents an attendee double-booking
|
||||||
|
THEMSELVES across different events: it consults the actor's persist-derived
|
||||||
|
availability (ev/free-p?) for the occurrence's window and returns
|
||||||
|
:time-conflict on overlap, else the normal ev/book-occ! result. Re-booking
|
||||||
|
the same occurrence is idempotent (:already, not a conflict); other actors are
|
||||||
|
unaffected. `ev/would-time-conflict?` predicate. +8 tests, 311/311 green.
|
||||||
- 2026-06-07 — End-to-end delivery pipeline (closes the derivation↔delivery
|
- 2026-06-07 — End-to-end delivery pipeline (closes the derivation↔delivery
|
||||||
gap). `ev/deliver-messages` bridges SX notification messages to the Scheme
|
gap). `ev/deliver-messages` bridges SX notification messages to the Scheme
|
||||||
notify flow: each (id recipient body) is `serialize`d to s-expression text,
|
notify flow: each (id recipient body) is `serialize`d to s-expression text,
|
||||||
|
|||||||
Reference in New Issue
Block a user