a5d870ea0e
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).
181 lines
11 KiB
Markdown
181 lines
11 KiB
Markdown
# 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 1–2 — payload builder + tier-2 shares job |
|
||
| `85b76e0` | Part 3 — offsite shares leg + B′ isolation proof |
|
||
| `900c870` | Parts 4–6 — 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).
|