Context
The ADR 0034 §5 E-map row this leaf files on demand (per the map's own instruction): E6 — packaging + signing pipeline: electron-builder config, per-platform artifacts, unsigned-CI/signed-release split — upstream contract §2.5. With E1 discharged in full (#13033 reconciled 2026-07-10: the harness/ root, multi-window shared heap, and the supervised Brain via the Arm-B verdict on #14967 / PR #14976), the packaging pipeline is the next un-blocked leaf on the v13.2 release-gate sentence — "a developer downloads and runs the local harness." The clone/fork door is #14230 (Euclid's cornerstone); THIS leaf is the stranger door: one double-clickable artifact (ADR 0034 §2.5.1 — "the two doors never merge").
The Problem
harness/ boots only from a repo checkout today: app:// serves the repo root through the renderer allowlist, the Brain children spawn from the checkout's ai/ tree against the checkout's node_modules, and npm supplies PATH/prepare conveniences. A downloaded artifact has none of those. Three gaps, in dependency order:
- Asset graph: the packaged app must carry the served source graph inside its own resources — the allowlist trees (
src/, apps/agentos/, dist/development/css/, fontawesome webfonts, resources/ subset) plus the Brain tree (ai/, the boot-required buildScripts/ subset) — and createHarnessAssetResolver's document root must become injectable (checkout root today; packaged resources root then).
- Runtime: the supervised Brain runs system Node against natively-built modules (
better-sqlite3). The dev-tree ABI verdict (#14967 falsifier 1: system-ABI 141 vs Electron-ABI 148, rebuild fights the shared dev loop) does not transfer to the packaged context — the bundle is its OWN node_modules, so building it for Electron's ABI and spawning Brain children via ELECTRON_RUN_AS_NODE collides with nothing. #14967 recorded this exact decision as owned by this leaf.
- First boot: a packaged app cannot run
npm run prepare -- --migrate-config. The instance ai/config.mjs must self-initialize from the template on first boot (the packaged twin of #14230's no-hand-edited-config outcome — same principle, different door).
The Architectural Reality
harness/brain.mjs already carries the seams this leaf needs: NEO_HARNESS_NODE_BIN (runtime selection), startBrainChild PATH guarantee (field note 6: "a packaged shell has no npm in the chain"), attach-or-own product boot (a fresh machine = own-mode, exactly the packaged case), settle-or-reject readiness (resolveBrainPaths surfaces config-staleness pre-spawn).
harness/contentPolicy.mjs#createHarnessAssetResolver(repoRoot) — the one root parameter to generalize; the allowlist + realpath containment semantics stay identical.
- §2.5.1 ("wraps BUILT Body") vs the newer §2.6 amendment (the window loads the zero-build SOURCE app — NL possession needs real ESM): the amendment governs. The packaged asset set is the SOURCE graph the allowlist names, not
dist/production. The implementing PR reconciles §2.5.1's wording (one-line ADR amend, flagged for review).
- Signing/notarization is operator-owned, release-line (§2.5.2) — never repo tooling. This leaf ships the UNSIGNED leg only.
The Fix
In harness/ (the ADR-designated packaging root; own package.json per ADR 0020 §3 — sibling precedent, no new top-level dirs):
- electron-builder config +
npm run dist producing a platform artifact (darwin dmg/zip first; the per-platform matrix rides CI later).
- A pack-stage asset manifest deriving the bundled trees FROM the contentPolicy allowlist + the Brain-boot set (one source of truth — no second hand-maintained list).
- The bundled
node_modules built for Electron's ABI (@electron/rebuild at pack time, scoped to the bundle) + NEO_HARNESS_NODE_BIN resolving to ELECTRON_RUN_AS_NODE in packaged mode. Falsifier-gated (the #14967 methodology): if the Electron-ABI rebuild of the ai/ native set fights structurally, the fallback arm is a documented system-Node prerequisite with an actionable preflight error — the arm outcome gets recorded either way.
- First-boot config self-init: generate the instance
ai/config.mjs from the template when missing (reusing the migrate-config machinery as a library call, not a shell-out to npm).
- Resolver root injection: packaged mode serves
app:// from the bundled resources root; checkout mode unchanged.
- Packaged verification: the existing smoke contract (boot, popup shared-heap, Brain leg incl. renderer→fleet verb, group teardown) executed against the PACKAGED artifact — the evidence tier for "double-click works."
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
harness dist pipeline (npm run dist) |
this leaf (new) — ADR 0034 §2.5 E6 row |
unsigned platform artifact bundling the allowlist source graph + Brain tree |
checkout mode untouched |
harness README |
packaged smoke run |
| Bundled Brain runtime |
#14967 recorded decision (this leaf owns it) |
Electron-ABI node_modules + ELECTRON_RUN_AS_NODE children |
system-Node prerequisite arm w/ preflight (falsifier-gated) |
README + arm record |
rebuild probe outcome |
createHarnessAssetResolver root |
harness/contentPolicy.mjs (existing, generalized) |
injectable document root (checkout / packaged resources) |
default stays repo root |
JSDoc |
ContentPolicy spec extension |
| First-boot config init |
ai/scripts/setup/initServerConfigs.mjs (existing machinery, consumed as library) |
instance config generated when missing, no npm in the chain |
daemon's own freshness guard still fails actionably |
README |
packaged first-boot log |
Acceptance Criteria
Out of Scope
Signing/notarization + release-channel wiring (§2.5.2 operator-owned; E7 territory) · autoUpdater (E7) · native menus/deep links + tray (E8, #14793 implements against it) · Windows/Linux artifact polish beyond a build-config stub (follow-up once darwin proves the pipeline) · CI packaging lane (E6-adjacent map decision, already flagged on #13377).
Avoided Traps
dist/production as the packaged app layer — rejected: the §2.6 amendment (operator decision 2026-07-10) makes source-ESM the harness contract; minification destroys Neural Link possession.
- Hand-maintained bundle file list — rejected: derive from the contentPolicy allowlist, one authority.
- electron-rebuild against the CHECKOUT's
node_modules — rejected: that is exactly the #14967 falsifier (kills the system-Node dev loop); the rebuild scopes to the pack-stage bundle only.
- Partial in-place organism updates — rejected by §2.5.3 (two-speed split; drift matrix).
Decision Record impact
aligned-with ADR 0034 (§2.5 + §5 E6 row; consumes the #14967 recorded arm decision) · aligned-with ADR 0020 §3 (packaging root, no source mixing). One §2.5.1 wording reconciliation flagged (amends-scope: one line, carried by the implementing PR).
Related
Parent: epic #13377 (E-map row E6). Upstream: ADR 0034 §2.5/§2.6 · #13033 (E1, reconciled) · #14967 (the ABI/arm record this leaf consumes) · #14230 (the contributor door — never merged with this one) · #14793 (the "download and run" UX spec — E8 consumer, cited for the first-run moment) · #14993 (crash-sweep identity hardening, Euclid — the sweep this artifact's smoke also exercises).
Live latest-open sweep: checked latest 20 open issues at 2026-07-10T22:35Z; no equivalent found (#14793 is the UX spec, not the pipeline). A2A in-flight claim sweep: last-90-min window scanned; the only related claim is my own broadcast (MESSAGE:ccefc83d) declaring this filing.
Origin Session ID: d2fbbdb4-404b-47e1-bbb3-1b9e0330894b
Retrieval Hint: "electron-builder packaging pipeline unsigned artifact ELECTRON_RUN_AS_NODE bundled node_modules harness E6"
Authored by Vega (Claude Fable 5, Claude Code).
Context
The ADR 0034 §5 E-map row this leaf files on demand (per the map's own instruction): E6 — packaging + signing pipeline: electron-builder config, per-platform artifacts, unsigned-CI/signed-release split — upstream contract §2.5. With E1 discharged in full (
#13033reconciled 2026-07-10: theharness/root, multi-window shared heap, and the supervised Brain via the Arm-B verdict on#14967/ PR #14976), the packaging pipeline is the next un-blocked leaf on the v13.2 release-gate sentence — "a developer downloads and runs the local harness." The clone/fork door is #14230 (Euclid's cornerstone); THIS leaf is the stranger door: one double-clickable artifact (ADR 0034 §2.5.1 — "the two doors never merge").The Problem
harness/boots only from a repo checkout today:app://serves the repo root through the renderer allowlist, the Brain children spawn from the checkout'sai/tree against the checkout'snode_modules, andnpmsupplies PATH/prepare conveniences. A downloaded artifact has none of those. Three gaps, in dependency order:src/,apps/agentos/,dist/development/css/, fontawesome webfonts,resources/subset) plus the Brain tree (ai/, the boot-requiredbuildScripts/subset) — andcreateHarnessAssetResolver's document root must become injectable (checkout root today; packaged resources root then).better-sqlite3). The dev-tree ABI verdict (#14967falsifier 1: system-ABI 141 vs Electron-ABI 148, rebuild fights the shared dev loop) does not transfer to the packaged context — the bundle is its OWNnode_modules, so building it for Electron's ABI and spawning Brain children viaELECTRON_RUN_AS_NODEcollides with nothing.#14967recorded this exact decision as owned by this leaf.npm run prepare -- --migrate-config. The instanceai/config.mjsmust self-initialize from the template on first boot (the packaged twin of #14230's no-hand-edited-config outcome — same principle, different door).The Architectural Reality
harness/brain.mjsalready carries the seams this leaf needs:NEO_HARNESS_NODE_BIN(runtime selection),startBrainChildPATH guarantee (field note 6: "a packaged shell has no npm in the chain"), attach-or-own product boot (a fresh machine = own-mode, exactly the packaged case), settle-or-reject readiness (resolveBrainPathssurfaces config-staleness pre-spawn).harness/contentPolicy.mjs#createHarnessAssetResolver(repoRoot)— the one root parameter to generalize; the allowlist + realpath containment semantics stay identical.dist/production. The implementing PR reconciles §2.5.1's wording (one-line ADR amend, flagged for review).The Fix
In
harness/(the ADR-designated packaging root; ownpackage.jsonper ADR 0020 §3 — sibling precedent, no new top-level dirs):npm run distproducing a platform artifact (darwindmg/zipfirst; the per-platform matrix rides CI later).node_modulesbuilt for Electron's ABI (@electron/rebuildat pack time, scoped to the bundle) +NEO_HARNESS_NODE_BINresolving toELECTRON_RUN_AS_NODEin packaged mode. Falsifier-gated (the#14967methodology): if the Electron-ABI rebuild of theai/native set fights structurally, the fallback arm is a documented system-Node prerequisite with an actionable preflight error — the arm outcome gets recorded either way.ai/config.mjsfrom the template when missing (reusing the migrate-config machinery as a library call, not a shell-out to npm).app://from the bundled resources root; checkout mode unchanged.Contract Ledger Matrix
harnessdist pipeline (npm run dist)#14967recorded decision (this leaf owns it)node_modules+ELECTRON_RUN_AS_NODEchildrencreateHarnessAssetResolverrootharness/contentPolicy.mjs(existing, generalized)ai/scripts/setup/initServerConfigs.mjs(existing machinery, consumed as library)Acceptance Criteria
cd harness && npm run distproduces an unsigned darwin artifact; the app launches by double-click on a machine-state WITHOUT the repo checkout on PATH-relevant env (no npm, no system-node assumption unless the fallback arm was taken and recorded).apps/agentosfrom the BUNDLED source graph overapp://(fail-closed allowlist semantics preserved; containment asserts against the packaged root).listAgentsreadiness; quit tears the tree down (group-empty) — the smoke contract, run against the artifact.#14967-style.Out of Scope
Signing/notarization + release-channel wiring (§2.5.2 operator-owned; E7 territory) · autoUpdater (E7) · native menus/deep links + tray (E8, #14793 implements against it) · Windows/Linux artifact polish beyond a build-config stub (follow-up once darwin proves the pipeline) · CI packaging lane (E6-adjacent map decision, already flagged on #13377).
Avoided Traps
dist/productionas the packaged app layer — rejected: the §2.6 amendment (operator decision 2026-07-10) makes source-ESM the harness contract; minification destroys Neural Link possession.node_modules— rejected: that is exactly the#14967falsifier (kills the system-Node dev loop); the rebuild scopes to the pack-stage bundle only.Decision Record impact
aligned-with ADR 0034 (§2.5 + §5 E6 row; consumes the
#14967recorded arm decision) · aligned-with ADR 0020 §3 (packaging root, no source mixing). One §2.5.1 wording reconciliation flagged (amends-scope: one line, carried by the implementing PR).Related
Parent: epic #13377 (E-map row E6). Upstream: ADR 0034 §2.5/§2.6 ·
#13033(E1, reconciled) ·#14967(the ABI/arm record this leaf consumes) · #14230 (the contributor door — never merged with this one) · #14793 (the "download and run" UX spec — E8 consumer, cited for the first-run moment) · #14993 (crash-sweep identity hardening, Euclid — the sweep this artifact's smoke also exercises).Live latest-open sweep: checked latest 20 open issues at 2026-07-10T22:35Z; no equivalent found (#14793 is the UX spec, not the pipeline). A2A in-flight claim sweep: last-90-min window scanned; the only related claim is my own broadcast (MESSAGE:ccefc83d) declaring this filing.
Origin Session ID: d2fbbdb4-404b-47e1-bbb3-1b9e0330894b Retrieval Hint: "electron-builder packaging pipeline unsigned artifact ELECTRON_RUN_AS_NODE bundled node_modules harness E6"
Authored by Vega (Claude Fable 5, Claude Code).