From cff36e6c1274474347f5c9ebcaf4d4a05d72413f Mon Sep 17 00:00:00 2001
From: Michael Heller <21163552+mdheller@users.noreply.github.com>
Date: Sun, 19 Jul 2026 20:03:33 -0400
Subject: [PATCH 1/4] =?UTF-8?q?feat(cockpit):=20Lane=202=20=E2=80=94=20emb?=
=?UTF-8?q?ed=20client-vue=20+=20sovereign=20runtime=20config=20injection?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Composition plan Lane 2 (docs/cockpit-composition-plan.md). Makes the SocioProphet
cockpit shippable inside the .app, offline-first, in sovereign mode.
- scripts/build-cockpit.sh — compiles client-vue → Contents/Resources/cockpit/
(reproducible: clones SocioProphet/socioprophet@ref, or COCKPIT_SRC local checkout;
asserts the runtime resolver is present), then injects the config loader into index.html.
- runtime/cockpit-config.js — sets window.__COCKPIT_CONFIG__ = { mode: 'sovereign',
bases: {...loopback...} }, loaded BEFORE the app bundle. Consumed by client-vue's
runtime resolver (socioprophet #468). BearBrowser rewrites the ephemeral sidecar
ports into it at launch; committed values are the sovereign default.
- docs/cockpit-spec.md — §3/§7/§8 app-vue → client-vue (the stale 'client-vue mocked'
decision, now that client-vue is canonical).
Verified: staged the built client-vue dist + injected the config + served it headless —
window.__COCKPIT_CONFIG__ loads as sovereign with loopback bases, app boots clean, 0
errors. The resolver (Lane 1) consumes it.
Follow-ups: per-service sovereign loopback map (mode semantics, plan gap #3); profile
newtab wiring to the cockpit origin; Lane 3 agent-machine sidecar (bun --compile).
---
docs/cockpit-spec.md | 24 ++++++++++-----
runtime/cockpit-config.js | 29 +++++++++++++++++
scripts/build-cockpit.sh | 65 +++++++++++++++++++++++++++++++++++++++
3 files changed, 111 insertions(+), 7 deletions(-)
create mode 100644 runtime/cockpit-config.js
create mode 100755 scripts/build-cockpit.sh
diff --git a/docs/cockpit-spec.md b/docs/cockpit-spec.md
index 7005e26..ea158e6 100644
--- a/docs/cockpit-spec.md
+++ b/docs/cockpit-spec.md
@@ -45,9 +45,17 @@ what policy did about it.
## 3. Cockpit shell — Vue, shipped inside the .app, offline-first
-**Base.** Fork `socioprophet-web/app-vue` (the real SPA — carries the Studio
-design language + Carbon tokens). *Not* `client-vue` (mocked) and *not* the React
-marketing shell. Product UI is Vue.
+**Base.** Embed `socioprophet-web/client-vue` — the canonical, real cockpit (54
+surfaces, live `/svc` backends, shipped to GKE via prophet-platform #882). *Not* the
+React marketing shell. Product UI is Vue.
+
+> **Decision update (2026-07-19, see `docs/cockpit-composition-plan.md`):** this was
+> `app-vue`, with `client-vue` dismissed as "mocked." That's stale — `client-vue`
+> became the canonical, real cockpit (the app-vue Studio surfaces were migrated into
+> it). Embedding `client-vue` gives ONE codebase across embedded + GKE-hosted +
+> Firebase. The build step is `scripts/build-cockpit.sh`; the sovereign runtime config
+> is `runtime/cockpit-config.js`, consumed by client-vue's runtime resolver
+> (`src/config/cockpitRuntime.ts`, socioprophet #468).
**Embedding — no network for the shell.** Built assets ship inside the app bundle
and load from a local origin, never HTTP:
@@ -149,7 +157,7 @@ Physical actions are governed by the **same engine** as browser actions.
```
┌──────────────────────────── BearBrowser.app (Gecko) ────────────────────────────┐
- │ Cockpit (Vue app-vue) origin: resource://bearbrowser-cockpit CSP: self+loopback │
+ │ Cockpit (Vue client-vue) origin: resource://bearbrowser-cockpit CSP: self+loopback │
│ │ fetch / WS (127.0.0.1 only) │
│ ├───────────────► SearXNG sidecar / hosted → federated search + receipts │
│ ├───────────────► iot-sidecar (Rust, loopback) │
@@ -170,9 +178,11 @@ Physical actions are governed by the **same engine** as browser actions.
1. **Gecko binary.** Linux via `scripts/gcp-build-linux.sh` → GCS →
`packaging/linux/binary-source.env`; macOS via `.github/workflows/nightly-dmg.yml`
(macos-15). human-secure builds on `latest`/150.
-2. **Cockpit assets.** Add a build step that compiles `app-vue` → static bundle →
- `Contents/Resources/cockpit/`, and a branding/omni hook that maps the internal
- origin. Register the service worker.
+2. **Cockpit assets.** `scripts/build-cockpit.sh` compiles `client-vue` → static bundle
+ → `Contents/Resources/cockpit/` and injects `runtime/cockpit-config.js` (sets
+ `window.__COCKPIT_CONFIG__` → sovereign loopback bases, read by client-vue's runtime
+ resolver). A branding/omni hook maps the internal origin; register the service worker.
+ BearBrowser rewrites the sidecar ports into `cockpit-config.js` at launch.
3. **Profile wiring.** Point `browser.newtab.url` + `browser.startup.homepage` at
the cockpit origin in each profile's `user.js`; keep hardening intact.
4. **Search default.** Add the SearXNG OpenSearch descriptor + set `Default` in
diff --git a/runtime/cockpit-config.js b/runtime/cockpit-config.js
new file mode 100644
index 0000000..c505c44
--- /dev/null
+++ b/runtime/cockpit-config.js
@@ -0,0 +1,29 @@
+// cockpit-config.js — sovereign runtime config for the embedded SocioProphet cockpit.
+//
+// build-cockpit.sh copies this into Contents/Resources/cockpit/ and injects a
+// }' "$OUT/index.html"
+ log "injected cockpit-config.js loader into index.html"
+fi
+
+log "done: embedded cockpit at $OUT ($(find "$OUT" -type f | wc -l | tr -d ' ') files)"
From cbeef13018970ccd49c8e2cd2ff62cbdced98ad7 Mon Sep 17 00:00:00 2001
From: Michael Heller <21163552+mdheller@users.noreply.github.com>
Date: Sun, 19 Jul 2026 20:13:16 -0400
Subject: [PATCH 2/4] macos(nightly-dmg): cbindgen v0.29.1 (FF 150's min =
build version); 0.29.4 regressed COUNT
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
I'd used cbindgen 0.29.4 (latest 0.29.x, from a newer Firefox) — it emits the broken
webrender_ffi macOS COUNT. FF 150.0.1's stated minimum (= its build version) is
v0.29.1, before that codegen regression. Install from the git tag with --locked
(crates.io 0.29.1 fails to build against current serde; the tag's Cargo.lock pins a
compatible one).
---
.github/workflows/nightly-dmg.yml | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/nightly-dmg.yml b/.github/workflows/nightly-dmg.yml
index 9d85808..48c4c40 100644
--- a/.github/workflows/nightly-dmg.yml
+++ b/.github/workflows/nightly-dmg.yml
@@ -111,19 +111,18 @@ jobs:
# and 21 (Xcode) both hit the same webrender COUNT error, so keep the
# simpler Xcode clang.
# - lld: Firefox's macOS linker (Xcode doesn't ship it).
- # - cbindgen: Mozilla's fork @ b826cb8 (0.29.4). This BUILDS and lets the
- # compile run fully, but it emits a webrender_ffi macOS-only block
- # (`#if defined(XP_MACOSX)` — hence Linux is unaffected) that references
- # an undeclared COUNT, failing at ~27min. 🔴 KNOWN REMAINING BLOCKER —
- # see docs note below. (0.28.0 is rejected as too old; crates.io 0.29.1
- # won't even compile against current serde; both clang 19 and 21 hit the
- # identical COUNT, so it is NOT the compiler.) The real fix is to build
- # with Mozilla's ACTUAL pinned toolchain bundle (what `mach bootstrap`
- # fetches, which fails on this fork) rather than piecing it from brew.
+ # - cbindgen: PINNED to v0.29.1 (git tag eef1776) — FF 150.0.1's stated
+ # minimum, i.e. its build version. v0.29.4 (b826cb8, the LATEST 0.29.x,
+ # from a newer Firefox) emits a webrender_ffi macOS-only block
+ # (`#if defined(XP_MACOSX)` — hence Linux is fine) referencing an
+ # undeclared COUNT — a codegen regression after 0.29.1, failing ~27min
+ # in. Install from the git TAG with --locked (crates.io 0.29.1 won't
+ # compile against current serde; the tag's own Cargo.lock pins a
+ # compatible one). Clang is NOT the cause (19 and 21 fail identically).
brew install nasm lld
echo "$(brew --prefix lld)/bin" >> "$GITHUB_PATH"
cargo install --git https://github.com/mozilla/cbindgen \
- --rev b826cb8911488fe8a209d2b693492c0c673e8cca cbindgen
+ --tag v0.29.1 --locked cbindgen
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
{
echo ""
From 8ae914b51351cb2745820147306050fbd61cd43e Mon Sep 17 00:00:00 2001
From: Michael Heller <21163552+mdheller@users.noreply.github.com>
Date: Sun, 19 Jul 2026 20:14:06 -0400
Subject: [PATCH 3/4] =?UTF-8?q?feat(cockpit):=20Lane=203=20=E2=80=94=20age?=
=?UTF-8?q?nt-machine=20as=20a=20bun-compiled=20loopback=20sidecar?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Composition plan Lane 3. Packages @noetica/agent-machine (the local sovereign brain,
823 /api/* routes) into a single self-contained binary — no host Node, no node_modules.
- scripts/build-agent-machine-sidecar.sh — bun build --compile server.ts → one 68M
executable, then smoke-boots it and asserts /api/status 200 on loopback. Reproducible
source (clone noetica@ref or AGENT_MACHINE_SRC local); COMPILES the engine, never edits
it (Noetica lane: package-only). Installs deps only when node_modules is missing, so it
doesn't touch an existing checkout's lockfile.
- scripts/bearbrowser-agent-machine — launcher mirroring bearbrowser-sidecar-server:
loopback-only, sovereign default (NOETICA_OFFLINE=1, no off-device egress), and it
rewrites the live port into the embedded cockpit's cockpit-config.js so client-vue's
resolver targets this sidecar — closing the Lane 1↔2↔3 loop.
Verified for real: bun --compile → 68M arm64 binary that BOOTS, binds 127.0.0.1 ONLY by
default, and serves /api/status 200 with NOETICA_OFFLINE=1 (no network). The build
script's own smoke gate passes. bundleability de-risk → done.
Follow-up: Homebrew/nix formula wiring (install the launcher + build step alongside
bearbrowser-sidecar-server); Lane 4 governance via agent-control-bridge.
---
scripts/bearbrowser-agent-machine | 45 +++++++++++++++++
scripts/build-agent-machine-sidecar.sh | 70 ++++++++++++++++++++++++++
2 files changed, 115 insertions(+)
create mode 100755 scripts/bearbrowser-agent-machine
create mode 100755 scripts/build-agent-machine-sidecar.sh
diff --git a/scripts/bearbrowser-agent-machine b/scripts/bearbrowser-agent-machine
new file mode 100755
index 0000000..7063e55
--- /dev/null
+++ b/scripts/bearbrowser-agent-machine
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+# bearbrowser-agent-machine — launch the bundled Noetica agent-machine as a
+# loopback-only sovereign sidecar, and point the embedded cockpit at it.
+#
+# Companion to bearbrowser-sidecar-server (the resolution sidecar) and the Rust
+# iot-sidecar. Lane 3 of docs/cockpit-composition-plan.md. The agent-machine binary
+# (built by build-agent-machine-sidecar.sh) already binds 127.0.0.1 only and reads
+# NOETICA_AM_PORT; this wrapper enforces the sovereign defaults and writes the live
+# port into the cockpit's runtime config so client-vue's resolver
+# (window.__COCKPIT_CONFIG__) targets this sidecar.
+#
+# NOETICA_AM_PORT listen port (default 8080)
+# NOETICA_OFFLINE sovereign default 1 (no off-device egress); set 0 to allow cloud models
+# COCKPIT_DIR the embedded cockpit dir to update (default: sibling Resources/cockpit)
+set -euo pipefail
+
+SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+RES_DIR="$(cd "$SELF_DIR/.." && pwd)" # …/Contents/Resources when installed in the .app
+
+# Locate the compiled sidecar binary (installed path, then dev/staging fallbacks).
+BIN=""
+for cand in \
+ "$RES_DIR/sidecars/bearbrowser-agent-machine-bin" \
+ "$RES_DIR/build/sidecars/bearbrowser-agent-machine-bin" \
+ "$SELF_DIR/../build/sidecars/bearbrowser-agent-machine-bin"; do
+ [ -x "$cand" ] && { BIN="$cand"; break; }
+done
+[ -n "$BIN" ] || { echo "[agent-machine] ERROR: sidecar binary not found (run build-agent-machine-sidecar.sh)"; exit 1; }
+
+PORT="${NOETICA_AM_PORT:-8080}"
+export NOETICA_AM_PORT="$PORT"
+export NOETICA_OFFLINE="${NOETICA_OFFLINE:-1}" # sovereign default: no off-device egress
+
+# Point the embedded cockpit at this sidecar: rewrite the loopback bases in
+# cockpit-config.js to our live port (the "host injects the ports" step).
+COCKPIT_DIR="${COCKPIT_DIR:-$RES_DIR/cockpit}"
+CFG="$COCKPIT_DIR/cockpit-config.js"
+if [ -f "$CFG" ]; then
+ # portable in-place edit (BSD/GNU sed differ on -i): write via tmp
+ sed -E "s#http://127\.0\.0\.1:[0-9]+#http://127.0.0.1:$PORT#g" "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
+ echo "[agent-machine] cockpit config → 127.0.0.1:$PORT ($CFG)"
+fi
+
+echo "[agent-machine] starting loopback sidecar on 127.0.0.1:$PORT (offline=$NOETICA_OFFLINE)"
+exec "$BIN"
diff --git a/scripts/build-agent-machine-sidecar.sh b/scripts/build-agent-machine-sidecar.sh
new file mode 100755
index 0000000..ba8e67f
--- /dev/null
+++ b/scripts/build-agent-machine-sidecar.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+# build-agent-machine-sidecar.sh — compile @noetica/agent-machine into a single
+# self-contained loopback-sidecar binary (bun --compile), staged into the .app.
+#
+# Lane 3 of docs/cockpit-composition-plan.md. The agent-machine is the local
+# sovereign brain (server.ts, 823 /api/* routes: graph, pipelines, devspace,
+# knowledge, chat). It's pure JS/TS (verified: @socioprophet/hellgraph is pure TS,
+# no native addons), so `bun build --compile` collapses it to one executable — no
+# node_modules, no host Node required. The heavy LLM runtime (Ollama) is NOT bundled;
+# agent-machine downloads it on demand (scripts/provision-runtime.ts) or uses a cloud
+# key, and boots + serves the non-chat surfaces without it.
+#
+# Source is reproducible: clone the noetica repo at a pinned ref, or point
+# AGENT_MACHINE_SRC at a local checkout (dev/CI cache). NOTE: this only COMPILES the
+# Noetica engine — it never edits it (Noetica lane: package-only).
+#
+# AGENT_MACHINE_SRC=/path/to/noetica # optional: skip the clone, use a local repo
+# AM_REF= # default: main
+# OUT=/path/to/Contents/Resources/sidecars # default: build/sidecars staging
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+AM_REF="${AM_REF:-main}"
+OUT="${OUT:-$REPO_ROOT/build/sidecars}"
+WORK="$REPO_ROOT/build/.agent-machine-src"
+BIN_NAME="bearbrowser-agent-machine-bin"
+AM_SUBDIR="agent-machine"
+
+log() { printf '[build-agent-machine] %s\n' "$*"; }
+
+command -v bun >/dev/null 2>&1 || { log "ERROR: bun is required (bun build --compile). Install: https://bun.sh"; exit 1; }
+
+# 1. Resolve the agent-machine source -----------------------------------------
+if [ -n "${AGENT_MACHINE_SRC:-}" ]; then
+ SRC="$AGENT_MACHINE_SRC/$AM_SUBDIR"
+ log "using local agent-machine source: $SRC"
+else
+ log "cloning noetica@$AM_REF (shallow)"
+ rm -rf "$WORK"
+ git clone --depth 1 --branch "$AM_REF" \
+ "${NOETICA_GIT_URL:-https://github.com/SocioProphet/noetica.git}" "$WORK"
+ SRC="$WORK/$AM_SUBDIR"
+fi
+[ -f "$SRC/server.ts" ] || { log "ERROR: agent-machine server.ts not found at $SRC"; exit 1; }
+
+# 2. Install deps (only if missing — don't mutate an existing checkout's lockfile) + compile
+if [ ! -d "$SRC/node_modules" ]; then
+ log "bun install in $SRC (fresh checkout)"
+ ( cd "$SRC" && bun install )
+else
+ log "node_modules present — skipping install (avoids touching the Noetica lockfile)"
+fi
+mkdir -p "$OUT"
+log "bun build --compile server.ts → $OUT/$BIN_NAME"
+( cd "$SRC" && bun build ./server.ts --compile --outfile "$OUT/$BIN_NAME" )
+[ -x "$OUT/$BIN_NAME" ] || { log "ERROR: compile produced no executable"; exit 1; }
+
+# 3. Smoke: boot on a throwaway loopback port, assert it serves ---------------
+PORT=8917
+NOETICA_AM_PORT="$PORT" NOETICA_OFFLINE=1 "$OUT/$BIN_NAME" >/tmp/am-sidecar-smoke.log 2>&1 &
+PID=$!
+for _ in $(seq 1 15); do
+ code="$(curl -s -m 2 -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT/api/status" 2>/dev/null || echo 000)"
+ [ "$code" = "200" ] && break
+ sleep 1
+done
+kill "$PID" 2>/dev/null || true
+[ "${code:-000}" = "200" ] || { log "ERROR: sidecar binary did not serve /api/status (got $code)"; cat /tmp/am-sidecar-smoke.log; exit 1; }
+log "smoke OK: /api/status 200 on 127.0.0.1:$PORT"
+log "done: $OUT/$BIN_NAME ($(du -h "$OUT/$BIN_NAME" | cut -f1))"
From 959cc34690b62f8fafc0350b139700deb6a4bab8 Mon Sep 17 00:00:00 2001
From: Michael Heller <21163552+mdheller@users.noreply.github.com>
Date: Sun, 19 Jul 2026 20:58:39 -0400
Subject: [PATCH 4/4] cockpit: document the sovereign capability subset
(mode-semantics gap #3)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Verified against agent-machine's route table: the local brain serves graph (76
/api/graph/* routes) + the agent surfaces + partial studio, but ZERO routes for
reasoner / entity-resolution / IE / algo / sherlock — those are separate GKE services
with no local equivalent. So they're intentionally UNSET in the sovereign config: the
resolver falls back to /svc/* (no offline proxy) and the surfaces degrade gracefully,
by design. Registering (connected mode) enables them. This resolves gap #3: it's a real
capability subset, not a mapping to be filled.
---
runtime/cockpit-config.js | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/runtime/cockpit-config.js b/runtime/cockpit-config.js
index c505c44..c880cf9 100644
--- a/runtime/cockpit-config.js
+++ b/runtime/cockpit-config.js
@@ -16,14 +16,19 @@ window.__COCKPIT_CONFIG__ = {
// 823 /api/* routes (graph, pipelines, devspace, knowledge, chat) on 127.0.0.1.
// BearBrowser injects the live port; 8080 is the agent-machine default.
agentMachine: 'http://127.0.0.1:8080',
- // The graph + studio surfaces are served by the same local brain in sovereign
- // mode (agent-machine's /api/graph/*, /api/studio/*), so they point at it too.
+ // The graph surface is served by the same local brain in sovereign mode
+ // (agent-machine serves 76 /api/graph/* routes), so it points at it. studio is
+ // partial locally (/api/studio); the full notebook/compute plane is cloud
+ // (lattice-studio), so studio degrades to read-only-ish offline.
hellgraph: 'http://127.0.0.1:8080',
studio: 'http://127.0.0.1:8080',
- // ie / algo / sherlock / reason / er are the CONNECTED cloud decomposition
- // (separate GKE services). In sovereign mode they either resolve into the
- // agent-machine's /api or degrade until the per-service loopback map lands
- // (mode-semantics follow-up — composition-plan gap #3). Left unset here so the
- // resolver's fallback governs, rather than pointing at a port that 404s.
+ // CAPABILITY TIERING (verified against agent-machine's route table):
+ // reason / er / ie / algo / sherlock are CONNECTED-ONLY — the local agent-machine
+ // serves ZERO routes for them (they're separate GKE services: owl-reasoner,
+ // entity-resolution, ie-engine, algo-engine, sherlock-engine). There is no local
+ // backend to map them to, so they are intentionally UNSET here: the resolver falls
+ // back to /svc/* (no proxy offline) and the surfaces degrade gracefully — they're
+ // built to handle an unavailable backend. Registering (connected mode) is what
+ // enables them. This is the sovereign capability subset, not a gap to be filled.
},
};