; 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)