Recovery: the account outlives its last device - #109
Merged
Conversation
runtime/RECOVERY.md is the round's design record: recovery is a DEVICE — a dormant member leaf whose secrets exist only in a sealed bundle — not a resurrection of a live device's identity. Dormancy is what kills bundle staleness (the G5 self-rotation finding never fires for a leaf that never authors), the K_p pickup answers the us-doc bootstrap chicken-and-egg SYNC.md parked, and single-use-consumed-at-restore makes the double-restore identity fork structurally impossible. Engine surface: recovery-kit-create bucket/file arms (generated 10-word EFF-short phrase with rejection sampling vs user passphrase — the exposure rule made structural: one slot per kit, the kind decides which), recovery-restore bucket/file arms (config-parameterized fetch before state exists; pickup-fork bootstrap verified, not assumed), recovery-consume (idempotent, provider-forked; dropbox refuses by name), recovery-kits / recovery-kit-revoke over a new additive us-doc recovery map. KpPayload.devices becomes directory ∪ grantees — the honest author set post-SYNC.md, without which a fresh account's K_p would name no real author. An empty doc-id on bucket-flush/pull now names the us-doc: the minimal unparking of us-through-the-bucket the restore requires. Gates: new 'just recover' battery (8 acts: bucket + file round trips against MinIO with a post-kit revocation epoch crossed before restore, wrong-phrase/wrong-passphrase/double-restore refusals asserted as classes, consume verified by object set-difference and idempotent retry); just pair, just resume, just check all green.
… bucket cycle The us-doc joins the flush/pull fan-out via the empty doc-id sentinel (flush: consume-retry, us-doc, pointer map; pull: us-doc first, then re-read the map — 'no partitions' can no longer stand in for 'no account'). Restore is a two-stage host ceremony (restorePrepare → consent → restore) because AttachSpec is remembered for the worker's life and a phrase must not rest there: bind with bindStore's own fail-at-bind validation (factored, not copied), engine born from recoveryRestore instead of stateResume/init, fan-out, first checkpoint, then consume — which is never fatal: consumePending rides SyncStatus and retries at the head of every flush cycle. Kit RPC (create/list/revoke) drives the ceremony's step-6 flush fan-out outside the store-hold. Secrets cross the port once and are nulled in finally. Devstore rows 54-60: create + phrase-absence scan, restore round trip with post-kit content, single-use, both refusal classes, injected consume-failure retried to success, the gdrive file kit end to end against the fake drive (the gd consume arm's promised coverage), and the us-doc riding the cycle. A new in-harness object store serves reads the recorder by design refuses. Matrix 60/60; demo deno check clean; invariants 9/9. Two findings recorded: a harness account built on the init-time partition is unreadable by every future device (rc-account-create now runs the real ceremony — embedders take note), and a name-key rotation strands a bucket-only lagging sibling on the us-doc until wire contact (pre-existing, SYNC.md territory; kit revocation makes it reachable — follow-up issue owed).
…he disaster, and the round's three pinned defects The solo page grows both kit ceremonies (the phrase displayed once behind an explicit confirm-dismiss with no timer; the file kind's three warning sentences and confirm field, delivered as a blob-URL download) hung off the storage sheet's bound view — the store-bound precondition made structural. The restore door exists TWICE: on the picker, and on the first-run fork — because a browser with no devices never sees a picker, and that is exactly the browser a real recovery happens on. Both walk one shared ceremony; the picker's ends at the ordinary startApp claim, the fork's reloads (the visor has already claimed by fork time). Kit lists render from the account's registry with revoke; the rebind caveat sits beside the choice it warns about. Three defects found by this track's gates, each now owned by a regression test: the page's nested-enqueue self-deadlock (a file-kit mint wedged the page-wide serialized chain forever — paintList now takes its chain position explicitly, and the revoke flow had the same bug unfired); the consume that outlived its checkpoint (internal driver mutations bypass the client-call debounce, and the pull fan-out's self-filter keeps a device's own flushed clear permanently out of its author's reach — every successful consume, create and revoke now checkpoints explicitly; devstore row 61, with a negative control); and the restored device's sheet arguing with its own announcement (now an e2e assertion: no kit, loudly, on the restored device's own list). The native battery gained the file-kit registry and own-view assertions the defects proved missing. RECOVERY.md carries the settled amendments (the consume-checkpoint discipline, the strand hazard); NOTES.md carries the round entry. Gates: e2e 27/27 (solo-recovery 10 acts, solo-recovery-file 8 acts), devstore 61 rows, just recover/pair/resume/check green, invariants 9/9, demo deno check clean.
lannbot
enabled auto-merge
August 25, 2026 05:24
…ows renumbered 57-64 behind the wave's 51-56; both scenario blocks kept in dependency order
This was referenced Aug 25, 2026
solo-erase flakes in full-suite runs: the erased device's IndexedDB database survives the erase
#112
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the parked us-doc-through-the-bucket item (runtime/SYNC.md) and implements #11's recovery-path body. Design record: runtime/RECOVERY.md (rulings settled with the owner, including the file-kit amendment); round entry in NOTES.md.
The shape
Recovery is a device, not a resurrection. The kit ceremony mints a dormant member leaf through the ordinary
enroll_devicepath — visible in the devices sheet, revocable like a lost phone — whose secrets exist only in a sealed bundle. Dormancy kills bundle staleness (the G5 self-rotation finding never fires for a leaf that never authors; the proven G4 tablet path with the tablet replaced by a blob). The K_p pickup answers the us-doc bootstrap chicken-and-egg: one pickup, sealed to prekeys that ride the bundle, bootstraps everything.Two kit kinds, one mechanism: a bucket kit under a generated 10-word phrase (argon2id → HKDF → both the object name and the KEK; the exposure rule made structural) and a file kit under a user passphrase (the custody exception, warned loudly, provider-neutral). Single-use, consumed at restore — the K_p's deletion enforces it even for a file we cannot delete: a second restore is a 404, never an identity fork.
The tracks
KpPayload.devices= directory ∪ grantees, the empty-doc-id us-doc sentinel, a newjust recoverbattery (8 acts).solo-recovery+solo-recovery-filee2e.Three defects found by the round's own gates, each now owned by a regression test
enqueueself-deadlock (a file-kit mint wedged the page-wide chain forever, masquerading as an engine bug).Known limitation recorded (follow-up issue on the way): a name-key rotation strands a bucket-only lagging sibling on the us-doc until wire contact — pre-existing SYNC.md territory that kit revocation now makes reachable.
Gates
just recover/pair/resume/checkgreen; devstore 61 rows; e2e 27/27 (solo-recoverydestroys the browser context and restores from phrase + re-entered credentials alone); invariants 9/9.Automerge is armed.