Data model
How data is shaped, owned, and accessed across the platform.
The rules
- One Aurora cluster, many schemas. Every service owns a Postgres schema (
identity.*,clinical.*,payments.*, etc.). - No cross-schema reads or writes. A service accesses other services’ data via HTTP, never via SQL across the schema boundary.
- Every table has
brand_id NOT NULL. Enforced by themigration-brand-id-requiredconvention check. - Every state-changing service has an
audit_logtable. Enforced byaudit-table-required. - Foreign keys exist within a schema, not across schemas. Cross-service relationships are by ID + the platform’s HTTP boundary.
- The ledger (
accounting.transactions) is append-only. Postgres triggers reject UPDATE and DELETE.
Schemas and their owners
| Schema | Owned by | Holds |
|---|---|---|
platform | services/platform | Brands catalog, platform audit log, cross-cutting metadata |
identity | services/identity | Users, OAuth clients, grants, tokens, sessions, role assignments |
accounting | services/accounting | Immutable ledger, balances |
payments | services/payments | Charges, refunds, disputes, subscriptions |
commerce | services/commerce | Carts, orders, subscription lifecycle |
clinical | services/clinical | Biomarkers, protocols, recommendations, red flags |
affiliates | services/affiliates | Affiliates, attributions, commissions, payouts |
membership | services/membership | Tiers, status, commission locks |
comms | services/comms | Messages, inbox, preferences, suppressions |
community | services/community | Posts, comments, likes |
follows | services/follows | Follows, blocks |
content | services/content | Payload CMS collections |
analytics | services/analytics | Tracked events, attributions, cohorts, outcomes |
patient_graph | services/patient-graph | Customer profiles, order history, wearable syncs |
webhooks | services/webhooks | Inbound + outbound webhook deliveries |
events | services/events | Outbox + delivery audit |
entitlements | services/entitlements | Per-tier feature access |
cash | services/cash | Loop cash rewards ledger |
booking | services/booking | Providers, availability, visits |
ai | services/ai | Conversations, embeddings, RAG corpus |
capri | services/capri | (Capri-specific) |
integrations | services/integrations | Wearable connections, sync runs |
jobs | services/jobs | Job execution log |
Cross-service references
When a row in service A needs to point at something owned by service B:
- Store the foreign ID as a plain string (
user_id,customer_profile_id,affiliate_id). - Don’t add a foreign key constraint across schemas.
- When you need the related entity, call B’s HTTP API.
Example: clinical.biomarkers.user_id holds a string. To get the user’s name, services/clinical calls services/identity via the SDK.
Migrations
Drizzle Kit per service. Numbered SQL files in services/<svc>/migrations/. Forward-only. Expand–contract pattern for any destructive change. See Zero-downtime migrations.
Audit log
Every service owns its audit table. Append-only (no UPDATE or DELETE grants). Schema:
CREATE TABLE <service>.audit_log (
id UUID PRIMARY KEY,
occurred_at TIMESTAMPTZ NOT NULL,
actor_type TEXT NOT NULL,
actor_id TEXT NOT NULL,
actor_brand_id TEXT,
event_type TEXT NOT NULL,
entity_type TEXT,
entity_id TEXT,
brand_id TEXT NOT NULL,
request_id TEXT,
details JSONB
);See Audit and PHI.