Files
admin aa74294a7d
gates / gates (push) Successful in 8s
docs: felhom-agent CLAUDE.md becomes a core plus path-scoped rules (R-229 leg b)
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.
2026-08-06 11:28:27 +02:00

2.2 KiB

paths
paths
internal/backup/**
internal/pbs/**
internal/pbsdr/**
internal/dr/**

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

  1. Set-only. pvesm remove DELETES the encryption key. Re-apply configuration; never remove and re-add a PBS storage to change it.
  2. Secret on stdin. A token secret is passed on stdin, never as an argv the process table shows.
  3. 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).