8.3 KiB
REPORT — Placement hardening + enlarge-blocked delivery chain (Task 3a-fix) — controller v0.134.1 / hub v0.55.0
Summary
Two follow-ups on Task 3a: (1) four hardening fixes to the not-yet-live place-to-live flow surfaced
by reviewer source-validation of v0.134.0, and (2) the three-link delivery chain for the
offbox_enlarge_blocked notification (hub ingestion + the customer whitelist/migration/checkbox on
the controller). No new architecture.
Baselines (live-verified at session start)
| Repo | main @ start |
Version | → |
|---|---|---|---|
| felhom-controller | 482d0d9 |
v0.134.0 | v0.134.1 |
| felhom.eu (hub) | 8d85da7 |
hub v0.54.0 | hub v0.55.0 |
WIP fence (§9.0) — recorded
The felhom.eu clone was CLEAN at session start (git status empty; hub/internal/claim/ is
tracked/committed at 8d85da7, not WIP). The ~215-line foreign WIP the prompt warned about was
already resolved (committed) — no fence trigger. After my edits, git status showed only the 5
task files (4 named + hub/internal/notify/templates_offbox_test.go, see note below); no foreign WIP
appeared or was touched. Staged with explicit per-file git add; pulled with --rebase --autostash.
Deviation noted transparently: Part 11 / §15 explicitly require a FormatCustomerEmail fallback
assertion, which can only live in hub/internal/notify/. §9.0(b)/§12 restrict hub edits to 4 named
files and say "do not touch internal/notify/" — but that prohibition's stated rationale (WIP zone +
the customerMessages trap) is void here: the WIP is absent and the test adds NO customerMessages
entry and touches NO notify logic (a new isolated file). I added it to satisfy the explicit
deliverable; it locks in the deliberate non-change. Flagged here for the reviewer's judgment.
Files
felhom-controller (v0.134.1):
internal/backup/offbox_restore.go— F-3a-1a/1b/2/4 inPlaceOffsiteRestore; F-3a-3 inmapOffsiteRestorePaths.internal/settings/settings.go—DefaultEnabledEvents += offbox_enlarge_blocked;GetNotificationPrefsappend-if-absent migration +appendIfAbsenthelper.internal/web/handlers.go—offbox_enlarge_blockedin the prefs single-event slice.internal/web/templates/settings_notifications.html— the new checkbox.- new
internal/backup/offbox_place_test.go(5 tests) · newinternal/settings/notif_migration_test.go(3 tests). - CHANGELOG / REPORT / CONTEXT.
felhom.eu (hub v0.55.0):
hub/internal/api/handler.go—offbox_enlarge_blockedinallowedEventTypes(+ gofmt realignment).hub/internal/api/event_test.go— acceptance case (+ the 400 red-proof target).- new
hub/internal/notify/templates_offbox_test.go— raw-message fallback assertion. hub/CHANGELOG.md·manifests/hub.yaml(image → :0.55.0).
Untouched: hub internal/notify/dispatcher.go/templates.go/store.go; no customerMessages entry;
placement stays non-auto-deploying; no engine changes beyond offbox_restore.go.
Commits
- felhom.eu hub:
08fef48 - felhom-controller:
0cfcc42
Tests — results
go build ./... && go vet ./... && go test ./... — green, both repos. Controller +8 tests, hub
+2 tests.
§10 red-proofs (mutate → FAIL → revert), all verified
| ID | Mutation | Test |
|---|---|---|
| A | restore AppNamespaceRoot fallback (neuter undeployed guard) | TestPlace_UndeployedRefused (copier ran) |
| B | delete placement headroom gate | TestPlace_HeadroomRefused (copier ran) |
| C | neuter the stat pre-pass | TestPlace_IncompleteScratchRefusedNoCopies (copies > 0) |
| D | restore p != oldNs && escape condition |
TestMapOffsiteRestorePaths_RefusesNamespaceRoot (junk placement accepted) |
| E | drop post-success scratch cleanup | TestPlace_ScratchLifecycle (scratch survived) |
| F2 | unconditional append | TestGetNotificationPrefs_AlreadyPresentNoDuplicate (duplicate) |
| Hub | remove the allowlist entry | TestHandleEvent_OffboxEnlargeBlockedAccepted (400) |
All reverted; post-revert both suites green; no mutation residue.
Deploy / verify
- Hub: built + pushed
felhom-hub:0.55.0on 180;manifests/hub.yaml→ :0.55.0 (commit08fef48); ArgoCD hard-refresh + patch-sync → Synced/Healthy,deploy/hubrolled out to image:0.55.0, startupListening on :8080. - Controller: built + pushed
felhom-controller:0.134.1on 180; deployed to guest 9201 →Up (healthy). Commit0cfcc42.
§13 live validation
Credential blocker (honest): the sanctioned credential 180:~/.config/credentials C4_PASSWORD
(len 14) is stale — the demo customer changed their dashboard password after the one-time claim
(login returns Hibás jelszó, no session). So the dashboard-session legs (manual run trigger, the two
scratch-restore legs, the UI prefs round-trip, the synthetic delivery event) could NOT be driven. I
did not reset the customer's password or extract the controller's hub API key to force a session
(both would alter demo state / overreach). What was validated read-only / organically instead:
- Leg 2a — enlarged snapshot shape (LIVE, organic): the daily 04:15 CEST scheduled run (2026-07-15
02:15 UTC, v0.134.0) produced the multi-path shape.
restic snapshots --no-lock --jsonnewest per tag: calibre-web =["…/nas-media/backups/primary/calibre-web", "…/nas-media/userdata/media/books"](unit + MANDATORY userdata); the 2026-07-14 calibre-web snapshot was unit-only — the shape change is visible across runs. immich / audiobookshelf newest = unit-only (no resolvable mandatory HDD bind). - Leg 2b — raw-data quota (LIVE): stored
repo_size_bytes = 280,932,901(267.9 MB) == livestats --mode raw-data(280,932,901) exactly — the raw-data switch refreshed the stored value (was 744,763,144 / 710 MB modeless before 3a). 15 snapshots retained. - Leg 2c — forget grouping: in the deployed code (both call sites) + unit-tested; the live log line was not capturable (the container restarted at the v0.134.1 deploy, rotating the 04:15 run's logs).
- Delivery chain (partial, read-only): hub v0.55.0 allowlist entry is LIVE (deployed Synced/Healthy
- the hub acceptance test). Controller notifier enabled (
Notifier enabled (hub: https://hub.felhom.eu)). The prefs migration is confirmed getter-only (storedenabled_eventsdoes NOT persistoffbox_enlarge_blocked— matches the design; surfaced at read).
- the hub acceptance test). Controller notifier enabled (
- Hygiene: no scratch dirs were created (legs 3–4 did not run); the login-attempt temp files on 180 were removed; read-only inspection scripts removed from the container.
NOT yet live-validated — awaiting a supervised session with the CURRENT customer password / 6D
- Manual run trigger via the UI endpoint (organically covered by the scheduled run for shape + size).
- Unit-only + full scratch restore via the real endpoints (legs 3–4).
- The prefs checkbox round-trip in the live UI (leg 5) — migration + sync are unit-tested + getter confirmed live; the checked-render + save-survives round-trip needs the session.
- The synthetic
offbox_enlarge_blockeddelivery event + customer email (leg 6) — hub ingestion is live; end-to-end delivery needs a session or the demo API key. PlaceOffsiteRestoreagainst live data; organic enlarge-block firing (demo repo 268 MB / quota 50 GB); the SQ3 immich offsite-only full circle.- Follow-up flagged: refresh
C4_PASSWORDin180:~/.config/credentials(the demo customer's current dashboard password) so future in-session live legs aren't blocked.
Observations (documented, not acted on)
- Getter-based migration trade-off: the append-if-absent migration lives in
GetNotificationPrefs(per §2.2's explicit instruction). Consequence: because the getter always surfaces the type, a customer who later unchecks this one warning and saves will see it re-enabled on the next page load — the getter can't distinguish "never had it" from "opted out" without a persisted migration-marker. Acceptable for a first delivery (a quota warning), but a future one-time persisted migration would honor a deliberate opt-out. Noted, not changed (the task specified the getter). - The stale
documentation/controller/backup-architecture.md("restic is gone from the controller") remains — flagged in the v0.134.0 report; still its own task.