v0.201.0 — a correct recovery code is never called wrong again (CAMPAIGN-11) — MinAgent 0.125.0
gates / gates (push) Successful in 9s

R-216: the offsite key recovery is a coupled feature and now says so. featureProbes +
featureMinAgent 0.125.0 + a Supports gate at the unlock entry point, FAILING CLOSED — an
agent that cannot answer is named as such instead of the customer's code being blamed.
Measured live: a 404 from agent 0.120.0 came back as "we did not accept your recovery
code, check that all ten words", in 0.134 s, against a perfect code.

R-218: delete the repo-password short-circuit in needsOffsiteCredential. The declaration
stops when the TIER WORKS, not when a key exists — installing a key is the recovery
screen's whole job, so succeeding at recovery was switching off the mechanism that would
have delivered the coordinates to use it.

R-219: the unlock finishes the job — place the key, bring the tier up, then list. Without
it the promised listing could never render on the shape the screen exists for.

R-217: an unreadable store no longer claims to have opened with unattributable content
(the OffsiteInventory{} zero value). Opened / empty / unreadable are three states.

R-222: a code that is right about a RETAINED earlier package is named, not blamed. States
what the hub knows and promises nothing — no read path exists.

R-215: GET /recovery is gated on the same predicate as the interception.

Five red-proofs, each demonstrated failing and restored.
This commit is contained in:
2026-08-05 17:48:08 +02:00
parent a315d623b8
commit a3499d1807
12 changed files with 778 additions and 20 deletions
+60
View File
@@ -1,3 +1,63 @@
## 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
byte-identical; the journey did not exist** — four operator interventions stood between a customer and
their files, and the first one was the machine telling them their perfectly correct recovery code was
wrong. This closes six of the nine findings.
**R-216 — the headline. A 404 was reported to the customer as a bad code.** The unseal lives in the
agent behind `POST /escrow/recover-offsite-password`, which ships in agent **v0.125.0**. On an older
agent the route 404s, and the unlock was attempted anyway: *„A megadott helyreállítási kódot nem
fogadtuk el. Ellenőrizd, hogy mind a tíz szót pontosan…"* — measured live in 0.134 s, far too fast for
`age`'s scrypt, against a code that was perfect. **It was the default state, not a misconfiguration**:
the day-0 manifest vouches agent 0.120.0, and a reinstall actively *downgrades* a hand-fixed box back
to it. Three parts:
- `FeatureOffsiteKeyRecovery` now exists in `featureProbes` + `featureMinAgent` (0.125.0) with a
`Supports` gate at the unlock entry point — **the convention `features.go:72-76` and
`publish-train-rules.md` rule 4 already required, which v0.195.0/v0.196.0 declared in their headers
and v0.200.0 dropped when it put the same coupled feature behind a customer-facing button.**
- **This gate FAILS CLOSED**, alone in that table: anything but `SupportYes` says *the machine* cannot
ask yet, and no unlock is attempted. An attempt that cannot succeed must never be made, because its
failure is what got attributed to the code. The asymmetry is documented beside the row.
- The hub's own guard could not catch it (see the hub changelog, v0.97.0).
**R-218 — succeeding at recovery stopped the box asking for what it still needed.**
`needsOffsiteCredential` short-circuited on a repository password existing — and installing one is the
recovery screen's whole job. 32 seconds after the hub re-staged the credential, the customer's success
switched off the mechanism that would have delivered the coordinates for the key they had just
recovered. **The short-circuit is deleted**; the declaration now stops when the tier works, not when a
key exists. Scenario E (a deliberately disabled target stays silent) is pinned by its own test.
**R-219 — the promised listing could never render on the shape the screen exists for.** Listing needs
a target; a target cannot exist without a repository password; shape (a) is defined by having none. The
unlock now **finishes the job** — place the key, bring the tier up, then list — and a tier that is not
up yet says *waiting for the connection details* rather than reporting a failure.
**R-217 — an unreadable store claimed to have opened.** The failure path passed
`backup.OffsiteInventory{}`, whose `Empty=false` the template read as *„A tároló megnyílt, és van benne
tartalom, de nem tudtuk alkalmazásokhoz rendelni."* — three assertions, none of them known. The field
built to prevent exactly this says so in its own doc comment. Opened / empty / unreadable are now three
distinguishable states.
**R-222 — reaching for a retained earlier package read as a wrong code.** The hub's ACK carries
`superseded_present` / `superseded_at` (hub v0.97.0); the screen now names that situation. It states
the two facts the hub knows and **does not promise the earlier package can be opened** — that read path
does not exist.
**R-215 — `GET /recovery` rendered the recovery story on a box that never had backups.** The predicate
was right and the page never asked it. Gated on the same predicate as the interception.
**Four messages where there was one**, each saying what to do next and only one mentioning typing.
Left open deliberately: **R-214** (the console never stops showing a stale pairing code), **R-220**
(drives cannot be re-enrolled after a rebuild), **R-221** (a rebuilt box cannot run the escrow ceremony
— a real blocker for that flow).
Tests: `recovery_gate_test.go` (Scenarios A/F/G/H/I, handler-level), `offbox_declare_test.go`
(Scenarios D/E). **Five red-proofs, each demonstrated failing and restored** — removing the capability
gate brings the accusation back verbatim.
## v0.200.0 — the recovery screen: unlocking, and only unlocking (2026-08-05, R-193)
R-193's remaining half. The drill proved a customer's file comes back after a machine is destroyed;