# 03 — TOPOLOGY / INFRA / DEPLOY Generated 2026-07-05 · prod arm64-5ee0ae12002f · regenerated after wipe > snapshot 2026-07-05 · regenerated from live code after the untracked-`git clean` wipe. Hosts/ports verified against 02-ARCHITECTURE + live source. Hybrid **M1 (build/orchestration) + Raspberry Pi (public runtime edge)** topology. The Next.js app is built ARM64 on the M1 (or Cloud Build) and shipped to the Pi, which is the only host exposed to the public internet. Postgres+pgvector, mail, and the app all run on/behind the Pi. The M1 additionally runs the out-of-band **agent contour** (build/dispatch/deploy orchestration) — that is NOT part of the product runtime. --- ## Hosts | Host | Address | Role | |---|---|---| | **M1 Mac** | `poolpooly@192.168.1.74` (LAN) | Dev + ARM64 build + agent contour (lead/engineer/worker swarm, board, stewards). Prisma reaches prod DB via SSH tunnel. **Not public-facing.** | | **Raspberry Pi** | `pi@mailhub.duckdns.org` ssh **:2046** | Public runtime edge: serves the standalone Next.js app on **:3010**, hosts prod Postgres+pgvector, SMTP/IMAP mail bridge, transport ingress. | | **Prod URL** | `nb.wool2.online` (app) · `bugs.wool2.online` (board/monitor/`/passport`) | Public HTTPS front for the Pi. | ## Processes / services (runtime) | Service | Where | Notes | |---|---|---| | **Next.js standalone app** | Pi · systemd `note-clone-3010.service` · port **3010** | All 424 API routes + App Router pages + RAG + AI + telephony + socket.io. `output: 'standalone'` build (`scripts/prepare-standalone.cjs`). Serving SHA of record → `arm64-` in `ExecStart`. | | **Postgres + pgvector** | Pi `localhost:55432` (container `noteclone-pgvector`) | **The one serving DB.** role/db = `noteclone`. 128 Prisma models; `DocumentChunk.embedding vector(1536)` + `fts tsvector`. Decoys: `localhost:5432` (app) and `:5433` (deploy-pg) are NOT prod — verify on :55432 only. | | **Mail bridge** | Pi | SMTP/IMAP inbound+outbound (agent-email, email ingest, notifications). | | **socket.io realtime** | inside the app process (Pi :3010) | realtime/embed/voice transport. | | **signal / telegram / whatsapp call pollers + SIP** | Pi / Codex-zone | telephony runtime (ports **9091/9092** = Codex zone; do not touch from contour). | | **Board server + Monitor** | M1 · `nc-agent-ops` | `bugs.wool2.online/monitor`, `/passport`, board render. Separate repo, not the product. | ## Deploy topology (ARM64 build → Pi) ``` worker branches off DEPLOYED base (autodispatch-base-351c) ─┐ → Build Candidate │ → build-steward.sh: gcloud builds submit │ cloudbuild.arm64-smoke.yaml (ARM64 artifact) OR local build:mac │ (CPUS=2, owner-gated) → scripts/pi-deploy.sh │ ships prebuilt artifact → pi@mailhub.duckdns.org :2046 │ swaps WHOLE app (cumulative!) → systemd restart note-clone-3010.service │ → verify-gate → Deployed ┘ ``` - **Cumulative deploy:** `pi-deploy` swaps the entire app, so the box = prod + all Build-Candidate branches together. Deploying an out-of-date base silently rolls back prior fixes → **STALE-BASE GUARD** in `build-steward.sh` syncs PROD to the real SHA the Pi serves (`curl nb.wool2.online/sw.js`) before assembling. - **Migrations are NOT auto-run** by deploy. `prisma migrate deploy` is gated to explicit owner-OK, applied over the SSH tunnel M1→Pi:55432. The `__tests__` folder under prisma has historically broken `migrate deploy` (drift root) — keep it out of the migration path. - **Base = DEPLOYED, never `origin/main`.** `main` is ~4774 commits behind live; the contour has no push creds. Branch only from the DEPLOYED base worktree. ## Environment / secrets (prod-relevant) | Var | Purpose | |---|---| | `DATABASE_URL` | → Pi Postgres+pgvector :55432 (via tunnel off-prod) | | `AUTH_SECRET` / `NEXTAUTH_*` | NextAuth session signing | | `ADMIN_EMAILS` | admin allowlist (csv, lowercase); fail-closed deny-all if unset. Prod: `admin@wool2.online,_@wool2.online` | | `CRON_SECRET` | scheduler-endpoint gate (see 05 CRON class) | | `STRIPE_*` / webhook secret | billing + Stripe `constructEvent` sig | | `SLACK_SIGNING_SECRET` | slack/command sig verify (fail-closed after SEC-0300) | | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_*`, xAI/OpenRouter keys | LLM/embeddings/TTS server keys (per-user quota gate = SEC-0309 umbrella) | | Telnyx/Twilio/Asterisk creds, `BRIDGE_SECRET` (BlueBubbles/iMessage) | telephony + messenger webhooks | | `PI_DEFAULT_TIMEZONE` | fallback TZ when `User.timezone` NULL | ## Known drift / risks (topology-level) - **SOURCE ↔ PROD DRIFT (system risk #1).** Security fixes are applied on prod (steward-on-live cherry-picks), but the source tree this passport reads from is behind live. Route auth-classes in 05 reflect **source**, not necessarily the hardened prod. Cross-check SEC-* board status (06) before trusting a "public/unauthed" flag. Ticket `OPS-DRIFT-RECONCILE`. - **No global `middleware.ts`** — every route self-guards; a new route defaults OPEN. - Three Postgres instances on the Pi; only `:55432` serves. Do not audit `:5432`/`:5433`.