175 -> 99 effective lines. New .claude/rules/{proxmox,localapi,backup,storage}.md alongside the
existing health-checks.md. The release section points at the felhom-build-deploy skill rather than
restating a table that drifts from the script; the layout section's per-package annotations moved
into the rule file for their area instead of being deleted.
Kept in the core because it is the only part re-injected after /compact: the root-CLI fence and its
three exceptions, the destructive-op gate, prove-ownership (audit A1), the gate entry point, the F9
live-validation fence, and the checklist.
health-checks.md overlaps localapi.md and storage.md on three globs -- deliberate, both load,
stated in each file. Go build/vet/test green and unchanged.
2.2 KiB
paths
| paths | ||||
|---|---|---|---|---|
|
Backup, PBS and DR
internal/backup/ is the vzdump runner, restore-test scheduler and report store. internal/pbs/ is
the fingerprint-pinned PBS-API client plus the verify maintenance loop. internal/pbsdr/ and
internal/dr/ carry the DR tier and recipe halves.
The three PBS laws
- Set-only.
pvesm removeDELETES the encryption key. Re-apply configuration; never remove and re-add a PBS storage to change it. - Secret on stdin. A token secret is passed on stdin, never as an argv the process table shows.
- Verify the pin BEFORE consuming the secret. A fingerprint check after the secret has been sent protects nothing.
Verify is server-side, and its default skips the work
The agent drives verification remotely via the PBS API; proxmox-backup-client has no verify
subcommand. POST .../verify defaults to ignore-verified=true, which SKIPS already-verified
snapshots — send ignore-verified=false to actually re-read and detect corruption. A verify that
skipped everything reports success.
Presence is not success
A timestamp recording an attempt is not evidence of a result. Where a status field travels beside a timestamp, the verdict must consult both — or the timestamp must record only successes. Ask of any timestamp: what exactly must have happened for this to be set? If the answer is "we tried", it cannot answer "did it work".
Corollary: when a verdict changes which field it counts from, the alarm text changes with it.
Leaving a message reading last run 8h ago while alarming on a six-day-old success turns a true
alarm into one the operator dismisses.
Prune is server-side now
DatastoreBackup carries no Datastore.Prune. Boxes set keep_last: 0 and the off-site endpoint
runs the prune jobs. Box tokens stay write-only — never widen that grant (R-89).