Provisa / Governance
Governance covers more than access control.
When access control is configured per API, each new interface is another integration to get right and another thing for an auditor to check. Adding a column means finding every place access is enforced and updating each one. Access control here is compiled into every query plan, on every language and every transport. That is one part of governance; where, when, how and what the data means are declared in the same model and applied the same way.
Five questions
- Where to process. Which engine runs the work, and which region homes the data, for performance, cost or sovereignty. Engines and regions.
- When to process. Declared freshness, materialized views, load management windows and triggers. Materialized views.
- How to process. SQL, GraphQL or Cypher over nine protocols, all compiled to one governed plan. Interfaces.
- Who to process for. Access control: the six layers below, and governed relationships.
- The meaning of what you process. The glossary, metrics and registered relationships. One model.
Access control: six layers, compiled into the plan
Access control is usually configured separately per API, so adding a new protocol leaves a gap until someone finds it. Every layer here is applied at query compile time, uniformly, on every language and every transport. Add a source, a column or a relationship and every layer applies to it; there is nothing to re-apply per interface.
0. Introspection filtering
Schema browsers only ever see what the role is allowed to see. A hidden table is absent from the catalog they are handed, so they can't discover it and then be denied.
1. Public access
The anonymous surface is declared explicitly. Nothing becomes publicly readable as a side effect of registering it.
2. Domain access
Roles reach only the domains registered to them. Domains are the coarse boundary the finer layers operate inside.
3. Row-level security
Per-table, per-role WHERE injection, inherited recursively: a
view over a governed table carries the predicate of everything beneath it, and so does
a materialized view built on that view.
4. Column visibility and masking
Columns are visible, hidden, or masked per role. Masking is regex, constant, or truncation, with an explicit role-based bypass where a privileged role needs the raw value. When a request acts as several roles, each row's columns are masked and shown according to the roles whose row filter admitted that row, and writes are checked the same way.
5. Predicate guard and approval
A pre-execution ABAC hook over webhook, gRPC, or unix socket. The plan is offered to your own decision service before it runs, which is where request-time context, such as a ticket number, a break-glass approval or a time window, enters the decision.
Roles from attributes, and roles that explain themselves
A derived role comes from a user's attribute: region = eu makes the
user hold region and region__eu. Derived roles carry no
rights and are never granted by hand; masks, row rules and grants name them like any
role. Where the organization's record of a member and the identity provider disagree,
the organization's record wins.
A role constant runs the other way: from role to value. Every request acting as the
role binds the constant, and a row rule reads it as it reads a claim, so a role for a
subsidiary can carry that subsidiary's code without a user attribute for it. A
constant may be an ${env:…} or ${secret:…} reference,
resolved when a request uses it; the model keeps the reference, never the value.
Every role and every row rule carries a required description of what it is for, so an access review reads the reason beside the rule. A role that means something different in one domain becomes a new role there, usually a child of the general one, with its own description.
An AI agent asks which role it acts as and is told that role, what it is for and the roles it may switch to. It is never told a rule, a mask or a grant. It is not told why a result was narrowed, and nothing marks a result as filtered: a role's data is its whole view, so there is nothing to probe.
Relationships are governed too
A JOIN, or a graph traversal, is legal only if it matches a registered, approved relationship. Unapproved paths are rejected and logged.
That is what lets the query and graph explorers stay open: people can explore the whole model, and AI agents can discover what connects to what, while every path stays inside the edges policy approved. Each relationship carries a reason for why the traversal exists, written for people and agents alike.
The relationship guard is a per-role right
The guard is a single right on the role. Leave it off for analysts and agents; grant it to a trusted role and that role can write any JOIN across the whole model across all sources. The query takes the same governed path, with the same row-level security and masking, without the relationship guard.
An MCP agent never acts beyond the roles it was given: a remote agent only the roles its token's principal holds, a local one only the set its server was pinned to.
A role grant can be given until a date, and a role can remove its grants once they go unused for a set number of days. Use is measured by data: a grant counts as used when a request depended on it to read, not when its holder merely signed in or acted as every role at once. The holder and the organization's administrators are emailed before either happens, and each removal is recorded.
Probing for a role is a security event
A legitimate client names the roles it was given. Naming one it does not hold is treated as a high-severity security event on every surface: the HTTP API, MCP, pgwire, Bolt, Arrow Flight and gRPC. The request is refused with "not permitted" and nothing about whether the role exists, and the connection or session ends.
On the machine transports the credential stops working: a personal access token is suspended, and so is a principal's token access to the organization when an external identity provider signed the token. Only an administrator lifts a suspension; no timer does. Each event lands in the organization's administrative trail with the principal, credential, address, transport, the role named and the roles held, and is mailed to the administrators and, where the operator has set one, posted to a security webhook.
One redaction list, applied at every exit
What Provisa writes about its own work is redacted by one list of rules: the deployment's, the organization's own, and Provisa's built-in patterns for common shapes such as card numbers and cloud keys. Every secret Provisa holds, and every one the model references, is redacted without a rule being written for it.
The built-in patterns are a starter set. Provisa cannot know every sensitive format, so the organization adds patterns for its own, and redaction is best effort: a value in a shape no rule describes passes through.
The list applies where records leave: to every span and log line, standard output included, to the audit log's query text before it is hashed and encrypted, and to the errors a client receives on every protocol. A source's own logs are written by that source and stay outside Provisa's reach.
Organizations stay apart on a shared deployment
Many organizations can share one deployment. Each one's catalogs on the engine are named for it, its statements run in its own resource group, and its secrets are read only inside its own prefix of a central vault. Every span, metric point and log record carries the organization it was written for, and a read of them answers only the reader's organization. Background work stays its organization's too: change processing, replica builds and the triggers they fire each run as the organization they belong to, reading its sources and writing its stores, and each organization's environments hold their own replication slots. An organization's sources may reach only public addresses, never the platform's private network.
Data stays in its region
A table can name a region as its home. Only that region builds its copy; every other region reads that copy in place, with the reader's row and column rules applied at home, so only what the reader may see crosses. A home that cannot answer refuses the read; the source is never read live from elsewhere instead.
Drafts follow the same line. A draft table that names a region is previewed only on
that region's nodes, and a copy held anywhere else, from before its region was set,
is retired and dropped. Moving a table between regions needs the
data_residency right for both, and a move can pass through draft, so the
table is served nowhere in between.
Encrypted in transit, under the organization's own keys at rest
Every listener Provisa terminates serves TLS. Inside the deployment, encrypting the engine's coordinator-to-worker traffic is an operator option; under the Helm chart the chart generates the certificate authority and node certificates and keeps them across upgrades. Spooled engine data is encrypted with a key made for each query and discarded when it ends, and the engine keeps source catalogs, credentials included, in memory only.
An organization can hold its own encryption key, which wraps its secrets, encrypted columns and audit query text, and can encrypt its file folder (uploads, landed files, spill and temporary files) under a key kept in its own vault.
Observability as data
Traces, metrics, and logs are collected via OpenTelemetry, compacted into
Iceberg, and registered as queryable tables. Join customers to
queries to see who ran what and how long it took. The audit trail is a governed table
like any other, not a log file you grep.