events: federated free/busy across trusted peers + 10 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m4s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m4s
Peers publish busy intervals per actor (iCal free/busy model — privacy- preserving, not event details). ev/peer-with-busy, ev/peer-busy; ev/federated-busy unions local availability-db busy + trusted peers' published busy (sorted); ev/federated-free? answers cross-instance availability, half-open, trust-gated (untrusted peers ignored). 219/219 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,20 @@
|
|||||||
;; This is the trust-gated stub: peers publish plain schedules and we fold the
|
;; This is the trust-gated stub: peers publish plain schedules and we fold the
|
||||||
;; trusted ones into a single sorted agenda. Real transport (fed-sx / signed
|
;; trusted ones into a single sorted agenda. Real transport (fed-sx / signed
|
||||||
;; fetch) slots in behind `ev/peer-agenda` without changing the merge.
|
;; fetch) slots in behind `ev/peer-agenda` without changing the merge.
|
||||||
|
;;
|
||||||
|
;; Federated FREE/BUSY follows the iCal model: a peer publishes BUSY intervals
|
||||||
|
;; for an actor (not event details — privacy-preserving), and we union local +
|
||||||
|
;; trusted-peer busy to answer "is this actor free?" across instances.
|
||||||
|
|
||||||
|
(define ev/peer (fn (id store) {:id id :busy (list) :store store}))
|
||||||
|
|
||||||
|
;; A peer that also publishes free/busy: `busy` is a list of
|
||||||
|
;; (actor ((start end) ...)) pairs.
|
||||||
|
(define ev/peer-with-busy (fn (id store busy) {:id id :busy busy :store store}))
|
||||||
|
|
||||||
(define ev/peer (fn (id store) {:id id :store store}))
|
|
||||||
(define ev/peer-id (fn (p) (get p :id)))
|
(define ev/peer-id (fn (p) (get p :id)))
|
||||||
(define ev/peer-store (fn (p) (get p :store)))
|
(define ev/peer-store (fn (p) (get p :store)))
|
||||||
|
(define ev/peer-busy-table (fn (p) (get p :busy)))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
ev-fed-member?
|
ev-fed-member?
|
||||||
@@ -96,3 +106,60 @@
|
|||||||
(fn
|
(fn
|
||||||
(agenda origin)
|
(agenda origin)
|
||||||
(filter (fn (o) (= (get o :origin) origin)) agenda)))
|
(filter (fn (o) (= (get o :origin) origin)) agenda)))
|
||||||
|
|
||||||
|
;; ---- federated free/busy ----
|
||||||
|
|
||||||
|
;; A peer's published busy intervals for `actor` ((start end) ...), or empty.
|
||||||
|
(define
|
||||||
|
ev/peer-busy
|
||||||
|
(fn
|
||||||
|
(peer actor)
|
||||||
|
(let
|
||||||
|
((row (ev-fed-assoc actor (ev/peer-busy-table peer))))
|
||||||
|
(if (nil? row) (list) (first (rest row))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
ev-fed-assoc
|
||||||
|
(fn
|
||||||
|
(k pairs)
|
||||||
|
(cond
|
||||||
|
((empty? pairs) nil)
|
||||||
|
((= (first (first pairs)) k) (first pairs))
|
||||||
|
(else (ev-fed-assoc k (rest pairs))))))
|
||||||
|
|
||||||
|
;; All busy intervals for `actor` across the LOCAL availability db plus every
|
||||||
|
;; TRUSTED peer's published free/busy, merged and sorted by start.
|
||||||
|
;; `local-db` is an availability db (see availability.sx ev-build-avail).
|
||||||
|
(define
|
||||||
|
ev/federated-busy
|
||||||
|
(fn
|
||||||
|
(local-db peers trust actor)
|
||||||
|
(let
|
||||||
|
((acc (list)))
|
||||||
|
(begin
|
||||||
|
(for-each (fn (iv) (append! acc iv)) (ev-busy local-db actor))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(peer)
|
||||||
|
(when
|
||||||
|
(ev/trusts? trust (ev/peer-id peer))
|
||||||
|
(for-each
|
||||||
|
(fn (iv) (append! acc iv))
|
||||||
|
(ev/peer-busy peer actor))))
|
||||||
|
peers)
|
||||||
|
(ev-sort-lists acc)))))
|
||||||
|
|
||||||
|
;; Half-open overlap of interval (s e) with window [qs, qe).
|
||||||
|
(define
|
||||||
|
ev-fed-overlaps?
|
||||||
|
(fn (iv qs qe) (and (< (first iv) qe) (< qs (first (rest iv))))))
|
||||||
|
|
||||||
|
;; Is `actor` free across [qs, qe) considering local + trusted-peer busy?
|
||||||
|
(define
|
||||||
|
ev/federated-free?
|
||||||
|
(fn
|
||||||
|
(local-db peers trust actor qs qe)
|
||||||
|
(not
|
||||||
|
(some
|
||||||
|
(fn (iv) (ev-fed-overlaps? iv qs qe))
|
||||||
|
(ev/federated-busy local-db peers trust actor)))))
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"lang": "events",
|
"lang": "events",
|
||||||
"total_passed": 209,
|
"total_passed": 219,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 209,
|
"total": 219,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"calendar","passed":37,"failed":0,"total":37},
|
{"name":"calendar","passed":37,"failed":0,"total":37},
|
||||||
{"name":"availability","passed":22,"failed":0,"total":22},
|
{"name":"availability","passed":22,"failed":0,"total":22},
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
{"name":"ticket","passed":31,"failed":0,"total":31},
|
{"name":"ticket","passed":31,"failed":0,"total":31},
|
||||||
{"name":"notify","passed":7,"failed":0,"total":7},
|
{"name":"notify","passed":7,"failed":0,"total":7},
|
||||||
{"name":"reminders","passed":14,"failed":0,"total":14},
|
{"name":"reminders","passed":14,"failed":0,"total":14},
|
||||||
{"name":"federation","passed":13,"failed":0,"total":13}
|
{"name":"federation","passed":23,"failed":0,"total":23}
|
||||||
],
|
],
|
||||||
"generated": "2026-06-07T04:58:42+00:00"
|
"generated": "2026-06-07T05:31:56+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# events scoreboard
|
# events scoreboard
|
||||||
|
|
||||||
**209 / 209 passing** (0 failure(s)).
|
**219 / 219 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
@@ -11,4 +11,4 @@
|
|||||||
| ticket | 31 | 31 | ok |
|
| ticket | 31 | 31 | ok |
|
||||||
| notify | 7 | 7 | ok |
|
| notify | 7 | 7 | ok |
|
||||||
| reminders | 14 | 14 | ok |
|
| reminders | 14 | 14 | ok |
|
||||||
| federation | 13 | 13 | ok |
|
| federation | 23 | 23 | ok |
|
||||||
|
|||||||
@@ -143,6 +143,81 @@
|
|||||||
(map (fn (o) (get o :origin)) pa)
|
(map (fn (o) (get o :origin)) pa)
|
||||||
(list (quote tokyo) (quote tokyo) (quote tokyo))))))))))
|
(list (quote tokyo) (quote tokyo) (quote tokyo))))))))))
|
||||||
|
|
||||||
|
;; ---- federated free/busy ----
|
||||||
|
(define
|
||||||
|
ev-fd-fb-run-all!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((local-db
|
||||||
|
(ev-avail-db
|
||||||
|
(list (ev-occ (quote yoga) (ev-dt 2026 6 1 9 0) 60))
|
||||||
|
(list (list (quote nia) (str (quote yoga) "@" (ev-dt 2026 6 1 9 0))))))
|
||||||
|
(berlin
|
||||||
|
(ev/peer-with-busy
|
||||||
|
(quote berlin)
|
||||||
|
(ev/empty)
|
||||||
|
(list
|
||||||
|
(list (quote nia)
|
||||||
|
(list (list (ev-dt 2026 6 1 14 0) (ev-dt 2026 6 1 15 0)))))))
|
||||||
|
(paris
|
||||||
|
(ev/peer-with-busy
|
||||||
|
(quote paris)
|
||||||
|
(ev/empty)
|
||||||
|
(list
|
||||||
|
(list (quote nia)
|
||||||
|
(list (list (ev-dt 2026 6 1 11 0) (ev-dt 2026 6 1 12 0))))))))
|
||||||
|
(let
|
||||||
|
((peers (list berlin paris)))
|
||||||
|
(do
|
||||||
|
;; peer-busy reads a peer's published intervals
|
||||||
|
(ev-fd-check!
|
||||||
|
"peer-busy returns published intervals for an actor"
|
||||||
|
(ev/peer-busy berlin (quote nia))
|
||||||
|
(list (list (ev-dt 2026 6 1 14 0) (ev-dt 2026 6 1 15 0))))
|
||||||
|
(ev-fd-check!
|
||||||
|
"peer-busy empty for an actor with nothing published"
|
||||||
|
(ev/peer-busy berlin (quote zed))
|
||||||
|
(list))
|
||||||
|
;; federated-busy unions local + trusted-peer busy, sorted
|
||||||
|
(ev-fd-check!
|
||||||
|
"federated-busy unions local + trusted peer, sorted"
|
||||||
|
(ev/federated-busy local-db (list berlin) (list (quote berlin)) (quote nia))
|
||||||
|
(list
|
||||||
|
(list (ev-dt 2026 6 1 9 0) (ev-dt 2026 6 1 10 0))
|
||||||
|
(list (ev-dt 2026 6 1 14 0) (ev-dt 2026 6 1 15 0))))
|
||||||
|
(ev-fd-check!
|
||||||
|
"untrusted peer busy is excluded from federated-busy"
|
||||||
|
(ev/federated-busy local-db peers (list (quote berlin)) (quote nia))
|
||||||
|
(list
|
||||||
|
(list (ev-dt 2026 6 1 9 0) (ev-dt 2026 6 1 10 0))
|
||||||
|
(list (ev-dt 2026 6 1 14 0) (ev-dt 2026 6 1 15 0))))
|
||||||
|
;; federated-free? considers both local and trusted-peer commitments
|
||||||
|
(ev-fd-check!
|
||||||
|
"free locally and on peers in an open window"
|
||||||
|
(ev/federated-free? local-db peers (list (quote berlin)) (quote nia) (ev-dt 2026 6 1 16 0) (ev-dt 2026 6 1 17 0))
|
||||||
|
true)
|
||||||
|
(ev-fd-check!
|
||||||
|
"not free during a LOCAL booking"
|
||||||
|
(ev/federated-free? local-db peers (list (quote berlin)) (quote nia) (ev-dt 2026 6 1 9 30) (ev-dt 2026 6 1 9 45))
|
||||||
|
false)
|
||||||
|
(ev-fd-check!
|
||||||
|
"not free during a TRUSTED PEER busy interval"
|
||||||
|
(ev/federated-free? local-db peers (list (quote berlin)) (quote nia) (ev-dt 2026 6 1 14 30) (ev-dt 2026 6 1 14 45))
|
||||||
|
false)
|
||||||
|
(ev-fd-check!
|
||||||
|
"free during an UNTRUSTED peer's busy interval (paris not trusted)"
|
||||||
|
(ev/federated-free? local-db peers (list (quote berlin)) (quote nia) (ev-dt 2026 6 1 11 15) (ev-dt 2026 6 1 11 45))
|
||||||
|
true)
|
||||||
|
(ev-fd-check!
|
||||||
|
"not free once paris is trusted too"
|
||||||
|
(ev/federated-free? local-db peers (list (quote berlin) (quote paris)) (quote nia) (ev-dt 2026 6 1 11 15) (ev-dt 2026 6 1 11 45))
|
||||||
|
false)
|
||||||
|
(ev-fd-check!
|
||||||
|
"federated-free? half-open at a busy edge"
|
||||||
|
(ev/federated-free? local-db peers (list (quote berlin)) (quote nia) (ev-dt 2026 6 1 15 0) (ev-dt 2026 6 1 16 0))
|
||||||
|
true))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
ev-federation-tests-run!
|
ev-federation-tests-run!
|
||||||
(fn
|
(fn
|
||||||
@@ -152,4 +227,5 @@
|
|||||||
(set! ev-fd-fail 0)
|
(set! ev-fd-fail 0)
|
||||||
(set! ev-fd-failures (list))
|
(set! ev-fd-failures (list))
|
||||||
(ev-fd-run-all!)
|
(ev-fd-run-all!)
|
||||||
|
(ev-fd-fb-run-all!)
|
||||||
{:failures ev-fd-failures :total (+ ev-fd-pass ev-fd-fail) :passed ev-fd-pass :failed ev-fd-fail})))
|
{:failures ev-fd-failures :total (+ ev-fd-pass ev-fd-fail) :passed ev-fd-pass :failed ev-fd-fail})))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ capacity rules, transactional booking, and a flow-driven notification dispatcher
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/events/conformance.sh` → **209/209** (Phases 1-4 complete: calendar, booking, notify, federation)
|
`bash lib/events/conformance.sh` → **219/219** (Phases 1-4 + ext: federated free/busy)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -81,11 +81,18 @@ lib/events/api.sx ── (events/schedule) (events/book) (events/agenda) ──
|
|||||||
## Phase 4 — Federation
|
## Phase 4 — Federation
|
||||||
- [x] cross-instance events (peer calendar) — trust-gated stub
|
- [x] cross-instance events (peer calendar) — trust-gated stub
|
||||||
- [x] tests: federated agenda merge
|
- [x] tests: federated agenda merge
|
||||||
- [ ] federated availability/free-busy across trusted peers
|
- [x] federated availability/free-busy across trusted peers
|
||||||
- [ ] real transport behind `ev/peer-agenda` (fed-sx / signed fetch)
|
- [ ] real transport behind `ev/peer-agenda` (fed-sx / signed fetch)
|
||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-07 — Federated free/busy (extension). Peers publish BUSY intervals
|
||||||
|
per actor (iCal free/busy model — privacy-preserving, not event details).
|
||||||
|
`ev/peer-with-busy`, `ev/peer-busy`; `ev/federated-busy` unions local
|
||||||
|
availability-db busy + trusted peers' published busy (sorted);
|
||||||
|
`ev/federated-free?` answers "is X free in [qs,qe)?" across instances,
|
||||||
|
half-open, trust-gated (untrusted peers' busy ignored; revocation immediate).
|
||||||
|
+10 tests, 219/219 green.
|
||||||
- 2026-06-07 — **Phase 4: federation (trust-gated stub).** `federation.sx`:
|
- 2026-06-07 — **Phase 4: federation (trust-gated stub).** `federation.sx`:
|
||||||
a peer publishes a schedule (events store); `ev/federated-agenda` merges the
|
a peer publishes a schedule (events store); `ev/federated-agenda` merges the
|
||||||
local agenda (origin :local) with every TRUSTED peer's agenda, sorted by
|
local agenda (origin :local) with every TRUSTED peer's agenda, sorted by
|
||||||
|
|||||||
Reference in New Issue
Block a user