OverviewFilesCommitsIssuesDocsReleasesPeers

favela-community commits / 2d0e5d6

favela-community 0.1.3: bind the community's co-owned atproto DID (meta:did)
CommunityRoom.create({ did }) + communityDid() + setDid() — the room records the community's atproto DID, the CO-OWNED identity that owns/signs the lens-export records/labels (spec/lens-export.md §3). This is the linkage (§3.4): the room's MODS are the DID's admin/signer set (StateBase device-writers) — one attested group, surfaced two ways. did:did is a plain meta op, so it linearizes + replicates to keyless readers; setDid is write-guarded. Test: keyless read of the DID + write refusal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2d0e5d6fbc3b84a5e7963b22456d33f41625f585
by plankatron <fleeky@gmail.com> · 2026-07-15T14:00:24.000Z

Changed files (3) +33 -3

lib/community-room.js +17 -2
……@@ -23,12 +23,14 @@ export class CommunityRoom {
2323 constructor (room) { this._room = room }
2424
2525 // OWNER — create a fresh community room. This peer is the genesis writer AND the sole indexer (N=1, the
26 // safe default per the autobase quorum rule — never auto-grow to N=2). Optionally set the name.
27 static async create (corestore, { namespace = 'community', name = null } = {}) {
26 // safe default per the autobase quorum rule — never auto-grow to N=2). Optionally set the name and the
27 // community's atproto DID (see communityDid below).
28 static async create (corestore, { namespace = 'community', name = null, did = null } = {}) {
2829 const room = new Room(corestore, { schema: communitySchema, bootstrapKey: null, namespace })
2930 await room.ready()
3031 const cr = new CommunityRoom(room)
3132 if (name) await cr.setMeta('name', name)
33 if (did) await cr.setMeta('did', did)
3234 return cr
3335 }
3436
……@@ -87,6 +89,19 @@ export class CommunityRoom {
8789
8890 async isBanned (did) { await this._room.base.update(); return !!(await this._room.view.get(BANNED + did)) }
8991
92 // The community's atproto DID (meta:did) — the CO-OWNED identity that owns/signs this community's
93 // app.bsky.feed.generator + app.bsky.labeler.service records and its com.atproto.label labels (the
94 // lens export, spec/lens-export.md §3). It is a SEPARATE substrate from this room: the DID is a
95 // co-owned hyper-proto-peer (StateBase admins + Lease + Signer/LabelRoom = the multiswarm-PDS pattern);
96 // this room is the keyless MEMBER roster. The room's MODS are the DID's admin/signer set — one attested
97 // group surfaced two ways (mods here; StateBase device-writers there). Returns null until set at create
98 // or via setDid. Keyless-safe (a plain meta read).
99 async communityDid () { return (await this.meta()).did || null }
100
101 // Owner/mod: bind (or rebind) the community's atproto DID. Written like any meta op, so it linearizes +
102 // replicates to keyless readers. Set once at create normally; this is for late-binding / migration.
103 async setDid (did) { this._assertWritable('setDid'); this._assertDid(did); await this.setMeta('did', did) }
104
90105 async meta () {
91106 await this._room.base.update()
92107 const m = {}
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-community",
3 "version": "0.1.2",
3 "version": "0.1.3",
44 "description": "Co-owned, plaintext community rooms for Favela — membership + moderation on a hyper-rooms Autobase. Read KEYLESSLY by the gateway (favela-web); WRITTEN by mods (hpp / full-node / CLI). One community protocol, shared across surfaces.",
55 "type": "module",
66 "main": "index.js",
test/community-room.test.mjs +15 -0
……@@ -67,6 +67,21 @@ assert(await until(async () => { const m = await reader.members(); return m.incl
6767const final = (await owner.members()).sort()
6868assert(final.length === 2 && final.includes(C) && final.includes(D), 'final roster = {C,D} (A banned by mod, B banned by owner)')
6969
70// ── COMMUNITY DID linkage: the room binds its co-owned atproto DID (meta:did), keyless-readable ──
71const DID_COMM = 'did:plc:community222222222222222'
72const didStore = new Corestore('data/cc-did-' + stamp)
73const didOwner = await CommunityRoom.create(didStore, { name: 'DID Community', did: DID_COMM })
74assert((await didOwner.communityDid()) === DID_COMM, 'community DID set at create is readable via communityDid()')
75const didReaderStore = new Corestore('data/cc-didr-' + stamp)
76const didReader = await CommunityRoom.openReadonly(didReaderStore, didOwner.key)
77const unwireD = wire(didOwner, didReader)
78assert(await until(async () => (await didReader.communityDid()) === DID_COMM), 'a KEYLESS reader replicates + reads the community DID (meta:did)')
79let didThrew = false
80try { await didReader.setDid('did:plc:evil22222222222222222222') } catch { didThrew = true }
81assert(didThrew, 'keyless reader cannot setDid (write-guarded)')
82unwireD(); await didOwner.close(); await didReader.close()
83try { await didStore.close(); await didReaderStore.close() } catch {}
84
7085// ── ROSTER WATCHER: a member added AFTER watch starts reaches the consumer with no re-open ──
7186// This is favela-web's path: index members added to a room LATER without a restart (DESIGN §7). We drive
7287// replication by hand (no swarm) and assert the watcher fires a baseline AND a delta.