Files
felhom-controller/REPORT.md
T
admin a5d870ea0e docs(shares): REPORT.md — R-7b v0.145.0 ship report
Findings, all six red-proof outcomes, deployment, live-validation evidence, and an
explicit list of what was NOT live-exercised (offsite leg + restore round-trip: the
demo box has no offsite target).
2026-07-18 13:34:59 +02:00

181 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REPORT — most recent implementation
## felhom-controller v0.145.0 — R-7b: share data enters the live backup runs (Model B) + samba liveness — 2026-07-18
**Class:** Implementation. **Shipped:** controller **v0.145.0**, deployed and live-validated on demo
guest 9201.
## 1. Baselines (re-confirmed at session start)
| Repo | `main` @ start | Version | → Shipped |
|---|---|---|---|
| felhom-controller | `3dfc49e` | v0.144.0 | **v0.145.0** |
| felhom.eu | `b76dad0` | docs tip | docs only |
Trunk-based, explicit-path staging, no Co-Authored-By.
> The repo root also held a stale, unrelated `TASK.md` ("Telemetry Debug Section"). It was ignored —
> the R-7b prompt was the spec.
## 2. What shipped, and why it matters
Before this release the „Megosztás" page rendered „Felhőmentés: bekapcsolva" for a share whose files
were **in no backup at all**. `backup.RunTier2` short-circuits on `os.Stat(unitDir)` before the
classification seam, and the offsite runner enumerates `GetOffboxApps()`; a share-only infra stack has
neither a recovery unit nor an offbox toggle, so it fell through both engines. The toggle is now true.
**Model B (Viktor's ruling) and its invariant.** Share data enters the runs through a **sibling
shares source** — additive job/leg code reusing the proven primitives while leaving **every per-app
engine path byte-identical**. That invariant is the headline claim and is enforced by test in both
tiers, each with a red-proof.
| Part | File | Shape |
|---|---|---|
| 1 payload | `internal/backup/shares_payload.go` | deterministic `_shares-manifest.json` + best-effort `passdb.tar` |
| 2 tier-2 | `internal/backup/tier2_shares.go` | per-source-drive legs → `backups/secondary/_shares/<driveKey>/<share>`, `_payload/`, marker LAST |
| 3 offsite | `internal/backup/offbox_shares.go` | ONE `restic backup --tag felhom-offbox --tag _shares`, after the app loop, before retention |
| 4 restore | `internal/backup/shares_restore.go` | scratch → prefix-asserted missing-only merge → definitions (existing-wins) → `ReconcileSamba` → credential |
| 5 liveness | `internal/monitor/healthcheck.go` | `EffectiveProtected` gains a settings-backed dynamic extra |
| 6 UI | `sharing.html`, `backups_restore.html`, handlers | per-tier status lines; „Megosztások" restore entry |
**Seams extracted (never forked):** `selectTier2TargetFrom` (source drive supplied explicitly;
`selectTier2Target` is now a thin wrapper — the headroom math is untouched) and `tier2ReconcileRoots`
(pure extraction; `tier2Reconcile` calls it with `hdd`/`userdata`).
**Degradation contract:** a quota-blocked offsite push falls back to the **manifest only, never to
nothing** — definitions protection must not regress because the files stopped fitting.
## 3. Commits
| Hash | Subject |
|---|---|
| `c81df55` | Parts 12 — payload builder + tier-2 shares job |
| `85b76e0` | Part 3 — offsite shares leg + B isolation proof |
| `900c870` | Parts 46 — restore, samba liveness, UI truth-up |
| `3b70a9e` | docs — CHANGELOG/CONTEXT/REUSE/README; caveats cleared |
| `d0c1d77` | fix — reserved key leaked into the `crossdrive_completed` hub event (found live) |
felhom.eu: capability-map SMB row, `backlog/ROADMAP.md` R-7b, `controller/sharing.md`.
## 4. Findings
**(a) The reserved-name assumption in the task was FALSE — guard added.** The task asked me to verify
at source that slice-1 validation excludes a leading underscore. It does **not**: `settings.nbNameRe`
begins with `[A-Za-z0-9_]`, so „_shares" was an **accepted share name**. `ValidateSMBShareName` now
refuses the leading-underscore namespace (on ADD only, so existing shares are never retroactively
invalidated). Stack names come from the git-synced catalog rather than customer input, so a `_shares`
STACK is not realistically reachable — but `RunAllTier2` and `RunOffboxBackup` skip one loudly as
defense in depth rather than let it clobber the shares tree.
**(b) Part 5 — the alert pipeline needed no further change.** A missing protected container →
`report.Issues``Status="fail"``notifier.NotifyHealthChange` → the **existing** `health_critical`
event. No new event type is introduced, so the `allowedEventTypes` gotcha does not apply. Verified
live: the hub accepted the event with HTTP 200.
**(c) A real bug, surfaced by test.** `shareSourceDrive` returned a slash-normalised path, which made
the target selector's source-drive equality check miss — a share group could have targeted **its own
source drive**, i.e. a same-disk copy pretending to be tier 2. POSIX-only in effect (`ToSlash` is
identity there), but a genuine defect. Fixed.
**(d) A real leak, surfaced by LIVE VALIDATION, not by any unit test.** The first demo run pushed a
hub event reading „Másodlagos mentés elkészült: **_shares**" — the reserved key reached Hungarian
customer/operator copy through the `tier2Notify``crossdrive_completed` path I had not mapped. Fixed
at the **source** of the notification (so no future notifier wiring can reintroduce it), with
`DisplayStackName` at the main.go wiring as idempotent defense in depth, plus a regression test and a
red-proof. Re-validated live: the event now reads „…: **Megosztások**". This is the clearest argument
for the live leg — six green red-proofs did not catch it.
**(e) Two warning-prose sites need no mapping.** `offbox_capture.go` and `tier2_capture.go` embed a
raw stack name in Hungarian prose, but the shares source is a sibling and never flows through the
capture-set helpers, so `_shares` cannot reach them. Verified rather than assumed; no edit made.
**(f) Pre-existing gate failure, untouched.** `scripts/docker_run_volume_path_gate.py` fails on
`internal/appexport/estimate.go:179`. It fails identically on the unmodified tree (verified by
stashing), predates this work, and is out of scope. All six other gates pass.
## 5. Tests
`go build ./... && go test ./...`**green** (23 packages). 25 new/extended cases in
`internal/backup` + 2 in `internal/monitor`.
### Red-proofs — all six run, all fired, all reverted
| # | Break introduced | Test that failed |
|---|---|---|
| 1 | shares leg appends its paths into the **app's** argv | `TestOffboxSharesLegLeavesAppCallsByteIdentical` — "B INVARIANT VIOLATED" |
| 2 | mandatory→offsite mapping inverted | `TestOffboxSharesLegPushesMandatoryShare` + `…ExcludesOptionalShare` |
| 3 | manifest-only degradation dropped (skip leg when blocked) | `TestOffboxSharesLegQuotaDegradesToManifestOnly` |
| 4 | prefix-assert removed (`liveShareRootOK` → true) | `TestSharesRestoreRefusesDestinationOutsideLiveRoots` — "PLACE GUARD BREACHED" ×2 |
| 5 | dynamic samba extra removed | `TestEffectiveProtectedTracksSharingToggle` (enabled case) |
| 6 | shares `destBase` drops the reserved segment | `TestSharesTier2LeavesPerAppTreeUntouched` |
Plus the post-hoc regression: passing the raw key to `tier2Notify` fails
`TestSharesTier2NotifierNeverLeaksReservedKey`.
## 6. Deployment
```
gitea.dooplex.hu/admin/felhom-controller:0.145.0 Up (healthy) guest 9201
```
Built on 180 (`/mnt/5_hdd/felhom.eu/build/felhom-controller`, explicit pull first), deployed via the
bootstrap service. Deployed twice — the second time carrying the finding-(d) fix.
## 7. Live validation (demo, through the REAL server-side pipeline)
Method: logged in and POSTed `/api/backup/tier2` with a real session + CSRF token — the exact endpoint
the UI button invokes. No engine internals were called directly.
**Box state:** sharing ON, 2 shares (`dokumentumok`, `filmek`, both „Felhőmentés" ON) on
`/mnt/felhom-drives/hdd_1` (`/dev/sdb`); second schedulable drive `scratch1` (`/dev/sdd`) — genuinely
different physical disks.
**Tier-2 leg — PASS.**
```
[shares] payload staged: 2 share definition(s), credential copy=true
[shares] tier-2 copied 2 share(s) from /mnt/felhom-drives/hdd_1
→ /mnt/felhom-drives/scratch1/backups/secondary/_shares (8.1 KB)
```
```
_shares/.felhom-tier2-layout → "2" (marker present, written last)
_shares/_payload/_shares-manifest.json → -rw------- , both shares, correct paths/flags
_shares/_payload/passdb.tar → -rw------- , 855 040 B (real capture)
_shares/mnt_felhom-drives_hdd_1/dokumentumok/… → r7.txt, r7b-live.txt
_shares/mnt_felhom-drives_hdd_1/filmek/
```
A file created *after* deploy (`r7b-live.txt`) was picked up on the next run and is **md5-identical**
to the source (`d6ce02292924d3dfb48cac0ede437225` both sides). Payload permissions survive the rsync
mirror as `0600`.
**Display mapping — PASS (after the finding-(d) fix).**
`Event pushed: crossdrive_completed (info) — Másodlagos mentés elkészült: Megosztások`
**Samba liveness (Scenario E, ON direction) — PASS.** With sharing on, stopping `felhom-samba`:
```
health: {"issues":["Protected container not running: felhom-samba"],"status":"fail"}
11:29:55 Event pushed: health_critical (error) — Rendszer állapot kritikus (volt: ok) hub_status 200
```
The next tick's `EnsureBaseStack` self-healed the container (`Up 32 seconds`), and an earlier cycle
produced the matching `health_recovered`. A first attempt showed no degradation event; investigating
rather than assuming showed `scheduler.Every` waits a full interval before its first run, so that tick
hit the deliberate `prev == ""` first-observation guard — a test-timing artifact, not a defect. The
clean re-run above is the real proof.
### NOT live-exercised — stated explicitly
- **The offsite `_shares` restic leg** and **the „Megosztások" restore round-trip (Scenario D)**. The
demo box has **no offsite target configured** (`offbox_target` empty — no Storage Box credentials,
no escrow), and the run gate refuses offsite work until the repo password is escrowed. Configuring
one is operator/Viktor territory ("first offsite run = Viktor"). Both are unit-covered and
red-proofed (red-proofs 2, 3, 4), but neither has touched a real restic repo.
- **Scenario E's OFF direction** was not live-toggled — disabling sharing on the demo box would down
the stack and churn customer state. Unit-tested in both directions with red-proof 5.
- **Scenario F on a genuinely disconnected drive** (unit-tested; the live shares are on healthy drives).
## 8. Handoff
- **Viktor: raise the managed-update floor to v0.145.0** (supersedes the earlier 0.144 note) so the
N100 rehearsal's day-0 box converges onto the honest version.
- **Viktor: the offsite leg + restore round-trip need a live leg** on a box with an escrowed offsite
target. That is the one part of R-7b whose real-world behaviour rests on tests alone.
- R-7 slice-2 remainder is unchanged (avahi/`.local`, app-folder presets, per-share users, recycle bin).