70f84941d4
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.
137 lines
8.0 KiB
Markdown
137 lines
8.0 KiB
Markdown
# 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:
|
|
|
|
```json
|
|
"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
|
|
|
|
```json
|
|
"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_json` carries 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` — **no `offsite_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) and `drill-r50` (0.113.0) were not upgraded** — neither was named as a venue, and `drill-r50` is 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.
|