Violation records¶
Every refusal writes a record to the server log.
Why a log line¶
A rejection raises an error, which rolls back the transaction. Anything written to an ordinary table as part of handling that rejection would roll back with it.
Log writes are not transactional, so the record survives the rollback for free — an attempt cannot erase its own record by failing.
Why a separate line, not errdetail¶
Two reasons.
The forensic record and the client-facing message have different audiences. Hanging the table's full permission set off the error would hand it to the session that just failed against it.
And a distinct, fixed-prefix, key=value line stays greppable and parseable by a log shipper without needing a multi-line rule.
Format¶
pg_vault_tables_violation: ts=... operation=... reason=... database=... schema=...
table=... permissions=... retention_days=... current_user=... session_user=...
client_addr=... application_name=... pid=...
Emitted at LOG level, which is written whatever log_min_messages is set to short of silencing the server entirely, and with errmsg_internal because the line is machine-readable output rather than user-facing prose to be translated.
Every field is present on every record, including ones a log_line_prefix might already carry. A site that trims its prefix must not silently lose the timestamp or the user identities.
Scope¶
Records are written for:
- Capability rejections — an operation not in the table's permissions.
- Retention rejections — a delete reaching a row whose deadline has not passed.
Both are anomalies. The purge procedure carries an explicit WHERE _$purge_ts < now() and therefore targets only eligible rows, so nothing in normal operation produces either. A retention rejection means something other than the purge went looking for a row it was not entitled to remove, which is exactly the kind of event worth a record.
Both user identities¶
current_user and session_user are recorded separately because they differ under SET ROLE, and that difference is forensically relevant on its own.
The test suite asserts they are actually distinct in that case — a record carrying the same value twice would satisfy a naive field-presence check while losing the very thing the field exists for.
What produces nothing¶
Permitted operations, and a purge-shaped delete removing eligible rows.
That is asserted explicitly. Signal that ordinary work generates is not signal.