Changelog

Versions are released on PGXN. Each upgrade script (pg_promise_guard--OLD--NEW.sql) documents, in its own header, exactly what changed and why; that is the authoritative per-version record.

0.2.9 – 2026-10-09

No behavior change; names inside function bodies are English (a local variable of watch()).

0.2.8 – 2026-10-09

The Medium and Low findings of the external audit of 0.2.5, each measured on 0.2.7 first (test/audit.sh, in make check-suites: every tooth red there with its control green).

  • PG-02: only objects of an extension owned by a superuser are skipped, in every branch; a tenant hid unenforced_rls by adding its table to an extension it created.
  • PG-03: a NOT ENFORCED constraint (PostgreSQL 18) is a breach, not_enforced_constraint.
  • PG-04: a trigger that fires only in replica mode is a gap; a disabled foreign-key trigger is a breach.
  • PG-05: a policy on a table without RLS, a disabled rule and a disabled event trigger are breaches; a NOT VALID domain constraint is a gap.
  • PG-06: a schema that does not exist raises, in check_promises(), promises_kept() and watch(); it read “no breaches”.
  • PG-07: it refuses outside READ COMMITTED: under REPEATABLE READ it read an old catalog and the assertion recorded holds after a breach.
  • PG-08: an index being built (PostgreSQL 12+) is a gap, not a breach on every deploy.
  • PG-09: a UNIQUE index invalid but ready is a gap that says new rows are checked. A CREATE UNIQUE INDEX CONCURRENTLY that fails on duplicates leaves both flags false (measured: duplicates go in), and stays a breach; the regression test now reproduces that state.
  • PG-10: RLS not forced is a gap when the owner cannot log in.
  • PG-11: watch() of a schema already watched returns its assertion.
  • Object names are quoted identifiers.

0.2.7 – 2026-10-09

  • The scanner reads the real catalog, whatever schema the extension lives in (PG-01, external audit of 0.2.5). Every function searched its own schema before pg_catalog. In an installation that began at 0.1.0 that schema is public, and a role allowed to create there – the default before PostgreSQL 15 – added empty public.pg_trigger and public.pg_index: check_promises() found nothing, promises_kept() said true, and the watched assertion recorded holds over a disabled trigger (measured on 0.2.6, test/from_010.sh). Every function now searches pg_catalog first; watch() qualifies what its check calls and declares it under that path. Watches declared before 0.2.7 keep their old path: the upgrade names them, and retiring and watching again moves them.

0.2.6 – 2026-10-08

  • Metadata only. The PGXN description is two sentences now; the longer explanation it carried is in this README. No code changed: the upgrade script 0.2.5 -> 0.2.6 changes no object.

0.2.5 – 2026-10-08

  • An installation that began at 0.1.0 can upgrade, and its watch() works. 0.1.0 fixed no schema, so it was installed wherever the caller said – usually public; 0.2.0 fixed schema = promise_guard for new installations only. The 0.2.3 -> 0.2.4 script named promise_guard literally and failed there with schema "promise_guard" does not exist (found upgrading a real database; the failure was all or nothing). The script now says @extschema@, as the 0.1.0 -> 0.2.0 script always did. watch() is recreated naming the schema the extension is in: the published 0.2.0 already did, and this normalizes a watch() from a pre-release build that named promise_guard literally. test/from_010.sh (make check-from-010) reproduces the origin – 0.1.0 installed with its own control file, then updated – which ci/upgrade_check.sh cannot, since it installs every old version with the current control file.

0.2.4 – 2026-10-08

  • A temporary table of the session that evaluates a promise can no longer hide a broken one. check_promises() reads the catalog without a schema under search_path = promise_guard, pg_catalog, and PostgreSQL searches an unnamed pg_temp first for tables: a temporary pg_trigger or pg_class stood in for the real catalog. That matters when the evaluation runs in someone else’s session with the owner’s rights – a SECURITY DEFINER function that runs the assertion watch() declared, as pg_agent_gate does inside an agent’s commit. Measured on 0.2.3 (test/pg_temp.sh, make check-pgtemp): a disabled audit trigger read broken, and holds once the evaluating session kept an empty pg_temp.pg_trigger or pg_temp.pg_class. Every function now names pg_temp last. No table changes.

0.2.3 – 2026-10-06

  • License: Apache License 2.0, replacing the PostgreSQL License, from this release on. Every version up to and including 0.2.2, already published, stays under the PostgreSQL License it was released with. No code changed.

0.2.2

Completes the copyright and licensing files: the copyright holder’s full legal name in LICENSE and README, and a per-file SPDX header on every SQL source file. No schema change.

0.2.1

No schema change. Adds project governance and legal files (NOTICE, AUTHORS, SECURITY, CONTRIBUTING, TRADEMARK). The database objects are byte-for-byte those of 0.2.0; the 0.2.0--0.2.1 upgrade is empty on purpose.

0.2.0 and earlier

See the header of each pg_promise_guard--*--*.sql upgrade script and the release notes on PGXN.