Governance
Most databases let you add access control. Relata builds it into the query path so there is no way to bypass it: every read passes through a Cedar-inspired ABAC policy engine, every write enters a tamper-evident audit hash chain, and classified types are blocked at egress regardless of query success. You cannot "accidentally" skip it.
The reason this matters: bolt-on access control fails at integration seams. When a new protocol door opens (pgwire, gRPC, S3, Arrow Flight), a bolt-on layer is easy to forget. In Relata the planner enforces policy before it hands results to any protocol handler — adding a new door does not create a new bypass.
Policy evaluation model
The policy engine lives in relata-acl. Its semantics are Relata's own — inspired by Cedar's attribute-based model but not using the open-source cedar-policy crate.
| Property | Behaviour |
|---|---|
| Decision rule | Deny-wins — any matching deny overrides all allows |
| Row filtering | Bitmap bitset; branch-predicted; ~1.0× raw-scan overhead on allow-all |
| Conditional ACL | ~1.32× raw-scan p50 (realistic selective policies) |
| Cell masking | CellFilter::apply_to_row — ~2.6× raw-scan p50 (allocates per-row); avoid on hot full-table scans |
| Organisation isolation | Planner guard + tenant_id keying in store/scan.rs |
| Multi-tenant | Every request carries tenant= → X-Organization-Id; no cross-tenant data ever crosses the planner |
EXPLAIN POLICY
To understand why a query was allowed or denied, prefix it with EXPLAIN POLICY:
EXPLAIN POLICY
SELECT name, ssn, salary
FROM Employee
WHERE department = 'Engineering'The response is a decision tree showing which rules matched, in evaluation order, and what action each took (allow / deny / mask). This is the primary debugging tool for policy authors.
PURPOSE tracking
Every query may carry a purpose token. When present, it is recorded in the audit log against the principal, timestamp, and affected rows. When absent, the query still runs — PURPOSE is optional by design — but recorded as purpose: null.
In strict mode, a missing or unregistered purpose is rejected before the query reaches the planner:
# Strict: all queries must declare a registered purpose
RELATA_PURPOSE_MODE=strict
RELATA_PURPOSES=analytics,audit,compliance,product_research
# Open: any purpose string is accepted (dev/test only)
RELATA_PURPOSE_MODE=openPurposes support hierarchical scoping with : as separator. A principal with permission for analytics automatically covers analytics:external and analytics:internal.
-- Declaring purpose in SQL (pgwire / CLI)
PURPOSE 'analytics' SELECT name, revenue FROM Account LIMIT 100Per-tenant encryption
Each tenant has a Data Root Key (DRK) managed by the KMS integration. Rows are encrypted at rest under the tenant's DRK. Cross-tenant reads are impossible at the storage layer — the planner guard and the encryption boundary are independent controls that both have to fail for data to leak.
When GDPR erasure runs, the DRK for the erased subject is destroyed, rendering all encrypted rows cryptographically unreadable without needing to locate and delete individual blocks.
Egress filtering
Four classified types are blocked at egress unconditionally — they never appear in query results, tool responses, or protocol-door outputs, regardless of what the query asked for or what ACL rules allow:
| Type | What it represents |
|---|---|
SourceTrueIdentity | HUMINT-protected true identity (SPECS §5.19) |
SigintIntercept | Signal intelligence intercept records |
AccessScopedIntercept | Restricted access-scoped data (SPECS §5.20) |
LawfulInterceptRecord | Lawful intercept records |
Egress filtering runs after ACL evaluation, so it catches cases where a policy bug would otherwise have allowed the data through.
GDPR Art. 17 erasure
ERASE SUBJECT 'person-42' REASON 'gdpr-art17' CERTIFY;What this does, in order:
- Shreds all rows whose
entity_idresolves toperson-42via theIdentityIndex. - Deletes orphaned content-addressed blobs.
- Destroys the per-subject Data Encryption Key via KMS (fail-closed — if the KMS call fails, erasure is aborted).
- Writes a tombstone to the WAL and advances the audit hash chain.
- Returns a signed Art. 17 receipt with a manifest hash.
With no KMS configured, step 3 succeeds in reason: "gdpr-art17" mode — the rows are deleted but the cryptographic key destruction step is skipped. The receipt makes this explicit.
The same operation is available as:
- MCP tool:
erase_subject - Python SDK:
IdentityClient.erase_subject(subject_id, reason="gdpr-art17")
Audit hash chain
Every write — INSERT, UPDATE, erasure, schema change — is recorded in the append-only audit log with: principal, timestamp (HLC ns), purpose, cost units, and a SHA-256 hash that chains to the previous entry. Any retroactive modification breaks the chain.
# Verify chain integrity
relata doctor
# Count entries and confirm chain validity
curl http://localhost:9090/audit/count
# → { "entries": 4821, "chain_valid": true }Governance operators (Python SDK)
from relata import RelataClient, GovernanceClient, AuditClient
with RelataClient(url, bearer_token=token, purpose="compliance") as client:
gov = GovernanceClient.from_client(client)
# Legal hold — suspends retention policies for a case
gov.place_legal_hold(case_id="case-7", reason="litigation hold")
# WORM retention — immutable for 7 years
gov.set_worm_policy(object_type="AuditEvent", retention_days=2555)
# Import Sigma detection rules
gov.import_sigma(open("sigma/financial-fraud.yml").read())
# Break-glass emergency access (requires approver)
gov.request_breakglass(reason="P0 incident", approver="ciso@example.com")
# Data Subject Access Request
gov.submit_dsar(subject_id="person-42", requester="dpo@example.com")
with RelataClient(url, bearer_token=token, purpose="compliance_review") as client:
audit = AuditClient.from_client(client)
# Paginated audit log
for page in audit.entries(filter={"purpose": "analytics", "since": "2026-01-01T00:00:00Z"}):
print(page)
# Signed receipt for a specific exhibit
receipt = audit.signed_receipt(exhibit_id="exhibit-7")
# PDF export for court submission
pdf = audit.export_pdf(filter={"case_id": "case-7"})What is not yet shipped
- gRPC cell masking is the highest-priority open audit finding — cell masking currently applies on the HTTP and SQL surfaces; gRPC responses can expose unmasked cell values to principals that should see redacted output.
See also
- Provenance — the hash chain that makes audit log entries tamper-evident
- Bi-Temporal Model — governance decisions are themselves time-stamped on the system axis
- Identity Resolution —
ERASE SUBJECTuses the identity graph to find all affected rows - SQL Reference —
EXPLAIN POLICY,ERASE SUBJECT,PURPOSEsyntax