Access control
Colrows enforces governance at compile time. Personas, scopes, and policy nodes shape an allowed semantic subgraph before any plan is generated - so unauthorized queries cannot be produced in the first place. This is how compliance becomes a structural property, not a procedural review.
Compile-time, not after-the-fact
In a traditional architecture, a query runs against the raw schema and a row-mask or column-redaction layer trims the result. The data is already touched, the audit trail begins after the fact, and the controls are only as good as the masking layer's coverage. Colrows takes a different approach: policy is part of the graph. During semantic binding, the requester's persona resolves an allowed subgraph; compilation occurs entirely within it. If a metric depends on a node outside that subgraph, resolution fails - there is no query to run.
The pieces
| Object | What it does |
|---|---|
| Persona | A first-class graph node representing a role. Holds policy bindings and scope. |
| Scope | The slice of the graph a request is allowed to traverse - global / datastore / persona / user. |
| Policy | A formal predicate attached to nodes - RBAC, ABAC, row-level, column-level, time-window, region. |
| Permission | The smallest unit: who, on what, can do what. Three flavors: fixed, regex, class. |
| Redaction policy | A policy that hashes, masks, or drops a column for a persona. Applied during plan generation. |
Fixed permissions
The simplest case. A persona is granted explicit access to a named dataset, column, or metric.
persona: regional_analyst
grant:
- datasets: [analytics.orders, analytics.refunds]
- metrics: [net_revenue, gross_margin]
- dimensions: [region, product_category]
Regex permissions
Pattern-based grants for catalogs whose object lists change frequently.
persona: bi_team
grant:
- datasets: ["analytics\\.fact_.*"] # every fact_* table
- columns: [".*"] # all columns
deny:
- columns: [".*pii_.*", ".*ssn.*", ".*aadhaar.*"]
Deny rules always win. Regex matches are evaluated against the fully qualified name of the node in the semantic graph.
Class permissions
Classification-based grants. Tag nodes with classes (pii, financial, regulated) and write policies against the classes - not against table names that drift.
class: pii # tag nodes with this class
applies_to:
- dataset.column where tag = "personal_data"
policy:
- persona: contractor → deny
- persona: support_l1 → redact (hash)
- persona: dpo → allow
Class permissions compose: a column tagged pii AND financial inherits the most restrictive policy for the requesting persona.
Sample policy definitions
Region scoping (ABAC)
policy: region_scope
applies_to: dataset.row
predicate: row.region = persona.region # ABAC
binds_to: [regional_analyst, regional_manager]
Time-window restriction
policy: rolling_90_days
applies_to: metric.net_revenue
predicate: time_window <= 90 days
binds_to: [auditor]
PII redaction for support tier 1
policy: pii_hash
applies_to: column where tag = "pii"
action: sha256(value)
binds_to: [support_l1]
Zero-trust data access
The combination of persona scope, class-based policy, and compile-time enforcement gives you zero-trust by default:
- Default deny. A persona starts with no access; every grant is explicit.
- No implicit relationships. Joins must be proven on the graph; a persona without access to one side cannot use it as a join key.
- Policy as code. Policy lives in version-controlled YAML in the graph and changes are versioned, attributed, and reversible.
- Audit trail by construction. Every executed query carries the persona, scope, policies evaluated, and the structural reasoning trace.
Masking trusts that every query runner remembered to apply the masking view, every BI tool refreshed the column list, and every ad-hoc analyst followed the policy. Compile-time governance assumes none of those things are true.
Best practices
- Tag columns with semantic classes (
pii,financial,regulated) and write policies against classes, not names. - Keep personas thin - bind one role to one persona; layer scopes through ABAC predicates.
- Prefer redaction (hash, mask) over outright denial when downstream signals still need a stable join key.
- Review the audit trail weekly - the failures (compilation refused) are as important as the successes.