> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pcnaid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture Overview

> Legal Hub runtime, authentication, data isolation, storage, queues, and deployment shape.

# Architecture Overview

## Application shape

Legal Hub is a Cloudflare-native full-stack application: one Worker serves the Hono API and the React 19 / Vite SPA through Workers Assets. An independent Astro marketing site lives under `marketing/` for Cloudflare Pages deployment.

```
Browser
  │
  ▼
Cloudflare Worker
  ├─ Workers Assets: React SPA
  ├─ Hono API: /api/*
  ├─ Security middleware: request IDs, secure headers, fail-closed CORS, rate limits
  ├─ Auth middleware: Worker-signed app-session JWT verification from HttpOnly cookies
  ├─ Tenant middleware: user → membership → active organization
  ├─ Repositories: tenant-safe logical collections
  ├─ Queues: task/email/sms/document/AI processing
  ├─ Redis: production rate limits and AI usage counters
  ├─ KV: queue idempotency fallback, upload tracking, tenant-context cache
  ├─ R2: document object storage + audit-log copies
  ├─ PostHog: product analytics
  └─ Sentry: error capture and tracing
  │
  ▼
Cloudflare Hyperdrive
  │
  ▼
Azure Postgres (Azure Database for PostgreSQL Flexible Server)
```

## Request and access path

The Worker applies request IDs, logging, security headers, CORS, error handling, and request telemetry before routing. Public configuration, contact, signup, authentication, brand lookup, webhook, and health routes are mounted separately from authenticated tenant routes.

The SPA starts Auth0 Universal Login with Authorization Code and PKCE. The callback verifies the Auth0 ID token against JWKS, then Legal Hub creates its own HS256 signed session set in the HttpOnly, Secure, SameSite=Lax `pcnaid_access_token` cookie. No Auth0 refresh token is requested or stored.

Authenticated API requests pass through authentication, CSRF, and tenant middleware. The selected organization is sent as `X-Organization-Id`; users with multiple active memberships must identify the organization. Individual route handlers then apply role and permission checks. Client portal routes also restrict records to explicit client access grants.

First-run onboarding serializes the authenticated user's membership check and organization creation within a transaction. Zero-membership users are routed via `RequireFirm` to `/onboarding` where they bootstrap their firm and initiate Stripe Checkout.

Audience routing resolves client-only users to `/portal`, firm users to the firm dashboard, and Pcnaid staff to `/admin`.

## Data model and tenant isolation

Each logical collection has its own Postgres table mapped by `COLLECTION_TABLE` in `src/worker/db/connection.ts`, storing an ID, organization ID, JSONB payload, and creation/update/deletion timestamps. The Worker compatibility layer preserves the query API used by routes and repositories while persisting through Postgres/Hyperdrive.

Every tenant-scoped record includes `organizationId`. Repositories add `deletedAt: null` and organization filters for normal reads and writes. The adapter reads the request's tenant context and sets transaction-local `app.org_id` and `app.access` for enforced row-level security. Tenant SQL can read its own organization and user identities referenced by that organization's non-deleted memberships.

`knowledge_chunk_embeddings` is a separate pgvector-backed semantic index for tenant-scoped AI retrieval. The legacy `app_records` table is retained as a historical source; normal collection operations use the per-entity tables.

## Cloudflare services

The Worker configuration declares:

* **Hyperdrive** for Postgres access.
* **KV** for tenant-context caching, upload tracking, and small idempotency or state records.
* **R2 buckets** for documents (`legal-hub-documents`) and audit-log objects (`legal-hub-audit-logs`).
* **Queues** for tasks, email, SMS, document processing, and AI work, each with a dead-letter queue.
* **Scheduled handler** for stale-upload cleanup, calendar OAuth cleanup, and reminders.
* **Workers AI** binding with AI provider settings supplied through runtime configuration.

Document uploads use R2 multipart uploads. The browser calculates a SHA-256 checksum, uploads parts through authenticated Worker routes, and completes the upload only after the Worker verifies the stored object's size and checksum.

## Auth and tenant resolution

1. React starts Auth0 Universal Login with PKCE (`screen_hint=signup` for signups, preserving optional `plan` in sessionStorage).
2. Auth0 redirects to `/auth/callback`.
3. The Worker exchanges the authorization code at `/api/auth/callback`.
4. The Worker verifies the Auth0 ID token through JWKS, then mints its own HS256 app-session JWT and sets it in the HttpOnly, Secure, SameSite=Lax `pcnaid_access_token` cookie.
5. API calls use same-origin cookies; no localStorage JWTs are stored or attached.
6. Worker middleware verifies the app-session JWT, finds the user by `auth0UserId`, and resolves memberships.
7. Zero-membership users are routed to `/onboarding`.
8. For active organizations, the frontend sends `X-Organization-Id`; the backend rejects ambiguous multi-org requests without it.

## Async processing

| Queue | Purpose | DLQ |
| - | - | - |
| `legal-hub-tasks` | Task reminders and operational jobs | `legal-hub-tasks-dlq` |
| `legal-hub-emails` | Resend transactional email | `legal-hub-emails-dlq` |
| `legal-hub-sms` | Twilio SMS | `legal-hub-sms-dlq` |
| `legal-hub-documents-processing` | R2 document processing and usage records | `legal-hub-documents-dlq` |
| `legal-hub-ai` | Embedding/indexing/reporting jobs | `legal-hub-ai-dlq` |

Queue handlers normalize message shapes and use Redis REST idempotency when available; otherwise async queue idempotency can fall back to Cloudflare KV. Request rate limits and AI usage counters require Redis in staging/production and fail closed if Redis is absent.

## AI/RAG

Knowledge chunks are stored tenant-scoped in Postgres. `/api/ai/query` embeds the user query, retrieves semantically similar chunks from `knowledge_chunk_embeddings` using pgvector, wraps context as untrusted data, calls OpenAI Responses API when configured, records the AI run, and returns provider metadata. A bounded JSON cosine fallback is available if the pgvector query path is unavailable during local development.

## Uploads and audit logs

Documents live in R2 under organization-scoped keys and complete only after SHA-256 checksum verification. The verifier uses R2 checksum metadata when present and falls back to server-side streaming SHA-256 over the completed object. Multipart upload state is tracked in KV, and a scheduled Worker aborts stale uploads.

Sensitive mutations write normal DB audit records and, when `AUDIT_LOGS_BUCKET` is bound, an additional JSON copy to R2 under `audit-logs/YYYY/MM/DD/...`.

## Observability

* **Sentry** wraps the Worker export and captures 500-level errors with request IDs.
* **PostHog** records API request events with method, path, status, latency, and tenant correlation where available.
* **Cloudflare Observability** is enabled in `wrangler.jsonc`.
* **Audit logs** persist sensitive application events and can be mirrored to R2.

## Build and deployment

```bash theme={null}
npm run build
npm run deploy
```

`npm run build` builds the SPA and runs `wrangler deploy --dry-run`; `npm run deploy` builds the SPA and deploys the Worker. Runtime bindings and non-secret defaults are declared in `wrangler.jsonc`. Production deployment requires real provider credentials and replacement Cloudflare binding IDs in `wrangler.jsonc` before `npm run deploy`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.