Advanced data governance (Enterprise: governance)
What this is. One place for a privacy-strict organization to control how a tenant's data is classified (labeled by sensitivity), redacted (masked before it leaves), retained (kept, then deleted on schedule), located (pinned to a region), and encrypted. This feature adds the new classification + redaction mechanism and composes it with capabilities that ship elsewhere in probectl, so an operator sees one coherent governance view per tenant rather than five scattered settings.
| Concern | Where it lives | Edition |
|---|---|---|
| Data classification (IPs-as-PII) | internal/govern |
core mechanism |
| Redaction / masking | internal/govern |
core mechanism |
| Configurable retention + cross-store erasure | internal/tenantlife |
core (a compliance right) |
| Residency controls | siloed stores / region topology | provider / core |
| BYOK + no-downtime rotation (HYOK planned) | ee/tenantkeys |
byok (Enterprise) |
| Remote-AI egress consent (enforcement) | internal/ai egress gate |
core (fail-closed; consent is set via the governance policy) |
| The governance POLICY + composed view | ee/governance |
governance (Enterprise) |
The split that matters: the classification + redaction mechanism is core — a
redacted export is useful to anyone, so it works on any deployment with no license.
The per-tenant policy + operator surface is the governance Enterprise
feature, installed onto the core govern seam at the attach seam.
Data classification
Classification is labeling: every sensitive data category (a kind of value
— an IP address, an email — not a specific column) gets a sensitivity
class, ordered low → high:
public < internal < confidential < pii < restricted.
Think of folder labels in a filing cabinet — the label, not the clerk's
judgment per page, decides how a folder is handled when it leaves the room.
(PII is personally identifiable information — data that can point back to
a human.)
| Category | Default class | Examples |
|---|---|---|
ip_address |
pii (the headline) | source / dest / exporter / next-hop IPs, probe targets |
email |
pii | operator / contact emails |
subject_id |
pii | user IDs, user names, display names, account IDs, session IDs, external IDs |
free_text |
pii | log bodies, AI questions/answers, JSON payload text, alert/change summaries |
attribute_map |
pii | OTLP resource/span/log attrs, nested JSON maps |
dns_name |
pii | DNS qname/resource values that may embed host/user identity |
url_path |
pii | HTTP paths and route/target values with subject IDs or credentials |
object_key |
pii | support/export/browser artifact keys and object-store paths |
geo |
pii | city / region / coordinates |
mac_address |
confidential | device MACs |
workload_name |
confidential | source/destination workload and service names |
org_unit |
confidential | department, team, organization-unit attributes |
hostname |
internal | device / exporter hostnames |
user_agent |
internal | RUM user agents |
asn |
public | autonomous-system numbers |
credential |
restricted | secrets, tokens, wrapped keys, BYOK refs |
IPs-as-PII is the headline. Under GDPR (the EU's privacy law) and similar
regimes an IP address is
personal data — it can identify a household or a person — so ip_address
defaults to pii and is masked by default whenever
redaction is active. A tenant's governance policy can re-classify any category
(e.g. treat hostname as pii).
Redaction / masking
When redaction is active, every category at or above the policy's redaction
floor (default pii) is masked — the floor is a water line: everything at
that sensitivity or above goes under, everything below stays readable. The
strategies:
| Strategy | Behavior | Example (203.0.113.42) |
|---|---|---|
partial (default) |
keep a coarse, non-identifying prefix | 203.0.113.0/24 (IPv4 → /24; IPv6 → /48; email → a***@domain; MAC → OUI) |
hash |
stable, non-reversible pseudonym (correlatable) | sha256:1a2b… (16-hex prefix) |
drop |
remove entirely | `` (empty) |
none |
leave as-is | unchanged |
partial is "blur the house number, keep the street": analytics can still
group by network, but no value points at one host. (A MAC's OUI is its
first three octets — the vendor prefix, shared by millions of devices.)
hash is pseudonymization: the same input always yields the same token, so
"this address appears in both records" survives while the address itself does
not. restricted (credentials) always drops in clear — secrets never
leave the
deployment in a governed export, regardless of strategy. All hashing routes
through the FIPS-swappable internal/crypto provider — no raw crypto
primitives outside it.
Redacted export
The tenant-portability export gains a redacted mode:
GET /v1/lifecycle/export?redact=true # mask PII per the tenant's policy
POST /v1/lifecycle/subjects/export # body: {"subject":"alice@example.com","redact":true}
and a tenant whose governance policy sets redact_export: true always gets a
redacted tenant or subject export, even when a requester forgets to ask. The
manifest carries "redacted": true. Postgres rows, flow records, and OTLP
subject rows are masked column-by-category (IPs, emails, geo, MACs, …) while
non-sensitive fields (counts, protocol, names) survive. Malformed lines pass
through untouched, so the bundle stays well-formed.
The redaction mechanism is core (the ?redact=true toggle works on any
deployment with the PII-floor default). The governance feature adds per-tenant
policy — custom classifications, a custom floor, and forced export redaction.
The governance policy + composed view
The provider plane exposes one place for a tenant's data governance
(governance-gated; the routes 404 when unlicensed — hidden, not locked):
GET /provider/v1/tenants/{id}/governance— the composed view: the effective classification of every category + the redaction policy + remote-AI egress consent + residency + isolation model + retention + BYOK status.PUT /provider/v1/tenants/{id}/governance— set the policy: classification overrides, the redaction floor (redact_from),redact_export, and the tenant's remote-AI egress consent (ai_remote_egress). Audited to the separate, tamper-evident provider audit stream (provider.governance_set), admin-only, and blocked by the read-only license degrade.
The policy persists in tenant_governance (migration 0033; migration 0037
adds the ai_remote_egress consent column): a tenant reads its
own policy under RLS (row-level security — the database enforces the tenant
boundary itself). It is on the silo deny list
(never copied into a per-tenant silo schema) and is erased with the tenant at
offboarding. The resolver installs onto the core govern seam, so redacted
exports honor per-tenant overrides.
Tenant self-service management (AUD-12)
An Enterprise tenant admin manages its OWN tenant's policy from the core tenant API, so granting AI-egress consent or setting governance policy never requires an operator or raw SQL:
GET /v1/governance/policy— the calling tenant's remote-AI egress consent, redaction policy, and the effective classification of every category (governance.read).PUT /v1/governance/policy— set classification overrides, the redaction floor (redact_from),redact_export, and the remote-AI egress consent (ai_remote_egress). The tenant comes from the authenticated principal, never the request body; the change is written to the tenant's own tamper-evident audit chain (governance.policy_set) in the same transaction (governance.write).
Both routes are unlocked only by the Enterprise governance feature and 404
when unlicensed (hidden, not locked). Like key rotation and remediation, the
PUT honors the read-only license degrade: past the grace period the policy
stays readable but an edit is refused with 403 license_read_only before it
reaches the store. One edit always goes through — withdrawing the remote-AI
egress consent (ai_remote_egress true → false) with nothing else changed —
because refusing it would keep telemetry flowing to a remote model the tenant
no longer consents to. This is a MANAGEMENT surface for the
existing consent, not a relaxation: the row stays write-fenced off the tenant
app role (migration 0111) — the handler writes it through the provider role in
a tenant-GUC-bound transaction — and the core AI egress gate still fails closed
and redacts regardless of who set the bit. The provider plane's
/provider/v1/tenants/{id}/governance remains the operator's cross-tenant path
for hosted tenants.
Remote-AI egress consent
ai_remote_egress is the tenant's opt-in for sending its telemetry summaries to
a remote AI model. It defaults to false, and the core AI egress gate
refuses remote synthesis for a non-consenting tenant — no consent row, no
database, or a read error all resolve to denied (fail closed). The
governance policy is only where the consent is recorded; the disclosure of
exactly what a remote call sends, and the other gates in front of it, is
ai-egress.md.
Retention, erasure & residency (composed, not re-implemented)
The governance view shows these together; it does not re-enforce them. Each is owned by its own subsystem — governance is the dashboard, not a second engine:
- Retention + cross-store erasure is core (
internal/tenantlife): configurable per-plane tenant retention for flow, OTLP, eBPF, path, audit, persisted AI answers, object artifacts, and derived identity caches, plus verifiable deletion across Postgres / ClickHouse / TSDB / object storage with a recomputable attestation. Erasure covers all live stores; backups are the operator's documented backup-TTL (PROBECTL_BACKUP_RETENTION_NOTE) — a governed deletion is not a backup purge. The data-class/purpose matrix isdata-retention.md, backed by the code-ownedinternal/govern.DataInventory()inventory; the operational procedure isrunbooks/tenant-offboarding.md. - Subject lifecycle is also core (
internal/tenantlife):POST /v1/lifecycle/subjects/exportandPOST /v1/lifecycle/subjects/erasehandle a person or identifier inside the caller's tenant. Identity rows, persisted AI answers, flow rows, OTLP spans/logs, TSDB metric labels, topology/device labels, eBPF workload aggregates, and endpoint latest views are filtered by subject after tenant scoping when the wired backend supports that operation. RUM is covered by the TSDB metric-label receipt, and aggregate-only remote backends report their retention/delete-series basis instead of silently disappearing. Audit uses an append-onlyprivacy.subject_erasemarker plus a tenant-routed, hash-only projection. All classified Postgres deletions and every stable-alias marker/projection commit in one tenant transaction; any marker failure rolls them all back. The projection survives ordinary audit-prefix retention (including marker-only writes from rolling older binaries), so the evidence chain stays sealed and retention can progress while the person's visible fields remain taped over in API, SIEM, subject-bundle, and full-tenant audit exports. - Residency is siloed stores pinned to a region, plus the region topology.
Strict tenants run siloed (their own schemas/databases rather than shared
ones) so their stores stay in the permitted region rather
than replicating globally. See
isolation.md,multi-region.md. - BYOK + no-downtime rotation is the
byokEnterprise feature (ee/tenantkeys): per-tenant customer-held keys (bring-your-own-key — the tenant supplies the key material the platform wraps per-tenant DEKs with), rotation with retired-versions-decrypt-only (no downtime), and crypto-offboarding. Seebyok.md. HYOK (hold-your-own-key — key material that never resides on the platform at all) is planned, not yet available: it is tracked as F56/Phase 2 (internal/crypto/envelope.go), and BYOK is the shipped per-tenant-key capability today.
Watch-outs
- Erasure must cover all stores, including the backup policy. Erasure clears the live stores and attests it; backups expire per your documented TTL.
- BYOK key-unavailability fails safe. An unreachable / destroyed key is an error, never a silent fallback to a shared key.
- Rotation across high-volume stores is deferred-rewrap. New data uses the new key immediately; old data re-seals on write — no downtime.
- Redaction is best-effort masking, not anonymization.
partialkeeps a network prefix andhashis correlatable. For irreversible removal, use erasure — you cannot un-mask, but you also cannot promise a masked value identifies nobody.