Architecture overview¶
The extension enforces at three levels, because PostgreSQL offers three different points at which an operation can be intercepted, and no single one of them sees everything.
Why three layers¶
| Layer | Mechanism | What it catches |
|---|---|---|
| 1 | Table access method | Anything that modifies a row |
| 2 | ProcessUtility hook |
Statements that change a table's definition |
| 3 | object_access hook |
DROP and TRUNCATE, including cascades |
Each exists because the others cannot see what it sees.
Layer 1 sees row changes but not DROP or TRUNCATE, because neither touches individual rows — TRUNCATE deallocates files directly.
Layer 2 sees statements, but only the objects the user actually named. DROP SCHEMA ... CASCADE never mentions the tables it destroys.
Layer 3 sees each object as PostgreSQL resolves it, cascades included — but fires too late for ALTER TABLE, where the change must be refused before a rewrite begins.
The wrapper principle¶
The vault access method is a thin wrapper over PostgreSQL's built-in heap. It does not implement storage.
At startup it copies heap's entire routine — a struct of around fifty function pointers — and replaces only the handful that can modify a row. Everything else is heap's own code, unchanged.
That has two consequences worth understanding:
- Vault tables are ordinary tables. MVCC, TOAST, vacuum, indexing and WAL all behave exactly as they do for any table, because they are heap's implementations.
- New PostgreSQL versions are inherited, not ported. A callback added in a future release arrives already populated with heap's version.
Version and platform coverage¶
The wrapper is what lets one set of sources compile unmodified across three PostgreSQL majors: churn in the forty-odd callbacks the extension does not override — the RelFileNode/RelFileLocator rename in 16, read-stream analyze in 17, the removal of scan_bitmap_next_block in 18 — never reaches this code. There is no #if PG_VERSION_NUM anywhere in the source.
PostgreSQL 16 is the floor, set by tuple_update's last argument changing from bool *update_indexes to TU_UpdateIndexes *update_indexes in that release. 15 and earlier are not supported.
The complete suite runs on two axes:
| Axis | Coverage |
|---|---|
| PostgreSQL version | 16, 17 and 18, each on Ubuntu 24.04 and 26.04 LTS |
| Distribution | Rocky Linux 8 and 9, Debian 12, Ubuntu 22.04, 24.04 and 26.04 LTS, and Fedora 44, on PostgreSQL 18 |
The compiler spread is deliberate rather than incidental — gcc 8 on Rocky 8 through gcc 15 on Fedora 44. New warnings are treated as build failures, and no single platform surfaces the whole diagnostic set.
Architecture coverage is x86-64. Nothing in the source is architecture-specific, but ARM is not currently in the matrix.
Fail closed¶
Wherever the extension cannot establish what a table permits, the answer is deny.
- Options that cannot be read produce an empty permission set, which grants nothing.
- A null retention deadline is treated as not yet expired.
- The library refuses to load outside
shared_preload_libraries, so a server that cannot enforce cannot open a vault table at all.
The failure mode of tampering is a frozen table, never an open one.
Files¶
| File | Responsibility |
|---|---|
vault_tableam.c |
The access method: the routine struct and its callbacks |
vault_enforce.c |
The decisions — permission checks, retention, insert-once |
vault_reloptions.c |
Parsing and storing permissions and retention |
vault_utility_hook.c |
Layer 2: creation-time handling and ALTER TABLE |
vault_object_access.c |
Layer 3: DROP and TRUNCATE |
vault_violation.c |
The violation record |
vault_options_accessor.c |
The SQL-callable options accessor |