R-1182: a finished off-site abandonment closes on the hub's abandon_purge_done (hub v0.163.0 keeps other copies' packages); unreleased
gates / gates (push) Successful in 1m18s
gates / gates (push) Successful in 1m18s
This commit is contained in:
1 parent
01c967e49e
commit
8f5bea0148
4 files changed
+68
-2
No files matched your search
+8
-1
@@ -1,4 +1,11 @@
|
|||||||
## Unreleased (2026-10-20) — the monitoring page's storage labels follow the reader's language (W26); text only
|
## Unreleased (2026-10-20/21) — the monitoring page's storage labels follow the reader's language (W26); a finished off-site abandonment closes on the hub's word (R-1182)
|
||||||
|
|
||||||
|
- **R-1182 (2026-10-21, with hub v0.163.0):** the hub now purges only the recovery packages that open the set-aside copy
|
||||||
|
it deleted, and keeps the packages of other stored copies, so `superseded_present` can stay true after a finished
|
||||||
|
abandonment. The ACK field `escrow.abandon_purge_done` now reaches `report.EscrowStatus`; the confirmer's new hook
|
||||||
|
`AbandonPurgeDone` (wired in `main.go`) closes the box's declaration. An older hub sends no field → the old rule
|
||||||
|
holds. Tests `TestR1182_TheHubsPurgeDoneClosesTheDeclaration` (red-proved: the call removed → `hook calls = 0`) and
|
||||||
|
`TestR1182_MainWiresAbandonPurgeDone`.
|
||||||
|
|
||||||
Found by the Tester-2 rehearsal of 2026-10-20 (felhom.eu `audits/rehearsal-2026-10-20/B6-failures/s16-README.md`, word
|
Found by the Tester-2 rehearsal of 2026-10-20 (felhom.eu `audits/rehearsal-2026-10-20/B6-failures/s16-README.md`, word
|
||||||
W26): on the English dashboard the monitoring page showed every storage label and purpose in Hungarian. No release in
|
W26): on the English dashboard the monitoring page showed every storage label and purpose in Hungarian. No release in
|
||||||
|
|||||||
@@ -1137,7 +1137,14 @@ func main() {
|
|||||||
// on every ACK and discarded. Wired at the same point as the two above, deliberately —
|
// on every ACK and discarded. Wired at the same point as the two above, deliberately —
|
||||||
// a second wiring site in this file is how six features got built and never reached.
|
// a second wiring site in this file is how six features got built and never reached.
|
||||||
RecordEscrowKeyHash: sett.SetHubEscrowKeySHA256,
|
RecordEscrowKeyHash: sett.SetHubEscrowKeySHA256,
|
||||||
Logger: logger,
|
// R-1182 (hub v0.163.0): the hub finished this box's abandonment purge while it rightly keeps the
|
||||||
|
// packages of OTHER set-aside copies — close the declaration (superseded_present may stay true).
|
||||||
|
AbandonPurgeDone: func() {
|
||||||
|
if backupMgr != nil {
|
||||||
|
backupMgr.ClearAbandonPurgeIfConfirmed(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
// R-314/R-279/R-177 (`09` §3 decision 185): the operator's closed list of actions, carried in the
|
// R-314/R-279/R-177 (`09` §3 decision 185): the operator's closed list of actions, carried in the
|
||||||
// report reply and run in THIS process (opactions_wiring.go). The result rides the next report;
|
// report reply and run in THIS process (opactions_wiring.go). The result rides the next report;
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ type EscrowStatus struct {
|
|||||||
// package" and simply keeps today's message: an older hub cannot make the screen say anything new.
|
// package" and simply keeps today's message: an older hub cannot make the screen say anything new.
|
||||||
SupersededPresent bool `json:"superseded_present"`
|
SupersededPresent bool `json:"superseded_present"`
|
||||||
SupersededAt string `json:"superseded_at"`
|
SupersededAt string `json:"superseded_at"`
|
||||||
|
// AbandonPurgeDone (R-1182, hub v0.163.0) — the hub answers THIS box's abandonment declaration: the packages that
|
||||||
|
// open the deleted set-aside copy are gone. The hub now keeps the packages of OTHER stored copies, so
|
||||||
|
// SupersededPresent can stay true for good after a finished abandonment; this field is what closes the
|
||||||
|
// declaration then. Absent on an older hub → false → the old rule (SupersededPresent=false) still closes it.
|
||||||
|
AbandonPurgeDone bool `json:"abandon_purge_done"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EscrowAutoConfirmer runs the auto-confirm check on each report ACK. Long-lived (one per process) so
|
// EscrowAutoConfirmer runs the auto-confirm check on each report ACK. Long-lived (one per process) so
|
||||||
@@ -70,6 +75,10 @@ type EscrowAutoConfirmer struct {
|
|||||||
// immediately, so the one fact that distinguishes a REBUILT box from a box that never had
|
// immediately, so the one fact that distinguishes a REBUILT box from a box that never had
|
||||||
// off-site backups was thrown away on every cycle. nil → not recorded (older wiring, tests).
|
// off-site backups was thrown away on every cycle. nil → not recorded (older wiring, tests).
|
||||||
RecordPresence func(present bool) error
|
RecordPresence func(present bool) error
|
||||||
|
// AbandonPurgeDone is called when the ACK says the hub finished THIS box's abandonment purge (R-1182) — the box
|
||||||
|
// closes its declaration. Wired at the same point as RecordPresence, for the reason given there. nil → the
|
||||||
|
// declaration closes only on SupersededPresent=false (the pre-R-1182 rule).
|
||||||
|
AbandonPurgeDone func()
|
||||||
// RecordSuperseded persists the ACK's `superseded_present` / `superseded_at` (v0.201.0, R-222) —
|
// RecordSuperseded persists the ACK's `superseded_present` / `superseded_at` (v0.201.0, R-222) —
|
||||||
// whether the hub is ALSO keeping an EARLIER sealed package for this box. Wired here for the same
|
// whether the hub is ALSO keeping an EARLIER sealed package for this box. Wired here for the same
|
||||||
// reason as RecordPresence: this is the one place the ACK's escrow object already arrives, and a
|
// reason as RecordPresence: this is the one place the ACK's escrow object already arrives, and a
|
||||||
@@ -157,6 +166,10 @@ func (c *EscrowAutoConfirmer) Reconcile(es *EscrowStatus) {
|
|||||||
c.logf("[WARN] [escrow-confirm] could not record the hub's superseded-package state (present=%v): %v", es.SupersededPresent, err)
|
c.logf("[WARN] [escrow-confirm] could not record the hub's superseded-package state (present=%v): %v", es.SupersededPresent, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// R-1182: the hub confirmed this box's abandonment purge. Same place and discipline as the two recorders above.
|
||||||
|
if es.AbandonPurgeDone && c.AbandonPurgeDone != nil {
|
||||||
|
c.AbandonPurgeDone()
|
||||||
|
}
|
||||||
// R-241: persist the hash the hub's package covers, with the moment we heard it. Same discipline,
|
// R-241: persist the hash the hub's package covers, with the moment we heard it. Same discipline,
|
||||||
// same place, same reason as the two above — and this one is the fact the recovery screen has been
|
// same place, same reason as the two above — and this one is the fact the recovery screen has been
|
||||||
// unable to see. A record failure is logged, never swallowed, and never blocks the auto-confirm.
|
// unable to see. A record failure is logged, never swallowed, and never blocks the auto-confirm.
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// R-1182 (hub v0.163.0): the hub now keeps the packages of OTHER set-aside copies after an abandonment, so
|
||||||
|
// superseded_present can stay true for good; `abandon_purge_done` is what closes this box's declaration. The hook
|
||||||
|
// runs on an unconfigured box too (the declaring box has deleted its set-aside copy; its target may be anything).
|
||||||
|
// COMPANION RED-PROOF: drop the AbandonPurgeDone call in Reconcile → the first assertion fails.
|
||||||
|
func TestR1182_TheHubsPurgeDoneClosesTheDeclaration(t *testing.T) {
|
||||||
|
calls := 0
|
||||||
|
c := &EscrowAutoConfirmer{
|
||||||
|
Pending: func() bool { return false },
|
||||||
|
Escrowed: func() bool { return false },
|
||||||
|
AbandonPurgeDone: func() { calls++ },
|
||||||
|
Logger: log.New(io.Discard, "", 0),
|
||||||
|
}
|
||||||
|
c.Reconcile(&EscrowStatus{IdentityBlobPresent: true, SupersededPresent: true, AbandonPurgeDone: true})
|
||||||
|
if calls != 1 {
|
||||||
|
t.Fatalf("purge-done hook calls = %d, want 1 — the box would declare its abandonment for ever", calls)
|
||||||
|
}
|
||||||
|
c.Reconcile(&EscrowStatus{IdentityBlobPresent: true, SupersededPresent: true})
|
||||||
|
if calls != 1 {
|
||||||
|
t.Fatalf("hook ran on an ACK without abandon_purge_done (calls = %d)", calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestR1182_MainWiresAbandonPurgeDone(t *testing.T) {
|
||||||
|
found, saw := confirmerFieldIsWired(t, "AbandonPurgeDone")
|
||||||
|
if !saw {
|
||||||
|
t.Fatal("no EscrowAutoConfirmer literal found in main.go")
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("EscrowAutoConfirmer is built WITHOUT AbandonPurgeDone — a finished abandonment never closes (R-1182)")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user