The Design Frontier
This is the honest open edge. Every layer review in Part III ends with a gap register; this chapter consolidates all of them into one place, grouped by theme rather than by beat, so a reader can see the shape of what is undesigned across the whole system at once. Nothing here is hidden debt — where the mission demands something no decision yet covers, it is recorded as first-class work.
Read this against the sovereignty litmus: inspect what it sees · fork permissions · BYO models + keys · movable memory · audit actions · exit without loss. Three of those six clauses are only half-met today, by our own accounting, and they lead the register below. The point of naming them is not to apologise for them — most of this system is built and proven — but to keep proven and still-to-design visibly separate, which is itself a litmus commitment.
Two honest framings before the register:
- The agent layer is the mission and the least-built layer. The whole stack — transport, serving, the device fabric, the capability substrate — exists so a resident agent can act for you under bounds you can fork and revoke. That agent, as a bounded resident process, does not exist yet. This is the program’s central honest fact, carried below unsoftened (theme D).
- Two mission-level openings are owned by no single layer. Multi-person sharing (sharing an app or data with someone who is not you — distinct from the multi-OS-user gap PLAT-5 below) has no decision at all; the current plan defers it to the local-first camp audience. And the P-192 two-engine result is an asset, not a deficiency — the same signed bundle rendered on Blink and Servo, which is the argument that keeps Xe from being a single-vendor dependency, revisited the week of Jul 20. Both are tracked in the review agenda, not as gaps here.
An external review now sits beside this register. On 2026-07-18 an independent frontier
model — gpt-5.6-sol at xhigh — read the whole site adversarially and returned
six memos. Its single loudest ask is a precondition on two of the
entries below: publish an explicit threat model, trusted computing base, and authority
matrix before ratifying d/103-B (F1)
or d/104 (E1), because every security claim in the
stack currently rests on an unnamed boundary. sol’s positions are folded into the entries
below as External review input lines; the two gaps it surfaced that no layer review had
named are collected in Theme G. Where sol disagrees
with one of our own Part III reviews, both voices are kept —
the disagreement is the point.
The frontier at a glance
Weight marks who must decide and how reversible the call is: ▲ heavy — Luke, a litmus-, security-, or frozen-schema-touching call that is expensive to reverse; ◆ medium — Luke + Jan, a ceremony, scheduling, or placement call; ○ light — reversible, an orchestrator/Fable hygiene or surface call. Counts are canonical entries after de-duplication.
flowchart TB DF["The Design Frontier<br/>27 canonical gaps · 7 themes"]:::root DF --> A["A · Sovereignty spine<br/>3 gaps · the half-met litmus"]:::heavy DF --> B["B · Serving & membership<br/>5 gaps"]:::heavy DF --> C["C · Permissions & consent<br/>2 gaps"]:::heavy DF --> D["D · The agent layer<br/>2 gaps · mission & least-built"]:::heavy DF --> E["E · Platform coverage<br/>5 gaps"]:::mixed DF --> F["F · Operational / versioning<br/>8 gaps"]:::light DF --> G["G · Local trust boundary<br/>2 gaps · net-new from sol"]:::heavy A --> A1["A1 movable memory ▲"]:::heavy A --> A2["A2 exit-without-loss ▲"]:::heavy A --> A3["A3 audit / journal unification ▲"]:::heavy B --> B1["B1 membership-blind serving ▲"]:::heavy B --> B2["B2 v1-vs-v2 group doc ▲"]:::heavy B --> B3["B3 relay trust ▲"]:::heavy B --> B4["B4 ticket distribution ◆"]:::med B --> B5["B5 Windows tripwire ○"]:::light C --> C1["C1 in-frame permissions ▲"]:::heavy C --> C2["C2 frame↔shell contract ○/▲"]:::mixed D --> D1["D1 resident agent + BYO model ▲"]:::heavy D --> D2["D2 delegation / agent://act ▲"]:::heavy E --> E1["E1 d/104 launch seam ▲"]:::heavy E --> E2["E2 authed-wake + push ▲"]:::heavy E --> E3["E3 Windows backend engine ◆"]:::med E --> E4["E4 GPU / ML class ▲"]:::heavy E --> E5["E5 multi-OS-user plane ◆"]:::med F --> F1["F1 route-table + d/103-B ▲"]:::heavy F --> F2["F2 no-502 ○"]:::light F --> F3["F3 signing ceremony ◆"]:::med F --> F4["F4 update loop ○"]:::light F --> F5["F5 resource limits ○"]:::light F --> F6["F6 P-174 sequencing ○"]:::light F --> F7["F7 code / doc hygiene ○"]:::light F --> F8["F8 fork-maintenance cadence ◆"]:::med G --> G1["G1 browser request admission ▲"]:::heavy G --> G2["G2 connector / engine-API auth ▲"]:::heavy classDef root fill:#1f2933,stroke:#1f2933,color:#fff,font-weight:bold; classDef heavy fill:#f4c7c3,stroke:#c0392b,color:#3d0f0a; classDef med fill:#fdebd0,stroke:#d68910,color:#4d340a; classDef light fill:#eafaf1,stroke:#27ae60,color:#0e3d24; classDef mixed fill:#eaf2f8,stroke:#2874a6,color:#0a2540;
Sixteen of the twenty-seven carry a ▲ Luke call — fifteen outright, one (C2) split Fable ○ / Luke ▲ — and most of those are litmus-touching, which is the honest signal that the remaining design work is about sovereignty and trust boundaries, not plumbing. The plumbing is largely built. The two net-new Theme G entries are both Luke calls, and both are the same call: is an untrusted same-host process inside the threat model?
Theme A — The sovereignty spine (the three half-met litmus clauses)
These three lead because they are the clauses the first audience will check first. Each is a site-wide finding that individual layer reviews surface locally.
A1 — Movable memory: app data is single-home
Gap. Sync moves the group document and its anchors — not app data, agent memory, or the audit ledger, all of which live local per device (devices G-5). 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.
Options. (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, honestly labelled). Peer-sync topology is the costliest steer to reverse (d/062) once sync formats ship.
External review input. sol backs a replicated-home collection as the honest v1 (single-writer, signed epoch home-transitions, at least one durable-replica acknowledgement) before any generic multi-writer CRDT; a blanket last-writer-wins rule is unacceptable for opaque app data — it silently discards work and leans on clocks the design otherwise distrusts. — sol · devices §5
Decides: Luke ▲ — peer-sync-v1 ratification (litmus: movable memory). Source: devices review G-5.
A2 — Exit without loss: xe export is intent, not a proven round-trip
Gap. Two faces of the same clause. (i) Per-app data: uninstall forces a data
disposition (VerifiedExportThenDelete | RetainDetached | DestructiveDeleteConfirmed)
and XAA archives back update snapshots, but there is no first-class xe app export
command surfaced, and the uninstall browser-storage sweep “does not independently prove
localStorage emptiness”
(app-model AM-5).
(ii) Device / key recovery: a lost device key has no recovery lifecycle — a new laptop
re-enrolls from scratch with no grant carryover, and self-revoked.json is one-way with
no surfaced rejoin ceremony
(devices G-3).
Options. For (i): add xe app export on the existing XAA machinery + a DOMStorage
receipt so uninstall can prove the sweep. For (ii): admin-re-invite as the blessed
ceremony (simplest; a lost authority device is unrecoverable) vs. threshold / social
recovery of the group authority key (resilient, heavier) vs. a surfaced rejoin flow.
External review input. sol wants the clause held to a proof, not an intent: a versioned portable-state container (catalog pins, identity, config, grants, journal refs, encrypted volume snapshots, per-app export hooks) with a fresh-machine export→import round-trip and an independent reader before exit-without-loss is called met — and re-origining an app is explicitly not user-data migration. — sol · mission §3 · sol · app-model §3
Decides: (i) orchestrator ○ for the export surface; (ii) Luke ▲ for key-recovery ratification. Source: app-model AM-5 · devices G-3.
A3 — Auditable actions: some journals sit outside the hash chain
Gap. Some grant/trust records are standalone JSON-lines logs sitting outside the
hash-chained audit journal, so the console’s Activity view cannot witness them. Worse,
the discipline is implemented twice — the companion’s Kotlin audit chain and the
agent-layer AuditEntry.prevHash are the same tamper-evident, 64-bit-truncated,
un-anchored idea built independently
(agent-layer convergence note).
The product should land one ledger discipline, not two subtly different “audit logs.”
Options. (a) Fold the standalone logs into the hash-chained journal as a work packet and converge both implementations on one discipline; (b) leave them separate and document the seam. Only (a) closes the inspectability gap.
External review input. Unifying the logs is necessary but not sufficient: sol shows a hash chain is tamper-evident only against a trusted head, so without signed, periodically-anchored checkpoints and cross-device head gossip it catches corruption, not adversarial tampering (truncation, rollback, wholesale replacement, and forking all survive it). The 64-bit truncation also needlessly weakens collision resistance — keep the full SHA-256. Converge the companion, agent, and shell journals on one full-hash, signed-checkpoint discipline with per-device chains whose heads devices witness for one another. — sol · devices §4 · sol · shell §1
Decides: Luke ▲ — approve journal unification (litmus: auditable actions). Source: agent-layer one-ledger note.
Theme B — Serving & membership
The serving beat’s honest one-liner: the serving path is membership-blind. A fully built, signed, revocation-capable membership plane sits one crate away from a serving tunnel that authenticates with a bearer secret and never consults it. Everything in this theme is downstream of that.
B1 — Membership-blind serving
Canonical entry — the transport and peer-serving reviews frame the same seam as G4 and G1.
Gap. Authentication for peer serving is a 32-byte bearer secret in a ticket file;
the serve tunnel builds IrohProvider::new, never with_membership. A device removed
from the group keeps serving and consuming for as long as it holds a live ticket —
enrollment and authorization-to-serve are different graphs today. The route secret
has no revocation list, epoch, or bounded lifetime; the only “permission change” is
restarting the publisher, which strands every consumer. (The
transport review
frames this as the identity gap G4; the
peer-serving review
frames the same seam as the revocation gap G1. They are one decision.)
Options. (a) Gate the serve ALPN on the existing MembershipRegistry — a small
change; group revocation then cuts serving tunnels for free, and this collapses B1, B4,
and much of B3 at once; (b) keep bearer tickets for the “share to a non-member” case and
add membership as an additional gate; (c) a bounded-lifetime (exp) ticket now to
unblock durable secrets while the fuller model is decided; (d) UCAN capabilities with
exp + revocation — heavier, and partly duplicates the group plane, which is already
a capability system. The review’s offered verdict: gate on membership first; reserve
UCAN for the one thing membership cannot express, delegation to a non-member.
External review input. sol narrows the review’s verdict: group membership is an
authenticated ACL, not a capability system, so gate on membership and a per-route,
audience-bound signed grant (compare the grant’s audience to the already-authenticated iroh
remote key) — membership alone still lets any member who learns the address reach every
exposed route, and a copied bearer secret stays usable. “Reserve UCAN for non-members” is the
wrong cut; the real boundary is transitive delegation/attenuation. Ship gating on a new
ALPN xenon/serve/tcp/2, never by mutating /1 in place. —
sol · transport §2
Decides: Luke ▲ — the sovereignty spine of the beat; effectively irreversible once tickets are in the wild. Source: transport G4 · peer-serving G1 · the UCAN question.
B2 — Which group document is canonical (v1 vs v2)
Gap. Two group-document lineages exist: v1 in crates/net/group (what gossip and the
membership gate compile against) and v2 in crates/xe-sync (MemberKind{Device, Companion},
endpoint bindings, v2 signing domains). Which is canonical for shipped serving is undecided,
and Companion has no serving semantics at all. This blocks B1 — you cannot gate serving
on a membership plane before deciding which membership plane.
Options. (a) v1 is the line for shipped serving, v2 folds in later; (b) v2 is canonical and serving waits on it; (c) settle the split explicitly and document the migration.
External review input. sol agrees: ship v1 for the first gated path (“a larger version
number is not a readiness argument”), but front it with a lineage-independent authorization
interface and one authoritative adapter, require a signed one-way v1→v2 transition that
binds the v2 genesis to a final v1 head, and never authorize from the union of both
lineages — and don’t admit Companion until its publish/consume rights are normative. —
sol · transport §3
Decides: Luke ▲. Source: transport review, questions 1 & 3.
B3 — Relay trust model
Gap. With no relays configured, iroh selects the number0 public relay set; group
documents can pin relay_endpoints under the authority signature, but peer-serve takes
relays from unsigned CLI arguments only. Relays see connection metadata (who talks to
whom, when, how much) and are a third-party availability dependency. No fallback policy is
stated anywhere.
Options. (a) Agent54-run relay(s) pinned in the signed head; (b) explicitly document
acceptance of n0’s relays, direct-preferred with relay fallback; (c) relay-optional
(hole-punch-only) for LAN-local fleets. Ships alongside scheduling the self-hosted
iroh-relay that d/014 requires before
production.
External review input. sol reinforces that “no central hub” holds only for device-state topology — catalog root, forge, release, and relays stay centralized operational dependencies — and that the relay’s authentication prerequisites belong in the protocol profile, not in the unsigned CLI args peer-serve reads relays from today. — sol · transport §4
Decides: Luke ▲ — infrastructure plus a trust boundary. Source: transport G3.
B4 — Ticket distribution channel
Gap. Publish prints only ticket_path=; the operator moves the bearer-secret file out
of band. Custody is meticulous at both endpoints and undefined in the middle — the
secret’s entire security rests on an unspecified transfer channel, which also leaves no
audit record.
Options. (a) Deliver tickets over the already-authenticated group gossip lane (member→member, in-band, encrypted); (b) QR handoff reusing the invite-QR machinery; (c) keep out-of-band and document the threat model honestly. B1(a) + a member-to-member path dissolve this gap entirely for members.
External review input. sol frames delivery as a first-class protocol question even after bearer secrets disappear: how route grants or non-secret discovery records are delivered, acknowledged, audited, refreshed, and withdrawn — the channel needs an audit record, not merely careful custody at the two endpoints. — sol · transport §6
Decides: Luke + Jan ◆ — UX + custody. Source: peer-serving G5.
B5 — Windows exposure-tripwire asymmetry
Gap. On Unix, opening an over-shared ticket refuses with a chmod 600 instruction;
on Windows the same condition is silently repaired (the ACL is narrowed and reading
proceeds). Refusal is a tripwire — “something widened access to your credential” is a signal
the operator should see; repair hides it.
Options. (a) Refuse by default on a non-owner ACE with an explicit --repair opt-in
(Unix-symmetric); (b) refuse + audit the observed ACL; (c) keep repair but emit a loud audit
record.
Decides: Fable ○ (reversible, low blast radius) — but it wants a taste call. Source: peer-serving G2.
Theme C — Permissions & consent
Where the phrase “your agent answers for you” is either made real or conceded.
C1 — The in-frame permission model
The headline gap of the shell layer.
Gap. Today only geolocation, only in the development driver, same-origin. The installed Controlled-Frame driver deliberately refuses rather than fake a result when it cannot mint a lease or journal the answer — honest, but it means the whole non-geolocation set (camera, microphone, clipboard, USB) is undesigned. Three open questions are each a sovereignty question: who prompts (the shell, or Chrome’s native dialog inside the partition), who remembers (inspectable, revocable per-origin persistence?), and the capability set itself.
Options. A — shell brokers everything via the agency ladder, shield-by-default, journaled: inspect what it sees + auditable both met, but real browser-driver engineering per capability. B — delegate to Chrome: nearly free, already works, but cedes “your agent answers for you” at exactly the surface where users feel it most.
External review input. sol endorses shell-brokering but lands on a hybrid with
browser-process enforcement: Prism owns policy, explanation, shielding, persistence, and
journaling; Chrome/OS owns final device selection and enforcement; the effective outcome is
the intersection, never the union. It also raises Option A’s cost — shield-by-default is
infeasible via injected JS, because the Controlled Frame permissionrequest API can only
allow/cancel, so an approximate location or virtual camera needs a Xenon browser-process
permission delegate or resource portal, not a JS patch. Journal every answer and
periodically sign and anchor the journal head. — sol · shell §1
Decides: Luke ▲ — litmus-touching; option A is the entire product differentiator. A design packet should precede any implementation. Source: shell G6.
C2 — The frame↔shell communication contract
Gap. The surface is deliberately asymmetric and minimal: app→shell is a four-verb,
nonce-authed channel (tab.open/close/reload, key); shell→app is a capability-leased
gateway. There is no contract for an app to set a window title, raise a
notification, request focus, or report liveness — a guest sets document.title and the
shell has no defined reaction.
Options. (a) Extend the nonce-authed verb set with a small app-declared surface (title / notify / badge) — ergonomic, but every verb is a capability whoever holds the port then holds; (b) keep apps fully unprivileged and derive what the shell needs by observation — tiny boundary, but apps cannot cooperate.
External review input. sol would split the surface into two specs — a cooperative
lifecycle/presentation RPC and a separately-audited privileged automation gateway
(readDom / request-rewrite / CDP are shell authority over a guest, not an app contract) —
and drop raw tab.open{url} (it conflicts with a descriptor-constrained launch model)
and key (too generic). The nonce is message validation, not app authentication: any
holder of the port has the capability, so bind the port to frame_id + document_epoch + app_id and re-key it on every committed navigation. — sol · shell §3
Decides: Fable ○ proposes the verb-set delta; Luke ▲ on anything touching user-visible identity (notifications). Source: shell G2.
Theme D — The agent layer
The agent layer is the mission and the least-built layer. The whole system exists so a resident agent can act for you, under your inspection, bounded by capabilities you can fork and revoke. That agent, as a bounded resident process, does not exist yet. What exists is a real capability-and-consent substrate and a real research-preview browser (darc) whose only live boundary is a human approving each tool call.
This is not a criticism to soften; it is the program’s central honest fact. Both gaps below are downstream of it.
D1 — The resident agent itself (darc → Reagent)
Gap. darc is real but unbounded: the agent sidebar and the untrusted page share one
IWA shell with no capability check between them, the CDP proxy forwards '*' so
Runtime.evaluate passes unconditionally, and daemon/main.js is 0 bytes. And darc’s model
IDs are hardcoded — the BYO-models litmus
violation the kickoff flagged, live in the tree. Open: where the agent physically lives (in
the shell now, or a real daemon/), what it may see and do, and how BYO-model wiring replaces
the hardcoded IDs. Until the lease-signing root (D-5) is answered, a “signed Lease” is
aspirational — the shell shares an address space with the forgeable iwa: IPC channel.
Options. Injection-boundary D-1: ship the boundary in the shell now (faster, keeps agent and page co-resident) vs. stand up the daemon first (the honest boundary, net-new). D-5: a separate signer process vs. the OS keystore vs. deferring signed leases. BYO model: a model-selection capability vs. a config file vs. status-quo hardcoding.
External review input. sol judges the shell an indefensible home for the boundary and
proposes a five-process split (agent daemon · policy/signing service · browser broker ·
shell-as-UI · page + model, both untrusted), with the lease-signing root and model credentials
out of the shell and the console.log('iwa:…') channel retired for an OS process boundary
with peer credentials. Human per-call approval is inspection, not an authority bound — it
fails under click-fatigue, injection, and TOCTOU, and does not constrain what the approved
primitive can do. Exclude raw CDP, execute.script, and persistent injection from v1;
start with a small brokered set (visible-text read, one-origin navigation, constrained element
activation). — sol · devices §3 · sol · shell §4
Decides: Luke + Jan ▲ — the injection-boundary D-1…D-7 burn-down. Source: agent-layer G-1.
D2 — Delegation model: per-action consent vs. standing grants
Gap. The substrate is genuinely UCAN-adjacent — xenon-cap capabilities, xenon-grant
authority-signed grant bundles with onward:false, shipped ConsentAttestation. But the
capabilities[] → plan table → minted grant pipeline is parsed, not wired; the agent
family (agent://act) is declared + audited only, not enforced; leases are designed but
not minted. You cannot yet fork or revoke the agent’s permissions because it holds none.
Options. Make the UCAN alignment an explicit product decision, not an implementation
detail: decide whether Reagent’s authority is the xenon-cap/xenon-grant substrate (it
should be — Grant = long-lived delegation, Lease = attenuated invocation, onward:false = a
no-proof leaf), and whether agent://act is enforced in v1 (honest, couples the agent
milestone to the grant-minting pipeline) or stays declared+audited (ships sooner, boundary
advisory). Also CM-1 (v1 grantable set) and CM-2 (outbound network: declared-only vs.
block-catalog).
External review input. sol finds a live contradiction in the model: onward:false
conflicts with minting a lease under a grant — a lease that hands authority to the agent
is the onward delegation the grant forbids. Resolve it by modelling the lease as an
audience-bound invocation, not a delegation (the install principal signs an exact request to
a named executor; model and page stay untrusted requesters holding no capability). Attenuation
must be proved by a deterministic subsumption algorithm (resource containment, an
ability-implication lattice, per-constraint partial orders, fail-closed on unknowns) with
conformance vectors — a signature proves issuance, not narrowing. Revocation has an unavoidable
availability boundary: publish a maximum stale-authority window, decide whether executors
fail closed past it, and requalify “verifiable offline” as verifiable against a named signed
snapshot under a declared staleness ceiling. Drop the “nonce is CID-equivalent” claim. —
sol · devices §1
Decides: Luke ▲ — CM-1…CM-7. Source: agent-layer G-2 · outline in Part V.
Theme E — Platform coverage
The device fabric is the most-built layer of its beat; its gaps are about crossing the gap between two devices and reaching platforms the container/wake model does not yet cover.
E1 — The d/104 launch seam — PROPOSED
Canonical entry — folds the shell review’s launch-seam item and the devices review’s G-7.
Gap. The launch descriptor — (app label → plane origin http://<app>.localhost:5454),
no per-app baked guest URLs, one route on every platform — is already the code on desktop
(Prism’s fleet catalog navigates Controlled Frames to arbitrary app origins), but blessing it
as the canonical cross-platform seam is a PROPOSED decision awaiting Luke. It surfaced when
an installed Android Prism could not repoint its baked guest to the plane origin. The same
proposal unblocks two things: Android’s two BLOCKED frame cells (in-frame render, in-frame
offline honesty) and the parked apps.list responder (P-188 — adopt_node never dispatches
LAUNCHER_APPS_ALPN; the responder exists with no caller). P-188 is parked on d/104 precisely
so it wires the ratified descriptor shape, not an improvised one.
Options. (a) Ratify the catalog descriptor as the seam — reversal is cheap, no code depends on it; (b) hold out for an explicit Prism/IWA install-time guest-manifest launch contract.
External review input. All three memos that touch d/104 agree it is “not ratifiable as
written.” Keep the descriptor as the seam but replace label → plane_origin — a mutable
label must not be the security principal. Bind launch to an immutable app_id +
release_digest + mutability_tier resolved by an authenticated plane router; hand Prism a
resolved, authenticated launch capability, not an arbitrary URL; and constrain
post-launch navigation (redirects, window.open, script navigation) via a Xenon
browser-process navigation guard (NavigationThrottle) bound to a verified LaunchGrant —
validating only the first URL leaves redirects outside the boundary. d/104 also embeds an
unresolved HTTP-vs-HTTPS Controlled Frame portability decision that ratification must
settle. — sol · mission §4 · sol · shell §2 ·
sol · devices §7
Decides: Luke ▲ — ratify d/104. Source: devices launch-seam · devices G-7 · shell desired-vs-current, launch seam · shape in Part V.
E2 — Authed-wake and the socket-activation crux
Canonical entry — folds devices G-4 (wake) and G-6 (per-OS push paths).
Gap. The peer transport that shipped is iroh (QUIC/UDP + relay), and iroh cannot be
OS-socket-activated the way launchd/systemd can hold a plain socket and start a process
on 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 socket; internet-only, Windows, and force-stopped
Android all honestly refuse today. The missing primitive behind the Android row is a
sovereign push path for a dead process (G-6).
Options. (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, net-new machinery with subtle replay/amplification concerns; (c) per-platform
wake shims. For the Android push primitive:
UnifiedPush self-hosted (on-thesis, more to run) vs. FCM opt-in
(trivial reach, a Google dependency on the wake path) vs. both, user-selected. The crypto core
(≤24h WakeAdmissionV1, response-free WakeHintV1, at-most-once replay admission) is already
designed either way.
External review input. sol backs same-network v1 but sharpens the wording: it is process activation, not wake (a kernel-held socket starts a verifier only while the OS is awake), and a relay mailbox is not an activation mechanism — nothing polls it. The sovereignty-fitting shape is a user-controlled home broker doing authenticated Wake-on-LAN plus contentless push hints (self-hosted UnifiedPush / opt-in FCM carrying only “check your mailbox”). Split the Android row into “process killed / background-restricted” and “user force-stopped” — normal push does not override a force-stop. Reserve the mailbox format; don’t build it until a target-side wake consumer exists. — sol · devices §2
Decides: Luke ▲ — AW-1 (accept the crux), AW-2 (authorization source), and d/037 Q3 (the push primitive). Source: devices G-4 · devices G-6.
E3 — No backend exec bridge for Windows (WSL2)
Gap. The connector exec bridge is implemented for smolvm and colima only. WSL2 and ProvisionedVm have no bridge — Windows backend-carrying apps are unsolved (the serve and in-frame consume legs are proven; a backend behind the app is not). A genuine hole, not a rough edge.
Options. (a) Add a WSL exec bridge; (b) publish a Windows named-pipe socket contract mirroring d/094. Trade-off: every new engine widens the socket-custody surface the launcher must own.
External review input. sol treats this as a real three-way (WSL exec bridge / versioned
named-pipe agent / explicit deferral) and warns the named-pipe design must not expose
Docker’s admin pipe — a narrow, launcher-authenticated Xe engine-agent protocol resolving an
opaque backend handle. A catch that also bounds the shipped macOS/Linux bridges: the
sh+nc exec bridge is not a general unmodified-container contract — distroless/minimal
images ship neither, so “unmodified backend” is already untrue for a real class of images and
the compose profile/probe must say so. — sol · app-model §5
Decides: orchestrator ○ for the reversible architecture; Luke + Jan ◆ on whether Windows-with-backend is in scope now. Source: engines EB-2.
E4 — GPU / ML backend class collides with the isolation subset
Gap. The safe-compose subset forbids exactly what GPU passthrough needs — raw
devices: and privileged are both on the normative refusal list. Immich dropped its ML
service to install at all (the honest P-207 stop). So a GPU-bearing backend — including a
BYO local model backend, which ties this straight to the
BYO-models litmus — collides head-on with the
validator.
Options. (a) A narrow, separately-consented device-grant carve-out; (b) route ML backends to a dedicated GPU placement outside the container subset. Any device access punctures the compose-subset trust boundary.
External review input. sol is firm that raw devices:/privileged must not be
admitted just because the user clicks a stronger dialog — GPU access should be a typed
placement capability (hardware.gpu/compute, with device class, sharing mode, reason) mapped
by the placement layer, or a dedicated ML placement (GPU VM / broker) outside the compose
profile. And Immich’s dropped ML service must be represented as an explicit Xe
feature-delta, not silently presented as the complete upstream release. —
sol · app-model §5
Decides: Luke ▲ — litmus / security-touching. Source: engines EB-4. Cross-ref D1 BYO-model.
E5 — Multi-user machines / per-user plane
Gap. 5454 is a single global loopback port; data roots are already per-user, but the
port is fixed, so two OS users on one host collide on 5454. (This is multi-OS-user on one
machine — distinct from multi-person sharing, which has no decision at all and lives in the
agenda.)
Options. Per-user port derivation (hash of UID into a range), with the origin string carrying it — but the origin is the browser’s storage/service-worker key, flagged (SEQ-1) as the single most expensive thing to change later; or first-user-wins + an honest doctor for the second user.
External review input. Whatever the port story, sol says stabilize the canonical origin first — derive it from package + instance identity, never a mutable display name — because the origin string is the browser’s storage/service-worker key and (per SEQ-1) the most expensive thing to change once users have accumulated origin-scoped state. — sol · app-model §4
Decides: Luke + Jan ◆. Source: serving G4.
Theme F — Operational / versioning
The long tail: schema evolution, honesty-contract refinements, ceremony scheduling, and the reversible hygiene the reviews flagged. Most are orchestrator/Fable calls; two need Luke or Luke + Jan.
F1 — Route-table evolution & the d/103-B whole-origin route type — PROPOSED
Canonical entry — folds serving G1 + G5 and app-model AM-4.
Gap. The route-table type discriminator is frozen (P-148: static-digest | backend-proxy | peer). The whole-origin compose case (a container service whose entire UI is the app) ships
as a backend-proxy overload — empty digest + prefix:"/" (P-203). Whether that overload
is blessed as the permanent shape or a distinct additive container-origin type is added is
d/103-B, PROPOSED, pending Luke — the first real test of how the frozen table grows. There is
no explicit v0→v1 migration path today (additive-only + fail-closed is the de-facto rule). Gates
P-210 (the Immich compose UI).
Options. (a) Accept the backend-proxy overload as documented (schema-conservative, already
tested); (b) add container-origin as an additive type (cleaner invariant: backend-proxy stays
“static digest + proxied prefix”) — this is the schema change requiring ratification. On growth:
additive-only + fail-closed, or explicit schema-version negotiation + a migration journal (there is
already a migrations vector precedent in the tombstone file).
External review input. sol calls “frozen but considering a fourth type” not a workable
evolution strategy: keep xe/origin-routes/v0 permanently at three types and put
container-origin in xe/origin-routes/v1 with an explicit rollout protocol
(reader-capability advertisement, dual-read order, atomic generation+digest, last-known-good
rollback, and a table-wide 503 Plane upgrade required rather than a misleading per-route
404). Take the distinct type, not the overload — overloading backend-proxy hides the shift
from an /api request boundary to an active-content publication boundary the VM does not
contain — but note a distinct tag is cosmetic unless it also gets a distinct policy/validation
path. — sol · serving §4 · sol · mission §4
Decides: Luke ▲ for the frozen-type addition; orchestrator ○ for reversible field
additions. Source:
serving G1 ·
serving G5 ·
app-model AM-4.
F2 — The no-502 question
Gap. There is no 502 today: upstream backend/peer failures are synthesized as 503
(a plane-side “can’t reach”), and a genuine upstream 5xx passes through unchanged. Distinguishing
“plane can’t reach upstream” (503) from “upstream errored” (502) is a deliberate addition to the
honest-error contract, not a bug.
Options. (a) Add the 502 distinction; (b) keep the single synthesized 503 and document it.
External review input. sol calls the 503 collapse defensible but a telemetry hazard:
reserve 502 for a protocol-invalid or absent upstream response and 504 for upstream
timeouts, pass a genuine upstream 500 through unchanged, and — whatever the status policy —
add machine-readable Xe-Plane-Error + Xe-Request-Id headers with Cache-Control: no-store on every synthesized error, so automation need not parse mutable English bodies.
(sol also caught the contract’s self-contradiction: the 400/405 rows carry no next-action
copy.) — sol · serving §4
Decides: orchestrator ○ (contract refinement), surfaced to Luke if it changes user-visible copy. Source: serving no-502 question.
F3 — Production signing & app-identity ceremony
Canonical entry — folds app-model AM-3 and shell G1.
Gap. The trust chain is closed in code, but the production signing keys do not exist yet —
the orchestrator holds only dev/build-phase seeds (d/066), and every install literally reads
“(DEV-SIGNED)”. Separately, compose apps are always UnsignedDerived (identity = URL + subdir,
no publisher key), with no per-publisher key pinning across updates. The offline split-custody
ceremony (Luke + Jan) gates all public distribution.
Options. (a) A single offline-root ceremony at world-release (current plan) — DEV until then; (b) an intermediate cross-signed release tier before public. And: does compose’s identity get a publisher-key story, or stay URL-derived?
External review input. sol says scheduling the ceremony does not answer governance: move catalog trust to a TUF/Uptane model — offline threshold roots, delegated publisher roles, snapshot/timestamp metadata, rollback+freeze protection, key rotation, compromise recovery, and user-selectable roots — and give compose apps a real publisher-key story instead of leaving identity URL-derived. — sol · mission §3 · sol · app-model §3
Decides: Luke + Jan ◆ — key custody is a hard rule; d/066 is a bounded build-phase override. Source: app-model AM-3 · shell G1.
F4 — App updates have no user loop
Gap. crates/apps/src/update.rs is a complete engine — semver-gated discovery,
escalation-detecting diff, snapshot → stage → activate → rollback-on-failure, an Auto | Notify | Manual policy where only patch, non-escalating updates auto-apply. There is no CLI or
consent-surface wiring driving any of it; the update loop is unproven end to end.
Options. (a) Wire xe app update + consent-queue integration and prove one
update-with-rollback; (b) leave until an app needs it.
External review input. sol wants the loop built as an authorization + recovery protocol,
not a CLI wrapper: bind a canonical UpdateAuthorization/v1 (from/to release digests,
diff_digest + ruleset version, provenance-bundle digest, rollout/rollback policy, expiry,
nonce) into the gate and recompute + re-verify it immediately before activation; layer TUF
metadata over it; and ship two distinct rollbacks (failure-triggered atomic soak;
user-triggered with explicit data-loss disclosure). Auto needs a signed policy object plus a
full non-escalation checklist, and a data-schema/migration declaration — volume-only restore
is not a real rollback contract. — sol · app-model §3
Decides: orchestrator ○ (surface + one end-to-end proof). Source: engines EB-1.
F5 — Resource limits are declared, not enforced
Gap. The XAM ResourceEnvelope is validated at install (request ≥ required_minimum), but the
compose adapter sets ResourceEnvelope::default() for every compose component and does not map
deploy.resources.limits into the envelope or into container cgroups. The envelope is advisory, not
runtime-enforced.
Options. Map deploy limits → envelope → engine --memory/--cpus.
External review input. sol wants resource/isolation declarations made executable engine policy: map normalized CPU/memory/PID/disk/restart/network/volume limits into each backend, verify effective settings after creation, and fail when a backend cannot enforce a required minimum — an unprivileged container can still exhaust memory, PIDs, CPU, or disk and deny service to co-tenants of the same engine. — sol · app-model §7
Decides: orchestrator ○ + a small spec addition. Source: engines EB-5.
F6 — Compose-validator holes / P-174 sequencing
Gap. The normative refusal list fails closed, but P-174 (open) documents three pre-existing
holes: short-form host ports (ports: ["8080"]), non-UTF-8 corruption in interpolate_declared,
and network_mode: container:. These matter because P-174 gates running compose on
user-supplied manifests — the guestbook proofs used authored manifests; a third-party manifest
is a different trust posture.
Options. (a) Dispatch P-174 to close the holes before P-210 (Immich, a third-party compose); (b) accept the holes as known-and-bounded and sequence P-210 first. Recommendation: P-174 first.
External review input. sol warns that closing the three recorded holes is not enough:
an allow-by-default parser plus a denylist inherits every new Compose feature as implicitly
permitted, and it enumerates many more vectors (gpus:, device_cgroup_rules, volumes_from,
use_api_socket, extends/include/anchors, configs/secrets, alternate build contexts,
implicit .env, hidden profiles). Expand P-174 into a versioned safe-profile project — an
allowlisted deployment IR, parser-differential tests, adversarial fixtures, fuzzing, and a
post-creation inspection of the created objects before start — and treat Compose as an
import language, not the deployment authority. — sol · app-model §2
Decides: Fable ○ to dispatch; confirm sequencing with Luke. Source: engines EB-3.
F7 — Code & doc hygiene bundle
Canonical entry — folds app-model AM-1 + AM-2 and engines EB-6.
Gap. Three reversible cleanups the decisions already settled: (AM-1) RecipeOperation::MarkUi’s
comment still says “Controlled Frame UI” and compose_components maps ui → RuntimeDriver::Cf,
contradicting d/103-A’s plane-origin rendering; (AM-2) two dev-signed swbn example apps
(scratchpad, appdex) are residue of the earlier per-app-IWA direction and don’t model the d/101
default cleanly; (EB-6) docs/serving-and-placement-design.md is stale — it still calls
backend-proxy “dormant … no listener owns 5199,” which shipped P-203 contradicts at every clause.
Options. Reconcile MarkUi to plane rendering; migrate or relabel the swbn examples; refresh the stale doc or supersede it with this site as live truth.
External review input. sol adds a naming hazard to the pile: the XAM document family
conflates three incompatible formats under package.json / .xam.json, and a byte-for-byte
Compose file named guestbook.xam.json trains tools and authors to trust an extension with no
reliable meaning — rename it now, and split recipe / manifest / lock into distinct
discriminated, independently-versioned names. — sol · app-model §1
Decides: orchestrator / Fable ○ (the decisions are made; this is hygiene). Source: app-model AM-1 · app-model AM-2 · engines EB-6.
F8 — Chromium-fork maintenance cadence
Gap. agent54/android-iwa is deliberately a patch series, not a full fork — three patches on
a stock Chromium base, upstream-aligned (Controlled Frame and IWAs are real upstream features). But no
rebase cadence, patch-count budget, or upstreaming path is recorded.
Options. (a) Ride the desktop-android variant + origin-trial features and drive patches toward zero as upstream lands (lowest burden, upstream-timing dependent); (b) maintain a standing carry-patch set (control at the cost of rebase toil). Note d/098: upstreaming is a human-published contribution, never an agent-opened PR.
External review input. sol names the missing browser-fork sustainability plan as an open question — who tracks Chromium security releases, the rebase and emergency-patch SLA, the behavior if Controlled Frame or IWA changes upstream — and asks for a conformance + patch-budget gate (which permission/navigation/isolation/multi-pane tests must pass before a rebase ships). It also flags the deeper dependency: Controlled Frame is an incubating, Chromium-specific API treated as a universal seam, and wants a documented abstraction + fallback. — sol · mission §5 · sol · shell §8
Decides: Jan + Luke ◆ — fork strategy. Source: shell G5.
Theme G — The local trust boundary
Net-new from the external review: two gaps that no Part III layer review named. sol surfaced them by probing the host-local boundary directly — a public web page, and an untrusted process running as the same OS user — rather than the peer or the guest. Both are downstream of the ruling the mission memo demands first: is an untrusted same-host process inside the threat model? Until that is answered, neither can be closed, only scoped.
G1 — Browser request admission on the loopback plane
Gap. The refusal ladder authenticates routing, not the caller. The cross-origin arm only
runs when an Origin header is present, and the Fetch-Metadata floor only refuses cross-site
unsafe/WebSocket requests — so a cross-site safe request without Origin (an image, a
top-level navigation) can reach a backend prefix, including a whole-origin backend-proxy.
Worse, /xe/api/index and /xe/api/catalog answer before the hostname/Origin/Fetch-Metadata
rungs, so app inventory is readable by anything that reaches the loopback socket. Sibling
<app>.localhost origins are cross-origin but same-site, which SameSite cookies and
Sec-Fetch-Site do not isolate. Host and Fetch Metadata are routing/CSRF signals, not plane
authentication, and any local process can forge them.
Options. (a) Adopt sol’s normative Browser Request Admission clause — exact authority,
exact Origin on unsafe/upgrade requests, coherent Fetch Metadata, a narrow
top-level-navigation exception, cross-origin backend subresources denied (incl. GET/HEAD) absent
an explicit Prism bootstrap capability, internal x-xe-* headers stripped from browser ingress,
and the reserved service paths authenticated — plus an explicit PNA/LNA preflight policy
(Access-Control-Request-Private-Network honoured only under a live grant). (b) Accept that even
(a) cannot authenticate an arbitrary local process and add an unguessable browser/Prism session
capability (or app-level auth) for unsafe backend requests. (c) Document the ladder’s limits
honestly and scope the untrusted-local-process threat out of v1. Regular apps should also receive
a minimal frame-ancestors CSP (Prism + declared embedders only) so a public page cannot frame
them for confused interaction.
Decides: Luke ▲ — a threat-model / security-boundary call, litmus-adjacent (inspect what it sees); it cannot be settled without ruling on the same-UID-process question. Source: sol · serving §1 · sol · mission §5.
G2 — Connector & engine-API caller authentication
Gap. The engines chapter’s “no loopback TCP” invariant is false as stated. The backend
connector listens on 127.0.0.1:5199 and authenticates only the app selector (a
syntactically-validated x-xe-app header), which does not authenticate the caller — any
local process can act as a confused deputy, drive the connector, and select an installed app;
the browser’s grant rules do not protect direct loopback access. And on macOS the engine
socket is a raw tcp://127.0.0.1:2375 Docker API, far more privileged than any backend port.
The defensible invariant is narrower than “no loopback TCP”: no app-owned backend port is
published on the host, and no app or router receives the engine credential.
Options. (a) Move connector access to a peer-authenticated local transport — a Unix socket with peer credentials, a Windows named pipe with an ACL, or a per-boot authenticated channel — and give the macOS Docker TCP endpoint an explicit local-adversary analysis (or replace it). (b) Keep loopback TCP but require an unguessable per-boot credential the router holds and apps never see. (c) Restate the invariant to the defensible one and scope the local-process threat out of v1. Any choice should carry a post-creation check that the connector resolves an opaque launcher-held handle, never an arbitrary caller-supplied project/service selector.
Decides: Luke ▲ — the same threat-model call as G1; couples to the Windows-backend engine decision (E3) and the connector’s role in the compose IR (F6). Source: sol · app-model §2 & §5.
The runnable disposition of every open decision above — in the order Luke and Jan should take them — is the review agenda. Protocol and format candidates already firming up out of the built code are in Part V.