Add the TapDB backup and recovery lifecycle (#89) - #91
Open
jdurham38 wants to merge 1 commit into
Open
Conversation
Replaces the previous ~140-line five-table backup command with a schema-scoped, checksummed, manifest-described backup subsystem driven by one shared service across CLI, admin API and the embedded GUI. Three backup classes (template-pack, full logical, provider-snapshot receipt). Restores default to an isolated target; in-place requires a typed confirmation and lands via a verified staged schema swap, so nothing is dropped until a verified replacement exists and every failure path renames the original back. Integrity is verified before and after, which is what makes EUID reuse impossible. Every mutating run emits an immutable hash-chained receipt with a head anchor, so truncation is detectable rather than merely unlikely. Item 10 is delivered in full: * `tapdb backup prune` enforces retention. A backup is deletable only when its hold set is empty -- nothing is ever selected for deletion, things fail to be protected -- behind seven gates that abort before any byte is removed. Dry run is the default; deleting needs the typed target label, and a delete ceiling guards the case where that label has become a constant in a scheduled job. * `tapdb backup health` is the alerting contract: exit 0 recoverable, 1 something is wrong, 2 the check could not run, with parseable JSON on stdout on every code. It needs no database, so it still answers when the database is down, and it writes nothing, so it is safe to poll. Alert delivery is the consumer's -- an embedded library reaching out to a notification channel from inside another service's process would be a surprise, not a feature. Also fixes a live defect found along the way: S3 list_keys built its Prefix without a trailing delimiter, so listing acme/orders also returned acme/orders-staging. Local storage has a real directory boundary, which is why no existing test could see it. Verification leaned on mutation testing throughout -- deliberately breaking each guarantee and confirming a test fails. That caught defects a green suite was hiding, including EUID reuse after in-place restore, CLI exit codes reporting 0 for failures, and a prune reconciliation path that could erase an entire store. Verified end to end against a real Aurora PostgreSQL 16.11 cluster. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #89.
Replaces the previous ~140-line five-table backup command with a schema-scoped,
checksummed, manifest-described backup subsystem driven by one shared service
across CLI, admin API and the embedded GUI, so a fix to how backups are taken is
a fix everywhere at once.
What it delivers
Three backup classes (template-pack, full logical, provider-snapshot receipt).
Restores default to an isolated target; in-place requires a typed confirmation
and lands via a verified staged schema swap, so nothing is dropped until a
verified replacement exists and every failure path renames the original back.
Integrity is verified before and after, which is what makes EUID reuse
impossible. Every mutating run emits an immutable hash-chained receipt with a
head anchor, so truncation is detectable rather than merely unlikely.
All 10 issue items are complete
tapdb backup prune: retention enforcement. A backup is deletable onlywhen its hold set is empty: nothing is ever selected for deletion, things
fail to be protected. Fourteen holds behind seven gates that abort before a
single byte is removed. Dry run is the default; deleting needs the typed target
label, and a delete ceiling guards the case where that label has become a
constant in a scheduled job.
tapdb backup health: the alerting contract. Exit0recoverable,1something is wrong,
2the check itself could not run, with parseable JSON onstdout on every code. The runbook documents cron/systemd/monitor wiring.
Please note
A live bug fix rode along.
S3StorageBackend.list_keysbuilt itsPrefixwithout a trailing delimiter, so listing
acme/ordersalso returnedacme/orders-staging, inflatingbackup list, the status page and the GUI withanother target's backups. Unrelated to #89, S3-only, and invisible to existing
tests because local storage has a real directory boundary.
data backup/data restoreare now deprecated in favour oftapdb backup.Verification
1620 passing, 14 skipped. ruff, format and bandit clean. 91.5% coverage against
an 85% gate. Verified e2e against a real Aurora PostgreSQL 16.11 cluster.
Testing leaned on mutation testing throughout, deliberately breaking each
guarantee and confirming a test fails. That is what caught the defects a green
suite was hiding, including EUID reuse after in-place restore, CLI exit codes
reporting
0for failures, and a prune reconciliation path that could erase anentire store from a single unvalidated receipt field.
One thing
setuptools_scmalready derives9.0.11.dev1. Tag9.0.11on the merge commit when something actually needs to pin it.
9.0.10and everyearlier tag are untouched, and no service pins a mutable ref, so nothing
downstream moves.