From 5acc76399299edf4f7b6890e3d7845c9507ae5a4 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 15:33:44 +0200 Subject: [PATCH] A file a tools bundle's words name restarts the runtime when it changes (hq ADR 0192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tool containers were restarted when their configuration file changed; the runtime now is too, for every file a module's words name exactly — configuration and own secret alike. --- internal/catalogue/declaration.go | 16 +++++++++++++++- internal/catalogue/runtime.go | 19 +++++++++++++++---- internal/catalogue/runtime_test.go | 10 ++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index 6761d4b..dbd83f6 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -930,7 +930,21 @@ func (r Resolution) compose(with Rendering, owner map[string]string, } words[m.Module] = w } - givenTo(out, owner, words, r.Account) + // And a file a module's words name is one the runtime is restarted for when it changes. + if named := givenTo(out, owner, words, r.Account); len(named) > 0 { + restarts, _ := process["restart-on"].([]any) + seen := map[string]bool{} + for _, id := range restarts { + seen[fmt.Sprint(id)] = true + } + for _, id := range named { + if !seen[id] { + restarts = append(restarts, id) + seen[id] = true + } + } + process["restart-on"] = restarts + } } if with.Adopted { // First, before anything a module declares: what the mesh needs reachable, then its guard. diff --git a/internal/catalogue/runtime.go b/internal/catalogue/runtime.go index de97cb3..0722e49 100644 --- a/internal/catalogue/runtime.go +++ b/internal/catalogue/runtime.go @@ -310,9 +310,13 @@ func bundleWords(m Manifest, with Rendering) (map[string]string, error) { // is owned by the account — a tool reads its configuration and its secret as the account, and a // root-owned 0600 file or a 0700 directory is one it cannot. Only where it says no owner already: // a module that named one knew why. Nothing on a machine with no account, where the runtime is root. -func givenTo(out []map[string]any, owner map[string]string, words map[string]map[string]string, account string) { - if account == "" || len(words) == 0 { - return +// +// It answers the files a word names exactly: what a tool reads, whose change the runtime must be +// restarted for, as the container the tools came from was restarted when its configuration changed. +func givenTo(out []map[string]any, owner map[string]string, words map[string]map[string]string, account string) []string { + var named []string + if len(words) == 0 { + return nil } for _, resource := range out { module := owner[fmt.Sprint(resource["id"])] @@ -328,7 +332,12 @@ func givenTo(out []map[string]any, owner map[string]string, words map[string]map if path == "" { continue } - if _, said := resource["owner"]; said { + for _, value := range mine { + if kind == "file" && value == path { + named = append(named, fmt.Sprint(resource["id"])) + } + } + if _, said := resource["owner"]; said || account == "" { continue } for _, value := range mine { @@ -338,4 +347,6 @@ func givenTo(out []map[string]any, owner map[string]string, words map[string]map } } } + sort.Strings(named) + return named } diff --git a/internal/catalogue/runtime_test.go b/internal/catalogue/runtime_test.go index 48afd36..4003fb2 100644 --- a/internal/catalogue/runtime_test.go +++ b/internal/catalogue/runtime_test.go @@ -283,6 +283,16 @@ func TestABundleIsGivenItsWordsResolvedForThisMachine(t *testing.T) { if r := fileNamed(out, "dash.unrelated"); r == nil || r["owner"] != nil { t.Errorf("a file no word names was given an owner: %v", r) } + // A file a word names restarts the runtime when it changes, as it restarted the tool container. + restarts := fmt.Sprint(process["restart-on"]) + for _, want := range []string{"dash.config", "dash." + NeedID("token")} { + if !strings.Contains(restarts, want) { + t.Errorf("the runtime is not restarted when %s changes: %s", want, restarts) + } + } + if strings.Contains(restarts, "dash.unrelated") || strings.Contains(restarts, "dash.mesh-state") { + t.Errorf("the runtime restarts for something no word names as a file: %s", restarts) + } t.Run("on a machine with no account the runtime is root and nothing is re-owned", func(t *testing.T) { out, err := Resolution{Node: "anchor", Modules: []Manifest{dash, theRuntime(t)}}.Declaration(with)