Files
felhom.eu/REPORT.md
T
admin 41dbecb264
gates / gates (push) Successful in 8s
docs: R-167 + R-158 CLOSED, R-165 SPIKED, R-174..R-177 filed
R-167/R-158 shipped and proven live (controller v0.191.x, hub v0.89.0):
two new capability-map rows PROVEN-LIVE with live citations, and
07-backup-architecture.md §7.5's closing claim "nothing warns when an app
crosses the line" is now false and rewritten (S-1: an architectural
contract changed in the same session). §7.5 also gains the caveat that its
size bound is ONE BOX'S, not the fleet's.

Part 3 SPIKE (audits/SPIKE-r165-mp1-merge-2026-08-02.md): M1-M5 measured,
NO layout touched. Three findings the merge session must not re-derive:
"the layout" is not one thing (200G/50G vs 50G/20G vs 16G/8G); mp1 is a
BULKHEAD and not only a ceiling, so after the merge an overflow reaches
/var/lib/docker; the golden fails closed on the split in four places.
D-a's condition (1) is currently SATISFIED — no external box is in the
hub's register, and both demo boxes are Tier 0 and reinstallable.
Recommendation given, choice NOT made — it ends at the operator's ruling.

CONTEXT.md S-11 (D-c's routing, and why R-158's own backup_failed proposal
was overruled) and S-12 (the monitoring landed BEFORE the merge).
STATUS.md gains the plain-language section and the merge decision, with two
older entries trimmed so the page did not grow.

New rows R-174 (closed same session), R-175, R-176, R-177; each ID grepped
free before minting.
2026-08-02 23:56:16 +02:00

9.9 KiB

REPORT — hub v0.89.0: the two halves of decision D-c, plus the R-165 merge spike (2026-08-02)

Overwritten per the standing rule. The prior contents (R-168, the CI runner, same day) have their durable record in scripts/CHANGELOG.md and CONTEXT.md S-8/S-9/S-10.

Companion report: felhom-controller/REPORT.md holds the controller side (v0.191.0/.1/.2), the full red-proof table, the Hungarian copy, and the live evidence for all three flows. This file covers the hub change, the documentation coupling, and Part 3's spike.


1. Baseline drift — recorded, because the task's §1 was wrong

The task targeted hub v0.87.0 → v0.88.0. On arrival main was at 8ef92a3f with hub v0.88.0 already shipped (R-172, the WAL fix), not d5774d318941/v0.87.0. Target corrected to v0.89.0. Highest register ID in use was R-173, not R-171.

2. Hub change (v0.89.0)

One new event type, not two. The task called for a new customer-facing type and a new operator-only one. Reconnaissance found disk_warning/disk_critical already allowlisted here, with Hungarian customerMessages, in the controller's DefaultEnabledEvents and behind a UI checkbox — and with no producer in any repo. The operator chose to wire that inert pair rather than mint a near-duplicate, so only the operator type is new.

Change File Why
+ "recovery_unit_capture_failed" internal/api/handler.go (allowedEventTypes) without it the controller's POST 400s and the event vanishes
+ "recovery_unit_capture_failed" internal/notify/dispatcher.go (operatorOnlyEvents) this is what makes it operator-only; the allowlist does not, and v0.78.0 claimed otherwise and shipped the defect
- customerMessages["disk_warning"], - ["disk_critical"] internal/notify/templates.go FormatCustomerEmail PREFERS the entry over the message, so a static template would discard the drive label and the free-space figures the controller now sends. Same reason offbox_enlarge_blocked and disk_health_degraded have none
+ func IsOperatorOnly internal/notify/dispatcher.go lets the api package pin BOTH registers in ONE test; checked separately, allowlisted-but-not-operator-only is invisible. Read-only — the register stays unexported so nothing can widen it at runtime
REUSE.md §5 "new event type" rewritten REUSE.md it told readers to always add a customerMessages entry, which is wrong for operator-only types and harmful for dynamic-message ones

Tests 574 → 579, full suite green (go build ./... && go vet ./... && go test ./...), all five repo_gates.py gates OK.

Red-proof (Scenario G), demonstrated not argued: removing recovery_unit_capture_failed from operatorOnlyEvents fails two tests, one reading "a customer was emailed the OPERATOR-ONLY recovery_unit_capture_failed (customer@example.com)". The dispatch test runs under the breaking configuration — the customer has the event enabled and an email set — because that is the only configuration in which the missing entry is visible.

Live (guest 9201 → hub): both event types accepted and stored; operator | sent; and the positive observable customer | recovery_unit_capture_failed | skipped | operator_only read from notification_log. The customer half: customer | disk_warning | sent and customer | disk_critical | sent with the dynamic Hungarian intact.

Deploy: GitOps only — manifests/hub.yaml bumped 0.88.0 → 0.89.0 (6d359a5), pushed, then a deliberate ArgoCD hard-refresh + sync. Never kubectl set image. App felhom Synced / Healthy, deploy/hub rolled out, running gitea.dooplex.hu/admin/felhom-hub:0.89.0, startup log clean.

3. Part 3 — the R-165 spike. M1-M5 each answered; nothing was changed.

Full document: documentation/audits/SPIKE-r165-mp1-merge-2026-08-02.md. No partition was created, resized, moved or deleted; no golden rebuilt; no guest config edited. ep0 and Peti's box were not contacted (D-d, runbooks/target-selection.md).

M1 — what is actually there. ANSWERED, and it contradicts the architecture doc.

demo-felhom demo-hp golden default
mp0 /var/lib/docker 200 G (13 G used) 50 G (5.4 G used) 16 G
mp1 /mnt/sys_drive 50 G (2.0 G used, 5%) 20 G (92 M used, 1%) 8 G

§7.5 documents the appliance as mp0 50G / mp1 20G — that is demo-hp exactly and not demo-felhom. Any merge plan expressed as a fixed pair is already wrong for one of the two boxes that exist. §7.5's headline bound ("≈ 19 GB … ≈ 10 GB") is derived from mp1 = 20 G and is therefore one box's, not the fleet's → R-175, filed and §7.5 annotated in this session.

M2 — what lives on mp1. ANSWERED, and it is not only backups. Four things would move: Tier-1 units of driveless apps (269 M, ~30 apps on demo-felhom), Tier-2 mirrors (1.7 G — i.e. the MAJORITY is Tier 2, not Tier 1), the userdata/import drop zone which lives on the system drive by contract (R-75), and the system-data userdata namespace. Observed fill is 5% / 1%: the constraint is a ceiling problem, not a current-fill one.

M3 — which merge shapes exist. ANSWERED for three shapes, with ONE item explicitly unmeasured. The golden fails closed on the split in four places, not one (build-golden.sh:126,130 separate-mount asserts + :315,319 vzdump-exclusion guards). The archive scope rootfs+mp0+mp1 stays complete after a merge (the data moves onto mp0). mountParity holds for new archives. Unmeasured and reported as such: whether a pre-merge archive restore-tests into a merged guest — reading mountParity says it should, but that is reasoning from source about an unvalidated mechanism, which this project has got wrong four times → R-176.

M4 — the bulkhead. ANSWERED, and it is the important one. mp1 is not only a ceiling: today an overflow is refused per app with the last good unit byte-identical and cannot reach /var/lib/docker. After the merge it can, and a full Docker data-root is a stopped box, not a slow one. Four replacements costed — a reserved block percentage, a refusal threshold in the capture path, a project quota, or deeming R-167's warnings sufficient — with the trade-off of each. Deliberately not chosen: this is the operator's ruling.

M5 — existing boxes. ANSWERED for the measurable population; one part honestly UNMEASURED. The hub's /hosts register holds four hosts, two ONLINE, both demo boxes — and both are Tier 0, therefore reinstallable rather than migratable (D-d), so migration cost for the measurable population is zero. D-a's condition (1) — "before any external install" — is currently SATISFIED, which makes this the cheapest this decision will ever be. peti-felhom exists as a customer with NO host in the register, so its layout is not knowable from the hub and the box was not contacted; whether it needs converting or reinstalling is the operator's information. The in-place migration procedure has never been rehearsed, so "is the box restorable at every point of it?" is currently unknown → also R-176.

Ranked options and recommendation: (1) S1 — one volume with the two paths as directories — plus B2, a refusal threshold in the capture path, shipped as a fresh-install shape with the demo boxes reinstalled; (2) S1 + warnings only; (3) S3, grow mp1 and keep the split (D-a's rejected baseline, measured for comparison); (4) S2, two mounts on one pool — not recommended at all, it satisfies every assertion while delivering none of the benefit and converts a clean per-app refusal into a shared-pool exhaustion neither df can see coming.

STOPPED at the operator's question, per the task. The merge is next session's supervised work.

4. Documentation coupling

File Change
documentation/backlog/OPEN-ITEMS.md R-158 closed (by R-167 — no second row for the same wire); R-167 closed; R-165 updated with M1-M5 + the operator question, stays open; 4 new rows R-174/175/176/177
documentation/backlog/ROADMAP.md R-158 collapsed to a shipped one-liner; R-167 added as shipped; R-165 added as spiked/waiting-on-operator
documentation/architecture/00-capability-map.md two new rows, both PROVEN-LIVE with live citations
documentation/architecture/07-backup-architecture.md §7.5 S-1: the contract changed in the same session. The section's closing claim "nothing warns when an app crosses the line" is now false; the alerting is written in, and the one-box-vs-fleet caveat added
CONTEXT.md S-11 (D-c's routing, and why R-158's own backup_failed proposal was overruled) and S-12 (the monitoring landed before the merge, not with it)
STATUS.md new plain-language section; the merge decision added to Waiting on you; two older entries trimmed so the page did not grow — one screen, per its own rule
REUSE.md the "new event type" extension point rewritten (see §2)

5. Register IDs

Opened: R-174, R-175, R-176, R-177. Each established free by grep -ro "R-17n\b" documentation/ *.md0 hits, run before minting. Closed: R-158, R-167, R-174. Updated, still open: R-165, R-163 (unchanged — it stays the record of the constraint until the merge lands).

6. CI — run ids and conclusions

Repo Final commit Run Conclusion
felhom-controller 9a3c485 see below see below
felhom.eu (docs commit) see below see below

(Filled in from …/actions/tasks?limit=3 after the final push — see the closing section of the session output; a green CI that was not looked at is an assumption, not an observation.)

7. --no-verify

Not used anywhere. Every push in this session ran .githooks/pre-push (repo_gates.py --fast / controller_gates.py --fast) and passed.