# 02 — ARCHITECTURE (C4-ish) snapshot: 2026-07-02, git 5302aacb2 Next.js App Router monolith ("note-clone" / Antigravity). ~2800 TS files, **390** API routes (`src/app/api/**/route.ts`), **115** Prisma models (`prisma/schema.prisma`). NextAuth Credentials, Prisma + pgvector RAG, Stripe billing, telephony/SIP, multi-tenant. **No global `middleware.ts`** — auth is enforced per-route. --- ## Stack + Versions (package.json) | Layer | Package | Version | |---|---|---| | Framework | `next` | 16.2.6 (App Router, `output: 'standalone'`, webpack) | | Runtime UI | `react` / `react-dom` | 19.2.0 | | Language | `typescript` | ^5 | | ORM | `@prisma/client` / `prisma` | ^5.22.0 (postgres, pgvector via `Unsupported("vector(1536)")`) | | Auth | `next-auth` | 5.0.0-beta.30 (`@auth/prisma-adapter` ^2.11.1, `@auth/core` 0.41.1) | | Passwords/2FA | `bcryptjs` ^3, `otplib` ^12 | | | AI SDK | `ai` ^5.0.101 + `@ai-sdk/{openai ^2.0.71, anthropic ^2.0.48, google ^2.0.43}` | | | Raw LLM SDKs | `openai` ^6.9.1, `anthropic` (stub 0.0.0) | | | Embeddings (local) | `@xenova/transformers` ^2.17.2 | | | Billing | `stripe` ^20, `@stripe/{stripe-js,react-stripe-js}` | | | Messaging | `@slack/bolt` ^4.6, `node-telegram-bot-api` ^0.66, `telegram` ^2.26, `twitter-api-v2`, `facebook-nodejs-business-sdk` | | | Telephony | `twilio` ^5.10 (+ Telnyx/Asterisk via custom clients) | | | Mail | `nodemailer` ^8, `resend` ^6.5, `mailparser` ^3.9 | | | Google | `googleapis` ^166, `google-auth-library` ^10 | | | Ingest/parse | `pdf-parse`, `pdf2json`, `mammoth`, `officeparser`, `xlsx`, `epub2`, `tesseract.js` (OCR), `jsdom`, `@mozilla/readability`, `turndown`, archives (`jszip`,`node-unrar-js`,`7zip-min`,`tar-stream`,`seek-bzip`) | | | Media | `sharp` 0.34.5, `jimp`, `youtube-dl-exec`, `youtube-transcript`, `@vercel/og` | | | Realtime | `socket.io` ^4.8 (+ client), `web-push` ^3.6 | | | Graph/viz | `d3`, `graphology`, `sigma`, `deck.gl` 8.9, `umap-js`, `recharts`, `framer-motion` | | | State/i18n | `zustand` ^5, `next-intl` ^4.5, `zod` ^4.1 | | | Observability | `@sentry/nextjs` ^10.27 | | pnpm workspace. Build `next build --webpack` → standalone (`scripts/prepare-standalone.cjs`). Tests: Playwright + `tsx --test`. --- ## Level 1 — Context (actors & external systems) **Actors:** end users (multi-tenant, tiered via billing), team members (RBAC), admins, embedded/public widget visitors, inbound/outbound phone callers, Slack/Telegram bot users. **External systems:** | System | Purpose | Code touchpoint | |---|---|---| | OpenAI | chat, embeddings (1536-d), transcription | `src/lib/ai/providers/openai.ts`, `src/lib/rag/embeddings.ts` | | Anthropic (Claude) | chat/reasoning | `src/lib/ai/providers/anthropic.ts` | | Google AI (Gemini) | chat + Drive/Docs ingest | `src/lib/ai/providers/google.ts`, `src/lib/integrations/googleDriveIndexer.ts` | | xAI / OpenRouter | alt LLM providers | `src/lib/ai/providers/{xai,openrouter}.ts` | | Stripe | subscriptions/billing | `src/lib/stripe/*`, `src/lib/billing/*` | | Slack | bot + forwarding | `src/lib/slack/bot.ts` | | Telnyx / Twilio / Asterisk (SIP) | PSTN calls, SIP provisioning | `src/lib/telephony/**` (`channels/pstnCall/telnyxClient`, `asteriskSipSync.ts`) | | Telegram / Twitter / Facebook | channel integrations | `src/lib/telegram`, `src/lib/messenger`, `src/lib/social` | | Mail (SMTP/Resend/IMAP) | inbound+outbound email | `src/lib/emails/sender.ts`, `src/lib/email/{parser,store}.ts` | | Notion / Dropbox / OneDrive / Google Drive | corpus ingest | `src/lib/integrations/*Indexer.ts` | | Sentry | error monitoring | `@sentry/nextjs` | --- ## Level 2 — Containers | Container | Host | Role | |---|---|---| | **Next.js web app** | Pi (`mailhub.duckdns.org`, ssh :2046) | standalone server: all 390 API routes, App Router pages, RAG, AI, telephony, socket.io | | **Postgres + pgvector** | Pi localhost:55432 (prod) | 115 models; `DocumentChunk.embedding vector(1536)` + `fts tsvector` | | **Mail Pi** | Pi | SMTP/IMAP inbound-outbound mail bridge | | **Contour orchestration** | M1 (`poolpooly@192.168.1.74`) | dev/build + multi-model agent orchestration (out-of-band from app runtime) | Prisma access to prod DB is via SSH tunnel from M1 → Pi localhost:55432. Prisma client singleton: `src/lib/prisma.ts` (global cache off-prod). --- ## Level 3 — Key Components (src/lib, one line each) **Auth** (`src/auth.ts` = NextAuth Credentials entry) - `auth/password.ts` — bcrypt hash/verify - `auth/2fa.ts` — TOTP + recovery codes (`otplib`) - `auth/sessions.ts` — `createUserSession` / `ensureSessionActive` (server-side session records) - `auth/rbac.ts` — role/permission checks; `auth/linking.ts` — account linking tokens - `auth/offlineIdentity.ts` — offline/local identity fallback; `auth/devBypass.ts` — local dev login bypass - `middleware/rateLimit.ts` — per-route rate limit (`consumeRateLimit`); `middleware/billingGuard.ts`, `middleware/errorHandler.ts` **Billing** (`src/lib/billing/*`, `src/lib/stripe/*`) - `stripe/billingService.ts` — subscription lifecycle; `stripe/stripeClient.ts` — SDK client; `stripe/pricing.ts` - `billing/tierLimits.ts`, `features.ts`, `videoFeatures.ts`, `gameFeatures.ts`, `personalSourceQuota.ts` — tier gating/quotas **RAG / vector** (`src/lib/rag/*`) - `vectorStore.ts` — pgvector store: `addDocuments`, `similaritySearch` (cosine `<=>`), `hybridSearch` (vector+FTS), `similaritySearchBestChunkPerSource`, `getOrderedChunksForSources` - `embeddings.ts` — OpenAI embeddings (1536-d, lazy SDK, `MOCK_OPENAI` mock, batch via `generateEmbeddingsBatch`) - `chunking.ts` — `chunkTextWithOffsets`; `textAnchors.ts` — line/paragraph anchors - `documentStore.ts` — Document metadata/stats; `accessScope.ts` — tenant-scoped source access; `entityRetrieval.ts`, `relatedNotes.ts` **Ingest** (`src/lib/ingest/*`) - `fileIngestor.ts` — entry: sanitize→dedupe→corpus route→extract→write - `extractors/{pdf,office,html,text,image,audio,video,archive,archive-extra}.ts` — per-type extraction (`index.ts` dispatch) - `dedupe.ts` (hash), `corpusRouter.ts`, `sourceWriter.ts` (→ `processDocument`), `limits.ts` (size/ext) **Documents** — `documents/processDocumentCore.ts` = server-only RAG ingestion core (called by routes + `actions.ts` wrapper) **videoContext** (`src/lib/videoContext/*`) — video pipeline: `runtime.ts`, `transcription.ts`, `scenes.ts`, `evidence.ts`, `frameHydration.ts`, `cloud/finalize.ts`, `runEventBus.ts`, `assetStorage.ts` **Telephony** (`src/lib/telephony/*`) — SIP/PSTN: `sipProvisioning.ts`, `asteriskSipSync.ts`, `channels/pstnCall/telnyxClient.ts`, `outbound/{orchestrator,sipPstnAdapter}.ts` (`provider='telnyx-outbound'`), `phoneVoiceEngine.ts`, `groundedAnswer.ts` (RAG-answer over phone), guards (`budgetGuard,quietHoursGuard,loopGuard,channelGuard`), `consentLog.ts` **Embed** (`src/lib/embed/*`) — public/embeddable widget: `service.ts`, `liveRuntime.ts`, `liveTransport.ts`, `publicGuardrails.ts`, `guard.ts`, `wildcardGate.ts`, `leads.ts`, `origin.ts`/`publicHost.ts` (CORS/origin) **AI / tools** (`src/lib/ai/*`) - `router.ts` — `routeRequest` (complexity + semantic routing to provider/model); `semanticRouter.ts` - `providers/{openai,anthropic,google,xai,openrouter}.ts` (+ `base.ts` ProviderId/Message) - `models/registry.ts` — model reference resolution (`capabilities`, `availability`, `picker`) - `byok.ts` / `serverApiKeys.ts` / `clientApiKeys.ts` — bring-your-own-key; `budgets.ts`, `pricing.ts`, `cost.ts` - `toolRegistry.ts` / `toolCatalog.ts` / `toolContext.ts` — agent tool wiring - `tools/*.ts` — ~60 agent tools (createNote, addTask, makeOutboundCall, generatePodcast, factCheck, planTrip, composeDiagram, semanticSearchSources, …) --- ## RAG Pipeline ``` upload → ingest/fileIngestor.ts sanitize filename → dedupe (hashBuffer/findDuplicate) → corpusRouter.resolveCorpusFor → extractors/ (text) → sourceWriter.writeSource → documents/processDocumentCore.processDocument: chunkTextWithOffsets (rag/chunking + textAnchors) → generateEmbeddingsBatch (rag/embeddings, OpenAI 1536-d) → vectorStore.addDocuments → Prisma DocumentChunk (embedding vector(1536), fts tsvector, line/para anchors) → documentStore + computeDocumentStats query (chat) → chat route (e.g. api/chat-global/route.ts) → auth() tenant scope → allowed sourceIds (rag/accessScope) → generateEmbeddings(query) → vectorStore.hybridSearch / similaritySearch (cosine <=> + FTS) → context → ai/router.routeRequest → provider → streamed answer ``` Chunk schema: `DocumentChunk` (`prisma/schema.prisma:1486`) — `embedding Unsupported("vector(1536)")?`, `fts Unsupported("tsvector")?`, offsets + `anchorId/startLine/endLine/startParagraph/endParagraph` (nullable pre-migration `20260505000000_document_chunk_anchors`; read paths NULL-tolerant). Written/read via raw SQL in `vectorStore.ts`. --- ## Request Flow (per-route, NO global middleware) There is **no `src/middleware.ts` / `middleware.ts`** — Next.js edge middleware is absent. Each Route Handler enforces its own auth/rate-limit/billing. `src/middleware/` is an in-app helper folder (`rateLimit.ts`, `billingGuard.ts`, `errorHandler.ts`), NOT Next middleware. ``` Request → src/app/api/<...>/route.ts (GET/POST) → const session = await auth() // from '@/auth' (NextAuth v5); often .catch(()=>null) fail-closed → guard: rbac / team permissions (lib/team/permissions canView/canEdit), middleware/rateLimit.consumeRateLimit, billingGuard/tierLimits → service layer (src/lib/**: ai, rag, billing, telephony, ingest, embed…) → prisma (src/lib/prisma.ts singleton) → Postgres/pgvector → NextResponse.json / streamed AI response ``` Auth entry `src/auth.ts`: NextAuth Credentials provider — Zod-validated creds → `verifyPassword` → optional TOTP (`consumeRecoveryCode`) → `createUserSession`; supports offline identity + local dev bypass; suspension check via raw `SELECT "isSuspended"` (schema-drift tolerant, codes P2009/P2022/42703). Multi-tenant isolation is enforced in-service via `userId`/source-scope filters (e.g. `rag/accessScope`, `VectorFilter.sourceIds`), not by a shared middleware — every route is individually responsible for scoping.