# 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/app` is 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 in `lib/git/{export,import}.sx`
  and 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!`).
