ADR 0034: Electron Shell Architecture — Process Model, Window Topology, Security Posture, Distribution
Architectural Decision Record settling the six load-bearing questions every Electron-shell leaf inherits (epic #13377): how the packaged shell BOOTSTRAPS the bundled local Agent OS plane — the cockpit itself always connects over the authenticated fleet wire (ADR 0038) — and what its window/app lifecycle means for the organism, under which constraints Chromium shares Neo's SharedWorkers across OS windows (verified empirically, not asserted), the fail-closed renderer security contract, the dock/OS-window fusion mapping, the distribution channel, and dev/prod parity. Everything here is additive on ADR 0020 §3 (the shell decision itself — Electron, decided) and consumes the landed window-manager substrate as boundaries — it reopens nothing.
| Attribute | Value |
|---|---|
| Status | Accepted — 2026-07-10 (#14786; PR #14924 merged to dev). |
| Amended | 2026-08-08 (#16747, ADR 0038 — the FM client topology, graduated from D#16720): §2.1's hosting frame and §2.6.4's dev-attaches/packaged-hosts dichotomy are re-scoped for the pure-client cockpit — the main process's hosting/supervision is the packaged product's local-plane BOOTSTRAP (role-3 host machinery; bundle = organism), never the cockpit's runtime identity. The cockpit connects to the (bundled or remote) plane's fleet service in every topology; identity, registry, credential, and authorization policy are plane-owned; lifecycle affordances surface behind CAN_ADMINISTER_FLEET_OF over the authenticated fleet wire. The retrieval statements themselves are rewritten in place (epigraph, §1 anchors, §2.1 heading + settled frame + binding 1, §2.6.4) with provenance brackets; ADR 0038 §3.3 is the canonical statement. |
| Author | @neo-opus-vega (Vega, Claude Fable 5, Claude Code) — #13377 epic steward. |
| Resolves | #14786 — the #13377 gate leaf: settle the shared shell questions ONCE, decision-record tier, before the E-leaves multiply incoherently (the ADR-0029 settle-shared-questions pattern). |
| Parent epic | #13377 (Electron shell — package + host the Agent OS) under #13012 (Agent Harness). |
| Depends on | ADR 0020 (the embodiment vessel — extended, never superseded; §3 fixes the shell decision, the in-process target + child-process fallback, source-tree discipline, and PATs-Brain-side); ADR 0029 (docking design — its §2.1 state-class table, window-manager boundaries, and semantic-restore rules are consumed, not reopened). |
| Connects to | #13033 (build-root leaf, owner @neo-opus-ada — its live Contract Ledger is consumed AS-IS; §5 maps the refinement leaves that follow it) · #13025 / #13028 (landed window-manager leaves, consumed as boundaries) · #13446 (NL window ops — gains an Electron backend per §2.4) · #14793 (native-shell UX spec — consumes §2.1's lifecycle decisions) · #14230 (fork-path onboarding — stays the contributor door per §2.5). |
| Empirical anchor | Carried as a live regression in the real native app — cd harness && npm run smoke (the harness IS the runnable Electron vessel; its own package.json pins Electron 43.1.0) emits a JSON verdict with sharedHeapEvidence: true + popupMaterialized: true at Chromium 150.0.7871.47 · darwin 25.5.0: SharedWorker sharing proven in the shipped app, not a throwaway. The negative-case findings that fixed the architecture (file:// kills sharing; per-window partitions) are recorded in §2.2 + §6. Re-run trigger: every Electron major bump BEFORE adoption (harness/ npm run smoke); owner: the §5 E2 leaf owner. (The redundant one-off spike — a duplicate Electron project — was removed from the tree; #15043 — the superseded spike/14786-electron-sharedworker branch is being retired separately, operator-gated.) |
| Implemented by | the §5 decomposition — one Contract-Ledgered leaf per row, mapped on the #13377 epic; each cites its section here as upstream contract. |
| Anti-anchor for | file:// harness loading (falsified: kills worker sharing, §2.2); a second window manager (Electron materializes, the Neo window substrate owns semantics); serialize-and-recreate across BrowserWindows (components exist once in the shared App-Worker heap — ADR 0029); a Brain-daemon fork (one lifecycle owner, §2.1); per-window session partitions (§2.2); credential or token bytes in renderer-readable state (§2.3); auto-spawning OS windows on perspective restore (ADR 0029's rule, re-bound §2.4). |
1. Context
ADR 0020 §3 decided the shell (operator, 2026-06-12): Electron — always Chromium + always Node.js, Tauri retired, the packaged product bootstrapping the local Agent OS plane from the Electron main (in-process as the target arm, child-process supervision as the sanctioned fallback) while the cockpit's runtime identity stays a pure client of the plane's fleet wire (ADR 0038), and web-served mode staying the dev convenience. The #13377 epic added the decisive boundary (operator correction, 2026-06-15): Electron is the shell only — it does not own the additional windows. Multi-window remains Chromium popups on Neo's own window substrate (browser-native → Electron-agnostic → the web version stays alive).
What no record settles is everything a packaged shell adds on top of those two anchors: the exact
constraints under which Chromium shares Neo's SharedWorkers across OS windows (the whole
architecture rides this — asserted often, verified never), what last-window-close means when the
shell bootstraps the bundled plane, the renderer security contract, how window.open popups materialize, what
artifact a stranger downloads, and how the packaged boot path stays identical to npm run dev.
Six questions, named by #14786; made implicitly per-leaf, they diverge — the access-ban lesson
(disconnected surfaces, no coherent shell) repeats at the OS level. This record settles them once.
2. Decision
§2.1 Process model — the shell bootstraps the bundled plane, one bootstrap supervisor (Q1)
Settled frame (carrying ADR 0020 §3 as amended, not re-deciding it): the packaged app boots the whole organism — the cockpit always connects over the authenticated fleet wire; packaged mode additionally BOOTSTRAPS the bundled local plane from the Electron main (orchestrator + MCP servers in-process as the target arm, or supervised as a child process as the sanctioned fallback). Which arm wins is #13033's spike outcome and stays that leaf's property; this ADR binds what is true in EITHER arm:
- One bootstrap supervisor. The main process is the single bootstrap authority for the bundled plane's start/stop/restart — whether that means in-process module lifecycles or child-process supervision. No second supervisor, no daemon fork: a packaged install and an externally-run daemon topology never manage the same plane state concurrently. Dev and packaged share ONE wire-only client contract: dev connects to an externally-run plane; packaged bootstraps the bundled plane and connects (ADR 0038 §2.7).
- Restart affordances settle-or-reject. Runtime MCP-server restarts resolve or reject every pending promise (the #13015 lifecycle guardrail, carried from ADR 0020 §4) — no orphaned in-flight calls across a restart, in either hosting arm.
- Data-root resolution moves to the app tier. The Memory Core owns SQLite/Chroma paths under
.neo-ai-data/; a packaged app resolves that root against the OS user-data directory (app.getPath('userData')), a repo checkout resolves it repo-relative. The resolution seam is ONE injectable path — Brain services never hardcode either root. - Port discipline. Brain-side listeners (NL WebSocket, MCP HTTP, the fleet dev transport) bind loopback-only in the packaged app (§2.3) with ports chosen/managed by the lifecycle owner, so two installs (or install + dev repo) fail loudly at boot instead of silently cross-talking.
- App lifecycle: the organism outlives its windows. The shell suppresses Electron's default
quit-on-
window-all-closed(that default silently empties a multi-window app). Closing the last window leaves the Brain running with the tray as its handle; Brain teardown happens ONLY on explicit quit (tray/app-menu). The cockpit window itself is hidden on close, never destroyed while the app runs: SharedWorker lifetime is client-scoped, so destroying the last window would kill the App-Worker heap (stores, undo state, live UI) while the tray still claims the institution is running. The UX surface of these semantics (tray triad, first-run, window defaults) is #14793's spec; the SEMANTICS are bound here.
Falsifier: if #13033's spike shows in-process hosting cannot satisfy settle-or-reject restarts (e.g. MCP server state cannot be torn down cleanly in-process), the child-process arm becomes the recorded topology — this section's five bindings survive unchanged; only the arm flips.
[Statements above amended in place 2026-08-08 — ADR 0038 §3.3 (#16747, from D#16720). Previously
the frame read "the Electron main process hosts the Agent OS in-process as the target topology"
and binding 1 kept attach-to-external as a dev-mode-only capability. The #13033 hosting-arm
decision is unchanged; identity, registry, credential, and authorization policy are plane-owned
(role-3 host machinery; bundle = organism, Body-first first-render); binding 2's settle-or-reject
semantics stay plane-side contracts whose affordances surface behind CAN_ADMINISTER_FLEET_OF;
attach-or-own as cockpit identity is retired (C5 #16746, blast-radius inventory first).]
§2.2 Window topology — the SharedWorker constraint set, verified (Q2)
Neo boots separate SharedWorker identities per role — App, VDom, Data, Canvas (as configured)
— and same-origin windows share EACH of those identities. That per-worker sharing is what the
multi-window architecture rides, and inside Electron it survives only under specific, now-named
constraints — established empirically and now guarded as a live regression by the harness
smoke (cd harness && npm run smoke → sharedHeapEvidence: true); the findings are the table below:
| Constraint | Empirical basis |
|---|---|
C1 — Real origin required, with the full privilege set. The harness MUST be served from a standard-scheme origin: a custom privileged scheme (app:// via protocol.registerSchemesAsPrivileged([{scheme, privileges: {standard: true, secure: true, supportFetchAPI: true}}]) + protocol.handle) or localhost HTTP. supportFetchAPI: true is normative — data.Store url seeds and every fetch-consuming surface depend on it (the harness smoke's working privilege set includes it; the fetch smoke consumes it). file:// loading silently isolates every window's workers — the app renders, boots, and shares nothing. |
file2win/filepopup = ISOLATED; app2win/http2win = SHARED; fetchsmoke = fetchOk: true |
| C2 — One session partition, positively verified. SharedWorker scope is partition-bound: with origin, page URL, and preload held constant and ONLY the partition varied, the second window reports a FRESH worker (count 1) — on both sharing origins. Every harness window lives in the default (or one named) partition. | partitionapp + partitionhttp = both windows report (1, 1) |
C3 — The popup path is preserved. window.open() — issued on the main thread by Neo.Main.windowOpen() — joins the same workers when the shell allows it via setWindowOpenHandler({action: 'allow', overrideBrowserWindowOptions}). The popup materializes as an Electron-managed BrowserWindow; the same-origin window.opener relationship survives. |
apppopup/httppopup = SHARED |
C4 — Secure defaults cost nothing. All of the above holds under contextIsolation: true, sandbox on, nodeIntegration: false — worker topology and the §2.3 security posture are decoupled. |
entire matrix ran on default secure flags |
C5 — Worker identity is (origin, URL) per worker. The packaged origin must be stable across windows and app restarts (one canonical app:// host + path scheme), or windows resolve different identities for the SAME worker role. |
phase-scoped worker URLs isolate by construction |
C6 — Protocol handlers are session-specific. protocol.handle on the default session does not serve other partitions; a window in a persist: partition cannot even LOAD app:// without its own registration. Operationally this reinforces C2: one partition, one registration. |
the partitionapp control required session.fromPartition(...).protocol.handle before window B loaded at all |
The seam, restated under the shell-only boundary — who owns what:
| Concern | Owner (current source) | In the shell |
|---|---|---|
| Popup creation | Neo.Main.windowOpen() (main thread) owns the actual window.open(url, name, features) call; App-Worker code (e.g. dashboard.Container.openWidgetInPopup()) reaches it as a remote method |
unchanged — the same call, materialized per C3 |
| Window observation + choreography | Neo.manager.Window (App Worker) — connected-window registry, geometry, getWindowAt(x, y), with main.addon.WindowPosition |
unchanged — gains precision via the §2.4 enhancement points |
| Detach semantics | DockZoneModel.detachItem() — catalog/tree state only |
unchanged |
The shell's contribution is exactly two enhancement classes: materialization (the
setWindowOpenHandler path turning the main-thread window.open into a real BrowserWindow
without browser popup ceremony) and OS chrome control (exact spawn-at-position, native
move/resize events, frame options — the enhancement points #13025/#13028/#13030 named and
#13446's NL window ops consume). The shell never grows placement policy, z-order arbitration, or
window state of its own.
Falsifier: a future Electron major changing SharedWorker scoping or protocol/session semantics — the harness smoke is the re-run gate (§6); the E2 leaf owner runs it on every major bump before adoption.
§2.3 Security posture — fail-closed everywhere, credentials never renderer-readable (Q3)
- Renderer flags:
contextIsolation: true, sandbox on,nodeIntegration: falsefor every window — including popups (enforced centrally in thesetWindowOpenHandleroverride). §2.2 C4 proves this costs the architecture nothing; there is no worker-topology excuse for weakening it. - Fail-closed window policy:
setWindowOpenHandlerALLOWS only same-origin harness URLs (an explicit origin/path allowlist) and DENIES everything else;will-navigatedenies any off-origin navigation in every window. The allow path IS the C3 popup contract; everything else fails closed. - Permissions and content: a
setPermissionRequestHandlerthat denies by default with a named, minimal allowlist (empty until a leaf needs one — additions amend this section); a restrictive CSP served by theapp://handler for every document; no remote documents, scripts, styles, fonts, frames, or media inside harness windows. Passive images are the sole exception and remain origin-allowlisted:github.com+avatars.githubusercontent.comfor the current roster-avatar source. Any additional image origin amends this section first. - IPC discipline: one minimal
contextBridgepreload exposing named, capability-shaped affordances — neveripcRendererraw, never Node. Main-process handlers validateevent.senderFrameorigin against the packaged origin before acting. The Brain-sideFleetControlBridgeallowlist choke-point (FLEET_WIRE_METHODS) is the in-repo model for this capability-allowlist discipline. - Brain endpoints bind loopback TCP (
127.0.0.1), never0.0.0.0, never unix sockets — rendererWebSocket/fetchcannot reach unix sockets, and the NL/MCP endpoints must stay reachable from the harness renderer and local tooling alike. When the Brain rides in-app, endpoint auth uses a per-boot token whose bytes never enter renderer-readable state: the preload/main capability attaches it to outbound requests internally; the renderer invokes named capabilities and never sees the secret. Loopback alone does not authenticate against other local processes. - Credential custody (ADR 0020 §3, re-bound and made honest): PATs live Brain-side. The
TARGET ingress is a shell-owned surface where credential bytes flow preload/main → Brain and
never enter App-Worker state. The CURRENT
Accountsadd-agent path — a PAT collected in an App-Worker form field and submitted once through the registry bridge (ephemeral, fail-closed, never stored Body-side) — is transitional drift against this posture, acceptable only in the dev-server topology; the packaged shell migrates credential entry to the shell-owned surface as part of the §5 E5 leaf. The Add-Peer curated-intent boundary (Body submits{harnessType, id, repo/account facts}— never command/args/env) is the design contract that surface implements. - Whole-Brain health is a named read affordance from the lifecycle owner (amended
2026-08-03, #16051 — the PR #16050 Drop+Supersede consequence): the preload exposes ONE
additional capability,
brainHealth()→ipcRenderer.invoke('brain-health'), answered by a main-process handler that binds the app-lifecycle owner's surface (appLifecycle.brainStateplus a retained cause) and validates the sender per §2.3.4 before answering. Pull-shaped by design: a renderer-initiated request/response keeps the audited surface bounded, adds no unsolicited main→renderer event channel, and cannot leak listeners across renderer generations (a dead renderer is itself a degrade source). An unpolled or unanswerable affordance claims nothing — the fail-closed shape of this posture — where a push channel invites silence to read as health. The consumer obligation is symmetric: a surface rendering this affordance re-reads it for as long as it renders — a single mount-time read re-imports silence-as-health through the consumer. The re-read interval is leaf property; the obligation is not. Payload: plain data —{state, cause: {source, detail, observedAt} | null},statefromBRAIN_STATES. No credential bytes (§2.3.5/.6 untouched), no window or Node references. Producer contract: the cause is produced where the fault is observed — thewatchBrainChildtermination edge carries the owned child's identity + termination summary; the cockpit-gone, render-process-gone, and boot-not-ready degrade sites name themselves. ONE bounded record under severity-tiered first-cause-wins: within a tier the first cause of a degraded episode is retained, and an owned-child fault supersedes a window-scoped cause — never the reverse — encoding the child-fault primacysettleBrainBootalready votes (up && !brainFaulted). The record clears on re-entry torunningAND on transition tostopped— an explicit quit/teardown must never render as impairment. Never accumulated main-side. Failure semantics: an invoke rejection or an absentneoShellis TRANSPORT truth and maps to the consumer's transport surface — never daemon truth. Ownership boundary: whole-Brain health never routes throughFleetControlBridge/FLEET_WIRE_METHODS— per-agent process rows answer "which agents run", not "is the organism impaired" (§4, rejected shapes). The fleet allowlist is the discipline model (§2.3.4), never the transport. Witness obligation: the consuming leaf's evidence is a shell-transition-driven witness — the lifecycle owner's transition drives the rendered banner; hand-assigned consumer state witnesses a pass-through and is not wiring evidence.
Falsifier: any leaf needing a renderer capability the preload cannot express through a named,
allowlisted intent amends THIS section first (ADR-0005 lifecycle) — it never flips a window to
nodeIntegration: true, widens the window allowlist, or moves secret bytes into renderer state
as a leaf-local decision.
§2.4 Dock/OS-window fusion — same semantics, richer vessel (Q4)
detachItem → OS window today spawns a browser popup; inside the shell it stays exactly that
call chain — App-Worker intent → Neo.Main.windowOpen() → window.open — materialized by
Electron per §2.2 C3 as a real BrowserWindow. The mapping, named:
| Layer | Owner | In the shell |
|---|---|---|
Detach semantics (detachItem, catalog membership, placement hints) |
dockZone.v1 + ADR 0029 §2.1 |
unchanged — worker-owned shared truth |
| Popup issuing | Neo.Main.windowOpen() (main thread), reached as a remote method |
unchanged — same API, same events |
| Window observation / God-View | Neo.manager.Window + main.addon.WindowPosition |
unchanged |
| Materialization | browser popup ceremony | setWindowOpenHandler → BrowserWindow (no popup blocker, no chrome-strip ceremony), under the §2.3.2 allowlist |
| OS affordances | unavailable | exact spawn-at-position, native move/resize events, frame control — consumed via the #13446 NL window-op backend and the #13025/#13028/#13030 enhancement points |
The embodiment vessel contract (ADR 0020) is extended, never superseded: a docked pane
detaches into a real OS window and returns as the same live heap object (ADR 0029's
no-serialize-and-recreate anti-anchor re-bound here). ADR 0029's semantic-restore rule carries
over explicitly: perspective restore re-enters items at their semantic fallbackTarget and
NEVER auto-spawns OS windows — the shell's removal of popup ceremony makes auto-spawn possible,
which is exactly why the prohibition is restated here rather than left implicit. What changes in
the shell is vessel quality — frameless windows, precise placement, native drag events — never
vessel semantics.
Falsifier: if a fusion leaf finds BrowserWindow materialization breaking an ADR 0029 §2.1
state-class boundary (e.g. render-projection state needing main-process persistence), that leaf
amends ADR 0029 and this section together, before implementation.
§2.5 Distribution — signed installers for strangers, the repo for contributors (Q5)
- Artifact: platform-native installers — macOS
dmg(signed + notarized), Windowsexe(signed), LinuxAppImage— built by an electron-builder-class pipeline from the packaging root (ownpackage.json, wraps BUILT Body + Brain; ADR 0020 §3 source-tree discipline). "The stranger downloads the harness" = one double-clickable artifact; thenpxbootstrap and the #14230 fork-path stay the contributor door — the two doors never merge. - Signing reality is a release-line concern: cert provisioning/notarization credentials are operator-owned (human-only, like the merge gate); CI produces unsigned artifacts for verification, the release pipeline signs. No leaf embeds signing material in repo tooling.
- Two-speed updates, decoupled by design: the SHELL updates rarely (Electron/Chromium security cadence — autoUpdater-class mechanism, its own channel); the ORGANISM (harness app + Brain) ships with the shell version it was packaged with and updates by shipping a new package. No self-mutating installed app in v1: partial in-place organism updates (pulling new Brain/Body into an installed shell) are explicitly deferred — they reintroduce the drift matrix (shell×organism version pairs) that packaged-together avoids, and nothing in H1 needs them.
- Update cadence ≠ repo release cadence: packaged releases cut from the release line
(
main), on their own schedule, versioned independently of npm releases.
Falsifier: if H1 adoption data shows install-base staleness hurting (users stuck on old organisms), §2.5.3's deferral is the named revisit point — the two-speed split is the decision, partial updates are the recorded alternative.
§2.6 Dev/prod parity — one harness root, two origins, identical topology (Q6)
- One boot path — the shell resolves the SAME source graph the dev http origin serves (from
the repo root through an explicit renderer-content allowlist), and the harness window loads the
zero-build SOURCE app (dev mode), not
a minified bundle. Amended 2026-07-10 (operator decision, #13033 slice 1): the Neural Link
is a first-class consumer of the harness — its possession depth (
inspect_class,get_method_source,patch_code) requires real source ESM, which minifieddistoutput destroys. Parity is therefore the source graph, not unrestricted filesystem exposure: no packaged-only entry file, no dev-only worker wiring;neo-config+ worker URLs resolve identically on either origin, and the explicitly requireddist/development/css/*assets stay reachable within the same root (#13033's build-root remains the substrate this section binds). - Parity is achievable BECAUSE of C1: both origins are standard-scheme with fetch support
(
supportFetchAPInormative — JSON seeds viadata.Storeurlbehave identically), so every SharedWorker identity the app boots (App/VDom/Data/Canvas) shares the same way dev↔packaged. The harness smoke's http/app rows agreeing — including the fetch smoke — is the empirical parity floor. - Web-served mode stays the dev convenience + goodie (ADR 0020 §3) — the shell never becomes
a dev-loop dependency; nothing in
src/orapps/may import from or feature-detect the packaging root (the hemisphere discipline, enforced at review). - Brain parity: ONE wire-only client contract in both arms — dev connects to an externally-run plane; packaged BOOTSTRAPS the bundled plane per §2.1 and connects over the same authenticated fleet wire. The seam that makes both true is §2.1.3's injectable data-root + §2.1.4's managed ports — the SAME Brain code, one bootstrap supervisor per plane, never two supervisors on one plane's state. [Amended in place 2026-08-08 — ADR 0038 §3.3 (#16747); previously "dev attaches to externally-run daemons; packaged hosts per §2.1 … two lifecycle owners, never both at once".]
Falsifier: any leaf introducing a process.versions.electron branch inside src/ or apps/
violates this section on its face — shell awareness lives in the packaging root and
main.addon.* enhancement points only.
3. Consequences
- Every #13377 E-leaf cites its §2 section as upstream contract; changing a §2 decision amends this ADR first (ADR-0005 lifecycle), never lands leaf-local.
- #13033 implements against §2.1/§2.6 as its live Contract Ledger stands and records the hosting-arm outcome; §5 maps the refinement leaves that follow it.
- The
app://scheme handler (with C1's full privilege set + §2.3.3 CSP), the materialization + window-policy bridge, and the preload capability contract each become one leaf (§5). - The harness smoke is the regression gate: the E2 leaf owner re-runs
cd harness && npm run smokeon every Electron major bump before adoption (§2.2 falsifier; the smoke's JSON verdict + pinned versions make drift visible). - Web portability is structurally guaranteed: because the shell only materializes and decorates, removing it leaves a working web app — the boundary that keeps the future web version alive.
4. Prior art & rejected shapes
- Tauri / WebKitGTK — retired at ADR 0020 (worker-topology determinism + Node-in-process beat binary-size aesthetics).
- Electron owning the windows — rejected (operator, 2026-06-15); would fork window management into a web arm and a shell arm and kill web portability.
file://+loadFile()packaging — the naive Electron default, empirically falsified (§2.2 C1): boots, renders, silently loses worker sharing.- Localhost HTTP as the packaged origin — works (verified), rejected operationally: port collisions, firewall prompts, and any local process gaining harness-origin access for free.
- Unix sockets for Brain endpoints — unreachable from renderer WebSocket/fetch; loopback TCP
- non-renderer-readable per-boot token instead (§2.3.5).
- Serialize-and-recreate popout state — rejected at ADR 0029; re-bound here because Electron makes it tempting (BrowserWindows feel ownable from main).
- Renderer-readable bearer tokens — rejected during review of this record: a token the renderer can read still crosses the boundary; capability-internal attachment replaced it.
- Per-agent fleet rows as whole-Brain health — rejected in PR #16050's Drop+Supersede review:
fleetRuntimeStatus()composes per-agent process truth and cannot produce the organism's impairment state; the fields it "fed" had no writer at all. Whole-Brain health crosses from the lifecycle owner via §2.3.7 — never inferred from fleet rows.
5. Decomposition — the E-leaf gate (map lives on the #13377 epic)
| Leaf | Scope | Upstream contract |
|---|---|---|
| E1 — build root (#13033, exists, owner @neo-opus-ada) | as its live Contract Ledger stands: packaging root + main entry, hosting-arm spike, Agent-OS boot, first harness window, popup windows joining the shared workers, and the window-management bridge enhancement points | §2.1, §2.6 (frame only — the leaf's ledger is consumed unchanged) |
| E2 — origin + scheme hardening (post-E1) | app:// privileged scheme with C1's FULL privilege set (supportFetchAPI normative), stable origin, CSP delivery, the fetch/JSON smoke, harness-smoke re-run ownership |
§2.2 C1/C5/C6, §2.3.3, §2.6 |
| E3 — window policy + materialization hardening (post-E1) | fail-closed setWindowOpenHandler allowlist, will-navigate denial, secure-flag enforcement on popups, OS-chrome enhancement points, #13446 NL backend |
§2.2 C3, §2.3.1–.4, §2.4 |
| E4 — Brain lifecycle service | start/stop/restart with settle-or-reject, port management, data-root injection | §2.1.1–.4 |
| E5 — preload capability contract + credential ingress | the one contextBridge surface + allowlist, sender validation, token custody, the shell-owned credential surface retiring the transitional Accounts path |
§2.3.4–.6 |
| E6 — packaging + signing pipeline | electron-builder config, per-platform artifacts, unsigned-CI/signed-release split | §2.5 |
| E7 — update channel | shell autoUpdater wiring, two-speed policy | §2.5.3 |
| E8 — app lifecycle + tray | window-all-closed suppression, explicit-quit-only teardown, hide-not-destroy cockpit close, tray handle — the #14793 UX spec implements against it |
§2.1.5 |
E2–E8 are filed on demand under #13377 (epic bodies never enumerate subs — ADR 0020 §6); each carries its own Contract Ledger and cites this table row. E2/E3 refine what E1 lands — they never reopen its ledger.
6. Verification
- Empirical: the harness's own smoke —
cd harness && npm run smoke— boots two windows on theapp://origin and proves the popup joins the SAME shared workers (sharedHeapEvidence: true,popupMaterialized: true), pinned Electron 43.1.0 / Chromium 150.0.7871.47 / darwin 25.5.0. The negative cases that fixed the architecture (file://kills sharing; per-window partitions) are recorded in §2.2 — one-time discovery, not a standing runnable. Re-run owner + trigger: the E2 leaf owner, every Electron major bump before adoption. - Authority chain: ADR 0020 §3 (shell decision, hosting arms, source discipline, PATs) ·
#13377 epic body (shell-only boundary, operator 2026-06-15; cites this record as design
authority) · ADR 0029 §2.1 (state classes, window-manager boundaries, semantic restore) ·
Neo.Main.windowOpen/Neo.manager.Window/DockZoneModel.detachItemownership verified at source during review cycle 2. - AC mapping (#14786): six questions → §2.1–§2.6, each with named constraints + falsifiers; anti-anchor row carries the three ticket-named minimums (no serialize-and-recreate, no second window manager, no Brain-daemon fork) plus the review-added ones; the SharedWorker constraint set verified by the harness smoke, not asserted; #13377 cites this ADR (updated with the resolving PR, status-honest as Proposed until merge).