Adds the full audit: Part 0's three answers, the pre/post recipe for both boxes, the on-disk proof that `local` froze at the 2026-07-28 target move while felhom-backup kept running, all seven red-proofs, and the three publish observables. R-125 filed: v0.118.0's R-106 half shipped INERT. Two tests ran the real Collector.Collect() but both injected a fakeObserver, and the break was one layer below in mergeConfig, which dropped the pbs namespace. The recipe still said "root" — now with namespace_state "resolved" beside it, confident and wrong. Caught by live validation, not by the green suite. Fixed in 0.118.1; filed for the doctrine point that a production-path claim must name the seam it injects at.
8.0 KiB
REPORT — R-106 + R-109 (+ R-122): closing the recipe-completeness set (2026-07-30)
Non-overwritten sibling per CLAUDE.md:82-87 — the shared REPORT.md holds R-117 and is not touched.
Shipped: agent v0.118.0 → v0.118.1 (felhom-agent 1c8a67e, 6b5dade) + hub v0.83.0
(felhom.eu acfc2b7). Neither half is useful alone.
Read §3 first if you read nothing else: v0.118.0's R-106 half shipped INERT and live validation is
what caught it — the recipe still said "root", now with namespace_state: resolved beside it. Full
account in the audit §6, filed as R-125.
Part 0 — the answers, before the fix
0.1 Which items are actually open, and R-105/R-106's registration
OPEN-ITEMS.md calls itself "the single source of truth for open work" (:1), with ROADMAP.md keeping
"the full history and reasoning" (:3-4).
| item | ROADMAP.md |
OPEN-ITEMS.md |
verdict |
|---|---|---|---|
| R-105 | row, READY — 2026-07-28 (:108) |
absent | open but UNREGISTERED |
| R-106 | row, READY — 2026-07-28 (:109) |
absent | open but UNREGISTERED |
| R-108 | row (:111) |
row (:50) |
registered |
| R-109 | row (:112) |
row (:61) |
registered |
So R-109's own cell — "third recipe-completeness defect beside R-105/R-106" — was the only place in the register naming two open items. That is exactly the thread-loss the register exists to prevent, and it is itself a finding (filed R-123). Both now have rows.
The set this task closes is R-106 + R-109, matching the arc's stated definition of done (OPEN-ITEMS.md:14).
R-105 is NOT in it and was not worked: it is M-sized and is about three hub-held DR records being {}
(hosts.dr_record_json, host_escrow.directive_json, and the drives third — already traced and populated
by the 2026-07-28 target move). Different fields, different owner, different size.
0.2 Where the recipe is generated — three producers, not two
| half | repo | function |
|---|---|---|
| host (guests/pbs/drives/pve_storage) | felhom-agent |
BuildDRRecipeHostHalf, internal/hub/dr_recipe.go:86 |
| app (customer/apps/offsite_restic) | felhom-controller |
controller/internal/report/dr_recipe.go |
| assembly + delivery | felhom.eu/hub |
AssembleDRRecipe, internal/store/dr_recipe.go:104; served by handleDRRecipeDownload, internal/web/dr_recipe.go:14, route internal/web/server.go:439 |
R-109's "host-half" is therefore the agent, and the field must also pass the hub's allow-list — see §2.
0.3 What the namespace field actually contained — verified, and the brief was RIGHT
The eleven-session-old brief held up. Live, pre-fix, from the hub for both boxes:
"pbs": { "repo_id": "felhom-pbs", "namespace": "root", "latest_snapshot_id": "9201" }
against /etc/pve/storage.cfg on the same boxes:
pbs: felhom-pbs
datastore felhom-offsite
namespace demo-felhom # demo-hp reads: namespace demo-hp
Traced to source: Snapshot.Namespace decodes ns (internal/pbs/client.go:97), which PBS does not echo
per item once the list is namespace-scoped via ?ns= (:118-120) → always empty → ToHub normalises empty
to "root" (internal/pbs/report.go:22-25) → latestPBSCoord writes it in.
The authority taken, and why: storage.cfg's namespace on the pbs storage. It is the same field
vzdump --storage <pbs> makes PVE read, and the agent's own verify client is built from it
(cmd/felhom-agent/main.go:1164). Deriving the recipe from anything else is how it drifts again.
1. R-109's ambiguity is real, in the boxes' own pre-fix recipe
"pve_storage": [
{ "name": "local-lvm", "type": "lvmthin", "content": "images,rootdir" },
{ "name": "felhom-backup", "type": "local-dir", "content": "backup" },
{ "name": "felhom-pbs", "type": "pbs", "content": "backup" },
{ "name": "local", "type": "local", "content": "backup,import,vztmpl,iso" }
]
No backup_target key anywhere. felhom-backup (live, /mnt/hdd_1) and local (/var/lib/vz, archives
frozen 2026-07-28) are both content=backup dir storages; local is also the historically correct answer,
which is what makes guessing it so easy.
2. R-122 — a fourth defect, found here, and it had already shipped
AssembleDRRecipe's hostHalfShape/appHalfShape are allow-lists dressed as forward-compat. The
controller has emitted offsite_restic since fork-4 (controller/internal/report/dr_recipe.go:39-41, "so DR
knows WHERE to recover from"); appHalfShape never listed the key. Verified both ways:
- stored:
dr_recipe.app_half_jsoncarries it for all three real customers —peti-felhom,demo-felhom(u629488-sub1.your-storagebox.de:23/home/felhom-repo),demo-hp. - delivered: the downloaded recipe's top-level keys were
recipe_version, customer, guests, pbs, drives, pve_storage, apps— nooffsite_restic.
So a restorer reading the recipe had no offsite location at all, for the whole life of the feature, with
a green suite throughout — because the test fixture drAppHalf is hand-written and omits the field.
Deviation from the task's §7.10 ("Findings — filed as R-n, none fixed"), stated rather than absorbed: I fixed it. Reasons — (a) Part 0 authorises working the real set if it differs; (b) it is the same symptom the task is named for (the recipe is incomplete), and the worst instance, a whole section missing; (c) it is in the same two structs R-109 forced me to edit, and leaving one of three known keys off a drop-list I was already correcting would be indefensible. It is filed as R-122 with a SHIPPED disposition.
3. The before/after recipe — both boxes, quoted
demo-felhom BEFORE "namespace":"root" backup_target absent offsite_restic absent
AFTER "namespace":"demo-felhom" backup_target {resolved, felhom-backup, /mnt/hdd_1}
offsite_restic {u629488-sub1…}
demo-hp BEFORE "namespace":"root" backup_target absent offsite_restic absent
AFTER "namespace":"demo-hp" backup_target {resolved, felhom-backup, /mnt/nvme-1tb}
offsite_restic {u629488-sub3…}
The two boxes DISAGREEING is the point — nothing is hardcoded. And the ambiguity was not theoretical:
on both boxes felhom-backup holds an archive from 07-30 04:36 while local stops at
07-28 17:5x, frozen at the target-move date. The recipe now names the live one.
Full evidence, all seven red-proofs and the publish observables:
documentation/audits/R106-R109-recipe-completeness-2026-07-30.md.
4. Findings filed (none of them fixed except R-122, see §2)
| id | finding |
|---|---|
| R-122 | AssembleDRRecipe allow-list dropped offsite_restic for the feature's whole life — FIXED here, hub v0.83.0 |
| R-123 | R-105 and R-106 were READY in ROADMAP.md with no OPEN-ITEMS.md row — referenced only inside R-109's prose. Registered here |
| R-125 | v0.118.0 shipped an inert R-106 because the "production path" test injected fakeObserver one layer below the break — FIXED in v0.118.1; filed for the doctrine point (name the seam you inject at) |
| R-124 | The recipe spells PBS's root namespace "root", but the PBS API spells it "" and there is no namespace literally named root — a restorer pasting it into pct restore --ns root would fail. Pre-existing wire convention, deliberately unchanged; documented at PBSRootNamespace |
5. Not done, and why
- R-105, R-108, D5 — out of scope by the task's §6. R-108 blocks D5; starting either would leave both half-done.
- The backup machinery — untouched. This corrects the record, not the doing.
sess-f(0.116.0) anddrill-r50(0.113.0) were not upgraded — neither was named as a venue, anddrill-r50is fenced by the task's §6.- R-124 not fixed — changing the wire's spelling of the root namespace mid-R-106 would shift the field's meaning during the fix meant to make it trustworthy.