Skip to content

Data model

Every tenant-owned primary and foreign key includes tenant_id. Database row-level security is defence in depth; application authorization remains mandatory. Public identifiers are random ULIDs/UUIDv7-like values, never sequential customer counts.

erDiagram
  TENANT ||--o{ LEGAL_ENTITY : contains
  TENANT ||--o{ USER : authorises
  LEGAL_ENTITY ||--o{ PROCESSING_ACTIVITY : owns
  PURPOSE ||--o{ PROCESSING_ACTIVITY : justifies
  PROCESSING_ACTIVITY }o--o{ SYSTEM : uses
  PROCESSING_ACTIVITY }o--o{ PROCESSOR : sends_to
  NOTICE_VERSION }o--|| PURPOSE : describes
  DATA_PRINCIPAL_REF ||--o{ CONSENT_RECEIPT : acts
  NOTICE_VERSION ||--o{ CONSENT_RECEIPT : proves
  DATA_PRINCIPAL_REF ||--o{ RIGHTS_REQUEST : submits
  RIGHTS_REQUEST ||--o{ FULFILMENT_TASK : dispatches
  RETENTION_RULE ||--o{ DISPOSAL_JOB : schedules
  LEGAL_HOLD }o--o{ DISPOSAL_JOB : blocks
  INCIDENT ||--o{ STATUTORY_CLOCK : triggers
  INCIDENT ||--o{ NOTIFICATION : produces
  VENDOR ||--o{ PROCESSOR : provides
  PROCESSOR ||--o{ SUBPROCESSOR : delegates
  CONNECTOR ||--o{ SYNC_RUN : executes
  AUDIT_EVENT }o--|| TENANT : records
create table tenant (
tenant_id uuid primary key,
slug text not null unique,
status text not null check (status in ('trial','configuring','live','suspended','offboarding','deleted')),
time_zone text not null,
deployment_profile text not null,
key_ref text not null,
created_at timestamptz not null,
version bigint not null default 1
);
create table data_principal_ref (
tenant_id uuid not null references tenant,
principal_ref uuid not null,
entity_id uuid not null,
subject_hmac bytea not null,
source_namespace text not null,
reidentification_connector_id uuid not null,
status text not null,
created_at timestamptz not null,
last_seen_at timestamptz,
primary key (tenant_id, principal_ref),
unique (tenant_id, entity_id, source_namespace, subject_hmac)
);
create table consent_receipt (
tenant_id uuid not null,
receipt_id uuid not null,
principal_ref uuid not null,
purpose_version_id uuid not null,
notice_version_id uuid not null,
action text not null check (action in ('grant','deny','withdraw')),
occurred_at timestamptz not null,
recorded_at timestamptz not null,
channel text not null,
proof_json jsonb not null,
previous_receipt_id uuid,
receipt_hash bytea not null,
idempotency_key_hash bytea not null,
primary key (tenant_id, receipt_id),
unique (tenant_id, idempotency_key_hash)
);
create table rights_request (
tenant_id uuid not null,
request_id uuid not null,
entity_id uuid not null,
principal_ref uuid,
request_type text not null,
state text not null,
source_clock_id uuid,
business_target_at timestamptz,
identity_assurance_level text,
assigned_team_id uuid,
resource_version bigint not null default 1,
submitted_at timestamptz not null,
closed_at timestamptz,
primary key (tenant_id, request_id)
);
create index rights_request_queue_idx
on rights_request (tenant_id, state, business_target_at)
where closed_at is null;
create table audit_event (
tenant_id uuid not null,
event_id uuid not null,
sequence bigint not null,
occurred_at timestamptz not null,
actor_type text not null,
actor_ref text not null,
action text not null,
resource_type text not null,
resource_id text not null,
correlation_id uuid not null,
reason_code text,
details_redacted jsonb not null,
previous_hash bytea,
event_hash bytea not null,
primary key (tenant_id, event_id),
unique (tenant_id, sequence)
) partition by range (occurred_at);

All foreign keys in production migrations include tenant ID. Examples abbreviate some composite references for readability.

  • tenant/access: Tenant, LegalEntity, BusinessUnit, Brand, User, Group, Role, RoleBinding, ServiceAccount, Approval;
  • legal/control: Source, InstrumentVersion, ApplicabilityAssessment, Obligation, Control, EvidenceArtifact, Exception;
  • inventory: System, Dataset, DataField, Classification, ProcessingActivity, DataFlow;
  • consent: Purpose, PurposeVersion, Notice, NoticeVersion, Translation, ConsentReceipt, Preference, PropagationTask;
  • Principal operations: DataPrincipalRef, IdentityMatch, RightsRequest, Grievance, Nomination, FulfilmentTask, SecureMessage, DisclosurePackage;
  • retention: RetentionPolicy, RetentionRule, LegalHold, DisposalJob, DeletionCertificate;
  • third party: Vendor, Processor, Subprocessor, Contract, Assessment;
  • incident: Incident, BreachAssessment, AffectedPopulation, StatutoryClock, Notification, RegulatorReport;
  • assurance: DPIA, Audit, AlgorithmicSystem, Finding, Remediation;
  • integration: Connector, IntegrationCredentialRef, SyncRun, WebhookDelivery, DeadLetter;
  • evidence: AuditEvent, ExportManifest.
ClassExampleProtection
public metadatasource title, control IDintegrity/signature
tenant confidentialpurpose, system, vendorenvelope encryption at rest
restricted operationalcases, incident, nomineeper-tenant DEK, field encryption for select text
secretOAuth token, private keyexternal secrets manager; reference only
raw customer PIIsource/disclosure contentavoid; bounded encrypted object with expiry if needed

Search indexes contain redacted projections. Sensitive free text is not placed in logs, metrics or traces.

The customer agent computes HMAC-SHA-256(tenant_subject_key, namespace || stable_customer_id). The control plane stores the HMAC and random Principal ref. Re-identification calls the scoped customer connector after policy/assignment checks. Rotate by dual-key migration; never use an unsalted plain hash of email/mobile/Aadhaar.

Use immutable versions and explicit supersession for legal decisions, notices, receipts, contracts and evidence. Soft delete is not a substitute for disposal: operational records use state/history; personal content receives physical deletion or crypto-erasure after holds/retention. Audit events retain minimised identifiers and action evidence under an approved rule.

{
"principal_ref": "subj_H7K4Q2",
"display_label": "Synthetic Customer 0042",
"source_namespace": "demo-core",
"purpose": "PUR-DEMO-SERVICE",
"notice": "NOT-DEMO-2",
"real_person": false
}