acfc2b7e95
AssembleDRRecipe's hostHalfShape/appHalfShape are ALLOW-LISTS, not the forward-compat their comment advertised: a section an emitter adds is silently discarded until it is named in both the shape struct and AssembledRecipe. No error, no log, no failing test. R-122 (found this session): that already happened and shipped. The controller has emitted offsite_restic since fork-4 — the offsite recovery LOCATION — the hub stored it for all three real customers, and appHalfShape never listed the key, so no delivered recipe has ever contained it. It stayed green because the fixture drAppHalf is hand-written and omits the field. R-109: the agent's new backup_target is a new top-level host-half section and would have been dropped identically, making the fix read as shipped while changing nothing an operator can see. 3 tests built on halves read verbatim out of the live dr_recipe table, plus 2 red-proofs (each mutation asserted to have landed). vet rc=0, suite rc=0, 17 ok. Registers: R-106 + R-109 dispositioned; R-105/R-106 were READY in ROADMAP with no OPEN-ITEMS row (→ R-123, registered); R-124 filed on the "root" spelling.
116 lines
6.4 KiB
Markdown
116 lines
6.4 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** (`felhom-agent` `1c8a67e`) + **hub v0.83.0**. Neither half is useful alone.
|
|
|
|
## 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 — same box, quoted
|
|
|
|
Full text in `documentation/audits/R106-R109-recipe-completeness-2026-07-30.md` §5.
|
|
|
|
## 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-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.
|
|
- **demo-felhom's agent was not upgraded past what the deploy step covers** — see the audit §6.
|