Review: Devices (desktop & Android)
Review of the device fabric as described, under the
sovereignty litmus. Verdicts cite xenon-runtime main @b0cfc90 and the
authed-wake design doc; gap IDs (G-3…G-7) are this beat’s device-side register
and feed Part IV. The agent-layer gaps (G-1
resident agent, G-2 delegation) are held in the
agent-layer review.
The headline
The device fabric is the most-built layer of this beat, and its launcher crossing is one dispatch call short of complete. Desktops are proven full peers on all three platforms; the phone carries real membership, real consent, and a real audit chain. What is missing is not craftsmanship — it is reach: the phone can dial a desktop’s app list but nothing answers (P-188); a sleeping device usually cannot be woken because the transport that reaches it is the one transport that cannot be OS-socket-activated (G-4); and the reverse lifecycle — lost phone, new laptop — is thin. The fabric holds the device truth honestly; the gaps are all about crossing the gap between two devices.
What the layer does well, it does with unusual care: START_NOT_STICKY
sessions that never lie about being up, kill-vs-off audit distinctions,
device-name sanitization guarding three sinks at once, hosting-side grant
re-checks on every open, and a launcher that shows honest
CHANNEL_UNAVAILABLE copy rather than a fake empty list.
Desired vs current
| Want (decisions / packets) | Have (code @b0cfc90) | Delta |
|---|---|---|
| Mobile both-track: v1 phone-as-client, v2 Android runtime (d/062) | Companion APK: consume + launcher + membership/consent/audit; v2 runtime not in this beat | On track; v1 is real. The launcher list itself is not yet served (G-7). |
| Every desktop a full peer (P-206) | Linux/macOS/Windows all publish + consume native; byte-identical proofs | Holds. Windows caveats disclosed (P-201w headless, 0-app catalog). |
| Catalog IS the launch seam (d/104 PROPOSED) | On-device plane + apps.list protocol exist; no launch descriptor ratified | d/104 unratified — blocks P-202’s two frame cells and unparks P-188. |
| Authorized peer may wake a device it’s allowed to (P-123) | Honest disabled stub (P-169); crypto core designed | No code; the zero-resident-vs-reachability crux is unresolved (G-4). |
| Litmus: movable memory | Group doc syncs; app state + agent memory are per-device, local | No cross-device memory layer; phone can’t hold app state offline (G-5). |
Gap register (device slice)
G-3 — Device enrollment / recovery lifecycle
Exists: the forward path is solid — runtime enrollment + group-join over
iroh (lib.rs:120-171, group.rs:244), an offline-verifiable roster
(roster_from group.rs:657), immediate revocation for a live target, and
self-revocation detected via a dial refusal → self-revoked.json
(group.rs:438). docs/key-recovery-design.md exists on paper.
Why this needs design: the reverse / loss paths are thin. A lost phone’s
key can be revoked, but the group must re-mint invites; a new laptop
re-enrolls from scratch with no grant carryover (by design — reinstall =
new principal, capability-model §4); self-revoked.json is one-way with no
surfaced rejoin ceremony; there is no account- or social-recovery of the group
authority key itself.
Options / tradeoffs: (a) admin-re-invite as the blessed ceremony — simplest,
but a lost authority device is unrecoverable; (b) threshold / social recovery
of the authority key — resilient, heavier ceremony; (c) a surfaced rejoin flow
that clears self-revoked.json after a fresh invite. Ties to AW-2’s
statement-typed policy.
Decides: Luke — key-recovery ratification (litmus: exit without loss).
G-4 — Authed-wake, and the socket-activation crux
Exists: the design is delivered (P-123 → docs/xe-authed-wake-design.md);
no code ships beyond the honest disabled [Wake device] stub (P-169). The
proposal is tight: a roster-member peer may cause a bounded, idempotent
ensure_running on a target it is authorized to wake — nothing more — under
four invariants (W-1 wake carries no authority; W-2 sync never wakes / wake
never syncs; W-3 the runtime learns only a reason, never a payload; W-4
honest availability).
Why this needs design — the crux (§2.1 of the design): the peer transport
that shipped is iroh (QUIC/UDP + relay), and iroh cannot be
OS-socket-activated the way launchd or systemd can hold a plain TCP
socket and start a process on the first connection. The transport a peer uses
to reach a device is precisely the transport that cannot wake a zero-resident
device. So the only honest wake is a same-network, zero-resident desktop
over an OS-held plain socket; everything else the button honestly refuses
with an action. The wakeability matrix (design §2.2):
| Target state | Wakeable? |
|---|---|
| Desktop, same network, zero-resident | Yes (best-effort, OS socket activation) |
| Desktop, internet-only | No (relay cannot socket-activate) |
| Android, session active | Moot (already up) |
| Android, force-stopped | No (needs the unbuilt notifications transport — G-6) |
| Windows | No (no socket-activation primitive) |
| Powered-off | No (out of scope) |
Options / tradeoffs: (a) accept same-network-only for v1 and reserve a
relay-mailbox design for cross-internet wake later — honest, ships, defers the
hard part; (b) design the relay mailbox now — full reach, significant net-new
machinery on a route with subtle replay/amplification concerns; (c)
per-platform wake shims (launchd/systemd now, Android notifications + Windows
later). The crypto core is already designed either way (target-issued
WakeAdmissionV1 ≤24h cert, response-free WakeHintV1, durable at-most-once
replay admission, per-class amplification budgets).
Decides: Luke — AW-1 (accept the crux) and AW-2 (authorization source:
membership-derived + admission, or a group-doc Statement wake-policy type).
AW-3 (≤24h stale-revocation ceiling), AW-4 (Android/Windows non-parity for v1),
AW-5 (button honesty default; AW-5b default OFF off-trusted-LAN) round it out.
G-5 — Cross-device state sync / movable memory
Exists: the group document syncs by gossip/announce; peer-sync-v1-design.md
is a design; the on-device plane bridges live host sessions only.
Why this needs design: what actually syncs is the group doc plus anchors — not app data, agent memory, or the audit ledger, all of which are local per-device. The phone cannot hold app state offline because the plane is host-routed and lives only for the session. “Movable memory” is asserted per-document but there is no cross-device memory layer yet.
Options / tradeoffs: (a) extend peer-sync-v1 to named data anchors with last-writer or CRDT merge — general, needs conflict semantics; (b) a designated home device per data class with pull-on-demand — simpler, weaker offline story; (c) leave app data single-homed and document it (status quo).
Decides: Luke — peer-sync-v1 ratification (litmus: movable memory).
G-6 — Notification / wake paths per OS
Exists: the honest matrix above. Android with an active session is moot; Android force-stopped needs an unbuilt notifications transport (d/037 Q3); Windows has no socket-activation primitive; powered-off is out of scope.
Why this needs design: a sovereign push path for a dead Android process is the missing primitive behind both G-4 (Android row) and any “your agent needs you” prompt. The tradeoff is self-hosted vs. vendor: UnifiedPush (sovereign, self-hostable) vs. FCM (vendor, ubiquitous, opt-in).
Options / tradeoffs: (a) UnifiedPush self-hosted — on-thesis, more to run; (b) FCM opt-in — trivial reach, a Google dependency on the wake path; (c) both, user-selected. Decision 037 Q3 parked exactly this.
Decides: Luke — d/037 Q3, tied to AW-4.
G-7 — The unwired apps.list responder (P-188)
Exists: the concrete, code-shaped gap from
devices §3.5.
adopt_node never dispatches LAUNCHER_APPS_ALPN (group.rs:327-328); the
protocol is not accepted inbound (:233,272); the responder
serve_list_apps_extra (launcher_apps.rs:271) exists but has no caller. Only
the debug TCP adapter serves today. The launcher shows the reviewed
CHANNEL_UNAVAILABLE copy in production.
Why this needs design: this is the difference between a launcher that lists a desktop’s apps and one that shows an honest “not available” card. It is small and mechanical — but it is parked on d/104, because the shape it serves should be the ratified launch descriptor, not an improvised one.
Options / tradeoffs: (a) wire the responder behind the member-gated serve
loop now, using the current LauncherAppEntry shape — unblocks the demo,
risks re-shaping later; (b) ratify d/104 first, then wire once to the
descriptor — one implementation, gated on a decision. P-188 R1/R2 is written
for option (b).
Decides: Luke — d/104 ratification, then a sol lane implements.
The launch seam (d/104, PROPOSED)
decisions/104-cf-guest-launch-seam.md: the catalog IS the launch seam.
The launch descriptor is (app label → plane origin http://<app>.localhost:5454)
— no per-app baked guest URLs, one route on every platform. Prism’s fleet
catalog (P-197) already navigates controlled frames to arbitrary app origins on
desktop; Android’s fix is mechanical. Ratifying it blocks P-202’s two BLOCKED
cells (in-frame render, in-frame offline honesty) and unparks P-188.
Grounded in d/100 (plane origin) + d/103 (compose UIs are plane origins).
Reversal is cheap: if Luke prefers an explicit Prism/IWA install-time
guest-manifest contract, it is discarded before any dispatch. The descriptor
shape is a Part V candidate:
Launch descriptor (d/104).
Sovereignty litmus check
| Criterion | Verdict for the device fabric |
|---|---|
| Inspect what it sees | Holds. status/doctor reconcile cached vs. live truth without waking; the launcher states are honest (incl. CHANNEL_UNAVAILABLE); every failure string ends in an action. |
| Fork permissions | Partial. Membership roles (Admin/Member/Observer) and revocation are real and offline-verifiable; but per-device capability granularity for the agent is not here yet (G-2, agent review). |
| BYO models + keys | Holds for keys — device keys are local, group authority is a local signed document, no server. Models are out of the device beat. |
| Movable memory | Weakest point. Group doc moves; app data + agent memory are single-homed per device; the phone can’t hold app state offline (G-5). |
| Auditable actions | Holds on-device. Hash-chained session/launcher ledger (wake/kill/off/cross/list/launch/resume), tamper-evident. Caveat: 64-bit truncated hash, not externally anchored. |
| Exit without loss | Partial. A consumer removes a peer and keeps its data; but a lost device has no recovery ceremony beyond admin-re-invite, and a new device inherits no grants (G-3). |
Questions for the Luke + Jan session
- d/104 — ratify the catalog descriptor as the single launch seam? It
unblocks P-202’s two BLOCKED Android frame cells and unparks P-188 (the
unwired
apps.listresponder). Reversal is cheap — no code depends on it. - AW-1 / AW-2 — the authed-wake crux: accept “same-network zero-resident
desktop only; cross-internet asleep-wake is out of v1” (relay mailbox
reserved), and membership-derived authorization (Observers cannot wake) vs. a
group-doc
Statementwake-policy type? These are the only AW items needing real interview time. - G-3 recovery — what is the lost-phone / new-laptop ceremony? Admin-
re-invite is the current implicit answer; is threshold / social recovery of
the group authority key in scope? (Ties to AW-2 and
key-recovery-design.) - G-6 push — sovereign push for a force-stopped Android process: UnifiedPush self-hosted, FCM opt-in, or both user-selected? (d/037 Q3.)