From ab058147fc9c219a4b403aaa3f6245343bb7cadb Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 3 Jul 2026 09:12:36 +0000 Subject: [PATCH] wire lib/events capacity-safe booking under the cinema buy path (first subsystem integration) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The events peer's ticket purchase now goes through lib/events' ATOMIC booking instead of a read-check-write race — real domain logic dropping in under the same clickable cinema, exactly as the seam promised. - MODULES (serve.sh + conformance.sh): load lib/persist/concurrency.sx (append-expect/conflict?) + lib/events/booking.sx. host/blog-store (persist/open) is stream-capable, same backend lib/events tests use. - host/blog-buy-ticket: replaced '(< (len sold) capacity)' with (ev/book! host/blog-store ); proceed only on :status :booked. occ-key = showing slug, capacity = host/blog--showing-capacity, actor = email + current roster len (unique per seat, collapses double-clicks, allows a person to hold several seats). persist append-expect retries on conflict — no oversell even under concurrent buys. - Per-offering cap + the sold-edge display are unchanged (render-safe); ev/book! is the authoritative gate in the handler. VERIFIED LIVE on events.rose-ash.com: cap-2 showing → 3 buys, exactly 2 booked (3rd refused); cap-1 showing → 10 CONCURRENT buys → exactly 1 sold, SOLD OUT. Sandbox: ev/book! returns booked/booked/full/already for a cap-2 occ. blog 218/218 (with the two new modules loaded). Co-Authored-By: Claude Opus 4.8 --- lib/host/blog.sx | 24 +++++++++++++++--------- lib/host/conformance.sh | 2 ++ lib/host/serve.sh | 2 ++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/host/blog.sx b/lib/host/blog.sx index 66d4c4a5..0754fb77 100644 --- a/lib/host/blog.sx +++ b/lib/host/blog.sx @@ -3242,17 +3242,23 @@ (email (or (host/field req "email") ""))) (begin (when (and showing offering (not (= email "")) - (< (len (host/blog--out-raw showing "sold")) (host/blog--showing-capacity showing)) (host/blog--offering-available? offering) (not (= host/blog--shop-base ""))) - (let ((body (get (http-request "POST" - (str host/blog--shop-base "/ticket?showing=" showing "&offering=" offering "&email=" email) - {} "") "body"))) - (when (starts-with? body "ticket:") - (let ((tid (substr body 7 (- (len body) 7)))) - (begin - (host/blog-relate! showing tid "sold") - (host/blog-relate! offering tid "sold")))))) ;; per-offering tally too + ;; lib/events ATOMIC capacity-safe booking (persist append-expect, retry-on-conflict) — + ;; replaces the former read-check-write race. A unique actor per seat (email + current + ;; count) lets one person hold several seats while collapsing double-clicks; :full ⇒ no + ;; oversell even under concurrent buys for the last seat. + (let ((actor (str email "#" (str (len (ev/roster host/blog-store showing)))))) + (let ((bk (ev/book! host/blog-store showing (host/blog--showing-capacity showing) actor))) + (when (= (get bk :status) :booked) + (let ((body (get (http-request "POST" + (str host/blog--shop-base "/ticket?showing=" showing "&offering=" offering "&email=" email) + {} "") "body"))) + (when (starts-with? body "ticket:") + (let ((tid (substr body 7 (- (len body) 7)))) + (begin + (host/blog-relate! showing tid "sold") + (host/blog-relate! offering tid "sold"))))))))) (dream-redirect (str "/" showing "/")))))) ;; the showing an offering belongs to (showing --offers--> offering). (define host/blog--offering-showing diff --git a/lib/host/conformance.sh b/lib/host/conformance.sh index e3270964..c47b1162 100755 --- a/lib/host/conformance.sh +++ b/lib/host/conformance.sh @@ -72,6 +72,8 @@ MODULES=( "lib/persist/kv.sx" "lib/persist/api.sx" "lib/persist/durable.sx" + "lib/persist/concurrency.sx" + "lib/events/booking.sx" "spec/render.sx" "web/adapter-html.sx" "lib/dream/types.sx" diff --git a/lib/host/serve.sh b/lib/host/serve.sh index dc86e1f8..af582d6b 100755 --- a/lib/host/serve.sh +++ b/lib/host/serve.sh @@ -105,6 +105,8 @@ MODULES=( "lib/persist/kv.sx" "lib/persist/api.sx" "lib/persist/durable.sx" + "lib/persist/concurrency.sx" + "lib/events/booking.sx" "spec/render.sx" "web/adapter-html.sx" "lib/dream/types.sx"