v0.126.0: a fetch failure is not a wrong recovery code (R-224)
gates / gates (push) Successful in 7s
gates / gates (push) Successful in 7s
A hub the agent could not reach was reported to the customer as a bad recovery code. Measured live 2026-08-05 (CAMPAIGN-11 F3): hub firewalled off, a CORRECT current code, and the customer told it did not open their package — in 0.0556s against ~1.0s for a real unseal. No unseal was attempted. The discriminator existed here and this boundary threw it away: recover.go fails at four distinguishable points and the local-api handler had cases for two, with a default answering 'the recovery code did not open the sealed bundle, OR the bundle could not be fetched'. escrow.ErrBundleFetch now joins the fetch leg and the handler routes it to 502 with its own words — the code was NOT used. 502 not 4xx: the request was not bad, an upstream dependency failed. Four situations, four statuses: 502 fetch / 400 fetched-and-refused / 404 no bundle / 409 predates the field. The controller classifies on the STATUS and never parses the sentence. A GREEN TEST NAMED THIS DEFECT AND DID NOT PREVENT IT. TestRecoverOffsiteRepoPassword_FetchErrorIsDistinct has said since v0.125.0 that the operator must not be sent to re-read their code because the hub was unreachable — and passed throughout, because it asserted this package's error STRING one layer below the merge, and a string is not something a caller can branch on. Re-pointed at the sentinel, with a consequence-level twin asserting the status. Red-proofs: removing the %w join fails the sentinel test; deleting the handler case makes fetch and wrong-code both answer 400 with the wrong-code sentence. 29 packages ok, vet clean, agent gates OK.
This commit is contained in:
@@ -1,3 +1,40 @@
|
||||
## v0.126.0 — a fetch failure is not a wrong recovery code (2026-08-06, R-224)
|
||||
|
||||
**A hub the agent could not reach was reported to the customer as a bad recovery code.** Measured live
|
||||
on 2026-08-05 (CAMPAIGN-11 F3): with the hub REJECTed at the appliance's firewall and a **correct,
|
||||
current** recovery code, the customer was told their code did not open their package — **in 0.0556 s**,
|
||||
against ~1.0 s for a genuine unseal. No unseal was attempted. F4 produced the same message in 0.0299 s
|
||||
with this agent stopped.
|
||||
|
||||
**The discriminator existed here the whole time and this boundary threw it away.** `recover.go` fails
|
||||
at four distinguishable points; the local-api handler had cases for two of them and a `default` that
|
||||
answered *"the recovery code did not open the sealed bundle, or the bundle could not be fetched"* —
|
||||
one sentence for two situations, only one of which is the customer's doing.
|
||||
|
||||
**The fix is a value, not a log line.** `escrow.ErrBundleFetch` joins the fetch leg's error, and the
|
||||
handler routes it to **502** with its own words: *"the sealed recovery bundle could not be fetched from
|
||||
the hub — the recovery code was NOT used and nothing was written."* 502 rather than 4xx because the
|
||||
request was not bad; an upstream dependency failed. The `default` now carries **only** the fail-closed
|
||||
wrong-code case and says so without the "or".
|
||||
|
||||
Four situations, four statuses — **502** fetch failed · **400** the bundle was fetched and refused the
|
||||
code · **404** the hub holds no bundle · **409** the bundle predates the repository-password field.
|
||||
The controller classifies on the STATUS and must never parse these sentences.
|
||||
|
||||
⚠ **A GREEN TEST NAMED THIS DEFECT AND DID NOT PREVENT IT.**
|
||||
`TestRecoverOffsiteRepoPassword_FetchErrorIsDistinct` has said since v0.125.0 that *"the operator must
|
||||
not be sent to re-read their recovery code because the hub was unreachable"* — and it passed
|
||||
throughout, because it asserted this package's error **string** one layer below where the merge
|
||||
happened, and a string is something no caller can branch on. It now asserts the sentinel, and its
|
||||
consequence-level twin asserts the STATUS at the boundary the customer's message is derived from.
|
||||
**Prefer the test that asserts the consequence over the one that asserts the mechanism.**
|
||||
|
||||
Tests: `recover_test.go` (fetch classifies as `ErrBundleFetch`; a wrong code does **not**; an absent
|
||||
blob keeps its own identity) and `localapi/escrow_recover_class_test.go` (each situation's status, and
|
||||
a standalone assertion that fetch-failure and wrong-code never share one). **Red-proofs:** removing the
|
||||
`%w` join fails the sentinel test; deleting the handler case makes both answer `400` with the
|
||||
wrong-code sentence — the exact pre-fix code, and the exact defect CAMPAIGN-11 measured.
|
||||
|
||||
## v0.125.0 — the agent opens the sealed bundle and returns one field (2026-08-04, R-199 links 7–8)
|
||||
|
||||
**Link 7 had one production caller and it was a `--selftest`.** `UnwrapIdentityBundle` has existed
|
||||
|
||||
Reference in New Issue
Block a user