Skip to content

Release v1.1.0 — stop emitting G3 as a runtime warning - #6

Merged
kengio merged 9 commits into
mainfrom
drop-g3-runtime-warning
Aug 27, 2026
Merged

Release v1.1.0 — stop emitting G3 as a runtime warning#6
kengio merged 9 commits into
mainfrom
drop-g3-runtime-warning

Conversation

@kengio

@kengio kengio commented Aug 27, 2026

Copy link
Copy Markdown
Owner

This pull request is the release change for v1.1.0. CONTRIBUTING.md reserves version bumps and dated CHANGELOG sections for a PR that says so; this one does. It bumps __version__ to 1.1.0, files ## [1.1.0] - 2026-08-27, and leaves ## [Unreleased] empty. The tag v1.1.0 is created on merge, not here.

What ships

G3 is no longer a runtime warning. RLS.null_safety_warning() is removed with it, and the trap it described moves into the Key invariants list in docs/architecture.md — with both mitigations and the direction of failure, which the first draft of this branch omitted.

Why the check went

The trap 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 check went because it could not tell that case from any other. It was a substring test on the raw condition rather than the literal-stripping lexer that rules C9/C11/C13 share, so:

predicate old verdict reality
status = 'CANNOT INVOICE' warns no NOT IN operator at all — the literal contains the substring
note = 'DO NOT INCLUDE' warns same false positive
region NOT IN ('a') AND type IS NULL clean region is unguarded; a different column's IS NULL muted it

Wrong in both directions, on every run, for the life of a config — and unfixable without the lexer it never used. 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.

Direction of failure

NOT IN against a NULL over-rejects. It removes rows a role should see and cannot widen access to any row. CONTRIBUTING.md requires stating this for anything touching validation; the first draft did not, and a reader could reasonably have inferred an exposure risk was traded for quiet.

Version

1.1.0. Strict SemVer reads a public removal as major — RLS.null_safety_warning was listed in docs/api/functions.md. Nothing is distributed as a package (users copy a notebook), SECURITY.md supports only the latest Preview, and real-world breakage is nil. Say the word if you would rather it were 2.0.0.

Review

Three independent lenses reviewed this branch; their findings are in the last commit. The ones worth naming:

  • The starter workbook configs/onelake_security.xlsx still told authors Every NOT IN needs OR <col> IS NULL — the single-mitigation framing this branch removed as wrong, stated more strongly, on the one artifact users copy from. Fixed.
  • configs/README.md said the workbook is versioned for the current 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 workbook is patched a zip member at a time on purpose: openpyxl.save() rebuilds the package and re-adds docProps, which the release gate rejects as person metadata. test_candidate_tree_passes_the_release_gate caught that, not me.

1653 tests pass. scripts/lint.sh clean.

kengio added 6 commits August 27, 2026 14:44
G3 warned whenever an rls_condition used NOT IN without OR <col> 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.
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
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.
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.
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.
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 <col> 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.
@kengio kengio changed the title Stop emitting G3 as a runtime warning Release v1.1.0 — stop emitting G3 as a runtime warning Aug 27, 2026
kengio added 3 commits August 27, 2026 20:20
…entity

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.
…rkflow

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.
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.
@kengio
kengio merged commit bceffe2 into main Aug 27, 2026
6 checks passed
@kengio
kengio deleted the drop-g3-runtime-warning branch August 27, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant