A policy test against the current table is not proof that every read path is protected. Analysts query views, applications use different identities, and reporting teams revisit historical versions. Each path needs its own access contract.
At NexOps, we have delivered Unity Catalog governance and self-service data onboarding in our RIVM engagement. That context informs our approach: policy behaviour belongs in deployment validation, not just an administrative checklist.
We will explain the new attribute-based access control (ABAC) support for views and qualifying time-travel queries, then turn it into an automated release gate. The goal is to validate identities, tags, schemas and compute together — without treating Beta support as universal coverage.
What Changed
Two September updates extend where ABAC row filters and column masks can operate in Azure Databricks. Both are Beta, with separate preview prerequisites.
| Update | Announcement | Scope and prerequisites |
|---|---|---|
| ABAC on views | 29 September 2026 | Row filters and column masks extend to views. An account admin enables the ABAC on Views preview through the account console's Previews page. Requires Databricks Runtime 19 or above. |
| Time travel with ABAC | 23 September 2026 | Historical reads on qualifying Unity Catalog managed tables enforce ABAC row filters and column masks. Requires the Time Travel on UC Managed Tables with Attribute-Based Access Controls preview. |
The ABAC on views release note establishes view support. The time-travel documentation supplies the more detailed eligibility rules:
- Queries must run on standard compute using Databricks Runtime 19 or above, or on serverless compute. Dedicated compute is not supported for this time-travel feature.
- Tables must be Unity Catalog managed Delta tables with catalog commits enabled, or managed Iceberg tables.
- For managed Delta tables, column mapping must be enabled in both the current and requested historical versions, using the same mapping mode.
- Time travel on tables with table-level row filters or column masks remains unsupported.
Supported historical query forms include SQL TIMESTAMP AS OF, VERSION AS OF and the @ identifier syntax, plus DataFrame timestampAsOf and versionAsOf options. The semantics matter as much as eligibility: Databricks compares the historical and current schemas, exposes only columns present in both, and applies the table's current policies to historical data for the querying user. If a current policy needs a column that is missing or incompatible in the historical version, the query fails closed.
The ABAC core concepts also clarify that views are included among the securable types targeted by FOR TABLES. That does not establish every view-query combination as supported; the supplied sources do not detail view-specific compute restrictions or policy composition.
The ABAC requirements add important constraints for view-based policies. ABAC policies on views are Beta, require the ABAC on Views account preview and Databricks Runtime 19 or above, and metric views are not supported. A single query can apply at most 20 ABAC policies across the applicable table and view hierarchy; exceeding that limit causes the query to fail.
View definitions can also change governance state. CREATE OR REPLACE VIEW removes governed tags and directly attached policies from the view. Treat view replacement as a security-relevant deployment change: reapply the intended tags and policies, then rerun the affected policy contracts before promotion.
These requirements are distinct from the stricter eligibility rules for ABAC time travel. Runtime 19+ eligibility for the view preview should not be interpreted as evidence that every compute mode or view/time-travel combination is supported. Keep those routes separate in the deployment matrix and approve only combinations explicitly supported by the relevant documentation.
Why It Matters for Platform Teams
Access-path coverage is now a release concern. A successful query against a protected table tells us nothing conclusive about a separately exposed view, a different execution identity or an eligible historical read. We need explicit expected behaviour for each route the organisation actually offers.
Governed tags also become security-relevant deployment inputs. Tags drive policy matching, and column tags must be applied directly: they do not inherit from their parent table. A deployment that creates the correct policy but misses the intended column tag can still violate the organisation's access contract.
Historical reporting introduces a second contract: availability under current governance. Time travel retrieves old data, not an old authorisation regime. A schema change can legitimately make an earlier version unreadable when today's policy cannot be evaluated safely against it.
Our position is straightforward: deploy policy code, tag assignments, relevant schema changes and their tests as one reviewed change. A green infrastructure deployment is insufficient; the gate must prove that restricted users receive the intended rows and masks, while authorised users retain their intended access.
Use Case: Governed Public-Health Reporting
Consider a national health institute that provides regional reporting over sensitive records. This is a hypothetical scenario based on public-sector governance constraints, not a claim that NexOps has deployed these Beta features at RIVM. Regional analysts need restricted extracts, authorised stewards need broader access, and reporting teams must reproduce calculations against earlier dataset versions.
The usual approach is to validate the base table using an administrator's notebook, then expose a reporting view. That misses the analyst's actual identity and leaves historical reads outside the test. A later column rename or tag edit can also change policy applicability without changing the reporting query.
The proposed architecture keeps governed managed tables in Unity Catalog, with reporting views as a separate consumer interface. Catalog- or schema-scoped ABAC policies use governed tags to select the relevant objects and columns. A deployment pipeline prepares synthetic fixtures, runs current-table and view queries under representative identities, and runs eligible historical-table queries on the intended compute.
The gate measures contract coverage, unexpected row or mask results, legitimate historical queries that fail after schema changes, and time spent diagnosing rejected releases. The institute should also track query latency for representative reporting workloads. None of these signals requires collecting sensitive query results into CI logs.
Implementation
1. Define the matrix before configuring the preview
Start with the supported routes you intend to expose — not every theoretical permutation. For each case, record the identity, object, query form, tag state, compute configuration and expected outcome.
| Dimension | Representative cases | Assertion |
|---|---|---|
| Identity | Restricted analyst, exempt steward, application service principal | Each receives its approved rows and column values |
| Object | Current table, reporting view | Each interface meets its own access contract |
| Historical read | Required versions and timestamps | Current policy is enforced on eligible historical data |
| Tags | Initial classification, addition, removal, reassignment | Behaviour matches the approved classification change |
| Schema | Compatible history, historical-only column, missing policy input | Projection or failure matches documented semantics |
| Compute | Each intended supported configuration | Eligibility is recorded and the contract passes |
Keep unsupported routes in the inventory, but mark them not approved for deployment. An unsupported route is not a test case whose successful execution grants permission to use it.
For ordinary table and view reads, verify their applicable compute requirements separately. The time-travel documentation's serverless support is not evidence for every view configuration.
2. Establish eligibility and a controlled fixture
Record the two preview settings separately. For historical managed Delta reads, verify catalog commits and column mapping in the current version and every historical version in scope; checking only today's table properties is inadequate.
Use a synthetic fixture containing records from two regions and an obviously artificial sensitive string. Configure an approved row-filter policy that restricts the analyst to one region, plus a mask returning *** for the protected string. Exempt the steward from both policies and grant the necessary underlying access separately.
Create a simple projection view over that fixture, exposing the same test columns without additional filtering. Establish the intended tag assignments on the relevant objects and columns, then verify the policy's actual effect on both interfaces. Do not assume a table's tag assignments automatically establish the view's required policy matching.
Retain a compatible historical table version containing the same fixture records. Capture its version number from the fixture setup; do not hard-code a production version into the test suite. For managed Delta fixtures, verify that column mapping is enabled in both the current version and every historical version under test, using the same mapping mode.
3. Run assertions under real execution identities
Run separate test sessions authenticated as the restricted and exempt principals. Switching an expectation variable inside an administrator's session does not switch the query identity.
The following PySpark notebook code is a starting contract for those two profiles. The environment variables are inputs to our test harness, not Databricks configuration keys. It assumes the fixture, policies, tags, permissions and compute prerequisites have already been prepared.
import json
import os
def run_policy_contract(spark):
# Harness inputs must be supplied separately for each authenticated session.
expected_principal = os.environ["ABAC_EXPECTED_PRINCIPAL"]
profile = os.environ["ABAC_TEST_PROFILE"]
historical_version = int(os.environ["ABAC_HISTORY_VERSION"])
if historical_version < 0:
raise ValueError("Historical version must be non-negative")
actual_principal = spark.sql(
"SELECT current_user() AS principal"
).first()["principal"]
if actual_principal != expected_principal:
raise RuntimeError("Test session has the wrong execution identity")
# Synthetic values only; the fixture contains both rows in both versions.
expected_by_profile = {
"restricted": [
("north-1", "north", "***"),
],
"exempt": [
("north-1", "north", "SYNTHETIC-NORTH"),
("south-1", "south", "SYNTHETIC-SOUTH"),
],
}
if profile not in expected_by_profile:
raise ValueError("Unknown test profile")
expected = sorted(expected_by_profile[profile])
projection = "event_id, care_area, person_label"
queries = {
"current_table": (
f"SELECT {projection} "
"FROM abac_acceptance.fixture.patient_events"
),
"current_view": (
f"SELECT {projection} "
"FROM abac_acceptance.fixture.analyst_events"
),
"historical_table": (
f"SELECT {projection} "
"FROM abac_acceptance.fixture.patient_events "
f"VERSION AS OF {historical_version}"
),
}
report = []
for path, query in queries.items():
# Collection is deliberate: this is a tiny, bounded synthetic fixture.
# Query errors propagate and fail the positive-access gate.
actual = sorted(tuple(row) for row in spark.sql(query).collect())
if actual != expected:
# Never include returned values in failure logs.
raise AssertionError(f"{path}: access contract mismatch")
report.append({
"path": path,
"profile": profile,
"outcome": "passed",
})
print(json.dumps(report, indent=2))
run_policy_contract(spark)
Exact row comparison catches both unexpected disclosure and unintended loss of access for this bounded fixture; a row-count check cannot do that. The identity check prevents an accidentally privileged test session from producing misleading evidence.
This example covers only three positive-access paths. Extend it with the production service principal, required timestamp-based reads and DataFrame reads if those interfaces are used by applications.
4. Add tag-change and schema-change contracts
Run classification transitions in an isolated test schema. Add, remove or change a governed tag, then repeat the relevant queries under each identity with explicitly reviewed expectations. A tag removal should never silently redefine “secure” as whatever the query happens to return.
Test historical schema behaviour independently. Include a version with a historical-only column and inspect the returned schema to verify that it is not exposed. Include a version missing a column required by the current policy and verify the documented fail-closed behaviour.
Separate expected query failures from ordinary positive-access tests. For a deliberate fail-closed case, validate the specific error class or signature observed and approved in your environment — the supplied documentation does not specify one. A timeout, missing privilege or unavailable compute must not count as successful security enforcement.
5. Promote the contract across DTAP
Package the test notebook alongside the deployment artefacts and make its outcome a required CI/CD check. Our declarative Asset Bundles guide covers the deployment foundation; the policy suite adds behavioural evidence to that process.
| Environment | Gate behaviour |
|---|---|
| Development | Build synthetic fixtures and exercise tag and schema transitions |
| Test | Run the full matrix with the intended preview settings and compute |
| Acceptance | Validate representative identity memberships and production-shaped query interfaces |
| Production | Use bounded synthetic smoke tests; avoid disruptive tag mutations on live sensitive objects |
Store evidence with the release: policy definitions, tag assignments, fixture history version, identity, compute details, preview prerequisites and assertion outcomes. Re-run the affected contracts after policy-function, tag, view-definition, schema or compute changes — not only after editing the policy itself.
Pitfalls and When Not to Use It
Beta approval is separate from test success. Passing a matrix does not override an organisation's restrictions on preview features. We would not introduce these access paths into regulated production workloads unless the organisation explicitly accepts Beta use and its operational implications.
Do not confuse ABAC with table-level row filters and column masks. The documented time-travel support applies to ABAC on qualifying tables; table-level filters and masks remain unsupported for time travel. Similar business rules do not imply interchangeable eligibility.
Do not promise permanent historical readability. Current policies are applied to historical data, and schema compatibility can force a legitimate query to fail closed. Regulatory reproduction requirements need tested historical versions and an agreed response when access is safely blocked.
Do not treat the two announcements as a combined support statement for historical reads through views. View support and qualifying historical-table support are distinct claims. Keep that combined route unapproved until official documentation establishes its eligibility and tests confirm its behaviour.
Finally, do not adopt a broad policy merely to simplify administration. Where classification ownership is unclear or tag completeness cannot be validated, a smaller, explicitly governed exposure surface is safer. Our preference is to expand supported access paths only as quickly as the organisation can test and operate them.
Key Takeaways
- Track both Beta previews separately. Record the view prerequisites and the stricter historical-table eligibility requirements before rollout.
- Test the actual execution identity. Cover restricted users, exempt users and application principals rather than relying on administrator queries.
- Assert values and schema, not just success. Validate exact synthetic results, masks, visible columns and deliberate fail-closed outcomes.
- Treat tags and schema changes as security changes. Re-run contracts when policy matching or historical compatibility can change.
- Gate deployment on supported behaviour. Keep unsupported routes unapproved, preserve release evidence and expand access only when the matrix proves the intended contract.
Databricks Data Contracts — Enforce Before You Publish
Build a Databricks data contract gate with explicit schemas, Delta constraints and DTAP tests, and decide what must pass before a dataset is published.
Platform Engineering · 8 minBuilding RIVM’s Self-Service Data Onboarding Platform
How we approach self-service data onboarding at RIVM: validated configuration, governed access and automated deployment without removing human accountability.
Case study · Data Platform EngineeringHow we did this at RIVM Data Platform →
Centralised Azure & Databricks data platform with a self-service data onboarding UI for the Dutch National Institute for Public Health.