From 34197afa01819555d02ca8ed89c467db8ea9e365 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Sat, 17 Oct 2026 16:43:49 +0000 Subject: [PATCH] Judge machines with the node-engine that knows unknown and knows (issue 1024, ADR 0422) The gate composes every machine with the node-engine's own validator, vendored at mesh-host of 2026-10-15, which refuses the `unknown` and `knows` keys mesh-host #117 added; every catalogue manifest using them was refused on every machine (mesh-catalog #349). The pin moves to #117's merge, 6469e8238443, delivered; only the declaration validator changes in vendor/. A test holds the gate to taking such a directory. --- cmd/mesh-controller/owned_paths_test.go | 23 ++++++++ go.mod | 2 +- go.sum | 4 +- .../internal/declaration/declaration.go | 59 +++++++++++++++++++ vendor/modules.txt | 4 +- 5 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 cmd/mesh-controller/owned_paths_test.go diff --git a/cmd/mesh-controller/owned_paths_test.go b/cmd/mesh-controller/owned_paths_test.go new file mode 100644 index 00000000..10784a11 --- /dev/null +++ b/cmd/mesh-controller/owned_paths_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/novox/mesh-host/validate" +) + +// A directory or an archive that says how it reacts to files it does not know (novox/hq ADR 0422) is a +// declaration the node-engine takes, so the gate, which judges every machine with the node-engine's own +// validator, takes it too. Before the pin moved, every catalogue manifest using the words was refused +// on every machine (issue 1024). +func TestTheGateTakesADirectoryThatSaysHowItReacts(t *testing.T) { + decl := fmt.Sprintf(`{"declaration":%d,"resources":[ + {"id":"xorg.config","type":"directory","path":"/home/a/.config/xorg","mode":"0755","owner":"a","unknown":"move-out","knows":["scripts/"]}, + {"id":"p10k.theme","type":"archive","source":"https://example.invalid/a.tar","digest":"sha256:`+ + "0000000000000000000000000000000000000000000000000000000000000000"+ + `","path":"/home/a/.local/share/powerlevel10k","unknown":"move-out"}]}`, validate.Version) + if problems := validate.Declaration([]byte(decl)); len(problems) != 0 { + t.Fatalf("refused: %q", problems) + } +} diff --git a/go.mod b/go.mod index aa638d77..109adb60 100644 --- a/go.mod +++ b/go.mod @@ -36,4 +36,4 @@ require ( // committed. Every build (the build agent's `go build`, the Dockerfile) compiles from vendor/ and // fetches nothing; go refuses to build when vendor/ and this file disagree, so a pin moved without // `go mod vendor` fails loudly, at once, everywhere. -replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 +replace github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261017162006-6469e8238443 diff --git a/go.sum b/go.sum index 61f5077c..77e26bb7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 h1:27nPn3XO9yfrHoAOuTuuJPVZTA3Bdnbqlc2wRI7S/LY= -git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98/go.mod h1:5QOm3cIfu/k9fN6QqgDbKGCcDst7Ul1UlcfsiMVm9j4= +git.novox.be/novox/mesh-host v0.0.0-20261017162006-6469e8238443 h1:zUtpGbiH9DLjCPY+KJsm+PZ25yW6k/NmGVS3haGFfMM= +git.novox.be/novox/mesh-host v0.0.0-20261017162006-6469e8238443/go.mod h1:5QOm3cIfu/k9fN6QqgDbKGCcDst7Ul1UlcfsiMVm9j4= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261015210729-d3e182503c19 h1:mAMt3KFmrHzDiBv4MLL94opWcG9y73ISMrBUyisapQE= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261015210729-d3e182503c19/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go b/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go index c66e9e7e..0475d6ac 100644 --- a/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go +++ b/vendor/github.com/novox/mesh-host/internal/declaration/declaration.go @@ -124,6 +124,60 @@ type Directory struct { // Owner is the user this belongs to, by name. Absent means root, which is what everything // managed was until users existed. Owner string `json:"owner,omitempty"` + // Unknown says how the directory treats a path inside it that nothing declared (issue 1024, novox/hq ADR 0422): + // move-out, module or program. Empty means not said, and nothing moves. + Unknown string `json:"unknown,omitempty"` + // Knows names paths relative to Path that are not declared but belong here; a trailing "/" names a folder and + // everything in it. + Knows []string `json:"knows,omitempty"` +} + +// How a directory or archive treats what it did not write (issue 1024, ADR 0422). +const ( + UnknownMoveOut = "move-out" + UnknownModule = "module" + UnknownProgram = "program" +) + +// checkUnknown judges the unknown and knows fields of a directory or an archive. +func checkUnknown(where, kind, unknown string, knows []string) []string { + var problems []string + switch unknown { + case "", UnknownMoveOut, UnknownModule, UnknownProgram: + default: + problems = append(problems, fmt.Sprintf( + "%s: a %s's unknown is %q; it is %q, %q or %q (issue 1024)", where, kind, unknown, + UnknownMoveOut, UnknownModule, UnknownProgram)) + } + if len(knows) > 0 && unknown == "" { + problems = append(problems, where+": knows is given without unknown, and says nothing without it") + } + seen := map[string]bool{} + for _, k := range knows { + switch { + case k == "": + problems = append(problems, where+": knows holds an empty entry") + continue + case strings.HasPrefix(k, "/"): + problems = append(problems, fmt.Sprintf("%s: knows entry %q is absolute; it is relative to the path", where, k)) + continue + } + bad := false + for _, c := range strings.Split(strings.TrimSuffix(k, "/"), "/") { + if c == "" || c == "." || c == ".." { + bad = true + } + } + if bad { + problems = append(problems, fmt.Sprintf("%s: knows entry %q holds an empty, . or .. component", where, k)) + continue + } + if seen[k] { + problems = append(problems, fmt.Sprintf("%s: knows entry %q is repeated", where, k)) + } + seen[k] = true + } + return problems } func (d *Directory) Identity() string { return d.ID } @@ -141,6 +195,7 @@ func (d *Directory) validate(where string, _ bool) []string { if len(mode) == 4 && mode[0] == '2' { mode = "0" + mode[1:] } + problems = append(problems, checkUnknown(where, "directory", d.Unknown, d.Knows)...) return append(problems, checkMode(where, mode)...) } @@ -696,6 +751,9 @@ type Archive struct { Path string `json:"path"` // Owner is the user the unpacked files belong to. Absent means root. Owner string `json:"owner,omitempty"` + // Unknown and Knows are as a directory's (issue 1024, ADR 0422). + Unknown string `json:"unknown,omitempty"` + Knows []string `json:"knows,omitempty"` } func (a *Archive) Identity() string { return a.ID } @@ -716,6 +774,7 @@ func (a *Archive) validate(where string, _ bool) []string { problems = append(problems, where+ ": an archive is pinned by digest, as sha256:<64 hex characters>") } + problems = append(problems, checkUnknown(where, "archive", a.Unknown, a.Knows)...) return problems } diff --git a/vendor/modules.txt b/vendor/modules.txt index 4990747b..11f2dce0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -79,7 +79,7 @@ github.com/nats-io/nkeys # github.com/nats-io/nuid v1.0.1 ## explicit github.com/nats-io/nuid -# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 +# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261017162006-6469e8238443 ## explicit; go 1.26.0 github.com/novox/mesh-host/internal/declaration github.com/novox/mesh-host/rootsearch @@ -139,4 +139,4 @@ golang.org/x/text/width # golang.org/x/time v0.15.0 ## explicit; go 1.25.0 golang.org/x/time/rate -# github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261015211328-5a7ec3775b98 +# github.com/novox/mesh-host => git.novox.be/novox/mesh-host v0.0.0-20261017162006-6469e8238443