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 pg driver 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: queryDb includes 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_token cookie with UUID-based user IDs
  • Admin: admin_token cookie 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 applications
  • packages/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, sessionValid
  • withAuth() 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: AuthConfigs object 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.tsx for 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_organizations is the real tenancy boundary, but it does not encode whether an org is internal or client-facing.
  • portal_organizations.settings is 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_kind field to portal_organizations with values:
    • client
    • internal_ops
  • Propagate workspace_kind through tenant resolution, entitlement context, and route guards so server-side authorization can distinguish internal workspaces from client workspaces.
  • Treat workspace_kind as the system boundary. Temporary rollout flags may still exist, but they must not be the security boundary.
  • Keep portal_organizations.settings for 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_messages ledger for every outbound send.
  • Keep email_events as the raw provider webhook/event log.
  • Use crm_communications as the canonical CRM/business timeline record for email communication history.
  • Keep campaign tables such as ai_email_campaigns for 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, or crm_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
Last updated on