Phase B — The device roster: a slot is held by enrolment, per organisation #4

Open
opened 2026-09-14 15:41:38 +01:00 by cruelacid · 2 comments
Owner

Part 2 of 3 of the sessions/device-roster plan (14 Sep 2026). Follows #2.

Model

  • A device is a machine (one Obsidian installation; install-wide id in raw localStorage, random, no hardware data). Each vault keeps today's keychain id as its install id; identity sessions stay keyed on it, so nothing live moves.
  • A device slot is held by enrolment on a per-organisation roster, from the moment the device is added until removed — online or not. Sign-in never takes a slot; sign-out never frees one.
  • A new device tries to add itself at sign-in, on create and on join (enrol:true); background refreshes do not. A full organisation refuses the device (DEVICE_LIMIT) but keeps the membership; the device syncs that organisation once someone removes another device there, or adds this one explicitly. Incumbents are never evicted.
  • Routes: POST /api/account/devices (add self), DELETE /api/account/devices/:id (remove; closes that device's sockets for this organisation with 4003), PATCH …/:id {label}. Handshake refuses a device not on the roster with 4003.

Checkpoint

  • roster.test.ts: third device refused at enrolment while the two enrolled stay OPEN connected or not; remove frees, re-add works; same device in two organisations has one row in each; empty id refused; enrol:false never enrols; limit shrink keeps incumbents; DELETE closes only that device's sockets for that account; export/import round-trips
  • plugin device.test.ts (one device id across two vaults, two install ids; localStorage failure falls back), cloud-session.test.ts (reached-but-refused keeps the membership), connections only for enrolled memberships
  • e2e: second vault joins a 1-device organisation → membership present, not enrolled, no socket, the notice; remove the first device → add succeeds and syncs; first vault gets 4003 and the badge
  • mutation checks: enrolment without the count; upgrade without isEnrolled; enrol:false ignored; remove without closing sockets; device id read from the keychain first; refused membership dropped
  • docs: accounts (public), identity, key-storage, security-model, competition-relay (Devices subsection with sources), positioning (parity claim), commercial-model, support-runbook, manual-testing 12b, plan-phase14
  • deploy identity → eu1 → plugin into both vaults
  • live: Mind Palace and Test Vault 2 show one device with two vaults under it; a third device is refused on a free organisation at the limit and appears after a remove
Part 2 of 3 of the sessions/device-roster plan (14 Sep 2026). Follows #2. ## Model - A device is a **machine** (one Obsidian installation; install-wide id in raw `localStorage`, random, no hardware data). Each vault keeps today's keychain id as its **install id**; identity sessions stay keyed on it, so nothing live moves. - A device slot is held by **enrolment on a per-organisation roster**, from the moment the device is added until removed — online or not. Sign-in never takes a slot; sign-out never frees one. - A new device tries to add itself at sign-in, on create and on join (`enrol:true`); background refreshes do not. A full organisation refuses the device (`DEVICE_LIMIT`) but keeps the membership; the device syncs that organisation once someone removes another device there, or adds this one explicitly. Incumbents are never evicted. - Routes: `POST /api/account/devices` (add self), `DELETE /api/account/devices/:id` (remove; closes that device's sockets for this organisation with 4003), `PATCH …/:id {label}`. Handshake refuses a device not on the roster with 4003. ## Checkpoint - [ ] `roster.test.ts`: third device refused at enrolment while the two enrolled stay OPEN connected or not; remove frees, re-add works; same device in two organisations has one row in each; empty id refused; `enrol:false` never enrols; limit shrink keeps incumbents; DELETE closes only that device's sockets for that account; export/import round-trips - [ ] plugin `device.test.ts` (one device id across two vaults, two install ids; `localStorage` failure falls back), `cloud-session.test.ts` (reached-but-refused keeps the membership), connections only for enrolled memberships - [ ] e2e: second vault joins a 1-device organisation → membership present, not enrolled, no socket, the notice; remove the first device → add succeeds and syncs; first vault gets 4003 and the badge - [ ] mutation checks: enrolment without the count; upgrade without `isEnrolled`; `enrol:false` ignored; remove without closing sockets; device id read from the keychain first; refused membership dropped - [ ] docs: accounts (public), identity, key-storage, security-model, competition-relay (Devices subsection with sources), positioning (parity claim), commercial-model, support-runbook, manual-testing 12b, plan-phase14 - [ ] deploy identity → eu1 → plugin into both vaults - [ ] live: Mind Palace and Test Vault 2 show one device with two vaults under it; a third device is refused on a free organisation at the limit and appears after a remove
Author
Owner

Landed as b53a851 and deployed 14 September 2026: eu1 applied migration 7 (device-roster); identity unchanged; plugin installed into both local vaults. Checked: roster.test (6), accounts device-limit tests rewritten for the roster, plugin device/cloud-session/membership-shape tests, full multi-vault e2e green (53) with the new roster scenario on case2, 7 mutation checks each fail when inverted, docs updated incl. competition-relay 'How they count a device' and positioning parity. Open: the live check on the user's machine (one device with two vaults; third device refused at the limit, appears after a remove) — needs both vaults reloaded onto the new build; until then the shard refuses their pre-roster sockets at the 30 s backoff, which heals on reload.

Landed as b53a851 and deployed 14 September 2026: eu1 applied migration 7 (device-roster); identity unchanged; plugin installed into both local vaults. Checked: roster.test (6), accounts device-limit tests rewritten for the roster, plugin device/cloud-session/membership-shape tests, full multi-vault e2e green (53) with the new roster scenario on case2, 7 mutation checks each fail when inverted, docs updated incl. competition-relay 'How they count a device' and positioning parity. Open: the live check on the user's machine (one device with two vaults; third device refused at the limit, appears after a remove) — needs both vaults reloaded onto the new build; until then the shard refuses their pre-roster sockets at the 30 s backoff, which heals on reload.
Author
Owner

Live, 14 Sep: after reloading both vaults the roster on eu1 shows one device (Desktop (macOS)) on the user's organisation with Mind Palace and Test Vault 2 listed under it — one slot for two vaults, as designed. Google sign-in from the page confirmed working after 6732f0b (a pre-existing page race found on the way, not part of this phase). Still open: the third-device refuse/remove/add check on real hardware; the same loop is exercised end to end by the roster e2e test and by roster.test.

Live, 14 Sep: after reloading both vaults the roster on eu1 shows one device (Desktop (macOS)) on the user's organisation with Mind Palace and Test Vault 2 listed under it — one slot for two vaults, as designed. Google sign-in from the page confirmed working after 6732f0b (a pre-existing page race found on the way, not part of this phase). Still open: the third-device refuse/remove/add check on real hardware; the same loop is exercised end to end by the roster e2e test and by roster.test.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Nectenda/nectenda#4
No description provided.