v0.288.0: the remove dialog and result name the household's userdata folders, which stay (decision 67, R-800)
gates / gates (push) Successful in 28s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-02 14:03:44 +02:00
parent 9821690980
commit 580b656914
116 changed files with 2099 additions and 5 deletions
@@ -416,3 +416,57 @@ func TestGetStackHDDData_R442_ReadsPerAppRecord(t *testing.T) {
t.Fatalf("hdd-data = %+v, want one existing entry for %s", resp, data)
}
}
// R-800 / `09` §3 decision 67: an app whose only drive folder is the household's userdata (MeTube's downloads) is removed
// WITH data — the folder stays, the result NAMES it, and the note does not claim "no data on a drive"; the dialog's
// source lists it too. Measured shape: demo-hp 2026-10-02 (audits/family-gate-2026-10-02/B4/metube-demo.txt).
func TestRemoveStack_R800_UserdataKeptIsNamed(t *testing.T) {
drive := t.TempDir()
compose := "services:\n" +
" app:\n" +
" image: nginx:1.27\n" +
" volumes:\n" +
" - ${USERDATA_PATH}/media/app:/downloads\n" +
" - app_state:/state\n" +
"volumes:\n" +
" app_state:\n"
m, _, _ := newR442Manager(t, "app", compose, driveAppYAML(drive), drive)
video := filepath.Join(drive, "userdata", "media", "app", "clip.mp4")
plantFile(t, video)
kept := filepath.Join(drive, "userdata", "media", "app")
hd, err := m.GetStackHDDData("app")
if err != nil {
t.Fatal(err)
}
if len(hd.UserdataKept) != 1 || hd.UserdataKept[0].Path != kept {
t.Fatalf("hdd-data userdata_kept = %+v, want [%s] — the dialog must be able to name the household's folder", hd.UserdataKept, kept)
}
resp, err := m.RemoveStack("app", true, nil)
if err != nil {
t.Fatalf("RemoveStack: %v", err)
}
if !exists(video) {
t.Fatal("the household's userdata file was deleted by a remove — decision 67 says it stays")
}
if len(resp.UserdataKept) != 1 || !strings.HasPrefix(resp.UserdataKept[0], kept+" (") {
t.Fatalf("userdata_kept = %v, want exactly [%q (size)]", resp.UserdataKept, kept)
}
if resp.HDDNote == noteNoDriveData {
t.Fatalf("hdd_note = %q — false: the app's files are on the drive and stay", resp.HDDNote)
}
}
// An SSD app: userdata_kept serialises as [] (never null), the "no drive data" note is unchanged.
func TestRemoveStack_R800_SSDAppUserdataKeptEmpty(t *testing.T) {
m, _, _ := newR442Manager(t, "app", ssdCompose, "deployed: true\nenv: {}\n", "")
resp, err := m.RemoveStack("app", true, nil)
if err != nil {
t.Fatal(err)
}
raw, _ := json.Marshal(resp)
if !strings.Contains(string(raw), `"userdata_kept":[]`) || resp.HDDNote != noteNoDriveData {
t.Fatalf("got %s", raw)
}
}