From 867a3f81a9cd15984633c815b76f2d978fa2a75b Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 15 Oct 2026 14:59:06 +0200 Subject: [PATCH] Hold a record-policy move that changes a non-file/directory resource (issue 785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR 0377 §3's allowlist names only tools bundles, files no container mounts and holds no data, and manifest fields that recreate nothing. Holds only checked containers, files, directories and processes/services; any other resource type (archive, package, user, network, ...) fell through unheld. Add a default case that holds and names the resource type and id. --- internal/catalogue/holds_test.go | 6 +++++- internal/catalogue/recreates.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/catalogue/holds_test.go b/internal/catalogue/holds_test.go index 8c66ed50..2a39fb71 100644 --- a/internal/catalogue/holds_test.go +++ b/internal/catalogue/holds_test.go @@ -23,7 +23,9 @@ func TestWhatAMoveUnderRecordHoldsFor(t *testing.T) { {"id":"inbox-note","type":"file","path":"${dir:data-mail}/README","content":"x"}, {"id":"imap","type":"container","image":"registry/imap@sha256:1","volumes":["${dir:data-mail}:/mail"], "env-file":["${dir:state}/mailu.env"]}, - {"id":"watcher","type":"process","command":"watch"}]}` + {"id":"watcher","type":"process","command":"watch"}, + {"id":"installation","type":"archive","path":"/opt/mailu-tools","artifact":"archive"}, + {"id":"guard","type":"package","package":"nftables"}]}` var m Manifest if err := json.Unmarshal([]byte(raw), &m); err != nil { t.Fatal(err) @@ -62,6 +64,8 @@ func TestWhatAMoveUnderRecordHoldsFor(t *testing.T) { {"a provider", func(m *Manifest) { m.Requires = append(m.Requires, "s3") }, "what it requires"}, {"what it provides", func(m *Manifest) { m.Provides = append(m.Provides, Offer{Name: "smtp"}) }, "what it provides"}, {"a process of a module with data", func(m *Manifest) { resource(m, "watcher")["command"] = "watch2" }, "long-running process watcher"}, + {"an archive resource", func(m *Manifest) { resource(m, "installation")["artifact"] = "archive2" }, "the archive installation"}, + {"a package resource", func(m *Manifest) { resource(m, "guard")["package"] = "nftables2" }, "the package guard"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { diff --git a/internal/catalogue/recreates.go b/internal/catalogue/recreates.go index 14350954..431a3f81 100644 --- a/internal/catalogue/recreates.go +++ b/internal/catalogue/recreates.go @@ -195,6 +195,12 @@ func Holds(from, to Manifest) string { if keepsData { return fmt.Sprintf("it changes its long-running %s %s, and it keeps data of its own", kind, id) } + default: + // Anything that is not a file or directory (judged above by whether a container mounts it + // or it holds data) always holds: an archive, a package, a user, a network, or any type + // added later (novox/hq ADR 0377 §3's allowlist names only tools bundles and manifest + // fields that recreate nothing). + return fmt.Sprintf("it changes the %s %s", kind, id) } } }