Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
5.1 KiB
R-105 — the two "empty DR records" — design proposal (burn-down night 2026-10-06, no code)
Baselines read: felhom.eu 8e2dc204 (hub v0.140.0 live), felhom-agent 74b5eae. Architecture: 05-hub-architecture.md
§9 and §11 (the "slim DR record"), 06-offsite-connectivity.md §3.5 (the escrow directive).
1. The problem, and what was measured
The row says two hub-held records are {} on every box: hosts.dr_record_json and host_escrow.directive_json
(the third, dr_recipe…drives, was fixed 2026-07-28 and is not re-measured tonight).
Tonight's reading is in SOURCE, not in the database — reading a copy of the hub database was refused by the session's permission check, so no value was read. Source proves more than a reading would: for these two fields the empty value is the only value the shipped code can produce. The hub's host pages (read through the operator UI) show "DR Recipe present / Key Escrow present" on demo-hp, demo-felhom and Tester 1, and "none / none" on Tester 2.
2. What the code does today (read in source)
hosts.dr_record_jsonhas no writer and no reader. Created by hub v0.7.0 (7c0c7545) with default'{}'(hub/internal/store/store.go:360); scanned intoHost.DRRecordJSON(store.go:2795,:2867) and read by nothing else in the hub, the agent or the controller (repo-wide grep). The05§9 "slim DR record" was never built.host_escrow.directive_jsonis written only by a by-hand flag. The agent sends a directive only when the operator runs--selftest=escrow-create -directive <file>(felhom-agent/cmd/felhom-agent/main.go:201,:2767-2771,:3132-3135). The production ceremony (the customer's escrow wizard) runs ONE fixed argv with no-directive(felhom-agent/configs/felhom-agent.sudoers:284). When that upload carries an identity blob, the hub stores the missing directive as'{}'(hub/internal/api/handler.go:1293-1305,store.go:3612) — so every wizard escrow writes{}, and also overwrites the one directive made by hand on 2026-07-04 (06§3.5).- Nothing reads the directive's fields. The hub serves it only from
/re-enrolland/restore-directive(hub/internal/api/dr.go:101,:155); no agent or controller code calls either route (grep). - The DR path that IS built does not need them. The agent's restore plan reads the desired-state
restore_directiveplus the DR recipe (felhom-agent/internal/dr/plan.go:45,:104); the recipe carries the PBS repo id and namespace (internal/hub/dr_recipe.goDRPBSCoord), the hub holds the endpoint's PBS fingerprint (hub/internal/tenantsync/client.go:136), and the wrapped key ishost_escrow.blob.
So the row's two thirds are not a fault in a running path. They are a design that was half-built and then bypassed, and two architecture documents still describe it as if it existed.
3. Options
A. Retire both, and correct the documents. Drop the DRRecordJSON scan field; stop storing the directive
(the column stays, read as {}); 05 §9/§11 and 06 §3.5 say where each fact really lives (recipe, tenantsync,
escrow blob). Cost: ~45 min, hub only, no box change. Can go wrong: if a future re-enroll client wants the
directive, it must be rebuilt — the routes stay and serve {}.
B. Populate the directive from the ceremony. The agent fills {pbs repo id, namespace, endpoint fingerprint}
in the wizard ceremony. Cost: agent + sudoers argv change (the argv is pinned byte-for-byte) + a bundle delivery;
~2 h and a release. Can go wrong: a second copy of facts the recipe already carries, which can disagree with it —
the R-106 namespace defect was exactly such a disagreement.
C. Do nothing more; close R-105 with this evidence. Cost: nothing. The two documents keep describing a record that does not exist, which is how this row was filed in the first place.
4. The pick — PROPOSAL for the operator, not a decision
Option A. One source per fact; the recipe is reported every cycle and was fixed to match the backup (R-106).
It removes a design promise in 05 §9, so it needs the operator's word (a design decision is not a defect).
Until then the row can move to WAITING-ON-OPERATOR: there is no data at risk — the empty fields have no reader.
5. First slice and its proof
- Red test first: a hub test that the escrow PUT with an identity blob and no directive leaves
directive_jsonunchanged from a hand-made value (today it overwrites with{}— fails); then decide by the option. - A test pinning "no reader": an AST/grep test in the hub that
DRRecordJSONis not referenced outside the store (so a new reader cannot appear without this design being revisited). - Live proof: none needed on a box (hub-only); after deploy, the host page still shows "DR Recipe present".
6. Open questions for the operator
- Retire the "slim DR record" of
05§9 (Option A)? If you do nothing: the fields stay empty and unread, the documents stay wrong, nothing breaks. - Tester 2's host page shows no DR recipe and no key escrow. Expected for a box that never did the escrow wizard — is that the case? If you do nothing: a Tester 2 host loss has no hub-held recovery material.