| … | … | @@ -0,0 +1,51 @@ |
| 1 | # favela-node |
| 2 | |
| 3 | The **full-node** that composes a Favela community's two substrates (see |
| 4 | [`favela-web/spec/lens-export.md`](../favela-web/spec/lens-export.md) §3): |
| 5 | |
| 6 | 1. the community's **co-owned atproto DID** — an [hyper-proto-peer](../hyper-proto-peer)-style identity |
| 7 | whose admins co-sign its repo ([atproto-room](../atproto-room): `StateBaseV3` admins + `Lease` + |
| 8 | `Signer` / `LabelRoom` = the **multiswarm-PDS pattern**). Owns/signs the community's |
| 9 | `app.bsky.feed.generator` + `app.bsky.labeler.service` records and its `com.atproto.label` labels; |
| 10 | IS the feed-generator + labeler service DID. |
| 11 | 2. the keyless **CommunityRoom** roster ([favela-community](../favela-community)) — the MEMBER audience |
| 12 | [favela-web](../favela-web) reads. The room's **mods == the DID's admins** (one attested set). |
| 13 | |
| 14 | A community is thus a **co-owned did:plc**: to bsky it's one account; behind it, N admins co-author with no |
| 15 | single point of key custody — exactly how a multi-device hyper-proto-peer presents one DID. |
| 16 | |
| 17 | ## API |
| 18 | |
| 19 | ```js |
| 20 | import { createCommunity, attestAdmin, bootCommunityAdmin, |
| 21 | publishFeedGenerator, publishLabelerService } from 'favela-node' |
| 22 | |
| 23 | // founder: mint the DID + boot its co-owned repo + create the linked keyless CommunityRoom |
| 24 | const c = await createCommunity({ rootMnemonic, founderDeviceMnemonic, name, roomStore, repoStorage, |
| 25 | handle, feedGenEndpoint, labelerEndpoint }) |
| 26 | // admit a co-admin (root attests its device → hand it a keyless bundle) |
| 27 | const bundle = await attestAdmin({ rootMnemonic, deviceMnemonic }) |
| 28 | const admin2 = await bootCommunityAdmin({ storage, did: c.did, signingKey: c.signingKey, |
| 29 | rootPub: c.rootPub, deviceMnemonic, deviceProof: bundle.deviceProof, |
| 30 | members: c.members }) |
| 31 | // publish the lens-export records into the co-owned repo (co-signed) |
| 32 | await publishFeedGenerator(c.repo, c.did, { rkey, displayName }) |
| 33 | await publishLabelerService(c.repo, { labelValues }) |
| 34 | ``` |
| 35 | |
| 36 | The community DID's genesis op declares `#bsky_fg` / `#atproto_labeler` services + a `#atproto_label` VM |
| 37 | (the endpoints point at the membrane that proxies the XRPC to favela-web). |
| 38 | |
| 39 | ## Notes |
| 40 | |
| 41 | - **Quorum (autobase 2f+1):** admin *indexers* must be an odd count ≥3 on independent boxes, or one |
| 42 | always-on anchor. Never N=2. Other admin devices default to plain writers. |
| 43 | - Key derivation is kept **identical to hpp `bootSwarmDevice`** so a community repo is interoperable with |
| 44 | the hpp / membrane stack. |
| 45 | |
| 46 | ## Test |
| 47 | |
| 48 | ```sh |
| 49 | bare test/publish.test.mjs # record shapes + genesis service entries (single-node) |
| 50 | bare test/community.test.mjs # two admins co-sign the community repo over a testnet + keyless roster read |
| 51 | ``` |