Review follow-up on #60: only `local` was checked with an invalid value,
so copying `retentionDays.offsite` unvalidated left the suite green.
Also name all four tokens in the Coolify runtime-only rule of CLAUDE.md.
Refs #58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Track the PostgreSQL cluster backups (la-compagnie-maximus#220) the same
way as /claude-usage: freshness is decided by the server.
- GET /backups (read token) serves { vps, lenovo }: the status.json the
VPS script writes, rebuilt through a whitelist (closed error codes,
rotation block included), and the last workstation pull report. Each
block gets ageSeconds, stale and staleAfterSeconds from the server
(BACKUPS_STALE_SECONDS, 36 h): VPS on generatedAt, workstation on the
server-stamped receivedAt. A broken file gives a null block plus
vpsError/lenovoError, never a 500; no_data only when both are absent.
- POST /backups/pull: fourth token kind, backups-ingest, on
BACKUPS_INGEST_TOKEN (503 naming it when unset). Strict validation of
{ pulledAt, files, ok, error? }, server receivedAt, atomic write in
mode 600 through writeJsonAtomic(), now shared with /claude-usage.
Answers 204 and 413 like the two other ingest routes (the agent only
checks for a 2xx), where the issue said 200 and 400.
- backups.js joins the Dockerfile COPY line. Tests: backups.test.js
(101 cases), auth/writes pins extended, 6 socket cases in test-curl.sh.
Refs #58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>