# 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 the `featureProbes` row the version path requires (a `featureMinAgent` row without one is never consulted). - `internal/agentapi/escrow.go` — class `RecoveryCodeOpensRetained` on **422**. `ClassifyRecoveryFailure` now 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 validation of the customer screen was **endpoint/handler-level, not a click-through**: the dashboard password of record no longer opens this box (R-308).