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>
3.5 KiB
3.5 KiB
sx-gitea — a federated git forge in plain SX
A git forge built by composing the x-on-sx subsystems: every phase wires one more substrate onto the forge. No third-party dependencies — the whole stack is SX on the OCaml kernel.
Run the suite: bash lib/gitea/conformance.sh (per-suite scores in
scoreboard.md). Suites are independent sx_server sessions; heavyweight
substrates (Smalltalk/content, Scheme/flow, APL/feed, Haskell/search) load
only for the suites that need them.
Composition map
| Phase | Module | Built on |
|---|---|---|
| 1 repo | repo.sx |
sx-git (lib/git, native-CID object store), persist kv |
| 2 access | access.sx |
acl (datalog): repo role groups, collaborators, org teams; bearer tokens |
| 3 wire | wire.sx |
git-style smart HTTP: pkt-line framing, upload/receive-pack, CID-verified packs; client (clone!/fetch!/push!) drives any dream app fn |
| 4 issues | issues.sx |
content (Smalltalk): Markdown bodies as block documents; relations (datalog): derived issue graph |
| 5 pr | pr.sx |
sx-git merge-base diffs + 3-way merge; flow (Scheme): durable open→approval→merge lifecycle; merge queue |
| 6 activity | activity.sx |
feed (APL): timelines/dashboard; events (flow): durable at-least-once notifications |
| 7 search | search.sx |
search (Haskell): tf-idf ranked code/issue/PR search, batched evaluations |
| 8 fed | fed.sx |
ForgeFed: AP actors, trust-gated inbox with provenance + materialized federated issues/PRs, mirrors over the wire client, cursor-based delivery |
| web | web.sx |
dream: routes, auth gating (401/403/404-hides-private), route-pack registry |
Architectural rules of thumb
- The kv store is the source of truth. Owners, repo records, issues,
PRs, collaborators, teams, tokens, follows, trust, mirrors — all plain
dicts under
gitea/...keys on one persist backend per forge. Deleting a repo is a prefix purge (no ghost state on recreate). - Derived, not maintained. The acl database and the relations graph are derived from kv state and rebuilt when the derived facts change (cached in the forge handle) — deletions can never dangle.
- Instrument in the runtime. Activity logging wraps the mutation
verbs by redefinition (
gitea/base-*!+ wrapper), so every caller emits activity with zero call-site edits. - Everything is testable without sockets. A forge is a value over a
persist/mem-backend;gitea/appis a pure request→response fn; the wire client federates two in-memory forges directly. - Trust is re-checked, never cached. Federation operations (inbox, mirror sync, delivery) consult the trust set at use time.
Per-repo git stores
Each repo's objects/refs live in their own git/repo-named namespace
forge/<owner>/<name> — identical content still shares CIDs, but repos
cannot see each other's objects. All ref moves go through ref-cas!;
concurrent pushes surface as stale/non-fast-forward per-ref statuses.
Known limits (deliberate, documented)
- Wire packs carry one object per pkt line (~64KB); side-band chunking
is a future extension (
gitea/pkt-fits?reports it). SHA-1/packfile byte compat for stock git clients lives inlib/git/{export,import}.sxand is not yet wired into the HTTP endpoints. - Inbox activities are trust-gated but not signature-verified.
- Reopening a PR restarts its lifecycle flow (a cancelled flow cannot resume); reviews survive.
- Issue web close/reopen does not emit activity (no actor at the core
call sites for
issue-close!).