Multi-tenancy
Every tenant-scoped table has an org_id TEXT column. BetterAuth’s organization.id and user.id are text (not UUID), so all FK columns match.
RLS is enabled on all tenant-scoped tables with service-role policies for defense-in-depth. Auth is enforced in the API route layer via BetterAuth’s getSession() — Supabase’s auth.uid() does not work because BetterAuth sessions are separate from Supabase Auth.
Entity hierarchy
Organization (BetterAuth)
│
├── ComplianceProgram (regulation_type: bsa_aml | third_party_oversight)
│ └── ComplianceDomain (one per regulatory category)
│ └── ScoringCriteria (evidence_status, linked_evidence JSONB, reviewer_notes)
│ └── regulatory_source_id FK → Knowledge Base
│
├── Examination (EX-2026-001, globally unique PK)
│ ├── ExaminationUpload (parse_status: pending → processing → completed)
│ ├── RequestItem (parsed line items, embedding vector(1024))
│ │ ├── EvidenceAttachment (file_path OR external_url)
│ │ └── EvidenceLink (cross-obligation link, status: auto_applied → confirmed/rejected)
│ ├── MRA (MRA-2026-001, status lifecycle)
│ │ ├── MraRequestItem (junction → RequestItem)
│ │ └── MraRemediationEvidence (file uploads)
│ └── ExaminationCorrespondence (post-exam documents)
│
├── AuditLog (immutable, org-scoped)
│
└── Knowledge Base (shared across all orgs, NOT org-scoped)
├── FfiecSection (FFIEC manual + OCC 2023-17, regulation_domain scoped)
├── FincenAdvisory (FinCEN guidance)
├── DeficiencyPattern (enforcement action patterns)
└── RedFlag (FFIEC Appendix F indicators)
Human-readable IDs
Primary keys like EX-2026-001, MRA-2026-001, CP-2026-001 are globally unique across all orgs (not org-scoped sequences). Generated by querying max(id) across all orgs with numeric suffix extraction:
CAST(SUBSTRING(id FROM '[0-9]+$') AS INTEGER)
Lexicographic max(id) breaks past 999 — EX-2026-999 > EX-2026-1000 in text comparison. The current implementation uses numeric suffix extraction to handle this correctly.
Scoring model
Three-tier hierarchy: Program → Domain → Criteria.
- Programs are scoped by
regulation_type — bsa_aml maps to 5 FFIEC categories, third_party_oversight maps to 8 OCC 2023-17 areas
- Domains correspond to regulatory categories from
REGULATION_CONFIG (the column is category, not pillar)
- Criteria have an
evidence_status enum: missing | stale | partial | sufficient
domain_score = ((sufficient_count × 100) + (partial_count × 50)) / total_criteria
Linked evidence
Stored as a JSONB array on each criterion:
{
"sourceType": "evidence_attachment",
"sourceId": "uuid",
"label": "Q4 Board Minutes",
"linkedAt": "2026-01-15T...",
"linkedBy": "user-id",
"suggested": false
}
Suggestions from auto-matching appear with suggested: true and require human acceptance.
Regulation configuration
backend/src/config/regulation-types.ts is the single source of truth for all regulation-type-specific behaviour:
REGULATION_CONFIG = {
bsa_aml: {
label: "BSA/AML Program",
categories: [
"internal_controls",
"independent_testing",
"bsa_officer",
"training",
"cdd_kyc"
],
kbDomains: ["bsa_aml"],
parserPrompt: "...",
outputSchema: z.object({})
},
third_party_oversight: {
label: "Third-Party Oversight Program",
categories: [
"bsa_aml_effectiveness",
"financial_condition",
// ...8 total
],
kbDomains: ["third_party_risk"]
// No parserPrompt — doesn't support document parsing
}
}
Never hardcode pillar labels, obligation types, or regulation types in UI or API routes. Always import from @verity/backend/config.
File storage
Evidence files are stored in Supabase Storage (evidence-files bucket).
Upload path convention
evidence/{orgId}/{examId}/{itemId}/{timestamp}-{uuid8}-{filename}
Unique paths prevent overwrites. upsert: false is enforced. Maximum 50 MB per file. Allowed types: PDF, DOCX, XLSX, CSV, PNG, JPG.