Architecture Decision Records
Documented decisions on technology choices, patterns, and trade-offs for the GaugeWell platform.
ADRs follow the format: Context (why we needed to decide), Decision (what we chose), Consequences (trade-offs and outcomes).
ADR-001: Neon PostgreSQL as Primary Database
Date: 2025
Status: Accepted
Context
We needed a managed PostgreSQL database that supports serverless workloads, branching for tenant isolation, and scales with Vercel’s edge deployment model.
Decision
Use Neon PostgreSQL as the primary database for all applications. Neon provides:
- Serverless auto-scaling with scale-to-zero
- Database branching for tenant isolation
- Compatible with standard
pgdriver and@neondatabase/serverless - Built-in connection pooling
Consequences
- Positive: Zero infrastructure management, instant branching for new tenants, cost-effective for variable workloads
- Positive: Native integration with Vercel’s serverless functions
- Trade-off: Cold starts on scale-to-zero can add ~200ms latency on first query after idle period
- Mitigation:
queryDbincludes retry logic (3 attempts with backoff) and detailed error guidance
ADR-002: Custom JWT Authentication (Not Clerk/Auth0)
Date: 2025
Status: Accepted
Context
We needed authentication for both the client portal and admin portal. Options considered: Clerk, Auth0, NextAuth, custom JWT.
Decision
Use custom JWT authentication for both portals with separate token stores:
- Portal:
portal_tokencookie with UUID-based user IDs - Admin:
admin_tokencookie with integer-based user IDs and RBAC
Consequences
- Positive: Full control over session management, token structure, and auth flows
- Positive: No vendor lock-in or per-user pricing
- Positive: Can implement custom 2FA/TOTP without third-party constraints
- Trade-off: More code to maintain (session validation, token rotation, password hashing)
- Trade-off: Must handle security best practices ourselves (bcrypt, secure cookies, CSRF)
- Mitigation: Global session-DB validation in
withAuth()ensures revoked sessions are rejected
ADR-003: Monorepo with pnpm Workspaces
Date: 2025
Status: Accepted
Context
The platform has multiple Next.js applications (admin, portal, docs, landing) that share UI components, design tokens, and utilities.
Decision
Use a pnpm workspace monorepo with a shared @gaugewell/theme package:
apps/— Individual Next.js applicationspackages/theme/— Shared design system, components, chart themes
Consequences
- Positive: Single dependency tree, shared components, consistent design
- Positive: Atomic commits across apps and shared packages
- Trade-off: Build times increase as the monorepo grows
- Mitigation: Vercel’s build cache and selective deployment reduce CI overhead
ADR-004: Granular Permission-Based Route Authorization
Date: February 2026
Status: Accepted
Context
The admin portal grew to 260+ API routes. Initially, most routes used AuthConfigs.authenticated (basic session check). We needed granular access control.
Decision
Migrate all routes to granular AuthConfigs with domain-specific permissions:
analyticsRead,contentManage,mediaManage,leadsManage,clientsManage,usersManage,contractsManage,crmManage,sessionValidwithAuth()wrapper handles authentication, authorization, error handling, and audit logging in one place
Consequences
- Positive: Zero routes on basic auth — every endpoint has a specific permission requirement
- Positive: Centralized error handling (standardized 500 responses) and audit logging (auto-log all mutations)
- Trade-off: Adding a new route requires choosing the correct permission level
- Mitigation:
AuthConfigsobject provides clear, named presets for common permission patterns
ADR-005: Python Microservices for Integrations
Date: 2025
Status: Accepted
Context
Integration services (CRM, CMS, Content AI, Social, Billing, Proofing, Northstar Auditor) need different runtime characteristics than the Next.js frontend.
Decision
Use Python microservices deployed as separate Vercel serverless functions or standalone services:
- Each integration is an independent package in
GaugeWell Integrations/apps/ - Services communicate via REST APIs with service tokens
- Shared utilities in
GaugeWell Integrations/shared/
Consequences
- Positive: Independent deployment and scaling per service
- Positive: Python ecosystem for AI/ML (Content AI, scoring algorithms)
- Trade-off: Cross-language boundary adds complexity (TypeScript frontend ↔ Python backend)
- Mitigation: Well-defined API contracts and service tokens for authentication
ADR-006: Shared Design System Package
Date: 2025–2026
Status: Accepted
Context
Multiple applications (admin, portal, docs, landing) need consistent branding, colors, and UI components.
Decision
Create @gaugewell/theme as a shared package with:
- Design tokens (colors, spacing, typography)
- Reusable components (
StatCard,TimeFilterDropdown,EmptyState, etc.) - Chart theme (
chart-theme.ts) for consistent Recharts styling - Email templates with shared tokens
Consequences
- Positive: Single source of truth for design — changes propagate to all apps
- Positive: No duplicate component code across applications
- Trade-off: Package changes require rebuilding dependent apps
- Mitigation: Subpath exports (
@gaugewell/theme/components,@gaugewell/theme/chart-theme) for tree-shaking
ADR-007: Nextra for Documentation Site
Date: 2025
Status: Accepted
Context
We needed a documentation site that integrates with our Next.js monorepo and supports MDX content.
Decision
Use Nextra (Next.js-based documentation framework) for docs.gaugewell.io:
- MDX pages with automatic navigation from file structure
- Built-in search, dark mode, and responsive layout
- Deploys alongside other apps on Vercel
Consequences
- Positive: Documentation lives in the same monorepo as the code it documents
- Positive: MDX allows embedding React components in docs
- Trade-off: Limited to Nextra’s layout and theming options
- Mitigation: Custom
theme.config.tsxfor branding and navigation customization
ADR-008: First-Class Internal Workspaces and Unified Email Operations
Date: 2026-03
Status: Proposed
Context
GaugeWell intentionally uses the client portal as the day-to-day operating surface instead of splitting operational tooling between the admin portal and the client portal. That creates a valid product requirement: one portal application must support two different workspace types:
- Client workspaces used by paying clients
- Internal operations workspaces used by GaugeWell staff for CRM, prospects, scans, and outbound outreach
Today that distinction is implicit and fragmented rather than modeled directly:
portal_organizationsis the real tenancy boundary, but it does not encode whether an org is internal or client-facing.portal_organizations.settingsis already used for UI-level access rules, which is useful for configuration but not strong enough for core authorization and product identity.- Email authoring, sending, and tracking are split across static portal templates, DB-backed templates, campaign tables, invite flows, and webhook-specific tracking.
- Prospect outreach, client email, and transactional email do not share a single send ledger or CRM timeline model.
We need a long-term architecture that preserves one portal product while guaranteeing that internal prospecting and CRM operations never leak into client workspaces.
Decision
Adopt an organization-scoped workspace model and treat it as the product and authorization boundary.
- Add a first-class
workspace_kindfield toportal_organizationswith values:clientinternal_ops
- Propagate
workspace_kindthrough tenant resolution, entitlement context, and route guards so server-side authorization can distinguish internal workspaces from client workspaces. - Treat
workspace_kindas the system boundary. Temporary rollout flags may still exist, but they must not be the security boundary. - Keep
portal_organizations.settingsfor configurable UI behavior and access preferences, not for workspace identity. - Allow GaugeWell staff to belong to the GaugeWell internal workspace, but not to client workspaces.
For outbound email, standardize on a single platform model:
- Use org-scoped templates as the source of truth for email authoring.
- Use org-scoped provider credentials and send profiles for delivery identity.
- Introduce a canonical
email_messagesledger for every outbound send. - Keep
email_eventsas the raw provider webhook/event log. - Use
crm_communicationsas the canonical CRM/business timeline record for email communication history. - Keep campaign tables such as
ai_email_campaignsfor authoring, scheduling, and aggregate reporting, but not as the source of truth for actual message delivery. - Require every non-transactional outbound send to be linked to an organization and at least one business entity (
business,lead, orcrm_client).
See Workspace Email Unification for the detailed schema and rollout plan.
Consequences
- Positive: GaugeWell can keep one portal application without duplicating systems across admin and client surfaces.
- Positive: Internal CRM/prospect tooling becomes explicitly separated from client workspaces at the data and authorization layers.
- Positive: All outbound email can be tracked, correlated to webhook events, and written back to a single CRM timeline model.
- Positive: Future sequence automation can reuse the same infrastructure instead of introducing a second send path.
- Trade-off: Existing portal routes, tenant resolution, template queries, and campaign code need refactoring to respect workspace identity.
- Trade-off: The current email template and campaign models need to be migrated toward scoped templates and tracked sends.
- Mitigation: Roll out in phases: workspace identity first, then template unification, then manual tracked send, then webhook/timeline integration, and only then automation.
Template for New ADRs
When adding a new decision record, use this format:
## ADR-NNN: Title
**Date**: YYYY-MM
**Status**: Proposed | Accepted | Deprecated | Superseded
### Context
Why we needed to make this decision.
### Decision
What we chose and why.
### Consequences
- **Positive**: Benefits
- **Trade-off**: Costs or risks
- **Mitigation**: How we address the trade-offs