R-1105 re-proof: restic pinned outside the OS (07 §6.8), evidence; R-1106, R-1107, R-1108 filed
gates / gates (push) Successful in 6m55s

This commit is contained in:
2026-10-16 21:20:23 +02:00
parent b94411b4b8
commit e713f6aeff
111 changed files with 2358 additions and 3 deletions
@@ -947,6 +947,37 @@ moves it to a wrong class. The order is deliberate: a cause that cannot be told
rather than being folded into a neighbour. Where the warning is SHOWN on the dashboard is a separate rule —
`02-controller-module-map.md`, „Alert placement".
### 6.8 Which restic the box runs — pinned outside the OS, and re-proven (R-1105, controller v0.323.0)
**[DESIGN] restic is a pinned decision, never „whatever the OS ships".** Until v0.322.0 it came from the runtime's apt
list, so a base-image move (Debian 12 → 13) would have swapped restic 0.14.0 for 0.18.0 under the off-site code with
nobody choosing it. Since v0.323.0 the controller image carries the **official restic release binary** of one version
(`ARG RESTIC_VERSION`, today **0.18.1**), checked at build time against restic's signed `SHA256SUMS` (vendored release
key, no keyserver) and against a per-architecture sum pinned in the Dockerfile; any mismatch fails the build. The
Proxmox hosts run no restic at all (measured on all three test hosts): the agent's escrow path only stores and returns
the repository password, and every restic call happens in the guest.
**Moving the version is a re-proof, not an edit.** `felhom-controller/controller/internal/backup/testdata/restic/<version>/`
holds REAL outputs of each version the code was checked against (captured on one shared scratch repository), and
`TestR1105_*` push them through the code's own parsers and classifiers for every version. The 0.14.0 → 0.18.1 re-proof
(`audits/debian13-restic-2026-10-16/`):
| What the code relies on | 0.18.1 |
|---|---|
| a missing source path is skipped with a warning, exit 0 (§6.3, SP-3.4); an unreadable file exits 3 | same |
| `backup --json` status lines (`percent_done`, totals, `current_files`) | same field names |
| `snapshots --json`, `snapshots latest --tag … --json`, `stats <id>` / `--mode raw-data` | same shapes (new fields only) |
| the §6.7 classes: wrong password, no repository, refused port / key, unreachable host, dead rclone ssh | same class each |
| lock text (both forms), `unlock` is stale-only, `unlock --remove-all` clears | same |
| a damaged pack reads as damage in `check` | same verdict, other wording |
| `restore --include <unit>` layout; `--no-lock` restores from a read-only repository | same (a plain `restore` DOES lock, on both) |
| **the `forget` plan, 7 / 4 / 6, `--group-by host,tags`** | **moved, safe direction:** while a `--keep-monthly` window has unused slots, 0.17+ also keeps each group's OLDEST snapshot — a subset of 0.14.0's removals (41-day set: 60 vs 62; 230-day set: identical). The fake-snapshot guard's verdict holds. |
| a store written by both: 0.14.0 reads and restores a 0.18.1 snapshot after a 0.18.1 `forget --prune`; 0.18.1 restores a 0.14.0 snapshot | proven on a scratch store; on demo-hp's real store both directions are proven (byte-identical trees); 0.14.0 reading after a 0.18.1 prune there waits for the store's next weekly clean-up window |
Exit codes changed (10 no repository, 11 locked, 12 wrong password); nothing in the controller reads a restic exit code.
**Open:** the 0.18.1 binary is built with Go 1.25.1 and carries 50 fixable scanner findings (R-1107 — the operator's
choice of 0.19.x).
## 7. The recovery chain (D3) — the reason this document exists
**[DESIGN] 3-2-1 describes copies. It does not describe recovery.**