feat(backups): pass the files[] block of status.json through GET /backups #62

Merged
maximus merged 2 commits from issue-61-backups-files into main 2026-10-13 00:18:54 +00:00
Owner

Refs #61

backup-postgres.sh writes files[] in status.json since la-compagnie-maximus#295 (installed on the VPS 2026-10-12). sanitizeBackupsStatus() dropped it as an unknown field: a failed archive turned the status red with no cause.

Change

  • backups.js: sanitizeFile() on the model of sanitizeDatabase(). FILE_ERROR_CODES is a closed list of its own (source_missing, archive_failed, encrypt_failed, invalid_name): a database code is dropped on a file entry and the reverse. FILE_NAME_RE = /^[a-z0-9_-]{1,63}$/ (the script's pattern, hyphen allowed; the 63 cap is the API's own bound, as for databases). archivedAt nullable. MAX_FILES = 16. An empty list is kept, an absent one gives no key and no error (script older than #295).
  • __tests__/backups.test.js: validStatus() now carries files in the script's real shapes (ok entry, source_missing with null and 0, encrypt_failed with a time and a size), so the identity, idempotence and route tests cover it. 13 new cases (102 -> 115).
  • CLAUDE.md: GET /backups contract and test counts.

No change to the routes, auth, the Dockerfile COPY or test-curl.sh. The /admin display belongs to la-compagnie-maximus#254 (contract noted there).

Verification

  • npm test 879/879, bash test-curl.sh 57/57, sh scripts/boot-from-copy.sh PASS.
  • Mutations on backups.js, each restored from a copy (suite green again after):
Mutation Red
regex without hyphen 8
regex cap 64 1
file and database codes merged 1
invalid_name removed from the file list 3
bound 17 2
non-object entries not filtered 1
archivedAt null not kept 6
files block removed 13

Post-merge checklist (hence Refs, not a closing keyword)

  1. Redeploy on Coolify (no auto-deploy).
  2. GET /backups in prod: vps.files[0].name == "claude-usage", ok: true, no vpsError.
  3. #61 is then closed by hand with the evidence.

🤖 Generated with Claude Code

Refs #61 `backup-postgres.sh` writes `files[]` in `status.json` since la-compagnie-maximus#295 (installed on the VPS 2026-10-12). `sanitizeBackupsStatus()` dropped it as an unknown field: a failed archive turned the status red with no cause. ## Change - `backups.js`: `sanitizeFile()` on the model of `sanitizeDatabase()`. `FILE_ERROR_CODES` is a closed list of its own (`source_missing`, `archive_failed`, `encrypt_failed`, `invalid_name`): a database code is dropped on a file entry and the reverse. `FILE_NAME_RE = /^[a-z0-9_-]{1,63}$/` (the script's pattern, hyphen allowed; the 63 cap is the API's own bound, as for databases). `archivedAt` nullable. `MAX_FILES = 16`. An empty list is kept, an absent one gives no key and no error (script older than #295). - `__tests__/backups.test.js`: `validStatus()` now carries `files` in the script's real shapes (ok entry, `source_missing` with null and 0, `encrypt_failed` with a time and a size), so the identity, idempotence and route tests cover it. 13 new cases (102 -> 115). - `CLAUDE.md`: `GET /backups` contract and test counts. No change to the routes, auth, the Dockerfile `COPY` or `test-curl.sh`. The `/admin` display belongs to la-compagnie-maximus#254 (contract noted there). ## Verification - `npm test` 879/879, `bash test-curl.sh` 57/57, `sh scripts/boot-from-copy.sh` PASS. - Mutations on `backups.js`, each restored from a copy (suite green again after): | Mutation | Red | |---|---| | regex without hyphen | 8 | | regex cap 64 | 1 | | file and database codes merged | 1 | | `invalid_name` removed from the file list | 3 | | bound 17 | 2 | | non-object entries not filtered | 1 | | `archivedAt` null not kept | 6 | | `files` block removed | 13 | ## Post-merge checklist (hence `Refs`, not a closing keyword) 1. Redeploy on Coolify (no auto-deploy). 2. `GET /backups` in prod: `vps.files[0].name == "claude-usage"`, `ok: true`, no `vpsError`. 3. #61 is then closed by hand with the evidence. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-13 00:04:14 +00:00
feat(backups): pass the files[] block of status.json through GET /backups
All checks were successful
CI / verification (pull_request) Successful in 35s
329d83e4e5
backup-postgres.sh now writes files[], one entry per archived host
directory (la-compagnie-maximus#295). sanitizeBackupsStatus() dropped
it as an unknown field, so a failed archive turned the status red with
no cause. It is now rebuilt from a whitelist like databases[], with a
closed code list of its own (source_missing, archive_failed,
encrypt_failed, invalid_name), the script's name pattern with a 63 cap,
a nullable archivedAt and a bound of 16. A status without files keeps
working.

Refs #61

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Verdict : APPROVE

Résumé : sanitizeFile() reprend fidèlement le patron de sanitizeDatabase() et colle au contrat réel de backup-postgres.sh (la-compagnie-maximus bd38f21) : mêmes clés { name, sizeBytes, archivedAt, ok, error? }, archivedAt à null via at or None, regex de nom ^[a-z0-9_-]+$, quatre codes d'erreur possibles et aucun autre. Routes, auth, COPY et surfaces d'écriture inchangés.

Vérifications rejouées par le relecteur

  • Suite complète sur une copie hors dépôt de 329d83e : 879/879.
  • CI de la tête 329d83e : success (statut de commit).
  • Timeline de #61 : pull_ref en ref_action: "none". Aucune fermeture au merge, cohérent avec le Refs et la checklist post-merge.
  • Contrat confronté au script : entries(lines, "archivedAt"), encrypt_failed qui garde archivedAt et size de l'archive en clair, invalid_name qui peut porter un nom arbitraire (filtré par FILE_NAME_RE), liste vide quand FILE_SOURCES est vide.

Mutations du relecteur (backups.js, copie hors dépôt, backups.test.js)

Mutation Rouges
codes de base (VPS_ERROR_CODES) sur un fichier 7
regex de base (sans tiret) sur un nom de fichier 8
slice avant filter 1
archivedAt non nullable 6
regex sans ancre ^ 1
sizeBytes recopié tel quel 1
ok recopié tel quel 1
files recopié tel quel 5

Fichier restauré, puis suite complète de nouveau à 879/879.

Suggestions (non bloquantes)

  1. CLAUDE.md : « un code de base est jeté sur un fichier et l'inverse » est vrai pour les codes exclusifs seulement. encrypt_failed et invalid_name figurent dans les deux listes et passent des deux côtés. Une précision d'une ligne (« sauf les deux codes communs ») éviterait une lecture trop stricte.
  2. Une entrée invalid_name dont la paire n'a pas de = (name = pair) garde un nom conforme à la regex. C'est sans danger (le nom passe la liste blanche) et l'affichage /admin (la-compagnie-maximus#254) n'a rien à prévoir de particulier. Je le note pour mémoire.

Actions faites

  • Copie hors dépôt scratchpad/pr62-copy supprimée en fin de revue.
## Verdict : APPROVE **Résumé** : `sanitizeFile()` reprend fidèlement le patron de `sanitizeDatabase()` et colle au contrat réel de `backup-postgres.sh` (la-compagnie-maximus `bd38f21`) : mêmes clés `{ name, sizeBytes, archivedAt, ok, error? }`, `archivedAt` à `null` via `at or None`, regex de nom `^[a-z0-9_-]+$`, quatre codes d'erreur possibles et aucun autre. Routes, auth, `COPY` et surfaces d'écriture inchangés. ### Vérifications rejouées par le relecteur - Suite complète sur une copie hors dépôt de `329d83e` : **879/879**. - CI de la tête `329d83e` : `success` (statut de commit). - Timeline de #61 : `pull_ref` en `ref_action: "none"`. Aucune fermeture au merge, cohérent avec le `Refs` et la checklist post-merge. - Contrat confronté au script : `entries(lines, "archivedAt")`, `encrypt_failed` qui garde `archivedAt` et `size` de l'archive en clair, `invalid_name` qui peut porter un nom arbitraire (filtré par `FILE_NAME_RE`), liste vide quand `FILE_SOURCES` est vide. ### Mutations du relecteur (`backups.js`, copie hors dépôt, `backups.test.js`) | Mutation | Rouges | |---|---| | codes de base (`VPS_ERROR_CODES`) sur un fichier | 7 | | regex de base (sans tiret) sur un nom de fichier | 8 | | `slice` avant `filter` | 1 | | `archivedAt` non nullable | 6 | | regex sans ancre `^` | 1 | | `sizeBytes` recopié tel quel | 1 | | `ok` recopié tel quel | 1 | | `files` recopié tel quel | 5 | Fichier restauré, puis suite complète de nouveau à 879/879. ### Suggestions (non bloquantes) 1. `CLAUDE.md` : « un code de base est jeté sur un fichier et l'inverse » est vrai pour les codes exclusifs seulement. `encrypt_failed` et `invalid_name` figurent dans les deux listes et passent des deux côtés. Une précision d'une ligne (« sauf les deux codes communs ») éviterait une lecture trop stricte. 2. Une entrée `invalid_name` dont la paire n'a pas de `=` (`name = pair`) garde un nom conforme à la regex. C'est sans danger (le nom passe la liste blanche) et l'affichage `/admin` (la-compagnie-maximus#254) n'a rien à prévoir de particulier. Je le note pour mémoire. ### Actions faites - Copie hors dépôt `scratchpad/pr62-copy` supprimée en fin de revue.
maximus added 1 commit 2026-10-13 00:18:48 +00:00
docs: the two backup code lists share encrypt_failed and invalid_name
All checks were successful
CI / verification (pull_request) Successful in 37s
CI / verification (push) Successful in 36s
9fa1f39e13
Review of #62, suggestion 1.

Refs #61

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus merged commit 9fa1f39e13 into main 2026-10-13 00:18:54 +00:00
maximus deleted branch issue-61-backups-files 2026-10-13 00:18:54 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/vps-health-api#62
No description provided.