Files
felhom-controller/REPORT.md
T
admin a62bb3874b
gates / gates (push) Successful in 9s
REPORT + CONTEXT: v0.202.0's rule, its live proof, and what was NOT verified live
CONTEXT gains the rule so it outlives the bug: on the unlock path the customer
is blamed only after a real attempt REFUSED their code; every other outcome,
including an unclassifiable one, says something else. Plus the two things that
must not be 'fixed' into it — elapsed time is never a classifier, and the error
TEXT is never read (when the distinction was not a value, the agent was changed
to provide one).

REPORT states the split honestly: the AGENT half is proven live on the venue
(400 -> 502 -> 400, same wrong code, only the hub's reachability changed), while
the controller's message selection rests on handler tests and red-proofs,
because /recovery correctly redirects since F7 set the old data aside and
restoring that state is the reconfiguration §11 forbids. Also records that the
correct codes were shredded by the previous session, so the live re-run used a
WRONG code — which makes the test harder, not weaker.

Two venue changes stated because they were not asked for, both restorations: a
fresh dashboard password (the previous session shredded it, leaving the box
impossible to log into) set through the supported --print-reset-code escape
hatch, and one normal off-site run to populate stats_known.
2026-08-06 08:34:19 +02:00

12 KiB
Raw Blame History

REPORT — felhom-controller v0.202.0 (+ felhom-agent v0.126.0)

The customer is blamed only after a real attempt refused their code. R-224, R-226, R-225, R-227, R-228 — all five closed. No campaign venue was rebuilt, re-ceremonied or reconfigured.

1. The venue's state at the end — WORKING

c11-36d660 ONLINE, controller 0.202.0 healthy, agent 0.126.0, off-site last_status: ok, 2 snapshots, stats_known: true, the set-aside history intact at /home/felhom-repo.orphaned-20260805. Every injected fault reverted; the hub route restored and confirmed (http=302, OUTPUT chain empty).

Two changes to the venue, both restorations rather than reconfigurations, and both stated because they were not asked for. The previous session shredded the venue's dashboard password along with the recovery codes, leaving the box impossible to log into — which a future re-walk needs. A fresh password was set through the supported escape hatch (--print-reset-code → the real /claim form, R-204 item 1) and stored 0600 in ~/.config/campaign11/. And a normal off-site run was triggered to populate the new stats_known field. Neither touches the recovery/escrow state the re-walk depends on.

2. Scenario A live, and its red-proof — the session's headline

Live, on the venue, with the SAME wrong code and only the hub's reachability changed:

hub reachable    → HTTP 400  "the recovery code did not open the sealed bundle — nothing was written"
hub REJECTed     → HTTP 502  "the sealed recovery bundle could not be fetched from the hub —
                              the recovery code was NOT used and nothing was written"
hub restored     → HTTP 400  (back to the first)

Controls: the hub read http=302 before, http=000 exit=7 while blocked, 302 after. Before this change both answered 400 with one merged sentence — that is CAMPAIGN-11 F3, which told a customer holding a correct code that it did not open their package.

Red-proof (unit): deleting the ErrBundleFetch case reproduces it exactly — status: got 400, want 502 — {"error":"the recovery code did not open the sealed bundle…"}. Deleting the controller's RecoveryHubUnreachable case fails Scenario A with the accusation restored.

3. Elapsed times, beside CAMPAIGN-11's originals

2026-08-05 (before) now
hub unreachable, correct code 0.0556 s → accusation 502, classified, no unseal attempted
agent stopped, correct code 0.0299 s → accusation transport error → agent-unreachable class
genuinely wrong code ×3 1.194 / 1.004 / 1.014 s → accusation without the typing hint 400 → the merged message with the ten-words prompt

A limit, stated plainly. The customer-facing messages were not re-driven end-to-end on the venue, because /recovery correctly redirects: the previous session's F7 set the old data aside, which retires the offer by design. Restoring that state would be exactly the "reconfigure the venue" §11 forbids. The agent half is proven live (above); the controller's message selection is proven by handler tests and their red-proofs. That split is the honest description of what was verified how.

Similarly, the correct recovery codes were shredded at the end of the previous session per its own §7, so the live re-run used a wrong code. That makes the test harder, not weaker: with the hub down and a genuinely wrong code, the system must still resist the easy accusation — and it does.

4. Every changed Hungarian string, for review as copy

New — hub unreachable:

„Most nem sikerült elérni a Felhom központi rendszerét, ezért a mentéseidet nem tudtuk megnyitni. A kódodat NEM használtuk fel, és semmi nem változott — tedd el biztonságos helyen, és próbáld újra néhány perc múlva. Ha egy óra múlva sem megy, szólj a Felhom ügyfélszolgálatának."

New — the machine's own service unreachable:

„A gép házon belüli szolgáltatása most nem válaszol, ezért a mentéseidet nem tudtuk megnyitni. A kódodat NEM használtuk fel, és semmi nem változott — tedd el biztonságos helyen. A gép magától rendbe jöhet; próbáld újra néhány perc múlva, és ha egy óra múlva sem megy, szólj a Felhom ügyfélszolgálatának."

New — the hub holds no package:

„Ehhez a géphez nem őrzünk lezárt csomagot, ezért nincs mit megnyitni. Ez nem a kódoddal van összefüggésben. Ha korábban készültek házon kívüli mentéseid, szólj a Felhom ügyfélszolgálatának."

New — the package predates the field:

„A kódod megnyitotta a csomagot, de az még nem tartalmazza a házon kívüli tárhely kulcsát — régebben készült, mint amikor ezt elkezdtük belerakni, és utólag nem pótolható. A kódoddal semmi baj. Keresd a Felhom ügyfélszolgálatát."

New — the neutral default:

„A művelet nem fejeződött be, és nem tudjuk biztosan, miért. Semmi nem változott, és a mentéseid érintetlenek. Próbáld újra néhány perc múlva — ha másodszorra sem sikerül, szólj a Felhom ügyfélszolgálatának."

CHANGED — the retained-earlier-package message (R-226 adds the mistype clause):

„Ez a kód nem nyitotta meg azt a csomagot, amit most őrzünk ehhez a géphez. Két oka lehet, és innen nem tudjuk megkülönböztetni őket. Lehet elgépelés: ellenőrizd, hogy mind a tíz szót pontosan, szóközökkel elválasztva írtad-e be — a kis- és nagybetűk nem számítanak. Vagy egy korábbi kódot adtál meg: a géped azóta új mentési kulcsot kapott, és a régebbi csomagot (…) nem töröltük — megőrizzük, megnyitni viszont innen egyelőre nem lehet. Ha újrapróbálod és úgy sem megy, és a régebbi mentéseidre van szükséged, keresd a Felhom ügyfélszolgálatát. A kódoddal semmi nem történt, és semmi nem változott."

NEW — the set-aside notice (R-228):

A korábbi mentéseid félre vannak téve — nem töröltük őket. Amikor új mentési kulcsot kapott a géped, a régebbi előzményt átmozgattuk a távoli tárhelyen, és ott is maradt. Megnyitni innen egyelőre nem lehet, és ez nem a kódodon múlik. Ha szükséged van rá, keresd a Felhom ügyfélszolgálatát."

CHANGED — the set-aside confirmation bullet (§7.6, it over-promised):

was: „a helyreállítási kód nélkül többé nem lesznek megnyithatók" now: „a félretett mentések innen többé nem nyithatók meg — sem kóddal, sem anélkül"

NEW — R-225's unknown states: „a pillanatképek száma még ismeretlen" · „még nem tudjuk, mennyi van a tárolóban — legfeljebb N GB"

NEW — R-227's gateway message: „A gép éppen újraindul, ezért most nem tudtuk befejezni a műveletet. Semmi nem változott. Várj néhány másodpercet, és próbáld újra — a kódodra továbbra is szükséged lesz, úgyhogy tartsd kéznél."

5. How rerr was classified, and what it cost

From the value. It could not be done in this repo alone, and that is the cost.

RecoverInstallCore returns the agent's error verbatim, and the agent answered HTTP 400 with one sentence for both a failed fetch and a wrong code. No value available to the controller separated them — Scenario A and Scenario C were mutually unsatisfiable. The task scoped felhom-agent as untouched; its §5 ("if the step is not recoverable from the value, make it so — and say what that cost") and §4.3 (the source wins) authorise the expansion, and the source forced it.

The cost: a second repo, agent v0.126.0, published and installed on the venue (7ecf8e9cdba237bc…, verified by independent download); a new escrow.ErrBundleFetch sentinel; a MinAgent 0.126.0 coupling; and an agent version that is deliberately NOT vouched — so new installs are unaffected until the operator vouches, and boxes on 0.125.0 simply degrade to the neutral message. Verified live: demo-hp runs controller 0.202.0 on agent 0.125.0, healthy.

Within the controller, agentapi.RecoveryRefusal carries the status; ClassifyRecoveryFailure is the single mapping point; no branch reads the error text. A test asserts the same sentence under two statuses classifies two ways.

6. §7.5 — which layer answers the gateway error

traefik, and this repo generates its config (internal/infra/templates/traefik*.tmpl). So the fix could live here — but traefik v3 serves no static files, so a branded page would need a new always-up container holding an error page for every 502 on the box. That is out of proportion to this finding and is scoped, not built. Shipped instead: the sanctioned client-side alternative — the unlock posts via fetch and answers a gateway failure in Hungarian in-page. Progressive enhancement: with no JS the plain POST is unchanged and still shows the proxy's error.

7. What is true about the set-aside store today (§7.6)

It cannot be opened by anyone — not the customer, not the operator. Serving a superseded package is an unbuilt link (R-199's inventory). The screen therefore states two facts and stops, and the confirmation copy was corrected because it implied that with the code the history could be reopened. The field's own comment called it "recovery-code-recoverable" — the same over-promise in the code, also corrected.

8. Tests and red-proofs

go build · go vet clean · 28 controller packages ok · 29 agent packages ok · all controller gates OK · all agent gates OK.

Scenario Red-proof mutation Result
A delete the RecoveryHubUnreachable case FAIL — accusation returns
A (agent) remove the %w join / delete the handler case FAIL — 400 with the wrong-code sentence
C remove the mistype clause FAIL — ten-words prompt unreachable again
D default to the accusing message FAIL — unclassifiable blames the customer
E route an instant transport failure to the typing message FAIL on that row
F remove the StatsKnown guards FAIL — "an unread store reports a snapshot COUNT of zero"
H delete the set-aside block FAIL — "the set-aside history is not mentioned at all"

Two existing tests encoded the defect and were corrected, not deleted — the web fake returned a bare error for "wrong code" (the shape of an unclassifiable failure), and R-222's test forbade any mention of typing on a superseded box, half of which R-226 deliberately reverses.

A third test named the defect and did not prevent it. The agent's TestRecoveryOffsiteRepoPassword_FetchErrorIsDistinct has asserted since v0.125.0 that "the operator must not be sent to re-read their recovery code because the hub was unreachable" — and passed throughout, because it checked an error string one layer below where the merge happened. Mechanism asserted, consequence unpinned.

9. Observations, not acted on

  • The Day-0 manifest still vouches agent 0.125.0. A new box therefore lands on the old agent and gets the neutral message rather than the typing hint. Vouching is deliberately the operator's act.
  • R-202's orphan card still promises recoverability („a hozzá tartozó helyreállítási kóddal később visszaállítható lehet"), which is the same over-promise this session corrected two doors away. It is explicitly left open and is now the last place on that surface still saying it.
  • The recovery screen's seal date still renders a raw RFC3339 string to a Hungarian household, in two different formats on the same screen. Cosmetic; recorded in CAMPAIGN-11 and untouched.