Skip to content
Merged
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ the whole round-trip end to end.
- **Kubernetes** — runs as a CronJob / Deployment; verified
end-to-end against CloudNativePG.
- Content-addressed **deduplication** (FastCDC, page-aligned splits)
— no incremental chains to break.
— every base backup is a self-contained full, deduplicated against
what's already in the repo, so storage stays incremental-sized
without a mandatory chain. PG 17+ incremental backups
(`backup --incremental-from`) are supported too when you want them.
- **AES-256-GCM** envelope encryption; a FIPS / BoringCrypto build
variant is available.
- **4 Tier-1 KMS providers** — AWS KMS · GCP KMS · Azure Key Vault ·
Expand Down Expand Up @@ -305,8 +308,9 @@ build.
Or via the canonical Makefile:

```sh
make # build bin/pg_hardstorage + bin/pg_hardstorage_testkit
make build-simple # the interactive quick-start helper
make build # build bin/pg_hardstorage (bare `make` prints the help menu)
make all-binaries # build + testkit + interactive helper
make build-simple # just the interactive quick-start helper
make build-compat # the pgBackRest / Barman / WAL-G shims
make build-fips # FIPS / BoringCrypto variant
make build-firecracker # microVM verifier-sandbox variant
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING-DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ can't bit-rot:
````markdown
```bash
# RUNNABLE
pg_hardstorage init --pg-connection postgres://… --repo fs:///tmp/hsr --yes
pg_hardstorage init --pg-connection postgres://… --repo file:///tmp/hsr --yes
```
````

Expand Down
2 changes: 1 addition & 1 deletion docs/compliance/audit-evidence-bundles.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pg_hardstorage audit export-bundle \
--until 2026-05-01T00:00:00Z \
--include-anchors \
--operator "ops@acme" \
-o ./acme-april-2026.tar.gz
--out ./acme-april-2026.tar.gz
```

### Verify
Expand Down
30 changes: 17 additions & 13 deletions docs/compliance/data-residency-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,17 @@ For v0.1, the safety is layered:
- An operator who configures a violating repo is responsible
for re-checking after configuration changes.

For v1.0+ the planned enforcement is:
What ships today is the read-only `residency check`
command: `pg_hardstorage residency check <deployment>`
reports `verify.residency_violation` (exit 9) when the
configured repo's region is outside the policy. It does not
mutate anything.

Planned enforcement (roadmap, not yet shipped):

- `residency check` becomes part of the pre-flight refusal
gate; backup commit refuses with `verify.residency_violation`
(exit 4) when the configured repo's region is outside the
policy.
when the configured repo's region is outside the policy.
- The audit chain records every residency check + violation
attempt.

Expand All @@ -141,16 +146,15 @@ For v1.0+ the planned enforcement is:

A deployment with residency `["eu"]` whose repo is also
async-replicated to a US region for DR is a policy
violation at the replica side. The replication subsystem
respects per-deployment residency:

- `repo replicate --from <eu-repo> --to <us-repo>` for a
deployment with `residency: ["eu"]` refuses with
`verify.residency_violation` unless explicitly
overridden via `--allow-cross-region`.
- The `--allow-cross-region` flag is recorded in the audit
chain — an explicit operator decision, not an
invisible default.
violation at the replica side.

Residency enforcement on the replication path is on the
roadmap and not yet shipped: `repo replicate` does not
consult per-deployment residency, does not refuse
cross-region copies, and there is no `--allow-cross-region`
flag. Until it lands, run `pg_hardstorage residency check`
against the replica repo out-of-band to detect a
cross-boundary replica.

---

Expand Down
20 changes: 13 additions & 7 deletions docs/compliance/fedramp.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ JSON is `fedramp`.

| Control | Title | Product feature | Command | Audit event |
| --- | --- | --- | --- | --- |
| AC-2 | Account management | RBAC scopes; per-tenant KEK; JIT tokens | `pg_hardstorage rbac ...`, `jit issue ...` | `rbac.*`, `jit.*` |
| AC-2 | Account management | RBAC scopes (enforced server-side / SCIM-provisioned; no operator CLI verb); per-tenant KEK; JIT tokens | `pg_hardstorage jit issue ...` | `jit.*` |
| AC-3 | Access enforcement | n-of-m approval gates on destructive ops | `pg_hardstorage approval request ...` | `approval.request`, `approval.approve` |
| AC-6 | Least privilege | RBAC scope per principal; JIT for break-glass elevation | `pg_hardstorage jit issue --scope ...` | `jit.issue` |
| AC-6(7) | Review of user privileges | `audit search --action-prefix rbac.` | `pg_hardstorage audit search ...` | `rbac.*` |
| AC-6(7) | Review of user privileges | JIT grant/revoke history via audit search; RBAC changes reviewed server-side | `pg_hardstorage audit search --action-prefix jit.` | `jit.*` |
| AC-7 | Unsuccessful logon attempts | RBAC denials recorded | (automatic) | `auth.denied` |
| AC-12 | Session termination | JIT tokens auto-expire (max 24h) | (automatic) | `jit.issue` |

Expand Down Expand Up @@ -125,8 +125,10 @@ make build-fips

The variant uses `GOEXPERIMENT=boringcrypto` and links
against the BoringCrypto FIPS-validated module. Refuses to
start if `crypto/tls` reports non-FIPS. `--fips-strict`
panics on any non-FIPS plugin.
start if `crypto/tls` reports non-FIPS. In that FIPS build,
`--fips-strict` panics on any non-FIPS plugin. (The FIPS build is
not yet shipped; `--fips-strict` is not present in the default
binary.)

### FedRAMP High vs Moderate

Expand All @@ -146,9 +148,13 @@ Use `residency` to pin to GovCloud regions:
pg_hardstorage residency set fedramp-prod us-gov-east-1 us-gov-west-1
```

The replication subsystem refuses cross-boundary
replication unless explicitly overridden via
`--allow-cross-region` (which is itself audit-logged).
Cross-boundary residency enforcement on replication is on
the roadmap and not yet shipped — `repo replicate` does not
refuse cross-region copies, and there is no
`--allow-cross-region` flag. What ships today is the
read-only `pg_hardstorage residency check <deployment>`,
which reports `verify.residency_violation` (exit 9) when the
configured repo's region is outside the policy.

---

Expand Down
6 changes: 4 additions & 2 deletions docs/compliance/hipaa.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ JSON is `hipaa`.

| Section | Description | Product feature | Command | Audit event |
| --- | --- | --- | --- | --- |
| §164.312(a)(1) | Access control | Per-tenant KEK + RBAC scopes | `pg_hardstorage rbac ...` | `rbac.*` |
| §164.312(a)(1) | Access control | Per-tenant KEK + RBAC scopes (RBAC enforced server-side / SCIM-provisioned; no operator CLI verb) | (server-side) | (server-side) |
| §164.312(a)(2)(i) | Unique user identification | RBAC actor identity recorded on every event | (automatic) | (every event records `actor`) |
| §164.312(a)(2)(ii) | Emergency access procedure | JIT tokens + n-of-m approval | `pg_hardstorage jit issue ...`, `approval request ...` | `jit.issue`, `approval.request` |
| §164.312(a)(2)(iii) | Automatic logoff | JIT tokens auto-expire (max 24h) | (automatic) | `jit.issue` |
Expand Down Expand Up @@ -72,7 +72,9 @@ make build-fips
```

The FIPS variant refuses to start if `crypto/tls` reports
non-FIPS. `--fips-strict` panics on any non-FIPS plugin.
non-FIPS. In that FIPS build, `--fips-strict` panics on any
non-FIPS plugin. (The FIPS build is not yet shipped; `--fips-strict`
is not present in the default binary.)

---

Expand Down
2 changes: 1 addition & 1 deletion docs/compliance/iso-27001-control-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ JSON is `iso27001`.
| Control | Title | Product feature | Command | Audit event |
| --- | --- | --- | --- | --- |
| A.5.10 | Acceptable use of information | RBAC scopes + JIT tokens for break-glass | `pg_hardstorage jit issue ...` | `jit.issue`, `jit.revoke` |
| A.5.15 | Access control | Per-tenant KEK + RBAC scope | `pg_hardstorage rbac ...` | `rbac.*` |
| A.5.15 | Access control | Per-tenant KEK + RBAC scope (RBAC enforced server-side / SCIM-provisioned; no operator CLI verb) | (server-side) | (server-side) |
| A.5.23 | Information security for use of cloud services | Storage plugin per-region scoping; air-gap mode | (config) | (config) |
| A.5.30 | ICT readiness for business continuity | Disaster runbooks R1–R7; recovery drills with measured RTO | `pg_hardstorage recovery drill ...` | `recovery.drill_failed` (on fail) |
| A.5.34 | Privacy and protection of PII | Data residency + classification | `pg_hardstorage residency set ...`, `classify set ...` | (config), (manifest tag) |
Expand Down
2 changes: 1 addition & 1 deletion docs/compliance/soc2-control-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The framework string in the report's JSON is `soc2`.
| --- | --- | --- | --- | --- |
| CC6.1 | Logical access security | Three-layer envelope encryption (RKEK → BDEK → per-chunk Kc) | `pg_hardstorage backup ...` | `backup.create` |
| CC6.6 | Logical access — boundary protection | Storage plugin per-region scoping; air-gap mode rejects public endpoints | `pg_hardstorage residency set ...` | (config) |
| CC6.7 | Restricted access to information assets | Per-tenant KEK + RBAC scopes | `pg_hardstorage rbac ...` | `rbac.*` |
| CC6.7 | Restricted access to information assets | Per-tenant KEK + RBAC scopes (RBAC enforced server-side / SCIM-provisioned; no operator CLI verb) | (server-side) | (server-side) |
| CC6.8 | Detection and prevention of unauthorised software | SLSA L3 build provenance + cosign signatures on every release | (build-time) | (cosign attest) |
| CC7.1 | Detection of system anomalies | Anomaly score (Z-score over 30-day baseline) per backup | `pg_hardstorage backup ...` | `anomaly.detected` |
| CC7.2 | System events logged in tamper-evident chain | Hash-chained Merkle audit log | `pg_hardstorage audit verify-chain` | (every event) |
Expand Down
18 changes: 10 additions & 8 deletions docs/explanation/architecture-tour.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ manifest is independently verifiable without external state.
"encryption": {
"scheme": "aes-256-gcm",
"wrapped_dek": "base64...",
"kek_ref": "local-keyring://default",
"kek_ref": "local:default",
"envelope_version": 2
},
"tablespaces": [{"oid":1663,"location":"pg_default"}],
Expand Down Expand Up @@ -363,13 +363,15 @@ cooperating mechanisms:
- **Strategy C (fallback):** recreate the slot on detection.
The agent runs `IDENTIFY_SYSTEM` after reconnect; if the slot
doesn't exist, recreate it and report any gap loudly.
3. **Dual-slot mode** (≥ 50 TB or `availability=high`). Two slots
on two nodes feed the same CAS; either stream can fail without
RPO impact. Shipped in v1.0.
4. **Synchronous-target mode** (`wal_mode: synchronous`). Agent
advertises as a `synchronous_standby_names` candidate, PG waits
for our flush ACK before commit. RPO = 0 at the cost of write
latency. Shipped in v1.0.
3. **Dual-slot mode** (≥ 50 TB, opt-in via a `patroni.slots:` list
of `{name, role}` entries). Two slots on two nodes feed the
same CAS; either stream can fail without RPO impact.
4. **Synchronous-target mode** (`wal_mode: synchronous`) — *not
implemented*. The aspiration is for the agent to advertise as a
`synchronous_standby_names` candidate so PG waits for our flush
ACK before commit (RPO = 0 at the cost of write latency). Today
there is no `wal_mode: synchronous` config key; only preflight
*detection* of where `sync_standby` slots are placed ships.

Strategy C is honest about loss — it never silently glosses over a
gap. Manifests record the gap; PITR inside the gap window is
Expand Down
15 changes: 7 additions & 8 deletions docs/explanation/audit-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,13 @@ The default 5-minute cadence is a tradeoff between two costs:
- Sparse anchoring widens the rewrite window — events
between two anchors are still rewritable until the next anchor.

`pg_hardstorage repo set audit.anchor_interval` accepts any
duration from 30 s to 1 h. Production tuning typically picks
between 1 min (security-sensitive, accept higher log volume) and
15 min (capacity-constrained).
The cadence is set per deployment via the `schedule.audit_anchor`
spec in `pg_hardstorage.yaml` (e.g. `every: 5m`). Production
tuning typically picks between 1 min (security-sensitive, accept
higher log volume) and 15 min (capacity-constrained).

The `pg_hardstorage_audit_anchor_lag_seconds` metric exposes how
long it's been since the last successful anchor; the default
alerting recipe fires if it exceeds 3× the configured interval.
Track anchor freshness from the `audit verify-chain` output, which
reports how long it's been since the last successful anchor.

---

Expand Down Expand Up @@ -284,7 +283,7 @@ reporting.

---

## What's deferred to v1.0
## What's deferred past v1.0

The chain is real and verifiable; the audit subsystem ships
hash-chained from v0.1 onward and the durable monotonic
Expand Down
5 changes: 1 addition & 4 deletions docs/explanation/comparison-pgbackrest-walg-barman.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ differentiation:
- **K8s with the operator-shim model**. The WAL-G shim and
pgBackRest shim let you swap into existing operator-managed
clusters without rewriting the operator. (A native CNPG-I
provider is on the roadmap for v0.5.)
provider is on the roadmap.)

- **Transparent Data Encryption (TDE) at the source.** PG forks
that encrypt heap / index / WAL at rest — CYBERTEC PGEE,
Expand All @@ -211,9 +211,6 @@ Where we are *honest* about being behind:
- **Ecosystem of expert operators.** More humans in the world
know pgBackRest than know `pg_hardstorage` today. Onboarding
cost is real.
- **Storage backends.** S3 and FS are first-class in v0.1.
Azure Blob and GCS are second-class until v0.5; SFTP and tar
are scoped for v0.5 / v1.0.

---

Expand Down
16 changes: 10 additions & 6 deletions docs/explanation/envelope-encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,11 @@ Single-org installs get a default tenant they never see — but the
tenant boundary exists in the data layout regardless. This is a
deliberate architectural choice, baked into the data layout:

- **GDPR crypto-shred** is `pg_hardstorage kms shred --confirm-keyring <keyring-dir>
--reason "GDPR Art. 17 request #4421"`. One key-destruction op. The
audit event is the compliance artifact.
- **GDPR crypto-shred** is `pg_hardstorage kms shred --repo <url>
--require-approval <approval-id> --confirm-keyring <keyring-dir>
--reason "GDPR Art. 17 request #4421"`. One key-destruction op,
gated behind an approved n-of-m request. The audit event is the
compliance artifact.
- **Multi-tenant SaaS** can isolate customer A from customer B
cryptographically: even if every chunk leaks, only the owner's
KEK can decrypt their tenant's BDEKs.
Expand All @@ -157,8 +159,10 @@ this effectively free for steady-state workloads.

## KEK rotation flow

`pg_hardstorage kms rotate --repo <url> --old-kek-ref <old> --new-kek-ref <new>` walks
every manifest in the repo wrapped under the old KEK ref:
`pg_hardstorage kms rotate --repo <url> --old-kek-ref <old>
--new-kek-ref <new> --old-kek-file <old-bytes> --new-kek-file
<new-bytes> --apply` walks every manifest in the repo wrapped
under the old KEK ref (omit `--apply` for a dry-run preview):

```mermaid
sequenceDiagram
Expand Down Expand Up @@ -200,7 +204,7 @@ A few properties worth highlighting:
After retirement the old KEK can be scheduled for deletion via
the KMS's own retention policy.

`pg_hardstorage repair attestation <backup-id>` exists for the
`pg_hardstorage repair attestation <deployment> <backup-id>` exists for the
adjacent case — re-signing a manifest whose Ed25519 signature no
longer validates after a rotation that also rotated the
attestation key. See the architecture tour for the full repair
Expand Down
2 changes: 1 addition & 1 deletion docs/explanation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ SPEC is the source of truth and we want to know — file an issue.
- [Coordination without etcd](coordination-without-etcd.md) — the
progressive ladder from JSON state files up to opt-in etcd.
- [Tier-1 vs Tier-2 plugins](tier1-vs-tier2-plugins.md) —
compile-time vs `go-plugin`, trust posture, registry roadmap.
compile-time vs stdio-JSON-RPC subprocess, trust posture, registry roadmap.
- [Verify-sandbox tradeoffs](verify-sandbox-tradeoffs.md) —
Docker default vs Firecracker microVM; isolation vs setup
cost.
Expand Down
33 changes: 17 additions & 16 deletions docs/explanation/llm-safety-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,29 +270,28 @@ independent layers of evidence.

---

## Skills are signed YAML files, not Go code
## Skills are YAML files, not Go code

The skills (`restore`, `incident`, `ask`, `explain`, `runbook`,
`postmortem`) are versioned, declarative YAML files — not Go
functions baked into the binary. Implications:
The builtin skills (`ask`, `explain`, `incident`, `restore`) are
versioned, declarative YAML files — not Go functions baked into
the binary. Implications:

- **Hot-fix loop in minutes, not weeks.** A bad skill response in
production gets a same-day patch — drop a new YAML file in
`/etc/pg_hardstorage/skills/`, increment the version, restart
the agent (or `pg_hardstorage llm reload-skills`). No binary
rebuild, no Debian package release.
- **Skill isolation.** A bug in the postmortem skill cannot
production gets a same-day patch — author a new YAML file,
increment the version, and `pg_hardstorage llm skill install
<file>` into the operator overlay (any existing version is
snapshotted for rollback). No binary rebuild, no Debian package
release.
- **Skill isolation.** A bug in the incident skill cannot
touch the restore skill. Each skill loads independently, has
its own tool allowlist, its own guardrails, its own RBAC scope.
- **Inspectable, with signing on the roadmap.** Skills load as
plain, reviewable YAML today. Cryptographic skill signing and
signature verification (a project key for shipped skills, the
operator's key for local ones) are planned, not yet shipped.
- **Linted + golden-tested.** `pg_hardstorage llm skill lint
<file>` validates the schema and static-checks the tool list
(no banned tools, no missing required ones).
`pg_hardstorage llm skill test <file>` runs golden test cases
against a pinned model checkpoint per release.
- **Linted.** `pg_hardstorage llm skill lint <file>` validates
the schema and static-checks the tool list (no banned tools, no
missing required ones).

A skill file declares which tools are available to the LLM in
that session. The `restore` skill explicitly excludes
Expand Down Expand Up @@ -334,8 +333,10 @@ specific set of failure modes:
- **"You released a bad model update."** The bundle records the
exact model id and version. Provider-side model versioning is
captured.
- **"The skill was malicious."** Skills are signed; unsigned
skills require a flag whose use is audited.
- **"The skill was malicious."** Skills load as reviewable YAML
and are schema-linted before install; the exact skill name and
version are recorded in the audit bundle. (Cryptographic skill
signing is roadmap, not yet shipped.)
- **"You hid prompts from me."** `/show-context` plus the export
bundle prove otherwise.
- **"A prompt injection in a manifest description executed
Expand Down
Loading
Loading