storage identity build A: 16 accepted + §13 build status; D14; close R-1072/R-1074/R-1075; open R-1083/R-1084; live evidence
gates / gates (push) Successful in 6m44s

The operator ruled D1-D6 as recommended. Build A shipped (agent v0.157.0, controller v0.317.0 MinAgent 0.157.0,
hub v0.154.0): live on Tester 1 (unknown/vm, the "cannot check" notice, copies made not counted), demo-hp and
the N100 (proven, different, unchanged). NAS notice and the no-drive sentence are unit-proven only.
STATUS: D10 removed; vouch agent 0.157.0 and rule on R-1082 added. Poster: the Tier-2 line, text only.

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-15 21:49:07 +02:00
parent bf99798c96
commit 314308b4aa
32 changed files with 2280 additions and 13 deletions
+8
View File
@@ -26,6 +26,14 @@
---
## 2026-10-15 (night) — storage identity build A (agent v0.157.0, controller v0.317.0, hub v0.154.0)
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-1074** | **Tier-2 decided „a different disk” by `st_dev`, so virtual disks on one host disk — and two partitions of one disk — counted as a real second copy.** (P3) | CLOSED 2026-10-15 — **agent v0.157.0 + controller v0.317.0** (`architecture/16-storage-physical-identity.md` §4, operator rulings D1/D2): the controller asks the agent (`POST /disks/same-disk`); same disk → no copy; unknown (a VM) → the copy is made but not counted, and the page says why; proven different → counted. An older agent keeps the `st_dev` rule (logged once). Unit tests red-proved. **Live:** Tester 1 (`unknown/vm`) — the „nem tudja ellenőrizni” notice, 10 apps copied and marked „nem számolja második másolatnak”; demo-hp and N100 (`proven`, different) — copies counted, no notice. The same-disk skip is unit-only (no box has two places on one disk). | `audits/storage-identity-buildA-2026-10-15/` `tester1-*`, `demo-hp-*`, `n100-*`, `agent-same-disk-live.txt` |
| **R-1072** | **The backup page spoke to the household in two registers in one card: „csatlakoztasson” (formal) and „Csatlakoztass” (informal).** (P4) | CLOSED 2026-10-15 — **controller v0.317.0**: the one-copy notice is now an i18n sentence in the informal voice (`note.backup.single_copy`); the other sentence (`backup.target.degraded`) was already informal. Checked on the three boxes' `/backups` after the release: `csatlakoztasson` 0 hits on each, 1 hit on the OMV-run page (positive control); no other formal imperative found. | `audits/storage-identity-buildA-2026-10-15/README.md`, `*-backup-pages-after.txt` |
| **R-1075** | **Adding a NAS share would have hidden the „Csak egy másolat…” warning, although a NAS is never a Tier-2 target.** (P3) | CLOSED 2026-10-15 — **controller v0.317.0**: the off-drive check skips network places before anything else, so a NAS never hides the notice. **Unit only**, red-proved (the skip removed → the test fails); not shown live — the scratch box cannot reach its agent (R-1083). | `audits/storage-identity-buildA-2026-10-15/` README; `felhom-controller/controller/internal/backup/diskrelation_test.go` (outside `documentation/`) |
## 2026-10-15 (evening) — controller v0.316.0, one column and one image per version
| Row | What | Closed | Evidence |
+6 -7
View File
@@ -155,7 +155,7 @@ stopping line that lies.
| **R-683** | App updates | P3 | **[P3-LOW] Watch: after a power cut during an update's health check, the hold named an HOUR-OLD second-drive copy, not the one the update's own backup should have just made.** 2026-09-24 chaos round 3 (nextcloud, `backup_max_age: 1m`): no `backing-up` phase was seen and the hold named Tier 2 at 13:04 for an update pressed at 14:04; the pre-cut controller log was lost with the container (the runner now saves it at arm time — R-320). Round 11, the same action without a power cut, named a fresh 14:34 copy and logged the Tier-2 copy. The sentence was TRUE (it named the copy it offered); the question is why the update did not back up first. Not reproduced; watch the next power-cut drill. `audits/night-2026-09-24/E/round-03*.json`, `E/round-11-controller-pre.log` | **OPEN — P3; owner: CC (watch)** | — | — | CC |
| **R-785** | App updates | P3 | **[P3-LOW] SparkyFitness is pinned 11 releases and a major behind upstream (v0.17.3; upstream v1.7.3, v1.6.0 dated 2026-07-24).** READ 2026-10-01 (`audits/visitors-2026-10-01/C/bench/C1-previous-tag.txt`). **Needs:** an update walk 0.17 → 1.x through the ladder (bench + box), after R-784 is decided. | **OPEN — rank P3-LOW; owner: CC (after R-784)** | — | — | CC |
## Backup & restore — 33 rows (P2 3, P3 19, P4 11)
## Backup & restore — 31 rows (P2 3, P3 18, P4 10)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -179,8 +179,7 @@ stopping line that lies.
| **R-921** | Backup & restore | P3 | **When the off-site tier answers BUSY, the household still gets a short stop of every app — with no copy.** MEASURED 2026-10-08 night on demo-hp (read-back 2026-10-09, `audits/dooplex-survival-2026-10-09/partE/R-518.txt`): local tier 20:20–20:25Z (own stop, < 2 min), then at 20:26:14Z the agent refused the off-site request (`backup refused — a heavy operation is already in flight`, `busy=backup:local` — the night OS step ran 20:27–20:29Z), yet the controller's metrics show all 15 app containers down at 20:26:19Z and back at 20:27:19Z; the retry ran the off-site tier at 20:34Z with its own short stop. So a two-tier night costs THREE stops, one of them for nothing. This was R-518's noted "unmeasured" risk. Fix shape: ask the agent whether it is free (or reserve the slot) BEFORE quiescing, and resume at once on BUSY. | **NARROWED 2026-10-09 — built; RELEASED 2026-10-10 (controller 0.305.0 on demo-hp, demo-felhom, Tester 1):** before stopping apps the controller asks the agent which tiers have a job in flight (`GET /backup/status`); another tier's job in flight → no stop, the tier stays due (red-proved: 3 apps stopped for a refused tier before); a refusal after the stop still resumes the apps at once (pinned). Also fixes a stop after a long upload or a restart mid-upload. **LEFT: the measured case itself** — the agent's host-wide busy lock (the night OS step, restore test, fstrim) is served on NO endpoint, so the controller cannot see it before stopping; needs one agent field (e.g. `busy` on `GET /backup/status`) + a controller pre-check behind a feature probe. `07` §6.4. | an agent change | Build the agent `busy` field + its controller check; read back a two-tier night | CC |
| **R-933** | Backup & restore | P3 | **Mirror the images of every catalog app we support into our own registry, before scale.** Operator, 2026-10-12 (S8, `09` §3 decision 203): *„Will need to mirror the apps we support later, just to be sure."* Today a restore re-pulls each image from its maker; a version the maker deleted cannot start (the accepted limit, `07` §6.6, R-698 closed). Size it first: the supported catalog's images (every ladder version a box can hold) × their size; then where the copy lives (the DooPlex registry is Tier 2, precious — the copy must not make DooPlex a single point of failure for every household's restore) and how a restore falls back to it. | **OPEN** — NEW 2026-10-12 | the size measurement | Measure the total size of the supported catalog's images; then a one-page design (where, how a restore falls back, cost) | CC (measure + design), operator (the go) |
| **R-1019** | Backup & restore | P3 | **The live download-client templates put downloads into two backup tiers nobody chose.** MEASURED 2026-10-14 with the controller's own classifier over the live templates (`audits/media-all-drives-2026-10-14/``A-drives/A6-3-capture-sets.txt`): Transmission's `${USERDATA_PATH}/media/transmission/watch` bind is not listed in its `backup:` block, so it is classed MANDATORY and goes into the OFF-SITE copy (a writable unlisted bind defaults to mandatory); and qBittorrent's `media/torrents` and Transmission's `media/transmission` are `optional`, so on a box with a second drive every download is mirrored there nightly (Tier 2 = mandatory + optional) — it can fill the backup drive with films. | **NARROWED** — filed 2026-10-14; the `watch` half FIXED 2026-10-14 (declared optional, app-catalog `bc54c01`); open: the `optional` download folders still get a nightly Tier-2 mirror on a box with a second drive | — | Declare the media binds `excluded` (the watch folder too) in the live-fix task; `15` §7 states the rule for the per-drive bind | CC |
| **R-1074** | Backup & restore | P3 | **Tier-2 decides „a different disk” by `st_dev`, so virtual disks on one host disk — and, later, two partitions of one disk — count as a real second copy.** MEASURED 2026-10-15: Tester 1 (a box inside a VM) has five data drives that are all files on demo-hp's one NVMe (`audits/storage-identity-2026-10-15/B-case56-host-truth.txt`); `selectTier2TargetFrom` (C/internal/backup/tier2.go:83-179) and `hasOffDriveTarget` (C/internal/backup/backup.go:1390-1402) use `SamePhysicalDevice` = `stat().st_dev` (C/internal/system/mounts_linux.go:225-245, whose comment names the partition gap). So Tier-2 copies there are counted as off-drive. On real hardware with whole-disk drives the answer is right. | WAITING-ON-OPERATOR | R-1079's rulings D1/D2. | Build per `architecture/16-storage-physical-identity.md` §4: the controller asks the agent. | CC |
| **R-1075** | Backup & restore | P3 | **Adding a NAS share would hide the „Csak egy másolat…” warning, although a NAS is never a Tier-2 target.** READ IN CODE 2026-10-15, not run: `hasOffDriveTarget` (C/internal/backup/backup.go:1390-1402) skips only the system path and same-`st_dev` paths, not `IsNetwork()`; a NAS is registered `Schedulable:true` (C/internal/web/netstorage_job.go:267-273) on a different `st_dev`; Tier-2 refuses it (tier2.go:141-143). Result: a one-disk box with a NAS would claim an off-drive copy is possible. Evidence: `audits/storage-identity-2026-10-15/README.md` „Also found”. | OPEN | — | Prove on a scratch box (add a NAS share, read `/backups`); then skip network paths in `hasOffDriveTarget` with a red-proved test — or fold into R-1079's build. | CC |
| **R-1084** | Backup & restore | P3 | **The whole-box backup offer promises protection against a disk failure without asking whether the drive is a different disk.** SEEN 2026-10-15 on Tester 1 (a box inside a VM, every place `unknown/vm`), controller 0.317.0, `/backups`: „Ezt a meghajtót kijelölheted a rendszermentés helyéül — így egy lemezhiba után is vissza tudod állítani a rendszert.” next to the new „A Felhom nem tudja ellenőrizni, hogy a második meghajtó valóban másik lemez-e…” — the page says both. Build A (`architecture/16-storage-physical-identity.md` §4) covered Tier-2 and the one-copy notice only; the whole-guest target offer (C/internal/web/backup_target_offer.go) still trusts any second drive. Evidence: `audits/storage-identity-buildA-2026-10-15/tester1-backup-pages-after.txt`. | OPEN | — | Make the offer ask the agent (`POST /disks/same-disk`, drive vs `/var/lib/felhom`): proven different → today's sentence; unknown → an honest one; same → no offer. A controller change with a test. | CC |
| **R-91** | Backup & restore | P4 | Old 13 GB datastore copy at `/srv/pbs-felhom` on ep0's root disk **Checked from source 2026-10-05 (burn-down round 2):** Gate is long past (row waits on demo-felhom's first post-migration PBS backup, migration 2026-07-27). Last positive record of the copy: audits/CAMPAIGN-9-restore-proof-2026-07-28.md:759 'ep0 : /srv/pbs-felhom rollback copy intact (13G)'; CONTEXT.md:3666 still says it is 13 G of dead weight awaiting R-91. No later record of deletion found (grep srv/pbs-felhom across felhom.eu). Deleting is on ep0 (protected) and needs an operator word. | WATCHING | demo-felhom's first **post-migration** PBS backup | Delete once it lands; fix `CONTEXT.md:1018` same commit | CC |
| **R-164** | Backup & restore | P4 | **C2's chain: the DB volume tar cannot be dropped until a SOUND dump predicate exists.** The unit carries both a volume tar and a SQL dump; the restore uses **both** — the dump is authoritative and replayed *after* the tar so it WINS (F17), with only the DB service up (R-47) — `internal/backup/restore_unit.go:262-266`. Dropping the DB container's tar would halve DB-app units **and** close the R-127(b) initdb-skip password trap (restored PGDATA ⇒ `POSTGRES_PASSWORD` ignored). | **BLOCKED** — on the predicate | a dump-validity predicate that is not `accounts has rows` | **The obvious gate is DEAD, measured:** `ValidateDump` warns when the `accounts` table is empty, and that warning was **correct** — the live DB genuinely had 0 accounts, and seeding one stopped the warning and put the row in the dump. But **a fresh appliance legitimately has zero accounts**, so promoting that predicate to a gate would **block every new customer's first backup**. Order: (1) a sound predicate — dump vs **live** per-table counts, not an absolute expectation; (2) warn→gate; (3) tar-drop. **Until (1), the tar is load-bearing** — not because dumps are bad, but because nothing can yet prove one is good. Pairs with **R-127** | CC |
| **R-213** | Backup & restore | P4 | **Putting files back in place — the half the recovery screen deliberately does not do.** The screen (R-193, controller v0.200.0) unlocks the repository and LISTS what is in it; restoring is per-app and lives in the backups area, and the operator ruled the two separate on 2026-08-05: *a screen that unlocks and then offers to overwrite is two decisions wearing one button*. What is missing is the step after the listing — a customer who can now SEE their files still has to work out, per app, which restore to choose. **The operator named its requirement: a live-versus-backup comparison** — the customer must be able to see what would change before anything is overwritten | **DEFERRED** — operator ruling 2026-10-12 (S18, `09` §3 decision 213): designed after the first customers. Stays P4; re-dated: look again when the first paying household has run 30 days. **OPEN — not started, deliberately** | the comparison design (nothing exists for it yet) | After the first customers | Operator + CC |
@@ -191,14 +190,13 @@ stopping line that lies.
| **R-832** | Backup & restore | P4 | **ep0's copy in a place outside both Hetzner and the operator's home (roadmap).** Today DooPlex (the operator's home) holds it (decision 71). A Hetzner Storage Box would share a provider with ep0 and with every household's file backups, and cannot run PBS, so the copy could not be verified or restored from directly. | **DEFERRED — later, if the product grows** | — | — | operator |
| **R-878** | Backup & restore | P4 | **A catch-up (R-871) runs the database-dump leg in the DAY, and that leg stops an app with a volume for its copy — the household may notice the stop, and a large volume makes it longer.** MEASURED 2026-10-05 on demo-felhom: the catch-up at 08:25:02 stopped opengist, copied 182.5 KB, started it again — about 1 s, then a few seconds of `health: starting`; the night does exactly the same, unseen. Nothing measured for a large volume. Fix direction (if it matters): skip the volume copy of a running app in a DAYTIME catch-up and leave it to the next night, or warn. `audits/catchup-2026-10-05/partA/live-demo-felhom.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A LIVE MEASUREMENT** — a timed catch-up on 9202 for an app with a multi-GB volume, then the skip-or-warn choice. | — | measure a large volume first | CC |
| **R-32** | Backup & restore | P4 | **[P2-HIGH] RESET must purge the customer base dir; the orphan card must stay honest; unattributed bytes must be visible.** The rehearsal's S7 said in advance that an orphan card would BE a finding — and one appeared (16:58:14). Cause: RESET's `"hetzner":"ok"` leg destroys the sub-account, but **a Hetzner sub-account is an access-control object, not a data object** — its directory survives, so re-enabling offsite recreated an account over the previous lifecycle's ciphertext, encrypted under a key that same RESET had destroyed. **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-21, size M, roadmap state `idea`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: design written** (`audits/night-burndown-2026-10-06/design-R-32.md`) — pick A: purge the repo + set-aside copies through the sub-account's own password login BEFORE deleting it (no main-account credential); the „repo data destroyed" comment at `hub/internal/offsite/offsite.go:276-280` is false for the shared tier; part (3) waits on a read-only measurement (`du` on port 23). Operator: agree to route A. **2026-10-07 07:58: `09` §3 decision 167 — option A yes, and delete the old RESET leftovers once.** **2026-10-07 (Part D step 2, read only):** the hub shows bx11 holding 3.3 GB of data while the four live customers account for ~0.6 GB → **~2.7 GB belongs to no live customer**; no live customer shows a set-aside folder, so the leftovers are in homes whose sub-account is gone, reachable only by the pool box's MAIN account. **The one-time clean-up is STOPPED:** no main-account login for bx11 is held on DooPlex (the API token in the credentials file sees no box). Needs: the main-account login, or the operator lists and deletes in the Hetzner console (`audits/day-2026-10-07/D/D2-listing-readonly.txt`). **2026-10-07: option A DELIVERED (hub 0.142.0):** RESET purges the repo and every `.orphaned-*` through the sub-account's own login before deleting it, and keeps the sub-account if the purge fails (red-proved, `audits/day-2026-10-07/D/`). Not yet seen live (no scratch RESET today). **The one-time clean-up of the ~2.7 GB of leftovers is STOPPED:** it needs the pool box's main-account login (see above). **2026-10-07 14:43 — `09` §3 decision 173: leave the ~2.7 GB of old leftovers for now; re-ranked P2 → P4.** Option A itself is delivered (hub 0.142.0). What stays open is ONLY the leftovers: they were computed (box total minus live customers), never listed, and deleting them needs the pool box's MAIN login, which reaches every customer's data. **Blocker: a safe main-account path.** | — | **Ruling from the run (three parts, deliberately separate):** (1) because RESET destroys custody, the ciphertext it leaves behind is unrecoverable **BY DESIGN** → RESET gains a **main-account purge of the customer base dir** (the existing operator ack already covers it); (2) the **move-aside guard STAYS** for reinstall-*without*-RESET — there custody survives and the card's "history recoverable" promise is true (R-26 depends on exactly that); (3) the operator **Restic tab shows per-customer directory bytes vs attributed snapshot bytes**, so dead data cannot hide. Measured on the pool box that night: **49 M attributed** (2 snapshots, 48.717 MiB) against **1.4 G + 3.0 M unattributed** across TWO `.orphaned-*` dirs. Evidence `restic-and-pool.txt` | CC |
| **R-1072** | Backup & restore | P4 | **The backup page speaks to the household in two registers in one card: „csatlakoztasson” (formal) and „Csatlakoztass” (informal).** MEASURED 2026-10-15 on controller 0.314.0, one-disk box, `/backups`: „Csak egy másolat készül (nincs második meghajtó) — a 3-2-1 mentéshez csatlakoztasson egy második meghajtót vagy offsite tárolót.” next to „… Csatlakoztass egy második meghajtót a teljes védelemhez.” The product's voice is informal (tegezés). Small; not fixed here because this run allowed no product change. Evidence: `audits/omv-vm-2026-10-15/B4-backup-pages.txt`. | READY | — | Change the first string to „csatlakoztass” (and „offsite tárolót” → „távoli tárhelyet”) in the controller's i18n file; the copy gates run. | CC |
## Storage & devices — 13 rows (P2 1, P3 7, P4 5)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1079** | Storage & devices | P2 | **Build „storage that knows its physical disks” (`architecture/16-storage-physical-identity.md`, DRAFT): every place carries `physical_disks` + `identity`, computed by the agent; Tier-2 counts only on proven different disks; a „Belső tárhely” internal place on one-disk boxes; partitions as places.** Operator ruling 2026-10-15 asked for it. Spike proved the agent-side walk on real devices (one disk, two disks, two partitions, USB pass-through) and showed a VM can only be `unknown` (`audits/storage-identity-2026-10-15/README.md`). Closes, when built: R-1068, R-1074, R-1075; touches R-1069, R-1071, R-1073, R-548. | WAITING-ON-OPERATOR | Operator: rulings D1–D6 in `architecture/16-storage-physical-identity.md` §12. | After the rulings, a build task: agent (place identity, same-disk call, partition candidates), controller (Tier-2 + notice ask the agent, i18n sentences, install refusal in words), installer (`step_grows` + internal LV), hub (two report fields), guides. | Operator |
| **R-1068** | Storage & devices | P3 | **On a box with one disk, a media app's (or any data-folder app's) Install is refused with a raw field error, and the way through is nowhere near the button.** MEASURED 2026-10-15 on the OMV-VM copy of the pilot's box (one passed NVMe, controller 0.314.0): `POST /api/stacks/jellyfin/deploy` with DOMAIN+SUBDOMAIN → **400 „a(z) "Médiatár útvonal" (HDD_PATH) mező kitöltése kötelező”** — the env var name reaches the household. R-1031's default-drive rule finds no drive because none is registered. The box DOES offer one: `/api/disks/candidates` listed `/mnt/sys_drive` (the installer's data volume, `already_mounted`), and `handleStorageRegisterMounted` (R-280) registers it — **seen in the candidate list and the code, NOT pressed** (the box was removed). With a second disk attached through „Csatlakoztatás”, the same Install succeeded in 30 s. Evidence: `audits/omv-vm-2026-10-15/README.md` „What happened”, `B4-deploy.txt`. **-- 2026-10-15 (storage-identity spike):** the `sys_drive` route is REJECTED by design — `sys_drive` is a folder of `mp0` (`backup=1`), so films there would ride in every whole-guest copy; the proposal is an internal data place outside vzdump, `architecture/16-storage-physical-identity.md` §5; folded into R-1079. | WAITING-ON-OPERATOR | R-1079's ruling D3/D4. | (Superseded:) prove the `sys_drive` route live on a one-disk box (Tester 1 or a scratch VM); then make the Install refusal name it in household words (a link to Meghajtók), or register `sys_drive` by default on a one-disk box. | CC |
| **R-1079** | Storage & devices | P2 | **Build „storage that knows its physical disks” (`architecture/16-storage-physical-identity.md`, DRAFT): every place carries `physical_disks` + `identity`, computed by the agent; Tier-2 counts only on proven different disks; a „Belső tárhely” internal place on one-disk boxes; partitions as places.** Operator ruling 2026-10-15 asked for it. Spike proved the agent-side walk on real devices (one disk, two disks, two partitions, USB pass-through) and showed a VM can only be `unknown` (`audits/storage-identity-2026-10-15/README.md`). Closes, when built: R-1068, R-1074, R-1075; touches R-1069, R-1071, R-1073, R-548. **-- 2026-10-15:** the operator ruled D1–D6 as recommended, and asked to check the D3 space. **Build A SHIPPED** (agent v0.157.0, controller v0.317.0 with MinAgent 0.157.0, hub v0.154.0): identity per place, the same-disk call, Tier-2 + the one-copy notice ask the agent, the D4 sentence, the hub shows the fields — `16` §13, `audits/storage-identity-buildA-2026-10-15/`. Closed R-1072, R-1074, R-1075; narrowed R-1068. | NARROWED 2026-10-15 — build A shipped; build B (internal place, partitions, installer) not started. | A build B task file (installer + agent partitions); the operator's vouch of agent 0.157.0 for the global floor. | Build B per `16` §5–§7 (check the D3 space on real pool sizes first). | Operator |
| **R-1068** | Storage & devices | P3 | **On a box with one disk, a media app's (or any data-folder app's) Install is refused with a raw field error, and the way through is nowhere near the button.** MEASURED 2026-10-15 on the OMV-VM copy of the pilot's box (one passed NVMe, controller 0.314.0): `POST /api/stacks/jellyfin/deploy` with DOMAIN+SUBDOMAIN → **400 „a(z) "Médiatár útvonal" (HDD_PATH) mező kitöltése kötelező”** — the env var name reaches the household. R-1031's default-drive rule finds no drive because none is registered. The box DOES offer one: `/api/disks/candidates` listed `/mnt/sys_drive` (the installer's data volume, `already_mounted`), and `handleStorageRegisterMounted` (R-280) registers it — **seen in the candidate list and the code, NOT pressed** (the box was removed). With a second disk attached through „Csatlakoztatás”, the same Install succeeded in 30 s. Evidence: `audits/omv-vm-2026-10-15/README.md` „What happened”, `B4-deploy.txt`. **-- 2026-10-15 (storage-identity spike):** the `sys_drive` route is REJECTED by design — `sys_drive` is a folder of `mp0` (`backup=1`), so films there would ride in every whole-guest copy; the proposal is an internal data place outside vzdump, `architecture/16-storage-physical-identity.md` §5; folded into R-1079. **-- 2026-10-15 (build A, controller v0.317.0):** the raw-error half is DONE — on a box with no drive the install answers „Ehhez az alkalmazáshoz egy adattároló kell. A Meghajtók oldalon adhatsz hozzá egyet.” with a link to the Meghajtók page (HTTP 400, code `needs_drive`), never the field name; unit-proven and red-proofed, **not seen live** (no one-disk box at hand). The internal-place half waits for build B (`16` §5, §13). | NARROWED 2026-10-15 — the sentence is built (v0.317.0); the internal place is not. | Build B of R-1079 (the installer's internal place). | Build B; then see the sentence live once on a one-disk box (a scratch VM with one disk). | CC |
| **R-1069** | Storage & devices | P3 | **A disk passed into a Felhom VM on a virtio bus cannot be formatted, and the household reads the agent's raw English refusal.** MEASURED 2026-10-15 (OMV KVM plugin, „Add host disk”, default Bus `virtio`): `POST /api/disks/format` → 500 „agentapi: format: HTTP 409: format refused: device has no durable id to bind the format to (path-only formats are not permitted)”. A virtio-blk disk without a serial has no `/dev/disk/by-id` link inside the guest, so the anti-retarget guard refuses — correct and safe. The same disk on Bus `sata` got `byid:ata-QEMU_HARDDISK_QM00003` and formatted. Affects every hypervisor that passes disks as virtio without a serial (OMV KVM, Proxmox without `serial=`). The pilot guide says `sata`. Evidence: `audits/omv-vm-2026-10-15/B4c-second-disk-format.txt`. | OPEN | — | Translate the refusal for the household („Ezt a lemezt a Felhom nem tudja biztonságosan azonosítani — a virtuális gépben állítsd a lemez buszát SATA-ra”); optionally fall back to a filesystem-UUID binding after a blank-format. | CC |
| **R-1073** | Storage & devices | P3 | **Felhom in a virtual machine sees no disk health: SMART is UNKNOWN for its own system disk and any passed disk.** MEASURED 2026-10-15 on the OMV-VM copy: every storage target reported `smart.health = UNKNOWN`, all counters null (the guest sees QEMU disks). On the pilot's box the NVMe and the „old, not stable” Toshiba would be watched by OMV only, and Felhom's drive-health alarms (R-330/R-332's whole point) cannot fire. Evidence: the host report in `audits/omv-vm-2026-10-15/README.md` (memory/disk section) and `C-hub-events-Tester4.txt`. | WAITING-ON-OPERATOR | Operator: is a VM install a supported shape, and if so, is „OMV watches SMART” acceptable or must Felhom say on the dashboard that it cannot watch the disks? | Operator rules; then CC adds a household line on the Meghajtók page when SMART is unavailable. | Operator |
| **R-1082** | Storage & devices | P3 | **The drives page offers `/mnt/sys_drive` for attaching, while the storage-identity design rejects `sys_drive` as a drive for household data — and the new no-drive install sentence links to that page.** FOUND 2026-10-15 building `16` build A: `attachableStores` (felhom-controller `internal/web/attach_sources.go:79-136`) offers the guest data volume's `/mnt/sys_drive` on purpose — R-280, the way back after a reinstall so a restore has a destination, pinned by `TestMountedUnregisteredStores_OffersTheGuestDataVolume`. `architecture/16-storage-physical-identity.md` §11 rejects registering it for media (it rides in every whole-guest copy, local and off-site). Build A's D4 refusal (controller v0.317.0) never names it, but its link leads to the page that offers it. Not changed: the brief says to stop and report where source and design disagree, and removing it would reverse R-280. | WAITING-ON-OPERATOR | Operator: keep it as the reinstall-restore way out only (and say so on the attach card), or remove it once build B's internal data place exists? | After the ruling: a household line on the attach card, or the removal in build B. | Operator |
@@ -309,10 +307,11 @@ stopping line that lies.
| **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator |
| **R-1035** | Business & legal | P4 | **Did the „felhő tárhely” page earn anything? Two dated checks.** **Baseline (reported by the operator from Search Console, 2026-10-14, last 3 months):** 1 click, 48 impressions, CTR 2.1 %, average position **73.6**; „felhő tárhely” 19 impressions, „felhőtárhely” 5, „felhő alapú tárhely” 3. CC measured separately that the phrase „felhő tárhely” appeared **0 times** on the home, FAQ and backup pages — Google was showing the home page for those queries by accident. `/sajat-felho-tarhely` + `/en/own-cloud-storage` published **2026-10-14**; full baseline, price sources and the power provenance in `audits/seo-2026-10-14/BASELINE.md`. **What to measure, both times:** impressions and average position for „felhő tárhely” and „saját felhő tárhely”, plus the new page's own impressions, clicks and position. **A null result at +4 weeks means nothing on its own** — a new page on a site at position 73.6 takes weeks to months to move, and „felhő tárhely” alone is held by Microsoft, Google and the big list sites; the page aims at the two longer phrases. That is why there are two checks and not one, and why neither should be read as a verdict on the copy. **There is no Search Console connector in this project, so CC cannot read these numbers** — the operator reads them, CC records and compares. **Blocked until the operator asks for indexing** (Search Console → URL-ellenőrzés → the URL → „Indexelés kérése”): until that happens, or Google finds the page itself, both checks read zero for a reason that is not the page. **This is a slow channel and does not replace talking to people.** | **WATCHING** | operator: request indexing, then supply the Search Console read | 2026-11-11: first read (+4 weeks); 2026-12-09: second (+8 weeks), compared with the first | operator + CC |
## Process & tooling — 28 rows (P3 10, P4 18)
## Process & tooling — 29 rows (P3 11, P4 18)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1083** | Process & tooling | P3 | **The scratch box 9202 cannot use any feature that needs the agent: its `bootstrap.json` has no `local_api` block, so its controller never gets an agent client.** MEASURED 2026-10-15 (storage-identity build A): 9202 put on controller 0.317.0 by hand to show that a NAS share no longer hides the one-copy notice (R-1075); its `bootstrap.json` (read on the box) holds no `local_api`, so the controller has no agent to ask and keeps the old `st_dev` rule — the check could not tell old from new — 9202 was put back on 0.314.0. Every agent-coupled feature (drives, same-disk, format, network storage) is untestable on the scratch box today, and the NAS leg of build A is proven by a red-proofed unit test only. Evidence: `audits/storage-identity-buildA-2026-10-15/9202-upgrade.txt`. | OPEN | — | Give 9202 a local-API token the normal way (the agent mints it for its guest), or record in `runbooks/target-selection.md` that 9202 is controller-only; then show the NAS notice live. | CC |
| **R-1081** | Process & tooling | P4 | **Two published controller tags hold content that is not what their number says: `0.315.0` and `0.316.3`.** Both were caused by CC on 2026-10-15, and both by the same shape — a guard that was not on the path the work takes. **`0.316.3`:** the build directory's `build.sh` is a symlink into the SHARED clone's working copy, which predated the `FELHOM_REPO_DIR` override, so the override was silently ignored and the build copied that tree — which held 31 uncommitted files of another session's physical-disk storage work. The banner printed `Commit: 0225255` with no `Repo:` line. **Nobody deployed it; that was luck, not a control.** Fixed at the root in controller v0.316.4: `build.sh` now refuses `--push` from a tree that is dirty, not at `origin/main`, or not a git checkout, with four test cases red-proved. **Neither tag is re-pushed or deleted** — a version number is pushed once, and that applies to numbers CC spoiled. **No box runs either:** demo-hp 9201 is on 0.317.0, the floor is 0.314.0, and the button offers the highest tag. **`0.315.0`:** Caused by CC on 2026-10-15 while proving R-1080's own fix: the guard was added to `controller/build.sh` in the repo, but the documented release command runs `$FELHOM_ROOT/build/felhom-controller/build.sh`, which was a **separate copy taken 2026-07-18** and had no guard. Running the refusal against a real existing tag therefore rebuilt and re-pushed 0.315.0 — a fourth time — with v0.316.0's code under it (digest `8887343011d6` → `b809201841f7`). **Fixed at the root in the same session:** the build directory's script is now a symlink to the repo's, the old copy kept beside it as `build.sh.stale-copy-2026-07-18`, and the refusal was then proven to fire from the documented path. **No box is affected:** nothing runs 0.315.0 (demo-hp 9201 is on 0.316.2), the floor is 0.314.0, and the household button offers the highest tag. What remains is two tags that must never be deployed and are no longer reproducible from the registry. | **OPEN** — filed 2026-10-15; nothing to do unless someone needs one of those two images, and then the answer is the current release | — | Leave them. If an audit ever needs the real 0.315.0 or 0.316.3, build it from the matching commit under a NEW number and say so | CC |
| **R-992** | Process & tooling | P3 | **The drill-catalog route that `09` §6.5 documents cannot be followed: `admin/app-catalog-drill` is PRIVATE and no box has catalog credentials.** Measured 2026-10-12 while publishing the Usenet pair: the box's clone fails with `fatal: could not read Username for 'https://gitea.dooplex.hu'` (exit 128), the Gitea API reports the drill repo `private=true` and the live catalog `private=false`, and NEITHER the current controller.yaml on 9202 NOR any of its 30 saved copies has ever carried `git.username` or `git.token` (the controller supports both — `internal/config/config.go` GitConfig). The route worked for the campaigns of 2026-10-10 and the torrent pair earlier the same day, so the repository's visibility changed between then and now. **Worked around, not fixed:** that campaign staged its two templates in the box's own catalog cache (`audits/usenet-2026-10-12/tools/stage_in_cache.py`), which is where the sync copies a catalog change anyway, with the sync interval raised to 12h for the duration and rescan used in place of sync. The real fix is an operator decision: either a read-only catalog credential on the scratch box (a secret on a Tier-0 guest) or making the drill repository readable. Until then every new-app campaign hits this. | WAITING-ON-OPERATOR | the operator's choice between a box credential and a readable drill repo | Decide; then either set `git.username`/`git.token` on 9202 from the credentials store, or flip the drill repo, and correct `09` §6.5 either way | operator |
| **R-1017** | Process & tooling | P3 | **A comment names a guard test that does not exist.** `felhom-controller/controller/internal/appbackup/paths.go:58` says „`TestNoBareDrivePathToUserdataDir` is the guard that keeps the count from growing”; no test of that name (or `NoBareDrive*`) exists in the repo (grep 2026-10-14; positive control: `TestRemoveStack_R800_UserdataKeptIsNamed` is found). A comment asserting an invariant with no test pinning it — the class in the `felhom-testing` skill. | **READY** — filed 2026-10-14 | — | Write the guard (a test that fails when a bare drive path reaches `UserdataDir`), red-proof it, or correct the comment | CC |