Skip to main content

Module Boundaries

Each module in the monorepo has strict ownership rules. No module writes another module's database tables directly, and cross-module communication happens through well-defined interfaces.

Apps

apps/web (Next.js)

AspectDetails
OwnsBetter Auth config and routes (/api/auth/*), user sessions (HttpOnly cookie), passkey enrollment/login, social sign-in UX
ExposesDashboard UI, auth flows
Must NOTStore provider refresh tokens, poll device flow, call provider token endpoints directly, serve JWKS or issue JWTs (those live in the Fastify API)

apps/api (Fastify)

AspectDetails
OwnsBusiness APIs, JWT validation plugin (via JWKS), OAuth integration service (auth code flow), encrypted token vault, RBAC/scope authorization, policy engine
Exposes/v1/* business APIs, /v1/oauth/{provider}/start (web auth-code connect), /v1/oauth/{provider}/callback (OAuth callback), GET /.well-known/jwks.json (public key for JWT verification), POST /api/internal/token/exchange (session to JWT exchange)
Must NOTHandle passkey ceremonies for app login, manage browser user sessions

Packages

PackageOwnsExportsMust NOT
packages/contractsZod schemas and TS types for all cross-service requests/responses, JWT claim interfaces (ApiAccessClaims, WorkspaceRole, Scope)Types, schemas, branded ID typesInclude runtime HTTP logic
packages/securityAES-256-GCM encryption utilities, envelope encryption, key rotation helpers (kid, key versioning)EnvelopeEncryptor, EncryptedEnvelopeV1Know database schema details
packages/sdkOfficial TypeScript SDK for external consumersSDK client, typed request/response helpersDepend on internal app code
packages/oauth-connectorsProvider metadata discovery, oauth4webapi wrappers for auth-code exchange, refresh, revokeOAuthConnector interface, ProviderCapabilities, per-provider implementationsAccess web session state
packages/openclawOpenClaw Gateway plugin for routing and lifecycle managementGateway plugin interfaceAccess database directly
packages/agenthifive-mcpMCP server implementation (stdio transport)MCP server entry pointDepend on internal app code
packages/openclaw-setupOpenClaw setup and configuration utilitiesSetup helpers, config generatorsAccess web session state
packages/integration-sdkIntegration SDK for third-party integrationsIntegration interfaces, helpersDepend on internal app code

Data Ownership

DataOwnerNotes
t_users, t_sessions, t_accountsapps/apiBetter Auth tables (managed by Fastify API)
t_connections, t_pending_connectionsapps/apiProvider integrations
t_agents, t_agent_bootstrap_secretsapps/apiAgent registration and auth
l_audit_eventsapps/apiAudit trail (append-only log table)
t_policiesapps/apiPolicy engine configuration
t_approval_requestsapps/apiHuman-in-the-loop approvals
t_workspacesapps/apiWorkspace multi-tenancy

Token Model

The system uses three distinct token types, each scoped to its layer:

  • Scope: Browser only
  • Security: HttpOnly, SameSite=Lax/Strict
  • Lifetime: 30 days (configurable)
  • Contains: Session ID referencing the database

2. Internal API JWT (issued by apps/web)

  • Scope: Web UI to API calls
  • TTL: 5 minutes
  • Audience: api
  • Issuer: web
  • Claims: { sub, wid, roles, scp, sid }
  • Verification: JWKS from /.well-known/jwks.json

3. Provider Token Set (stored by apps/api)

  • Contents: access_token, refresh_token, expiry, provider_account_id
  • Storage: Encrypted at rest (AES-256-GCM) with key ID for rotation
  • Rotation: Key versioning supported via kid

Dependency Flow

apps/web ──────────────> packages/contracts
|
+──────────────> packages/sdk

apps/api ──────────────> packages/contracts
|
+──────────────> packages/security
|
+──────────────> packages/oauth-connectors ──> packages/contracts

apps/cli ──────────────> packages/contracts
|
+──────────────> packages/sdk

packages/openclaw ─────> packages/contracts
packages/agenthifive-mcp > packages/contracts
packages/openclaw-setup -> packages/contracts
packages/integration-sdk > packages/contracts

Packages depend only on other packages, never on apps. Apps depend on packages and never on each other -- they communicate over HTTP.