fix(AGENT-001): anti-retarget re-resolution for inline customer-confirmed wipe
handleDiskFormat's customer-confirmed branch formatted the mutable req.Device path; the durable id only bound the confirmation, never the mkfs target. A /dev reassignment between inspect and mkfs could wipe the wrong physical disk. Now mirrors signedjobs.WipeExecutor: resolve confirmed durable id -> current device, re-derive + require exact match, re-inspect (still data-bearing), then format THAT device. Any refusal -> 409, no mkfs. New antiRetargetResolve helper (injected deps, unit-tested: mismatch/gone/blank/empty all refuse). Injectable reresolveWipe seam on Server (defaults to real storage funcs). BRANCH ONLY — pending supervised review/deploy (see AGENT-001-FIX-NOTES.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
||||
)
|
||||
|
||||
// Test for [AGENT-001]: the inline customer-confirmed wipe must re-resolve the
|
||||
// confirmed durable id to the current device and refuse if anything changed in
|
||||
// the window, rather than formatting a mutable /dev path that may now point at a
|
||||
// different physical disk.
|
||||
func TestAntiRetargetResolve(t *testing.T) {
|
||||
const durable = "byid:wwn-0xCONFIRMED"
|
||||
const dev = "/dev/sdb1"
|
||||
|
||||
// probe helpers
|
||||
dataBearing := storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4"} // DataBearing() true
|
||||
blank := storage.DeviceProbe{Probed: true} // Probed, no signatures → not data-bearing
|
||||
okInspect := func(string) (storage.DeviceProbe, error) { return dataBearing, nil }
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
durable string
|
||||
resolve func(string) (string, error)
|
||||
derive func(string) (string, error)
|
||||
inspect func(string) (storage.DeviceProbe, error)
|
||||
wantDev string
|
||||
wantErr string // substring; "" = expect success
|
||||
}{
|
||||
{
|
||||
name: "happy-path-matches",
|
||||
durable: durable,
|
||||
resolve: func(string) (string, error) { return dev, nil },
|
||||
derive: func(string) (string, error) { return durable, nil },
|
||||
inspect: okInspect,
|
||||
wantDev: dev,
|
||||
},
|
||||
{
|
||||
name: "empty-durable-refused",
|
||||
durable: "",
|
||||
resolve: func(string) (string, error) { return dev, nil },
|
||||
derive: func(string) (string, error) { return durable, nil },
|
||||
inspect: okInspect,
|
||||
wantErr: "path-only",
|
||||
},
|
||||
{
|
||||
name: "durable-no-longer-resolves",
|
||||
durable: durable,
|
||||
resolve: func(string) (string, error) { return "", errors.New("gone") },
|
||||
derive: func(string) (string, error) { return durable, nil },
|
||||
inspect: okInspect,
|
||||
wantErr: "no longer resolves",
|
||||
},
|
||||
{
|
||||
// THE core AGENT-001 case: the /dev node now points at a DIFFERENT disk,
|
||||
// so re-deriving its durable id yields a different value → refuse.
|
||||
name: "retarget-mismatch-refused",
|
||||
durable: durable,
|
||||
resolve: func(string) (string, error) { return dev, nil },
|
||||
derive: func(string) (string, error) { return "byid:wwn-0xDIFFERENT", nil },
|
||||
inspect: okInspect,
|
||||
wantErr: "durable-id mismatch",
|
||||
},
|
||||
{
|
||||
name: "no-longer-data-bearing-refused",
|
||||
durable: durable,
|
||||
resolve: func(string) (string, error) { return dev, nil },
|
||||
derive: func(string) (string, error) { return durable, nil },
|
||||
inspect: func(string) (storage.DeviceProbe, error) { return blank, nil },
|
||||
wantErr: "no longer data-bearing",
|
||||
},
|
||||
{
|
||||
name: "reinspect-error-refused",
|
||||
durable: durable,
|
||||
resolve: func(string) (string, error) { return dev, nil },
|
||||
derive: func(string) (string, error) { return durable, nil },
|
||||
inspect: func(string) (storage.DeviceProbe, error) { return storage.DeviceProbe{}, errors.New("io") },
|
||||
wantErr: "re-inspect",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := antiRetargetResolve(tc.durable, tc.resolve, tc.derive, tc.inspect)
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != tc.wantDev {
|
||||
t.Fatalf("device = %q, want %q", got, tc.wantDev)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("expected refusal containing %q, got device %q and nil error", tc.wantErr, got)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Fatalf("error %q does not contain %q", err.Error(), tc.wantErr)
|
||||
}
|
||||
if got != "" {
|
||||
t.Fatalf("on refusal the device must be empty, got %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user