R-1295 live on hub rc.1 (a fresh Tester4 install set up its whole-guest off-site tier; the box consumed the credential and made its first copy); R-1294 read: an honest plan the cap can never catch up with (D54); R-1291 design (08 §10, D53); design lines for R-1295, R-1285, R-1292, R-1294; R-1296 filed; STATUS.
gates / gates (push) Successful in 13m41s

This commit is contained in:
admin committed 2026-10-26 13:42:47 +01:00
1 parent ee38dee2c9
commit 232f42d21e
17 files changed
+138 -10

No files matched your search

@@ -391,6 +391,17 @@ the explicit Re-issue action") and the re-issue itself then refused with 400 —
tier. **Not built:** releasing ONLY the token on host delete. The endpoint's only removal op
(`deprovision`) destroys the backups too, so a token-only release needs a new endpoint operation.
**[DESIGN — R-1295, hub v0.172.0 (rc.1 deployed for the proof) + tenantsync v1.4.0 (committed, not on ep0)] A new
token's self-check waits out PBS's permission cache, and a failure is never silent.** PBS's proxy caches permissions for
up to 5 s and an ACL-only change does not clear that cache; a request from another client between the token create and
its grant leaves the token refused (403) for up to ~5 s, and the script's one quick retry fell inside the window
(Tester4, 2026-10-26: rolled back, the box got no whole-guest off-site tier, only a log line). Measured on a throwaway PBS
4.2.5 under load: v1.3.0 failed 11 of 30, v1.4.0 0 of 30 (`audits/r1295-pbsdr-selfcheck-2026-10-26/`). The endpoint now
re-checks the same token (and its own admin token) for up to 15 s; the hub repeats a `self_check_failed` rollback (3
attempts, 7 s apart, a log line each) in the provision and the F-14 re-issue paths; a final failure of the WG hook raises
the operator-only `pbsdr_provision_failed` („… save the customer config to retry"). Live 2026-10-26 on hub rc.1: a fresh
Tester4 install provisioned the tier on the first try.
## 15. Customer e-mails — what the hub writes to a household [hub v0.118.0, R-558]
**The section this document did not have.** The hub composes every sentence a household reads before
@@ -255,6 +255,13 @@ the identity bundle's shape is `{tunnel_token, pbs_token, wg_private_key, restic
**[DESIGN] Off-site deletion custody (decisions 68–69, 2026-10-03) — BUILT hub v0.127.0 / controller v0.289.1, live on both demo boxes.** The restic repository password stays on the box only. The box's off-site key is append-only (pinned in `authorized_keys`, written by the hub — the key registrar); the box never receives the sub-account password, which the hub stores encrypted at rest. Old snapshots are pruned by the box itself, only in a weekly window the hub opens, behind a fake-snapshot guard (R-822); the window has run live on both demo boxes since 2026-10-05 (R-95, closed). The guard's residual — past-dated fakes steering the keeps, and a window check that trusts the box's own counts — is R-822 and R-895. **Hub v0.158.0 (built 2026-10-18; deployed the same day on the operator's yes — corrected 2026-10-24) adds the hub's own check:** the 07:10 key audit compares the repository's `snapshots/` listing, over the hub's own login, with yesterday's — files gone that no clean-up window explains raise `offsite_snapshots_vanished` (error), files no reported box run explains raise `offsite_snapshots_unexplained` (warning); both operator-only (R-895 slice 2). A Felhom-side pruner holding repository passwords is rejected.
**[FINDING — R-1294, 2026-10-26, read only; a rule change waits for the operator (STATUS D54)] A store that once goes
over the window's cap never comes back by itself.** The cap is half the store (hub `offsitekeys.MaxRemove`); each refused
window adds a week of snapshots and removes none, so the plan grows faster than the cap. demo-hp: one test day
(2026-10-16, 152 snapshots) aged past 7 days and the 10-26 window refused 209 > 179 — an HONEST plan (every removal 7–30
days old, every app keeps ≥ 9 or its last copies; `audits/r1294-offsite-cap-2026-10-26/`). The existing way out is the
operator's one-shot raised cap (R-833, `GrantOffsiteWindowOnceMax`).
**[DESIGN, built hub 2026-10-07 — R-32 option A, `09` §3 decision 167] What RESET does to the household's off-site
copy.** On the shared pool box a sub-account is a LOGIN, not the data: deleting it leaves its folder (measured
2026-07-21: 1.4 GB in two `.orphaned-*` folders after a RESET), and a re-enabled customer lands on the same folder
+43 -1
View File
@@ -596,7 +596,9 @@ normal use; work it does not explain is the sign of a hacked app. Numbers and me
(`upwatch.line_test_holds`), then the apps; a record whose app was removed while held is dropped (it used to stop the
50 % rule for good); qBittorrent's hold names its slot (`up_limit` / `alt_up_limit` through `app/preferences` —
`transfer/uploadLimit` follows the CURRENT speed mode, measured on 5.2.4), so the household's switch to the
alternative speed during the test never leaves 1 KiB/s behind. **[FACT]** the night's rig (BYO VM, agent 0.165.0,
alternative speed during the test never leaves 1 KiB/s behind. **[DESIGN — R-1292, controller `2619191`, unreleased]:**
the 50 % rule owns the NORMAL slot only (`up_limit` through `app/preferences`); the household's alternative limit is
never read or written by it, and a mode switch never reads as „the household changed it". **[FACT]** the night's rig (BYO VM, agent 0.165.0,
rc.1, two seeders to 9202's qBittorrent): with a mode switch mid-test both limits came back, the test refused
(the switch let qBittorrent upload); without, both held (qBittorrent in its alternative slot), 94.8 Mbit/s measured,
both limits back exactly (`audits/night-2026-10-25/rig/hold-watch*.txt`).
@@ -607,3 +609,43 @@ normal use; work it does not explain is the sign of a hacked app. Numbers and me
whatever the API says; not built. The box cannot tell LAN from internet bytes per app; a host-network container
(Samba) has no counter; the push floor is a lower bound, the weekly test is close to the line.
7. **Release:** released 2026-10-24 on the operator's yes (controller v0.342.0, hub v0.169.0; R-1244) — corrected 2026-10-24.
## 10. A held app the operator can see [DESIGN — proposed, R-1291, 2026-10-26; no code]
**Why.** A held app is stopped on purpose and stays stopped until a person acts — and today nobody is told after the
first minute. demo-hp's Kimai sat held for 11 days (2026-10-14 → 10-26) and was found by accident (R-1286). The
controller sends one event when the crash-loop guard stops an app (`app_stopped_unhealthy`); no other hold sends
anything, and the hub has no held-app state at all. On a household box the same silence would hide a failed restore.
**The holds that exist** (controller `settings.RestoreHold.Reason`, the sentences of `holdWhy`, R-1286):
| Reason | What put it there | Who may clear it |
|---|---|---|
| `restore_failed` | a restore whose rollback also failed | **a person only** (R-379: support, `--clear-restore-hold`) |
| `restore_mixed` | an off-site restore whose database replay failed | a person (the same) |
| `update_failed` | a failed update | the household, by restoring the app from its backup |
| `unhealthy_stop` | the crash-loop guard | the household's Start (one more try, `09` §3 decision 28) |
| `put_back_pending` | a restore waiting for its pre-restore copy | nobody — it clears itself when the drive comes back |
**What the controller reports.** The legacy report is frozen (hub rule), so the event channel carries it:
`app_held` when a hold is written (stack, reason, since — the hold's own `At`) and `app_hold_cleared` when it goes.
Because an event can be lost, the controller re-sends `app_held` for every hold still in place **once a day**; the hub
treats it as idempotent. Both types enter `allowedEventTypes` AND `customerMessages` (hu + en) in the same hub commit
(the coupling rule); `app_held` is operator-only in v1.
**Where the hub shows it.** The host page (and the customer page) get a „Held apps" list: app, reason in one line
(the `holdWhy` sentence), since, and who may clear it. Built from the open `app_held` events per customer and stack.
**When it mails.** One operator mail per hold after a grace that follows the reason: `put_back_pending` 6 hours (it
should have cleared itself — if not, something is stuck); `restore_failed` / `restore_mixed` 1 day (only a person can
act); `update_failed` and `unhealthy_stop` 3 days (the household can act first). Then one reminder a week while it
lasts, through the dispatcher's existing per-type cooldown (§6.2). A cleared hold sends nothing.
**Who clears it.** Unchanged: R-379 holds only a person; the household's Start or restore for its own two. **The hub
never clears a hold** — it only shows and reminds.
**Cost.** One controller release (the two events + the daily re-send, ~½ day with tests) and one hub release (allow-list,
the i18n keys, the list on two pages, a reminder checker; ~1 day). No agent change.
**Open pick (STATUS D53):** v1 operator-only (CC's pick), or also mail the household for the two holds it can clear
itself („press Start" / „restore from the backup")?
@@ -84,6 +84,10 @@ boxes installed. Nobody keeps a hand-written list.
one (R-1285). **[DESIGN — R-1285, agent `abcc1e6`, unreleased]:** right after every bundle install the agent asks the
wrapper now on disk for a policy pass (`mode: "bundle-policy"`), which removes our appliance-only files from a BYO
record (no signature: it only removes a file we wrote; a failing `sshd -t` puts it back).
**[DESIGN — R-1285 follow-up, agent `b1bc63d`, unreleased]:** in the normal delivery the bundle job runs in the OLD
process (measured on demo-hp 2026-10-26: the bundle 09:03:07–09:03:09, the restart into 0.166.0 at 09:03:10), so the
new agent also runs the pass once right after its self-update commits (`selfupdate.Manager.SetOnCommitted`). Only a
box coming from 0.164.0 can show it: 0.165.0's wrapper already applies D43 at plan time.
- **The Proxmox MAJOR upgrade** (PVE 9 → 10). It is a later step of its own, drilled first (R-808 item 5).
- **`cloudflared` and the other infrastructure images** (traefik, filebrowser). **[FACT] (C8)** They are containers in
the guest, pinned in the controller (`internal/infra/infra.go:26`: `cloudflare/cloudflared:2026.6.0`, since
@@ -0,0 +1,30 @@
# R-1294 — demo-hp's weekly off-site clean-up refused by its own cap (read only, 2026-10-26)
## The rule (code)
- Controller `internal/backup/offbox_window.go:132` `offsiteGuard`: refuses a future-dated snapshot, a snapshot newer
than the hub allows, a removal younger than `keepDaily` (7) calendar days unless superseded the same day (R-824/R-867),
and **a plan larger than `maxRemove`** (`:152`). The policy: keep-daily 7, keep-weekly 4, keep-monthly 6, grouped by
host+tags (`:47-49`, `:82`).
- Hub `internal/offsitekeys/service.go:288` `MaxRemove(countBefore)` = **half of what is there, at least 5**. An operator
one-shot grant may raise it for one window (`store/offsite_keys.go:179` `GrantOffsiteWindowOnceMax`, R-833: „after a
long gap the honest backlog exceeds half the snapshots and the default cap refuses every window").
## demo-hp's store (read only, the box's own transport; `restic snapshots` + `restic forget --dry-run`, `--no-lock`)
- 359 snapshots, 18 groups (one per app). restic's own dry-run plan: **keep 150, remove 209**.
- **Every removal is 7–30 days old; none is younger than 7 days.** Every group keeps snapshots: the nine long-lived apps
keep 11 each (remove 16), the five media apps keep 9 (remove 11–13), rallly/grimmory keep 2 (remove 4), nextcloud and
vaultwarden keep their 1 (remove 0). No group loses its last copy.
- The bulk: **2026-10-16 holds 152 snapshots** (that day's test runs on demo-hp); every other day ~14 (one per app). The
keep-daily rule keeps one per day, so the extra ~138 of 10-16 go, plus the normal ageing-out.
**Verdict: the plan is honest** — superseded copies by the keep rule, nothing it should keep.
## Can it get back under the cap by itself? No.
Hub window history (`offsite_windows`, hub DB with its WAL): demo-hp 10-12 pruned 190→100; 10-19 pruned 324→261; **10-26
refused** (359, plan 209 > cap 179). Each refused week adds ~98 snapshots (14 a day) and nothing is removed, so the cap
grows ~49 a week while the plan grows ~84 (the normal ageing plus the stuck backlog): the gap widens every week, and the
box mails the operator an error each Sunday. demo-felhom (the 2026-10-05 refusal was pre-R-867) and Tester 1 pruned
normally today (63→48, 78→57) — only demo-hp has this shape, caused by one day of 152 test snapshots.
Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

@@ -0,0 +1 @@
Install pressed 11:43:16Z
@@ -0,0 +1,19 @@
# R-1295 live proof on hub 0.172.0-rc.1 (2026-10-26): a fresh install as Tester4 (VM 9373 on demo-hp, public ISO 1.30.0).
Install pressed 11:43:16Z
bind 11:51:01Z (public /bind page, 200 Sikeres)
## hub log (channel A)
2026/10/26 12:51:01 [INFO] self-bind SUCCESS: appliance 53 bound to customer Tester4 by customer self-service (token 39e83db7…)
2026/10/26 12:51:49 [INFO] host enrolled: Tester4-b5ec39 (customer Tester4)
2026/10/26 12:52:19 [INFO] host-report from Tester4-b5ec39 (0 guests, 2 storage targets, 0 backups, 0 restore-tests, 0 pbs-snapshots, 12221 bytes)
2026/10/26 12:52:19 [INFO] tenantsync: provision ok for Tester4 (ns=Tester4, token_id=felhom@pbs!Tester4; secret withheld from logs)
2026/10/26 12:52:19 [INFO] pbsdr provisioned for Tester4 (host Tester4-b5ec39, ns Tester4, token_id felhom@pbs!Tester4, gen 2; secret stored consume-once, withheld from logs)
2026/10/26 12:52:19 [INFO] pbsdr auto-provisioned for Tester4 on WG registration (hands-free cascade)
2026/10/26 12:58:53 [INFO] Event from Tester4: backup_tier_skipped (warning) — Whole-guest backup tier felhom-pbs skipped: its storage does not exist on the host (never provisioned or removed). No app was stopped for it.
2026/10/26 12:58:53 [INFO] Operator email suppressed for Tester4/backup_tier_skipped — the box enrolled less than 1h0m0s ago
2026/10/26 13:07:24 [INFO] host-report from Tester4-b5ec39 (1 guests, 2 storage targets, 1 backups, 0 restore-tests, 0 pbs-snapshots, 18286 bytes)
2026/10/26 13:07:25 [INFO] pbs token secret consumed by host Tester4-b5ec39 (single-use; value withheld from logs)
2026/10/26 13:22:24 [INFO] host-report from Tester4-b5ec39 (1 guests, 3 storage targets, 1 backups, 0 restore-tests, 1 pbs-snapshots, 19649 bytes)
## ep0, read only (channel B)
token felhom@pbs!Tester4 present; ACL /datastore/felhom-offsite/Tester4: felhom@pbs!Tester4 + felhom@pbs DatastoreBackup; 0 proxy 403s for Tester4 since 11:50Z
## the box's own report (hub DB with WAL)
consumed_at=2026-10-26 12:07:25 report=2026-10-26 12:22:24 storages=['felhom-pbs', 'local', 'local-lvm'] pbs_snapshots=1
@@ -0,0 +1,12 @@
name: r1295-box
scsi0: nvme-scratch:9373/vm-9373-disk-0.raw,size=64G
scsi1: nvme-scratch:9373/vm-9373-disk-1.raw,size=48G
scsihw: virtio-scsi-single
hostname: pbs-r1295-loop
rootfs: nvme-scratch:9374/vm-9374-disk-0.raw,size=8G
purging VM 9373 from related configurations..
purging CT 9374 from related configurations..
0
0
0
Mem: 29 13 9 0 6 15
+7 -7
View File
@@ -145,7 +145,7 @@ stopping line that lies.
| **R-1215** | Apps & catalog | P4 | **One Immich photo upload hung 10 minutes with no answer and was not stored, while the box was healthy.** Night soak 2026-10-21 (`audits/night-2026-10-21/P1-soak/08-load/proof.log`): cycle 36, `IMG_20120021.jpg` sent 19:38:30 UTC, the uploader gave up at its 600 s read limit; Immich's database holds 302 photos for that user, the other cycles 303 (`P1-soak/09-immich-cycle36.txt`). At that moment memory „full" pressure was 3.9 % and `/api/health` answered in 0.26 s. 1 of about 26 000 uploads that night. Not diagnosed: Immich restarted at 02:16 for the off-site dump, so its log of 19:38 is gone. The Immich phone app retries a failed upload. | WATCHING — **2026-10-22 night:** not reproducible tonight; the Immich server log of 19:38 is gone (Immich restarted at 02:16 for the off-site dump). What the household sees: the upload fails after 10 minutes and the Immich phone app sends it again. | — | Watch the next long upload run with the Immich server log saved; file upstream only with a log | CC |
| **R-1267** | Apps & catalog | P4 | **Every recreate of Immich leaves one empty anonymous Docker volume behind: the image declares `VOLUME /data`, the template does not map it.** Found 2026-10-24 (review round 2, while putting 9202 back): 9202 holds 296 empty anonymous volumes; each night chain's recreate of `immich-server` made a new one (12:58, 13:00, 13:01 — the three test runs; the two dangling ones this session made were removed by name, nothing pruned). The photos are NOT affected: Immich picks `/usr/src/app/upload` whenever both folders exist (`detectMediaLocation` in the image's `server/dist/services/storage.service.js:28-39`), and the template binds that folder (`templates/immich/docker-compose.yml:49`) — NOT-A-FINDING for data placement. **Seen again 2026-10-24 night on Tester 1:** privatebin's image declares `/run`, `/tmp` and `/var/lib/nginx/tmp` as volumes — each recreate (the night dump's stop/start) leaves three empty anonymous volumes; the same class as Immich's `/data` (`audits/restore-holes-2026-10-24/P1-tester1/42-anon-volumes.txt`). | OPEN **2026-10-24 (catalog sweep):** the same class for OnlyOffice and RomM (RomM's image volume `/romm` holds only the empty mount points of its binds); after every catalog app had been removed WITH data, the sweep box still held 52 such volumes (PrivateBin 21, OnlyOffice 8, RomM 8, Immich 3, and 12 from 12:50–12:52 before the batch, holding the same nginx folders as PrivateBin's) — remove-with-data does not see them (no compose project label). All small (4–72 KB), no household data (`audits/catalog-sweep-2026-10-24/_box/anon-volumes.txt`). | — | declare the image's VOLUME paths as tmpfs or named volumes in the catalog (Immich, privatebin), or remove the dangling ones a recreate leaves, by name | CC |
| **R-1268** | Backup & restore | P3 | **Remove with „remove backups” leaves a restore's undo copy on the drive (`backups/restore-undo/<app>/`).** Seen 2026-10-24 night on Tester 1 (restore-holes Part 1, run 1): MeTube was held for support after a failed crash put-back, then removed with `remove_backups=true` — the 1.5 GB copy stayed (the removal deletes `backups/primary` and the second-drive mirror, not `restore-undo`), and nothing lists it (`audits/restore-holes-2026-10-24/P1-tester1/14-run1-teardown.txt`; removed by hand, CC's own test data). The copy is kept on purpose while a hold exists; after the app is gone it is orphaned. | OPEN | — | Remove with `remove_backups` also deletes the app's `restore-undo` folder (under the same path guard), or lists it as a kept copy | CC |
| **R-1292** | Apps & catalog | P4 | **qBittorrent's 50 % rule reads and writes the limit of the CURRENT speed mode: with the household's alternative speed on, it sets the alternative limit, and a later switch reads as „the household changed it" — the rule stops for good.** Measured 2026-10-25 night on qBittorrent 5.2.4 (`transfer/uploadLimit`/`setUploadLimit` follow the mode; `audits/night-2026-10-25/r1261-qbittorrent-api-measured.txt`); found while fixing R-1261 (the hold now names its slot). | OPEN | — | make the rule own the NORMAL slot only (`up_limit` through `app/preferences`, as the hold does) + a test with the alternative mode on (red first); one controller release | CC |
| **R-1292** | Apps & catalog | P4 | **qBittorrent's 50 % rule reads and writes the limit of the CURRENT speed mode: with the household's alternative speed on, it sets the alternative limit, and a later switch reads as „the household changed it" — the rule stops for good.** Measured 2026-10-25 night on qBittorrent 5.2.4 (`transfer/uploadLimit`/`setUploadLimit` follow the mode; `audits/night-2026-10-25/r1261-qbittorrent-api-measured.txt`); found while fixing R-1261 (the hold now names its slot). | **VERIFY** — 2026-10-26: controller `2619191` (unreleased) — the 50 % rule owns the NORMAL slot only (`up_limit` via app/preferences); test with the alternative mode on, red-proved; live on Tester 1 (rc `0.345.0-rc.1`; 9202 has no tested line, so the rule never acts there): `audits/r1292-qb-normal-slot-2026-10-26/`. **Earlier:** OPEN | — | make the rule own the NORMAL slot only (`up_limit` through `app/preferences`, as the hold does) + a test with the alternative mode on (red first); one controller release | CC |
## App updates — 5 rows (P2 1, P3 4)
@@ -212,8 +212,8 @@ stopping line that lies.
| **R-1232** | Backup & restore | P2 | **A reinstalled box may take its first whole-box copy to ep0 up to a week late: the old box's copy in the same household space seems to count as its last one.** Test night 2026-10-23 (`audits/night-2026-10-23/P4-testnight/`): the new Tester4 box (guest 9201) had its PBS tier applied at 19:28 UTC, but no whole-box run to ep0 came all night; the only archive in the household's ep0 space is the rehearsal box's `ct/9201` of 2026-10-20 (same guest number), and the tier's cadence is 168 h. The agent's due-check reads the newest archive for that vmid on the tier's storage (`localapi/server.go` `newestArchiveOn`) — so the old box's copy reads as „2.6 days old, not due”. If so, the ep0 copy is the OLD box's for up to a week. PLAUSIBLE — read from the code and the night's silence, not proven by a log line (the controller logs the due-check only at debug). | OPEN | — | Prove: read `GET /backup/tiers` for the box (the agent's answer per tier) on the next reinstall; fix: a tier's „last backup” counts only archives this host wrote (or after the host's enrollment); an agent test with a foreign archive in the namespace | CC |
| **R-1235** | Backup & restore | P3 | **The release copy on ep0 (D33) is proven to load, not yet to restore a registry — and it follows the newest versions, not the ones the fleet needs.** Built 2026-10-23 (`scripts/release-copy/`, `audits/day-2026-10-23/08-release-copy-D33.txt`). (a) `total-loss-of-dooplex.md` „Restore the release copy” steps 2 (skopeo push back, same digest) and 3 (agent packages back) were never walked into a real new registry — only a `docker load` on 9202. (b) The copy keeps the newest 3 image tags and 5 agent versions; it does not ask the hub for the floor and the vouched agent and always include them — a vouch more than 5 releases behind would fall out. (c) The golden (`felhom-golden`, 637 MB for 0.331.0) is not copied: a new install after a DooPlex loss needs it (the brief named images + agent packages only; one golden fits under the 3 GB cap). | OPEN | — | (a) a throwaway registry on demo-hp, push one tag back, compare digests; (b) read `/configuration`'s floor + vouched agent; (c) the operator says whether the golden goes in | CC |
| **R-1263** | Backup & restore | P4 | **Small items from review round 2:** a LOCAL restore's put-back mails the operator „its off-site restore FAILED” (`felhom-controller/controller/cmd/controller/main.go:1514-1518`, shared via `restore_unit.go:517-518`); Remove — even „keep my data” — deletes undeclared volumes labelled with the app's project, which no tier holds (`internal/stacks/delete.go:763-764`, R-1117); the capped copy's `--delete` reads the source in the daemon's mount view, the emptiness check in the controller's (`tier2.go:834` vs `:464`); stale comments — `felhom-agent/internal/localapi/backup_cap.go:15` („keep-last”) and `restore_crash_putback.go:32` (names a test file that does not exist); the qBittorrent password goes to whatever answers the DNS name `qbittorrent` (`upwatch/explain.go:94,108-113`); kept-copy Remove has a check-to-remove gap (`tier2_kept.go:92-111`); the release-copy restore check trusts hashes stored on ep0 (`runbooks/total-loss-of-dooplex.md`); rrsync without `-munge`/`-wo` (`audits/day-2026-10-23/08-release-copy-D33.txt:6`). | OPEN | — | fix in each repo's next attended pass | CC |
| **R-1294** | Backup & restore | P3 | **demo-hp's weekly off-site clean-up was refused by its own guard on 2026-10-26 (window 16): the plan would remove 209 of 359 snapshots, above the week's cap (179) — nothing was deleted, and each refused week adds to the next plan.** Seen in the operator mailbox during the 2026-10-26 release run (03:17Z, `offsite_prune_guard_refused`, error; details `count_before 359, count_after 359, outcome guard-refused`). The cap refusal is kept on purpose (R-824, R-867 closed the false ones); the open question is whether a store that once exceeds the cap can ever prune again by itself, or refuses every week while it grows. demo-hp is the Tier-0 box with many hand-made backups (sweeps, probes). Earlier cap refusals: demo-hp 2026-10-03 (window 1), demo-felhom 2026-10-05 (window 3). | OPEN | — | read the guard's cap rule (controller off-site prune) against demo-hp's snapshot list: is the plan honest (old, superseded) or not; if honest, say how such a store gets back under the cap (an attended one-time prune, or a cap that follows the store) — a design question for the operator if it changes what a box may delete | CC |
| **R-1295** | Backup & restore | P3 | **A re-enrolled customer's whole-guest off-site tier (PBS DR) can fail to provision at the fresh install and only the hub log says so.** Seen 2026-10-26 on Tester4's fresh install (golden 0.344.0 proof, host `Tester4-eb7371`): 10:31:15 CET `[ERROR] pbsdr auto-provision for Tester4 (WG-registration hook): pbsdr: F-14 auto re-issue for Tester4: tenantsync: endpoint refused: own-namespace list as felhom@pbs!Tester4 failed twice — rolled back (code self_check_failed) — save the customer config to retry`. No operator mail came (mailbox read 09:33Z: only the household's setup-code mail). On a household reinstall this would leave the box without its whole-guest off-site copy until someone reads the log. Not retried here: the retry lists ep0, fenced in this run. Evidence `audits/release-monday-2026-10-26/fresh-install/proof.txt`. | OPEN | — | read why the own-namespace list fails right after the re-issue (a token not yet valid on ep0? a namespace left by the deleted host?); make the rollback mail the operator (an event through the allow-list); retry once with the operator present | CC |
| **R-1294** | Backup & restore | P3 | **demo-hp's weekly off-site clean-up was refused by its own guard on 2026-10-26 (window 16): the plan would remove 209 of 359 snapshots, above the week's cap (179) — nothing was deleted, and each refused week adds to the next plan.** Seen in the operator mailbox during the 2026-10-26 release run (03:17Z, `offsite_prune_guard_refused`, error; details `count_before 359, count_after 359, outcome guard-refused`). The cap refusal is kept on purpose (R-824, R-867 closed the false ones); the open question is whether a store that once exceeds the cap can ever prune again by itself, or refuses every week while it grows. demo-hp is the Tier-0 box with many hand-made backups (sweeps, probes). Earlier cap refusals: demo-hp 2026-10-03 (window 1), demo-felhom 2026-10-05 (window 3). | **WAITING-ON-OPERATOR** — 2026-10-26 (read only, `audits/r1294-offsite-cap-2026-10-26/`): the plan is HONEST (restic's own dry-run: keep 150, remove 209; every removal 7–30 days old; every app keeps ≥ 9 or its last copies; the bulk is one test day, 2026-10-16, 152 snapshots). It can NOT recover by itself: the cap is half the store and grows ~49/week while the plan grows ~84/week. Only demo-hp has this shape (demo-felhom and Tester 1 pruned normally 10-26). The pick is STATUS D54. **Earlier:** OPEN | — | read the guard's cap rule (controller off-site prune) against demo-hp's snapshot list: is the plan honest (old, superseded) or not; if honest, say how such a store gets back under the cap (an attended one-time prune, or a cap that follows the store) — a design question for the operator if it changes what a box may delete | CC |
| **R-1295** | Backup & restore | P3 | **A re-enrolled customer's whole-guest off-site tier (PBS DR) can fail to provision at the fresh install and only the hub log says so.** Seen 2026-10-26 on Tester4's fresh install (golden 0.344.0 proof, host `Tester4-eb7371`): 10:31:15 CET `[ERROR] pbsdr auto-provision for Tester4 (WG-registration hook): pbsdr: F-14 auto re-issue for Tester4: tenantsync: endpoint refused: own-namespace list as felhom@pbs!Tester4 failed twice — rolled back (code self_check_failed) — save the customer config to retry`. No operator mail came (mailbox read 09:33Z: only the household's setup-code mail). On a household reinstall this would leave the box without its whole-guest off-site copy until someone reads the log. Not retried here: the retry lists ep0, fenced in this run. Evidence `audits/release-monday-2026-10-26/fresh-install/proof.txt`. | **VERIFY** — 2026-10-26 afternoon: **cause measured** — PBS's proxy caches permissions up to 5 s and an ACL-only change does not clear it; under concurrent requests the new token is refused for up to ~5 s and the script's one quick retry falls in the window (throwaway PBS 4.2.5 under load: v1.3.0 11/30 failed, v1.4.0 0/30; candidates ruled out with evidence, `audits/r1295-pbsdr-selfcheck-2026-10-26/`). Fixed: tenantsync v1.4.0 (`722013da`, waits up to 15 s, the admin token too) — **not installed on ep0** (an ep0 change, the operator's yes); hub v0.172.0 (`722013da`) retries a rollback 3×, 7 s apart, and a final failure mails the operator (`pbsdr_provision_failed`, operator-only). Red-proved. **Live on hub rc.1:** a fresh Tester4 install provisioned on the first try; the box consumed the credential 12:07:25Z and made its first whole-guest copy (1 PBS snapshot). Left: the hub release and the ep0 script install. **Earlier:** OPEN | — | read why the own-namespace list fails right after the re-issue (a token not yet valid on ep0? a namespace left by the deleted host?); make the rollback mail the operator (an event through the allow-list); retry once with the operator present | CC |
## Storage & devices — 13 rows (P3 4, P4 9)
@@ -278,10 +278,10 @@ stopping line that lies.
| **R-1004** | Box system & updates | P4 | **After an unclean stop every guest ext4 volume waits ~43 s in multi-mount protection before it mounts, so each restart after a power cut takes ~1.5 min longer.** Kernel: „EXT4-fs warning (device dm-6): ext4_multi_mount_protect: MMP interval 42 higher than expected, please wait" on dm-6/dm-7 and loop0/loop1, demo-hp and Tester 1, every round of `audits/night-power-2026-10-13/` (R1 `repairs.txt`). | **READY** — filed 2026-10-14 | — | CC: find where MMP is switched on for the guest volumes (Proxmox `pct` create, or our own mkfs), whether it protects anything on a one-host disk, and measure the restart without it on a scratch guest | CC |
| **R-1013** | Box system & updates | P4 | **After a power cut the agent's start-up stale-lock pass logs an ERROR „pct unlock failed … no lock found" when Proxmox's own start-all removed the lock in the same second.** The outcome is the goal (no lock), so the line is a false alarm in the log. Seen on demo-hp 2026-10-14 12:07:44–45 UTC (`audits/power-cut-fixes-2026-10-14/E/after-boot.txt`; Proxmox: „removed left over backup lock from '9201'!"). Small, but the agent's release for this session is spent (v0.156.1). | **OPEN** — filed 2026-10-14 | — | CC: treat „no lock found" from `pct unlock` as already clear (INFO), with a test; ride the next agent release | CC |
| **R-1061** | Box system & updates | P4 | **A per-customer floor reached Tester 1 only at its next 15-minute report, not in seconds.** Set 2026-10-15 12:55:51 UTC for tester-1, demo-hp and demo-felhom together (hub 0.153.0, CC's token): demo-hp and demo-felhom ran 0.314.0 within 13 s (the hub's wake); Tester 1 moved at 13:09:35, its regular report (`audits/gate-restore-2026-10-15/release/03-boxes-move.txt`). It had restarted at 12:54:29 onto rc.1 and logged „hub wait channel active". Why the wake did not reach it is not read (the wake line is DEBUG). No harm: the floor arrived. | **OPEN** — filed 2026-10-15 | — | Read Tester 1's DEBUG ring at the next floor change; compare with the hub's bump log | CC |
| **R-1285** | Box system & updates | P4 | **D43's removal of our SSH drop-in from a BYO host happens one bundle LATE: the bundle run that delivers agent 0.165.0 is planned by the box's OLD (0.164.0) wrapper, which writes the drop-in as before („same"), and only then replaces itself.** Measured 2026-10-25 on the BYO test host (VM 9371 on demo-hp, `Tester4-f967d4`; attended release run): 0.164.0 day-0 → signed `agent_update` + `agent_config_update` 0.165.0 → the planted drop-in still there; a second `agent_config_update` (same bundle) → `BUNDLE REMOVED … (ours, on a BYO host — D43)`, nothing written, `sshd -T` the owner's own `permitrootlogin yes` (`audits/release-2026-10-26/byo/byo-proof.txt`). No box is affected today (the fleet's only BYO host is this test host); a fresh BYO install on ≥ 0.165.0 runs the new wrapper from the start. | **OPEN** — 2026-10-26 (agent 0.166.0 released, attended run): the policy pass is wired, but in the normal delivery it does NOT run: the box gets `agent_update` and `agent_config_update` in ONE pass, and the bundle job runs in the OLD process (demo-hp: pid 1966780, agent 0.165.0, bundle 09:03:07–09:03:09 CET; the restart into 0.166.0 at 09:03:10) — no `bundle policy pass` line on any of the three boxes. So a BYO host coming from 0.164.0 still loses our drop-in one release late. The night proof (`audits/night-2026-10-25/rig/r1285-live.txt`) ran a bundle under an agent that was ALREADY 0.166.0-code, so it did not cover this order. No BYO host is in the fleet today. Fix options: the agent runs the pass once at start-up after `update committed`; or the release procedure sends the bundle job only after the commit. **Earlier state:** **VERIFY** — fixed on main 2026-10-25 night (agent `abcc1e6`), waits for the agent release + the vouch: the agent runs the wrapper's `bundle-policy` pass after every install (red-proved, `redproof-r1285.txt`). **Live on the rig in ONE signed run** (BYO host, staged as upgraded from 0.164.0: the new agent by hand, the old 0.164.0 wrapper on disk, our drop-in planted; one `agent_config_update` of a test bundle): the old wrapper installed the bundle and replaced itself, then „BUNDLE POLICY REMOVED … (ours, on a BYO host)", `sshd -T` the owner's own `permitrootlogin yes` (`rig/r1285-live.txt`); the test package deleted from the registry after. **Earlier state:** OPEN | — | either the wrapper plans again after it replaced itself (one agent change + a test that a 0.164.0→0.165.0 bundle on a BYO record removes our copy in ONE run, red first), or the release procedure sends a BYO box's config bundle twice; until then a BYO box upgraded from ≤ 0.164.0 keeps our drop-in until its next bundle | CC |
| **R-1285** | Box system & updates | P4 | **D43's removal of our SSH drop-in from a BYO host happens one bundle LATE: the bundle run that delivers agent 0.165.0 is planned by the box's OLD (0.164.0) wrapper, which writes the drop-in as before („same"), and only then replaces itself.** Measured 2026-10-25 on the BYO test host (VM 9371 on demo-hp, `Tester4-f967d4`; attended release run): 0.164.0 day-0 → signed `agent_update` + `agent_config_update` 0.165.0 → the planted drop-in still there; a second `agent_config_update` (same bundle) → `BUNDLE REMOVED … (ours, on a BYO host — D43)`, nothing written, `sshd -T` the owner's own `permitrootlogin yes` (`audits/release-2026-10-26/byo/byo-proof.txt`). No box is affected today (the fleet's only BYO host is this test host); a fresh BYO install on ≥ 0.165.0 runs the new wrapper from the start. | **VERIFY** — 2026-10-26: agent `b1bc63d` (unreleased) — the new agent runs the bundle-policy pass once right after its self-update commits (`selfupdate.SetOnCommitted`); test in the real order (commit, then one pass), red-proved. **Not live:** only a box coming FROM 0.164.0 discriminates (0.165.0's wrapper already applies D43), which needs a signed downgrade chain on a BYO box — the live proof rides the next agent release. **Earlier:** **OPEN** — 2026-10-26 (agent 0.166.0 released, attended run): the policy pass is wired, but in the normal delivery it does NOT run: the box gets `agent_update` and `agent_config_update` in ONE pass, and the bundle job runs in the OLD process (demo-hp: pid 1966780, agent 0.165.0, bundle 09:03:07–09:03:09 CET; the restart into 0.166.0 at 09:03:10) — no `bundle policy pass` line on any of the three boxes. So a BYO host coming from 0.164.0 still loses our drop-in one release late. The night proof (`audits/night-2026-10-25/rig/r1285-live.txt`) ran a bundle under an agent that was ALREADY 0.166.0-code, so it did not cover this order. No BYO host is in the fleet today. Fix options: the agent runs the pass once at start-up after `update committed`; or the release procedure sends the bundle job only after the commit. **Earlier state:** **VERIFY** — fixed on main 2026-10-25 night (agent `abcc1e6`), waits for the agent release + the vouch: the agent runs the wrapper's `bundle-policy` pass after every install (red-proved, `redproof-r1285.txt`). **Live on the rig in ONE signed run** (BYO host, staged as upgraded from 0.164.0: the new agent by hand, the old 0.164.0 wrapper on disk, our drop-in planted; one `agent_config_update` of a test bundle): the old wrapper installed the bundle and replaced itself, then „BUNDLE POLICY REMOVED … (ours, on a BYO host)", `sshd -T` the owner's own `permitrootlogin yes` (`rig/r1285-live.txt`); the test package deleted from the registry after. **Earlier state:** OPEN | — | either the wrapper plans again after it replaced itself (one agent change + a test that a 0.164.0→0.165.0 bundle on a BYO record removes our copy in ONE run, red first), or the release procedure sends a BYO box's config bundle twice; until then a BYO box upgraded from ≤ 0.164.0 keeps our drop-in until its next bundle | CC |
## Monitoring & notifications — 23 rows (P2 1, P3 16, P4 6)
## Monitoring & notifications — 24 rows (P2 1, P3 16, P4 7)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -307,8 +307,8 @@ stopping line that lies.
| **R-1223** | Monitoring & notifications | P3 | **After a reboot, a box whose tunnel ep0 refuses is invisible: the future stamp is gone, so neither the D30 re-key nor the hub's alarm sees it.** Fresh review 2026-10-23 night (`REPORT-night-2026-10-23.md`, A8 + H1): the kernel forgets `latest-handshake` when the interface restarts (0 = never); the agent then sends neither `last_handshake_age_s` nor `handshake_future_s`, and `checkWGTunnel` (`hub/internal/api/host_tunnel.go`) does not judge a registered, active tunnel with no handshake at all. Shape: the clock ran ahead, was corrected, the box rebooted (some households switch off at night), ep0 still holds the future stamp and starts nothing. | OPEN | — | Agent: report `tunnel_up_s` (how long the unit has been active) with no handshake; hub: a registered, active tunnel with no handshake for > 30 min while reporting → `wg_tunnel_stale`; the agent re-keys the same way after 30 min with no handshake. Tests both sides; rides an agent release + a hub deploy | CC |
| **R-1224** | Monitoring & notifications | P4 | **The operator's 1-hour mail cooldown for `wg_tunnel_stale` is per customer, not per host, and `wg_tunnel_ok` does not end it.** Fresh review 2026-10-23 night (`REPORT-night-2026-10-23.md`, H3): `notify/dispatcher.go:620` keys the cooldown `customerID:eventType`. Two hosts of one household going stale within an hour, or stale → ok → stale, store a second event row but mail once (the suppression is in `notification_log`). | OPEN | — | Key the cooldown by `host_id` from the details for host-scoped operator events, and let the matching ok event end it; a dispatcher test with two hosts; rides a hub deploy | CC |
| **R-1233** | Monitoring & notifications | P4 | **`[ERROR] [stacks] execCommand failed: exit status 1` names no command.** Seen once a night (2026-10-21 00:30:49 and 2026-10-23 02:17:14 UTC, each right after an app stopped for its dump); the command and stderr go only into the returned error, which the caller may drop (`internal/stacks/manager.go:1757`). An ERROR nobody can judge. | OPEN | — | Log the command's first two words (`docker inspect`, never the arguments — `docker exec … psql` can carry a password) and the stderr's first line; find the caller of the nightly one | CC |
| **R-1291** | Monitoring & notifications | P3 | **A held app gives the operator no lasting signal: one event at the stop, then nothing — demo-hp's Kimai sat held 11 days unseen.** Found 2026-10-25 night (R-1286). No held-app state reaches the hub (the report carries none; the hub shows none); the crash-loop stop sends `app_stopped_unhealthy` once. On a household box an app held after a crash loop, a failed update or a failed restore stays stopped until someone opens its page. | OPEN | — | a two-repo design: the controller reports its holds (stack, reason, since), the hub shows them on the host and mails a reminder after N days (the D-level of the reason); one row's worth of design first | CC |
| **R-1291** | Monitoring & notifications | P3 | **A held app gives the operator no lasting signal: one event at the stop, then nothing — demo-hp's Kimai sat held 11 days unseen.** Found 2026-10-25 night (R-1286). No held-app state reaches the hub (the report carries none; the hub shows none); the crash-loop stop sends `app_stopped_unhealthy` once. On a household box an app held after a crash loop, a failed update or a failed restore stays stopped until someone opens its page. | **WAITING-ON-OPERATOR** — 2026-10-26: the design is written (`08` §10 [DESIGN — proposed]: `app_held` / `app_hold_cleared` events + a daily re-send, a „Held apps" list on the host page, an operator reminder after a grace per reason, the hub never clears a hold; ~½ day controller + ~1 day hub). The pick is STATUS D53. **Earlier:** OPEN | — | a two-repo design: the controller reports its holds (stack, reason, since), the hub shows them on the host and mails a reminder after N days (the D-level of the reason); one row's worth of design first | CC |
| **R-1296** | Monitoring & notifications | P4 | **When ep0's tenancy script refuses, the hub log shows only its one-line error — PBS's own reason (the 403 `missing Datastore.Audit|Datastore.Backup on …`) stays in the script's stderr, which the hub drops on a refusal.** Found 2026-10-26 diagnosing R-1295: the cause was visible only by reading ep0's proxy journal over root SSH. `hub/internal/tenantsync/client.go` `parseResponse` returns the JSON error and ignores stderr; the script keeps secrets off stderr by design (its header), so the tail is safe to log. | OPEN | — | append the last ~3 stderr lines to the refusal error (bounded, never stdout); a test that a secret on stdout never appears | CC |
## Hub & operator — 12 rows (P3 3, P4 9)