Files
rose-ash/lib/gitea/tests/fed.sx
giles 50e6da2ae9 sx-gitea Phase 8: fed — ForgeFed federation (TDD, 615/615 all suites)
lib/gitea/fed.sx: forges federate as peers. Each forge carries an
instance id; users and repos project as AP actor documents (Person/
Group/Repository with inbox/outbox + clone endpoint); the outbox is the
activity log in an AP-shaped envelope.

Trust follows the events-federation pattern — a kv set of peer ids
RE-CHECKED on every operation (inbox, mirror sync, delivery), so
revoking a peer takes effect immediately; peer transports (dream app
fns) live only in the runtime cache.

Inbox (POST /api/ap/inbox, trust-gated): every accepted activity lands
in a federated log with :origin provenance; open-issue/comment/open-pr
MATERIALIZE — the foreign author becomes an auto-created proxy user
'<name>@<peer>' and the issue/comment/PR is created locally under that
identity. fed-deliver! pushes public-repo activities (cursor-based,
never private) to every trusted peer's inbox. Cross-instance repo
follow = mirror!/mirror-sync! over the Phase 3 wire client.
fed-timeline merges local + foreign activities with provenance tags.

Suite: two in-memory forges federating end to end — actor docs, trust
lifecycle, materialization, proxy-user reuse, wire inbox 400/403/200,
mirrors (clone/sync/trust-revocation), cursor delivery, timelines.

Adds lib/gitea/README.md (composition map, architectural rules, known
limits). Final scoreboard: 615/615 across repo/access/wire/issues/pr/
activity/search/fed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:07:47 +00:00

372 lines
12 KiB
Plaintext

; lib/gitea/tests/fed.sx — Phase 8: ForgeFed. Two in-memory forges:
; AP actor docs + outbox, trust-gated inbox with provenance log and
; materialized federated issues/comments/PRs (proxy users), cursor-based
; outbound delivery, cross-instance repo mirrors, federated timeline.
(st-bootstrap-classes!)
(content/bootstrap!)
(content-bootstrap-markdown!)
(content-bootstrap-table!)
(define gitea-fed-pass 0)
(define gitea-fed-fail 0)
(define gitea-fed-fails (list))
(define
gitea-fed-test
(fn
(name actual expected)
(if
(= actual expected)
(set! gitea-fed-pass (+ gitea-fed-pass 1))
(begin
(set! gitea-fed-fail (+ gitea-fed-fail 1))
(set! gitea-fed-fails (append gitea-fed-fails (list {:name name :expected (inspect expected) :actual (inspect actual)})))))))
; ── forge A ──────────────────────────────────────────────────────────
(define gf-dbA (persist/mem-backend))
(define gf-A (gitea/forge gf-dbA))
(gitea/instance! gf-A "forge-a")
(gitea/user-create! gf-A "alice")
(gitea/org-create! gf-A "acme")
(gitea/repo-create! gf-A "alice" "lib" {:created-at 1})
(gitea/repo-create! gf-A "alice" "hid" {:created-at 2 :visibility "private"})
(define gf-gA (gitea/repo-git gf-A "alice" "lib"))
(git/add! gf-gA "README.md" "the lib\n")
(git/commit! gf-gA {:message "c1" :time 3 :author "alice"})
(gitea/issue-create!
gf-A
"alice"
"lib"
"alice"
"Public issue"
"hello"
{:created-at 4})
(gitea/issue-create!
gf-A
"alice"
"hid"
"alice"
"Hidden issue"
"shh"
{:created-at 5})
; ── forge B ──────────────────────────────────────────────────────────
(define gf-dbB (persist/mem-backend))
(define gf-B (gitea/forge gf-dbB))
(gitea/instance! gf-B "forge-b")
(gitea/user-create! gf-B "bob")
(gitea/repo-create! gf-B "bob" "hub" {:created-at 10})
(define gf-gB (gitea/repo-git gf-B "bob" "hub"))
(git/add! gf-gB "main.txt" "hub main\n")
(git/commit! gf-gB {:message "h1" :time 11 :author "bob"})
(git/branch! gf-gB "feat")
(git/checkout! gf-gB "feat")
(git/add! gf-gB "feat.txt" "hub feat\n")
(git/commit! gf-gB {:message "h2" :time 12 :author "bob"})
(git/checkout! gf-gB "main")
(define gf-appA (gitea/app gf-A))
(define gf-appB (gitea/app gf-B))
(gitea/peer-register! gf-B "forge-a" gf-appA nil)
(gitea/peer-register! gf-A "forge-b" gf-appB nil)
; ── identity + actor documents ───────────────────────────────────────
(gitea-fed-test "instance id" (gitea/instance-id gf-A) "forge-a")
(gitea-fed-test
"actor id"
(gitea/actor-id gf-A "user:alice")
"forge-a/user:alice")
(gitea-fed-test
"ap user type"
(get (gitea/ap-user gf-A "alice") :type)
"Person")
(gitea-fed-test
"ap user id"
(get (gitea/ap-user gf-A "alice") :id)
"forge-a/user:alice")
(gitea-fed-test
"ap org type"
(get (gitea/ap-user gf-A "acme") :type)
"Group")
(gitea-fed-test "ap user missing" (gitea/ap-user gf-A "zeb") nil)
(gitea-fed-test
"ap repo type"
(get (gitea/ap-repo gf-A "alice" "lib") :type)
"Repository")
(gitea-fed-test
"ap repo attribution"
(get (gitea/ap-repo gf-A "alice" "lib") :attributedTo)
"forge-a/user:alice")
(gitea-fed-test
"ap repo clone endpoint"
(get (gitea/ap-repo gf-A "alice" "lib") :clone)
"/alice/lib/info/refs")
(gitea-fed-test
"outbox is ap-shaped"
(get (first (gitea/ap-outbox gf-A "alice" 10)) :actor)
"forge-a/user:alice")
(gitea-fed-test
"outbox hides private repos"
(len (gitea/ap-outbox gf-A "alice" 10))
2)
; ── trust ────────────────────────────────────────────────────────────
(gitea-fed-test "untrusted by default" (gitea/trusted? gf-B "forge-a") false)
(gitea-fed-test
"inbox rejects untrusted"
(get (gitea/fed-receive! gf-B "forge-a" {:verb "open-issue"}) :error)
"untrusted-peer")
(gitea-fed-test
"rejected activity not logged"
(len (gitea/fed-log gf-B))
0)
(gitea/trust! gf-B "forge-a")
(gitea-fed-test "trusted after trust!" (gitea/trusted? gf-B "forge-a") true)
(gitea-fed-test "trusted peers" (gitea/trusted-peers gf-B) (list "forge-a"))
; ── inbound materialization ──────────────────────────────────────────
(define gf-r1 (gitea/fed-receive! gf-B "forge-a" {:actor "forge-a/user:alice" :detail {:title "Fed issue" :body "opened from forge-a"} :object "issue:bob/hub#0" :at 50 :tags (list "repo:bob/hub") :verb "open-issue"}))
(gitea-fed-test "fed issue accepted" (get gf-r1 :materialized) "issue")
(gitea-fed-test "fed issue number" (get gf-r1 :number) 1)
(gitea-fed-test
"proxy user created"
(gitea/owner-exists? gf-B "alice@forge-a")
true)
(gitea-fed-test
"fed issue author"
(get (gitea/issue-get gf-B "bob" "hub" 1) :author)
"alice@forge-a")
(gitea-fed-test
"fed issue title"
(get (gitea/issue-get gf-B "bob" "hub" 1) :title)
"Fed issue")
(gitea-fed-test
"fed log provenance"
(get (first (gitea/fed-log gf-B)) :origin)
"forge-a")
(define gf-owners-before (len (gitea/owners gf-B)))
(define gf-r2 (gitea/fed-receive! gf-B "forge-a" {:actor "forge-a/user:alice" :detail {:body "following up"} :object "issue:bob/hub#1" :at 51 :tags (list "repo:bob/hub") :verb "comment"}))
(gitea-fed-test "fed comment accepted" (get gf-r2 :materialized) "comment")
(gitea-fed-test
"fed comment recorded"
(len (get (gitea/issue-get gf-B "bob" "hub" 1) :comments))
1)
(gitea-fed-test
"proxy user reused"
(len (gitea/owners gf-B))
gf-owners-before)
(define gf-r3 (gitea/fed-receive! gf-B "forge-a" {:actor "forge-a/user:alice" :detail {:source "feat" :title "Fed PR" :body "take my branch" :target "main"} :object "pr:bob/hub#0" :at 52 :tags (list "repo:bob/hub") :verb "open-pr"}))
(gitea-fed-test "fed pr accepted" (get gf-r3 :materialized) "pr")
(gitea-fed-test
"fed pr author"
(get (gitea/pr-get gf-B "bob" "hub" (get gf-r3 :number)) :author)
"alice@forge-a")
(gitea-fed-test
"fed pr branches"
(get (gitea/pr-get gf-B "bob" "hub" (get gf-r3 :number)) :source)
"feat")
(gitea-fed-test
"unknown verb still logged"
(get (gitea/fed-receive! gf-B "forge-a" {:actor "forge-a/user:alice" :object "repo:bob/hub" :at 53 :tags (list "repo:bob/hub") :verb "star"}) :materialized)
"none")
(gitea-fed-test
"comment with bad object"
(get (gitea/fed-receive! gf-B "forge-a" {:actor "forge-a/user:alice" :object "nonsense" :at 54 :verb "comment"}) :error)
"bad-object")
(gitea-fed-test "fed log grows" (len (gitea/fed-log gf-B)) 5)
; ── inbox over the wire ──────────────────────────────────────────────
(define
gf-postB
(fn
(body)
(gf-appB (dream-request "POST" "/api/ap/inbox" {} body))))
(gitea-fed-test
"web inbox missing peer 400"
(dream-status (gf-postB (dream-json-encode {:activity {}})))
400)
(gitea-fed-test
"web inbox untrusted 403"
(dream-status (gf-postB (dream-json-encode {:activity {} :peer "forge-x"})))
403)
(gitea-fed-test
"web inbox trusted 200"
(dream-status (gf-postB (dream-json-encode {:activity {:actor "forge-a/user:alice" :detail {:body "via web"} :object "issue:bob/hub#1" :at 55 :tags (list "repo:bob/hub") :verb "comment"} :peer "forge-a"})))
200)
(gitea-fed-test
"web inbox materialized"
(len (get (gitea/issue-get gf-B "bob" "hub" 1) :comments))
2)
; ── mirrors (cross-instance repo follow) ─────────────────────────────
(define gf-m1 (gitea/mirror! gf-B "bob" "libmirror" "forge-a" "alice" "lib"))
(gitea-fed-test "mirror clones" (get gf-m1 :owner) "bob")
(gitea-fed-test
"mirror branch matches upstream"
(git/branch-get (gitea/repo-git gf-B "bob" "libmirror") "main")
(git/branch-get gf-gA "main"))
(gitea-fed-test
"mirror recorded"
(get (gitea/mirror-of gf-B "bob" "libmirror") :peer)
"forge-a")
(gitea-fed-test "mirrors list" (gitea/mirrors gf-B) (list "bob/libmirror"))
(git/checkout! gf-gA "main")
(git/add! gf-gA "more.txt" "more\n")
(define gf-c2 (git/commit! gf-gA {:message "c2" :time 6 :author "alice"}))
(gitea-fed-test
"mirror-sync pulls new commits"
(get (gitea/mirror-sync! gf-B "bob" "libmirror") :stored)
3)
(gitea-fed-test
"mirror advanced"
(git/branch-get (gitea/repo-git gf-B "bob" "libmirror") "main")
gf-c2)
(gitea/untrust! gf-B "forge-a")
(gitea-fed-test
"sync of untrusted peer refused"
(get (gitea/mirror-sync! gf-B "bob" "libmirror") :error)
"untrusted-peer")
(gitea-fed-test
"mirror of untrusted peer refused"
(get (gitea/mirror! gf-B "bob" "another" "forge-a" "alice" "lib") :error)
"untrusted-peer")
(gitea/trust! gf-B "forge-a")
(gitea-fed-test
"sync ok after re-trust"
(get (gitea/mirror-sync! gf-B "bob" "libmirror") :stored)
0)
(gitea-fed-test
"non-mirror sync refused"
(get (gitea/mirror-sync! gf-B "bob" "hub") :error)
"not-a-mirror")
; ── outbound delivery ────────────────────────────────────────────────
(gitea/trust! gf-A "forge-b")
(define gf-d1 (gitea/fed-deliver! gf-A))
; A's public activity so far: create-repo lib, open-issue lib#1,
; comment... none; private create/issue excluded
(gitea-fed-test
"deliver pushes public only"
(get gf-d1 :delivered)
2)
(gitea-fed-test
"deliver reaches trusted peers"
(get gf-d1 :peers)
(list "forge-b"))
(gitea-fed-test
"peer logged deliveries"
(len (gitea/fed-log gf-B))
8)
(gitea-fed-test
"delivered origin"
(get
(first
(filter
(fn (e) (= (get (get e :activity) :verb) "create-repo"))
(gitea/fed-log gf-B)))
:origin)
"forge-a")
(gitea-fed-test
"second deliver is a no-op"
(get (gitea/fed-deliver! gf-A) :delivered)
0)
(gitea/issue-comment!
gf-A
"alice"
"lib"
1
"alice"
"one more"
{:at 7})
(gitea-fed-test
"incremental deliver"
(get (gitea/fed-deliver! gf-A) :delivered)
1)
; ── federated timeline ───────────────────────────────────────────────
(define gf-tl (gitea/fed-timeline gf-B nil 100))
(gitea-fed-test
"fed timeline mixes local and foreign"
(>
(len (filter (fn (a) (= (get a :origin) "forge-a")) gf-tl))
0)
true)
(gitea-fed-test
"local activities carry no origin"
(>
(len
(filter
(fn
(a)
(and (nil? (get a :origin)) (= (get a :verb) "create-repo")))
gf-tl))
0)
true)
; ── actor docs over the wire ─────────────────────────────────────────
(define
gf-getA
(fn (target) (gf-appA (dream-request "GET" target {} ""))))
(gitea-fed-test
"web ap user 200"
(dream-status (gf-getA "/api/ap/users/alice"))
200)
(gitea-fed-test
"web ap user type"
(get
(dream-json-parse (dream-resp-body (gf-getA "/api/ap/users/alice")))
:type)
"Person")
(gitea-fed-test
"web ap user missing 404"
(dream-status (gf-getA "/api/ap/users/zeb"))
404)
(gitea-fed-test
"web ap repo 200"
(dream-status (gf-getA "/api/ap/repos/alice/lib"))
200)
(gitea-fed-test
"web ap private repo hidden"
(dream-status (gf-getA "/api/ap/repos/alice/hid"))
404)
(gitea-fed-test
"web outbox 200"
(dream-status (gf-getA "/api/ap/users/alice/outbox"))
200)
(gitea-fed-test
"web fed timeline 200"
(dream-status (gf-getA "/api/fed/timeline"))
200)