Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2221,6 +2221,60 @@ by recovery; boot pull never awaited by unseal), the pairing battery's
chain act asserting zero duplicate chunk names across concurrent
sibling flushes, resume/pair/check green, invariants 9/9.

**Recovery: the account outlives its last device** (2026-08-25; design
record [runtime/RECOVERY.md](runtime/RECOVERY.md)). The round that
closed SYNC.md's parked us-doc-through-the-bucket and #11's recovery
body. The ruling: **recovery is a DEVICE, not a resurrection** — the
kit ceremony mints a dormant member leaf through the ordinary
`enroll_device` path (visible in the devices sheet, revocable like a
lost phone) whose secrets exist only in a sealed bundle; dormancy is
what 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), and the K_p pickup answers the us-doc
bootstrap chicken-and-egg (one pickup, sealed to prekeys that ride the
bundle, bootstraps everything; `KpPayload.devices` became directory ∪
grantees on the way, without which a post-SYNC.md account's pickup
named no real author). Two kit kinds, one mechanism: a BUCKET kit
under a generated 10-word EFF-short phrase (argon2id at a fixed
context salt → HKDF → both the object name and the KEK — the exposure
rule made structural: replicated copies get generated-secret slots
only; S3-only at this rev, names being locations only there) and a
FILE kit under a user passphrase (the custody exception, warned
loudly, provider-neutral; the owner's amendment — disallowing custody
would be paternalism). **Single-use, consumed at restore**: dormancy
ends at first authoring, double-restore is an identity fork against
the single-writer-per-name invariant, and a phrase captured during
the ceremony must be worthless afterward; the K_p's deletion enforces
single-use even for a file we cannot delete (a second restore is a
404, never a fork). The us-doc now rides the ordinary flush/pull
cycle via an empty-doc-id sentinel; restore is a two-stage worker
ceremony (bind with fail-at-bind, engine born from the bundle instead
of init, fan-out, checkpoint, then consume) ending at the claim —
colour, name, icon from the pulled profile. Three defects found and
pinned during the round, each with a regression owner: the solo
page's nested-`enqueue` self-deadlock (a file-kit mint wedged the
page-wide chain forever; the sheet's own "measured" comment had
recorded the symptom as an engine bug), the consume-outlives-its-
checkpoint strand (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 reach — every internal mutation now
checkpoints explicitly; devstore row 64 pins it with a negative
control), and the same stranding for kit create/revoke. Known
limitation recorded, follow-up owed: 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. Parked: Drive bucket kits, the PRF second-input slot, kit
migration on storage rebind, platform-posture migration for restored
devices. Gates: a new `just recover` battery (8 acts: both kinds
round-trip against MinIO with a post-kit revocation epoch crossed
before restore, refusals as classes, consume verified by object
set-difference and idempotent retry, the restored device's own-view
registry agreement), devstore 64-row matrix, e2e 27/27 (`solo-recovery`
destroys the browser context and restores from phrase + re-entered
credentials alone; `solo-recovery-file` catches the real download and
proves the wrong passphrase is one clean keyslot miss),
pair/resume/check green, invariants 9/9.

## Parked and candidate non-goals

- **Metadata privacy**: relays, push services, and origins see traffic
Expand Down
20 changes: 20 additions & 0 deletions demo/e2e/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import drawerOverflow from "./scenarios/drawer-overflow.ts";
import soloAccountStorage from "./scenarios/solo-account-storage.ts";
import soloOfflineSync from "./scenarios/solo-offline-sync.ts";
import soloPasskey from "./scenarios/solo-passkey.ts";
import soloRecovery from "./scenarios/solo-recovery.ts";
import soloRecoveryFile from "./scenarios/solo-recovery-file.ts";
import visorReset from "./scenarios/visor-reset.ts";
import firefoxSmoke from "./scenarios/firefox-smoke.ts";
import crossEnginePairing from "./scenarios/cross-engine-pairing.ts";
Expand Down Expand Up @@ -268,6 +270,24 @@ const SCENARIOS: Scenario[] = [
// follows (nothing in this suite currently depends on that, but nor
// did the observation cost anything to write down).
soloPasskey,
// ACCOUNT RECOVERY (runtime/RECOVERY.md's T-C gate) — the round's
// money shot, and the only pair of scenarios in this suite that
// DESTROY a browser context mid-story rather than merely reloading.
//
// THEY RUN HERE, after every storage scenario, because they consume
// all of it: an account, a bound bucket, a flush that actually landed,
// and then a wiped browser that has to find its way back to the
// account with nothing but a phrase (or a file) and the credentials
// the user remembers. A failure here with solo-storage and
// solo-offline-sync green says the fault is in the RECOVERY path — the
// kit ceremony, the restore bring-up, the consume — and not in the
// egress or the schedule it rides on.
//
// The phrase kind goes first: it is the record's primary kind, it
// needs no filesystem, and a failure in it makes the file kind's
// failure much easier to read.
soloRecovery,
soloRecoveryFile,
// THE TWO RELAY-PARTITION PINS, both expected RED and both SLOW —
// each spends minutes proving a heal that does not come (the
// freshly-paired ceremony wires never re-dial, and `conn-status`
Expand Down
Loading
Loading