docs(map): capability-map evidence audit — verify every status claim against source

Opened every citation in 00-capability-map.md and judged each of the 55 rows
against the specific scenario (not topic adjacency). 23 confirmed (incl. 5
MISSING proven-absent by code sweep), 26 citation-corrected, 6 demoted; no
upgrades. Status: 32 PROVEN-LIVE / 15 IMPLEMENTED / 3 PARTIAL / 5 MISSING
(was 38 PROVEN-LIVE).

Demotions PROVEN-LIVE->IMPLEMENTED: B6 (post-deploy config — T-PAGE-ALL is
render-only), E7 (FileBrowser access — no browse/download proof), F1 (health
email — cited P3-DELIVERY is the enlarge-block trigger), F4 (metrics — cited
legs are H1/H2 harness artifacts), F6 (operator alerting — no corpus doc),
G6 (offsite fill/staleness/freeze — no live-fired leg, freeze inconclusive).

Key corrections: D6 cited CAMPAIGN-3 F10/F11/F12 which are the CRITICAL
failures — fixes shipped agent v0.85 and were re-validated live in CAMPAIGN-4
/6A; C6 cited offbox-restic doc for a PBS-restore claim -> CAMPAIGN-2 T-P9;
C1/C5/B5 cited defect-discovery docs instead of the fix-validation legs.

Closed ROADMAP R-20: operator-key pinning is fully automatic in day-0
(felhom-host-install.sh step_agent_config, L2044/2146-2156/2332-2337).
Header draft caveat swapped for the verified stamp. Full per-row verdict
table in REPORT.md.
This commit is contained in:
2026-07-16 09:19:51 +02:00
parent 4b18cc5208
commit d202b43b95
3 changed files with 150 additions and 71 deletions
+117 -38
View File
@@ -2,47 +2,126 @@
> **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md).
## PBS-DR self-heal reconciler (hub v0.56.0) — 2026-07-15
## Capability-map audit — verify every status claim against evidence — 2026-07-16
**Task:** implement `SPIKE-pbsdr-selfheal-2026-07-15` (`e8f8c44`) — a hub periodic reconciler that
re-stages a consumable PBS-DR secret for boxes stuck in `waiting_secret`/`consumed_failed` after
losing their agent-side converged marker (re-install / restore / snapshot rollback onto a stable
`host_id`). Class: risky/supervised. Repo: `felhom.eu` (`hub/`) only — **no agent change** (the spike
proved the agent already self-heals once a secret is consumable).
**Task:** docs-only audit of `documentation/architecture/00-capability-map.md`. Open every citation,
confirm/correct/demote each of the 55 rows against the actual evidence doc (scenario-specific PASS,
not topic adjacency), prove the MISSING rows absent by code sweep, close ROADMAP R-20, and swap the
draft caveat in the map header. **Zero code changes; read-only against three sibling repos.**
**Baselines:** hub main `e8f8c44` → this work; hub live image v0.55.0 → **v0.56.0**. Green gate
(`go build/vet/test ./...` in `hub/`) passing across the module.
### 1. Confirmed baselines (re-confirmed at task start)
**What shipped**
- `internal/store/pbsdr.go``RestageHostPBSSecret(hostID) (bool, error)`: clears `consumed_at` IFF
a secret row exists (no INSERT, no value change, **no generation bump**); `restaged=false` → no row
→ the caller escalates. `PBSDRHealStates()`: one query joining each host's descriptor
enable/provision flags to its latest report `pbs_dr.state` + id (mirrors `GetHostOOBStates`).
- `internal/pbsdrheal/reconciler.go` (new) — 5-min reconciler (wgsync shape). Work set = descriptor
**enabled + provisioned**. Per host, on the latest report `pbs_dr.state` held across a **debounce
(≥2 distinct reports)**: `waiting_secret` → re-stage the stored secret (no ep0 call, no gen bump),
or escalate to Re-issue if none stored; `consumed_failed` → escalate to Re-issue only.
Converged/`disabled`/`verify_failed`/unprovisioned/DR-OFF = pure no-op. Distinct audit events per
heal. `PBSDRHEAL_ONLY_HOST` scopes a supervised rollout.
- `internal/web/pbsdr.go``ReissuePBSDR(ctx, customerID)`: the non-HTTP core of the operator
Re-issue button, now the reconciler's escalation seam. The operator handler is unchanged (303/400
codes stay test-pinned).
- `cmd/hub/main.go` — reconciler started unconditionally; `PBSDRHEAL_ONLY_HOST` env read.
| Repo | `main` @ commit | Role |
|------|-----------------|------|
| felhom.eu | `4b18cc5` (local == origin) | audit target — WRITE |
| felhom-controller | `e99c675` (v0.137.0) | read-only reference |
| felhom-agent | `c040c18` (v0.88.0) | read-only reference |
| app-catalog-felhom.eu | `21e8df1` | read-only reference |
**Tests (green) + red-proofs (verified):** `internal/pbsdrheal/reconciler_test.go` (Scenarios AF +
scope + no-re-heal, real store + fake action seam, audit events asserted); `internal/store/pbsdr_test.go`
(re-stage semantics + no-generation-bump guard + `PBSDRHealStates` parsing). All six §10 red-proofs
(A, A-gen, B, C, D, E) failed exactly their guard test then reverted.
The map's evidence corpus is read at felhom.eu tip `4b18cc5`; the header caveat now records that.
**Deploy + live validation:** built `felhom-hub:0.56.0`, manifest bump + ArgoCD sync (Synced/Healthy);
first rollout scoped via `PBSDRHEAL_ONLY_HOST=demo-vm-felhom-2f4b00` (STOP discipline). Drill live-leg
(qm300 rolled to `post_day0_golden136` → stuck `waiting_secret`): the reconciler observed
`waiting_secret` across two reports and at **17:10:00 UTC re-staged** the stored secret (event
`pbsdr_selfheal_restaged`; no ep0 token, no gen bump) → agent **re-consumed 17:10:26** → **converged
`applied` 17:10:28**, hands-free. The demo host `demo-felhom-01` was never touched (out of scope).
**Fleet-wide widening (remove `PBSDRHEAL_ONLY_HOST`) is a deliberate operator follow-up**, not done
this session.
### 2. Counts
**⚠️ ARCHITECTURE IMPACT:** before this, no automatic recovery existed for a box re-installed/restored
onto its stable `host_id` — PBS-DR (hence escrow + offsite) stayed silently unconverged until a manual
operator Re-issue. This reconciler closes that gap.
**55 rows audited = 23 confirmed (18 confirmed + 5 confirmed-absent) + 26 corrected + 6 demoted.**
No row was upgraded (candidate upgrades are Observations). Status distribution after the audit:
**32 PROVEN-LIVE / 15 IMPLEMENTED / 3 PARTIAL / 5 MISSING** (was 38 PROVEN-LIVE → 6 demoted).
### 3. Per-row verdict table
Each verdict names the doc opened and the leg/section carrying (or failing to carry) the scenario.
| Row | Scenario | Verdict | Evidence opened → leg / reason |
|-----|----------|---------|--------------------------------|
| A1 | Appliance day-0 zero-click auto-confirm | **CONFIRMED** | `DRILL-day0-take2` §2/§3 (auto-confirm 6m39s, zero clicks; floor self-update 0.120→0.122; claim gate closed) + `DRILL-day0-vm` §5.6 |
| A2 | BYO install (PARTIAL) | **CONFIRMED** | `DRILL-GL6` Phase 2 byo happy-path PASS; coexistence guards (GL6-F3/F6) only fixed v1.12.0, never live-revalidated — PARTIAL right |
| A3 | Customer claim | **CORRECTED** | `DRILL-day0-vm` §10/F-4 carries it; dropped mis-cited `CAMPAIGN-4` F-C (that is the escrow-claim 502, not the password claim) |
| A4 | Escrow ceremony | **CORRECTED** | agent v0.88 REPORT (claim 200→410, R absent). Qualifier fixed demo→**drill VM**; browser-wizard leg flagged not-yet-validated |
| A5 | DR tier by default (IMPLEMENTED) | **CORRECTED** | cited `SPIKE-pbs-tier-provisioning` = slice-0, shipped nothing → `DRILL-day0-take2` §2 (WG both modes, PBS-DR descriptor auto-provisioned live). Upgrade candidate |
| A6 | Uninstall (PARTIAL) | **CORRECTED** | `DRILL-GL6` Phase 1/5 (KEPT-vs-WIPED printed, data intact ×3); secret-purge fixed v1.12.0, `mnt-*.mount` residue open — PARTIAL right |
| B1 | Deploy app from catalog | **CORRECTED** | `CAMPAIGN-2` T-DEPLOY-SET + `RERUN-p1p3` PASS; dropped T-RES-MEMGUARD (guard never *fired* — ample RAM / auth-walled) |
| B2 | App lifecycle | **CORRECTED** | T-LIFECYCLE PASS + remove live in `CAMPAIGN-3`; T-REMOVE-REDEPLOY never cleanly passed (stale dryrun) — noted open edge |
| B3 | Protected infra un-stoppable | **CORRECTED** | cited `CAMPAIGN-2` T-SEC-PROTECTED was a stale-dryrun FAIL → `CAMPAIGN-nomercy` + `RERUN-p1p3` T-SEC-PROTECTED PASS |
| B4 | Catalog sync + validation choke | **CONFIRMED** | `CAMPAIGN-2` T-SYNC-IDEMPOTENT PASS; choke-point is unit-red-proofed (v0.132) — status held |
| B5 | App crash → customer notified (IMPLEMENTED) | **CORRECTED** | citation `CAMPAIGN-3 fix-3 arc` (surfaced the gap) → controller v0.120.0 (impl+red-proof); "delivery proven" softened (6B deferred/6C inconclusive) |
| B6 | Post-deploy config + restart | **DEMOTED** PROVEN-LIVE→IMPLEMENTED | T-PAGE-ALL is a GET-only page-render smoke test; config-save+restart exercised in no campaign (CAMPAIGN-3 skipped it) |
| B7 | Backup classification (13 apps) | **CONFIRMED** | `SPIKE-backup-classification` SQ2 (13 bind apps) + 6D/6E live legs |
| C1 | Nightly DB dumps, atomic writes | **CORRECTED** | cited `CAMPAIGN-3` F7 is the *finding* of non-atomic writes + T-RST-DB auth-hollow → `CAMPAIGN-6B` P4 + `6E` B1/B2 (SIGKILL→last-good byte-unchanged) + `6D` P-FAB |
| C2 | Tier-2 copy | **CONFIRMED** | `CAMPAIGN-6E` P-TIER2 deep-4 4/4 PASS + `6C` |
| C3 | Offsite restic | **CORRECTED** | enlargement gate live (`6D` P3-DELIVERY); noted raw-data-quota/retention are `SPIKE-restic-snapshot-shape` **dry-run** only |
| C4 | Offsite restore | **CONFIRMED** | `CAMPAIGN-6D` P-IMMICH/P-PLACE (functional from offsite alone, byte-identical) |
| C5 | Manual `.fab` | **CORRECTED** | `6D` P-FAB (1.7GB circle) + `6B` P2 (chunking at real CF edge via `curl --resolve`); rendered-browser upload leg still deferred; C6B-F1 was the 6B *finding* |
| C6 | Guest-loss PBS DR | **CORRECTED** | cited `VALIDATION-newbox-restore` = offbox **restic** (wrong tier) → `CAMPAIGN-2` T-P9-DESTROY-RESTORE (whole-guest `pct restore`→healthy) + T-PBS-VERIFY (13 snapshots) |
| C7 | PBS-DR self-heal (IMPLEMENTED) | **CORRECTED** | cited spike = findings-only → hub v0.56.0 reconciler + §10 red-proofs. Facts fixed: **scoped to one host** (not fleet-wide); **already fired live** on drill qm300 |
| C8 | Crash mid-backup/migration self-heal | **CORRECTED** | cited `CAMPAIGN-2` T-RBT-* legs empty/auth-hollow → `6D` P5-REST + `6E` B1-B3; mid-*migration* crash still weakest sub-claim |
| C9 | Soft-quota block + notify | **CONFIRMED** | `6D` P3-DELIVERY (block + op+customer email live); usage-bar UI only weakly corroborated |
| C10 | Customer performs a restore (MISSING) | **CONFIRMED-ABSENT** | sweep: only self-reference at map L34 "never executed by a non-Viktor human"; all restores operator-driven |
| D1 | Drive wizard incl. legacy LVM-root | **CORRECTED** | `DISPOSITION-ia` (legacy EFI+LVM, root not offered) + `storage-lifecycle-acceptance` (enroll live) + agent fence self-test; cited CAMPAIGN-2 legs were auth-hollow; UI-wizard leg supervised |
| D2 | Data migration (all/per-app) | **CORRECTED** | `6C` 4P-5 (per-app) + `storage-lifecycle-acceptance` (2× migrate-all via UI, byte-identical); cited CAMPAIGN-2 legs auth-hollow; crash-safe design-level only |
| D3 | NAS client verify-before-commit | **CONFIRMED** | `SPIKE-nas-verify` Q2/Q4 + `SPIKE-nas-dsm` (real DSM 7.2 end-to-end) + `CAMPAIGN-3` |
| D4 | USB enroll/unplug/recommission | **CORRECTED** | `storage-lifecycle-acceptance` E4 (yank→auto-rebind) + E10 (re-enroll) + `4/6A` (3-USB reshuffle); cited RUNBOOK could not complete enroll |
| D5 | Decommission / eject | **CORRECTED** | `storage-lifecycle-acceptance` E9 (decommission-anyway, reboot-safe) + E12; cited CAMPAIGN-2 legs auth-hollow, SPIKE was report-only |
| D6 | Boot ordering survives reboot | **CORRECTED** | **cited `CAMPAIGN-3` F10/F11/F12 are the CRITICAL/HIGH failures** → fixes shipped agent v0.85 + re-validated live `CAMPAIGN-4` (0 ordering cycles) + `6A` 1D |
| E1 | Remote CF Tunnel | **CONFIRMED** | `CAMPAIGN-2` T-FLT-CF PASS (not in the H1-hollow set; recovered 20s) |
| E2 | LAN access when net down (IMPLEMENTED) | **CONFIRMED** | empty citation consistent with IMPLEMENTED; never drilled as customer experience → R-19 |
| E3 | Phone photo backup | **CONFIRMED** | `6D` Accept #1 + SQ3 (immich functional from offsite, byte-identical) |
| E4 | Documents/OCR | **CORRECTED** | tightened vague "6C/6E legs" → `6C` 4P-1 (paperless consume+OCR PASS) |
| E5 | SMB **server** (MISSING) | **CONFIRMED-ABSENT** | sweep `smbd\|samba\|wsdd\|net usershare\|smb.conf`: 10 hits, all client/offsite/docs — no server. Hetzner `SambaEnabled` = a box we write to, not a share we expose |
| E6 | DLNA to TV (MISSING) | **CONFIRMED-ABSENT** | sweep `minidlna\|dlna\|ssdp\|upnp`: 3 hits, all ROADMAP/map docs; zero implementation |
| E7 | File access via browser | **DEMOTED** PROVEN-LIVE→IMPLEMENTED | T-PAGE-ALL renders only controller dashboard pages, not FileBrowser; no browse/download proof anywhere |
| E8 | Forgot-password reset code | **CORRECTED** | tightened "F-15 arc" → `DRILL-day0-take2` F-15 (live re-run, hash applied 1s, code accepted first try) |
| E9 | Multi-user accounts (MISSING) | **CONFIRMED-ABSENT** | sweep of controller `auth.go`: single `effectivePasswordHash()`, no user table / role / permission model |
| E10 | WireGuard OOB (IMPLEMENTED) | **CONFIRMED** | `SPIKE-oob-wg-operator-peer` GO + `SPIKE-felhom-sshd`; unproven on true 100.64/10 — IMPLEMENTED right |
| E11 | Break-glass recovery (IMPLEMENTED) | **CONFIRMED** | `runbooks/break-glass.md`; live incident reproduced in SPIKE-felhom-sshd; no end-to-end recovery drill — IMPLEMENTED right |
| F1 | Health-degradation email | **DEMOTED** PROVEN-LIVE→IMPLEMENTED | cited `6D` P3-DELIVERY is the **enlarge-block** trigger, not health-degradation; `NotifyHealthChange` never fired an email live in any doc |
| F2 | Event catalog | **CORRECTED** | vague "per-arc" → 3 live-delivered families: enlarge-block (`6D`), claim code (`DRILL-day0-vm` F-4), reset code (`take2` F-15); `app_start_failed`/dead-app unit-only |
| F3 | Empty-email wipe guard (IMPLEMENTED) | **CONFIRMED** | controller v0.137.0, red-proofed 07-15 |
| F4 | System/container metrics | **DEMOTED** PROVEN-LIVE→IMPLEMENTED | cited T-RES-CPU/T-SOAK-LOOP are H1/H2 harness artifacts (auth-302); SQLite/Chart.js/30-day downsampling validated nowhere |
| F5 | Debug rings + log-bundle pulls | **CORRECTED** | rings live-exercised `CAMPAIGN-3` fix-6; noted log-bundle-pull TTL/custody half is changelog-only |
| F6 | Operator alerting (Healthchecks) | **DEMOTED** PROVEN-LIVE→IMPLEMENTED | no corpus doc; healthchecks-config app not deployed; per enum "no citation → not PROVEN-LIVE". Re-upgrade candidate if operator cites a live alert |
| G1 | Customer/host management UI | **CORRECTED** | cited "daily operator use" no-doc + `AUDIT-hub-gui` predates features (v0.25) → hub v0.53 (Peti-cluster live) + `CAMPAIGN-4` + `take2` F-16; 8-tab render = render-test-validated (UI password-gated) |
| G2 | Publish train (PARTIAL) | **CONFIRMED** | `runbooks/publish-train-rules.md` (floor-LAST, MinAgent gate); never proven on a real remote customer — PARTIAL right |
| G3 | Agent self-update | **CORRECTED** | cited `SPIKE-agent-selfupdate` is dummy-only, no operator-signed leg → `DRILL-GL6` Phase 3 (armed-key round-trip with the REAL key) |
| G4 | Controller self-update | **CORRECTED** | cited "07-10 arc" CHANGELOG → `DRILL-day0-take2` §2 (credential-empty box self-updated 0.120→0.122 live) |
| G5 | Offsite provisioning | **CONFIRMED** | `VALIDATION-offsite-provisioning-e2e` PASS (real box 611714, end-to-end) + `SPIKE-hetzner-api` + re-issue in `CAMPAIGN-offsite-overnight` |
| G6 | Per-customer offsite fill/staleness/freeze | **DEMOTED** PROVEN-LIVE→IMPLEMENTED | `OffsiteChecker` in code but no live-fired leg; `CAMPAIGN-offsite-overnight` = no fill/staleness emails, freeze write-block inconclusive |
| G7 | Box-level Storage Box aggregate (MISSING) | **CONFIRMED-ABSENT** | hub sweep `storagebox\|aggregate\|oversubscription\|sum.*quota`: only per-customer OffsiteChecker + app-telemetry; no box-level roll-up |
| G8 | Secrets hygiene (IMPLEMENTED) | **CONFIRMED** | hub v0.53.0 closing bundle (bearer → k8s Secret) |
| G9 | Operator login password from UI (IMPLEMENTED) | **CONFIRMED** | hub v0.54.0 (`handleChangePassword`, `effectivePasswordHash()`) |
### 4. R-20 verdict — **CLOSED**
Read from current `scripts/felhom-host-install.sh` source (not the changelog). Operator-key pinning
is **fully automatic in the day-0 flow, no manual step**: keys resolve at L11811219 (script
constants `OPERATOR_KEY_OPERATIONAL_*`/`_RECOVERY_*`, already populated, optional
`--operator-pubkey-file` override); pinned by `step_agent_config()` — "STEP 6/8 agent config +
service" (L2044; embedded python builds `authz.signers` L21462156, and a re-install with no keys
preserves an existing box's signers); verified at the step-8 check L23322337 ("authz signers: N
(operator-signed self-update armed)"). No interactive prompt or post-install hand-edit gates it →
ROADMAP R-20 set to **closed**. One loose end (doc-drift, not a reopen): the L193197 comment still
says the constants are "EMPTY by default" though L198201 are populated — folded into R-16 hygiene.
### 5. Commits pushed to `main`
- (recorded on push) — `docs(map): capability-map evidence audit` (00-capability-map.md header + 26 corrected / 6 demoted rows; ROADMAP R-20 closed; this REPORT).
### 6. Observations (out of scope — NOT acted on)
- **Candidate upgrades (audit cannot upgrade):** **A5** DR-tier-by-default is shown working *live and
hands-free* in `DRILL-day0-take2` §2 — arguably PROVEN-LIVE(drill), left IMPLEMENTED per the
no-upgrade rule. **C7** self-heal has code + red-proofs + a hands-free live convergence on qm300 —
stronger than a bare IMPLEMENTED, but scoped-to-one-host with no real-customer firing.
- **F6 demotion is a judgment call:** operator Healthchecks→Resend alerting may genuinely run in
production; it simply has no corpus doc, and the strict enum forbids PROVEN-LIVE without a citation.
Re-upgrade the moment a live alert is cited.
- **Recurring "cite-the-finding" inversion:** several rows cited the doc where a defect was *discovered*
as if it were the proof — D6 (F10/F11/F12 failures), C1 (F7 non-atomic finding), C6
(VALIDATION-newbox = wrong tier), C5/B5 (6B/CAMPAIGN-3 findings). The fixes live in later legs. Worth
a standing authoring rule: cite the fix-validation leg, never the finding ID.
- **`storage-lifecycle-acceptance-2026-06-15` is an uncited, high-value corpus doc** — it live-proves
D2/D4/D5 (and local-drive boot survival). Now cited; consider it first for storage rows.
- **CAMPAIGN-2 is fragile as a citation source for restore/reboot/storage rows:** harness bug H1
(auth-session expiry → CSRF-403) hollowed every `T-RST-*`, `T-STG-*`, `T-RBT-*`; several cited
evidence dirs are literally empty or a 302. Re-anchor such rows on 6D P5-REST / 6E Phase B /
storage-lifecycle-acceptance, which actually exercised these live.
- **MEMORY.md drift:** the note that `CAMPAIGN-6D` "DEFERRED P3-BROWSER/P3-DELIVERY" is stale — the
committed 6D marks both **PASS**.