| 1 | # π€ Accounts, sign-in & recovery β the anchor model |
| 2 | |
| 3 | Status: **plan of record β phase 1 largely shipped** (decided with Paul |
| 4 | 2026-07-16; status refreshed 2026-07-22). Live today: the six sign-in doors on |
| 5 | home (passkey, email code, Apple, Google, Microsoft, GitHub β the #1112 card), |
| 6 | identity anchors + ceremony proofs (`stateless.go`), the Sign-in options |
| 7 | management step, account combine/merge (#1098), sealed anchor sessions, and |
| 8 | the native passkey/Apple/email doors. Merged but dark: the synced DEK |
| 9 | (#1144, flag off pending the device matrix). Not built yet: the `/invite` |
| 10 | link redesign (the phase-0 locked-card rungs shipped on the Mac 2026-07-22; |
| 11 | native add-device mint/QR still open), owner anchoring at creation, |
| 12 | approve-from-another-device, the recovery-passkey offer, and the native OIDC |
| 13 | doors (server legs done, client work open) β see *Phasing* below, per-item. |
| 14 | (The **paper recovery kit** that used to sit in this list was cut on |
| 15 | 2026-07-25 β Paul: recovery is a passkey, never a passphrase or a printed |
| 16 | code; the recovery-passkey section below replaces it.) |
| 17 | This document supersedes the *Invariants* section of |
| 18 | `onboarding-and-home-server.md` β the "no email, no social login, ever" line β |
| 19 | after that invariant was field-falsified by real lockouts. Everything else in |
| 20 | that doc (the welcome page, the home server machinery, the blob schema, |
| 21 | enrolment, the fragility banner) remains accurate and in force; this doc is |
| 22 | about **what identifies a person and what brings their keys back**. |
| 23 | |
| 24 | ## Why we're revising a "permanent" decision |
| 25 | |
| 26 | Passkey-only sign-in was declared a Day-1 invariant. Within a week of the |
| 27 | native apps leaning on it, two of our first users were locked out: |
| 28 | |
| 29 | - The macOS ceremony sheet only reaches iCloud Keychain (plus QR/security |
| 30 | keys). A passkey living in a Chrome profile or a third-party manager is |
| 31 | invisible to it β the user sees "a passkey window and no way to enter a |
| 32 | password." |
| 33 | - 1Password's macOS provider returns a PRF that doesn't match its own browser |
| 34 | extension's for the same credential, so even a *present* passkey can fail to |
| 35 | decrypt (the browser-handoff workaround exists because of this). |
| 36 | - The locked-space card offered exactly one rung ("Sign In with Elevenβ¦") and |
| 37 | a destructive one ("Remove from This Mac"). One-rung ladders read as "the |
| 38 | app is broken", not "the design is working". |
| 39 | |
| 40 | The root cause is architectural, not cosmetic: the passkey was doing **two |
| 41 | different jobs at once**, so any failure of the ceremony was total. |
| 42 | |
| 43 | ## The two jobs |
| 44 | |
| 45 | Every mechanism in this design does one or both of: |
| 46 | |
| 47 | 1. **Identity** β proving you're the same person who enrolled. Lets the home |
| 48 | server return your record and gates destructive operations. |
| 49 | 2. **Keys** β holding or deriving the client-side secret (the KEK) that |
| 50 | unwraps the vault **DEK**. The DEK decrypts the blob, which holds live |
| 51 | sessions + per-instance private keys. Nothing server-side can ever do this |
| 52 | job β that's the one rule. |
| 53 | |
| 54 | | Mechanism | Identity | Keys | Notes | |
| 55 | |---|---|---|---| |
| 56 | | Passkey + PRF | β | β | today's model; elegant, and why its failure is total | |
| 57 | | Sign in with Apple | β | β | stable team-scoped `sub`; can request no email at all | |
| 58 | | Email code | β | β | ecosystem-neutral; blind-indexed (below) | |
| 59 | | iCloud-synced DEK item | β | β | invisible; Apple-ecosystem only | |
| 60 | | Device key + approval | β | β | transfers keys to a new device, given identity | |
| 61 | | Recovery passkey (manager / hardware key) | β | β | a second, independent credential; ecosystem-free | |
| 62 | | Guardian wrap | β | β | DEK/credentials wrapped to another member's key | |
| 63 | |
| 64 | An **identity anchor alone can never decrypt anything.** That single sentence |
| 65 | forbids the tempting shortcut (email-gated server escrow of the DEK) and |
| 66 | shapes everything below. |
| 67 | |
| 68 | ## The new invariants |
| 69 | |
| 70 | What "no email, no social login, ever" weakens to β each line deliberate: |
| 71 | |
| 72 | 1. **Membership stays anonymous.** Joining a space requires no identifier, |
| 73 | ever β invites remain the only membership door. No change from today. |
| 74 | 2. **Ownership is anchored.** Every space's recovery chain must terminate at |
| 75 | a member whose recovery survives total device loss β normally the owner. |
| 76 | Practically: *creating* a space requires an anchor; *joining* one never |
| 77 | does. The owner's anchor is a **pair**: an identity anchor (email, Sign in |
| 78 | with Apple, or a successor β see the open slot) *plus* a key anchor (on |
| 79 | Apple platforms the synced DEK rides along invisibly; otherwise a |
| 80 | recovery passkey in a synced manager or on a hardware key). |
| 81 | 3. **Providers attest; they are never the identity.** A third-party sign-in |
| 82 | is at most a verifier of an anchor. Our record is a blind index (a hash) β |
| 83 | there is no provider user-id column, no stored email address, and swapping |
| 84 | providers never orphans an account. |
| 85 | 4. **The server never holds a readable key.** No escrow of the DEK under any |
| 86 | server-side or identity-gated key, ever β a compromised home server plus a |
| 87 | passed email gate must still yield nothing. Recovery that "just works with |
| 88 | an email" is only achievable by breaking the one rule; we don't. |
| 89 | 5. **The home server stays strictly additive** (unchanged): no enrolment |
| 90 | record β no requests to it, ever. |
| 91 | 6. **Copy stays human** (unchanged rule): never "passkey", "PRF", "key", |
| 92 | "escrow", or "backup" in UI. People sign in, save their spaces, and add |
| 93 | ways back in. |
| 94 | |
| 95 | ## Account classes |
| 96 | |
| 97 | | Class | Identity (job 1) | Keys (job 2) | Lost everything | |
| 98 | |---|---|---|---| |
| 99 | | **Owner** (anchored) | SiwA / email / successor | synced DEK, or recovery passkey | full restore via anchor pair | |
| 100 | | **Member, anchored** (optional) | same menu | same menu | same ladder as owner | |
| 101 | | **Member, identifier-less** | the key *is* the identity | passkey(s) or nothing | re-invite floor | |
| 102 | | **Guardian-managed** | none β guardian vouches | wrapped to guardian's key | guardian re-links, instantly | |
| 103 | |
| 104 | - **Owner.** Whoever creates a space attaches the anchor pair at creation |
| 105 | (or is nudged until they do β open decision). This lands the identifier |
| 106 | exactly where accountability already lives: space creation is the abuse |
| 107 | surface (quotas, reaping, billing), membership never was (invite-gated). |
| 108 | - **Identifier-less member.** Today's model, kept on purpose for |
| 109 | privacy-maximalists: bootstrap by invite, optionally strengthen with a |
| 110 | passkey (or several), accept the re-invite floor. This class is why |
| 111 | the product can still truthfully say "no signup required". |
| 112 | - **Guardian-managed** β the family case. A child has no email and no |
| 113 | passkey. The guardian's signed-in app mints the child's credentials |
| 114 | (an add-device link / QR β machinery that exists today) and keeps a copy |
| 115 | as a **managed entry in the guardian's own vault**. The child has *no home |
| 116 | account at all* β no server record, no identifier, nothing collected |
| 117 | (the right COPPA/GDPR-K posture by construction). Restore = guardian opens |
| 118 | their app β managed member β "link this device" QR. The guardian's own |
| 119 | recovery ladder is the redundancy: recover the guardian account and the |
| 120 | managed entries come back with the vault. Disclosed honestly: a guardian holding a member's keys can read |
| 121 | and act as that member β that is what guardianship *is*, and the UI says |
| 122 | so at creation. (A later variant can give an older child their own home |
| 123 | account with the DEK additionally wrapped to the guardian β the graduation |
| 124 | path β but the vault-entry form is the v1.) |
| 125 | |
| 126 | ## What recovery restores β the ladder |
| 127 | |
| 128 | Top rung first; each rung only matters when everything above it is gone: |
| 129 | |
| 130 | 1. **Any surviving signed-in device** β everything, silently (the cached DEK; |
| 131 | today's silent sync). |
| 132 | 2. **A synced key anchor** β a passkey that synced, or the iCloud-synced DEK: |
| 133 | new device + one identity ceremony = full restore, zero surviving devices |
| 134 | needed. |
| 135 | 3. **Approve from another device** β a new device signs in with an identity |
| 136 | anchor; an enrolled device gets a push ("Approve this Mac?"); on approval |
| 137 | the DEK is re-wrapped to the new device's ephemeral key. This is the |
| 138 | add-device flow wearing mainstream clothes (WhatsApp/Signal/Apple trained |
| 139 | everyone on it) and is the workhorse for web and non-Apple platforms. |
| 140 | 4. **A recovery passkey outside the ecosystem** β a passkey in a |
| 141 | cross-platform manager or on a hardware security key, enrolled as one |
| 142 | more way in. This is what survives losing a whole vendor account: it is |
| 143 | deliberately not parked where rung 2 lives. |
| 144 | 5. **Control-only recovery + the re-invite floor** β identity anchor alone. |
| 145 | The owner recovers *control* of the space (see below) and can re-admit |
| 146 | anyone; a re-invited member gets the **same stable thread keys re-wrapped |
| 147 | to their new keypair by any live member's client, so full history |
| 148 | decrypts**. What this rung cannot do: if literally every member's key |
| 149 | material is gone (a solo space; a whole family's devices at once), the |
| 150 | history is cryptographically sealed. That is the design working β and the |
| 151 | reason invariant 2 requires the owner's anchor to be a *pair*, not an |
| 152 | email alone. |
| 153 | |
| 154 | ## Sign in with Apple, precisely |
| 155 | |
| 156 | The seamless path for the current (Apple-heavy) user base: |
| 157 | |
| 158 | - **Request no email scope.** The stable team-scoped `sub` is the account |
| 159 | key, stored as `HMAC(pepper, sub)` β nothing mailable, nothing to leak. |
| 160 | Apple learns sign-in timing; we learn nothing new. |
| 161 | - **Pair it with the synced DEK.** The native DEK cache is deliberately |
| 162 | device-local today (`kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, |
| 163 | `ios/LChatCore/.../Keychain.swift`). Storing the DEK *also* as a |
| 164 | synchronizable iCloud Keychain item makes the key travel invisibly β same |
| 165 | trust anchor as an iCloud passkey, zero WebAuthn ceremonies, and the |
| 166 | third-party-provider bug class (the 1Password PRF mismatch) disappears |
| 167 | because credential managers are never in the loop. *Built (issue #1089): |
| 168 | `ios/LChatCore/.../DEKSync.swift` β a second item beside the device-local |
| 169 | one (service `com.lchat.client.dek-synced`, added by addition, never |
| 170 | delete-then-add), write-gated on the account holding a SiwA anchor. |
| 171 | Build-flag gated, OFF by default until the device matrix passes.* |
| 172 | - **Job 1 gates job 2.** A synced DEK alone would auto-unlock on any device |
| 173 | signed into the same Apple ID (family iPad on a parent's account). The |
| 174 | synced item is used only after a fresh SiwA assertion for the matching |
| 175 | hashed `sub`. Face ID on the SiwA sheet is the one visible moment. *In the |
| 176 | built slice `DEKSync.release` is the item's only reader, called solely |
| 177 | with the token a fresh SiwA verify just returned; release further requires |
| 178 | the resolved account's anchor list to contain the very anchor the item was |
| 179 | written under AND the DEK to actually open that account's blob.* |
| 180 | - **The honest trade, written down:** the "zero surviving devices" recovery |
| 181 | trust moves *into* Apple's ecosystem (Apple ID + iCloud Keychain escrow) |
| 182 | rather than being eliminated. That is exactly what the seamless experience |
| 183 | buys, and the identifier-less class is the opt-out. |
| 184 | - **Web and non-Apple:** SiwA's web flow exists but presumes an Apple ID. |
| 185 | The web keeps passkey-PRF as its local mechanism and gains rung 3 |
| 186 | (approve-from-phone). The email/successor anchor is the ecosystem-neutral |
| 187 | spine when it lands. |
| 188 | |
| 189 | ## Handoff: onboarding that travels |
| 190 | |
| 191 | The goal (Paul, 2026-07-16): with identity at the *front* of onboarding, a |
| 192 | person can start joining on the web and finish in the app β or the reverse β |
| 193 | **without losing any fidelity**. Identity-first is what makes this nearly |
| 194 | free: |
| 195 | |
| 196 | - **Today's order strands progress.** Claim-first-save-later means the |
| 197 | claimed credentials, chosen name, and onboarding position live in one |
| 198 | browser until the person explicitly enrols β the "Open in Eleven" jump has |
| 199 | to smuggle state across (the fragment parked in `sessionStorage`, the |
| 200 | enrolment popup dance). |
| 201 | - **Identity-first inverts it.** Sign in is the first step (one Face ID via |
| 202 | SiwA on Apple devices), so every artifact of onboarding lands in the vault |
| 203 | *as it is created*: the claim writes the credential entry in the same |
| 204 | breath (the old doc's "golden path", now the default because sign-in got |
| 205 | cheap). The vault is the handoff bus. |
| 206 | - **Fidelity is then free, not built.** Once keys + session cross via the |
| 207 | vault, the rest follows automatically: the onboarding conversation is |
| 208 | messages in a thread and mini-app state snapshots, both of which sync to |
| 209 | any device holding the keys. Jumping devices resumes the scripted |
| 210 | onboarding at the exact message β no state machine to serialize, no |
| 211 | "continue where you left off" feature to build. The jump itself carries |
| 212 | nothing sensitive: "Open in Eleven" / universal link β app signs in with |
| 213 | the same identity (same Apple ID: zero ceremony) β vault pull β the space |
| 214 | and the conversation are already there. |
| 215 | - **Reverse direction** (native β web) is rung 3 of the ladder: the web |
| 216 | signs in with the identity anchor and an enrolled device approves it β or |
| 217 | a QR from the app, exactly the add-device mechanics. |
| 218 | - **Identifier-less joiners** keep today's mechanics (fragment rides the |
| 219 | universal link; post-join nudge) β handoff fidelity is one of the things |
| 220 | an anchor *buys*, stated as such in the nudge copy. |
| 221 | |
| 222 | ## Invite links: pretty, and how short they can get |
| 223 | |
| 224 | Today's link is `β¦/join/join#e1.<T>~<encoded name>~<brandingKey>` β and each |
| 225 | ugly part exists for a different reason, so they improve on different |
| 226 | schedules: |
| 227 | |
| 228 | - **`/join/join` β pure accident.** The path label slot is cosmetic |
| 229 | (`invite-redesign.md`) and every minter hardcodes it to `"join"`. Free to |
| 230 | fix: a single `/invite` path, doubling as the format version (old |
| 231 | `/join/join#e1.` links parse forever). |
| 232 | - **`~<name>` β in the fragment on purpose.** Member names are E2EE; a name |
| 233 | in the *path* would hand it to the server (access logs included). So |
| 234 | `β¦/invite/Paul/<secret>` as a default breaks the rule. The name can lead |
| 235 | the fragment instead β `β¦/invite#Paul/<secret>` β which reads like a path |
| 236 | (the `#` is the only tell) and stays server-blind. `/` is the right |
| 237 | separator, not `.`: it's a legal, unescaped fragment character that |
| 238 | browsers/linkifiers/QRs carry verbatim, and `encodeURIComponent` escapes |
| 239 | `/` inside names (but leaves `.` alone), so the slots are collision-free |
| 240 | by construction β a raw `/` can only be our delimiter. (Name-in-path could |
| 241 | be offered as an explicit per-link "include my name in the address" |
| 242 | opt-in β the space name of an unsealed instance is already public |
| 243 | branding, so *that* variant leaks nothing β but the default stays blind.) |
| 244 | - **`~<brandingKey>` β the price of sealed instances.** On a sealed instance |
| 245 | nothing is public, so the link must carry the key that unlocks the welcome |
| 246 | card. Unsealed instances don't need it. Unavoidable where it applies. |
| 247 | - **`<T>` (22 chars) β load-bearing, for now.** T is not a ticket; it |
| 248 | offline-wraps the group key (`wrapped = AES-GCM(groupKey, HKDF(T))` sits in |
| 249 | the server's DB). A shorter T is brute-forceable *offline* by anyone who |
| 250 | steals the DB β a 10-char secret falls to a GPU farm in days β which would |
| 251 | break "compromise the server, get nothing". 128 bits (22 chars, re-encoded |
| 252 | base58 so it at least *looks* less like line noise) is the floor **while |
| 253 | invites carry the key offline**. |
| 254 | - **Redirects can't shorten the secret.** Browsers do carry a fragment |
| 255 | through a redirect (that's how a short-*domain* bouncer could work), but a |
| 256 | server can only put in a `Location` what it knows β and it must never know |
| 257 | T. The secret travels in the link the person shares, full stop. |
| 258 | |
| 259 | So there are two roads: |
| 260 | |
| 261 | 1. **Cosmetic redesign (no crypto change):** |
| 262 | `https://xyz.11ch.at/invite#Paul/2vXk9QmR4pTnWb8zJcHe6f` β one path word, |
| 263 | name up front, base58 secret, branding key as a third slot only where |
| 264 | sealed (`#Paul/<T>/<brandingKey>`). Roughly half today's length, and |
| 265 | honest about which half remains. Grammar notes: the name slot is always |
| 266 | present (falling back to the space name) so a new link is never |
| 267 | confusable with a legacy raw-T fragment; version detection is |
| 268 | fragment-contains-`/` β this format, `e1.` prefix β current, else legacy. |
| 269 | 2. **Genuinely short links = the parked PAKE phase** (`invite-redesign.md` |
| 270 | Phase 2). A low-entropy secret is only safe in a *live* exchange |
| 271 | (Magic-Wormhole style): the server relays an exchange it can't read, the |
| 272 | short code authenticates it against MITM, and the key travels |
| 273 | device-to-device β no offline-crackable blob, nothing a compromised |
| 274 | server can claim for itself. The historical blocker was needing a member |
| 275 | online during the join; **the sidecar bot dissolves it** β it's a member, |
| 276 | it holds keys, it's always on, so bot-attended spaces get instant short |
| 277 | joins (and unattended ones degrade to "waiting for Paul to let you in", |
| 278 | which is respectable UX in its own right). This is also where invite |
| 279 | links become *words* β `β¦/invite#Paul/coral-piano-42` β which is the |
| 280 | least scary a secret can look. |
| 281 | |
| 282 | ## Email (and the successor slot) |
| 283 | |
| 284 | Email is one implementation of the terminal identity anchor β **the slot is |
| 285 | deliberately abstract** because a stronger alternative is being designed |
| 286 | (Paul, 2026-07-16; unspecified here). Whatever fills the slot must provide: |
| 287 | a user-memorable claim ("this account is mine"), a reachable out-of-band |
| 288 | channel to complete a challenge, and a blind-indexable identifier. |
| 289 | |
| 290 | If plain email fills it: |
| 291 | |
| 292 | - **Blind index:** store `HMAC(pepper, normalize(email))` only. At recovery |
| 293 | the user types their address; we hash, match, and send a code to what they |
| 294 | typed. No plaintext address is ever stored. Normalization is lowercase |
| 295 | only β plus-addressing and provider dot-games are respected, not stripped. |
| 296 | - **Honest limits, for the copy and the threat model:** email hashes are |
| 297 | dictionary-reversible for anyone holding the pepper (tiny input space); |
| 298 | the running server and the mail provider both see the plaintext address |
| 299 | transiently at send time. The claim softens from "we can't spam what we |
| 300 | don't have" to *"we only see your address the moment you use it, and never |
| 301 | keep it."* Defensible; say it exactly like that. |
| 302 | - **Fully native-able:** a 6-digit code works in a plain text field β no |
| 303 | WebAuthn sheet, no browser bounce, no provider roulette. The exact |
| 304 | complaint that started this ("no way to enter a password") dissolves. |
| 305 | - **Costs:** outbound mail infrastructure (SES/Postmark, SPF/DKIM, bounces); |
| 306 | the provider becomes a party who sees addresses and timing; per-address |
| 307 | rate limits and disposable-domain policy become the abuse levers. |
| 308 | |
| 309 | ## The recovery passkey |
| 310 | |
| 311 | **Decision (Paul, 2026-07-25): recovery is a passkey, never a passphrase or |
| 312 | a printed code.** The earlier paper-kit design (a client-generated PDF |
| 313 | wrapping the DEK under a printed code, riding the vault's passphrase-wrap |
| 314 | primitive #995β#997) is dead β that primitive was removed from the product |
| 315 | the same day. One kind of key everywhere; nothing to type, transcribe, or |
| 316 | find in a drawer. |
| 317 | |
| 318 | - **Mechanically it's just another enrolment.** Home already wraps the DEK |
| 319 | per enrolled credential, so "add a recovery passkey" is the existing |
| 320 | add-a-passkey ceremony pointed at a *different home* for the credential: a |
| 321 | cross-platform password manager (1Password, Bitwarden, Google Password |
| 322 | Manager) or a hardware security key. No new crypto, no new server surface. |
| 323 | - **What paper used to buy β surviving a lost vendor account β a |
| 324 | hardware key or cross-platform manager buys instead.** The offer's copy |
| 325 | steers there: a second passkey parked in the *same* iCloud account as |
| 326 | rung 2 adds nearly nothing; one on a YubiKey in a drawer, or in a manager |
| 327 | that syncs everywhere, is the real ecosystem escape hatch. |
| 328 | - **Losing it is survivable and revocable.** A found hardware key is inert |
| 329 | without its PIN, and the credential (plus its DEK wrap) can be removed |
| 330 | from the account like any other β unlike a printed code, which could |
| 331 | never be un-printed. |
| 332 | - **Framing:** never a sign-up speed bump β offered from settings and at |
| 333 | space creation ("add another way back in"), in the same no-jargon voice as |
| 334 | the rest (invariant 6: the word "passkey" stays out of the UI). |
| 335 | - **Managed members** need no kit of their own: their credentials live as |
| 336 | entries in the guardian's vault (above), and the guardian's ladder is the |
| 337 | redundancy. |
| 338 | |
| 339 | ## Control-plane recovery (why the owner can always re-admit) |
| 340 | |
| 341 | The admin token is authorisation, not decryption β its hash lives in the |
| 342 | instance DB, and the cloud provisioner already knows which home account |
| 343 | created which space. So "recover control" is a service-layer capability |
| 344 | re-issue gated on the owner's identity anchor: verify the anchor, re-issue |
| 345 | the admin link, and the owner is back to roster management β able to |
| 346 | re-invite themselves and everyone else (rung 5). Self-hosters have the same |
| 347 | power through `/data` on their own box. No key material is involved at any |
| 348 | step, which is what makes this rung safe to gate on identity alone. |
| 349 | |
| 350 | ## App Store guideline 4.8 |
| 351 | |
| 352 | - An **own-account system** (email code, passkey) does not trigger 4.8 at |
| 353 | all β same exemption we rely on today. |
| 354 | - **Sign in with Apple, offered alone, satisfies 4.8 by construction** β it |
| 355 | *is* the privacy-respecting option the rule demands β and future-proofs |
| 356 | the day a second provider (Google-as-attester) is ever added, because the |
| 357 | compliant option is already present. |
| 358 | - The order therefore stays: SiwA and/or email first; other providers only |
| 359 | ever as additional attesters, never the primary. |
| 360 | |
| 361 | ## Who sees what β the deltas |
| 362 | |
| 363 | | Party | Sees (new, per class) | |
| 364 | |---|---| |
| 365 | | Apple | that a SiwA user signs in, and when | |
| 366 | | Mail provider | addresses + timing, at send time only (email class) | |
| 367 | | Home server | blind indexes; one extra wrapped copy of the DEK per anchor | |
| 368 | | A guardian | everything their managed member can see (disclosed at creation) | |
| 369 | | A found hardware key | nothing without its PIN β and the credential is revocable from the account | |
| 370 | | A compromised home server | still nothing readable (invariant 4) | |
| 371 | |
| 372 | ## Phasing |
| 373 | |
| 374 | Status per item as of 2026-07-22: |
| 375 | |
| 376 | 0. **Sweep (no model change):** the locked-space card gains its |
| 377 | missing rungs β "Link from your iPhone" (add-device QR) and "Ask for a |
| 378 | new invite" β and failure copy that never dead-ends at a ceremony sheet. |
| 379 | The **cosmetic invite-link redesign** (`/invite#Name/<T>`) also rides |
| 380 | here: no crypto change, old links parse forever. β **partially built** |
| 381 | (2026-07-22): the Mac locked pane now carries both rungs (each opens the |
| 382 | Add-from-link sheet with contextual guidance β the consume side of |
| 383 | add-device links already worked) plus signed-in-but-still-locked |
| 384 | feedback. Still open: a native mint/QR for add-device links (minting is |
| 385 | web-only today), and no `/invite` route exists. |
| 386 | 1. **SiwA + synced DEK** on iOS/Mac; web gains approve-from-phone. |
| 387 | Identity-first onboarding lands with it β the vault becomes the handoff |
| 388 | bus and webβnative jumps stop losing fidelity. β **partially shipped**: |
| 389 | SiwA is live on web + both natives; the synced DEK is merged behind an |
| 390 | off flag (#1144, device matrix owed); approve-from-phone and the |
| 391 | identity-first ordering are not built. The web doors overshot the plan β |
| 392 | Google/Microsoft/GitHub ship too; natively their server legs |
| 393 | (`elevenmessenger://home-oidc`) are done and the client halves are open. |
| 394 | 2. **Guardian-managed members + the recovery-passkey offer** (the family |
| 395 | story; settings + creation). β **not built.** |
| 396 | 3. **Owner anchoring at creation** β email or the successor anchor; the |
| 397 | provisioner starts requiring/nudging it. β **not built** (creation from |
| 398 | home is effectively anchored via the home session; a bare setup link |
| 399 | still claims anonymously). |
| 400 | 4. **Providers-as-attesters**, if ever. **Short/word-code invites** (the |
| 401 | PAKE phase, bot-attended) slot in whenever they earn their build. β |
| 402 | **not built.** |
| 403 | |
| 404 | ## Open decisions |
| 405 | |
| 406 | - **The successor identity anchor** (Paul's design) β this doc holds the |
| 407 | slot; email is the fallback filler. |
| 408 | - Anchor **required** at space creation vs **nudged** until attached. |
| 409 | - Synced DEK: default-on for SiwA accounts vs opt-in ("Keep me signed in on |
| 410 | my other devices"). |
| 411 | - What identity-only recovery may show before a key anchor confirms: |
| 412 | recommendation **nothing** β the space list is metadata worth the same |
| 413 | protection, so rungs 3β5 reveal it only after approval/passkey/re-invite. |
| 414 | - Invite links: whether "include my name in the address" (name-in-path) |
| 415 | is offered as an explicit opt-in, or names stay fragment-only always. |
| 416 | - When the PAKE short-invite phase earns its build β and whether the |
| 417 | sidecar bot is a *sufficient* counterparty for unattended spaces or the |
| 418 | flow always needs a human approval option too. |
| 419 | |