R-414: the fallback scratch must also be DELETABLE - caught by live validation
gates / gates (push) Successful in 12s
gates / gates (push) Successful in 12s
The system-data fallback resolved a scratch fine and removeProofScratch then refused to delete it: its accepted-roots list is built from REGISTERED drives, and a driveless box has none. Observed on demo-felhom: 'refusing to remove ... it is not inside a proof root', with the copy still on disk. Every nightly proof would have left one behind, growing forever, on exactly the boxes the fallback exists for. My defect, introduced with the fallback in the same session. The unit tests missed it because every one of them registers a drive; the new pair deliberately does not, and the second asserts the guard still REFUSES a path outside every proof root, so the fix is not a widening into uselessness.
This commit is contained in:
@@ -3,6 +3,7 @@ package backup
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -197,3 +198,46 @@ func TestR414_NoCustomerAlarm(t *testing.T) {
|
||||
t.Fatalf("a driveless box must raise no event from the backup layer; got %d", pushed)
|
||||
}
|
||||
}
|
||||
|
||||
// TestR414_ProofScratchIsDeletedOnADrivelessBox — the leak live validation caught.
|
||||
//
|
||||
// The fallback resolved a scratch on the system data path, and `removeProofScratch` then refused to
|
||||
// delete it because its accepted-roots list is built from REGISTERED drives, which a driveless box has
|
||||
// none of. Observed on demo-felhom 2026-09-01: *"refusing to remove … it is not inside a proof root"*,
|
||||
// with the copy still on disk. Every nightly proof would have left one behind.
|
||||
//
|
||||
// The unit tests did not catch it because they all register a drive. This one deliberately does not.
|
||||
func TestR414_ProofScratchIsDeletedOnADrivelessBox(t *testing.T) {
|
||||
m, _, _ := drivelessHarness(t, "opengist")
|
||||
|
||||
scratch, _, err := m.offboxProofScratchDir("opengist")
|
||||
if err != nil {
|
||||
t.Fatalf("the unit-only scratch must resolve: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(scratch, "marker"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(scratch); err != nil {
|
||||
t.Fatalf("fixture: the scratch must exist before the removal is attempted: %v", err)
|
||||
}
|
||||
|
||||
m.removeProofScratch("opengist", scratch)
|
||||
|
||||
if _, err := os.Stat(scratch); !os.IsNotExist(err) {
|
||||
t.Fatalf("the proof copy must be removed on a driveless box too; %s still exists (stat err=%v)", scratch, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestR414_RemovalStillRefusesOutsideAProofRoot — the guard must not be widened into uselessness.
|
||||
func TestR414_RemovalStillRefusesOutsideAProofRoot(t *testing.T) {
|
||||
m, _, _ := drivelessHarness(t, "opengist")
|
||||
outside := t.TempDir()
|
||||
keep := filepath.Join(outside, "not-a-proof-root")
|
||||
if err := os.MkdirAll(keep, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.removeProofScratch("opengist", keep)
|
||||
if _, err := os.Stat(keep); err != nil {
|
||||
t.Fatalf("a path outside every proof root must be REFUSED, not deleted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user