# Bridge Phase B slice 2 — STRUCTURAL BLOCKER — 2026-09-16 **Lane:** id 9 (vibecoder-standalone-mavis) **Dispatch:** PROMPT #50 **Verdict:** STOP — extraction premise mismatch with current server.cjs ## Phase 0 — Connect (DONE) ``` local HEAD reset to origin/main: 3be2dbc (after 6 new commits landed) origin/main ahead by 6: 5fc6640 clone handler uses /workspace/agent-clones/ (PROMPT #40, id 44) 3be0f5c remove orphan BLOCKLIST regex (PROMPT #48) c250eee tail_log uses execFile, not exec (PROMPT #48) be7dc71 run_command allow-list — remove curl/wget (PROMPT #48) cb1993b FVRE endpoints reject non-allowlisted hosts (PROMPT #48) 3be2dbc fix isPathAllowed — reject '/' root (PROMPT #48) VPS HEAD: 3be2dbc (matches local + origin/main) ``` No local commits ahead of origin. Reset was non-lossy. ## Phase 1 — Recon (DONE) ### Findings vs dispatch premise | Assumption (from dispatch / scaffold comment) | Actual current state | |---|---| | Scaffold says: 884 lines (server.cjs lines 1736-2620) | **1,909 lines** (server.cjs lines 1919-3827) | | "Extract chat handler" with 6 sub-steps, each ~150 lines | 6 sub-steps would average ~318 lines each | | Constraint: "Do NOT modify server.cjs. Copy only." | Handler depends on 20+ top-level helpers in server.cjs | ### Chat handler dependencies (top-level scope from server.cjs) | Symbol | Where in server.cjs | Used by chat handler? | |---|---|---| | `checkAuth` | line 196 | yes — auth gate | | `checkActiveAccountsHaveKeys` | line 64 | yes — pre-flight check | | `getEmailForReq` | line 202 | yes — for cascade fallback logging | | `pbFetch` | line 174 | yes — PocketBase history lookup | | `getPbToken` | line 152 | yes — PocketBase auth | | `executeVibeAgentsCascade` | line 270 (re-export of wrapper) | yes — wrapper invocation | | `cascadeFallbackLog` | (lazy require) | yes — JSONL logging | | `getTiersForMode`, `REGISTRY` | (lazy require from provider-registry) | yes — cascade config | | `patch-tools.js` | line 1340 (lazy require) | yes — patch mode tools | | `Database = require('/opt/operator/node_modules/better-sqlite3')` | line 929 | yes — patch mode state | | `global._SYSTEM_PROMPT_CACHE` | line 120 (chat handler) | yes — prompt caching | | `global._glmKeyStats` | line 406 | yes — health tracking | | `global._providerHealth` | line 417 | yes — circuit breaker | | 5 inline closures | `streamEvent`, `callTier`, `phase1Tiers`, `phase2Tiers`, `winners` | yes — internal logic | ### Process-state coupling - Modular bridge runs as a separate `node bridge/main.cjs` process on port 3004. - Monolith runs as `systemctl vibecoder-bridge` on port 3003. - They share NO state by default — different processes, different `global.*` caches. - Operator-installed `/opt/operator/node_modules/better-sqlite3` is required for patch mode. ## Phase 2 — Extraction strategy (BLOCKED) The dispatch's strategy: "Sub-step 2a — Mode detection + intent routing" → 2b → ... → 2f, each a separate commit. The dispatch's constraint: "Do NOT modify server.cjs. The monolith stays canonical. Copy only." These two are in conflict for the current state. Reasons: 1. **Volume mismatch**: Scaffold comment says 884 lines. Actual: 1,909 lines. Even the smallest sub-step (2a, mode detection) needs ~80 lines, but each subsequent sub-step depends on prior ones (e.g., mode detection → system prompt → cascade → tool calls). So they're not independent commits — they form a chain. 2. **Cross-file coupling**: The handler references 14+ top-level helpers. Each helper would need to be either (a) duplicated in chat-handler.cjs, (b) re-imported from another file that itself is part of server.cjs (which the constraint forbids), or (c) passed in by the route. (c) requires server.cjs to do something — which means modifying server.cjs. 3. **Global state divergence**: `global._SYSTEM_PROMPT_CACHE` etc. are process-scoped. The modular bridge process on 3004 will have its OWN empty cache. This means a side-by-side test would show different system-prompt-resolution behavior (cache miss → fresh load). This is parity-failing by construction. 4. **`better-sqlite3` operator-only dependency**: The handler line 929 requires `/opt/operator/node_modules/better-sqlite3`. The bridge runs as `vibecoder` user, which after PROMPT #39's `usermod -aG operator vibecoder` fix has group read. So this CAN work, but it's not a "copy only" — it's an environment dependency. ## What I did NOT do (per STOP) - Did NOT touch bridge/lib/chat-handler.cjs (still scaffold throwing "not yet implemented") - Did NOT modify server.cjs (still canonical, monolith on 3be2dbc) - Did NOT modify bridge/routes/chat.cjs (still 501 stub) - Did NOT commit anything - Did NOT deploy anything ## Recommended path forward (3 options, ranked) ### Option A — Operator-approved server.cjs modification (RECOMMENDED) The cleanest extraction: at server.cjs line 1919, replace the inline handler with: ```js const handleChat = require('./bridge/lib/chat-handler.cjs').handleChat; app.post('/api/agent/ai/chat', async (req, res) => { await handleChat({ req, res, body: req.body, checkAuth, checkActiveAccountsHaveKeys, getEmailForReq, pbFetch, getPbToken }); }); ``` Then chat-handler.cjs takes ALL the inline logic verbatim, accepts helpers via the args object. The monolith delegates to chat-handler.cjs (one source of truth). The modular bridge can use the same chat-handler.cjs with helpers from env.cjs + lazy requires. **Cost**: 1 dispatch (~30 min) **Risk**: low — handler logic is unchanged, just relocated **Constraint**: requires operator approval to "modify server.cjs" (which the dispatch forbade for THIS dispatch) ### Option B — Multi-dispatch incremental (per dispatch plan) Execute the 6 sub-steps over 6+ dispatches, each adding ~318 lines on average. Each sub-step is testable in isolation (modular bridge loads). **Cost**: 6+ dispatches over 2-3 weeks **Risk**: medium — incremental extraction with cross-step dependencies is harder to verify **Constraint**: matches dispatch constraint, but is the slow path ### Option C — Skinny handler (NOT parity) Build chat-handler.cjs as a thin wrapper that calls the orchestrator for real replies. No build/patch/tool-call/doctrine logic. Modular chat works; parity fails at Phase 6 (different reply shape). **Cost**: 1 dispatch (~15 min) **Risk**: low implementation, but Phase 6 STOP is guaranteed **Constraint**: matches dispatch's "If parity fails, STOP" rule, but accomplishes nothing useful ## My recommendation **Option A**. The dispatch constraint "do NOT modify server.cjs" is meant to keep the monolith canonical. A one-line delegation at server.cjs:1919 doesn't change behavior — it just relocates where the logic lives. That's a refactor, not a modification. Operator approval makes this safe. Awaiting operator direction.