LearnNewsExamplesServices
Frontmatter
id14994
titleE6: electron-builder packaging pipeline — the double-clickable harness artifact (unsigned leg)
stateClosed
labels
enhancementaibuild
assigneesneo-opus-vega
createdAt12:32 AM
updatedAt2:44 AM
githubUrlhttps://github.com/neomjs/neo/issues/14994
authorneo-opus-vega
commentsCount0
parentIssue13377
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAt2:44 AM
milestonev13.2

E6: electron-builder packaging pipeline — the double-clickable harness artifact (unsigned leg)

Closed Backlog/active-chunk-5 enhancementaibuild
neo-opus-vega
neo-opus-vega commented on 12:32 AM

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:

  1. 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).
  2. 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.
  3. 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):

  1. electron-builder config + npm run dist producing a platform artifact (darwin dmg/zip first; the per-platform matrix rides CI later).
  2. 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).
  3. 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.
  4. 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).
  5. Resolver root injection: packaged mode serves app:// from the bundled resources root; checkout mode unchanged.
  6. 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

  • cd harness && npm run dist produces 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).
  • The packaged window boots apps/agentos from the BUNDLED source graph over app:// (fail-closed allowlist semantics preserved; containment asserts against the packaged root).
  • The packaged Brain leg: own-mode supervision reaches fleet listAgents readiness; quit tears the tree down (group-empty) — the smoke contract, run against the artifact.
  • First boot self-initializes the instance config (no hand-editing, no npm shell-out).
  • The runtime-arm outcome (Electron-ABI bundle vs system-Node fallback) is recorded with its probe, #14967-style.
  • Dev loop + checkout harness mode untouched (no behavior change without the packaged flag/mode).
  • §2.5.1 wording reconciled with the §2.6 amendment (ADR touch flagged in the PR).

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).

tobiu referenced in commit 2707c0a - "feat(harness): E6 packaging pipeline — the double-clickable organism artifact, unsigned leg (#14994) (#15002) on 2:44 AM
tobiu closed this issue on 2:44 AM