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
+38
View File
@@ -70,6 +70,22 @@ type Settings struct {
// first ACK — which is correct, because the hub is the authority on what the hub holds.
HubEscrowIdentityPresent bool `json:"hub_escrow_identity_present,omitempty"`
// HubEscrowSupersededPresent / HubEscrowSupersededAt (v0.201.0, R-222) cache the report ACK's
// `escrow.superseded_present` / `superseded_at` — whether the hub is ALSO keeping an EARLIER
// sealed package for this box, and when it was set aside.
//
// They exist for one message and nothing else. Without them the recovery screen cannot tell a
// genuinely wrong recovery code from a code that is RIGHT about an earlier package, because both
// look identical from here: the unseal fails closed against the package the hub currently serves.
// Measured on 2026-08-05 (CAMPAIGN-11 Phase 3 step 7) — the customer entered the correct code for
// their orphaned history and was told to check their typing.
//
// ⚠ These grant NO read path. The hub serves the CURRENT package only; retrieving a superseded one
// is a link that has never been built (R-199's inventory). The screen may say an earlier package is
// kept and when — both true, both non-secret — and must not promise it can be opened.
HubEscrowSupersededPresent bool `json:"hub_escrow_superseded_present,omitempty"`
HubEscrowSupersededAt string `json:"hub_escrow_superseded_at,omitempty"`
// RecoveryNoticePostponed (v0.200.0, R-193) — the customer chose "most nem" on the full-page
// recovery screen. It suppresses THE FULL-PAGE INTERRUPTION ONLY. The entry point in the backups
// area stays, permanently, for as long as the situation lasts: the data is still there whether or
@@ -647,6 +663,28 @@ func (s *Settings) SetHubEscrowIdentityPresent(present bool) error {
return s.save()
}
// GetHubEscrowSuperseded reports whether the hub is keeping an EARLIER sealed package for this box,
// and when it was set aside (R-222). Both zero until an ACK has said so.
func (s *Settings) GetHubEscrowSuperseded() (present bool, at string) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.HubEscrowSupersededPresent, s.HubEscrowSupersededAt
}
// SetHubEscrowSuperseded records the ACK's `escrow.superseded_present` / `superseded_at`. Same
// last-write-wins mirror discipline as SetHubEscrowIdentityPresent — the hub is the authority, and a
// customer RESET that clears the retained rows must be able to turn the box's message back off.
// Saves only on a change.
func (s *Settings) SetHubEscrowSuperseded(present bool, at string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.HubEscrowSupersededPresent == present && s.HubEscrowSupersededAt == at {
return nil
}
s.HubEscrowSupersededPresent, s.HubEscrowSupersededAt = present, at
return s.save()
}
// ── Recovery screen (v0.200.0, R-193) ──────────────────────────────────────────
// GetRecoveryNoticePostponed reports whether the customer chose "most nem" on the recovery page.