CHANGELOG: controller v0.202.0 (MinAgent 0.126.0)
gates / gates (push) Successful in 10s

Declares the coupling: an agent below 0.126.0 answers 400 for both a fetch
failure and a wrong code, so FeatureRecoveryFailureClass withholds the refusal
reading and the 400 degrades to the neutral message. The gate blocks nothing —
it only decides whether the customer may be told to check their typing.
This commit is contained in:
2026-08-06 08:19:07 +02:00
parent c7446f2d6a
commit 7534ea203d
+93
View File
@@ -1,3 +1,96 @@
## v0.202.0 — the customer is blamed only after a real attempt refused their code (2026-08-06, R-224/R-226/R-225/R-227/R-228) — MinAgent 0.126.0
**CAMPAIGN-11's headline defect had moved, not gone.** v0.201.0 stopped an agent that is too OLD from
being reported as a wrong recovery code. An agent that is **stopped**, and a hub that cannot be
**reached**, still fell through to a message about the code — measured live on 2026-08-05 with a
**correct, current** code at **0.0299 s** and **0.0556 s**, against ~1.0 s for a genuine unseal. The
machine had not tried, and told the customer their code was wrong.
**And the inverse was true at the same time (R-226).** The one message that says *"check your ten
words"* was tested AFTER the retained-earlier-package message, so on any box that has re-escrowed —
precisely the box whose customer has just been handed a new code — a genuine mistype could never
reach it. Three unrelated failures got the accusation; the one that deserved it got something else.
**One defect from both ends: nothing on that path asked WHY it failed.** `rerr` was never inspected.
### The rule, and it is the whole change
> **The customer is blamed only after a real attempt refused their code. Every other outcome —
> including one we cannot classify — says something else.**
Five classes, **from the value and never the text**:
| class | from | what the customer is told |
|---|---|---|
| hub-unreachable | 502/503 | the connection failed; **the code was NOT used** |
| agent-unreachable | no agent verdict at all | the machine's own service is not answering; **NOT used** |
| no-bundle | 404 | nothing is held for this machine; not about the code |
| bundle-too-old | 409 | the code WORKED; the package predates the field |
| **asked-and-refused** | **400** | **the only class that may mention typing** |
| unknown | anything else | **neutral — claims neither that the code was wrong nor that it went unused** |
`agentapi.RecoveryRefusal` carries the status as a value; `refusalError` flattened it into a sentence,
and a sentence is not something a caller can branch on.
**THE SAFE DEFAULT IS THE POINT.** `RecoveryUnknown` is the zero value, and an unrecognised status
lands there rather than in an accusation. **That is the rule whose absence let this survive being
fixed once.**
**Coupling — `MinAgent 0.126.0`.** An agent below it answers **400 for both** a fetch failure and a
wrong code, so a 400 from one cannot be read as a refusal. `FeatureRecoveryFailureClass` withholds
that reading and the 400 degrades to **neutral**. The gate **blocks nothing** — the unlock is
attempted either way — it only decides whether the customer may be told to check their typing, and
"not sure" means they may not. It heals itself when the agent updates.
**Elapsed time is logged** (it is what diagnosed this, and it is the cheapest tell for the operator)
**and is never the classifier.** Time is a symptom; the status is the fact.
### R-225 — unknown is not zero
An unread store rendered `Tároló méret · 0 pillanatkép` and `Tárhelykeret: 0 / 50 GB (0%)` **directly
above a card saying the store held backups under another key**. An SFTP listing found snapshot
`f3d9cd67` and **12 535 KB** really there; `snapshot_count` and `repo_size_bytes` were simply ABSENT
from `settings.json` and the zero value spoke for them. `StatsKnown` is **named**, for the same reason
`OffsiteInventory.Empty` is — zero is what an unread store and an empty one both look like, and both
fields are `omitempty` ints, so on disk "absent" and "0" are the same bytes. The fill bar renders only
when the fill is known: **a 0 %-wide bar is a picture of emptiness, and a picture is a claim.** A
*measured* zero still says zero.
### R-227 — the gateway error
**Which layer answers: traefik**, whose config this repo generates. A branded proxy page is therefore
possible here, but traefik v3 serves no static files, so it would need a **new always-up container**
for every 502 on the box — out of proportion to this finding, and **scoped in the report rather than
built**. Shipped instead: the unlock posts via `fetch`, so a gateway failure is answered in Hungarian
without leaving the page. **Progressive enhancement** — with no JS the plain POST is unchanged and
still shows the proxy's error, and this entry says so rather than implying otherwise.
### R-228 — the set-aside history is visible, and honest
A customer who chose "I do not want the old data" was told the backups would be **kept**, and the
move-aside did exactly that — 12 535 KB, byte-exact. The box recorded the path in
`orphaned_renamed_to` and **a census found zero references to it in any template or handler.** It is
surfaced now as two facts and stops.
**It does NOT promise the history can be reopened, and the confirmation copy was corrected for the
same reason.** *"a helyreállítási kód nélkül többé nem lesznek megnyithatók"* implied that **with**
the code they could be; serving a superseded package is an unbuilt link (R-199's inventory), so it
cannot be opened by the customer, the operator, or anyone. The field's own comment called it
*"recovery-code-recoverable"* — the same over-promise, in the code.
### Tests
Scenarios A-H at the **handler** and as **render tests per branch of each gate**. Red-proofs, each
demonstrated failing and restored: delete the 502 case (A) - remove the mistype clause (C) - default
to the accusation (D) - route an instant transport failure to the typing message (E) - remove the
`StatsKnown` guards (F) - delete the set-aside block (H).
**Two existing tests encoded the defect and were corrected rather than deleted.** The web fake
returned a **bare** error for "wrong code" — which is the shape of a failure we cannot classify, and
now correctly renders the neutral message; saying "wrong code" in a test requires saying it the way
the agent says it. And R-222's test forbade **any** mention of typing on a superseded box, **half of
which R-226 deliberately reverses**: what stays forbidden is the bare accusation, not the hint.
## v0.201.0 — a correct recovery code is never called wrong again (2026-08-05, CAMPAIGN-11) — MinAgent 0.125.0 ## v0.201.0 — a correct recovery code is never called wrong again (2026-08-05, CAMPAIGN-11) — MinAgent 0.125.0
CAMPAIGN-11 walked the whole recovery journey end to end for the first time. **The data came back CAMPAIGN-11 walked the whole recovery journey end to end for the first time. **The data came back