Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The shortest path you know. The most useful ingredients, sanitized:

**Environment**

- OLAF version (`__version__`, printed at load): e.g. 1.0.0
- OLAF version (`__version__`, printed at load):
- Where it ran: Fabric notebook / pipeline / the pytest suite locally
- Python version, if running the suite locally

Expand Down
19 changes: 12 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,22 @@ jobs:
shell: bash
run: |
set -euo pipefail
archive_one="$RUNNER_TEMP/olaf-v1.0.0-one.tar"
archive_two="$RUNNER_TEMP/olaf-v1.0.0-two.tar"
git archive --format=tar --prefix=olaf-1.0.0/ --output="$archive_one" HEAD
git archive --format=tar --prefix=olaf-1.0.0/ --output="$archive_two" HEAD
# The prefix is release evidence, so it must not name a version this tree may no
# longer be. One source of truth, and no inline script: a heredoc here sits at
# column 0 and silently ends the YAML block, which parses to zero jobs.
version="$(python scripts/print_version.py)"
prefix="olaf-$version/"
archive_one="$RUNNER_TEMP/olaf-v$version-one.tar"
archive_two="$RUNNER_TEMP/olaf-v$version-two.tar"
git archive --format=tar --prefix="$prefix" --output="$archive_one" HEAD
git archive --format=tar --prefix="$prefix" --output="$archive_two" HEAD
cmp --silent "$archive_one" "$archive_two"
sha256sum "$archive_one"
python scripts/check_public_release.py archive "$archive_one" --tree HEAD --prefix olaf-1.0.0/
source_tree="$RUNNER_TEMP/olaf-v1.0.0"
python scripts/check_public_release.py archive "$archive_one" --tree HEAD --prefix "$prefix"
source_tree="$RUNNER_TEMP/olaf-v$version"
mkdir "$source_tree"
tar --extract --file="$archive_one" --directory="$source_tree"
python scripts/check_secrets.py self-test
python scripts/check_secrets.py tree "$source_tree/olaf-1.0.0"
python scripts/check_secrets.py tree "$source_tree/olaf-$version"
python scripts/check_secrets.py history .
python scripts/check_secrets.py all-objects .
46 changes: 44 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,55 @@
# Changelog

Notable changes to OLAF — OneLake Access Framework — are recorded here using
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) structure. Runtime
version `1.0.0` maps to release tag `v1.0.0`.
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) structure. The runtime's
`__version__` maps to the release tag `v{__version__}`.

## [Unreleased]

No changes yet.

## [1.1.0] - 2026-08-27

### Changed

- Guardrail **G3** is no longer a runtime warning. The trap it named is real and unchanged —
`NOT IN` against a NULL is UNKNOWN, `WHERE` keeps only TRUE, and the row disappears from a
deny-list's result. **The direction is over-rejection: it removes rows a role should see and
cannot widen access to any row.** What went was the check, because it could not tell the two
apart. It was a substring test on the raw condition rather than the literal-stripping lexer
rules C9/C11/C13 share, so it warned on `status = 'CANNOT INVOICE'` — which contains no `NOT IN`
operator — and stayed silent on `region NOT IN ('a') AND type IS NULL`, where an unrelated
column's `IS NULL` muted it. Both directions wrong, on every run, for the life of a config.
It also named only one of the two valid mitigations, so a config that deliberately asserts the
column is never NULL could never come back clean.
- The `Key invariants` entry in `docs/architecture.md` now carries the trap, both mitigations, and
the direction of failure. The starter workbook's own note said `Every NOT IN needs OR <col> IS
NULL`; it now names both mitigations too.

### Removed

- `RLS.null_safety_warning()`, the helper behind G3. It was listed in `docs/api/functions.md` as a
directly-callable helper, so this is a public-API removal. Shipped as MINOR rather than MAJOR as
a recorded deviation, not an oversight: nothing is distributed as a package — the runtime is a
notebook users copy — `SECURITY.md` supports only the latest Preview, and the helper had no
caller inside or outside the framework. The repo states no Preview exemption from SemVer, so
this note is the exemption.

### Upgrade note

- **The first `generate` after upgrading re-stamps the mapping, even on an unchanged config.** The
idempotent-skip fast path requires the stored `framework_version` to equal `__version__`, so on
the first run it does not match: `changed` is `True` rather than `False`, the status is `success`
rather than `skipped`, two log rows are written instead of one, and the mapping table is rewritten
with `framework_version` `1.1.0`. `config_hash` does not move — it hashes config rows only — and
the run after that skips again. A pipeline gated on `envelope["changed"]` will therefore re-plan
and re-apply once per deployment on upgrade. Nothing fails; both `success` and `skipped` exit
normally.
- `mapping_hash` projects to the mapping columns and excludes provenance, so it is unchanged: a
saved-plan row stamped `1.0.0` still opens the apply gate, and the mapping-history CSV is reused
verbatim. That CSV's own `framework_version` column therefore still reads `1.0.0` while the
mapping table reads `1.1.0` — expected, and the reason the plan gate survives an upgrade at all.

## [1.0.0] - 2026-08-26

First public release, positioned as an independent community Preview for
Expand Down
15 changes: 14 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,20 @@ build that has nothing wrong with it. Read its report; it will not block you.
## Versioning

`__version__` in `notebooks/olaf.ipynb` is stamped into `framework_version` on audit rows. Release
tags use `v{__version__}` (for example, runtime `1.0.0` maps to tag `v1.0.0`). Put user-visible
tags use `v{__version__}`, and the annotated tag object must be created with the **sanitized
maintainer identity**, never a personal one. A tag object is public and immutable, and
`scripts/check_public_release.py` has `tree` and `archive` modes only — nothing catches a bad
tagger afterwards. The identity is deliberately absent from every tracked file (the gate reports
`APPROVED_IDENTITY_CONTEXT` if it appears in one), so read it off the previous release tag rather
than typing it:

```
git -c user.name="$(git for-each-ref --format='%(taggername)' refs/tags/vPREV)" \
-c user.email="$(git for-each-ref --format='%(taggeremail:trim)' refs/tags/vPREV)" \
tag -a vX.Y.Z -m 'release: OLAF vX.Y.Z'
```

Put user-visible
changes under `Unreleased` in `CHANGELOG.md`; release maintainers move them into a dated version
section as part of the release review. Do not bump a version or create a tag in an ordinary pull
request unless the pull request is explicitly the release change.
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ OLAF changes authorization state and stores principal/access metadata. A defect
misconfiguration can grant unintended access, remove required access, or expose
control data. Treat this policy as part of the operating contract.

> **Release status:** OLAF v1.0.0 is an independent community Preview for
> **Release status:** OLAF is an independent community Preview for
> evaluation and development, not a production-ready security product. The bulk
> DAR mutation endpoint on which it depends is officially Preview:
> [Microsoft REST reference](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles).
Expand Down Expand Up @@ -164,7 +164,7 @@ a platform guarantee:

The same-lakehouse design is not cryptographic or transactional isolation. If the
trusted-administrator and externally controlled sharing boundary is unacceptable,
do not import real principal data or run sensitive modes in v1.0.0.
do not import real principal data or run sensitive modes in this release.

## Operational recommendations

Expand Down
2 changes: 1 addition & 1 deletion SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Thanks for using OLAF. Here is where to take each kind of question, and what to
honestly expect back.

OLAF v1.0.0 is an independent community Preview for evaluation and development;
OLAF is an independent community Preview for evaluation and development;
it is not a production support offering. Its mutating DAR endpoint is officially
Preview: [Microsoft REST reference](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles).

Expand Down
2 changes: 1 addition & 1 deletion configs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ intent from that ID. Microsoft documents NotebookUtils token audiences separatel
OLAF's decision not to call Graph must not be read as a universal Fabric limitation:
[NotebookUtils credentials](https://learn.microsoft.com/en-us/fabric/data-engineering/notebookutils/notebookutils-credentials#get-token).

Platform guidance in the workbook is versioned for OLAF v1.0.0 and points to the
Platform guidance in the workbook is versioned for this OLAF release and points to the
maintained [platform contract](../docs/platform-contract.md). Microsoft's service
rules and limits may change; the official links remain authoritative.
Binary file modified configs/onelake_security.xlsx
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A one-stop index of everything under `docs/`. Start with the "Start here" row if you're new to
the framework; the rest is reference material you'll come back to.

OLAF v1.0.0 is an independent community Preview for evaluation and development.
OLAF is an independent community Preview for evaluation and development.
Read the platform and control-data boundaries before using real principal data.

## Start here
Expand Down
4 changes: 2 additions & 2 deletions docs/api/Audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Back to [API index](../api-reference.md) - [docs](../README.md).

> OLAF v1.0.0 is a community Preview. Audit helpers summarize OLAF control records and the DAR
> OLAF is a community Preview. Audit helpers summarize OLAF control records and the DAR
> responses they can read; they are not universal authorization proofs across every Fabric engine
> or access mode. Use the [platform contract](../platform-contract.md) when interpreting results.

Expand Down Expand Up @@ -185,7 +185,7 @@ Returns: dict | None -- `config_hash`, `config_version`, `framework_version`, `g

```python
trail.current_generation()
# {"config_hash": "284ae40f8b47a294", "config_version": 42, "framework_version": "1.0.0",
# {"config_hash": "284ae40f8b47a294", "config_version": 42, "framework_version": "1.1.0",
# "generated_at": "2026-07-11T12:00:00+00:00", "mapping_hash": "9f8e7d6c00000000", "mapping_version": 7}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/api/FabricClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ and submits OneLake data access roles and resolves a lakehouse display name. Kee
real identifiers, request bodies, and responses out of repository artifacts and public issues.

> **Preview boundary:** Microsoft labels the bulk create/update DAR endpoint **Preview**, for
> evaluation and development, and not recommended for production use. OLAF v1.0.0 is therefore a
> evaluation and development, and not recommended for production use. OLAF is therefore a
> community Preview. The official contract says the supplied roles are created or updated; it does
> not promise atomic full-set replacement or deletion of roles omitted from the body.
> [Official bulk endpoint](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles).
Expand Down
3 changes: 1 addition & 2 deletions docs/api/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ beyond what's noted.

They are **static methods on classes**, not bare module-level functions -- call them as written
here. Most of those classes are pure namespaces (`Hash`, `Parse`, `ScopePath`, `Target`, `DAR`,
`RLS`, `Catalog`). `OLAFError` is not: it is the framework's **exception base class**
`Catalog`). `OLAFError` is not: it is the framework's **exception base class**
(`class OLAFError(Exception)`, the parent of `ValidationError`, `DARHTTPError` and `UsageError` --
see [errors.md](errors.md)) that additionally hosts the `classify` static method below.

Expand All @@ -34,7 +34,6 @@ see [errors.md](errors.md)) that additionally hosts the `classify` static method
| `Target.tenant(tenant_id=None)` | Explicit `tenant_id` wins; else best-effort auto-resolve from the runtime context; `None` if neither is available. |
| `Target.run_by(spark=None)` | Who is running this: runtime-context `userName` (an interactive user's UPN), else runtime-context `userId` (the running principal's Entra **object id** -- the layer that makes a service-principal / workspace-identity pipeline run attributable at all), else Spark `current_user()`, else `None`. Exception-safe. `Log` then labels a GUID-shaped result via [`Log.resolve_principal`](Log.md#resolve_principalspark-member_table-value) -- the id is never replaced. |
| `OLAFError.classify(exc)` | Map an exception to the audit `error_category` vocabulary: `http` \| `validation` \| `guard` \| `unexpected`. |
| `RLS.null_safety_warning(rls_condition)` | Warn when an `rls_condition` has `NOT IN` without `OR <col> IS NULL` (SQL three-valued-logic trap; guardrail G3). |

The rest of `notebooks/olaf.ipynb` -- the `generate`/`plan`/`apply` pipeline steps
(`Generate.rows`, `Catalog.canonical`, `DAR.diff`, `DAR.merge_upsert`/`DAR.merge_replace`,
Expand Down
8 changes: 4 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture — OneLake Access Framework

OLAF v1.0.0 is an independent community Preview for evaluation and development,
OLAF is an independent community Preview for evaluation and development,
not a production-ready security product. It turns an authored role × scope × rule
matrix into reviewed Microsoft Fabric OneLake data access role (DAR) requests and
audit evidence from a Fabric notebook. Its bulk DAR mutation dependency is officially
Expand Down Expand Up @@ -597,7 +597,7 @@ the release does not publish an exact-SHA live service result for constant predi

The check asks whether a bareword appears outside string literals. It deliberately
avoids interpreting keywords as columns. The contract is limited to ASCII identifiers;
non-ASCII predicate identifiers are outside OLAF v1.0.0's supported authoring surface.
non-ASCII predicate identifiers are outside OLAF's supported authoring surface.

**Scope of that claim — ASCII only (known limitation).** The bareword test is `[A-Za-z_]`, so
"bareword" means *ASCII* bareword. This never widens access; it can only over-reject. Any non-ASCII
Expand Down Expand Up @@ -762,7 +762,7 @@ Two consequences worth stating plainly:

### Limits (fail at generate/plan, not at apply)

OLAF v1.0.0 snapshots the following compatibility ceilings from Microsoft's
OLAF snapshots the following compatibility ceilings from Microsoft's
official limitations reviewed on 2026-08-22. They are volatile service values; check
the current source before operation:
[OneLake security limitations](https://learn.microsoft.com/en-us/fabric/onelake/security/data-access-control-model#onelake-security-limitations).
Expand Down Expand Up @@ -790,6 +790,6 @@ records when generate ran. Full detail: data-model.md.
1. The plan/apply role build reads **only** the mapping lock-file — never the short config (TOCTOU closed).
2. New tables are absent from the saved mapping until the next generate. The resulting platform access still depends on workspace/item permissions and engine/access mode; OLAF does not infer that nobody can read them.
3. Glob (A2): 0-match on an include OR an exclude = error; table schema part is literal-only. Case resolves through the catalog. Role and predicate limits are OLAF compatibility guards tied to the cited current platform pages, not permanent no-workaround guarantees.
4. G3: `NOT IN` without `OR <col> IS NULL` = warning (three-valued logic drops NULL rows silently).
4. `NOT IN` against a nullable column drops NULL rows silently — three-valued logic makes the term UNKNOWN for a NULL and `WHERE` keeps only TRUE. **This never widens access; it can only over-reject** — a row vanishes from a deny-list's result, so the failure is missing data, not exposure. Guard it with `OR <col> IS NULL`, or assert the column is never NULL; do one of the two deliberately. OLAF warned about this per row (guardrail G3) until the release noted in the CHANGELOG and no longer does: the check was a substring test on the raw condition, so it fired on `status = 'CANNOT INVOICE'` (no `NOT IN` operator at all) and stayed silent on `region NOT IN ('a') AND type IS NULL` (a different column's `IS NULL` muted it). It could not be made sound without the literal-stripping lexer the other RLS rules share, and it named only one of the two mitigations above.
5. Cross-row rules (see the Rule catalog above) warn/block ambiguous multi-role policy shapes. Effective access is engine-explicit because SQL endpoint CLS differs from non-SQL CLS. C5 is a conservative guard around Microsoft's documented unsupported RLS/CLS combinations; it does not extrapolate untested behavior across tables or membership paths.
6. Log rows are single-valued — one row per (role × scope × member × action) step; lists never reach the log. Every log row also carries `config_hash`/`config_version`.
11 changes: 9 additions & 2 deletions docs/config-examples.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Config cookbook — worked config → mapping examples

All values here are synthetic. OLAF v1.0.0 is an independent community Preview;
All values here are synthetic. OLAF is an independent community Preview;
these examples describe OLAF's authored/mapping behavior, not proof of service
enforcement. Platform rules and limits remain governed by the
[official platform contract](platform-contract.md).
Expand Down Expand Up @@ -32,6 +32,13 @@ platform impossibility claim. NotebookUtils documents its current audience surfa
ceiling, rule C6) — every `role_name` used below already fits; see
[architecture.md](architecture.md#c12) for the full format rules.

**`NOT IN` and NULL — nothing checks this for you.** A deny-list predicate silently drops rows whose
column is NULL: three-valued logic makes `col NOT IN (...)` UNKNOWN for a NULL, and `WHERE` keeps
only TRUE. The direction is over-rejection — a role sees fewer rows than intended, never more — so
it is a data-completeness bug, not an exposure one, and no rule catches it. Either write
`col NOT IN (...) OR col IS NULL`, or satisfy yourself the column is never NULL. Decide which;
do not leave it to chance. See [architecture.md](architecture.md#key-invariants).

**Autotrim:** every cell shown below is stripped of leading/trailing whitespace before `generate`/
`validate` reads it (`Parse.trim_row`), so a stray space from a copy-paste never trips a validation
error. Whitespace *inside* an `rls_condition` string literal (e.g. `Region = 'TH '`) is preserved —
Expand Down Expand Up @@ -364,7 +371,7 @@ aggregates every error above (catalog validation **plus** the No-Graph member ga
lakehouse target guard E14, the cross-role RLS×CLS guard E15, and the column-case guard E16) and
rejects once with the full list, so you fix them in a single pass.

The numeric platform-limit example is an OLAF v1.0.0 compatibility snapshot reviewed
The numeric platform-limit example is an OLAF compatibility snapshot reviewed
on 2026-08-22, not a permanent service guarantee. Verify Microsoft's current
[OneLake security limitations](https://learn.microsoft.com/en-us/fabric/onelake/security/data-access-control-model#onelake-security-limitations)
before operation. Folder and permission behavior likewise follows the current
Expand Down
4 changes: 2 additions & 2 deletions docs/control-data-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ audit log, role backups, and review artifacts contain principal identifiers and
authorization or recovery state. The reserved boundary is the four configured
control-table paths plus the complete `/Files/security` subtree.

OLAF v1.0.0 uses a fail-closed operating model for every sensitive write,
OLAF uses a fail-closed operating model for every sensitive write,
including first setup, workbook import, generate, plan, apply, reset, rollback,
and backup creation. These modes are disabled by default until the technical
DAR check passes. The per-run operator attestation is recorded, never required.
Expand Down Expand Up @@ -79,7 +79,7 @@ Fabric REST. The operator-attestation model deliberately trusts authorized
administrators and external access controls for the unobservable interval.

Organizations that cannot accept that trusted-administrator boundary should not
import real principal data or run sensitive modes in v1.0.0. Use a separately
import real principal data or run sensitive modes in this release. Use a separately
secured control store or wait for a design that provides the required isolation.

## Bootstrap sequence
Expand Down
Loading