Skip to main content

Data Model

What this covers / who it's for. The conventions every table follows (identifiers, soft-delete, blamable, money precision) and the one map no single module README can hold: the cross-module domain clusters and their tenancy posture. For migration/model recipes and the exemption mechanics, defer to the skill reference.

The conventions (mandatory-unless-excluded)

Every domain entity table is built from the same parts, and two test guards — tests/Architecture/ModelConventionsTest.php (models) and tests/Feature/SchemaConventionTest.php (the realized Postgres schema) — go red if one is skipped:

ConventionMechanismWhy
Identifiers (D25)$table->identifiers() macro → bigint id PK + uuid with DEFAULT uuidv7(); model trait HasPublicUuidThe bigint powers internal FKs and the RLS session key; the DB-generated UUIDv7 is the only identifier that ever leaves the app (see api-conventions.md). PHP never generates uuids — Postgres owns the default on every write path.
Soft deletes + blamable (D26)$table->softDeletes() + $table->blamable(); traits SoftDeletes + BlamableUniversal row-level authorship: polymorphic created_by/updated_by/deleted_by stamped from the active guard (operator or client — no cross-boundary FK). Complements, never replaces, the bus audit trail.
Partial unique indexesraw CREATE UNIQUE INDEX ... WHERE deleted_at IS NULLA soft-deleted row's unique value (an email, a role name) can be re-registered. Manual — the guards do not catch a plain ->unique(); apply it by hand on every unique-on-soft-deletable constraint.
Money precision (D61)decimal(38, 18) for every monetary amount (orders, payouts, transactions, balances, filings)Exact stablecoin-grade arithmetic; never floats.
TenancyforeignId('tenant_id')->constrained('tenants') = tenant-scoped (auto-RLS); no FK path = central; plain no-FK tenant_id = registered central (D53)See tenancy-and-rls.md — the FK decides the table's whole security posture.

Exemptions are deliberate and registered (pivots, framework/vendor tables, the tenants boundary table, append-only ledgers) — the how lives in the skill reference.

The domain map

Cluster-level view — boxes are entity clusters, not tables, and edges show tenancy linkage plus the main domain flows. Solid edges are real tenant_id FKs (RLS-policied); dotted edges are the plain no-FK tenant_id of registered central tables (read cross-tenant by operators).

What each cluster holds, its tenancy posture, and where the current specifics live:

ClusterMain tablesTenancyModule README
Tenanttenants (+ status, base currency, level/jurisdiction)The boundary itself — central, never policiedtenancy
Operatorsusers, spatie roles/permissions, passkeysCentralidentity, authorization
Team & identity (client)client_users, tenant_roles + pivots, team_invitations, mfa_credentials (nullable tenant_id: operator rows central, client rows scoped)Tenant-RLSidentity, authorization, team
Onboardingcustomers, applications, onboarding_documents (tenant); provider_customers (central tenant↔provider map, read pre-tenancy)Tenant-RLS + one registered centralonboarding
Accountswallets, virtual_accounts, wallet_signers, signing_quorums, account_balances (projection)Tenant-RLS (provider mirrors)accounts
Fundingtransactions (the provider transaction mirror)Tenant-RLSfunding
Paymentsorders, order_transactions, payouts, registered_addresses, whitelist_recipientsTenant-RLSpayments
Compliancecompliance_cases, account_states, case_notes, held_incoming_items, sar_reports, ctr_filings, travel_rule_recordsCentral (registered no-FK tenant_id — operator-worked, cross-tenant)compliance
Documentsdocuments, document_accesses (encrypted vault + per-serve access log)Tenant-RLSdocuments
Notificationsnotifications (inbox), notification_preferences (tenant); notification_log_items (nullable tenant); operator_notifications (central)Mixednotification
Features & jurisdictionscatalog/levels + jurisdictions reference (central); feature_usages, tenant_feature_entitlements (tenant)Mixedfeatures, jurisdictions
Async requestsasync_requests (the pollable request store)Tenant-RLSasync-requests
Idempotency & driftcommand_dedup_records, client_idempotency_keys, mirror_driftTenant-RLSfoundation, provider-mirror
Central infra ledgersaudit_events + audit_checkpoints (hash-chained trail), task_runs/task_run_units, vendor_request_logs, webhook_calls, the DLP logs (data_access_logs, dlp_incidents, dlp_egress_events; tenant-surface tenant_data_access_logs is RLS-scoped)Central (append-only; tenant-of-record columns, registered in the inverse-leak guard)audit, task-runner, rails, webhooks, dlp

Two structural notes worth internalizing:

  • The money clusters are mirrors, not sources of truth. Accounts/funding rows mirror the external finance rail (Conduit) via webhooks + reconciliation; payments rows are local intent that the rail executes. See provider-integration.md.
  • "Central" in this map means one of two different things: pure reference/framework data (no tenant linkage at all — jurisdictions, spatie RBAC), or the deliberate no-FK tenant_id pattern for operator-worked and rollback-surviving rows — the mechanics and the guard for the latter are in tenancy-and-rls.md.

Going deeper


← Engineering wiki index