feat(cloud-backups): retention-gated drop for pg audit-rotate - #7
Merged
Conversation
Rework `pg audit-rotate` from keep-copy + same-run drop to retention-gated
whole-archive drop ("Approach B"): each run rotates the write-only audit table
aside as an immutable timestamp-named archive, backs it up to a permanent
bucket, RETAINS it for AUDIT_RETENTION_DAYS (default 30), and DROPs it whole
once aged. A two-pass reconcile recovers interrupted backups (reuse-if-present
via HeadObject, never a blind re-dump) and drops aged-out archives.
- Drop-gate oracle = rotation timestamp parsed from the archive NAME; a parse
failure fails safe (retain, never the time.Parse zero-value).
- Pre-drop gate is cheap by default (dump + .sha256 both present via Head);
--verify-restore adds re-download + pg_restore -l + SHA-256.
- Preflight guards: refuse on frozen entity_name='instances' rows still read by
the app (--drop-instance-rows to override), and on non-owner GRANTs that
CREATE TABLE LIKE won't reproduce (split-role safety).
- --drain-backlog cutover drops the new archive immediately (first run only).
- `pg backup` gains --exclude-table so the full-DB backup skips the resident
archive tables (avoids re-dumping them + a DROP-vs-pg_dump race).
- audit_rotate_summary log (dropped/retained/recovered/quarantined,
oldest_archive_age_days) for dead-man / disk alerting.
- Removed the vestigial --no-drop/--drop-pending staging flags (retention is the
inspect-before-drop buffer). Renamed keep-tail-days -> audit-retention-days.
- Typed storage.ErrNotFound (S3 + Azure) so the drop gate tells a definitive
absence from a transient error. Unit + 8-scenario stress + 15GB real-data
cutover validated.
Paired with the rearm-core helm PR; merge + build the image before rolling helm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ReARM-Agent: 1420896f-adf5-4843-896f-d863cfcc6528
ReARM-Agentic-Session: 7fa98c2d-b192-4b4a-b1a8-ac64349a89b2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Reworks
pg audit-rotatefrom keep-copy + same-run drop to retention-gated whole-archive drop ("Approach B"). Each run rotates the write-onlyaudittable aside as an immutable timestamp-named archive, backs it up to a permanent bucket, retains it forAUDIT_RETENTION_DAYS(default 30), and DROPs it whole once aged. A two-pass reconcile recovers interrupted backups (reuse-if-present via HeadObject, never a blind re-dump) and drops aged-out archives.Highlights
time.Parsezero-value)..sha256both present viaHead);--verify-restoreadds re-download +pg_restore -l+ SHA-256.entity_name='instances'rows still read byInstanceService→--drop-instance-rowsto override (not data loss — backed up — but app instance-revision reads go empty ~retention days post-cutover).CREATE TABLE LIKEwon't reproduce → split-role safety (would otherwise break audit writes after the first rotation).--drain-backlogcutover drops the new archive immediately (first-run only).pg backup --exclude-tableso the full-DB backup skips the resident archive tables (avoids re-dumping them into the expiring bucket + a DROP-vs-pg_dumprace).audit_rotate_summarylog (dropped/retained/recovered/quarantined,oldest_archive_age_days) for dead-man / disk alerting.--no-drop/--drop-pendingstaging flags — retention is the inspect-before-drop buffer now. Renamedkeep-tail-days→audit-retention-days.storage.ErrNotFound(S3 + Azure) so the drop gate distinguishes a definitive absence from a transient error.Testing
Unit tests (gate, oracle, mappings, exclude-table, ACL/instances SQL) + an 8-scenario stress test against Postgres 17 + MinIO + a 15 GB real-data cutover (rotate catalog-only ~30 ms → 4.47 GB encrypted → verify-restore → DROP reclaimed ~15 GB). Reviewed across correctness/security, conventions, SRE, DBA, and holistic-simplicity lenses.
Rollout
Paired with the rearm-core helm PR (values/cronjob +
EXCLUDE_TABLE+ a bucket-collisionfailassert). Merge this and build the image before rolling the helm change. Enabling in prod also requires external dead-man + disk-free alerting (the reclaim-nothing failure is otherwise silent) and a separate no-expiry bucket (GOVERNANCE-not-COMPLIANCE / versioned+noncurrent-expiry — a deny-overwrite WORM bucket is incompatible with recovery).🤖 Generated with Claude Code