Skip to main content

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: ProgramDomainCriteria.
  • Programs are scoped by regulation_typebsa_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

Score formula

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.