docs: R-165 merge built and proven at the bake; R-163 + R-175 closed, R-178 filed
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
07-backup-architecture.md gains §7.5.1 (S-1: the contract changed in the same session): the ceiling §7.5 describes no longer exists for a box built from golden >= 0.192.0, the bulkhead's replacement is recorded, and R-175 is FIXED here rather than left standing — the bound is restated as a function of mp1 and scoped to split-layout boxes, naming all three real shapes. Capability map: new row as IMPLEMENTED, deliberately NOT proven-live, with the missing leg named — no box has been reinstalled from the golden, and "the golden baked" is not "a box built from it works". R-163 CLOSED: the ceiling it recorded stops existing. R-176(a) answered by P1; (b) WITHDRAWN, since every node is reinstalled rather than migrated. R-178 filed for the reinstalls, which were not done this session. CONTEXT S-13 (the variant chosen on measurement; pruning rejected with its reason) and S-14 (prove first, then vouch — the golden is published but deliberately unvouched, because vouching is what makes a fresh install pick up a layout no box has been proven from). STATUS: plain-language section; both operator questions now answered, so the waiting-on-you item is cleared. Two older entries trimmed so the page did not grow.
This commit is contained in:
@@ -1,147 +1,83 @@
|
||||
# REPORT — hub v0.89.0: the two halves of decision D-c, plus the R-165 merge spike (2026-08-02)
|
||||
# REPORT — R-165: the `mp1` merge, built and proven at the bake (2026-08-03)
|
||||
|
||||
**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.
|
||||
**Overwritten** per the standing rule. The prior contents (hub v0.89.0 / R-167, 2026-08-02) have their
|
||||
durable record in `hub/CHANGELOG.md` and `CONTEXT.md` S-11/S-12.
|
||||
|
||||
**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**.
|
||||
**Companion:** `felhom-agent/REPORT.md` holds the full session detail — probes, variant evidence, the
|
||||
bake transcript, red-proofs and teardown. **This file covers what changed in THIS repo, and the CI
|
||||
verification for all three.**
|
||||
|
||||
> **Scope, stated first.** The merge is **built and green through Phase 5**. **Phases 6–7 — reinstalling
|
||||
> the two demo boxes from the merged golden and proving one end to end — were NOT done, and nothing
|
||||
> was wiped.** The golden is therefore deliberately **unvouched**. Remaining work: **R-178**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Baseline drift — recorded, because the task's §1 was wrong
|
||||
## 1. What changed in `felhom.eu`
|
||||
|
||||
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.
|
||||
**No hub change; no hub version bump.** This repo changed in two places, one of them forced.
|
||||
|
||||
## 2. Hub change (v0.89.0)
|
||||
**`scripts/felhom-host-install.sh` — forced by a census, not planned.** `step_grows` computed **two**
|
||||
volume sizes and the install call passed both, so it had to change with the agent or every install
|
||||
would have provisioned a half-sized box. It now computes ONE total, summing the old 80/20 split
|
||||
(`226` = the previous `184 + 42`), so **a standard appliance keeps exactly the 250 G it had** — it is
|
||||
simply no longer split by a wall. **The size still comes from the physical disk**: `step_grows`
|
||||
already read the thin pool's real free space, and the merge only collapsed its two outputs into one.
|
||||
That is the answer to the task's §8.2 — no row needed filing. `--sysdata-grow` is deprecated but still
|
||||
honoured, because the agent **folds** a hand-passed value in rather than dropping it.
|
||||
|
||||
**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.
|
||||
**Documentation**, per the coupling rule — see §3.
|
||||
|
||||
| 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 |
|
||||
## 2. The bake, as this repo's audit trail records it
|
||||
|
||||
**Tests 574 → 579**, full suite green (`go build ./... && go vet ./... && go test ./...`), all five
|
||||
`repo_gates.py` gates OK.
|
||||
`documentation/audits/SPIKE-r165-phase0-2026-08-03.md` (new) holds P1, P2 and P3 with method,
|
||||
measurement and ruling, including the teardown of every probe artefact at all three layers. The
|
||||
headline measurements:
|
||||
|
||||
**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.
|
||||
- **P1 PASS** — a real pre-merge archive restore-tests clean, `mount_parity: ok`, 84 s, `mountParity`
|
||||
untouched.
|
||||
- **P2** — all three variants boot and reboot 3/3; they are separated by **scoping**, not mechanics.
|
||||
The container's view of `/mnt` is 8.0K under V-a and V-c, and **17.9M — Docker's entire data-root —
|
||||
under V-b**. The operator chose **V-c**.
|
||||
- **P3** — the four retargeted golden assertions, run against a deliberately wrong shape: **8/8**.
|
||||
|
||||
**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
|
||||
## 3. 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) |
|
||||
| `documentation/architecture/07-backup-architecture.md` | **S-1: the contract changed in the same session.** New **§7.5.1** — the ceiling §7.5 describes no longer exists for a box built from golden ≥ 0.192.0, the bulkhead's replacement (B2) is recorded, and **R-175 is FIXED here**: the bound is restated as a function of `mp1` and scoped to split-layout boxes, naming all three real shapes |
|
||||
| `documentation/architecture/00-capability-map.md` | new row — **IMPLEMENTED, not PROVEN-LIVE**, with the missing leg named (no box reinstalled, R-178) and the bake cited as the evidence it is |
|
||||
| `documentation/backlog/OPEN-ITEMS.md` | **R-165** → shipped-not-yet-proven-live; **R-163 CLOSED** (its ceiling no longer exists); **R-175 CLOSED**; **R-176** (a) answered by P1, (b) **withdrawn** — every node is reinstalled, not migrated; **R-178 NEW** |
|
||||
| `CONTEXT.md` | **S-13** (the variant, chosen on measurement; B2's floor; pruning rejected with its reason; no migration exists) and **S-14** (prove first, then vouch) |
|
||||
| `STATUS.md` | rewritten section in plain language; the *Waiting on you* item cleared — both questions are answered; **two older entries trimmed** so the page did not grow |
|
||||
| `scripts/CHANGELOG.md` | the host-install change, with why it was forced |
|
||||
|
||||
## 5. Register IDs
|
||||
## 4. CI — run ids and conclusions
|
||||
|
||||
**Opened:** R-174, R-175, R-176, R-177. Each established free by
|
||||
`grep -ro "R-17n\b" documentation/ *.md` → **0 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
|
||||
|
||||
Checked by PULL from `…/actions/tasks`, matching `head_sha` to each commit — CI emails only on
|
||||
failure, so a green that was never looked at is an assumption, not an observation. **Every commit this
|
||||
session, both repos, is green.**
|
||||
Checked by **PULL**, matching `head_sha` to each commit — CI mails only on failure, so an unchecked
|
||||
green is an assumption.
|
||||
|
||||
| Repo | Commit | Task id | Run # | Conclusion |
|
||||
|---|---|---|---|---|
|
||||
| `felhom-controller` | `cf48214` (v0.191.0) | 31 | 11 | **success** |
|
||||
| `felhom-controller` | `5adae4d` (v0.191.1) | 34 | 12 | **success** |
|
||||
| `felhom-controller` | `9a3c485` (v0.191.2) | 35 | 13 | **success** |
|
||||
| `felhom.eu` | `179dd79` (hub v0.89.0) | 32 | 17 | **success** |
|
||||
| `felhom.eu` | `6d359a5` (manifest 0.89.0) | 33 | 18 | **success** |
|
||||
| `felhom.eu` | `41dbecb` (docs) | 36 | 19 | **success** |
|
||||
| `felhom-controller` | `4be6467` (v0.192.0) | 39 | 15 | **success** |
|
||||
| `felhom-agent` | `cd6e267` (v0.120.0) | — | — | *see below* |
|
||||
| `felhom-agent` | `4bb84fc` (REPORT) | — | — | *see below* |
|
||||
| `felhom.eu` | `7406ac7` (phase-0 audit) | 40 | 21 | **success** |
|
||||
| `felhom.eu` | `e3525e6` (host-install) | — | — | *see below* |
|
||||
|
||||
## 7. `--no-verify`
|
||||
*(The remaining rows are filled in from `…/actions/tasks` after the final push; any that had not
|
||||
finished at write time are listed with their status rather than assumed green.)*
|
||||
|
||||
**Not used anywhere.** Every push in this session ran `.githooks/pre-push` (`repo_gates.py --fast` /
|
||||
`controller_gates.py --fast`) and passed.
|
||||
## 5. `--no-verify`
|
||||
|
||||
**Not used anywhere.** Every push in this session ran its repo's `.githooks/pre-push` and passed.
|
||||
|
||||
## 6. What remains — R-178
|
||||
|
||||
1. Reinstall **demo-hp** from golden 0.192.0 through the real installer path; prove claim → deploy an
|
||||
app → back up → restore; show `df` proving one filesystem and a recovery unit landing on it.
|
||||
2. Only then reinstall **demo-felhom** (it carries the PBS-DR/offsite tier, so it is the box whose
|
||||
backup chain a reinstall actually disturbs).
|
||||
3. **Then** vouch golden 0.192.0 in the hub, and flip the capability-map row to PROVEN-LIVE.
|
||||
4. Re-run P1's restore-test against agent v0.120.0 — one command, turns a sound inference into an
|
||||
observation.
|
||||
|
||||
Reference in New Issue
Block a user