Skip to content

Commit b0f31d3

Browse files
forge: AGENT.md — the decentralized-git-node architecture + CD/sync workflow
Portable architecture doc (no hosts/domains/keys): the four-layer model (storage/identity/truth/discovery+reaction), what a forge instance provides, the event-driven trust-verified Bitcoin-anchorable sync loop (sync-as-CI), the trust model (single-writer mark chain, trusted keys, requireAnchor), operating knobs, multi-forge composition, and the known walls (the core --git shadow #616, anchor cadence, api.podOf).
1 parent b138a25 commit b0f31d3

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

forge/AGENT.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# forge — a node in a decentralized git network
2+
3+
`forge/` is not just a git host with a UI. It is a **node** in a
4+
trust-minimized, decentralized git-collaboration network: your code lives on
5+
one (or many) forges, its canonical history is settled by Bitcoin, and updates
6+
propagate over Nostr. This file is the architecture and the workflow. For the
7+
route/API/config reference and the numbered findings, see
8+
[`README.md`](./README.md).
9+
10+
Everything here is portable — no host, domain, key, or path is specific to any
11+
deployment. Substitute your own.
12+
13+
## The layered model — one job per layer
14+
15+
The design deliberately separates four concerns that decentralized-git efforts
16+
usually tangle together:
17+
18+
| Layer | What does it | Why it's separable |
19+
|---|---|---|
20+
| **Storage** | the forge hosts the git objects (bare repos under `pluginDir`), serves them over smart-HTTP, and renders a GitHub-light UI + JSON API | many forges can hold the same repo (mirrors) |
21+
| **Identity** | owners are `did:nostr:<hex>` keys **or** pod WebIDs — no central account authority | a key/WebID is sovereign; nobody can revoke it |
22+
| **Truth** | a Blocktrails **mark chain** binds each repo state to a Bitcoin transaction | Bitcoin gives total order + finality + fork-resolution, so no forge has to be trusted for "which state is real" |
23+
| **Discovery / reaction** | the forge emits **NIP-34** Nostr events; subscribers react (pull, mirror, build, deploy) | Nostr is the trigger bus; anyone can subscribe, no central webhook |
24+
25+
A forge implements storage + truth + discovery. The *reaction* side (mirroring,
26+
CD) is any Nostr subscriber — e.g. a git-sync daemon.
27+
28+
## What a forge instance provides
29+
30+
- **Hosting** — push-to-create bare repos at `<prefix>/<owner>/<name>.git`;
31+
`<owner>` is a pod username or a 64-hex `did:nostr` pubkey. Anonymous clone
32+
by default; `config.privateRepos` gates reads to the owner.
33+
- **Browse** — server-rendered GitHub-light UI (file tree, blob, commits,
34+
green/red diffs, branches, tags, releases with `git archive` tarballs) and a
35+
matching **JSON API** under `<prefix>/api/...`. Zero deps, zero build.
36+
- **Forks & PRs**`git clone --local` forks, cross-fork compare, and real
37+
merges (`git merge-tree``commit-tree` → compare-and-swap `update-ref`).
38+
- **Issues & PRs as data you own** — an issue/comment body is a JSON-LD
39+
resource in the **author's own pod** (WAC-governed); the forge stores only a
40+
validated pointer in its index. Delete the resource from your pod and the
41+
thread shows "content removed by its author." Podless `did:nostr` agents get
42+
forge-hosted storage instead.
43+
- **WebID / Solid writes** — because DPoP (and NIP-98) proofs are bound to one
44+
request, the *browser* does the pod write via `authFetch` (fresh proof per
45+
request) and the forge validates the pointer is inside the author's own pod
46+
area. This is the WAC-correct, server-can't-forge-a-credential design.
47+
- **Bitcoin anchoring** — per-repo Blocktrails trails: enabling derives a
48+
genesis P2TR address (pure crypto, **no chain I/O by the server**); the owner
49+
reports the funding txo; the forge serves a verifier-compatible
50+
`blocktrails.json`. Derive-and-record on the server, broadcast-and-verify off
51+
it — the SSRF-free split.
52+
- **Nostr discovery (NIP-34)** — emits a **kind-30617** repo announcement
53+
(name, clone/web URLs, maintainers, and — if anchored — the `r`(blocktrails)
54+
+ `anchor`(chain, txid) tags) and a **kind-30618** repo-state event (the
55+
`refs/heads/*` → commit shas, plus the same anchor tags). Signed by a
56+
per-instance announce key (persisted, `0600`), or `config.announceKey`.
57+
58+
## The decentralized CD / sync workflow (the loop)
59+
60+
This is what ties it together — event-driven, trust-verified, Bitcoin-anchorable
61+
sync that generalizes to CI/CD:
62+
63+
```
64+
edit + commit on forge A
65+
66+
▼ (owner anchors, or calls the announce endpoint)
67+
forge advances the mark ──▶ Bitcoin (testnet/mainnet) ← the truth
68+
69+
▼ emits NIP-34 30617 + 30618 (carrying the new commit + the anchor txid)
70+
Nostr relay(s) ── your own JSS --nostr relay, and/or public relays
71+
72+
▼ (open subscription)
73+
a subscriber node (e.g. nostr-git-sync)
74+
│ 1. verify: event signed by a TRUSTED key? (reject otherwise)
75+
│ 2. optional: require the commit be Bitcoin-anchored (requireAnchor)
76+
│ 3. git pull from forge A's clone URL
77+
│ 4. run postSync ── the CI/CD step: mirror, build, deploy, test, …
78+
79+
the action happens — on any number of independent nodes
80+
```
81+
82+
Key properties:
83+
84+
- **No central authority.** The trigger is a Nostr event on a relay you chose
85+
(it can be your own); the trust is a key you chose; the truth is Bitcoin.
86+
This is `push → CI → deploy` with GitHub removed from every step.
87+
- **Sync ≈ CI.** `postSync` is an arbitrary command. Mirroring is one action;
88+
building and deploying a site (gh-pages-style) is another; running tests is
89+
another. Same primitive: *on a verified update event, do work.*
90+
- **Trust-minimized.** A subscriber accepts an update only if it's signed by a
91+
trusted maintainer key. With `requireAnchor`, it additionally refuses any
92+
state not committed on Bitcoin — so a compromised or lying mirror cannot
93+
advance the canonical history; only the holder of the trail key can, and
94+
Bitcoin is the arbiter. The mark chain is **single-writer, multi-reader**.
95+
96+
## Trust & verification model
97+
98+
- **Who can advance the canonical state:** only the holder of the repo's
99+
Blocktrails trail key (the maintainer). Mirrors replicate and verify; they
100+
cannot rewrite the truth.
101+
- **Who a subscriber accepts events from:** its `trusted` list of `did:nostr` /
102+
npub keys. An event from any other key is ignored.
103+
- **What "verified" means on-chain:** the mark chain is an unbroken sequence of
104+
Bitcoin UTXO spends; whichever mirror's state matches it is canonical. Anyone
105+
can check independently from `blocktrails.json` — no forge is trusted for it.
106+
- **Who signs the announcements:** by default the **forge instance key** (an
107+
honest "this node hosts these clone URLs, anchored here" claim). The
108+
`maintainers` tag names the real maintainer key, so a maintainer-signed
109+
announcement can supersede it. The principled end state is client-side
110+
signing (NIP-07 / a login widget) with the forge as a pure relay.
111+
112+
## Operating knobs (all optional, all in the entry `config`)
113+
114+
- `privateRepos` — gate clone/read to the owner (default public).
115+
- `announceRelays: [wss://…]` — enable NIP-34 emission (empty = off).
116+
- `announceKey` — hex privkey for the forge's Nostr identity (else generated
117+
and persisted in `pluginDir`).
118+
- `chain` — Blocktrails chain (default a testnet); mainnet requires
119+
`allowMainnet: true`.
120+
- `cspConnect: [origin, …]` — extra `connect-src` origins for a login widget
121+
that talks to external identity providers.
122+
- `pushTokenTtl`, `gitHttpBackend` — see `README.md`.
123+
124+
## Composing a multi-forge deployment
125+
126+
- **Mirror:** point a second forge (or a sync daemon) at the first's clone URL;
127+
verify against `blocktrails.json`. If a host disappears or censors, others
128+
hold the repo, and Bitcoin says which copy is honest.
129+
- **Announce with mirror URLs:** a maintainer-signed 30617 can list several
130+
`clone` URLs — one repo, many mirrors — which a NIP-34 viewer shows as one
131+
entry.
132+
- **CD fan-out:** many subscribers can watch the same repo and each run their
133+
own `postSync` (deploy to different edges, run different builds).
134+
135+
## Known walls / upstream
136+
137+
- **Core `--git` shadows plugin-owned git paths.** A server that also runs core
138+
git-over-HTTP intercepts `<prefix>/…/*.git` smart-HTTP requests by a global
139+
substring match, so the forge can't serve its own git alongside core `--git`.
140+
Filed upstream; the fix is for core's git guard to skip plugin-owned
141+
prefixes. A one-line local shim (exclude the forge prefix) is the interim.
142+
- **Anchor granularity is a policy choice.** You cannot anchor every commit
143+
(cost, block time); the mark is a periodic checkpoint (per release, per N
144+
commits, a rolling mark) with git content-addressing between checkpoints.
145+
- **`api.podOf(agent)`** would let a `did:nostr` key that owns a pod write its
146+
issue/comment bodies into that pod instead of forge-hosted storage. Today a
147+
bare key has nowhere of its own to write.

0 commit comments

Comments
 (0)