5.6 KiB
REPORT — controller v0.214.0: the screen stops hedging about a code it can now check (R-311, 2026-08-12)
The premise, corrected before anything was written
The task described the customer being told "the recovery code did not open the sealed bundle". That is the agent's local-API reply — machine-facing English. The customer-facing screen already hedged (R-222/R-226): it named both possible causes, named the kept package and its date, and said „innen nem tudjuk megkülönböztetni őket" — we cannot tell them apart from here.
That sentence was honest. It was also a statement about our own incuriosity: it could not tell them apart because nothing ever looked. Agent v0.129.0 looks, so the hedge becomes an answer. That is what shipped — a smaller and more precise change than "stop the lie", and stated as such.
Verified at file:line against live source, per rule 1, including the claims in the prompt itself.
What changed
internal/agentapi/features.go—FeatureRetainedRecoveryClass, MinAgent 0.129.0, with thefeatureProbesrow the version path requires (afeatureMinAgentrow without one is never consulted).internal/agentapi/escrow.go— classRecoveryCodeOpensRetainedon 422.ClassifyRecoveryFailurenow takes(err, trustRefusal, trustRetained). Two gates, not one: they name different agent versions (0.126.0, 0.129.0) and a box can sit between them, where a 422 is a shape we did not design. The compiler found every call site.internal/web/recovery_handlers.go— the new case and its message;recoveryRetainedTrusted.internal/web/server.go— the test seam field.scripts/retrieval_promise_gate.py— see below.
The message, and what it deliberately will not say
„A kódod helyes, de egy korábbi csomagot nyit meg, nem azt, amit most őrzünk ehhez a géphez. A géped időközben új mentési kulcsot kapott. A korábbi csomagot (dátum) nem töröltük, megőrizzük — a mostani mentéseidet ez nem érinti, azokkal semmi nem történt. A régebbi előzményed visszanyitásához a Felhom ügyfélszolgálatának segítsége kell: írj nekik, és add meg, hogy a régi mentéseidhez szeretnél hozzáférni. A kódodat tedd el, szükség lesz rá."
Bytes verified as hex: á c3 a1 · é c3 a9 · í c3 ad · ó c3 b3 · ö c3 b6 · ú c3 ba · ü c3 bc · ő c5 91 · — e2 80 94. No emoji. 443 chars / 487 bytes.
It states the code is correct, names the date, says the package is kept, and denies the assumption it otherwise creates — that the current backups are affected. It promises no restore: there is no in-product route to a set-aside store (R-312), and a retained package may itself predate the repository-password field. A conditional promise that turns out false on this screen is worse than saying less — R-202, on the highest-stakes copy in the product. It routes to support, which can do it: yesterday's drill did exactly that by hand.
An older agent keeps the hedged sentence. Unknown → claims less → heals itself on update.
The claim guard grew a surface, and immediately convicted something
retrieval_promise_gate.py scanned internal/web/templates only — while every recovery message
is a Go string in a handler. The highest-stakes copy in the product had never been scanned. It now
also scans recovery_handlers.go (Go comments stripped, as template comments are), and visszanyit
joins the stems because the new message uses a fourth verb for the same claim — the gate's own history
is what happens when it chases words instead of claims.
On its first run it found a PRE-EXISTING unregistered claim (RecoverRefused's "reopening would
overwrite it"), now registered as an explanation rather than a promise. 8 → 10 registered claims.
Tests and red-proofs
Six handler tests asserting which sentence the customer sees. Full suite green; all 11 controller gates OK. Red-proofs, each mutation asserted to have applied:
| Mutation | Result |
|---|---|
delete the RecoveryCodeOpensRetained case |
FAILS — customer gets the neutral message (R-224's safe default catches it; the lie itself lives in the agent) |
| make 422 unconditional (ignore the gate) | FAILS — an agent that never looked is read as having looked |
| route 400 to the new class | FAILS — a mistype is congratulated |
Deployed
gitea.dooplex.hu/admin/felhom-controller:0.214.0 built, pushed, deployed to guest 9201 —
Up … (healthy).
Live, on hardware: the agent returns HTTP 422 for the old code (opens_retained: true,
superseded_at: 2026-08-12T15:18:55Z) and 400 for a wrong one. The hub serves the retained
packages (200, count=2, unopenable_count=1).
What was NOT walked: the customer's rendered sentence. recoveryUnlockHandler redirects to
/backups/remote when !recoveryOffer(), and this box holds its own repository password again, so it
is correctly not in the offered state. Forcing it would mean removing that password to fake a rebuilt
box — destabilising a healthy machine to render a sentence pinned by six handler tests whose upstream
422 is proven live. Method: endpoint-level for the agent/hub, handler-level for the message.
Correction — R-308 is WITHDRAWN. I previously reported the stored dashboard password as stale. It
is not: the ~/.config/credentials values are wrapped in SINGLE quotes and I stripped only double
ones, so I was sending a literal '. Unquoted correctly it logs in first try (HTTP 302 + session
cookie). The same bug made this session's first live R-311 test read as a failure for twenty minutes.