From cb288a6e222a5787e3e47cb43bb39652df45e00b Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 14:44:47 +0700 Subject: [PATCH 1/9] Stop emitting G3 as a runtime warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit G3 warned whenever an rls_condition used NOT IN without OR IS NULL. The trap it names is real: SQL three-valued logic makes NOT IN UNKNOWN against a NULL, WHERE keeps only TRUE, and the row disappears. What made it the wrong shape for a warning is that it fired on every deny-list, in every run, for the life of a config — and it named only one of the two valid mitigations. A config that deliberately asserts the column is never NULL had no way to be clean, so its author learned to read past the warnings. Permanent warnings hide the occasional real one, which is the opposite of what a guardrail is for. The knowledge moves to docs/architecture.md, where G3 now describes the trap and both mitigations as guidance rather than being emitted per row. RLS.null_safety_warning() goes with it. Minor rather than patch: the warnings a caller sees change, and a documented helper leaves the public API. --- CHANGELOG.md | 13 ++++++++++++- docs/api/functions.md | 1 - docs/architecture.md | 7 ++++++- notebooks/olaf.ipynb | 21 +++------------------ tests/test_unit_lib.py | 12 +++++------- tests/test_unit_validation.py | 10 +--------- 6 files changed, 27 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbd296..27b79b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,23 @@ 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`. +version `1.1.0` maps to release tag `v1.1.0`. ## [Unreleased] No changes yet. +## [1.1.0] - 2026-08-27 + +### Changed + +- Guardrail **G3** is no longer a runtime warning. `NOT IN` without `OR IS NULL` used to add + one warning per row, on every run, for the life of the config — and it named only one of the two + valid mitigations, so a config that deliberately asserts the column is never NULL could never be + clean. Permanent warnings hide the occasional real one. The trap itself is real and unchanged; + it is now documented in `docs/architecture.md` as guidance rather than emitted per row. +- `RLS.null_safety_warning()` is removed along with it. + ## [1.0.0] - 2026-08-26 First public release, positioned as an independent community Preview for diff --git a/docs/api/functions.md b/docs/api/functions.md index 6abcb5b..911921f 100644 --- a/docs/api/functions.md +++ b/docs/api/functions.md @@ -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 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`, diff --git a/docs/architecture.md b/docs/architecture.md index 651bb54..2799d38 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -790,6 +790,11 @@ 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 IS NULL` = warning (three-valued logic drops NULL rows silently). +4. G3 (guidance, not enforced): `NOT IN` against a nullable column drops NULL rows silently — + SQL three-valued logic makes `NOT IN` UNKNOWN for a NULL, and `WHERE` keeps only TRUE. Guard it + with `OR IS NULL`, or assert the column is never NULL; do one of the two deliberately. + OLAF emitted this as a per-row warning until 1.1.0 and no longer does: it fired on every + deny-list in every run, named only one of the two valid mitigations, and the noise hid real + warnings. 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`. diff --git a/notebooks/olaf.ipynb b/notebooks/olaf.ipynb index e30e050..ac8e76d 100644 --- a/notebooks/olaf.ipynb +++ b/notebooks/olaf.ipynb @@ -24,7 +24,7 @@ "\n", "Cells execute in order, so every name is defined before use — do not reorder the code cells.\n", "\n", - "Naming rule for maintainers: names must say what they do — internal design codes (G3, C1, …)\n", + "Naming rule for maintainers: names must say what they do — internal design codes (C1, B2, …)\n", "appear only inside docstrings/messages as traceability references to docs/architecture.md." ] }, @@ -144,7 +144,7 @@ "source": [ "## Constants — no Spark, no side effects; the CI extract-and-exec suite runs these directly.\n", "\n", - "Module constants (member-column maps, rule/limit thresholds, regexes) plus the smallest pure helpers: the config `config_hash` staleness fingerprint, the G3 NULL-safety warning, and the RLS predicate/column-reference extractors. Everything below is importable and runs anywhere." + "Module constants (member-column maps, rule/limit thresholds, regexes) plus the smallest pure helpers: the config `config_hash` staleness fingerprint, and the RLS predicate/column-reference extractors. Everything below is importable and runs anywhere." ], "id": "0f8356efc505" }, @@ -155,7 +155,7 @@ "execution_count": null, "outputs": [], "source": [ - "__version__ = \"1.0.0\"\n", + "__version__ = \"1.1.0\"\n", "\n", "import re\n", "import json\n", @@ -1512,18 +1512,6 @@ " referenced-column extraction, unsupported-syntax/complexity checks (B2, C7, C9, C10).\"\"\"\n", "\n", " @staticmethod\n", - " def null_safety_warning(rls_condition):\n", - " \"\"\"'NOT IN' without 'OR IS NULL' is an OLAF SQL guardrail for null handling.\n", - " Validate the observed result in the intended target engine/access mode.\"\"\"\n", - " if rls_condition:\n", - " u = rls_condition.upper()\n", - " if \"NOT IN\" in u and \"IS NULL\" not in u:\n", - " return (\n", - " \"'NOT IN' without 'OR IS NULL' silently drops NULL rows (guardrail G3)\"\n", - " )\n", - " return None\n", - "\n", - " @staticmethod\n", " def to_predicate(table, condition):\n", " \"\"\"WHERE-only condition from config -> full predicate string the DAR API expects.\n", " Verify once on your live API that the schema-qualified FROM is accepted.\"\"\"\n", @@ -3092,9 +3080,6 @@ " perm = raw_perm # carried for display only — the error above blocks the config\n", " rls = row.get(\"rls_condition\") or None\n", " if rls:\n", - " w = RLS.null_safety_warning(rls)\n", - " if w:\n", - " warnings.append(f\"{rid}: {w}\")\n", " # B2 — RLS needs a table: an rls_condition with no include_tables is rejected\n", " if not eff_tables:\n", " errors.append(\n", diff --git a/tests/test_unit_lib.py b/tests/test_unit_lib.py index 8fba65c..38212df 100644 --- a/tests/test_unit_lib.py +++ b/tests/test_unit_lib.py @@ -74,11 +74,6 @@ def test_parse_table_entry_empty_part_rejected(entry): Parse.table_entry(entry) -@pytest.mark.parametrize("condition", [None, ""], ids=["None", "empty"]) -def test_null_safety_warning_empty_is_none(condition): - assert RLS.null_safety_warning(condition) is None - - # --------------------------------------------------------------------------------------------- # LibPureLogicGaps — diff 'update' verdict # --------------------------------------------------------------------------------------------- @@ -193,7 +188,10 @@ def test_exclude_side_parse_error_recorded(): assert any("schema part must be literal" in e for e in errors) -def test_rls_null_safety_warning_in_generate(): +def test_a_bare_not_in_no_longer_warns(): + """G3 was a runtime warning until 1.1.0. It fired on every deny-list a config held, forever, + and the only mitigation it named was one of two valid ones — so it became noise that hid real + warnings. The trap it described is real and now lives in docs/architecture.md instead.""" _errors, warnings = generate_warnings( [ make_row( @@ -204,7 +202,7 @@ def test_rls_null_safety_warning_in_generate(): ) ] ) - assert any("guardrail G3" in w for w in warnings) + assert warnings == [], warnings def test_rls_without_tables_b3(): diff --git a/tests/test_unit_validation.py b/tests/test_unit_validation.py index c5f030b..dd9c4c7 100644 --- a/tests/test_unit_validation.py +++ b/tests/test_unit_validation.py @@ -1,4 +1,4 @@ -"""The documented error/warning cases E1-E12 plus the retained G3 / duplicate invariants and +"""The documented error/warning cases E1-E12 plus the retained duplicate invariants and the C4-C13 rule family, as pytest functions. Ported from `olaf_test_unit.ipynb` class `ValidationRules`. @@ -946,14 +946,6 @@ def test_b3_readwrite_cannot_carry_cls(): assert any("rule B3" in e for e in errors) -def test_g3_not_in_without_is_null_warns(): - assert RLS.null_safety_warning("CategoryId NOT IN ('a')") is not None - - -def test_g3_not_in_with_or_is_null_is_clean(): - assert RLS.null_safety_warning("CategoryId NOT IN ('a') OR CategoryId IS NULL") is None - - def test_exact_duplicate_row_skipped(): row = make_row(role_name="R", include_tables="sales.orders", include_group_names="sg-analysts") grants, errors, warnings, _ = Generate.rows([row, dict(row)], CANON) From 0d25347960b476b29cde0c8f58f7cf5347223009 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 14:56:25 +0700 Subject: [PATCH 2/9] Point the version-bearing docs at 1.1.0 The release notes moved to 1.1.0 but every doc still opened with "OLAF v1.0.0 is an independent community Preview", and the framework_version examples still showed what 1.0.0 stamped. Left alone deliberately, because they are true as written and changing them would make them false: CHANGELOG [1.0.0] the 1.0.0 release, dated roadmap.md what the first public candidate added test_mock_pipeline.py the 1.0.0 hash algorithm, kept for back-compat test_unit_apply.py what 1.0.0 solved olaf.ipynb hash comments a 1.0.0-stamped plan row still opens the gate CONTRIBUTING.md an example of the runtime-to-tag rule test.yml / test_public_release.py archive prefixes, mechanical .superpowers/ the 1.0.0 release record --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- SECURITY.md | 4 ++-- SUPPORT.md | 2 +- configs/README.md | 2 +- docs/README.md | 2 +- docs/api/Audit.md | 4 ++-- docs/api/FabricClient.md | 2 +- docs/architecture.md | 6 +++--- docs/config-examples.md | 4 ++-- docs/control-data-security.md | 4 ++-- docs/data-model.md | 4 ++-- docs/fabric-import.md | 2 +- docs/live-smoke-test.md | 2 +- docs/modes.md | 2 +- docs/platform-contract.md | 4 ++-- docs/roadmap.md | 2 +- docs/runbook.md | 4 ++-- notebooks/README.md | 2 +- tests/olaf_test_smoke.ipynb | 2 +- 19 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 81134b3..c2f5e70 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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): e.g. 1.1.0 - Where it ran: Fabric notebook / pipeline / the pytest suite locally - Python version, if running the suite locally diff --git a/SECURITY.md b/SECURITY.md index 16bbdd8..7925f50 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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 v1.1.0 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). @@ -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 v1.1.0. ## Operational recommendations diff --git a/SUPPORT.md b/SUPPORT.md index 801c137..b26772c 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -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 v1.1.0 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). diff --git a/configs/README.md b/configs/README.md index 7710d34..3c8855b 100644 --- a/configs/README.md +++ b/configs/README.md @@ -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 OLAF v1.1.0 and points to the maintained [platform contract](../docs/platform-contract.md). Microsoft's service rules and limits may change; the official links remain authoritative. diff --git a/docs/README.md b/docs/README.md index e2c372b..2564cd6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 v1.1.0 is an independent community Preview for evaluation and development. Read the platform and control-data boundaries before using real principal data. ## Start here diff --git a/docs/api/Audit.md b/docs/api/Audit.md index 2b4da14..f096449 100644 --- a/docs/api/Audit.md +++ b/docs/api/Audit.md @@ -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 v1.1.0 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. @@ -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} ``` diff --git a/docs/api/FabricClient.md b/docs/api/FabricClient.md index b6f54a3..ed62593 100644 --- a/docs/api/FabricClient.md +++ b/docs/api/FabricClient.md @@ -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 v1.1.0 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). diff --git a/docs/architecture.md b/docs/architecture.md index 2799d38..a854cb5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture — OneLake Access Framework -OLAF v1.0.0 is an independent community Preview for evaluation and development, +OLAF v1.1.0 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 @@ -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 v1.1.0'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 @@ -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 v1.1.0 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). diff --git a/docs/config-examples.md b/docs/config-examples.md index aa2ac44..17379a2 100644 --- a/docs/config-examples.md +++ b/docs/config-examples.md @@ -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 v1.1.0 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). @@ -364,7 +364,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 v1.1.0 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 diff --git a/docs/control-data-security.md b/docs/control-data-security.md index 6c32d38..dd3dad7 100644 --- a/docs/control-data-security.md +++ b/docs/control-data-security.md @@ -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 v1.1.0 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. @@ -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 v1.1.0. Use a separately secured control store or wait for a design that provides the required isolation. ## Bootstrap sequence diff --git a/docs/data-model.md b/docs/data-model.md index aaf3019..f90ff11 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -128,7 +128,7 @@ latest generation; history lives in `onelake_security_log`. This is the **only** | `generated_at` | timestamp | — | UTC ISO-8601 | No | `2026-07-11T12:00:00+00:00` | `generate` | audit display; sufficient proxy for "when generate ran" (replaces the dropped `catalog_snapshot_at`) | | `config_hash` | string | — | 16-char hex — `sha256(json.dumps(rows, sort_keys=True))[:16]` over the active rows **projected to `CONFIG_AUTHOR_COLUMNS`** (foreign columns on the physical table never enter the fingerprint) | No | `284ae40f8b47a294` | `generate` | `plan`/`apply` staleness guard (content-based — a no-op config rewrite does not invalidate a pending plan); generation-trace queries | | `config_version` | bigint | — | Delta commit version of `onelake_security_config` at generate time; `null` if the config table isn't Delta or `DESCRIBE HISTORY` is unavailable | Yes | `42` | `generate` (`SELECT max(version) FROM (DESCRIBE HISTORY onelake_security_config)`) | generation-trace queries (`VERSION AS OF`), generation timeline, `show` config_version annotation | -| `framework_version` | string | — | semver of the library (`__version__`) | No | `1.0.0` | `generate` | provenance display, compatibility checks | +| `framework_version` | string | — | semver of the library (`__version__`) | No | `1.1.0` | `generate` | provenance display, compatibility checks | 23 columns. The `member_*_names` carry the human-facing effective set; the `member_*_ids` carry the objectIds resolved from `onelake_security_member` (No-Graph; aligned 1:1) that `DAR.to_role` @@ -178,7 +178,7 @@ string deliberately (see the physical note above). | `tenant_id` | string | — | Entra **tenant GUID** of the run (blank on `setup`, which resolves no tenant) | Yes | `00000…` | every mode that logs | tenant-scoped audit queries | | `mapping_hash` | string | — | 16-char content fingerprint of the mapping lock-file at run time | Yes | `9f8e7d6c…` | `generate`/`plan`/`apply` | the saved-plan gate (`apply` matches the `plan` record on `config_hash` **and** `mapping_hash`, binding the plan to the exact mapping generation it reviewed); mapping-generation trace (config → mapping → run) | | `mapping_version` | bigint | — | Delta version of `onelake_security_mapping`, or null | Yes | `7` | `generate`/`plan`/`apply` | mapping-generation timeline | -| `framework_version` | string | — | semver of the framework (`__version__`) that wrote the row — the **run-time** code version (may differ from the mapping's generate-time version) | No | `1.0.0` | every mode that logs | which code version ran — completes the config → mapping → code → run provenance chain | +| `framework_version` | string | — | semver of the framework (`__version__`) that wrote the row — the **run-time** code version (may differ from the mapping's generate-time version) | No | `1.1.0` | every mode that logs | which code version ran — completes the config → mapping → code → run provenance chain | 27 columns. `member_name` (display name) and `member_id` (resolved objectId) ride together on every grant-grain row; `show`'s enrichment joins the live DAR (which exposes objectIds) on `member_id`. diff --git a/docs/fabric-import.md b/docs/fabric-import.md index 2bd5842..4760072 100644 --- a/docs/fabric-import.md +++ b/docs/fabric-import.md @@ -112,7 +112,7 @@ Deployment pipelines are documented separately in [Fabric deployment pipelines](https://learn.microsoft.com/en-us/fabric/cicd/deployment-pipelines/intro-to-deployment-pipelines). Use local CI for deterministic tests. Treat any external smoke run as separately authorized, -redacted evidence bound to the exact commit SHA. Do not promote OLAF v1.0.0 Preview as a +redacted evidence bound to the exact commit SHA. Do not promote OLAF v1.1.0 Preview as a production-ready security control. ## Which path when diff --git a/docs/live-smoke-test.md b/docs/live-smoke-test.md index d453e2f..9e81078 100644 --- a/docs/live-smoke-test.md +++ b/docs/live-smoke-test.md @@ -1,6 +1,6 @@ # Optional live Fabric validation protocol -The public v1.0.0 release has fixture-based CI evidence only. It does **not** claim +The public v1.1.0 release has fixture-based CI evidence only. It does **not** claim that the release commit was run against a live Microsoft Fabric tenant. This page defines the minimum record for a future, separately authorized live diff --git a/docs/modes.md b/docs/modes.md index 1ca7e54..e77e2eb 100644 --- a/docs/modes.md +++ b/docs/modes.md @@ -1,6 +1,6 @@ # Mode manual -OLAF v1.0.0 is an independent community Preview for evaluation and development. +OLAF v1.1.0 is an independent community Preview for evaluation and development. The bulk DAR mutation endpoint used by `apply`, `reset`, and rollback's apply leg is officially Preview and is not a production contract: [Microsoft REST reference](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles). diff --git a/docs/platform-contract.md b/docs/platform-contract.md index 064d380..83a47de 100644 --- a/docs/platform-contract.md +++ b/docs/platform-contract.md @@ -8,7 +8,7 @@ platform with which OLAF is intended to interoperate; see Microsoft's ## Release status -OLAF v1.0.0 is a **community Preview for evaluation and development**, not a +OLAF v1.1.0 is a **community Preview for evaluation and development**, not a production-ready security product. Its mutating path uses the bulk Data Access Roles (DAR) `PUT`, which Microsoft labels **Preview** and says is not recommended for production use. Review that endpoint's current status before every deployment: @@ -115,7 +115,7 @@ or remediated: ## Evidence status -The public v1.0.0 release has automated fixture-based CI evidence. It does **not** +The public v1.1.0 release has automated fixture-based CI evidence. It does **not** claim exact-release-SHA verification against a live Fabric tenant. Any future live result must state the release SHA, Fabric Runtime, API date, target class, test scope, and cleanup result, and must be described as a dated observation rather than a diff --git a/docs/roadmap.md b/docs/roadmap.md index 34f7770..bbc104f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap -OLAF v1.0.0 is a community Preview. Roadmap items are options, not promises or +OLAF v1.1.0 is a community Preview. Roadmap items are options, not promises or release dates. They must not weaken the control-data boundary or turn an observed service behavior into a Microsoft platform contract. diff --git a/docs/runbook.md b/docs/runbook.md index d2b4425..c636937 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -1,6 +1,6 @@ # Runbook — setup, config, and operations -OLAF v1.0.0 is an independent community Preview for evaluation and development, +OLAF v1.1.0 is an independent community Preview for evaluation and development, not a production-ready security product. Its mutating path depends on Microsoft's Preview bulk DAR endpoint: [Create or update data access roles](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles). @@ -173,7 +173,7 @@ Before a real apply/reset, OLAF records prepared intent and a backup pointer. If operation becomes ambiguous, preserve the incident sentinel, prepared row, and backup. Stop later sensitive modes; do not automatically restore over a concurrent change. -OLAF v1.0.0 has **no supported public backup-replay method**. Do not call +OLAF v1.1.0 has **no supported public backup-replay method**. Do not call `FabricClient.put_roles()` directly from a public workflow and do not treat a backup pointer as a local file path. A low-level call bypasses the mandatory sentinel and control-data gates, and no high-level guarded recovery method exists yet. diff --git a/notebooks/README.md b/notebooks/README.md index 12962be..1648a2c 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -1,6 +1,6 @@ # notebooks/ -**One self-contained runtime, plus three optional notebooks.** OLAF v1.0.0 is +**One self-contained runtime, plus three optional notebooks.** OLAF v1.1.0 is an independent community Preview for evaluation and development. `olaf` is the only file you *need* — it's the whole tool a user or pipeline runs. `olaf_master_workflow` is the recommended starting point: the runtime driven end to end, one stage per cell. `olaf_runner` is the pipeline wrapper — diff --git a/tests/olaf_test_smoke.ipynb b/tests/olaf_test_smoke.ipynb index 2996d14..ad287e5 100644 --- a/tests/olaf_test_smoke.ipynb +++ b/tests/olaf_test_smoke.ipynb @@ -7,7 +7,7 @@ "source": [ "# OLAF external smoke protocol — no embedded results\n", "\n", - "> **Community Preview:** this notebook is an authorization-only protocol for an isolated non-production Microsoft Fabric environment. It contains no proof that OLAF v1.0.0 or any repository commit has been executed against Fabric. OLAF is an independent community project and is not affiliated with or endorsed by Microsoft.\n", + "> **Community Preview:** this notebook is an authorization-only protocol for an isolated non-production Microsoft Fabric environment. It contains no proof that OLAF v1.1.0 or any repository commit has been executed against Fabric. OLAF is an independent community project and is not affiliated with or endorsed by Microsoft.\n", "\n", "Do not upload or execute this notebook without explicit authorization, an external-access review, synthetic fixtures, a recovery plan, and a result-redaction plan. See [docs/live-smoke-test.md](../docs/live-smoke-test.md).\n" ] From 759e434b248280e5cd308c1f41cc11aa36dc2e52 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 15:24:17 +0700 Subject: [PATCH 3/9] Keep one Run-All guard in the cookbook, not two The cookbook opened with two guards that do the same thing. The short one came with the 1.0.0 release; the longer one was added by 301c24c without noticing it, so both shipped. Kept the longer one. It says what would actually happen on a Run All, names the paths that trigger it (Run All, a Data Factory pipeline, notebookutils.notebook.run), and tells the reader how to use the notebook instead. Deleting the short one also makes its own claim true: it is now the first code cell, so "this guard runs FIRST" describes where it is rather than where it wishes it were. --- notebooks/olaf_cookbook.ipynb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/notebooks/olaf_cookbook.ipynb b/notebooks/olaf_cookbook.ipynb index 865e03e..aaefa1c 100644 --- a/notebooks/olaf_cookbook.ipynb +++ b/notebooks/olaf_cookbook.ipynb @@ -29,22 +29,6 @@ "(the raw dict stays at `OLAF.last_result`)." ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "9540642a", - "metadata": {}, - "outputs": [], - "source": [ - "# Safety guard: Run All stops here. Every example below reaches a live lakehouse, so\n", - "# they are meant to be read first and run one at a time, deliberately.\n", - "import notebookutils\n", - "\n", - "notebookutils.notebook.exit(\n", - " \"OLAF cookbook is examples-only; review and run individual cells deliberately\"\n", - ")" - ] - }, { "cell_type": "markdown", "id": "toc", From bc9ba7eeb87bf735902a21766f3f2ad937903498 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 15:32:59 +0700 Subject: [PATCH 4/9] Stop naming the version in prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every doc opened with "OLAF v1.x.x is an independent community Preview", so a release meant editing eighteen files to say a number that __version__ and the CHANGELOG already carry. That is how they came to disagree in the first place. The prose now says "OLAF". Two places still carry a number on purpose: the CHANGELOG, which is versioned by definition, and __version__ itself. The three sample values (data-model.md, Audit.md) stay concrete because an example needs to show a real shape, and nobody reads sample output as a claim about today. Statements about WHEN something changed keep their number — "a runtime warning until 1.1.0" is history, and dropping the version would delete the fact. --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- CHANGELOG.md | 4 ++-- SECURITY.md | 4 ++-- SUPPORT.md | 2 +- configs/README.md | 2 +- docs/README.md | 2 +- docs/api/Audit.md | 2 +- docs/api/FabricClient.md | 2 +- docs/architecture.md | 6 +++--- docs/config-examples.md | 4 ++-- docs/control-data-security.md | 4 ++-- docs/fabric-import.md | 2 +- docs/live-smoke-test.md | 2 +- docs/modes.md | 2 +- docs/platform-contract.md | 4 ++-- docs/roadmap.md | 2 +- docs/runbook.md | 4 ++-- notebooks/README.md | 2 +- tests/olaf_test_smoke.ipynb | 2 +- 19 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c2f5e70..a0e40a4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,7 +30,7 @@ The shortest path you know. The most useful ingredients, sanitized: **Environment** -- OLAF version (`__version__`, printed at load): e.g. 1.1.0 +- OLAF version (`__version__`, printed at load): - Where it ran: Fabric notebook / pipeline / the pytest suite locally - Python version, if running the suite locally diff --git a/CHANGELOG.md b/CHANGELOG.md index 27b79b1..153b0ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # 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.1.0` maps to release tag `v1.1.0`. +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) structure. The runtime's +`__version__` maps to the release tag `v{__version__}`. ## [Unreleased] diff --git a/SECURITY.md b/SECURITY.md index 7925f50..87813b1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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.1.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). @@ -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.1.0. +do not import real principal data or run sensitive modes in this release. ## Operational recommendations diff --git a/SUPPORT.md b/SUPPORT.md index b26772c..7dd0de7 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -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.1.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). diff --git a/configs/README.md b/configs/README.md index 3c8855b..fabe3ee 100644 --- a/configs/README.md +++ b/configs/README.md @@ -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.1.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. diff --git a/docs/README.md b/docs/README.md index 2564cd6..ecfcac2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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.1.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 diff --git a/docs/api/Audit.md b/docs/api/Audit.md index f096449..cdf7b69 100644 --- a/docs/api/Audit.md +++ b/docs/api/Audit.md @@ -2,7 +2,7 @@ Back to [API index](../api-reference.md) - [docs](../README.md). -> OLAF v1.1.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. diff --git a/docs/api/FabricClient.md b/docs/api/FabricClient.md index ed62593..d278505 100644 --- a/docs/api/FabricClient.md +++ b/docs/api/FabricClient.md @@ -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.1.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). diff --git a/docs/architecture.md b/docs/architecture.md index a854cb5..70ef8c0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture — OneLake Access Framework -OLAF v1.1.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 @@ -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.1.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 @@ -762,7 +762,7 @@ Two consequences worth stating plainly: ### Limits (fail at generate/plan, not at apply) -OLAF v1.1.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). diff --git a/docs/config-examples.md b/docs/config-examples.md index 17379a2..9b01c05 100644 --- a/docs/config-examples.md +++ b/docs/config-examples.md @@ -1,6 +1,6 @@ # Config cookbook — worked config → mapping examples -All values here are synthetic. OLAF v1.1.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). @@ -364,7 +364,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.1.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 diff --git a/docs/control-data-security.md b/docs/control-data-security.md index dd3dad7..0ce85bc 100644 --- a/docs/control-data-security.md +++ b/docs/control-data-security.md @@ -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.1.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. @@ -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.1.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 diff --git a/docs/fabric-import.md b/docs/fabric-import.md index 4760072..bb68b81 100644 --- a/docs/fabric-import.md +++ b/docs/fabric-import.md @@ -112,7 +112,7 @@ Deployment pipelines are documented separately in [Fabric deployment pipelines](https://learn.microsoft.com/en-us/fabric/cicd/deployment-pipelines/intro-to-deployment-pipelines). Use local CI for deterministic tests. Treat any external smoke run as separately authorized, -redacted evidence bound to the exact commit SHA. Do not promote OLAF v1.1.0 Preview as a +redacted evidence bound to the exact commit SHA. Do not promote the OLAF Preview as a production-ready security control. ## Which path when diff --git a/docs/live-smoke-test.md b/docs/live-smoke-test.md index 9e81078..3763912 100644 --- a/docs/live-smoke-test.md +++ b/docs/live-smoke-test.md @@ -1,6 +1,6 @@ # Optional live Fabric validation protocol -The public v1.1.0 release has fixture-based CI evidence only. It does **not** claim +The public release has fixture-based CI evidence only. It does **not** claim that the release commit was run against a live Microsoft Fabric tenant. This page defines the minimum record for a future, separately authorized live diff --git a/docs/modes.md b/docs/modes.md index e77e2eb..0c2c15e 100644 --- a/docs/modes.md +++ b/docs/modes.md @@ -1,6 +1,6 @@ # Mode manual -OLAF v1.1.0 is an independent community Preview for evaluation and development. +OLAF is an independent community Preview for evaluation and development. The bulk DAR mutation endpoint used by `apply`, `reset`, and rollback's apply leg is officially Preview and is not a production contract: [Microsoft REST reference](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles). diff --git a/docs/platform-contract.md b/docs/platform-contract.md index 83a47de..3f88441 100644 --- a/docs/platform-contract.md +++ b/docs/platform-contract.md @@ -8,7 +8,7 @@ platform with which OLAF is intended to interoperate; see Microsoft's ## Release status -OLAF v1.1.0 is a **community Preview for evaluation and development**, not a +OLAF is a **community Preview for evaluation and development**, not a production-ready security product. Its mutating path uses the bulk Data Access Roles (DAR) `PUT`, which Microsoft labels **Preview** and says is not recommended for production use. Review that endpoint's current status before every deployment: @@ -115,7 +115,7 @@ or remediated: ## Evidence status -The public v1.1.0 release has automated fixture-based CI evidence. It does **not** +The public release has automated fixture-based CI evidence. It does **not** claim exact-release-SHA verification against a live Fabric tenant. Any future live result must state the release SHA, Fabric Runtime, API date, target class, test scope, and cleanup result, and must be described as a dated observation rather than a diff --git a/docs/roadmap.md b/docs/roadmap.md index bbc104f..588cc47 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap -OLAF v1.1.0 is a community Preview. Roadmap items are options, not promises or +OLAF is a community Preview. Roadmap items are options, not promises or release dates. They must not weaken the control-data boundary or turn an observed service behavior into a Microsoft platform contract. diff --git a/docs/runbook.md b/docs/runbook.md index c636937..a7b6a57 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -1,6 +1,6 @@ # Runbook — setup, config, and operations -OLAF v1.1.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. Its mutating path depends on Microsoft's Preview bulk DAR endpoint: [Create or update data access roles](https://learn.microsoft.com/en-us/rest/api/fabric/core/onelake-data-access-security/create-or-update-data-access-roles). @@ -173,7 +173,7 @@ Before a real apply/reset, OLAF records prepared intent and a backup pointer. If operation becomes ambiguous, preserve the incident sentinel, prepared row, and backup. Stop later sensitive modes; do not automatically restore over a concurrent change. -OLAF v1.1.0 has **no supported public backup-replay method**. Do not call +OLAF has **no supported public backup-replay method**. Do not call `FabricClient.put_roles()` directly from a public workflow and do not treat a backup pointer as a local file path. A low-level call bypasses the mandatory sentinel and control-data gates, and no high-level guarded recovery method exists yet. diff --git a/notebooks/README.md b/notebooks/README.md index 1648a2c..ae6521e 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -1,6 +1,6 @@ # notebooks/ -**One self-contained runtime, plus three optional notebooks.** OLAF v1.1.0 is +**One self-contained runtime, plus three optional notebooks.** OLAF is an independent community Preview for evaluation and development. `olaf` is the only file you *need* — it's the whole tool a user or pipeline runs. `olaf_master_workflow` is the recommended starting point: the runtime driven end to end, one stage per cell. `olaf_runner` is the pipeline wrapper — diff --git a/tests/olaf_test_smoke.ipynb b/tests/olaf_test_smoke.ipynb index ad287e5..0b105ba 100644 --- a/tests/olaf_test_smoke.ipynb +++ b/tests/olaf_test_smoke.ipynb @@ -7,7 +7,7 @@ "source": [ "# OLAF external smoke protocol — no embedded results\n", "\n", - "> **Community Preview:** this notebook is an authorization-only protocol for an isolated non-production Microsoft Fabric environment. It contains no proof that OLAF v1.1.0 or any repository commit has been executed against Fabric. OLAF is an independent community project and is not affiliated with or endorsed by Microsoft.\n", + "> **Community Preview:** this notebook is an authorization-only protocol for an isolated non-production Microsoft Fabric environment. It contains no proof that OLAF or any repository commit has been executed against Fabric. OLAF is an independent community project and is not affiliated with or endorsed by Microsoft.\n", "\n", "Do not upload or execute this notebook without explicit authorization, an external-access review, synthetic fixtures, a recovery plan, and a result-redaction plan. See [docs/live-smoke-test.md](../docs/live-smoke-test.md).\n" ] From 5c310326e4639ac74035d4d1fe214fd5864a2d15 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 16:03:10 +0700 Subject: [PATCH 5/9] Drop the RLS import that lost its last user Removing the G3 tests left RLS imported and unused in test_unit_lib.py. Tests do not fail on an unused import, so the local run stayed green and CI caught it. The lesson is the run, not the import: the repo lints notebooks and Python together through scripts/lint.sh, and I pushed twice without running it. --- tests/test_unit_lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_unit_lib.py b/tests/test_unit_lib.py index 38212df..7d9f346 100644 --- a/tests/test_unit_lib.py +++ b/tests/test_unit_lib.py @@ -13,7 +13,6 @@ CONFIG_AUTHOR_COLUMNS, DAR, MAPPING_COLUMNS, - RLS, Catalog, Generate, Member, From e5fdff2ecd235e3ba406663c46c9364ca7e65709 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 16:26:43 +0700 Subject: [PATCH 6/9] Carry the G3 removal into the surfaces a config author actually opens Review found the change stopped at the code and the internal design doc. Three places still spoke for the deleted check, and one sentence this branch wrote was false. configs/onelake_security.xlsx the starter workbook's own note said "Every NOT IN needs OR IS NULL" -- the single-mitigation framing this branch removed as wrong, stated more strongly, on the artifact users copy from configs/README.md said the workbook is "versioned for this OLAF release" while the workbook stamped v1.0.0 in three cells. Both now name no version docs/api/functions.md listed RLS among namespaces hosting callable helpers; its only row was the one removed The CHANGELOG now says the thing that actually justifies the deletion, which the first draft did not. The check was a substring test on the raw condition rather than the literal-stripping lexer C9/C11/C13 share, so it warned on "status = 'CANNOT INVOICE'" -- no NOT IN operator anywhere -- and stayed silent on "region NOT IN ('a') AND type IS NULL", muted by a different column. Wrong in both directions, and unfixable without the lexer it never used. It also states the direction of failure, which CONTRIBUTING requires of anything touching validation and the first draft omitted: NOT IN against a NULL over-rejects. It removes rows a role should see and cannot widen access. The workbook is patched as a zip, one member at a time. openpyxl's save() rebuilds the package and re-adds docProps, and the release gate rejects that as person metadata -- caught by test_candidate_tree_passes_the_release_gate, not by me. --- CHANGELOG.md | 24 ++++++++++++++++++------ CONTRIBUTING.md | 2 +- configs/onelake_security.xlsx | Bin 10565 -> 10709 bytes docs/api/functions.md | 2 +- docs/architecture.md | 7 +------ notebooks/olaf.ipynb | 2 +- notebooks/olaf_cookbook.ipynb | 2 +- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 153b0ec..a9d5d30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,24 @@ No changes yet. ### Changed -- Guardrail **G3** is no longer a runtime warning. `NOT IN` without `OR IS NULL` used to add - one warning per row, on every run, for the life of the config — and it named only one of the two - valid mitigations, so a config that deliberately asserts the column is never NULL could never be - clean. Permanent warnings hide the occasional real one. The trap itself is real and unchanged; - it is now documented in `docs/architecture.md` as guidance rather than emitted per row. -- `RLS.null_safety_warning()` is removed along with it. +- 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 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. ## [1.0.0] - 2026-08-26 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f31604..aa51f42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -147,7 +147,7 @@ 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__}`. 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. diff --git a/configs/onelake_security.xlsx b/configs/onelake_security.xlsx index b90040ba70a5ba1164f928efd41792fc60c60a92..1977e8be589e92c2f8cb2a6443887239eb341793 100644 GIT binary patch delta 9598 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 delta 9403 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 QGr%fY8IeIu{}}cE05pAKy#N3J diff --git a/docs/api/functions.md b/docs/api/functions.md index 911921f..9069523 100644 --- a/docs/api/functions.md +++ b/docs/api/functions.md @@ -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. diff --git a/docs/architecture.md b/docs/architecture.md index 70ef8c0..9ba6fe7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -790,11 +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 (guidance, not enforced): `NOT IN` against a nullable column drops NULL rows silently — - SQL three-valued logic makes `NOT IN` UNKNOWN for a NULL, and `WHERE` keeps only TRUE. Guard it - with `OR IS NULL`, or assert the column is never NULL; do one of the two deliberately. - OLAF emitted this as a per-row warning until 1.1.0 and no longer does: it fired on every - deny-list in every run, named only one of the two valid mitigations, and the noise hid real - warnings. +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 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`. diff --git a/notebooks/olaf.ipynb b/notebooks/olaf.ipynb index ac8e76d..cc0096b 100644 --- a/notebooks/olaf.ipynb +++ b/notebooks/olaf.ipynb @@ -144,7 +144,7 @@ "source": [ "## Constants — no Spark, no side effects; the CI extract-and-exec suite runs these directly.\n", "\n", - "Module constants (member-column maps, rule/limit thresholds, regexes) plus the smallest pure helpers: the config `config_hash` staleness fingerprint, and the RLS predicate/column-reference extractors. Everything below is importable and runs anywhere." + "Module constants (member-column maps, rule/limit thresholds, regexes) plus the smallest pure helpers: the config `config_hash` staleness fingerprint and the RLS predicate/column-reference extractors. Everything below is importable and runs anywhere." ], "id": "0f8356efc505" }, diff --git a/notebooks/olaf_cookbook.ipynb b/notebooks/olaf_cookbook.ipynb index aaefa1c..2f4f596 100644 --- a/notebooks/olaf_cookbook.ipynb +++ b/notebooks/olaf_cookbook.ipynb @@ -22,7 +22,7 @@ "> control tables, so they only execute inside a Fabric workspace with `olaf` imported\n", "> and a lakehouse attached. The CI test + coverage harness targets `olaf.ipynb` only;\n", "> this cookbook is never executed or measured. Values below (`priya@contoso.com`, `SalesReaders`,\n", - "> `2026-07-15`, …) are illustrative placeholders. **The first cell is a guard** — running this notebook whole (Run All / a pipeline / `notebook.run`) exits immediately without touching anything; run the cells you want **one at a time**.\n", + "> `2026-07-15`, …) are illustrative placeholders. **The first code cell is a guard** — running this notebook whole (Run All / a pipeline / `notebook.run`) exits immediately without touching anything; run the cells you want **one at a time**.\n", "\n", "Every `OLAF.*` method returns a **Spark DataFrame** for an easy `display(...)` — a query method\n", "returns its result table; an ops method returns a compact DataFrame *view* of the outcome envelope\n", From 0b80d8123b32aeec8f547709f6e07ea4e6ffb62d Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 20:20:05 +0700 Subject: [PATCH 7/9] Put the NULL trap where a first-time author walks, and pin the tag identity Round two of review. The removal was complete in code and correct in the design doc, and still left three ways to get hurt. THE ROUTE, NOT THE KNOWLEDGE. Following docs/README.md's own "Start here" trail, a user authoring their first NOT IN reads config-examples.md and runbook.md section 2 -- and meets the trap in neither. It survives only in architecture.md's Key invariants, inside the group that same index calls "reference material you'll come back to." Before this branch there was a runtime backstop: generate printed the warning at the moment of authoring, and no navigation was involved. Deleting it without touching the authoring path traded a discovery mechanism for a footnote. Both docs now carry it, and both say the direction: over-rejection, a role sees fewer rows and never more. THE TAG. v1.0.0 was signed with a sanitized maintainer identity. This repo's git config -- local and global -- is a personal address, and check_public_release.py lists that domain under PERSONAL_EMAIL. The gate has tree and archive modes only; neither reads a tag object. So a plain `git tag -a` stamps a personal address into a public, immutable object with nothing to stop it. CONTRIBUTING.md now carries the tag command, and reads the identity off the previous tag rather than naming it -- writing the address down is itself a finding (APPROVED_IDENTITY_ CONTEXT), which is how the first draft of this paragraph failed the gate. THE GUARD'S MARGIN. Removing the duplicated Run-All guard was right, but it took the redundancy with it: one unpinned cell now stands between Run All and live operations, and nothing asserted it existed. test_the_cookbook_guard_still_stands_ before_every_live_example pins the property -- exactly one guard, first code cell, before %run. Also: the release-evidence archive in test.yml was still named olaf-1.0.0; it now reads the version from the runtime, so the evidence cannot be filed under a version the tree is not. The MINOR bump over a public-API removal is recorded in the CHANGELOG as a deviation with its reasons, rather than left to look like an oversight. And the workbook note is back to one row's worth of text -- the longer wording was clipped by row 3's customHeight, so half of it was invisible in the one place a config author actually reads it. 1654 tests pass. Release gate PASS. lint clean. --- .github/workflows/test.yml | 24 +++++++++++++++++------- CHANGELOG.md | 6 +++++- CONTRIBUTING.md | 15 ++++++++++++++- configs/onelake_security.xlsx | Bin 10709 -> 10654 bytes docs/config-examples.md | 7 +++++++ docs/runbook.md | 6 ++++++ tests/test_notebook_contracts.py | 31 +++++++++++++++++++++++++++++++ 7 files changed, 80 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a0161b..12c6e74 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,17 +103,27 @@ 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. Read it from the runtime instead of freezing it here. + version="$(python - <<'PY' +import json, re +nb = json.load(open("notebooks/olaf.ipynb", encoding="utf-8")) +src = "\n".join("".join(c.get("source", [])) for c in nb["cells"]) +print(re.search(r'__version__\s*=\s*"([^"]+)"', src).group(1)) +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 . diff --git a/CHANGELOG.md b/CHANGELOG.md index a9d5d30..3a7c8e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,11 @@ No changes yet. ### 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. + 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. ## [1.0.0] - 2026-08-26 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa51f42..b86265f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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__}`. 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. diff --git a/configs/onelake_security.xlsx b/configs/onelake_security.xlsx index 1977e8be589e92c2f8cb2a6443887239eb341793..49c31f285cb69db386e6628655e02c0d3ba4026f 100644 GIT binary patch delta 1325 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 IS NULL`, or satisfy +yourself the column is never NULL. See +[config-examples.md](config-examples.md) and [architecture.md](architecture.md#key-invariants). + The authored tables are: - `config`: role name, target lakehouse label, table/folder includes and excludes, diff --git a/tests/test_notebook_contracts.py b/tests/test_notebook_contracts.py index e068995..78a5db5 100644 --- a/tests/test_notebook_contracts.py +++ b/tests/test_notebook_contracts.py @@ -701,3 +701,34 @@ def test_no_shipped_file_still_names_the_retired_since_accessor(): assert not offenders, "retired `since` accessor/column still named in:\n " + "\n ".join( offenders ) + + +def test_the_cookbook_guard_still_stands_before_every_live_example(): + """olaf_cookbook runs live operations against the attached lakehouse, and one cell is all that + stops a Run All from executing them for real. + + It used to carry TWO guards — the 1.0.0 release added one and a later commit added another + without noticing it. The duplicate was removed, which is correct, but it also took the margin: + what was two independent stops is now one, and nothing pinned either of them. A cell reordered + above the survivor would un-guard a notebook whose own text says every cell below reaches a + live lakehouse, and every gate would stay green while it happened. + + Pin the property, not the prose: exactly one guard, it is the FIRST code cell, and it precedes + the `%run` that loads the runtime. + """ + cookbook = REPO_ROOT / "notebooks" / "olaf_cookbook.ipynb" + cells = code_cells(cookbook) + guards = [i for i, text in cells if "notebookutils.notebook.exit(" in text] + assert len(guards) == 1, f"expected exactly one Run-All guard, found {len(guards)} at {guards}" + + first_code_index = cells[0][0] + assert guards[0] == first_code_index, ( + f"the guard is at cell {guards[0]} but the first code cell is {first_code_index} — " + "a live example now runs before it" + ) + + # the magic itself, not a mention of it — the guard cell names %run in its own comment, + # and this repo already pins a %run cell to hold nothing else + runs = [i for i, text in cells if text.lstrip().startswith("%run")] + assert runs, "the cookbook no longer loads the runtime" + assert guards[0] < runs[0], "the guard must exit before %run loads anything" From cb7a8dcf9139c48dc9032a58e7dc968363f66c7f Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 20:27:17 +0700 Subject: [PATCH 8/9] Read the archive version from a script, not a heredoc that ate the workflow The previous commit computed the release-archive prefix with a Python heredoc inside the release_hygiene `run:` block. The heredoc body sits at column 0; the block scalar is indented ten. YAML ends a literal block at the first line indented less than the block, so the workflow file stopped being parseable at `import json, re` -- and GitHub does not fail a run it cannot read. It ran ZERO jobs and reported "no checks reported on the branch", which reads like CI has not started yet rather than like CI is broken. Branch protection then blocked the PR waiting for five checks that could never appear. That is the same shape as the bug this release is about: a check that is absent looks exactly like a check that passed. scripts/print_version.py now reads `__version__` out of the notebook and prints it. The workflow calls it. No inline script, one source of truth, and the version is still not frozen into the evidence name. Verified rather than assumed: the workflow parses (4 jobs), the helper prints 1.1.0, two archives at that prefix are byte-identical, and the archive gate passes on it. 1654 tests, tree gate PASS, lint clean. --- .github/workflows/test.yml | 11 +++-------- scripts/print_version.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 scripts/print_version.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12c6e74..348faaa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -104,14 +104,9 @@ jobs: run: | set -euo pipefail # The prefix is release evidence, so it must not name a version this tree may no - # longer be. Read it from the runtime instead of freezing it here. - version="$(python - <<'PY' -import json, re -nb = json.load(open("notebooks/olaf.ipynb", encoding="utf-8")) -src = "\n".join("".join(c.get("source", [])) for c in nb["cells"]) -print(re.search(r'__version__\s*=\s*"([^"]+)"', src).group(1)) -PY -)" + # 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" diff --git a/scripts/print_version.py b/scripts/print_version.py new file mode 100644 index 0000000..db70f4c --- /dev/null +++ b/scripts/print_version.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Print the runtime's `__version__`, read from the notebook that defines it. + +The release-evidence archive is named after the version, and naming it in the workflow froze it: +the v1.1.0 evidence would have been filed under `olaf-1.0.0`. Reading it here keeps one source of +truth and keeps the workflow free of an inline script -- an earlier attempt embedded a heredoc in +the `run:` block, whose body sat at column 0 and silently terminated the YAML literal, so GitHub +could not parse the workflow and ran zero jobs while reporting only "no checks reported". +""" + +import json +import pathlib +import re +import sys + +NOTEBOOK = pathlib.Path(__file__).resolve().parent.parent / "notebooks" / "olaf.ipynb" + + +def main() -> int: + cells = json.loads(NOTEBOOK.read_text(encoding="utf-8"))["cells"] + source = "\n".join("".join(cell.get("source", [])) for cell in cells) + found = re.search(r'__version__\s*=\s*"([^"]+)"', source) + if not found: + print(f"no __version__ in {NOTEBOOK}", file=sys.stderr) + return 1 + print(found.group(1)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 3ba6f8e7fef014cd046b6e0908a7f0ef8769cc46 Mon Sep 17 00:00:00 2001 From: Suppaseth Charoenkarnka Date: Thu, 27 Aug 2026 20:32:38 +0700 Subject: [PATCH 9/9] Guard the break that ate CI, and say what upgrading actually does Round three. Two findings, and the first one is about a test I wrote badly. THE GUARD THAT GUARDED NOTHING. test_workflow_block_scalars_never_dedent_below_ their_own_block now catches an under-indented line inside a `run: |` block -- the break that made this workflow unparseable, so GitHub ran zero jobs and reported "no checks reported on the branch", which reads like CI has not started. The first version of it passed against the very file that carried the bug. It compared indentation: the heredoc body sits at column 0, which is BELOW the block's own key, and that read as "closing an outer mapping" rather than as the killer. What it should ask, and now asks, is whether the dedented line is a YAML key or list item at all. `import json` is neither. I only know that because I put the bug back and ran the test against it. A guard that has never seen the thing it guards is a guess. WHAT UPGRADING DOES. The release note omitted the one behaviour change an operator will actually observe, and it comes from the version bump, not from G3. The idempotent-skip fast path requires the stored framework_version to equal __version__, so the first generate after upgrading re-stamps the mapping even on a byte-identical config: changed True instead of False, success instead of skipped, two log rows instead of one. A pipeline gated on envelope["changed"] re-plans and re-applies once per deployment. config_hash does not move and the next run skips again. Also recorded: mapping_hash excludes provenance, so a 1.0.0-stamped saved plan still opens the apply gate and the mapping-history CSV is reused verbatim -- its framework_version column reads 1.0.0 while the table reads 1.1.0. That is the mechanism that lets a plan survive an upgrade, so it is worth stating rather than leaving to be discovered. The note sits after ### Removed so the Keep a Changelog sections stay in order. 1655 tests, lint clean, release gate PASS, workflow parses to 4 jobs. --- CHANGELOG.md | 15 ++++++++ tests/test_public_release.py | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a7c8e9..b94b84f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,21 @@ No changes yet. 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 diff --git a/tests/test_public_release.py b/tests/test_public_release.py index f317542..9ad324d 100644 --- a/tests/test_public_release.py +++ b/tests/test_public_release.py @@ -694,3 +694,77 @@ def test_all_objects_hands_the_object_stream_to_an_interactive_container(tmp_pat "the stdin-mode container must be interactive, or docker replaces its stdin with " f"/dev/null and the scan reads nothing: {command}" ) + + +def test_workflow_block_scalars_never_dedent_below_their_own_block(): + """The other half of the same footgun, and it has now bitten this repo twice. + + A `run: |` block ends at the first non-blank line indented LESS than the block's own content + indent. A heredoc written inside one looks natural at column 0 -- + + run: | + version="$(python - <<'PY' + import json + PY + )" + + -- and YAML stops reading the block at `import json`, then tries that line as a mapping key. + GitHub rejects the whole file: zero jobs, no annotations, and `gh pr checks` says "no checks + reported on the branch", which reads like CI has not started rather than like CI is broken. + Branch protection then waits forever for checks that can never appear. + + The sibling test above guards the `": "` inline-scalar shape, and its docstring records that + the same class of break once meant CI "had never run a single time, on any commit". It cannot + see this one: nothing on those lines is an inline scalar. Same outcome, different syntax. + + pyyaml is deliberately not a test dependency (see the sibling), so this measures indentation + rather than parsing the document. + """ + import re + + block_open = re.compile(r"^(\s*)-?\s*(?:run|if|shell|env|with):\s*[|>][+-]?\s*$") + # a mapping key, or a list item — anything else at this indent is not YAML + yaml_key = re.compile(r"^\s*(?:-\s+)?(?:[A-Za-z_][\w.-]*|'[^']*'|\"[^\"]*\"):(?:\s|$)|^\s*-\s") + offenders = [] + for path in sorted((REPO_ROOT / ".github").rglob("*.yml")): + lines = path.read_text(encoding="utf-8").splitlines() + relative = path.relative_to(REPO_ROOT).as_posix() + index = 0 + while index < len(lines): + opened = block_open.match(lines[index]) + if not opened: + index += 1 + continue + key_indent = len(opened.group(1)) + index += 1 + # the block's content indent is set by its first non-blank line + while index < len(lines) and not lines[index].strip(): + index += 1 + if index >= len(lines): + break + content_indent = len(lines[index]) - len(lines[index].lstrip()) + if content_indent <= key_indent: + continue # an empty block; the next key follows + while index < len(lines): + line = lines[index] + if not line.strip(): + index += 1 + continue + indent = len(line) - len(line.lstrip()) + if indent >= content_indent: + index += 1 + continue + # Dedented, so YAML has ended the block here. That is only legal if this line is + # the next mapping key or list item. Testing the indent instead is what a first + # draft of this guard did, and it passed on the very break it was written for: + # a heredoc body at column 0 is BELOW the block's key, which read as "closing an + # outer mapping" rather than as the killer it is. + if not yaml_key.match(line): + offenders.append(f"{relative}:{index + 1}: {line.strip()[:60]!r}") + break + + assert not offenders, ( + "a line inside a `run: |` block is indented below the block, which ends the block and " + "makes the workflow unparseable — GitHub then runs zero jobs and reports no checks:\n " + + "\n ".join(offenders) + )