## Today — Forgejo private, public repos mirrored to `knot1.tangled.sh` Forgejo holds canonical state. A small bridge translates Forgejo webhook events into AT Proto XRPC calls and signed git pushes against the hosted knot. Forgejo image untouched. ```mermaid flowchart LR user(["author"]) -->|git push| forgejo subgraph private["my forge — private"] forgejo["Forgejo
(authoritative)"] webhook[/"webhook config
per repo or org"/] forgejo --- webhook end webhook -->|push / create / delete
HMAC-signed| bridge subgraph bridgeBox["forgejo-knot-bridge — small Go service"] bridge["event handler
user → DID mapping"] keystore[("signing keys
per bridged DID")] statedb[("bridge.db
repo→DID,
last-mirrored ref")] bridge --- keystore bridge --- statedb end bridge -->|sh.tangled.repo.create
Service-Auth JWT| knot bridge -->|git push --mirror
Service-Auth Bearer| knot subgraph hosted["knot1.tangled.sh — hosted, third-party"] knot["knotserver"] appview["tangled.sh appview
(consumes firehose)"] knot -.->|sh.tangled.knot.subscribeRepos
ws firehose| appview end plc[("plc.directory")] -.->|DID document
verifies signing key| knot ``` The bridge needs three things from the knot side: knot URL + DID, `server:member` role for the DIDs being published as, and signing keys for those DIDs. From Forgejo it needs a webhook subscription, public-repo read access, and a Forgejo-user → DID mapping. --- ## Tomorrow — Forgejo *is* the knot Instead of bridging Forgejo *into* a separate knotserver, teach Forgejo to *be* a knot. Same Forgejo binary, same `codeberg.org` hostname, with a module that exposes the knot lexicon surface backed by Forgejo's existing repo storage. ```mermaid flowchart LR subgraph today["Today (above)"] f1["Forgejo"] -->|webhook| b1["bridge
(separate service)"] b1 -->|XRPC + git push| k1["knotserver
(separate process)"] end subgraph proposed["Forgejo as a knot"] forgejo["Forgejo
+ knot-frontend module
(or external sidecar)"] users(["users
(unchanged web/SSH/REST)"]) --> forgejo forgejo -.->|"/xrpc/sh.tangled.*
/events firehose
Service-Auth on writes"| world["AT Protocol clients
(tangled.sh appview,
knotmirror, ...)"] end today -.->|same protocol surface,
different deployment| proposed ``` The mapping is mostly mechanical — `sh.tangled.repo.{create,delete,tree,log,branches,diff,...}` lands on Forgejo's existing repo APIs in XRPC envelopes; `sh.tangled.knot.listKeys` reuses Forgejo's user SSH keys; identity reconciles via a `did` field on Forgejo's user table. No-trivial part is `sh.tangled.knot.subscribeRepos` — an atproto-style sequenced WebSocket subscription with `cursor` resume and `ConsumerTooSlow` semantics. Forgejo's existing internal event hooks (the same ones driving its ActivityPub outbound delivery) are the natural source; you translate ref-update events into the lexicon's wire format and replay from a `tangled_firehose_events` table on reconnect.