From a0a1556ce609b37d1aeec54b595f2f99416f44a1 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 27 Jul 2026 18:12:57 +0200 Subject: [PATCH] docs: R-88b shipped; standing rule 4; READY rows re-ranked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R-88b closed (agent v0.105.0 + controller v0.178.0) — age_state gives 'unknown' its own representation, with empty meaning legacy rather than unknown so the first-backup valve keeps working on un-upgraded boxes. R-97 note updated: hub v0.79.0 (R-97c) replaced a FALSE operator-only comment with a real register — the comment claimed a guarantee the code did not provide. Standing rule 4 (R-96): a recommendation that is not followed gets one line saying why. Added to the live CLAUDE.md and this versioned copy — the live file is not in a git repo, so committing to it alone would leave the rule as durable as the chat it came from. READY re-ranked: R-95 now leads. --- documentation/backlog/OPEN-ITEMS.md | 23 ++++++++++------------ documentation/runbooks/workspace-CLAUDE.md | 6 ++++++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 2244255..49afd51 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -9,11 +9,11 @@ State: `BLOCKED` · `READY` · `WAITING-ON-OPERATOR` · `WATCHING`. Every row ha | ID | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---| | **R-88a** | ~~Failing backup re-quiesces every 5 min, no backoff~~ | **SHIPPED** (controller v0.176.0, 2026-07-27) | — | Live on both boxes; breaker 15m→4h, per-tier, never permanent | — | -| **R-88b** | `/backup/due` cannot say *unknown* — "read errored" and "never backed up" are byte-identical, so nil still bypasses the window gate | **READY #1** | — | Agent wire change: give unknown its own representation; compat rule both ways + MinAgent floor | CC | -| **R-95** | restic offsite credential **can delete** (`readonly=False`, `forget --prune` runs from the box); SFTP cannot express append-only | **READY #2** | — | Root exposure still open. Mitigation now ARMED — split prune off-box or move to REST `--append-only` | CC | -| **R-94** | Hub hands out host-install `1.19.0`; `1.20.0` is what carries R-82's backup default | **READY #3** | — | Bump `configs.go:28`, and stop hand-syncing a version constant across repos | CC | -| **R-86** | Restore-tests are interval-scheduled, not backup-aligned | **READY #4** | R-90 (ep0 headroom) informs cadence | Trigger a tier ~24 h after **its own** newest archive | CC | -| **R-87** | The restic tier is never restore-tested | **READY #5** | — | Design a controller-side test (no scratch-guest analogue transfers) | CC | +| **R-88b** | ~~`/backup/due` cannot say *unknown*~~ | **SHIPPED** (agent v0.105.0 + controller v0.178.0, 2026-07-27) | — | `age_state` known/absent/unknown; empty = legacy. MinAgent 0.105.0, met on both boxes | — | +| **R-95** | restic offsite credential **can delete** (`readonly=False`, `forget --prune` runs from the box); SFTP cannot express append-only | **READY #1** | — | Root exposure still open. Mitigation now ARMED — split prune off-box or move to REST `--append-only` | CC | +| **R-94** | Hub hands out host-install `1.19.0`; `1.20.0` is what carries R-82's backup default | **READY #2** | — | Bump `configs.go:28`, and stop hand-syncing a version constant across repos | CC | +| **R-86** | Restore-tests are interval-scheduled, not backup-aligned | **READY #3** | R-90 (ep0 headroom) informs cadence | Trigger a tier ~24 h after **its own** newest archive | CC | +| **R-87** | The restic tier is never restore-tested | **READY #4** | — | Design a controller-side test (no scratch-guest analogue transfers) | CC | | — | Storage Box **snapshots** on `storage-box-pool-1` — plan SET (daily 00:00, keep 7) but **0 taken yet** | WATCHING | first run tonight 00:00 | Confirm `size_snapshots > 0` tomorrow; until then the mitigation is armed, not proven | CC | | — | `PBS-storage-1` (u629193, box 611421) still `status=active`, 19.9 MB | WAITING-ON-OPERATOR | operator console | Delete the box | operator | | **R-90** | ep0 RAM headroom — **4 GiB swap added 2026-07-27** (persistent, verified without reboot); 3.8 GB RAM unchanged | **BLOCKED** (interim done) | Hetzner CX33 availability | Rescale when available; swap now covers Sunday's first GC | operator | @@ -21,21 +21,18 @@ State: `BLOCKED` · `READY` · `WAITING-ON-OPERATOR` · `WATCHING`. Every row ha | — | First-ever **GC** on `felhom-offsite` (armed today 13:11 UTC, never run) | WATCHING | schedule | **Sun 2026-08-02 04:30 UTC** — confirm it completes | CC | | — | demo-felhom's next weekly PBS backup (newest is 2026-07-26) | WATCHING | schedule | ~2026-08-02; also releases R-91 | CC | | — | demo-felhom's next restore-test (84 h cadence, last 2026-07-27 06:38 UTC) | WATCHING | schedule | ~2026-07-30 18:38 UTC | CC | -| **R-97** | ~~Whole-guest backup tier had no hub signal; quiesce blamed the apps~~ | **SHIPPED** (controller v0.177.0 + hub v0.78.0, 2026-07-27) | — | Operator-only events proven end-to-end; app suppression is cycle-keyed with a 180 s expiring grace | — | +| **R-97** | ~~Whole-guest backup tier had no hub signal; quiesce blamed the apps~~ | **SHIPPED** (controller v0.177.0 + hub v0.78.0/v0.79.0, 2026-07-27) | — | v0.79.0 (R-97c) replaced a FALSE operator-only comment with a real `operatorOnlyEvents` register | — | | **R-89** | Retention as a per-customer **commercial** policy on the hub | READY (increment 2) | — | Policy object + reconciler → ep0 prune job; keep box tokens write-only | CC | | **R-92** | Hub PBS-DR gauge is 0.1 GB-granular — small deltas unverifiable | READY (XS) | — | Widen precision when retention becomes customer-visible | CC | | **R-93** | `drill-r50` is both a blocked customer and the only drift fixture | READY (XS) | — | Retire it for a synthetic fixture, or unblock + silence per-customer | CC | ## Why the READY rows rank this way -1. **R-88b** — the loop itself is fixed (R-88a, v0.176.0), so the active harm is gone; what remains is - that an unknown still fires the safety valve, so a read failure can take **one** out-of-window - quiesce. Bounded now, but it is the fourth appearance of this class and the only one still open. -2. **R-95** — the largest *data* exposure: the tier holding the customer's documents and photos is the +1. **R-95** — the largest *data* exposure: the tier holding the customer's documents and photos is the one whose credential can delete. The snapshot mitigation is now **armed** (daily 00:00, keep 7), but it has taken zero snapshots so far and it does not touch the root cause — the box can still `forget --prune` its own repo. -3. **R-94** — a one-line constant, but until it moves every hub-driven install gets the pre-R-82 +2. **R-94** — a one-line constant, but until it moves every hub-driven install gets the pre-R-82 backup default. Cheapest high-consequence fix on the list. -4. **R-86** — an operator ruling already exists; it only waits on knowing what load ep0 can take. -5. **R-87** — real and unbuilt, but needs its own design, so it should not jump work that is specified. +3. **R-86** — an operator ruling already exists; it only waits on knowing what load ep0 can take. +4. **R-87** — real and unbuilt, but needs its own design, so it should not jump work that is specified. diff --git a/documentation/runbooks/workspace-CLAUDE.md b/documentation/runbooks/workspace-CLAUDE.md index 29a3ddd..031beae 100644 --- a/documentation/runbooks/workspace-CLAUDE.md +++ b/documentation/runbooks/workspace-CLAUDE.md @@ -87,6 +87,12 @@ These were agreed in conversation and lived nowhere, so they bound nobody. They `/backup/due` polls in `pveproxy/access.log`), and a hub DB copy whose write had silently failed, returning a confident "0 events in window" from a file a day stale until its mtime was checked. +4. **A recommendation that is not followed gets one line saying why.** Silence reads as agreement and + the disagreement is lost. Twice in the R-88/R-97 arc a review point was absorbed rather than + argued: R-84 was folded into R-82 without a word, and R-97a's operator-only guard was dropped + while the claim it was meant to enforce got committed as a comment — which is how a false + guarantee shipped and survived a release. Disagreeing is fine; disagreeing silently is not. + - **Push to `main` directly** — no feature branches. > **Clean-tree gate before any build:** `git status --porcelain` must be empty and