Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Caller-owned physical/WAL/PITR recovery profile binder
(`bind_postgres_physical_recovery_profile()` /
`parse_postgres_physical_recovery_profile()`). The seam records method,
recovery-target kind, continuous-WAL necessity, isolated-target readiness,
and optional RPO/RTO objectives. It does not execute `pg_basebackup` or
restore. `wal_archive_required=False` means no continuous archive, not the
absence of backup-internal WAL. `pitr` plus `immediate` is a consistent-state
stop, not replay-to-end-of-archive. Lone-surrogate profile text fails as
`PostgresPhysicalRecoveryError`.
- Read-only exact-head release acceptance that builds wheel and source distribution artifacts twice from clean Git archives, proves byte-identical SHA-256 identity, records bounded canonical evidence, and keeps publication and attestation authority separate.
- Optional bounded provider output/error-file lifetime controls for batch creation, with exact local validation before credential resolution and backward-compatible omission for provider-neutral callers.
- Trusted tenant-scoped durable lifecycle identities for shared-table MSA deployments, including `TenantDurableBatchAPIClient`, tenant-qualified persistence and read helpers, transaction-local PostgreSQL context, forced default-deny row-level security, and explicit standalone compatibility.
Expand Down
78 changes: 78 additions & 0 deletions docs/adr/0019-postgres-physical-pitr-profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ADR 0019: Bind a caller-owned physical/WAL/PITR recovery profile

- **Status:** Accepted for the bounded physical/PITR profile seam
- **Date:** 2026-08-16

## Context

Protected main already records content-free logical-recovery receipts and
artifact/schema integrity evidence. Issue #204 still requires a separately
reviewed physical/WAL/PITR profile. A buyer who can dump and restore SQL still
cannot state, in machine-readable form, whether recovery is crash-consistent
only or can follow time.

PostgreSQL continuous archiving uses a base backup plus a WAL archive and a
recovery target (`immediate`, time, transaction identifier, named restore
point, or LSN). Those choices are deployer-owned. The package must not execute
`pg_basebackup`, invent an RPO/RTO, or treat a physical base backup as a
point-in-time restore.

## Decision

`bind_postgres_physical_recovery_profile()` records one caller-owned profile:

- `backup_method` is exactly `physical` or `pitr`.
- `recovery_target_kind` is exactly `immediate`, `time`, `xid`, `name`, or `lsn`.
- Point-in-time kinds require `backup_method="pitr"`.
- `pitr` requires `wal_archive_required=True`.
- `wal_archive_required=False` on a `physical` profile means no continuous WAL
archive. Crash-consistent restore still requires backup-internal WAL from
`pg_basebackup -X stream` or `-X fetch`.
- `pitr` plus `immediate` is `recovery_target=immediate`, not replay to the end
of the archive.
- `isolated_target_prepared` must be the exact boolean `True`.
- Optional `rpo_seconds` and `rto_seconds` are deployer-selected objectives.
- Emitted evidence always sets `package_capability_claim` to `False`.
- Profile parse wraps UTF-8 encoding failures, including lone surrogates, as
`PostgresPhysicalRecoveryError` so hosts catching only that type see a
content-free diagnostic.

The seam does not receive archive paths, DSNs, WAL locations, or restore
commands, and it does not start PostgreSQL tools.

```mermaid
flowchart TD
A[Caller asserts isolated target] --> B{Method}
B -->|physical| C[immediate target only]
B -->|pitr| D[WAL archive required]
D --> E[time / xid / name / lsn / immediate]
C --> F[Profile JSON with capability_claim false]
E --> F
F --> G[Operator runs pg_basebackup / restore_command outside this package]
```

## Consequences

Hosts can persist a reviewed physical/PITR contract next to #205 receipts
without claiming that this package completed backup, restore, CSAP, or SOC 2.
Logical restore (#208/#212) remains a separate executor. Live WAL replay and
base-backup execution remain later #204 slices.

## Rollback

Delete the profile module, tests, and this decision record. No schema
migration is required.

## References

Swanson, M., Bowen, P., Phillips, A., Gallup, D., & Lynes, D. (2010).
*Contingency planning guide for federal information systems* (NIST Special
Publication 800-34 Rev. 1). National Institute of Standards and Technology.
https://doi.org/10.6028/NIST.SP.800-34r1

The PostgreSQL Global Development Group. (2026). *Continuous archiving and
point-in-time recovery (PITR)*. PostgreSQL 18 documentation.
https://www.postgresql.org/docs/18/continuous-archiving.html

The PostgreSQL Global Development Group. (2026). *pg_basebackup*. PostgreSQL 18
documentation. https://www.postgresql.org/docs/18/app-pgbasebackup.html
89 changes: 89 additions & 0 deletions docs/doctoring/postgres-physical-pitr-profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# PostgreSQL physical and PITR recovery profile

This record is the operator contract for
`bind_postgres_physical_recovery_profile()`. Use it before you claim that a
deployment can recover through WAL replay. The function binds evidence. It does
not run `pg_basebackup`, `restore_command`, or `pg_restore`.

## What to do next

1. Create an isolated recovery target first. Pass
`isolated_target_prepared=True` only after that target exists. The boolean
is your assertion, not package proof of isolation.
2. Choose `backup_method="physical"` only for a crash-consistent base backup
whose recovery target kind is `immediate`. `wal_archive_required=False`
means you are not relying on a continuous WAL archive. It does **not** mean
the base backup may omit backup-internal WAL. Crash-consistent restore still
needs the WAL that `pg_basebackup -X stream` or `-X fetch` wrote into the
backup.
3. Choose `backup_method="pitr"` only when a continuous WAL archive is already
being written and you can restore to a time, transaction identifier, named
restore point, LSN, or `immediate` consistent state. `pitr` plus
`immediate` stops at the first consistent state (`recovery_target =
immediate`). It is not replay-to-end-of-archive. Default replay to the end
of the archive is the absence of a recovery-target kind, which this binder
does not encode.
4. Record deployer-selected `rpo_seconds` and `rto_seconds` when your
organization has those objectives. Read `package_capability_claim=false` as
a hard rule: this package did not meet those numbers for you.
5. Keep Fernet keys, TLS private material, host configuration, and provider
files outside database and WAL custody unless a separately reviewed adapter
owns them.
6. After you bind the profile, run the actual base backup and WAL restore
outside this seam, then prove schema, tenant/RLS, checkpoint, and lifecycle
usability on the isolated target.

## Time-flow boundary

PostgreSQL point-in-time recovery is a time-flow control. Kinds `time`,
`xid`, `name`, and `lsn` are accepted only on a `pitr` profile with
`wal_archive_required=True`. A physical profile cannot borrow those kinds even
when WAL is being archived. That prevents labeling a crash-consistent restore
as a point-in-time restore.

## What this slice does not prove

This binder does not execute physical backup, WAL archive, or replay. It does
not prove schema/RLS/constraint/extension parity, target isolation, universal
RPO/RTO, high availability, disaster recovery, CSAP, or SOC 2 readiness. Those
remain issue #204 and deployment-specific evidence. Canonical CHANGELOG and
README ownership for the broader recovery program stays with the documentation
and restore-executor writers.

## Verification

Confirm on the exact current head that:

- a PostgreSQL 18 `pitr` + `time` profile round-trips without a capability claim;
- `pitr` + `immediate` is accepted as a consistent-state stop, not end-of-WAL;
- lone-surrogate profile text raises `PostgresPhysicalRecoveryError`, not
`UnicodeEncodeError`;
- `pitr` without a WAL archive fails closed;
- point-in-time kinds on a `physical` method fail closed;
- `isolated_target_prepared=False` fails closed;
- exact-type and hostile-subclass metadata fail closed;
- parse rejects duplicate keys, unknown keys, and a true capability claim;
- production statement and branch coverage and public docstrings remain 100%.

## References

International Organization for Standardization. (2022). *Information security,
cybersecurity and privacy protection — Information security management systems
— Requirements* (ISO/IEC 27001:2022). Control A.8.13 requires information
backup; this package records a bounded physical/PITR profile, not a certified
ISMS.

Swanson, M., Bowen, P., Phillips, A., Gallup, D., & Lynes, D. (2010).
*Contingency planning guide for federal information systems* (NIST Special
Publication 800-34 Rev. 1). National Institute of Standards and Technology.
https://doi.org/10.6028/NIST.SP.800-34r1

The PostgreSQL Global Development Group. (2026). *Backup and restore*.
PostgreSQL 18 documentation. https://www.postgresql.org/docs/18/backup.html

The PostgreSQL Global Development Group. (2026). *Continuous archiving and
point-in-time recovery (PITR)*. PostgreSQL 18 documentation.
https://www.postgresql.org/docs/18/continuous-archiving.html

The PostgreSQL Global Development Group. (2026). *pg_basebackup*. PostgreSQL 18
documentation. https://www.postgresql.org/docs/18/app-pgbasebackup.html
Loading
Loading