From 0bd4cd02beca0a1cf141f81b3336f93d8f400496 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 10 Jul 2026 00:24:57 +0200 Subject: [PATCH] docs: v0.109.1 README + REPORT (SLICE 4 deployed + live-verified) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6 --- REPORT.md | 73 +++++++++++++++++++++++++------------------- controller/README.md | 11 +++++++ 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/REPORT.md b/REPORT.md index 89024f8..fd6ddf8 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,40 +1,49 @@ -# REPORT — controller v0.108.0: SLICE 3 hub-verified escrow auto-confirm — DEPLOYED +# REPORT — controller v0.109.1: SLICE 4 soft-quota + offsite health — DEPLOYED + live-verified -**Date:** 2026-07-09 · **Class:** implementation. Baseline v0.107.0 → **v0.108.0** (live on 9201, -`Up (healthy)`). The chain pairs: **agent v0.79.0** (ceremony uploads `restic_pw_sha256`) + **hub v0.40.0** -(stores it; report ACK serves `escrow:{identity_blob_present, restic_pw_sha256, created_at}`) — both live. +**Date:** 2026-07-10 · **Class:** implementation. Baseline v0.108.0 → v0.109.0 → **v0.109.1** (live on +9201, `Up (healthy)`). Pairs with **hub v0.41.0** (OffsiteChecker + freeze lever) — live. The offsite arc's +last build item. ## What shipped (controller) -`report.EscrowAutoConfirmer` — runs on every report ACK; flips offbox `EscrowState` pending→escrowed ONLY -when `sha256(local repo_password) == restic_pw_sha256` from the ACK. Verifies the RIGHT fact: not "a blob -exists" but "the blob covers the CURRENT key" (a stale blob — re-provision, inject, drive history — must -never truthfully-claim; that would re-open the exact un-recoverable-ciphertext gap fork-4 closed). -- Match → flip + wipe the agent-staged secret (the v0.107.0 DELETE path; best-effort, loud on failure). -- Mismatch → stays pending + LOUD warn naming the fix ("run the escrow ceremony"), deduped per distinct - hash (not per 15-min cycle). -- No row / NULL hash / hash-without-identity-blob / no local password file → silently pending (fail-closed). -- Non-pending → total no-op — **auto-UN-confirm does not exist**. -- `backup.HashResticPassword` (canonical: sha256 hex of the TRIMMED string) + `Manager.OffboxRepoPasswordHash`; - **pinned cross-repo test vector** (same in felhom-agent) so the two hashers can never drift silently. -- Manual `POST /backup/offbox/confirm-escrow` → documented **deprecated fallback** (legacy hash-less blobs). +- **Data:** `OffboxTarget.QuotaGB` (bridge maps it from the descriptor; the descriptor hash now includes it + so a hub-side quota change re-applies — via key-auth-first, no password consumed) + `RepoSizeBytes` + (persisted from `restic stats`; failed stats keeps last-known — stale-but-safe). +- **Pre-run soft-quota gate:** ≥100% refuses NEW backups (Hungarian: "A NAS-mentés túllépte a + tárhelykeretet (X/Y GB)…", `LastStatus=error`, operator alert via the existing notify) — but + **`offboxPruneOnly` still runs the retention step** (pruning is the only way back under quota) and + **restore is never gated**. ≥80% (<100%) sets the Hungarian usage `LastWarning`. Quota 0 = no gate. +- **UI:** `/backups` usage bar (used/quota + %, green/amber/red) when quota>0. Template gates green. +- **Report:** the hub report carries the non-secret + `offsite:{enabled, escrow_state, last_run, last_status, snapshot_count, repo_size_bytes, quota_gb}`. +- **v0.109.1 (live finding):** the deploy's own re-apply DEMOTED the escrowed demo to pending + wiped its + runtime status (`ApplyOffsiteTarget` replaced the whole target). Now a re-apply preserves the existing + `EscrowState` (custody tracks the preserved repo password, not the coords) + runtime status; a fresh + guest still lands pending. -## Tests + companion red-proof (ALL-GREEN, red-proof fired) -Scenarios: A match→flip+wipe (+repeat ACK no-op); B stale-blob stays pending + warn-once-per-hash (+new -hash warns again); C all silent-pending cases; E never acts outside pending (even a mismatch on an escrowed -target is silent); wipe-failure keeps the confirm. **Red-proof:** modeled the blob-present-only check → the -stale-blob AND hash-less scenarios flipped when they must not → both tests FAILED. Reverted. -Pinned vector + trim convention asserted in `internal/backup` (mirrors the agent's). +## Tests + companion red-proofs (ALL-GREEN, all three fired) +- Scenario A: over-quota → backup 0 calls, **prune 1 call**, Hungarian status, restore reaches the runner. + **Red-proof:** gated the prune too → "prune MUST still run over quota, got 0" → FAIL. +- Scenario B: 84% → run ok + "A NAS-mentés a keret 84%-át használja (42/50 GB)." + bytes persisted. +- Scenario C: quota 0 → no gate/warn at any size. Bridge quota mapping + quota-change re-apply + (panicking consumer) tested. +- v0.109.1 preserve: **red-proof** dropped the EscrowState carry-over → "must NOT demote an escrowed + target, got pending" → FAIL. +- Report object present when enabled / nil when unconfigured (nil-safe hub-side). -## Live state check (deployed, no operator action) -Hub 0.40.0 Synced/Healthy; migration applied (`host_escrow.restic_pw_sha256` present; all 3 legacy rows -NULL — demo included). Controller 0.108.0 healthy; the startup report went through the new hub and the -demo's `escrowed` state produced **zero `escrow-confirm` log lines** — Scenario E proven live for free. -Agent 0.79.0 on felhom-pve (56/56 capabilities). +## Live verification (all through the real pipeline) +- **Key-auth-first proven LIVE:** the quota-hash change triggered a real re-apply — "existing key still + authenticates … re-pinned + reconfigured without consuming a password" against the real box. +- v0.109.1 deployed; demo repaired via the deprecated manual confirm (truthful — same escrowed password; + the confirm's wipe also removed the re-staged agent secret, verified empty) + a manual run: + **2 snapshots, 41s, `escrowed`, `quota_gb: 50`, `repo_size_bytes` 45 MB**. +- The next report carried the full offsite object hub-side; the **OffsiteChecker held it healthy** + (0 `offsite_*` events — 0% fill, not stale). The `/backups` usage bar renders live: + "Tárhelykeret: 43.2 MB / 50 GB (0%)" (curl through the in-guest UI). -## NOT yet live-exercised -The full auto-confirm chain (fresh enable → ceremony records the hash → ACK → auto-flip) — first real -offsite enable proves it (Peti onboarding runbook). Unit red-proofs carry the logic per the task. +## NOT live-exercised (per the STOP) +The freeze lever (would write-lock the demo's working offsite) and the 100% quota-refusal (demo repo is +0.08% of quota) — unit red-proofs cover both; live exercise waits for a real need / the Peti window. ## NEXT -SLICE 4 (soft-quota on `quota_gb`), Peti onboarding runbook (will live-exercise auto-confirm + F4), -NEW-box restore round-trip → retire the old-box archive. +Peti onboarding runbook (live-exercises auto-confirm + F4 + fresh-provision quota), NEW-box restore +round-trip → retire the old-box archive. diff --git a/controller/README.md b/controller/README.md index 28e99fb..be72d34 100644 --- a/controller/README.md +++ b/controller/README.md @@ -746,6 +746,17 @@ not just those with HDD data. Non-HDD apps can configure destination, method, an > target. Image: `restic` + `openssh-client` (re-added; restic's sftp backend shells out to `ssh`). > - UI: the "Külső (NAS) mentés" section on the backups page (configure target, per-app toggles, run-now, > restore, status). Config: `settings.OffboxTarget` + per-app `AppBackupPrefs.Offbox`. Daily at 04:15. +> - **Soft quota (v0.109.x, SLICE 4; pairs with hub v0.41.0).** The shared-model quota (`quota_gb`) rides +> the descriptor into `OffboxTarget.QuotaGB` (0 = no limit — dedicated boxes are Hetzner-enforced; the +> hash includes it, so a hub-side quota change re-applies via key-auth-first, no password consumed). +> `RepoSizeBytes` persists from `restic stats` (last-known on failure — stale-but-safe). Pre-run gate: +> **≥100% refuses NEW backup runs** (Hungarian error + operator alert) but the **prune step still runs** +> (the only way back under quota) and **restore is never gated**; ≥80% sets a Hungarian usage warning. +> `/backups` shows a usage bar when quota>0. The hub report carries +> `offsite:{enabled, escrow_state, last_run, last_status, snapshot_count, repo_size_bytes, quota_gb}` — +> the hub's OffsiteChecker alerts on fill (90/95%) and staleness (escrowed + no run >48h); the Hetzner +> `readonly` freeze is an OPERATOR lever on the hub (never automatic). A RE-apply preserves the existing +> target's EscrowState + runtime status (v0.109.1 — custody tracks the preserved repo password). > - **Hub-driven provisioning apply-bridge (v0.106.x, SLICE 2; pairs with hub v0.38.x — validated live > 2026-07-09).** When the hub provisions the offsite tier (a Hetzner Storage Box sub-account or dedicated > box), the served `controller.yaml` gains an `offsite:` section (host/user/port/repo_path/quota_gb +