Files
felhom-controller/REPORT.md
T

76 lines
4.6 KiB
Markdown

# 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).