| … | … | @@ -0,0 +1,82 @@ |
| 1 | # Consumer extraction — session plan |
| 2 | |
| 3 | Repoint hpp / atproto-room / atproto-bridge / favela onto `atproto-base`, retire the 3× duplicate |
| 4 | consumer primitive, and (separately) fix favela's per-node-appview divergence with a shared feed-room. |
| 5 | This TOUCHES LIVE ACCOUNTS (wj62 `p2p.7rnx.net`, loser-club, ashatron) — **offline-first, staged, with |
| 6 | a rehearsed migration.** Do NOT cut a live DID over on a clean-testnet's word (every autobase failure |
| 7 | this session was flaky-link/concurrency-specific — see the session memories). |
| 8 | |
| 9 | ## Where atproto-base stands (start point) |
| 10 | 19 tests / 93 asserts green: deterministic projection · record engine · admission · finalized-view · |
| 11 | indexer topology (keet-correct) · revocation · firehose (`#commit` frames + `subscribeRepos`). But it |
| 12 | was built with SIMPLIFICATIONS that must close before it can replace StateBaseV3 for a REAL co-owned DID. |
| 13 | |
| 14 | ## PHASE 0 — close the atproto-base gaps (all OFFLINE, extend the test suite). BLOCKING. |
| 15 | Grounded in atproto-room/lib/state-base.js (the god-class being replaced): |
| 16 | 1. **Deterministic base key.** StateBaseV3 derives the autobase base key from a members-only |
| 17 | `roomSeed` (`deriveGsKeyPair(roomSeed, GS_NAMESPACE)` → `deriveBaseKey`) so every co-owner opens the |
| 18 | SAME base. atproto-base currently uses corestore-derived keys + explicit `bootstrap`. Port the |
| 19 | deterministic derivation + the `forceCreator`/`forceNonCreator` resync postures (needed for the |
| 20 | fresh-corestore heal). Test: two co-owners with the same roomSeed open the same base with no shared bootstrap. |
| 21 | 2. **Encryption.** Co-owned DIDs run an ENCRYPTED base keyed by a members-only key. atproto-base takes |
| 22 | `encryptionKey` but wire the members-only derivation + `encrypted:true`. Test: a non-member can't read. |
| 23 | 3. **keet-attestation admission proof.** atproto-base admits via a raw ed25519 `sign(localKey‖base.key)`; |
| 24 | real co-owner devices use keet-writer-control (manifest + device→member→role, `_verifyAdmissionInApply`). |
| 25 | Add the keet-attestation proof path alongside the raw one (injectable `attest`/`verifyAttest`). Test |
| 26 | against a real device chain (reuse hpp's identity fixtures). |
| 27 | 4. **Repo projection for the bridge.** The bridge's PeerFront reads a `RepoStore` + `FirehoseSequencer` |
| 28 | (getRepo assembles a CAR from the RepoStore; the sig comes off the firehose). atproto-base emits the |
| 29 | firehose; confirm/ADD a repo projection (an atproto-store RepoStore fed from the finalized record set) |
| 30 | so `getRepo` works, OR decide the bridge reads the firehose and reconstructs. Ground this by reading |
| 31 | PeerFront.getRepo (`atproto-bridge/pds/lib/peer-front.js`) end-to-end first. |
| 32 | 5. **Records migration reader.** A function to read an EXISTING StateBaseV3 DID's records (the |
| 33 | `@state/entries`/PointerSubstrate view) and import them into atproto-base storage — the migration input. |
| 34 | |
| 35 | ## PHASE 1 — unify the consumer primitive (offline) |
| 36 | The "replicate membrane + follow firehose + verify head" primitive exists 3×: `PeerFront`/`PeerRouter` |
| 37 | (bridge, best — multi-front + freshest-by-rev), `atproto-did-peer` `DidPeer` (favela, behind — single |
| 38 | front, no failover), and a dormant bridge `did-peer.js`. Pick ONE canonical (extend PeerRouter's |
| 39 | multi-front + freshest-live logic — it already handles co-owned multi-membrane), make it read |
| 40 | atproto-base's firehose/repo, and repoint favela's `atproto-did-peer` at it. Retire the other two. |
| 41 | Fold in the bridge front-liveness fix from this session's audit (staleStrikes on `rpcConnected:false`, |
| 42 | never relay from a dead front) — that was a separate open bug (hyper-social #10/#11). |
| 43 | |
| 44 | ## PHASE 2 — migration, rehearsed on a SNAPSHOT (never hot) |
| 45 | Per the wj62 discipline (`reconcileFromCar` on FRESH storage; rocksdb hot-copy is inconsistent): |
| 46 | stop a daemon, snapshot its storage, run the Phase-0 migration reader → atproto-base on a THROWAWAY |
| 47 | copy, boot it, verify the projected head matches the DID's current bsky head (`getRepo` byte-compare). |
| 48 | Only after a clean offline rehearsal on real data do we touch the live daemon. |
| 49 | |
| 50 | ## PHASE 3 — staged cutover (lowest-risk DID first) |
| 51 | 1. A THROWAWAY test DID (no bsky account) end-to-end: hpp on atproto-base → bridge → `requestCrawl` → bsky. |
| 52 | 2. **loser-club** (single-creator, no co-owner quorum → lowest risk) — migrate, boot-test the BUILT |
| 53 | `--production` closure (`npm run smoke`, not just units — test-green ≠ deploy-ready), deploy via |
| 54 | `pgh publish` (NOT git-push CI — hpp fleet has autoupdate off; `pgh app update`+restart), verify e2e. |
| 55 | 3. **wj62** (co-owned, quorum, HIGHEST risk — currently in a gen-2 resync/heal state) LAST, only after 1+2 |
| 56 | are stable for a soak. Rehearse the co-owned handoff offline (both co-owners on atproto-base, converge, |
| 57 | post, verify no fork) before touching prod. |
| 58 | |
| 59 | ## PHASE 4 — favela shared-appview (the original divergence, separate track) |
| 60 | Replace favela-web's per-node local `Appview` (never replicated → tabertron≠ashatron) with a **co-owned |
| 61 | atproto-base feed-room**: the community feed is an atproto-base instance whose `apply()` folds member |
| 62 | posts (favela's already-deterministic `applyCommit`), that ALL favela nodes REPLICATE and read — one |
| 63 | shared view, no per-node re-index. "Multiple feed-rooms = multiple feeds." Depends on Phase 0 (shared |
| 64 | base key) + Phase 1 (one consumer primitive). See [[reference_shared-autobase-view-keet-blueprint]]. |
| 65 | |
| 66 | ## Verification discipline (every phase) |
| 67 | - Offline test FIRST; a clean-testnet pass is NOT proof for a flaky-link/live scenario. |
| 68 | - Boot-test the actual `--production` closure before any deploy (declare-your-deps footgun, 2 past outages). |
| 69 | - e2e = post → bridge → bsky visible; `getRepo` byte-matches; no fork (`wj62-state.mjs`-style diagnose). |
| 70 | - Rollback: keep the old StateBaseV3 storage untouched (nothing deleted); a bad cutover reverts by |
| 71 | pointing the daemon back at the old app version. |
| 72 | |
| 73 | ## Risks / open decisions to settle at session start |
| 74 | - Does the bridge consume atproto-base's FIREHOSE directly, or a membrane repo projection? (Phase-0 #4 read decides.) |
| 75 | - Migration determinism: does re-projecting existing records reproduce the SAME `rev` chain the relay |
| 76 | already has? (Forward-only relay cursor — a rev regression parks the host Idle, federation-wall #3.) |
| 77 | Likely need to continue the chain from the current head (adopt), not re-derive from rev 0. |
| 78 | - Multi-indexer reliability at N≥3 under real links is still uncharacterized — wj62 co-owned cutover |
| 79 | should stay N=1 (single designated indexer) + notary-revival recovery until N≥3 is soak-tested. |
| 80 | |
| 81 | Related session artifacts: `project_atproto-base-deterministic-projection-2026-07-23`, |
| 82 | `reference_pptaproto-architecture-holes`, `project_atproto-storage-consensus-redesign-2026-07-19`. |