v0.129.0 — a correct code for an earlier package stops being called wrong (R-311)
gates / gates (push) Successful in 14s
gates / gates (push) Successful in 14s
Yesterday's drill proved a retained escrow package opens a set-aside store and restores planted files byte-identical, while this agent answered the customer's correct code with "the recovery code did not open the sealed bundle". Nothing had ever tried the retained packages, so a correct-but-earlier code and a mistype were genuinely indistinguishable. OffsiteKeyRecoverer gains an optional FetchRetained, consulted ONLY after the current package refuses, so the ordinary recovery pays nothing for it and cannot fail because of it. A match returns ErrCodeOpensRetained wrapped in a RetainedOpenedError carrying the supersession date - no material, no code, no password. The local API answers 422: a FIFTH status added to the R-224 switch, never a restructuring of it. Fail-safe in every direction. Nil fetcher, a hub too old for the route (404 is a clean "none"), a transport failure, a malformed package: each leaves the original refusal standing. Attempts bounded at 6 because each unwrap is ~1s of scrypt. Seven tests with REAL age crypto - the two situations are indistinguishable AT THE UNWRAP, so a faked unwrap would prove nothing. Red-proof asserted applied: remove the retained lookup and the fail-closed wrong-code error returns, which is the lie in those exact words.
This commit is contained in:
@@ -98,6 +98,35 @@ func (s *Server) handleRecoverOffsitePassword(w http.ResponseWriter, r *http.Req
|
||||
case errors.Is(err, escrow.ErrNoEscrowBlob):
|
||||
s.logger.Warn("local-api: offsite key recovery: the hub holds no sealed bundle for this host", "vmid", vmid)
|
||||
writeErr(w, http.StatusNotFound, "the hub holds no sealed recovery bundle for this host — no escrow ceremony has run")
|
||||
// ── R-311 (2026-08-12) — THE CODE IS RIGHT, JUST NOT FOR THE CURRENT PACKAGE. ─────────
|
||||
//
|
||||
// Placed ABOVE the default for the same reason ErrBundleFetch is: the default blames the
|
||||
// customer, and this case is the one where the customer is provably not at fault. The code was
|
||||
// used, it worked, and it opened a package the hub is deliberately keeping.
|
||||
//
|
||||
// 422 rather than 400: the request was well-formed AND the credential was valid — what could
|
||||
// not be processed is the pairing of a correct code with the CURRENT package. A 400 would put
|
||||
// it in the same bucket as a mistype, which is the whole defect. The status is the
|
||||
// machine-readable half; the controller classifies on it and must never parse this sentence.
|
||||
//
|
||||
// The date travels in the body because it is the one fact that lets a customer recognise which
|
||||
// code they are holding. No material, no code, no password — only when that package stopped
|
||||
// being current, and whether it can yield a repository password at all.
|
||||
case errors.Is(err, escrow.ErrCodeOpensRetained):
|
||||
var ro *escrow.RetainedOpenedError
|
||||
match := escrow.RetainedMatch{}
|
||||
if errors.As(err, &ro) {
|
||||
match = ro.Match
|
||||
}
|
||||
s.logger.Info("local-api: offsite key recovery: the code did NOT open the current package but DID open a RETAINED one — the customer is not at fault",
|
||||
"vmid", vmid, "superseded_at", match.SupersededAt, "retained_has_restic_pw", match.HasResticPassword)
|
||||
writeStatus(w, http.StatusUnprocessableEntity, false,
|
||||
map[string]any{
|
||||
"opens_retained": true,
|
||||
"superseded_at": match.SupersededAt,
|
||||
"retained_has_restic_pw": match.HasResticPassword,
|
||||
},
|
||||
"the recovery code is correct, but it belongs to an EARLIER sealed package (superseded "+match.SupersededAt+"), not the one currently held")
|
||||
case errors.Is(err, escrow.ErrNoResticPassword):
|
||||
s.logger.Warn("local-api: offsite key recovery: the bundle opened but predates the repository-password field", "vmid", vmid)
|
||||
writeErr(w, http.StatusConflict, "the recovery code opened the bundle, but it carries NO offsite repository password (sealed before that field existed; it cannot be retro-fitted)")
|
||||
|
||||
Reference in New Issue
Block a user