diff --git a/scripts/assemble-cockpit.sh b/scripts/assemble-cockpit.sh new file mode 100755 index 0000000..025d96d --- /dev/null +++ b/scripts/assemble-cockpit.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# assemble-cockpit.sh — build + stage the FULL sovereign cockpit into a Resources +# layout ready to drop into BearBrowser.app/Contents/Resources/. +# +# OBJ-A of the composition (docs/cockpit-composition-plan.md): fuse the pieces that +# landed across Lanes 1–4 + Receipts into one assembled surface — +# +# Contents/Resources/ +# ├─ cockpit/ client-vue, built + config-injected (Lane 2) +# │ └─ cockpit-config.js sovereign runtime config (resolver reads it) +# ├─ sidecars/ +# │ └─ bearbrowser-agent-machine-bin 68M single binary (Lane 3) +# ├─ policy/bearbrowser-contract.yaml the enforcing contract (Lane 4) +# └─ scripts/ +# ├─ bearbrowser-agent-machine sidecar launcher (Lane 3) +# ├─ bearbrowser-agent-machine-gate.py governance gate (Lane 4) +# ├─ agent-control-bridge.py the one policy engine +# ├─ bearbrowser-receipts.py trust-ledger service (Receipts) +# └─ bearbrowser-cockpit-up runtime orchestrator (this OBJ) +# +# Reproducible; pass COCKPIT_SRC / AGENT_MACHINE_SRC to build from local checkouts. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +STAGE="${STAGE:-$REPO_ROOT/build/app-staging/Contents/Resources}" +log() { printf '[assemble] %s\n' "$*"; } + +log "staging root: $STAGE" +rm -rf "$STAGE"; mkdir -p "$STAGE/scripts" "$STAGE/policy" "$STAGE/sidecars" + +# 1. Cockpit (Lane 2) — build client-vue → $STAGE/cockpit + inject sovereign config. +log "building cockpit …" +OUT="$STAGE/cockpit" bash "$REPO_ROOT/scripts/build-cockpit.sh" + +# 2. agent-machine sidecar (Lane 3) — bun --compile → $STAGE/sidecars/…-bin (self-smoke). +log "building agent-machine sidecar …" +OUT="$STAGE/sidecars" bash "$REPO_ROOT/scripts/build-agent-machine-sidecar.sh" + +# 3. Stage the runtime scripts + the enforcing contract (Lane 4 + Receipts + orchestrator). +log "staging runtime scripts + policy contract …" +for s in bearbrowser-agent-machine bearbrowser-agent-machine-gate.py agent-control-bridge.py \ + bearbrowser-receipts.py bearbrowser-cockpit-up; do + cp "$REPO_ROOT/scripts/$s" "$STAGE/scripts/$s" +done +cp "$REPO_ROOT/policy/bearbrowser-contract.yaml" "$STAGE/policy/bearbrowser-contract.yaml" +chmod +x "$STAGE/scripts/"* + +# 4. Manifest — record what was assembled. +cat > "$STAGE/cockpit-app.manifest.json" < gate:8080 -> sidecar:8091 ; receipts:8092 ; sovereign loopback only" +} +JSON + +log "assembled OK → $STAGE" +log " cockpit files: $(find "$STAGE/cockpit" -type f | wc -l | tr -d ' ')" +log " sidecar: $(du -h "$STAGE/sidecars/bearbrowser-agent-machine-bin" | cut -f1)" +log "run it: $STAGE/scripts/bearbrowser-cockpit-up" diff --git a/scripts/bearbrowser-cockpit-up b/scripts/bearbrowser-cockpit-up new file mode 100755 index 0000000..cd91999 --- /dev/null +++ b/scripts/bearbrowser-cockpit-up @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# bearbrowser-cockpit-up — stand up the assembled sovereign cockpit, governed. +# +# The runtime capstone (OBJ-A). Brings up the three loopback services and points the +# embedded cockpit at the GATE (never the raw sidecar) so every agent action is +# classified before execution: +# +# cockpit (resource://) ─► gate 127.0.0.1:8080 ─► agent-machine sidecar :8091 +# (agent-control-bridge, sovereign) receipts :8092 +# +# Loopback only, no off-device egress. Foreground; Ctrl-C / SIGTERM tears down all three. +# +# BEARBROWSER_AM_GATE_PORT cockpit-facing gate port (default 8080) +# NOETICA_AM_UPSTREAM_PORT real sidecar port, behind the gate (default 8091) +# BEARBROWSER_RECEIPTS_PORT trust-ledger port (default 8092) +set -uo pipefail + +SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RES="$(cd "$SELF_DIR/.." && pwd)" # …/Contents/Resources +BIN="$RES/sidecars/bearbrowser-agent-machine-bin" +GATE="$SELF_DIR/bearbrowser-agent-machine-gate.py" +RECEIPTS="$SELF_DIR/bearbrowser-receipts.py" +COCKPIT_CFG="$RES/cockpit/cockpit-config.js" + +GATE_PORT="${BEARBROWSER_AM_GATE_PORT:-8080}" +UPSTREAM_PORT="${NOETICA_AM_UPSTREAM_PORT:-8091}" +RECEIPTS_PORT="${BEARBROWSER_RECEIPTS_PORT:-8092}" + +for f in "$BIN" "$GATE" "$RECEIPTS"; do + [ -e "$f" ] || { echo "[cockpit-up] ERROR: missing $f (run assemble-cockpit.sh)"; exit 1; } +done + +pids=() +cleanup() { echo "[cockpit-up] stopping…"; for p in "${pids[@]:-}"; do [ -n "$p" ] && kill "$p" 2>/dev/null; done; } +trap cleanup EXIT INT TERM +probe() { curl -s -m 1 -o /dev/null -w '%{http_code}' "$1" 2>/dev/null || echo 000; } + +# 0. Point the cockpit at the GATE (governed) up front — the port is known before +# anything starts. Everything the sidecar serves (agentMachine/graph/studio) routes +# through the gate, so nothing reaches the agent un-classified. +if [ -f "$COCKPIT_CFG" ]; then + sed -E "s#http://127\.0\.0\.1:[0-9]+#http://127.0.0.1:$GATE_PORT#g" "$COCKPIT_CFG" > "$COCKPIT_CFG.tmp" && mv "$COCKPIT_CFG.tmp" "$COCKPIT_CFG" + echo "[cockpit-up] cockpit → gate 127.0.0.1:$GATE_PORT" +fi + +# 1. Start all three loopback services (the gate binds immediately and forwards once +# the sidecar answers; no serial blocking). +echo "[cockpit-up] sidecar → :$UPSTREAM_PORT gate → :$GATE_PORT receipts → :$RECEIPTS_PORT" +NOETICA_AM_PORT="$UPSTREAM_PORT" NOETICA_OFFLINE="${NOETICA_OFFLINE:-1}" "$BIN" >/tmp/bb-cockpit-sidecar.log 2>&1 & pids+=($!) +NOETICA_AM_UPSTREAM_PORT="$UPSTREAM_PORT" BEARBROWSER_AM_GATE_PORT="$GATE_PORT" python3 "$GATE" >/tmp/bb-cockpit-gate.log 2>&1 & pids+=($!) +BEARBROWSER_RECEIPTS_PORT="$RECEIPTS_PORT" python3 "$RECEIPTS" >/tmp/bb-cockpit-receipts.log 2>&1 & pids+=($!) + +# 2. Bounded health summary — report, don't hang. +gate_ok=000; rcpt_ok=000 +for _ in $(seq 1 30); do + gate_ok="$(probe "http://127.0.0.1:$GATE_PORT/api/status")" + rcpt_ok="$(probe "http://127.0.0.1:$RECEIPTS_PORT/health")" + [ "$gate_ok" = "200" ] && [ "$rcpt_ok" = "200" ] && break + sleep 1 +done +echo "[cockpit-up] health: gate(→sidecar)=$gate_ok receipts=$rcpt_ok" +[ "$gate_ok" = "200" ] || { echo "[cockpit-up] ERROR: gate not serving"; cat /tmp/bb-cockpit-gate.log; exit 1; } + +echo "[cockpit-up] SOVEREIGN COCKPIT UP — cockpit=$RES/cockpit/index.html gate=$GATE_PORT sidecar=$UPSTREAM_PORT receipts=$RECEIPTS_PORT" +wait diff --git a/scripts/build-cockpit.sh b/scripts/build-cockpit.sh index a333a36..dda7198 100755 --- a/scripts/build-cockpit.sh +++ b/scripts/build-cockpit.sh @@ -38,7 +38,7 @@ else SRC="$WORK/$CLIENT_VUE_SUBDIR" fi [ -d "$SRC" ] || { log "ERROR: client-vue not found at $SRC"; exit 1; } -grep -q "cockpitRuntime" "$SRC/src/config/cockpitRuntime.ts" 2>/dev/null \ +grep -q "resolveBase" "$SRC/src/config/cockpitRuntime.ts" 2>/dev/null \ || { log "ERROR: cockpit source lacks the runtime resolver (needs socioprophet #468 landed)"; exit 1; } # 2. Build the static bundle ---------------------------------------------------