OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social commits / 49da021

docs: retiring favela-web — who owns each of its jobs, and the order
favela-web hit 20.3 GB on stortron for the third time on 2026-09-29 and is retired rather than maintained. Maps each live job to favela-peer, favela-bridge, hpp, atproto-bridge or favela-surface; the community stays a favela-peer room with no new op types (_hs:room-meta, _hs:mod-label, reader-side role check); labels reuse atproto-room LabelRoom on hpp's election; over-length bsky posts truncate with a link; loser-club migrates off favela-community; a URI-for-URI parity gate precedes the switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49da021cf795afbe5b9e6bad88d397bee6ba95c4
by plankatron <fleeky@gmail.com> · 2026-09-30T01:35:01.000Z

Changed files (1) +251 -0

docs/FAVELA-WEB-RETIREMENT.md +251 -0
……@@ -0,0 +1,251 @@
1# Retiring favela-web
2
3**Date:** 2026-09-29 · **Status:** spec, nothing built · **Follows:** #61, #62 §3, #73, `SOCIAL-MODEL.md` §6.7
4
5favela-web is the July-era atproto appview. It is being retired, not maintained: on 2026-09-29 it
6reached 20.3 GB on stortron for the third time, and the fix shipped (0.3.92) needed three releases
7because its dependency closure is fragile. This document says where each of its jobs goes, what has
8to be built, and in what order.
9
10The direction is the one `favela-identity/spec/identity.md` §6.3.1 already draws: favela-peer →
11bridge:bsky (holds the key, signs) → keyless atproto-bridge. favela-web does not appear in it.
12
13---
14
15## 1. What favela-web does in production today
16
17Read from the live process on stortron (`/proc/<pid>/environ`), 2026-09-29.
18
19| # | Job | Live config | Fate |
20|---|---|---|---|
21| 1 | Public SSR site at `favela.7rnx.net` — an atproto appview over wj62 + e2jev | `FAVELA_ROSTER`, `FAVELA_PORT=4041` | → favela-surface, public read-only mode (§6) |
22| 2 | The `loser-club` bsky custom feed (`getFeedSkeleton`, `describeFeedGenerator`) | `FAVELA_COMMUNITY_ROOMS=loser-club=91ce…` | → favela-bridge computes, hpp serves (§4, §5) |
23| 3 | Inbound replies/likes from bsky (spacedust + constellation), retained ≤ 256 MB | `FAVELA_SPACEDUST=1`, `FAVELA_RETENTION_BUDGET` | → the bridge's `collect` (#73 §3); stops at retirement |
24| 4 | Posting to wj62's repo through hpp's control socket | `FAVELA_HPP_SOCK` | Already superseded: favela-bridge projects favela-peer posts |
25| 5 | Reading ActivityPub followers for display | `FAVELA_AP_BRIDGE` | Display only; stops, nothing lost |
26| — | Labeler | **not live**: `FAVELA_LABELER_SEED` unset, `queryLabels` 404, no labeler service record | Built fresh (§5.2); nothing to migrate |
27
28atproto-bridge currently depends on favela-web — `FAVELA_WEB_UPSTREAM=http://127.0.0.1:4041`,
29`pds/lib/favela-proxy.js:9-13`. **That is the wrong direction** and is removed by §5.3.
30
31---
32
33## 2. Ownership
34
35| Concern | Owner | Why there |
36|---|---|---|
37| Posts, communities, roles, curation, moderation decisions | **favela-peer** | the canonical records (`record-vocabulary.md` §0) |
38| favela record → atproto (posts, skeletons, label intents) | **favela-bridge** | the only component allowed to know both sides (#61 §10 boundary). Owns `tidFor(name)` |
39| Keys; signing commits **and labels** | **hyper-proto-peer** | holds wqnwul's key, which IS its PLC `#atproto_label` key (derived did:key matches) |
40| Serving to bsky, keyless | **atproto-bridge** | reaches hpp over P2P only; no HTTP upstreams |
41| Pages | **favela-surface** | renderer over favela-peer's socket |
42
43Retired after parity (§8): **favela-web**, and the old community stack — **favela-community**
44(`CommunityRoom`, hyper-rooms on autobase, DID-keyed) and **favela-node**.
45
46⛔ **The record→at:// mapping is a function, not state.** `favela-bridge/lib/tid.js:14-17`: the
47bridge "holds no record→rkey map". An at:// URI is `at://<designated DID>/app.bsky.feed.post/` +
48`tidFor(name)`. It is one-way (`tid.js:148-151`). Nothing in this spec stores it.
49
50---
51
52## 3. The community is a favela-peer room — no new op types
53
54`favela-peer/lib/room.js` implements keet's contract (#62 §3; commit `58f4fed`), and
55`5920678`: *"a community is a room, not a type"*. Its op set is closed and its config has five keys
56(`room.js:59-61`); **anything else is a message by convention**. This spec adds no op.
57
58### 3.1 `_hs:room-meta` carries the community's projection
59
60The pinned, admin-authored `_hs:room-meta` message (`ROOMS-ARE-THE-COMPUTER.md:42,51,69-70`)
61already has `type`, `discoverable` and `bridgedTo`. It gains:
62
63```json
64{"_hs":"room-meta","type":"community","public":true,
65 "bridgedTo":[{"platform":"bsky","did":"did:plc:wqnwulql7ndcz6xmtvaovl6u","feed":"loser-club"}],
66 "curation":{"curator":"roster","params":{}}}
67```
68
69- **`public`** — the room's content may be projected and rendered to non-members. Rooms carry no
70 visibility today ("visibility is a place", `room.js:29`), so without this flag nothing in a room is
71 public. `record-vocabulary.md` §3.1: only public rooms get an at:// projection.
72- **`bridgedTo[bsky]`** — the community DID and feed rkey. This replaces favela-community's
73 `meta:did`.
74- **`curation`** — favela-web's contract (`curators.js:35-47`: `roster` | `subject`), moved verbatim.
75
76### 3.2 ⛔ The role check is the reader's, and it is mandatory
77
78A message is gated at **peer** in apply (`room.js:329`), not at admin or moderator. So every reader
79of a conventional message must check the author's role itself, via the room's existing
80`roleOf`/`members` (`room.js:477-485`):
81
82- `_hs:room-meta` is honoured only from an **admin**; the newest admin-authored one wins.
83- `_hs:mod-label` (§5.2) is honoured only from a **moderator or admin** of the moderation room.
84
85One shared function does this check, and favela-bridge calls favela-peer for it — never a local
86re-derivation of roles.
87
88---
89
90## 4. favela-bridge gains a room pass
91
92Today it projects only the node's own feed (`peer.js:1006-1023`; nothing calls `room-*`).
93
94For each room this node belongs to whose `_hs:room-meta` has `public: true` and a
95`bridgedTo[bsky]` entry:
96
971. **Roster.** Members with role ≥ peer. Each member is an identity root; its atproto DID is that
98 identity's **designated credential** (`identity.md`). Here: wj62 = tabertron's identity
99 (feed `5271cdd4…`), e2jev = ashatron's (feed `13ea8136…`) — confirmed by the operator.
1002. **Skeleton.** Apply `curation` over the members' posts the node holds (favela-peer replicates
101 followed feeds, so no index is needed), newest first, and emit
102 `at://<member DID>/app.bsky.feed.post/<tidFor(name)>`. Filter exactly as `feedgen.js:114-135` does
103 today: `app.bsky.feed.post` only, valid TID rkeys only — one bad rkey poisons the whole response.
1043. **Hand to hpp** with `put-feed-skeleton` (§5.1) for `bridgedTo.did`.
1054. **Labels.** Read `_hs:mod-label` messages (§5.2), role-checked (§3.2), and hand each to hpp as a
106 `label-intent`.
107
108⚠ The deployed favela-bridge (0.0.12) predates its own source: edges (likes, replies, reposts) are
109in `lib/project.js` / `plan-edge.js` since `2e8ef98` but were never version-bumped, so no desktop
110node projects them. Bump and deploy before adding this pass.
111
112### 4.1 Over-length posts: truncate with a link (decided 2026-09-29)
113
114`design.md` §7 (`:192`) says over-300-grapheme posts are truncated with a permalink; the code
115withholds them (`plan.js:174`). **The spec wins.** Truncate at a sentence boundary, append `…`, add
116a link facet to the post's page in the public read-only surface (§6).
117
118Rejected: splitting into a thread. It breaks one record → one at:// URI (parts need derived rkeys
119and every edge must pick a part), an edit that changes the part count orphans bsky replies on removed
120parts, and markdown does not survive 300-grapheme cuts. ActivityPub has no such limit and keeps the
121full text.
122
123---
124
125## 5. hpp and atproto-bridge
126
127### 5.1 Feed skeleton
128
129New verbs on **`atproto-room/lib/serve-membrane.js`** — the responder atproto-bridge actually
130reaches (`device-node.js:637`). ⛔ Not `serve-device.js`; `serve-membrane.js:333-336` warns about
131exactly that two-surfaces trap.
132
133| verb | caller | gate |
134|---|---|---|
135| `put-feed-skeleton {did, rkey, uris[]}` | favela-bridge, over hpp's control socket | local UDS only |
136| `get-feed-skeleton {did, rkey, limit, cursor}` | atproto-bridge | public, like `get-repo-car` |
137| `describe-feed-generator {did}` | atproto-bridge | public |
138
139The skeleton is state in the DID's StateBase, so every co-owner of wqnwul serves the same one.
140
141⚠ Most verbs on that channel require `canLogin` (`jwtSecret && appPasswordHash`,
142`sessions.js:14`); wqnwul has neither on stortron or tabertron. These must be ungated reads.
143
144### 5.2 Labels — `LabelRoom`, wired to hpp's election
145
146`SOCIAL-MODEL.md` §6.7: a moderation signal is an **edge** `(moderator, flag, target)` with a
147reason, written as a message in a **moderation room**; subscribing to a community's signals is
148joining that room; `flag` → a signed atproto label, → fediverse **nothing** (`loss[]`).
149
150- Decision: a `_hs:mod-label` message `{target, val, neg?, reason}` in the community's moderation
151 room, from a moderator (§3.2).
152- Signing: `atproto-room/lib/label-room.js` already does the rest — intents in the DID's StateBase,
153 one holder signs with the `#atproto_label` key (`label-signer.js`, RFC6979-deterministic so a
154 handoff never forks), signed labels in the shared `__label__` log, `projectTo(feedCore)` appends
155 them to a hypercore.
156- ⛔ **`LabelRoom` elects its signer with `Lease`** (`label-room.js`). hpp replaced the lease with
157 a presence-based election after outages (atproto-room skill). Wire `tick()` to hpp's election, not
158 to `Lease`, or the two elections can disagree about who signs.
159- New verb `label-intent {did, uri, val, neg}` (local UDS only) → `LabelRoom.label/negate`.
160- ⚠ The label key IS the repo key (`#atproto_label` == `#atproto` in wqnwul's PLC doc), so labels
161 inherit the key-copied-to-every-co-owner posture that `identity.md` §6.1.1 wants replaced. Not made
162 worse; not fixed here.
163
164**Not urgent:** no labels exist today. Build it when a community needs moderation.
165
166### 5.3 atproto-bridge: P2P only
167
168- `getFeedSkeleton`, `describeFeedGenerator`, `queryLabels` → `DidPeer` calls to the §5.1/§5.2 verbs
169 over the existing `hyper-proto-peer/rpc/v1` channel (`atproto-did-peer/lib/did-peer.js`).
170- `subscribeLabels` → **replicate the label hypercore and frame it locally**, the same pattern as
171 `subscribeRepos` from the firehose core. protomux-rpc is request/response only.
172- Delete `FAVELA_WEB_UPSTREAM`, `favela-proxy.js`, and the WS upstream at `server-bare.js:431-444`.
173 The membrane's did:web doc advertises `#bsky_fg` unconditionally for DIDs that have a feed.
174
175---
176
177## 6. favela-surface: public read-only mode (decided 2026-09-29)
178
179A second listener, or a mode flag, that serves **GET only** and renders **only public content**:
180
181- The node's own feed and the feeds it follows. Feeds are public by key (#61 §8: "unlisted is not
182 private"), and favela-bridge already projects the own feed publicly.
183- Rooms whose `_hs:room-meta` says `public: true` — the community pages (`/r/<key>`).
184- `/blob`, `/avatar`, `/static` for the above.
185
186Never rendered: documents (keyed, private by definition), non-public rooms, DMs, knocks,
187notifications, settings, `/api/*`, and every POST. Unauthenticated requests to any of those → 404,
188not a login redirect — the public site should not advertise that there is more.
189
190Each post gets a stable public URL — the permalink §4.1 links to.
191
192The proxy entry `favela` (`~/.config/7rnx-proxy/registry.json`, stortron:4041) moves to this
193listener. favela-surface binds `127.0.0.1` today; the public listener needs `FAVELA_SURFACE_HOST`
194reachable by the proxy.
195
196---
197
198## 7. Migrating loser-club
199
200loser-club is a favela-community `CommunityRoom` today (`favela-community/lib/community-room.js`),
201DID-keyed, on autobase. Nothing links it to favela-peer. The migration:
202
203| old (favela-community) | new (favela-peer room) |
204|---|---|
205| `member:<did>` — wj62, e2jev | members = the identity roots behind them (tabertron, ashatron) |
206| roles `owner` / `writer` / `mirror` | owner → **admin**; writer → **peer**; mirror → none (a replicator, not a member) |
207| `ban`/`unban` | `remove-member` (moderator); a ban list, if kept, is a moderation-room message |
208| `set-curation` | `_hs:room-meta.curation` |
209| `meta:did` = wqnwul | `_hs:room-meta.bridgedTo[bsky].did` |
210
211The community DID does not change, so the bsky feed URI
212`at://did:plc:wqnwul…/app.bsky.feed.generator/loser-club` survives the move.
213
214⚠ Read the live old room's roster, bans and curation **before** migrating — this table is from the
215schema, not from the data.
216
217---
218
219## 8. Order
220
221Each step needs the one before it.
222
2231. favela-bridge version bump — ship the unshipped edge projection.
2242. favela-peer: `_hs:room-meta` public/bridgedTo/curation, and the shared role-check function (§3).
2253. favela-surface public read-only mode (§6). Can run in parallel with 4–5.
2264. favela-bridge room pass (§4) and truncate-with-link (§4.1).
2275. hpp feed verbs (§5.1); atproto-bridge P2P routing (§5.3).
2286. Create the loser-club favela-peer room and migrate it (§7).
2297. **Parity gate:** the new `getFeedSkeleton` for loser-club returns the same URIs as favela-web's
230 live one, URI for URI, before any switch.
2318. Switch atproto-bridge off `FAVELA_WEB_UPSTREAM`; repoint `favela.7rnx.net`.
2329. Retire: stop + disable autostart for favela-web on stortron, tabertron and ashatron; then
233 favela-community / favela-node. **Data stays on disk.**
23410. Labels (§5.2) when moderation is needed.
235
236---
237
238## 9. Open
239
2401. **Designation lookup from another node.** Step 4.1 needs "identity root → designated DID" for
241 *other* members (e2jev from tabertron's bridge). Whether favela-identity answers that for a
242 non-local identity is unverified.
2432. **Which node runs the room pass.** favela-bridge runs on tabertron and ashatron, not stortron.
244 hpp co-hosts wqnwul on tabertron (creator) and stortron (joiner). Running the pass on one node is
245 enough, because the skeleton lands in the shared StateBase.
2463. **Room posts vs member posts.** `curation: roster` reproduces today's feed — everything members
247 post anywhere. A feed of only what is posted *in* the community room needs room posts projected
248 into their authors' repos, which is #61 §3 item 8 (gathering N rooms into one signed repo). Out of
249 scope here.
2504. **Spec/code drift, settled here:** §4.1 (truncate). Also open: `record-vocabulary.md` §3.1 says
251 the rkey pairing is derivable both ways; `tidFor` is one-way. One of them changes.