R-302: the abandon banner promises only what the box can still see is true
gates / gates (push) Successful in 10s

The retrieval clause rendered unconditionally on every page and is false on a
reachable state - the same screen where the orphan card says we cannot tell.

The condition is a fingerprint PINNED at the decision, not a comparison against
the current key. The obvious proxy asks about the wrong key: the set-aside
copies were written under an older key the box no longer has, so on a
twice-rebuilt box the proxy promises about copies nothing can open. Demonstrated
- under the proxy, the replaced-package and legacy cases both flip back to
promising.

The pin is a recorded assumption and says so: nothing on the box records which
key wrote those copies. Empty is not a match. A countdown started before this
carries no pin and takes the cautious branch, not a backfill.

A sweep of all 36 templates found a fourth instance (backups page, same
condition applied) and a fifth (the confirmation screen, correctly left alone -
true at the moment of the decision).

New retrieval_promise_gate registers each claim with a reason rather than
banning a verb: a string ban failed twice, and the honest replacement copy
contains the stem.
This commit is contained in:
2026-08-12 15:27:29 +02:00
parent 1b66010298
commit 89712563a0
11 changed files with 554 additions and 3 deletions
+21
View File
@@ -377,6 +377,27 @@ type OffboxTarget struct {
// hub's ACK stops reporting a superseded package. If the two halves could not be removed together
// this marker is what makes the box keep asking until they are (Scenario F).
AbandonPurgeRequested bool `json:"abandon_purge_requested,omitempty"`
// AbandonPinnedEscrowKeySHA256 (R-302) — the hub's escrow key fingerprint AS CACHED AT THE MOMENT
// THE CUSTOMER DECIDED. It is NOT the current key and NOT re-read: the banner's retrieval promise
// is rendered only while the hub is still holding that same package.
//
// ⚠ IT IS A RECORDED ASSUMPTION, NOT A PROOF, AND THAT IS DELIBERATE. Nothing on the box records
// which key wrote the set-aside copies — that key is gone, which is why they were set aside. What
// is pinned is the package the hub held at the decision, which in the ordinary rebuilt-box story IS
// the pre-rebuild escrow covering the pre-rebuild repo password, i.e. the one that wrote them. On a
// TWICE-rebuilt box that presumption can be wrong: the hub may hold rebuild #2's package while the
// set-aside copies are rebuild #1's, and no key on file opens them. This pin cannot detect that.
//
// What it DOES detect, and what the rejected alternative could not: the package being REPLACED
// after the decision — a fresh escrow ceremony, which is exactly the act that cost both demo boxes
// their history on 2026-08-04. The rejected proxy (hub fingerprint vs the CURRENT key, compared at
// render) answers "does the hub hold a different key?", which is TRUE in the co-render case and so
// would promise precisely where the promise is least safe.
//
// EMPTY IS MEANINGFUL AND IS NOT A MATCH: the hub sends "" for a legacy package that provably seals
// no repository password, and a countdown started before R-302 has no pin at all. Both take the
// cautious branch. NEVER rendered, logged or reported — it is the hash of a secret.
AbandonPinnedEscrowKeySHA256 string `json:"abandon_pinned_escrow_key_sha256,omitempty"`
}
// CrossDriveBackup configures per-app backup to a secondary drive.