132 lines
8.3 KiB
Markdown
132 lines
8.3 KiB
Markdown
# 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 in `PlaceOffsiteRestore`; F-3a-3 in `mapOffsiteRestorePaths`.
|
||
- `internal/settings/settings.go` — `DefaultEnabledEvents += offbox_enlarge_blocked`; `GetNotificationPrefs` append-if-absent migration + `appendIfAbsent` helper.
|
||
- `internal/web/handlers.go` — `offbox_enlarge_blocked` in the prefs single-event slice.
|
||
- `internal/web/templates/settings_notifications.html` — the new checkbox.
|
||
- **new** `internal/backup/offbox_place_test.go` (5 tests) · **new** `internal/settings/notif_migration_test.go` (3 tests).
|
||
- CHANGELOG / REPORT / CONTEXT.
|
||
|
||
**felhom.eu (hub v0.55.0):**
|
||
- `hub/internal/api/handler.go` — `offbox_enlarge_blocked` in `allowedEventTypes` (+ 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.0` on 180; `manifests/hub.yaml` → :0.55.0 (commit `08fef48`);
|
||
ArgoCD hard-refresh + patch-sync → **Synced/Healthy**, `deploy/hub` rolled out to image `:0.55.0`,
|
||
startup `Listening on :8080`.
|
||
- **Controller:** built + pushed `felhom-controller:0.134.1` on 180; deployed to guest 9201 →
|
||
`Up (healthy)`. Commit `0cfcc42`.
|
||
|
||
## §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 --json` newest 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) == live
|
||
`stats --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 (stored `enabled_events` does NOT persist
|
||
`offbox_enlarge_blocked` — matches the design; surfaced at read).
|
||
- **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_blocked` delivery event + customer email (leg 6) — hub ingestion is
|
||
live; end-to-end delivery needs a session or the demo API key.
|
||
- `PlaceOffsiteRestore` against 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_PASSWORD` in `180:~/.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.
|