---
id: glyco-architecture
title: Architecture — Stack, Routes, Data, AI Runtime, Security, Testing, Deploy
chapter: 05
source-of-truth:
  - docs/ARCHITECTURE.md (feature-sliced rules)
  - .dependency-cruiser.cjs (enforced boundaries)
  - src/middleware.ts (auth/security pipeline)
  - supabase/migrations/ (schema history)
stack: { next: 16.2.6, react: 19.2.6, typescript: 5-strict, tailwind: 4, supabase: ssr, ai-sdk: 6 }
---

# 05 — Architecture

> ⚠️ **Framework caveat (from repo `AGENTS.md`):** this is Next.js **16** — APIs,
> conventions, and file structure may differ from training data. Read the relevant guide
> in `node_modules/next/dist/docs/` before writing framework-level code.

## 1. Stack

| Layer | Tech | Version |
|-------|------|---------|
| Framework | Next.js (App Router, Turbopack) | 16.2.6 |
| UI | React | 19.2.6 |
| Language | TypeScript (`strict: true`) | ^5 |
| Styling | Tailwind CSS v4 (CSS `@theme`, no config file) + tw-animate-css | ^4 |
| Primitives | @base-ui/react + CVA + @floating-ui/react | 1.x |
| Animation | motion (Framer) 12 + GSAP 3.15 | |
| State | Zustand 5 (client), server components for data | |
| Forms | react-hook-form 7 + Zod 4 | |
| Charts | Recharts 3 | |
| Data/Auth | Supabase (`@supabase/ssr`) — RLS everywhere | |
| AI | Vercel AI SDK 6 + @ai-sdk/anthropic + @ai-sdk/openai | |
| Billing | Stripe | 22 |
| Rate limit / cache | Upstash Redis + @upstash/ratelimit | |
| Observability | Sentry + PostHog (client & server) | |
| Tests | Vitest 4 (+ Testing Library) · Playwright 1.49 (+ axe-core) · Artillery (stress) | |

Path alias `@/*` → `src/*`. Pre-commit: husky → `tsc --noEmit` + lint-staged
(prettier + eslint). ESLint forbids `any`, `@ts-ignore`, and `as any` chains.

## 2. Feature-sliced layout (enforced)

```
src/
├── app/             # routes only (page/layout/route/middleware)
├── features/        # per-domain slices
├── design-system/   # reusable UI (forms, charts, primitives, tokens)
├── components/      # legacy component tree (being migrated to features/)
└── lib/             # pure code: types, clients, security, ai, lgpd
```

Import direction — checked by `npm run lint:arch` (dependency-cruiser):

```
app → features → design-system → lib        (never the reverse; no cross-feature imports)
```

Branded IDs (`src/lib/types/ids.ts`): `PatientId`, `DoctorId`, `ClinicId`,
`PrescriptionId`, `AppointmentId`, `ProtocoloId`… — constructed at a single validation
seam; raw strings don't cross domain boundaries.

## 3. Route map (summary)

- **Public:** `/login`, `/termos`, `/privacidade`, `/cookies`, `/dpo`, `/dpa`
- **Onboarding FSM:** `/onboarding/{account-created → logo-reveal → name-capture →
  memed-connect → dashboard-handoff}` — middleware pins users to their current step
- **MFA:** `/mfa/enroll` (TOTP) — gates `/dashboard/*` (admin exemptions)
- **Doctor app:** `/dashboard` (home), plus `agenda, pacientes(/[id]), prontuarios,
  receitas, protocolos, bioimpedancia, cgm, canvas, clinical-ai, automacoes, equipe,
  faturamento, relatorios, teleconsulta, integracoes, configuracoes, privacidade, admin`
- **Patient portal:** `/paciente/*` — separate auth, simplified copy
- **API:** `/api/copilot` (+`/action /authorization /confirm /undo`), `/api/brief`,
  `/api/health(/deep)`, and `/api/v1/*` for domain resources (ai, clinical-ai, pacientes,
  protocolos, lgpd, stripe, memed, birdid, inbody, telemed, whatsapp, calendar, tiss,
  cron, csp-report)

## 4. Data layer (Supabase + RLS)

- **Clients:** cookie-bound **anon** client (`@supabase/ssr`) in middleware and route
  handlers → RLS enforced by `auth.uid()`. The service-role key is server-script-only and
  **forbidden in copilot tool paths** (ADR-0002).
- **Tenancy:** `user_id` is the primary tenant key; optional `clinic_id` via
  `clinic_members` for team plans.
- **Domains:** `user_profiles`, `onboarding_state`, `patients`, `appointments`,
  `glucose_readings`, `bioimpedance`, `lab_results(+reference_intervals)`,
  `prescriptions`, `protocolos`, `prontuario_entries(+history)`, `copilot_threads/
  messages`, `agent_actions` (audit ladder), `artifact_drafts`, `integrations_config`
  (per-doctor AI keys), `clinical_kb_sources/chunks` (RAG), `stripe_subscriptions`,
  `consent_records`, `audit_log`, `dsr_requests`, `whatsapp_messages`,
  `signed_documents`, `telemed_hubs/rooms`.
- **Migrations:** `supabase/migrations/` (~71 files, `YYYYMMDDHHMMSS_description.sql`).
  Migration files in the repo are the record of what is applied in prod — keep them in
  sync when applying via MCP/CLI.

## 5. AI / Copilot runtime

**Model resolution** (`src/lib/copilot/model.ts`): doctor-configured provider
(`integrations_config`: anthropic, openai, deepseek, gemini, groq, mistral, grok,
openrouter, kimi, perplexity) → server fallback **Anthropic claude-sonnet-4-6, branded
"Glyco Clínico 1.0"** → OpenAI fallback → synthetic stub.

**Copilot** (`POST /api/copilot`, SSE):

- Tool contract (`src/lib/copilot/types.ts`): `{ name, description, input: ZodSchema,
  kind: read|navigate|write|artifact, risk: low|med|high|irreversible,
  surface: inline|drawer|navigate|action, preview?, execute, undo? }`.
- `ToolCtx = { supabase (RLS-scoped), userId, clinicId?, audit, data }`; data source is
  Supabase in prod, synthetic in dev/demo.
- Write tools gated by license + explicit authorization
  (`/api/copilot/authorization`); irreversible actions need `/api/copilot/confirm`;
  reversible ones register undo tokens (`/api/copilot/undo`).
- Every invocation is written to `agent_actions`
  (`planned → previewed → confirmed → executed → undone|failed`).
- **Prompt-injection guard:** all user/patient content wrapped in sentinel tags treated
  as data; patient context is **anonymized** before reaching any LLM
  (`src/lib/ai/anonymize.ts` / `minimize`).
- Rate limiting per user via Upstash.

**Clinical RAG** (`POST /api/v1/clinical-ai/chat`): hybrid retrieval over
`clinical_kb_chunks` — dense `halfvec(3072)` HNSW (text-embedding-3-large) + sparse
tsvector (PT+EN) → rerank → citation-enforced generation → audit log. KB ingestion via
`scripts/ingest-kb-cli.ts`; evals via `npm run eval:*`.

## 6. Security pipeline

`src/middleware.ts`, in order: honeypot bait-path detection → per-request **CSP nonce**
(strict-dynamic; injected into RSC bootstrap) → open-redirect param sanitization →
Supabase session refresh → onboarding FSM gate → MFA gate → security headers.

- Headers (`src/lib/security-headers.ts` + `vercel.json`): HSTS preload (2y), nosniff,
  DENY framing, COOP/CORP same-origin, restrictive Permissions-Policy,
  `Cache-Control: no-store` on `/api/*`.
- Env (`src/lib/env.ts`): central parse; `assertProductionEnv()` fails prod builds with
  missing keys; `NEXT_PUBLIC_DEV_SKIP_AUTH` is blocked from Vercel builds at config level.
- Webhooks (Stripe, Memed, BirdID, Daily) verify HMAC signatures.
- LGPD: audit log on read/write/export/anonymize; patient export (ZIP); anonymization
  (right to be forgotten); DSR tracking; consent records per channel.

## 7. Testing & quality gates

| Gate | Command | Scope |
|------|---------|-------|
| Types | `npm run typecheck` | strict TS |
| Lint | `npm run lint` | next/core-web-vitals + TS rules |
| Architecture | `npm run lint:arch` | dependency-cruiser boundaries |
| Unit | `npm test` | Vitest + jsdom, colocated `*.test.ts(x)` |
| E2E | `npm run e2e` | Playwright: Chromium + Mobile Safari; axe-core a11y |
| Security e2e | `npm run e2e:security` | dedicated suite |
| Stress | `npm run stress:*` | Artillery: smoke/rampup/spike/rate-limit |
| Secrets | `npm run scan:bundle-secrets` | pre-build bundle scan |

## 8. Deployment

- **Vercel**, region `gru1` (São Paulo). Production deploys on merge to `main`.
- Crons (`vercel.json`): telemed room cleanup daily 03:00; WhatsApp reminders daily 12:00
  (authorized via `CRON_SECRET`).
- `next.config.ts`: `images.unoptimized`, `poweredByHeader: false`, build-time env +
  dev-skip-auth assertions.
- Preview deploy per PR. Local preview requires real npm install, dev CSP unsafe-eval,
  and `NEXT_PUBLIC_DEV_SKIP_AUTH` outside Vercel env (see README/docs).
