fix(recovery): fail-closed physical profile parse and WAL wording - #219
Conversation
Record a content-free physical or PITR recovery profile without executing pg_basebackup or WAL replay. Point-in-time targets require a PITR method and a WAL archive. Isolated-target readiness is mandatory. Optional RPO/RTO values remain deployer objectives and cannot be persisted as a package capability claim. Refs #204. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Wrap profile JSON encode in UnicodeError so lone surrogates raise the typed parser error, matching the receipt seam. Treat wal_archive_required=False as no continuous archive, not omitted backup-internal WAL, and record that immediate is end-of-backup consistency rather than replay-to-end-of-archive. Refs #204. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Landing vehicle for the #217 repairs
This head (
b03d913) is the successor to #2172aaa6d84. It keeps the evidence-only physical/WAL/PITR profile seam and repairs the two Important defects:
parse_postgres_physical_recovery_profilenow wrapsencode("utf-8")inexcept UnicodeErrorand raisesPostgresPhysicalRecoveryError, matching the receipt parser on main.- Physical+immediate wording now means no continuous WAL archive, not omitted backup-internal WAL.
immediateis end-of-backup consistency, not replay-to-end-of-archive.Local evidence on this head: 43 tests;
coverage run --branch --source=pg_llm_batch.postgres_physical_recoveryis 71 stmts / 22 branches / 0 miss / 0 partial = 100%.ruffclean on the changed files.Do not merge #217. Do not self-approve this PR. Merge only after required checks are terminal-success on unchanged
b03d913and a non-author human approval lands. Keep #208 and #212 as the logical dump/restore executors. Do not start a live #204pg_basebackup+ WAL drill from this branch.Sent by Cursor Automation: Fix Issues
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
Review of #219 at b03d913
This head is the landing vehicle for the #217 repairs. It is cursor-authored, so this is not an approval.
Stated repairs
parse_postgres_physical_recovery_profilewrapsencode("utf-8")inexcept UnicodeErrorand re-raises the typed JSON error withfrom None, matching the receipt parser. Lone surrogates stay inside that error.wal_archive_required=Falsenow means no continuous WAL archive. It does not waive backup-internal WAL (pg_basebackup -X streamor-X fetch).immediateis end-of-backup consistency. Replay-to-end-of-archive is the absence of arecovery_target*setting, not a recorded kind.pitr+immediatehas a success test.
package_capability_claim remains hardcoded false. The seam does not execute pg_basebackup or WAL replay. No Critical or Important fail-open parse path, leaked exception text, type-confusion, or ADR collision with #212/#215/#216 was found on this exact head.
Minor
The documentation test locks backup-internal / continuous-archive wording, but not the immediate end-of-backup clarification. That is not a merge blocker.
Operator next action
Human-review this exact SHA. Merge only after every required check on b03d913 is terminal-success and a non-author approval lands. Do not merge #217 or #220. Do not start a live #204 pg_basebackup / WAL drill on this head. Keep #208 and #212 as the logical dump/restore executors. After this lands, the next unowned recovery slice is the #218 successor (re-inspect current bytes; do not treat exact-type evidence objects as inspection provenance; allocate ADR 0020). Do not open a third physical-profile PR.
Sent by Cursor Automation: Fix Issues
Succeed the #204 isolated-target gap without racing #212. Operators must name a live pg_service and a distinct restore-drill service before pg_restore. DSNs, tenant scope, and same-name reuse fail closed. Allocate ADR 0021 so the record does not collide with #216/#219/#221. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
seonghobae
left a comment
There was a problem hiding this comment.
Reviewed exact head b03d91389afb4b32cd5350f890d9ddc682a7edeb against protected main@d2f1e32271910a6db98a0757d67194ddadca4566. The profile remains evidence-only: it does not execute pg_basebackup, archive WAL, restore a cluster, or claim RPO/RTO capability. Exact-type and bounded JSON parsing fail closed, lone-surrogate encoding is normalized into the package error boundary, PITR requires a WAL archive, point-in-time targets require the PITR method, and package_capability_claim is fixed false. The physical/immediate wording correctly distinguishes backup-internal WAL from a continuous WAL archive. No unresolved review thread or current must-fix finding was found. Approval is for this unchanged head only; merge still requires every then-live exact-head workflow/check to terminal-success and current ancestry/mergeability to remain valid.
seonghobae
left a comment
There was a problem hiding this comment.
One current documentation-regression gap remains on exact head b03d91389afb4b32cd5350f890d9ddc682a7edeb against protected main@5267146534a259f85c0985e153f3f6cb1281f58f. The operator document correctly states that PITR immediate means end-of-backup consistency and is not replay-to-end-of-archive, but tests/test_postgres_physical_recovery_documentation.py does not pin that distinction; the existing unresolved inline thread identified the same gap. Because this successor specifically repairs the misleading physical/PITR wording, leaving its defining immediate semantics unguarded permits a future documentation regression while CI stays green. Please add a narrow documentation regression that requires the end-of-backup/non-replay-to-end-of-archive contract in both authoritative documents, then re-run the focused profile/documentation tests and exact 100% owned coverage/docstrings. Do not change the evidence-only runtime semantics or widen this into a live WAL/PITR executor. This review supersedes my earlier approval after re-verifying the unresolved current finding; it is exact-head only and must not transfer after the repair.
seonghobae
left a comment
There was a problem hiding this comment.
Reviewed exact current head 8d75bc2dce5909d0967ac961549f379c3b5c0252 against protected main@76e704415651bdef6ceb06efa8db279349bea22e. This head supersedes my prior CHANGES_REQUESTED on b03d91389afb4b32cd5350f890d9ddc682a7edeb: a fresh compare shows the only #219-owned change since that reviewed head is the requested narrow documentation regression, while the remaining head advancement incorporates commits now on protected main. tests/test_postgres_physical_recovery_documentation.py now requires both authoritative documents to retain immediate, end-of-backup, and replay-to-end-of-archive, in addition to the backup-internal-WAL versus continuous-archive contract. The PR's five-file net delta remains evidence-only; it does not execute pg_basebackup, archive/replay WAL, or claim package RPO/RTO capability. The sole visible review thread is resolved. No current source/documentation must-fix remains from the prior review. Approval applies only to this unchanged exact head and does not waive terminal-success exact-head workflows/checks, live ancestry/mergeability, or thread-resolution requirements before merge.
…228) * test(recovery): require isolated restore-target service names Add the RED contract for #204 isolated-target identity: a live pg_service name and a restore-drill name must be exact distinct libpq service identities. DSNs, tenant scope, subclasses, and same-name reuse must fail closed before pg_restore. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> * feat(recovery): isolate restore-target libpq service names Succeed the #204 isolated-target gap without racing #212. Operators must name a live pg_service and a distinct restore-drill service before pg_restore. DSNs, tenant scope, and same-name reuse fail closed. Allocate ADR 0021 so the record does not collide with #216/#219/#221. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> * fix(recovery): allocate collision-free restore-target ADR 0022 #222 already files ADR 0021 for the workflow-registry audit. Keep the isolation seam unchanged and retarget this decision, doctoring, and the documentation contract to 0022 after a fresh open-writer inventory. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> * fix(recovery): require cluster identity for restore-target isolation Distinct libpq service names are not cluster isolation. Require caller-owned pg_control_system() identifiers so two aliases for the same production cluster fail closed before pg_restore. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> Co-authored-by: Seongho Bae <me@seonghobae.me>
Dismissed as superseded predecessor-head review evidence. This CHANGES_REQUESTED review is bound to b03d913 and requested only the missing immediate/end-of-backup documentation regression. That exact repair was later verified and approved on 8d75bc2; the current head a623bd2 advances from 8d75bc2 only by protected-main integration commits and retains the same five-file PR delta. Current exact-head coverage/opencode checks are terminal-success. Dismissal removes stale negative evidence only. It does not transfer the old approval or satisfy the live ruleset's required qualifying approval of the current last push.


Why this exists
#217 at
2aaa6d84binds a caller-owned physical/WAL/PITR profile, butparse_postgres_physical_recovery_profileleaksUnicodeEncodeErroron lone surrogates, and the physical+immediate contract wording says a base backup "may omit WAL". PostgreSQL 18 still requires backup-internal WAL for a crash-consistent restore (pg_basebackup -X streamor-X fetch).wal_archive_required=Falseonly means no continuous archive.This successor keeps the #217 seam and repairs those two contracts. It does not execute
pg_basebackup, archive WAL, or replay a cluster.Operator action
2aaa6d84. Do not merge feat(recovery): bind caller-owned physical/WAL/PITR profiles #217.isolated_target_prepared=True.backup_method="physical"only withrecovery_target_kind="immediate".wal_archive_required=Falseas no continuous WAL archive. Keep backup-internal WAL with the base backup.backup_method="pitr"only when a WAL archive exists; point-in-time kindstime,xid,name, andlsnrequire that method.immediateas end-of-backup consistency, not replay-to-end-of-archive.rpo_seconds/rto_secondsas your objectives. The emitted profile always setspackage_capability_claim=false.pg_basebackupplus WAL replay drill. Keep feat(recovery): execute bounded logical PostgreSQL backups #208 and fix(recovery): accept custom-format restore seek positions #212 as the logical dump/restore executors.Evidence
coverage run --branch --source=pg_llm_batch.postgres_physical_recovery: 43 passed, 71 stmts / 22 branches / 0 miss / 0 partial = 100%.ruffclean on the changed files.docs/doctoring/postgres-physical-pitr-profile.md,docs/adr/0019-postgres-physical-pitr-profile.md.Writer boundary
Same five files as #217. Does not race #208 backup execution, #212 restore seek, #214 docs overlay, #215 receipt binding, or #216 catalog inspection. CHANGELOG/README/ARCHITECTURE stay with those writers.
This slice does not prove live WAL replay, schema/RLS parity, CSAP, or SOC 2 readiness. Refs #204.