Part 4 half 2: upgrade path PASSES, recovery journey dead-ends (R-236/237/238)
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
Destroyed guest 9201 (guarded on hostname — demo-hp also has a 9201), wiped both
drives leaving the raw /mnt/<name> mounts in place, reinstalled from the
published installer v1.25.0.
PASS — the upgrade path:
agent 0.127.0 -> 0.127.0 across the rebuild. The previous re-walk DOWNGRADED
0.126.0 -> 0.125.0 (R-216 part 4). Controller 0.203.0 from the baked golden,
Day-0 provision SUCCESS in ~2m50s, nothing applied by hand.
R-220 FIXED, proven on a genuinely rebuilt box: with both devices mounted raw
AND managed, /disks/candidates offered both drives (before the fix: two empty
lists), and both re-attached through the customer endpoint.
R-218's consume half proven live end to end: credential re-staged at 13:26:32Z,
collected on a tick, tier configured — no guest command line. Previously this
exact situation was dead end 1 and needed a guest shell.
FAIL — the data half, NOT reached. Three new findings, in the order they bite:
R-236 nothing re-stages the off-site one-time password after a rebuild, so the
"Nincs teendod" promise stalls until an operator re-issues. The retry job
is NOT at fault — it retries correctly and logs why.
R-237 the restore surface is keyed on installed + remote-marked apps, so after
a rebuild the customer is shown no backups at all — a circular dead end.
R-238 "Teljes visszaallitas elokeszitese" returns 302 and does NOTHING: no job,
no state change, no error, no line in the controller's own debug ring.
mode=unit on the same form works. This is why the sentinel byte-identity
criterion was not met — nothing came back, rather than coming back wrong.
No guest command line was used at any point in either half — the property the
previous walk could not claim.
This commit is contained in:
@@ -160,6 +160,10 @@ the fault was real. Full observables: `tests/campaign11-evidence-2026-08-05/jour
|
||||
| **R-234** | **An off-site run reports success while silently omitting an app the customer just switched on.** Found 2026-08-06 on the Part 4 venue (`part4`, VM 323), and found ONLY because the pre-destruction verification restore was run instead of trusting the green tick. Sequence, measured: a run with no app selected produced **1 snapshot**; `POST /backup/offbox/toggle` enabled `calibre-web` (HTTP 302, and the „Nincs távoli mentésre jelölt alkalmazás" warning disappeared, so the selection HAD landed); the next run finished in 30 s and reported **„✓ Rendben · 12.0 MB · 1 pillanatkép"** — still one snapshot. The app restore then refused: **„offbox: nincs pillanatkép a(z) calibre-web alkalmazáshoz"**. A THIRD run took the snapshot count to 2 and the same restore then succeeded. So a run that the customer sees as a green success did not carry the app they had just enabled, and **nothing in the card distinguishes that from a run that did**. The customer's belief ("my app is off-site") and the truth diverge silently, and they would discover it only at restore — the worst possible moment. The snapshot count is on the same card, which is what makes the omission detectable in hindsight and invisible in the moment. **Not yet root-caused**: the likely shape is that the run captured the app-selection set before the toggle committed, but that is a hypothesis, not a measurement. **This is the exact class the project already has a rule for** — "presence is not success": the run's timestamp and tick record that a run HAPPENED, not that it carried what the customer asked for. | **READY** — owner Viktor |
|
||||
| **R-235** | **The appliance console keeps telling an already-paired box to go and pair itself.** Measured 2026-08-06 on VM 323: **25 minutes after** the operator bind, with the guest provisioned, the controller reporting 0.203.0 and the agent ONLINE, the physical console still displayed „Felhom — a doboz készen áll, és a **párosításra vár**" together with the now-spent pairing code `US3-6GP` — and, in the same panel, the promise **„Ez a képernyő magától frissül — nincs teendő a doboznál"**. It does not refresh. A customer looking at their screen is told the setup has not happened, and is told the screen would have updated if it had. Cosmetic in mechanism, not in effect: it invites the customer to re-pair a working box, or to call for help about a box that is already fine. Same family as R-234 — a surface asserting a state that stopped being true. | **READY** — owner Viktor |
|
||||
|
||||
| **R-236** | **After a guest rebuild the hub never re-stages the off-site credential, so the customer's "automatic" recovery stalls until an operator notices.** Measured 2026-08-06 on the Part 4 venue. The unlock screen promises „A gép még várja a házon kívüli tárhely kapcsolódási adatait — amint megvannak, a mentéseid listája megjelenik… **Nincs teendőd**". What actually happens: the rebuilt box declares `needs_credential`, R-218's retry job ticks every 5 min exactly as designed and logs the honest reason — `[offsite-apply] credential retry: consume one-time password: no unconsumed offsite password (already consumed or none provisioned) (the box still declares a need; retrying)` — **forever**, because the one-time password was consumed by the guest that no longer exists and nothing mints a new one. The fix is a single operator action that already exists (`/configs/<id>/offsite-reissue`), and pressing it resolved the stall within one tick. **R-218's consume half is not at fault — it is the half that works**; the gap is upstream, in who re-stages after a rebuild. The customer-facing copy is honest in its fallback („Ha egy napon belül nem áll be, jelezd az üzemeltetőnek") but the headline „Nincs teendőd" is not true for the rebuild case, which is precisely the case the recovery feature exists for. | **READY** — owner Viktor |
|
||||
| **R-237** | **After a successful recovery the customer is shown no backups at all, because the restore surface is keyed on apps that are currently installed and currently marked for future remote backup.** Measured 2026-08-06. Post-rebuild, with the key recovered, the tier configured and the escrow re-sealed, `/backups/restore` said „**Nincs telepített alkalmazás**" and „Nincs távoli mentésre jelölt alkalmazás — a kijelölés a Távoli mentés oldalon történik" — pointing at a page which itself said there were no installed applications. A **circular dead end**: to restore an app you must select it; to select it, it must be installed; to know what to install, you must see the backup you cannot see. Reaching the restore actually required three undocumented steps in order — re-attach the drives, redeploy the app, and toggle it on for **future** remote backups (`/backups/restore/app` refuses with „Ez az alkalmazás nincs távoli mentésre kijelölve", i.e. a *forward-looking* setting gates a *backward-looking* action). None of this is hinted at by the recovery screen, which says „Nincs teendőd". A household that has just lost its box does not know which apps it used to run. | **READY** — owner Viktor |
|
||||
| **R-238** | **„Teljes visszaállítás előkészítése" — the only path to the customer's own files — accepts the click and does nothing, silently.** Measured 2026-08-06, repeatedly. `POST /backup/offbox/restore` with `mode=full` (the exact form the page renders) returns **302**, and then: no job is ever recorded (`/api/backup/restore-status` `last` stays `null` across ~9 minutes of polling), the wizard stays on step 1 „Előkészítés" with the same two forms, no error is shown to the customer, and **the controller's own debug ring contains no line for it at all** — zero `offbox`, `prepare` or `snapshot` entries. `mode=unit` on the identical form works and reports properly, so the plumbing and the session are fine. This is the terminal dead end of the recovery journey: everything upstream succeeded — code accepted, key recovered, tier configured, escrow re-sealed, drives re-attached, app redeployed — and the customer still cannot get their files back. **PASS for the R-201 re-walk was defined as a sentinel's sha256 byte-identical after recovery; this is why that criterion was NOT met.** Related but distinct from R-237: that one is about not being able to *find* the backup, this one is about the button not working once found. | **READY** — owner Viktor |
|
||||
|
||||
**Recorded against existing rows by Phase 2:**
|
||||
|
||||
- **R-216 — §4.1 is now MEASURED, not deduced.** The previous session could only offer two absences.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# PART 4 — the re-walk on a real installation, with no hand upgrade (2026-08-06)
|
||||
|
||||
Task 4 Part 4. **Half 1 (the fixture) is complete and the venue is STANDING.** Nothing has been
|
||||
destroyed; half 2 (destroy → reinstall → recover → compare) is owed and deliberately not started.
|
||||
Task 4 Part 4. **Both halves were walked.** Half 1 (the fixture) is below; **half 2 (destroy →
|
||||
reinstall → recover) is at the end of this file**, with its verdict stated separately as required:
|
||||
the upgrade path **PASSED**, the data half **FAILED — not reached**, because the recovery journey
|
||||
dead-ends before any file comes back (R-238). The venue is still standing and its teardown is owed.
|
||||
|
||||
The question this venue exists to answer: **does a box built from the published artifacts get R-218's
|
||||
and R-220's fixes without anyone touching it?** The previous re-walk could not ask that — its reinstall
|
||||
@@ -248,3 +250,140 @@ that was empty.
|
||||
correctly; the extra state was mine.
|
||||
- Grepping the rendered page for `Hiba` matched **JavaScript template strings**, not errors. Counted
|
||||
and discarded rather than reported as failures.
|
||||
|
||||
---
|
||||
|
||||
# HALF 2 — destroy, reinstall, recover (2026-08-06, same day)
|
||||
|
||||
**VERDICT, both halves separately:**
|
||||
|
||||
| half | verdict |
|
||||
|---|---|
|
||||
| **The upgrade path** — does a real installation get the fixes with no hand upgrade? | **PASS** |
|
||||
| **The data** — are the sentinels byte-identical after recovery? | **FAIL — not reached.** The journey dead-ends before any file comes back (R-238). |
|
||||
|
||||
## The destruction
|
||||
|
||||
`pct destroy 9201 --purge` at **15:04:15** on the appliance (guarded on `hostname = part4`, because
|
||||
`demo-hp` also has a guest 9201 and destroying the wrong one would have been serious). Both LVs
|
||||
removed; the host identity `part4-905bdb` survived. Both drives then wiped to 20 K **with their mounts
|
||||
left in place** — that is deliberate, because the surviving raw `/mnt/<name>` mount **is** the R-220
|
||||
condition.
|
||||
|
||||
The originals of all three sentinels are on DooPlex with known hashes, so the wipe risked the *test
|
||||
outcome*, never the data.
|
||||
|
||||
**A stale-report window worth recording:** immediately after the purge the hub still showed
|
||||
`1/1 running` for a guest that no longer existed, until the next agent report. `pct list` on the box
|
||||
was already empty. Presence is not success — and a report is not a measurement.
|
||||
|
||||
**The box does not self-heal a purged guest.** Nothing re-provisioned it; the reinstall was needed.
|
||||
|
||||
## The reinstall — and the R-216 recurrence is GONE
|
||||
|
||||
`felhom-host-install.sh` **v1.25.0** fetched live from `felhom.eu/scripts/`, run as a transient unit
|
||||
with the retrieval passphrase read from a `0600` file (`--passphrase-file`), never a command line.
|
||||
|
||||
```
|
||||
Day-0 provision SUCCESS — vmid=9201 host_id=part4-905bdb customer=part4 (~2 m 50 s)
|
||||
controller image: gitea.dooplex.hu/admin/felhom-controller:0.203.0
|
||||
```
|
||||
|
||||
| | previous re-walk | this one |
|
||||
|---|---|---|
|
||||
| agent across the rebuild | **0.126.0 → 0.125.0 (DOWNGRADE)** | **0.127.0 → 0.127.0** |
|
||||
|
||||
That downgrade was R-216 part 4 recurring: an operator who fixes a box by hand has it re-broken by the
|
||||
very event that makes recovery necessary. It does not happen when the vouched agent *is* the newest —
|
||||
which is what Part 3's three-field vouch achieved.
|
||||
|
||||
## R-220 — FIXED, proven on a genuinely rebuilt box
|
||||
|
||||
Precondition verified **before** measuring — both devices mounted twice, raw and managed:
|
||||
|
||||
```
|
||||
/dev/sdb /mnt/adatok ext4 /dev/sdb /mnt/felhom-drives/adatok ext4
|
||||
/dev/sdc /mnt/mentes ext4 /dev/sdc /mnt/felhom-drives/mentes ext4
|
||||
```
|
||||
|
||||
The agent's own `/disks/candidates` (measured at the agent, because that is where the fix lives):
|
||||
|
||||
```
|
||||
attach : /dev/sdb, /dev/sdc
|
||||
initialize : /dev/sdb, /dev/sdc
|
||||
```
|
||||
|
||||
Before the fix this returned `initialize: [], attach: []`, and the deploy refused by telling the
|
||||
customer to choose from a list that was empty. Both drives then **re-attached through the customer
|
||||
endpoint** (`registered: true`) — the end-to-end confirmation.
|
||||
|
||||
## R-218 — the consume half, proven live, end to end
|
||||
|
||||
The unlock screen put the box in exactly R-218's situation: „A gép még várja a házon kívüli tárhely
|
||||
kapcsolódási adatait — amint megvannak… **Nincs teendőd**."
|
||||
|
||||
The retry job then did precisely what it was built to do, and said why:
|
||||
|
||||
```
|
||||
[offsite-apply] credential retry: consume one-time password: no unconsumed offsite password
|
||||
(already consumed or none provisioned) (the box still declares a need; retrying)
|
||||
```
|
||||
|
||||
**The blocker was upstream, not in the retry** — after a rebuild nothing re-stages the one-time
|
||||
password (**R-236**). Operator re-issue at **13:26:32Z**, and the box collected it on a tick:
|
||||
|
||||
```
|
||||
[offsite-apply] settle-gate: GO — at/above floor 0.156.0 (we are 0.203.0)
|
||||
[offsite-apply] offsite configured for u629488-sub6@…:/home/felhom-repo (pending key escrow)
|
||||
```
|
||||
|
||||
**A credential staged after start-up, collected without intervention and without a guest command
|
||||
line.** In the previous walk this same situation was dead end 1 and needed a guest shell.
|
||||
|
||||
## The customer journey, as read from the screen
|
||||
|
||||
- **The recovery screen appeared without being sought** — `/` landed on „Adatok visszaszerzése",
|
||||
claim-gate markers 0.
|
||||
- It states the seal date, that nobody can replace the code, and that **this step changes nothing**.
|
||||
*(The seal date still renders as a raw RFC3339 string — `2026-08-06T12:41:55Z` — to a Hungarian
|
||||
household. Third walk running; still unfixed.)*
|
||||
- **The unlock took 1.804 s** — a real unseal. For contrast, a submission with an empty field returned
|
||||
in **25 ms** with „Add meg a helyreállítási kódot" — a **neutral prompt, not a wrong-code
|
||||
accusation**. That is R-224/R-226 behaving as shipped: unknown defaults to neutral, and the typing
|
||||
message is reachable only after a real refusal.
|
||||
- Escrow re-sealed ~15 min later („A helyreállítási kód letétbe helyezve").
|
||||
|
||||
## Where the journey ends — and it ends short
|
||||
|
||||
Everything cryptographic succeeded. The customer still cannot reach a single file:
|
||||
|
||||
1. **R-237** — the restore surface is keyed on apps that are *currently installed* and *currently
|
||||
marked for future remote backup*. Post-rebuild there are none, so „Nincs telepített alkalmazás",
|
||||
and the restore page points at a page that says the same thing. Reaching a restore at all required
|
||||
three undocumented steps in order: re-attach the drives, redeploy the app, toggle it on for
|
||||
**future** backups. A household that just lost its box does not know what it used to run.
|
||||
2. **R-238** — and then the button does nothing. „Teljes visszaállítás előkészítése"
|
||||
(`mode=full`) returns 302 and starts **no job**, leaves the wizard on step 1, shows no error, and
|
||||
writes **no line to the controller's own debug ring**. `mode=unit` on the identical form works.
|
||||
|
||||
**That is why the data half is FAIL rather than PASS.** It is not that a sentinel came back wrong —
|
||||
nothing came back at all. The sentinels' pre-destruction hashes are recorded above and their originals
|
||||
are intact on DooPlex, so the comparison can be completed the moment R-238 is fixed.
|
||||
|
||||
## Honest accounting of what half 2 did NOT establish
|
||||
|
||||
- No sentinel was restored, so **the byte-identity claim is unproven**, in either direction.
|
||||
- The off-site repository was proven readable *before* the destruction (verification restore), and the
|
||||
repo held 12.0 MB of unique data — consistent with sentinel C and inconsistent with config+DB alone.
|
||||
That is corroboration, not proof, that the sentinels are recoverable.
|
||||
- I used appliance-level SSH (host, not guest) throughout. **No guest command line was used at any
|
||||
point in either half** — the property the previous walk could not claim.
|
||||
|
||||
## Harness faults in half 2, separated from the product's
|
||||
|
||||
- Posted the unlock with field `code` instead of `recovery_code`. The product's response to that was
|
||||
*correct and informative* (the neutral empty-field prompt), which is how I noticed.
|
||||
- Nested quoting through the double SSH hop (DooPlex → demo-hp → appliance) corrupted several
|
||||
commands; switched to scp'd script files. Two "0 results" readings were **my broken parser**, not
|
||||
absence — re-measured by grepping the raw file, exactly the trap the project's own rule names.
|
||||
- A `Hiba` grep matched JavaScript template strings, not errors.
|
||||
|
||||
Reference in New Issue
Block a user