Contents
Changelog
Versions are released on PGXN.
Each upgrade script (pg_living_assertions--OLD--NEW.sql) documents, in its own
header, exactly what changed and why; that is the authoritative per-version
record.
0.5.8 – 2026-10-09
- A check runs as the role that declared it (external audit: F9, F6; the same class
as pg_plan_guard’s PG-S1). Up to 0.5.7 it ran with the privileges of whoever called
run()– documented, and demonstrated bytest/privilegios.shreading the owner’s secret through a trusted role’s check. The seal bounded writes to the database and nothing else:COPY ... TO PROGRAMis a read, so a check ran a program as the caller; a session advisory lock stayed in the caller’s session; and a check that cancelled its own backend abortedrun_all()for every assertion. Inside the seal the evaluator now doesSET ROLEtodeclared_by(not when that is the current user), so a check can do what its author could and no more; what needs more is that assertion’serroring, and cancelling the caller’s backend is refused the same way. Advisory locks taken in the seal are released (test/sql/read_only.sqlmeasured the lock held until 0.5.7). declared_bycannot be forged at insert: a trigger accepts a name other than the declaring role only from a role that maySET ROLEto it (a superuser restoring a dump).- Behaviour change for callers. A caller must be able to
SET ROLEto each author; a superuser can. ASECURITY DEFINERcaller – pg_agent_gate binding an assertion – runs the checks its owner declared; the others areerroring, with the reason. test/audit.sh: the F9/F6 teeth, andtest/privilegios.shinverted – red on 0.5.7 with their controls green.
0.5.7 – 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.5.6 -> 0.5.7 changes no object.
0.5.6 – 2026-10-08
From an external audit of 0.5.5, each finding measured on 0.5.5 before it was changed
(test/audit.sh, make check-audit, in make check-suites: every tooth red on 0.5.5
with its control green).
- The recorded
search_pathno longer stays in the caller’s session (F1).run()applied it withset_config(..., false), and the 0.5.5 comment said the function’sSETclause would restore it on exit. It does not: a plainSETinside a function with aSETclause overrides the clause and persists after the function. Afterrun_all()a runner’s next unqualified call reached a function in a schema the author of an assertion wrote, and aSECURITY DEFINERwrapper with its ownSET search_pathcontinued under the author’s path oncerun()returned. run()’s bookkeeping no longer runs under the author’s path (F2). With a recorded path ofevil, pg_catalog, the author’sclock_timestamp()ran as the runner in a session that only calledrun_all().- Both closed in one place: the path is applied inside
_evaluate’s sealed subtransaction withset_config(..., true), right after read-only is switched on, and the rollback that undoes the check undoes it too._evaluatehas its ownSET search_path = pg_catalog, pg_tempfor everything outside the seal, and the calls around the check are schema-qualified.run()no longer touches the path. - An unparsable recorded path is that assertion
erroringinstead ofrun_all()raising for everyone (F7), and the path is split the way PostgreSQL splits it: 0.5.5 broke a quoted schema name containing a comma (F15), and did not recognise an unquotedPG_TEMPaspg_temp.SETstores the path lower-cased, but a path set withset_config()orALTER ROLE ... SETis recorded as written, and withPG_TEMPfirst a temporary table of the evaluating session answered for the check. - A forged verdict cannot be pinned (F3). The latest verdict was the one with the
latest
checked_at, and a role allowed to run checks needsINSERTonchecks: a row dated'infinity'outranked every honest check forever. The latest verdict is now the last row written (byid), andchecked_atmust be finite. Such a role can still write a row; it lasts until the next honest check (README). - An assertion is not edited in place, all of it (F4). The trigger compared five
columns;
search_path,declared_by,why_changedandidare fixed now, and a retirement is written once – not undone, not re-dated, its reason not rewritten. - A
NULLreason no longer passes the checks that make retiring and replacing cost one (F5). - Two concurrent replacements of one assertion no longer both retire it, the second reason overwriting the first (F11): the predecessor is locked.
- The upgrade adds the new constraints
NOT VALIDand validates them; an installation already holding rows they refuse upgrades, gets aWARNINGnaming them, and keeps them, since the record is append-only.
0.5.5 – 2026-10-08
- A temporary table of the session that evaluates an assertion can no longer
change what it reads. PostgreSQL searches
pg_tempfirst for tables wheneversearch_pathdoes not name it, and no path here named it.run()evaluated the check under the declarer’s path ("$user", public), sofrom cuentasread the evaluating session’spg_temp.cuentas; andrun()looked the assertion up withFROM assertions, so a temporaryassertionswith a forged row – a failing assertion’s name, the id of one that holds – made it answerholds.retire()andrun_all()read and wroteassertionsthe same way. That matters when the check runs in someone else’s session with the owner’s rights: aSECURITY DEFINERfunction of the owner that callsrun(), which is what pg_agent_gate does inside an agent’s commit. Measured on 0.5.4 with the real table broken (test/pg_temp.sh,make check-pgtemp): both ways answeredholds, and the record saidholds. Now every function namespg_templast,run(),run_all()andretire()name the registry by its schema, and the declared path is applied withset_config()– with anypg_tempin it moved to the end – instead of being concatenated into aSETstatement.
0.5.4 – 2026-10-06
- License: Apache License 2.0, replacing the PostgreSQL License, from this release on. Every version up to and including 0.5.3, already published, stays under the PostgreSQL License it was released with. No code changed.
0.5.3
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.5.2
No schema change. Adds project governance and legal files (NOTICE, AUTHORS,
SECURITY, CONTRIBUTING, TRADEMARK). The database objects are byte-for-byte those
of 0.5.1; the 0.5.1--0.5.2 upgrade is empty on purpose.
0.5.1 and earlier
See the header of each pg_living_assertions--*--*.sql upgrade script and the
release notes on PGXN.