Skip to main content

ADR-0003: Cross-System Tenant Registry

  • Status: Proposed
  • Date: 2026-01-11
  • Author: @digiwedge/engineering

Context

DigiWedge platform has multiple independent systems that each have their own tenant/club representations:

SystemEntityPrimary KeyNotes
Access ControlTenantUUID idSource of truth for tenant identity
SCLTenantClubUUID idHierarchical: tenant owns clubs
TeeTimeClubUUID id, clubCodeClubs have tenantId FK to AC
MessagingContact scoped by tenantIdStringReferences AC tenant
CRMCustomer scoped by tenantIdUUIDReferences AC tenant

Currently, tenant mapping is ad-hoc:

  • TeeTime clubs have tenantId field linking to Access Control
  • SCL has its own Tenant table with a code field
  • Messaging references tenants by string IDs
  • No central registry for cross-system lookup

Problem Statement

  1. No single source for tenant configuration - Each system maintains its own tenant metadata
  2. Cross-system queries require multiple lookups - Finding "all clubs for tenant X" requires knowing system-specific IDs
  3. Onboarding is manual - No automated provisioning of tenant across all systems
  4. DigiWedge corporate tenant - Platform needs a known tenant for CRM leads without manual ID management

Decision

Create a Tenant Registry in Access Control that serves as the cross-system mapping layer.

Schema Addition

model TenantRegistry {
id String @id @db.Uuid
tenantId String @unique @db.Uuid

// SCL mapping
sclTenantCode String? @unique
sclEnabled Boolean @default(false)

// TeeTime mapping (array of clubIds)
teeTimeClubIds String[] @db.Uuid
teeTimeEnabled Boolean @default(false)

// Messaging mapping
messagingTenantId String? @unique
messagingEnabled Boolean @default(false)

// CRM mapping
crmTenantId String? @unique
crmEnabled Boolean @default(false)

// Billing/Payments
billingEnabled Boolean @default(false)
paymentsEnabled Boolean @default(false)
paymentProviders String[] // ['stripe', 'peach', etc.]

// Association integration
associationProvider String? // 'dotgolf', 'golfrsa', null
associationMemberId String? // External ID in association system

// Lifecycle
provisionedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Relations
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)

@@index([sclTenantCode])
@@index([messagingTenantId])
@@index([crmTenantId])
}

Well-Known Tenants

Deterministic UUIDs for platform-level tenants (already implemented):

// libs/prisma/access-control-client/src/lib/constants/well-known-tenants.ts
export const DIGIWEDGE_TENANT_ID = 'fd3c07a1-8038-5e37-beb6-537fda68d644';
export const DIGIWEDGE_TENANT_NAME = 'DigiWedge';

API Surface

interface TenantRegistryService {
// Lookup methods
findByTenantId(tenantId: string): Promise<TenantRegistry | null>;
findBySclCode(sclCode: string): Promise<TenantRegistry | null>;
findByTeeTimeClubId(clubId: string): Promise<TenantRegistry | null>;
findByMessagingTenantId(messagingId: string): Promise<TenantRegistry | null>;
findByCrmTenantId(crmId: string): Promise<TenantRegistry | null>;

// Provisioning
provision(tenantId: string, config: TenantProvisionConfig): Promise<TenantRegistry>;
enableSystem(tenantId: string, system: 'scl' | 'teetime' | 'messaging' | 'crm'): Promise<void>;
addTeeTimeClub(tenantId: string, clubId: string): Promise<void>;
}

Consequences

Positive

  1. Single source of truth for cross-system tenant mapping
  2. Deterministic IDs for well-known tenants prevent ID drift
  3. Onboarding automation possible via provision() API
  4. Query optimization - single lookup resolves all system IDs
  5. Feature flags per tenant - can enable/disable systems per tenant

Negative

  1. Migration required - existing tenants need registry entries
  2. Sync complexity - must keep registry in sync with individual systems
  3. Additional dependency - services need Access Control for tenant resolution

Neutral

  1. Eventual consistency - registry is updated after system-specific provisioning
  2. Optional adoption - existing direct mappings continue to work

Migration Plan

  1. Add TenantRegistry table to Access Control schema
  2. Create seed script to populate registry for existing tenants
  3. Add API endpoints in Access Control Admin
  4. Update onboarding wizard to use registry
  5. Gradually migrate services to use registry for cross-system lookups

References

  • libs/prisma/access-control-client/src/lib/constants/well-known-tenants.ts
  • libs/tee-time-services/src/lib/utils/club-resolution.ts
  • libs/prisma/scl-data/prisma/schema.prisma (Tenant model)