v0.32.0: self-serve decommission endpoint + intent-aware re-assert (B2a)

POST /disks/decommission mirrors eject (withGuest, user-data role gate) — no
operator signature, non-destructive (never formats): sets IntentDecommissioned,
prunes the GuestBindStore entry, unmounts. ReassertGuestBinds is now intent-aware
(skip non-enrolled) so a decommissioned-but-present drive never auto-rebinds on
agent restart — the load-bearing F9-reconnect fix. GuestBindStore.Remove added.
Operator-signed DecommissionExecutor + classify untouched. Non-hollow tests incl.
the intent-aware reassert companion (mutation-proven to fail on intent-blind code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 19:55:17 +02:00
parent 8e6d00a57f
commit f43697c881
6 changed files with 397 additions and 49 deletions
+31
View File
@@ -71,6 +71,37 @@ func (s *GuestBindStore) Record(vmid int, durableID string) error {
return s.saveLocked()
}
// Remove drops (vmid, durableID) from the enrolled set. Idempotent — absent (vmid or id) is a no-op
// returning nil. Atomic write (tmp+rename) like Record/saveLocked. Called by the self-serve
// decommission endpoint so a permanently-removed drive no longer lingers in the startup re-assert
// record (hygiene — the intent-aware ReassertGuestBinds is the load-bearing guard).
func (s *GuestBindStore) Remove(vmid int, durableID string) error {
s.mu.Lock()
defer s.mu.Unlock()
ids, ok := s.m[vmid]
if !ok {
return nil
}
kept := ids[:0:0]
found := false
for _, id := range ids {
if id == durableID {
found = true
continue
}
kept = append(kept, id)
}
if !found {
return nil // idempotent: nothing to remove
}
if len(kept) == 0 {
delete(s.m, vmid)
} else {
s.m[vmid] = kept
}
return s.saveLocked()
}
// Guests returns a copy of the vmid → enrolled-durable-ids map.
func (s *GuestBindStore) Guests() map[int][]string {
s.mu.Lock()