| … | … | @@ -7,6 +7,14 @@ blocks. **This** doc is the deeper architecture: build the real thing |
| 7 | 7 | sync in a **little-heap** way, and treat bsky as *one network to bridge |
| 8 | 8 | to at the edge* — not the home. |
| 9 | 9 | |
| 10 | > ⛔ **SUPERSEDED IN PART — 2026-08-31.** §1's thesis ("adopt AT Proto's *data |
| 11 | > model and lexicons*") was the decision that made atproto favela's CANONICAL |
| 12 | > P2P path. That is now reversed: **the canonical record is a markdown |
| 13 | > document, and atproto + ActivityPub are both projections derived from it.** |
| 14 | > atproto's machinery stays — at the BRIDGE/RELAY layer, which is where this |
| 15 | > doc is still correct. See `favela-markdown/spec/design.md` and |
| 16 | > `favela-web/spec/record-vocabulary.md` §0. |
| 17 | |
| 10 | 18 | Status: **design / research synthesis** (2026-06-19). Nothing here is |
| 11 | 19 | built yet. Several entries correct earlier assumptions — see |
| 12 | 20 | [Corrections](#corrections-things-that-look-true-but-arent). |
| … | … | @@ -93,22 +101,38 @@ The PLC directory is being spun out to a neutral Swiss association + |
| 93 | 101 | gaining a mirroring API; we can also mirror it locally for our own |
| 94 | 102 | reads. |
| 95 | 103 | |
| 96 | | ### The handle is the stubborn part (a *separate* hostname problem) |
| 104 | ### Handles — AUTHORITATIVE (settled 2026-07-01; stop re-deriving this) |
| 97 | 105 | |
| 98 | | A handle must be a **hostname** — paths are illegal, one handle per |
| 99 | | account (upstream rejected multi-handle, [#2206]). So per-user *verified |
| 100 | | pretty* handles fundamentally need a hostname each: |
| 106 | Handle resolution returns the account's **specific DID** via one of two |
| 107 | methods ([handle spec](https://atproto.com/specs/handle); the resolver tries |
| 108 | DNS first, then HTTPS). A handle must be a **hostname**, one per account |
| 109 | ([#2206]). The two methods differ ONLY in where that per-user DID is served: |
| 101 | 110 | |
| 102 | | | Handle option (no subdomain) | bsky accepts? | Verified @name? | Multi-user? | |
| 111 | | Method | Per-user artifact | TLS cert on handle host? | Scale story | |
| 103 | 112 | |---|---|---|---| |
| 104 | | | Bare apex `@7rnx.net` | ✅ | ✅ | ❌ one user | |
| 105 | | | Bring-your-own domain | ✅ | ✅ | ✅ (user owns a domain) | |
| 106 | | | Invalid / no handle | ✅ followable by DID | ❌ shows ⚠ | ✅ but all ⚠ | |
| 107 | | | Subdomain `@alice.7rnx.net` | ✅ | ✅ | ✅ — needs wildcard cert **or** cert-free DNS-TXT | |
| 108 | | |
| 109 | | So: **identity without a subdomain is solved (did:plc); a verified |
| 110 | | pretty handle is not** — it's bare-apex (1), BYO-domain, unverified, or |
| 111 | | subdomain (wildcard TLS cert, or DNS-TXT with delegated DNS control). |
| 113 | | **DNS-TXT** `_atproto.<handle>` = `did=<their DID>` | one DNS TXT record per user | **none** | one DNS write per user (automatable with a DNS API) | |
| 114 | | **HTTPS well-known** `https://<handle>/.well-known/atproto-did` → plain DID | none (one server answers all by `Host` header) | **wildcard cert** for `*.<domain>` | add users in-app, zero DNS writes | |
| 115 | |
| 116 | **Bottom line for us (do not relitigate):** |
| 117 | - **No wildcard TLS cert is required.** Certless verified pretty handles work |
| 118 | via DNS-TXT — *proven live*: `jay.bsky.team` resolves on real bsky with **no |
| 119 | A record and no web server/cert at all** (see |
| 120 | `scripts/atproto-handle-resolution-test.sh`). |
| 121 | - **The ONLY required cert is the PDS data host** (`pds.7rnx.net`) — one |
| 122 | ordinary single-name cert, NOT a wildcard. |
| 123 | - **A wildcard cert is OPTIONAL convenience only.** Its sole benefit is |
| 124 | onboarding many users without a per-user DNS write; it is how `bsky.social` |
| 125 | serves `*.bsky.social` (HTTP resolution — bnewbold, |
| 126 | [discussion #1661](https://github.com/bluesky-social/atproto/discussions/1661)). |
| 127 | Skip it for the prototype; adopt it later only if per-user DNS writes become a |
| 128 | chore. |
| 129 | - **did:web path-based remains a dead end** (see Corrections); identity is |
| 130 | did:plc and needs no hostname at all. |
| 131 | |
| 132 | So: identity (did:plc) needs no hostname; a *verified pretty handle* needs a |
| 133 | hostname each but **no cert** via DNS-TXT. Options: bare-apex `@7rnx.net` |
| 134 | (1 user); BYO-domain (user owns it); subdomain `@alice.7rnx.net` via DNS-TXT |
| 135 | (certless) or the optional wildcard-cert well-known path for hands-off scale. |
| 112 | 136 | |
| 113 | 137 | ### 7rnx.net facts (verified live) |
| 114 | 138 | |
| … | … | @@ -230,6 +254,60 @@ only applies at the optional main-bsky bridge. |
| 230 | 254 | |
| 231 | 255 | --- |
| 232 | 256 | |
| 257 | ## 8b. Migration path — stock PDS now, Hypercore PDS later |
| 258 | |
| 259 | The Hypercore PDS does **not** have to exist before we federate. Run a |
| 260 | **stock PDS** first (reference PDS or `millipds`, §10) to get real accounts + |
| 261 | verified handles live today, then migrate accounts to our Hypercore PDS once it |
| 262 | passes the fidelity gate. **This is atproto's first-class account-migration |
| 263 | flow — not a custom hack** ([guide](https://atproto.com/guides/account-migration)): |
| 264 | |
| 265 | 1. `com.atproto.server.createAccount` on the **new** (Hypercore) PDS, proving |
| 266 | control of the existing DID. |
| 267 | 2. `com.atproto.sync.getRepo` (CAR) from the stock PDS → |
| 268 | `com.atproto.repo.importRepo` into ours; blobs moved one-by-one via |
| 269 | `com.atproto.sync.listBlobs` / `com.atproto.repo.listMissingBlobs`. |
| 270 | 3. Re-point identity: `requestPlcOperationSignature` → `signPlcOperation` → |
| 271 | `submitPlcOperation` — a **signed PLC op** updating the DID doc's |
| 272 | `serviceEndpoint` (PDS host) + repo signing verificationMethod. |
| 273 | |
| 274 | **What does NOT change:** the **DID** (`did:plc:…`) and the **handle** |
| 275 | (`@alice.7rnx.net`, DNS-TXT). Both bind to the *identity*, not the PDS host — so |
| 276 | the stock-PDS + wildcard-handle work done now carries over 1:1 and is not |
| 277 | throwaway. Migration is **per-account and reversible** (migrate one test user, |
| 278 | migrate back if it misbehaves — no big-bang). |
| 279 | |
| 280 | **The gating test (this is the real work, and it's the §3/§9 crux):** migration |
| 281 | round-trips the repo **through a CAR file** that the relay re-crawls. So the |
| 282 | make-or-break is whether our Hypercore PDS emits a **byte-exact canonical MST / |
| 283 | DAG-CBOR / signed-commit** repo that survives `importRepo` and relay |
| 284 | acceptance. The migration *ceremony* is trivial and proven; **MST fidelity is |
| 285 | the unproven piece.** Good news: `importRepo` + relay-accept is a clean pass/fail |
| 286 | harness — validate fidelity by migrating one real account and checking it |
| 287 | round-trips and re-indexes. |
| 288 | |
| 289 | ### Deployment topology (local PDS + 7rnx + gateway) — no new risk |
| 290 | |
| 291 | The PDS runs **locally** (tabertron); its **public face is `https://7rnx.net`** |
| 292 | (apex, HTTP-01 cert) via the 7rnx Apache reverse proxy; the **hyperident-gateway |
| 293 | VPN (10.22.0.0/16) is the internal backhaul** — identical to how pear-git / |
| 294 | wirecore are already served. The relay only ever talks to `https://7rnx.net`; |
| 295 | the VPN is invisible to it. Rules: |
| 296 | |
| 297 | - **`serviceEndpoint` = the PUBLIC URL** (`https://7rnx.net` or a single-name |
| 298 | `https://pds.7rnx.net` — an easy HTTP-01 cert, *not* the wildcard problem), |
| 299 | **never a `10.22.0.x` VPN IP** (the relay can't reach it). |
| 300 | - The PDS wants root-level paths (`/xrpc/*`, `/.well-known/*`) — give it the apex |
| 301 | root (`--root-path /`) or its own host so it doesn't collide with `/git`, |
| 302 | `/wirecore`. |
| 303 | - `com.atproto.sync.subscribeRepos` is a **long-lived WebSocket** firehose. Apache |
| 304 | already proxies WS (`RewriteRule → ws://`); the one thing to watch is the |
| 305 | **alpha gateway's connection stability** (a VPN flap drops the firehose — relays |
| 306 | reconnect by cursor, so it's resilient but flapping = indexing lag) and **MTU** |
| 307 | on large `getRepo`/CAR pulls. |
| 308 | |
| 309 | --- |
| 310 | |
| 233 | 311 | ## 9. Corrections (things that look true but aren't) |
| 234 | 312 | |
| 235 | 313 | - **Path-based did:web is NOT valid in AT Proto.** Spec: *"only |