| … | … | @@ -160,6 +160,37 @@ side, tuples instead of per-record pointers. |
| 160 | 160 | - **Retention of foreign RECORDS** (replies, quotes). That is Tier B2 and already specified; this is |
| 161 | 161 | only the count-bearing half §7.6 excluded. |
| 162 | 162 | |
| 163 | ## 7.5 Prior art — what atproto actually does, measured |
| 164 | |
| 165 | ⛔ **THERE IS NO PDS TO ASK.** A like is a record in the **liker's** repo on the **liker's** PDS. The |
| 166 | post author's PDS has no idea it happened, so "ask the PDS on every view" was never an option — the |
| 167 | data is scattered across every PDS in the network. |
| 168 | |
| 169 | atproto's answer is the **AppView**: a service that drinks the entire firehose from a relay and builds |
| 170 | a global index. Both the count and the actor list come from there, not from any repo. Measured |
| 171 | 2026-09-15 against the live network: |
| 172 | |
| 173 | ``` |
| 174 | app.bsky.feed.getPosts → likeCount 0 · repostCount 1 · replyCount 1 served by public.api.bsky.app |
| 175 | app.bsky.feed.getLikes → a hydrated list of actors served by public.api.bsky.app |
| 176 | ``` |
| 177 | |
| 178 | ★ **And our own PDS proves the point by failing to be one.** `getLikes` against `pds.7rnx.net` |
| 179 | returns **200 with a byte-identical body** to the AppView's — 688 bytes both — because |
| 180 | atproto-bridge proxies unknown `app.bsky.*` methods upstream. Even our own infrastructure cannot |
| 181 | answer the question locally. (What our repo DOES hold is the likes we *made*: `listRecords` on |
| 182 | `app.bsky.feed.like` returns them, because those are ours to author.) |
| 183 | |
| 184 | ⇒ So the answer to "store, or ask every view?" is **store — enormously.** atproto pushes the whole |
| 185 | aggregation problem into one index that must hold the entire network, which is why an independent |
| 186 | backlink index (Constellation) had to be built at all: the only other option is running an AppView. |
| 187 | |
| 188 | ⚠ **That is the same necessity at a different scope, and the scope is the whole difference.** This |
| 189 | design indexes one hop from our own records rather than the world, which is why it fits in 0.50 MB |
| 190 | instead of a datacentre. It also names the dependency we are currently paying: |
| 191 | `hyper-proto-peer/lib/notifications.js` proxies `listNotifications` to bsky's AppView — a favela user |
| 192 | learns somebody interacted with them by asking Bluesky's servers. |
| 193 | |
| 163 | 194 | ## 8. Open |
| 164 | 195 | |
| 165 | 196 | 1. **Segment size N.** Tail-rewrite cost against pointer size. Wants a measurement of real inbound |
| … | … | @@ -170,6 +201,9 @@ side, tuples instead of per-record pointers. |
| 170 | 201 | currently interns per-subject. |
| 171 | 202 | 3. **Un-like.** A tombstone tuple, or rewrite the tail segment? Sealed segments say tombstone; §4.3 of |
| 172 | 203 | `interaction.md` already says reversal is a tombstone everywhere else. |
| 173 | | 4. **Who writes it.** The ingest runs where a socket can stay up. That is not the phone, and it is |
| 174 | | currently favela-web, which the android boundary test forbids the handset from importing. The |
| 175 | | writer's home is an open placement question and should be settled before this is built. |
| 204 | 4. ~~**Who writes it.**~~ **CLOSED 2026-09-15 (operator): the writer is `ap-bridge`. favela-web is |
| 205 | deprecated.** It already holds the properties the ingest needs — a socket that stays up, a swarm |
| 206 | membership, N admitted peers, and a registration that binds a peer's identity root to a feed key. |
| 207 | The ingest code currently lives in favela-web (`spacedust.js`, `link-drain.js`, `retention.js`) and |
| 208 | MOVES rather than gets rewritten; `link-drain.js` is already pure + injected, so the sink and the |
| 209 | store are parameters at the new home. |