85 lines
5.2 KiB
Markdown
85 lines
5.2 KiB
Markdown
# REPORT — R-101 + F-DIAG: the customer must not be told a failed backup is a copy (v0.182.0) (2026-07-28)
|
|
|
|
**Overwritten** per the standing rule. Controller **v0.181.0 → v0.182.0**. Full arc incl. Phase 0, the
|
|
F-OPS runbook and the rendered dialogs: `felhom.eu/REPORT-r101.md`.
|
|
|
|
## Baselines (reconfirmed, not copied)
|
|
`felhom-controller 3db8bfb` = origin/main, clean; 0.181.0 live on both boxes; hub 0.80.0 ready 1/1.
|
|
|
|
## R-101 — misinformation at a decision point, not an alarm bug
|
|
|
|
`Tier2LastRun` is the ATTEMPT clock (`recordTier2Failure` writes it too) and it was rendered as
|
|
„Legutóbbi másolat" in the **restore confirm dialog**. The restore it guards fills in MISSING files
|
|
without touching existing ones — so a customer whose Tier-2 had been failing was told a copy existed
|
|
from last night, restored, and silently received **older** files while believing they were recent. No
|
|
error, no signal, and the customer's own reason for restoring quietly defeated.
|
|
|
|
`CrossDriveBackup` gains `LastSuccess` (the offsite anchor's rule and shape, not a second
|
|
implementation) plus `SuccessTracked`, which distinguishes "predates the anchor" from "has one and it
|
|
is empty". That marker is not optional: **all 7 Tier-2 rows on the fleet** were pre-anchor at deploy,
|
|
so without it every customer would have been shown „Még nincs sikeres másolat" at once. Legacy rows
|
|
migrate truthfully on first touch — an `ok` row adopts its time, an `error` row seeds nothing.
|
|
|
|
Three dishonest render sites fixed; the main configured branch and the shares surface were already
|
|
honest. The dialog also stops printing raw UTC RFC3339 — new `fmtTimeStr` gives Budapest-local
|
|
`2026-07-28 16:40`.
|
|
|
|
## Part 2 — the copy-site hazard, in the path of Part 1
|
|
|
|
The three `record*` helpers each built a WHOLE struct literal with two fields re-applied; everything
|
|
else was zeroed on every status write. Adding `LastSuccess` to that shape would have had
|
|
`recordTier2Failure` **clear** it — the mirror image of the defect, firing on the first failure.
|
|
`tier2Update` now copies the existing row and overlays the outcome: **safe by construction**. Sweep:
|
|
`SetTier2Preference` mutates in place (safe), `SetCrossDriveConfig(name, nil)` is a deliberate delete.
|
|
|
|
## F-DIAG — six classes, and a leak I caught in my own fix
|
|
|
|
`ClassifyOffsiteFailure` → `quota` / `orphaned` / `no_repo` / `no_units` / `transport` / **`unknown`**,
|
|
each with its own Hungarian message; unclassifiable says so rather than being folded into a neighbour.
|
|
|
|
The old message was a raw `err.Error()` passthrough carrying `sftp:<user>@<host>:<path>` off the box.
|
|
My first sanitiser regex-matched `sftp:…` and `user@host` and looked complete — **its own test caught it
|
|
leaking** on `ssh: connect to host <host> port 23: Connection refused`, a bare hostname in neither
|
|
shape. It now redacts the target's **actual** host/user/path literally, regex kept as a backstop.
|
|
|
|
## Red-proofs — all observed failing
|
|
| # | red-proof | observed failure |
|
|
|---|---|---|
|
|
| A | dialog back on the attempt clock | `the dialog does not name the last SUCCESSFUL copy` |
|
|
| C | gate the restore on `LastRun` | `a tier that has NEVER succeeded still offers a restore` |
|
|
| D | caution made unconditional | `a HEALTHY tier shows the failed-attempt caution ("nem sikerült")` |
|
|
| F | clear the anchor on failure | `a FAILED run wiped the success anchor (round 1)` |
|
|
| + | raw sanitiser | `the repo reference reached the message ("sftp:" leaked)` |
|
|
|
|
F runs the **real** `recordTier2Success` → `recordTier2Failure` sequence; A/C/D **render the production
|
|
template tree** and assert the string the customer reads.
|
|
|
|
`go build`, `go vet ./...`, `go test ./...` — 27 packages `rc=0`; template-ID and emoji gates OK.
|
|
|
|
## LIVE on demo-hp — the rendered dialog
|
|
|
|
Failure induced by moving the Tier-2 destination aside and leaving a file in its place, so
|
|
`mkdir …/recovery-unit` fails. (`chmod` is useless — the controller is root; `chattr +i` is refused in
|
|
an unprivileged container. Both tried, both reported.) Data only moved, never deleted; restored intact.
|
|
|
|
```
|
|
status=error last_run=14:42:18Z (ADVANCED) last_success=14:40:55Z (HELD)
|
|
```
|
|
**failed state:** `… Legutóbbi sikeres másolat: 2026-07-28 16:40. Figyelem: a legutóbbi mentési
|
|
kísérlet nem sikerült, ezért a visszaállított fájlok ennél régebbiek lehetnek.`
|
|
**healthy state:** `… Legutóbbi sikeres másolat: 2026-07-28 16:43.` — no caution, no tonal change.
|
|
**legacy state (pre-run):** `Legutóbbi másolat: 2026-07-28 03:30` — today's wording, unchanged.
|
|
|
|
Restored: destination a directory again, 86 MB intact, mode 755, `status=ok`.
|
|
**demo-felhom is the untouched control** — all 5 rows still legacy, rendering today's way, 15/15 up.
|
|
|
|
## Deployed
|
|
`felhom-controller` **0.182.0** on demo-hp and demo-felhom, both `Up (healthy)`.
|
|
|
|
## NOT yet live-validated (carried forward)
|
|
- **F-DIAG's classes** — unit-proven; no live failure of each class was induced.
|
|
- **Scenario C live** — never-succeeded is unit-proven only; no fleet row is in that state and
|
|
manufacturing one means destroying an app's only Tier-2 history.
|
|
- **The Tier-2 restore itself** — this arc changed what the dialog says, not what the restore does.
|
|
- R-100's 48h threshold (injected clock), fault 4, R-99, F-HUB, fault 12, the concurrency overlap.
|