Files
felhom.eu/documentation/backlog/OPEN-ITEMS.md
T
admin 7383400a23 docs: file R-29 to the register; attach the gate-orphan instance to its class
d4c07873 filed "hostinstall_gates.py is invoked by nothing" as a novel
observation. It is not novel — R-29 already names the class (green gates are
enforced nowhere; one sat RED for 16 releases while every REPORT said green),
and R-29 was missing from OPEN-ITEMS.md entirely, having never been carried
across the 2026-07-27 register rebuild. An open item about work not getting
done was absent from the page that decides what gets done.

Ruling on whether R-29 is the right home for a non-design-v2 gate: YES. Its
title says design-v2, but its own audit list already spans mount-safety,
secrets and dedup gates across four repos, and its part (b) — "the systemic
half is the real item" — is about the enforcement mechanism, which is
gate-agnostic. hub_confirm_gate.py is already on its list and sits in the same
scripts/ directory. No new ID minted; R-29's own text forbids it, and this is
the third re-raise it has absorbed.

- OPEN-ITEMS.md: open R-29 (READY, S(a)/M(b)), with the orphan evidence and
  the two separable parts R-29 already defines.
- OPEN-ITEMS.md: R-94 leg (b) now points at R-29 as its class.
- ROADMAP.md:158: audit list extended with hostinstall_gates.py (RED today,
  1.19.0 != 1.22.0) + hub_confirm_gate.py verified orphan. Entry not rewritten.
- ROADMAP.md:147: cited a non-existent R-164 — it means controller v0.164.0.
- CONTEXT.md:564: asserted in the present tense that the version cross-check is
  "gated by scripts/hostinstall_gates.py". It exists, is red, and runs nowhere.
- OPEN-ITEMS.md: READY #1/#3/#4 markers dropped — they duplicated ranked-list
  positions and the gap was left by the row merged in d4c07873.
- OPEN-ITEMS.md: E-2d citation :322-341 widened to :322-343; the invocation it
  describes is at :343, two lines outside the old range.
- backlog/README.md: two-line lead naming OPEN-ITEMS.md and ROADMAP.md.
- REPORT-record-correction-2026-07-29.md: the report CLAUDE.md:82-87 requires
  for both commits. Root REPORT.md (E-2 increment 1) untouched.

No gate wired, fixed, run or deleted — that is R-29 part (b), its own task.
Documentation only. No version bump, no CHANGELOG entry, no box touched.
2026-07-29 11:13:23 +02:00

27 KiB
Raw Blame History

OPEN-ITEMS — the single source of truth for open work

Rebuilt 2026-07-27 by read-only triage. ROADMAP.md keeps the full history and reasoning; this page keeps only what is open, and it is the file to read first. REPORT.md is per-session and overwritten — nothing durable may live only there.

State: BLOCKED · READY · WAITING-ON-OPERATOR · WATCHING. Every row has an owner.

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 SHIPPED + PROVEN-LIVE (agent v0.105.0 + controller v0.178.0, 2026-07-27) age_state=unknown captured on real hardware during a deliberate ep0 outage; controller deferred, zero app stacks stopped
E-2d Prove E-2 on a fresh VM on the t740 — the only remaining route to four unproven items: a real felhom-host-install.sh 1.22.0 run (never done), Case B naturally (single-drive install renders the degraded banner without degrading a live box), a claimable customer so the three claim-gated items stop being gated, then add a drive (the offer) and unplug it (backup_target_absent end-to-end) READY (M) Space checked 2026-07-29 — NOT a blocker, with one constraint: the VM disk must NOT go on local-lvm. That thin pool is over-subscribed (144 GB allocated against 54 GB, 38.8% used) on a box running a live customer guest, and a full thin pool corrupts every guest on it. local has only 23.7 GB and sits on pve-root. Use /mnt/nvme-1tb (888 GB free). Caveat found: a dir storage at a SUBDIRECTORY there will fail the agent's exactMount check and report disconnected in the host report — cosmetic, but decide the placement deliberately. Do NOT unblock drill-r50 (deliberately blocked; unblocking it means the fixture stops representing anything real). CORRECTED 2026-07-29 — the ISO is not an obstacle to the 1.22.0 proof, it is the best route to it. felhom-bootstrap.sh:96 fetches from https://felhom.eu/scripts/felhom-host-install.sh, not the hub, and that URL serves 1.22.0 (git-sync from main, ≤30 s — live fetch confirmed). A fresh ISO install therefore exercises 1.22.0 automatically — which makes the ISO leg the stronger proof, because it is the real customer path rather than a proxy for it, and it retires the "run 1.22.0 manually" step. The Phase 0 question that decides the shape is ANSWERED — it was a source read, and the answer is yes. A fresh VM with no baked customer-id lands in PAIRING mode (felhom-bootstrap.sh:537-541), not DIRECT (:312), and only DIRECT passes --customer-id / --mode / --passphrase-file. But on a 200 from /api/v1/appliance/poll the pairing loop writes the hub-delivered FELHOM_CUSTOMER_ID + FELHOM_RETRIEVAL_PASSPHRASE into the 0600 env, re-sources it and calls run_direct in the same invocation (:495-499) — so pairing reaches the identical installer invocation (:322-343 — the single $INSTALL_URL fetch at :322-330, the --customer-id/--mode/--hub-url/--passphrase-file args array at :334, and the bash "$SCRIPT_TMP" "${args[@]}" call itself at :343) and the customer it yields is the one the operator bound, i.e. claimable. So the ISO leg is the spine; a manual 1.22.0 run is not needed as a separate scenario CC
R-94 A hand-synced version constant drifts, and the gate that would catch it is never runhub/internal/web/configs.go:28 pins hostInstallVersion = "1.19.0" while scripts/felhom-host-install.sh:187 is SCRIPT_VERSION="1.22.0" READY (XS) CORRECTED 2026-07-29 — the earlier framing of this row was false and is retracted. The constant selects no script: its only consumers are configs.go:487 (ScriptVersion) and render_test.go:219, and it renders as a label at customer_unified.html:494. The install command beneath that label fetches https://felhom.eu/scripts/felhom-host-install.sh (customer_unified.html:563, :1262), which the website git-syncs from main on a 30 s period (manifests/webpage.yaml) — so 1.22.0 is what every install already gets (live fetch, 2026-07-29). Every flag the generator emits is parsed by 1.22.0 (customer_unified.html~:1210:1238 vs felhom-host-install.sh:1177:1210): no functional gap, only a wrong number on the operator's screen. Three legs, all XS: (a) derive the label from SCRIPT_VERSION rather than hand-syncing it, or delete it; (b) scripts/hostinstall_gates.py fails today and is invoked by no Makefile, hook or CLAUDE.md — wire it next to site_gates.py or delete it, because a gate nobody runs reads as coverage it is not providing (this leg is one instance of → R-29, which is the class: gates are enforced nowhere, and the enforcement decision belongs there, not here); (c) render_test.go:219 compares the constant to itself and passes at any value — replace it with the cross-file assertion. No longer blocked on E-2d — it never gated anything CC
R-110 main is the installer's publish channel — there is no staging. manifests/webpage.yaml git-syncs /scripts/ from --branch=main on a 30 s period and nginx serves that working tree directly (location /scripts/, root …/current). So pushing scripts/felhom-host-install.sh is publishing it: within thirty seconds it is what every subsequent felhom-bootstrap.sh fetch (scripts/iso/felhom-bootstrap.sh:96) and every operator-run day-0 command (customer_unified.html:563) receives. There is no tag, no pinned-version path, no staging copy and no rollback other than another push — for the artifact that runs as root on a virgin box, the single most privileged thing Felhom ships WAITING-ON-OPERATOR (S) operator ruling Two consequences worth stating: E-2d is not a gate before exposure — 1.22.0 has been the live installer since it hit main on 2026-07-29 — and the precaution recorded on the old R-94 row as "do not point every new box at an installer that has never run" was never available to take. Open question for the operator, not a defect to fix blind: whether /scripts/ should serve a pinned release (tag-tracked path, or a versioned directory with the customer command naming a version) or whether main-tracking is the accepted shape for a one-operator product. Exposure today is zero — there are no boxes installing — which is exactly why it is cheap to decide now CC
R-29 The green gates are not enforced anywhere — one was RED for 16 releases before anyone ran it. This is the class, not an instance: a gate that exists, asserts something true, is red, and is invoked by nothing reads as coverage it is not providing. controller/scripts/docker_run_volume_path_gate.py failed continuously from 2026-07-14 (v0.129.0) until R-7b's close-out ran it by hand at v0.145.0 — sixteen releases in which every REPORT said "green" READY (S for (a) / M for (b)) This item has existed at ROADMAP.md:158 since before the register was rebuilt (2026-07-27) and was never carried across — that omission is itself part of the finding, because it is an open item about work not getting done that then went missing from the page that decides what gets done. Two separable parts, per R-29's own analysis: (a) the docker_run_volume_path_gate finding is benign and the fix is a 3-line ALLOWLIST addition with its why — not a rewrite of the flagged call — and it gets its own reviewed diff, never bundled into a feature commit; (b) the systemic half, the real item: decide where gates run (pre-push hook, build.sh step, or CI) and make a red gate block the train the way the Go green gate does. Two further orphans confirmed 2026-07-29 by repo-wide grep across all file types + sibling repos + ~/.claude settings/skills/hooks + .git/hooks (none non-sample) + Makefile/justfile/Taskfile find (only hub/Makefile, zero gate occurrences) + CI-directory find (this repo has no CI at all) — every one of the 19 hits is a docstring, a code comment or prose, and not one is an invocation: scripts/hostinstall_gates.pyRED today (hub Setup-tab hostInstallVersion=1.19.0 != SCRIPT_VERSION=1.22.0, exit 1), the same finding as R-94 leg (b) — and scripts/hub_confirm_gate.py. Of the four gates in scripts/, only site_gates.py is mandated anywhere (CLAUDE.md:153) and manifest_bearer_gate.py is named in runbooks/secrets.md:76. In R-29's own words, carried forward deliberately: do not mint a new ID for a new instance — the 2026-07-18 rehearsal independently re-raised this item and no second ID was minted then either CC
R-95 restic offsite credential can delete (readonly=False, forget --prune runs from the box); SFTP cannot express append-only READY Root exposure still open. Mitigation now ARMED — split prune off-box or move to REST --append-only CC
R-86 Restore-tests are interval-scheduled, not backup-aligned READY 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 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 survived its first reboot 2026-07-27; 3.8 GB RAM unchanged BLOCKED (interim proven) Hetzner CX33 availability — confirmed unavailable even powered OFF, so it is the Cost-Optimized "Limited availability", not the power state Re-check CX33; escape hatch if urgent = CPX/CCX lines (no availability warning, higher cost) operator
R-91 Old 13 GB datastore copy at /srv/pbs-felhom on ep0's root disk WATCHING demo-felhom's first post-migration PBS backup Delete once it lands; fix CONTEXT.md:1018 same commit CC
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/v0.79.0, 2026-07-27) v0.79.0 (R-97c) replaced a FALSE operator-only comment with a real operatorOnlyEvents register
F-CRIT-2 A failed offsite backup left a phantom snapshot (1 B, manifest-less, NEWEST) that RESET the tier's freshness clock — 7 days silent on the real 168h cadence, invisible to both the R-88 breaker and the hub deadline monitor SHIPPED + PROVEN-LIVE (agent v0.106.0, 2026-07-28) NewestArchiveTime now counts only plausibly-complete entries (measured 1 MiB floor; undecidable ⇒ not counted). Campaign fault 2 replayed on demo-hp: phantom rejected + logged once, tier correctly DUE and backed up, and no thrash on the inverse
R-99 Server-side prune never removes a phantom snapshot. Confirmed it does NOT count them toward keep-last (dry-run kept 2 real + the phantom) so there is no retention/data-loss bug — but one accumulates per aborted upload, forever READY (S) Decide a cleanup path. Deletion on a customer datastore is a separate ruling — detection shipped, removal deliberately not automated CC
F-CRIT-1 An app that fails to restart after a quiesce never alarms on any channel — restartAll discarded the error AND StateStopped was whitelisted on invariant I1, which the quiesce path had made false SHIPPED + PROVEN-LIVE (controller v0.179.0, 2026-07-28) Both causes fixed. Live on demo-hp: alarmed 9s after grace expiry, banner shows (stopped); a deliberate user stop stayed silent through 9 dead-app scans
F-A1 A restore-test in flight made a healthy backup report as FAILED (HTTP 409 read as a tier failure): breaker armed + operator emailed, on both boxes SHIPPED + PROVEN-LIVE (controller v0.179.0, 2026-07-28) 409 → contention: tier stays DUE, dropped before anything stops (15m), and BLOCKED alarm if contention outlives the agent's 120m ceiling (3h). Hub DB: 409 → 0 operator emails, real failure → 1
R-100 A restic offsite tier that fails every night never goes stale on the hub — isStale counted from LastRun, which the controller writes unconditionally on failure SHIPPED + PROVEN-LIVE (controller v0.181.0 + hub v0.80.0, 2026-07-28) Anchored on a new last_success. Severity corrected during Phase 0: this was NOT a silencebackup_failed does fire nightly and reaches the operator (live DB: 5 sends). The real defect is defeated defence in depth: the hub-side pull net was anchored on a field the failing controller keeps refreshing, so it could not compensate for a lost push (cf. F-HUB). Live on demo-hp: induced failure → last_run advanced 11:25:48Z, last_success held 11:24:20Z; demo-felhom healthy → anchor advanced. Legacy degrade logged once per customer, live
R-101 Tier-2 LastRun is written on failure and rendered to the customer as „Legutóbbi másolat" — including in the restore confirm dialog SHIPPED + PROVEN-LIVE (controller v0.182.0, 2026-07-28) CrossDriveBackup.LastSuccess + SuccessTracked; the dialog names the last successful copy and discloses a failed newest attempt. Legacy rows migrate truthfully on first touch (an ok row adopts its time; an error row seeds nothing) — without the marker all 7 fleet rows would have flipped to „Még nincs sikeres másolat" on deploy. Part 2: the three record* sites rebuilt the whole struct; replaced by tier2Update (copy-and-overlay, safe by construction) — the naive fix would have had recordTier2Failure CLEAR the anchor. Live on demo-hp, rendered dialog read in both states
C9-F1 Tier-2 „Fájlok visszaállítása" is offered for apps whose copy has no restorable file leg; stops the app, restores 0 files, reports „Nincs hiányzó fájl — minden fájl megvan a helyén." SHIPPED + PROVEN-LIVE (controller v0.183.0, 2026-07-28) Phase 0 sized it: 43 of 53 catalog apps read NOTHING, 9 read file legs but never their DB/volumes, 1 stateless. Honesty half shipped: Tier2RestoreCoverage refuses UP FRONT without stopping the app and NAMES the working action; a run that proceeds claims only what it examined and discloses that the database and volumes are not covered. Live on demo-felhom: bookstack refused, uptime stayed „Up About an hour" (was „Up 25 seconds"); paperless A1 re-run still byte-identical, 16/16 docs clean
C9-F2 An app in a Docker crash loop never alarms on any channel; StateRestarting is in no down-set SHIPPED + PROVEN-LIVE (controller v0.183.0, 2026-07-28) StateRestarting deliberately NOT added to IsDownState (that alarms on every deploy fleet-wide); a SUSTAINED run becomes down after crashLoopAfter=5m, set above the 120s deploy timeout, Mealie's 60s start_period and R-97b's 180s grace. Dashboard counter uses the same predicate so it no longer contradicts the alarm. Red-proof that matters: the naive IsDownState change fails the brief-restart test
C9-F3R-104 An interrupted offsite run leaves an exclusive restic lock the self-heal cannot reach: resticStep (offbox.go:634-648) has unlock --remove-all, but ensureOffboxRepo's probe fails first, classifyResticProbe (offbox.go:77-93) has no lock case → "other" → fail-fast. Tier dead until a human unlocks; ClassifyOffsiteFailure likewise has no lock case so the operator is told „A távoli mentés ismeretlen okból nem sikerült" for a precisely-known, self-healable condition READY (MEDIUM) Add a lock case to both classifiers and let the probe path escalate to unlock --remove-all. Answers Phase C item 8: the repo is NOT usable after a killed run. Cleared manually this run; tier proven working again (ok, 1m35s). Reachable by any interruption — container restart, OOM, host reboot mid-backup CC
C9-F1bR-103 Tier-2's restore cannot cover 43 of 53 apps; the action that CAN is the keep-side unit restore (POST /backup/restoreRestoreFromRecoveryUnit, replays volume tars + DB dumps). v0.183.0 NAMES it in the refusal text but does not route to it READY Put the working action in the card the customer already opened. Deliberately its own task: it places a DESTRUCTIVE operation (overwrites live data with the backup state) behind a button reached via a NON-destructive one, so the confirm copy must carry that difference — the reason it was not folded into v0.183.0 CC
C9-F4R-102 Nothing reads the Tier-2 copy's recovery-unit/ mirror. It is written by EVERY Tier-2 run (tier2.go:369, „Unit leg (always)") and read by no code path: RecoveryUnitPath resolves to backups/**primary**/ (appbackup/paths.go:46-48), and the only reader of the secondary tree is tier2_restore.go:79, which reads hdd/+userdata/ only READY (potentially > C9-F1) Tier-2 exists for the case where the PRIMARY drive is lost — and in exactly that case the primary unit is gone while this mirror survives on the second drive, unreachable by any customer action, leaving offsite as the only route. Verified by enumeration: 6 references to "secondary" in the tree, one writer, one reader, one wipe-warning lister CC
R-108 Network storage can host an app's namespace, and FileBrowser binds a network share at its ROOT. Local drives are userdata-scoped (web/handlers.go:2450-2460); network paths are bound at the share root (:2432) and served with download: true. No IsNetwork() filter guards the deploy dropdown (settings.go:904-914), the per-app migrate targets (handlers.go:674-679), or handleStorageMigrateApp (storage_handlers.go:410-424 — its whole-namespace sibling DOES refuse, :397) READY — BLOCKS an architectural target This is why D5 was not adopted in the 2026-07-28 07-backup-architecture.md rewrite: D5 moves app secrets into the local recovery unit so Tier-1/Tier-2 restore stop needing the guest, and that is safe only if no browsing surface can reach the backup tree. Every other surface was verified clean (SMB both namespace shapes, FileBrowser for drives, .fab import + download, /api/debug/*, all three ServeFile sites, storage-path add) — 07 §10.1 has the full sweep. Not a leak today (the unit's app.yaml is secret-stripped). Verified LIVE in demo-hp's generated FileBrowser compose CC
F-DIAG Four distinct offsite failure causes collapse into two operator-visible strings SHIPPED (controller v0.182.0, 2026-07-28) ClassifyOffsiteFailure → quota / orphaned / no_repo / no_units / transport / unknown, each with its own Hungarian message. Unclassifiable says so rather than being folded into a neighbour. Secrets: the old message was a raw err.Error() passthrough carrying sftp:<user>@<host>:<path>; redaction is now by the target's actual host/user/path (a first regex-only attempt leaked on a bare hostname and its own test caught it). Unit-proven; not yet exercised by a live offsite failure of each class
F-OPS A manual pct restore inherits the source guest's bind mounts — during a real DR, on a different host, under pressure DOCUMENTED (2026-07-28) documentation/runbooks/RUNBOOK-manual-guest-restore.md: which mpN are volumes vs host binds, the mp9 source-VMID trap (it can bind another guest's bootstrap credentials), strip-and-re-add before first boot, and a positive pre-start verification. Docs only by design — the agent already neutralises binds on its own restore paths, and a second implementation would drift
F-REBOOT A guest rebooted during its backup does not come back — shutdown completes, start never happens, no self-heal; 9m47s total appliance outage with every alarm silent SHIPPED + PROVEN-LIVE (agent v0.107.0, 2026-07-28) 60 s guest-power watchdog; onboot is the deliberate-stop discriminator (already the stale-lock path's, and what pve-guests consults), retry bounded 3x/1m-2m-4m then escalates once. Live on demo-hp: 120 s unattended vs the incident's 587 s with a human; Scenario B proven (an onboot:0 guest left stopped)
F-LEAK A failed restore-test cannot destroy its own scratch guest (403 VM.Allocate); the 10-slot VMID band shrinks silently SHIPPED + PROVEN-LIVE (agent v0.110.0 + host-install v1.21.0, 2026-07-28) Three attempts, two refuted live. (1) Pool adoption: PUT /pools/{pool} also needs VM.Allocate on the VM — membership cannot bootstrap its own authority. (2) Per-path /vms/990000..990009 ACLs: work, but PVE's destroy calls remove_vm_access (LXC.pm:906) which deletes every ACL at /vms/<vmid>consumed by the op it authorises, one use per slot. (3) SHIPPED: 4th root-fenced exception, band enforced in sudoers literally (pct destroy 99000[0-9] --purge) + in code + at the caller; API destroy still tried first. Live: band PERMITTED, 9201/9100/9999/990010/1 REFUSED, and pct start 990000 REFUSED too
F-OBS deadapp-check leaves NO positive observable on a default (info-level) box — "no alarms" was indistinguishable from "never ran" SHIPPED + PROVEN-LIVE (controller v0.180.0 + agent v0.109.0, 2026-07-28) INFO summary every 20th scan carrying scans/evaluated/down. Agent v0.109.0 fixes the same shape in the guest-power watchdog shipped hours earlier in v0.107.0 — it logged only at startup and when it acted, so its health could be read only from absence
E-2 Drive-role machinery around the moved vzdump target SHIPPED (hub 0.81.0, agent 0.113.0, controller 0.185.1, host-install 1.22.0 — 2026-07-29) Parts 15 complete. Role model + offer-only assignment + Hungarian degraded banner + absent-target signal + installer Case A/B. NOT yet live-proven: the DEGRADED banner and the offer acceptance (both demo boxes are healthy, so neither state occurs naturally) and backup_target_absent end-to-end. Installer is installer-logic-tested, not install-tested — no reinstall was performed CC
E-2a The target move needs a root-fenced wrapper — the agent cannot do it SHIPPED + PROVEN-LIVE (agent v0.113.0 + host-install v1.22.0, 2026-07-29) felhom-backup-target-apply behind a literal FELHOM_BACKUPTARGET sudoers alias; the agent's PVE role was NOT widened. Enforces F-1 (mountpoint -q) and F-2 (is_mountpoint 1 hardcoded), refuses a root-device target, has NO storage-removal path (grep-assertable), is idempotent and refuses to repoint. All five laws proven live as root on demo-hp with 0 stray storages
E-2b NotifyStorageDisconnected/Reconnected defined and called NOWHERE — a drive going absent emitted no event on any channel SHIPPED + PROVEN-LIVE (controller v0.184.1 + agent v0.112.0 + hub v0.81.0, 2026-07-29) Seam wired in ReconcileDriveGates; a target drive raises the specific backup_target_absent instead. A keying bug was caught before deploy: a.Path is the registered GUEST path, not the agent's host MountPath, so the target branch was unreachable — every absent drive, target included, fell through to the generic event (v0.184.1). Tests observe the WIRE (httptest hub), not a mock
E-2c E-1 put the whole-guest backups on a drive POST /disks/eject would eject SHIPPED + PROVEN-LIVE (agent v0.112.0, 2026-07-29) Eject + decommission refuse 409 on the backup-target mount, naming the storage and the remedy. Live on BOTH boxes: demo-hp /mnt/nvme-1tb and demo-felhom /mnt/hdd_1 both refused, drives unmoved. NOT a role reclassification — RoleForStorage untouched, because on both boxes that drive is ALSO the enrolled user-data drive; TestEjectStillAllowedOnANonTargetDrive pins the non-over-correction and /var/lib/vz is still refused by the PRE-EXISTING role gate, not this one
PETI peti-felhom deliberately NOT migrated. Its whole-guest backup still shares a device with its guest, so a drive failure there is offsite-only recovery ACCEPTED RISK — parked operator's next visit (tester reinstalling from scratch) Accepted until the reinstall; re-evaluate if that slips past ~2026-09-01. Do not migrate, do not touch operator
R-109 The DR recipe records no backup target. It lists every storage's name/type/content but never which one holds the local archives — and each demo box now carries TWO content=backup dir storages, felhom-backup (live) and local (frozen 2026-07-28 archives) READY (XS) Add the resolved BackupTarget() to the host-half. Third recipe-completeness defect beside R-105/R-106 CC
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-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.
  2. R-94de-ranked 2026-07-29. The prior rationale ("until it moves every hub-driven install gets the pre-R-82 default") was false: the constant selects no script and every install already fetches 1.22.0. What remains is a wrong label plus two pieces of dead safety equipment — a drift gate nobody runs and a test that compares a constant to itself. Cheap and worth doing; not high-consequence, and it blocks nothing.
  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.
  5. R-110 — last because it is not a READY row: the ruling is the operator's, not CC's, and there is nothing for CC to build until it lands. Ranked here rather than omitted because it is the only item on this page about the publish channel of the most privileged artifact Felhom ships, and today's exposure is zero — which makes now the cheapest moment it will ever be to decide.