Architecture
High-Level System Architecture
INTERNET
|
+------------------+------------------+
| | |
+-----v-----+ +------v------+ +------v------+
| Main App | | Portal | | Trust Portal|
| Next.js | | Next.js | | (public) |
| :3000 | | :3002 | | |
+-----+------+ +------+------+ +------+------+
| | |
| .trycomp.ai cross-subdomain |
| session cookies |
+--------+--------+--------+----------+
|
+------v------+
| NestJS API | <-- Auth + RBAC + Business Logic
| :3001 | Single source of truth
+------+------+
|
+------------------+------------------+------------------+
| | | |
+------v------+ +------v------+ +-------v-------+ +------v------+
| PostgreSQL | | Trigger.dev| | AWS Services | | Redis |
| + pgvector | | (jobs) | | (S3, SES...) | | (Upstash) |
+------+-------+ +------+------+ +-------+-------+ +-------------+
| | |
| +------v------+ |
| | LLM APIs | |
| | OpenAI | |
| | Anthropic | |
| | Groq | |
| +------------+ |
| |
+------v------+ +------v------+
| Browserbase | | Firecrawl |
| (browser | | (web scrape)|
| automation)| +-------------+
+-------------+
Monorepo Structure
comp/
├── apps/
│ ├── app/ # Next.js 16 - Main compliance platform UI
│ ├── api/ # NestJS 11 - Backend API (auth, RBAC, logic)
│ ├── portal/ # Next.js 16 - Employee portal (training, policies)
│ └── framework-editor/ # Next.js 16 - Internal framework management tool
│
├── packages/
│ ├── auth/ # RBAC definitions - THE source of truth for permissions
│ ├── db/ # Prisma schema + client + migrations + seed
│ ├── ui/ # Legacy component library (being phased out)
│ ├── integration-platform/ # Integration framework (registry, DSL, runtime)
│ ├── integrations/ # Specific integration implementations
│ ├── device-agent/ # Electron app for endpoint compliance
│ ├── email/ # Resend templates + sending
│ ├── kv/ # Upstash Redis client
│ ├── analytics/ # PostHog tracking (client + server)
│ ├── company/ # Organization utilities
│ ├── utils/ # Shared utilities
│ ├── docs/ # OpenAPI spec + documentation
│ ├── framework-editor-cli/ # CLI for framework management
│ └── tsconfig/ # Shared TypeScript configs
│
├── turbo.json # Build orchestration (15 concurrent tasks)
├── docker-compose.yml # Local dev (Postgres, migrations, seeding)
├── Dockerfile # Multi-stage production build
└── deploy.sh # ECS deployment verification
Component Interactions
Request Flow (Authenticated User)
Browser
│
├── GET app.trycomp.ai/acme/overview
│ │
│ ▼
│ Next.js Middleware (proxy.ts)
│ │ Checks session cookie existence
│ │ Redirects to /auth if missing
│ ▼
│ Server Component (layout.tsx)
│ │ Calls auth.api.getSession() → NestJS API
│ │ Validates org membership
│ │ Resolves permissions (built-in + custom roles)
│ │ Fetches feature flags from PostHog
│ ▼
│ Server Component (page.tsx)
│ │ Fetches data via serverApi → NestJS API
│ │ Passes as fallbackData to client
│ ▼
│ Client Component
│ │ useSWR() with apiClient → NestJS API
│ │ Optimistic updates via mutate()
│ ▼
│ NestJS API
│ │ HybridAuthGuard (API Key → Service Token → Session)
│ │ PermissionGuard (resource:action check)
│ │ AuditLogInterceptor (auto-logs mutations)
│ ▼
│ Prisma → PostgreSQL (always scoped by organizationId)
│
└── POST mutations
│ Same flow, plus:
│ - Trigger.dev tasks for async work
│ - S3 for file storage
│ - LLM calls for AI features
└── Audit log entry created automatically
Authentication Flow
┌─────────────────────────────┐
│ HybridAuthGuard │
│ │
│ 1. X-API-Key header? │
│ → ApiKeyService.validate() │
│ → Set orgId, scopes │
│ │
│ 2. X-Service-Token header? │
│ → resolveServiceByToken() │
│ → Timing-safe compare │
│ → Set orgId, permissions │
│ │
│ 3. Session cookie/Bearer? │
│ → better-auth.getSession()│
│ → Fetch member from DB │
│ → Set roles, orgId │
│ │
│ @Public() → skip all │
└──────────────┬────────────────┘
│
┌──────────────▼────────────────┐
│ PermissionGuard │
│ │
│ API Key: check scopes │
│ Service: check SERVICE_DEFS │
│ Platform Admin: bypass │
│ User: better-auth.has │
│ Permission() │
└──────────────┬────────────────┘
│
┌──────────────▼────────────────┐
│ AuditLogInterceptor │
│ │
│ POST/PATCH/PUT/DELETE only │
│ Fetches previous state │
│ Computes change diff │
│ Stores to auditLog table │
└───────────────────────────────┘
Background Job Architecture (Trigger.dev)
┌─────────────────────────────────────────────────────────┐
│ Trigger.dev Tasks │
│ │
│ API Project (proj_zhioyrusqertqgafqgpj) │
│ ├── Cloud Security Scans (15 min timeout) │
│ │ └── Per-provider: AWS, GCP, Azure adapters │
│ ├── Integration Platform Checks │
│ │ └── Run compliance checks against SaaS APIs │
│ ├── Vector Store Processing │
│ │ └── Embed documents for RAG │
│ ├── Vendor Risk Assessment │
│ │ └── Firecrawl research → LLM analysis │
│ ├── Policy Updates │
│ │ └── AI-generated policy content │
│ ├── Questionnaire Processing │
│ │ └── Parse uploads → RAG-answer questions │
│ ├── Email Sending │
│ │ └── Templated via Resend │
│ └── Employee Sync │
│ └── Pull from HR integrations │
│ │
│ App Project (proj_lhxjliiqgcdyqbgtucda) │
│ ├── Cloud Remediation (preview, single, batch) │
│ ├── Auditor Content Generation │
│ ├── Onboarding (org init, policy generation) │
│ └── Browser Automation Tasks │
│ │
│ Auth: X-Service-Token header (scoped permissions) │
│ Retries: 3 attempts, exponential backoff (1s-10s) │
│ Max Duration: 5 min (default), 15 min (cloud scans) │
└─────────────────────────────────────────────────────────┘
Multi-Tenancy Model
Every database query is scoped by organizationId. The system enforces this at multiple levels:
Browser (cookie contains session)
↓
HybridAuthGuard (resolves organizationId from session/API key/service token)
↓
Controller (@OrganizationId() decorator extracts it)
↓
Service (passes orgId to all Prisma queries)
↓
Prisma (WHERE organizationId = ?)
↓
PostgreSQL (data isolation by org)
Cross-Subdomain Cookie Architecture
.trycomp.ai domain
┌──────────────────────────────────────┐
│ │
│ app.trycomp.ai → Main app │
│ api.trycomp.ai → NestJS API │
│ portal.trycomp.ai → Employee portal │
│ *.trust.inc → Trust portals │
│ │
│ Cookie: __Secure-better-auth. │
│ session_token │
│ Domain: .trycomp.ai │
│ Secure: true │
│ HttpOnly: true │
│ SameSite: None (cross-subdomain) │
│ │
└──────────────────────────────────────┘
Data Flow: AI-Powered Questionnaire Answering
User uploads questionnaire (Excel/PDF/Word)
│
▼
Trigger.dev: process-knowledge-base-document
│
├── Extract content (mammoth/exceljs/unpdf)
│ └── Fallback: Claude vision for images/PDFs
│
├── Parse questions with AI
│ └── Groq (fast) → Claude (large context) → OpenAI (fallback)
│
├── For each question:
│ ├── Generate embedding (text-embedding-3-small)
│ ├── Vector similarity search (pgvector)
│ │ └── Sources: policies, context docs, manual answers
│ ├── Build RAG prompt with top-k results
│ └── Generate answer (gpt-4o-mini)
│ └── Guardrail: "N/A - no evidence found" if insufficient
│
└── Save answers to questionnaire_question_answer table
└── Status: generated (vs. manual, untouched)
Integration Platform Architecture
┌─────────────────────────────────────────────────────┐
│ Integration Platform │
│ │
│ Registry (Code + Dynamic manifests) │
│ ├── AWS (30+ service checks) │
│ ├── Azure, GCP │
│ ├── GitHub, Google Workspace │
│ ├── JumpCloud, Rippling │
│ └── Vercel, Aikido │
│ │
│ Runtime (CheckContext) │
│ ├── Auto-authenticated HTTP (OAuth/API Key/Bearer) │
│ ├── Pagination helpers (cursor, offset, Link header)│
│ ├── Result recording (pass/fail with evidence) │
│ └── State management (persistent across runs) │
│ │
│ DSL Engine │
│ ├── Declarative check definitions (JSON) │
│ ├── Expression evaluator (16 operators) │
│ ├── Step interpreter (fetch, forEach, branch, emit) │
│ └── Falls back to code checks when DSL insufficient │
│ │
│ Task Mappings │
│ └── 75 framework-aligned templates │
│ Auto-complete tasks when checks pass │
└─────────────────────────────────────────────────────┘
Deployment Architecture
Docker Multi-Stage Build
Stage 1: deps → Bun install (skip Prisma postinstall)
Stage 2: migrator → Minimal Prisma for migrations
Stage 3: app-builder→ Next.js build (standalone output)
Stage 4: app → Node 22 Alpine, runs Next standalone
Stage 5: portal-builder → Portal build
Stage 6: portal → Node 22 Alpine, runs portal
Production (AWS ECS)
ECS Service
├── Task Definition (app container)
│ ├── Health: /api/health
│ ├── Port: 3000
│ └── Node 22 Alpine
├── Task Definition (portal container)
│ ├── Health: /
│ ├── Port: 3002
│ └── Node 22 Alpine
└── deploy.sh verifies:
├── Desired == Running task count
├── Health check passing
├── Accepts partial deploy after 3 min
└── 10 min total timeout
Key Architectural Decisions
1. NestJS API as Auth Authority
All authentication flows through the NestJS API. No app runs its own Better Auth instance. This creates a single enforcement point for sessions, RBAC, and audit logging.
2. Session-Only Authentication (No JWTs)
Cross-subdomain httpOnly cookies provide session auth. This avoids JWT token management, refresh flows, and token-in-localStorage vulnerabilities. The tradeoff is tight coupling to the cookie domain.
3. Server Components + SWR Hydration
Server components fetch initial data via serverApi, pass it as fallbackData to client-side SWR hooks. This gives SSR performance with client-side reactivity.
4. Flat Permission Model
resource:action pairs instead of hierarchical RBAC. Simple to reason about, easy to extend with custom roles. Every API endpoint declares its required permission via decorators.
5. Trigger.dev for Background Work
Long-running tasks (cloud scans, AI generation, file processing) run in Trigger.dev with retry policies. Service tokens provide scoped API access from background jobs.
6. Multi-Model AI Strategy
Different LLMs for different tasks based on cost, speed, and capability. Groq for fast parsing, GPT-4o-mini for structured extraction, Claude for complex reasoning, GPT-5 for general chat.