package catalogue import ( "encoding/json" "fmt" "strings" "testing" ) // The node's tool runtime (novox/hq ADR 0175, to-be 38): where the runtime module is assigned, a // machine is sent every assigned module's tools bundle as an archive, and the runtime's own process // loading them. Where it is not, the machine is sent exactly what it was sent before. var bundleDigest = "sha256:" + strings.Repeat("b", 64) // aToolsModule is a module whose tools come as a compiled bundle and nothing else — the shape every // module takes once its tool container goes (to-be 38 WP4). func aToolsModule(t *testing.T, name string, entrypoints ...string) Manifest { t.Helper() m := Manifest{Module: name, Version: "1", Tools: []string{"status"}, Build: &Build{Artifacts: []Artifact{ {Name: "tools", Kind: ArtifactBundle, Language: "typescript", Entrypoints: entrypoints}, }}} resolved, err := m.Resolve([]Built{{Name: "tools", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + name + "/tools/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } return resolved } // theRuntime is the runtime module as the catalogue holds it: its own bundle, run rather than // loaded, and its broker secret to receive the node's credential in. func theRuntime(t *testing.T) Manifest { t.Helper() m := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "typescript", Entrypoints: []string{"src/main.js"}}}}} resolved, err := m.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } return resolved } func TestEveryToolsBundleIsDeliveredWhereTheRuntimeRuns(t *testing.T) { store := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}} nftables := aToolsModule(t, "nftables", "tools/index.js") zsh := aToolsModule(t, "zsh", "tools/index.js", "tools/more.js") t.Run("with the runtime, one archive per tools bundle", func(t *testing.T) { r := Resolution{Node: "anchor", Modules: []Manifest{nftables, zsh, theRuntime(t)}} out, err := r.Declaration(store) if err != nil { t.Fatal(err) } archive := fileNamed(out, "nftables."+BundleID("tools")) if archive == nil { t.Fatalf("nftables' tools bundle was not delivered: %v", ids(out)) } if archive["type"] != "archive" || archive["digest"] != bundleDigest || archive["path"] != BundleRoot+"/nftables/tools" { t.Errorf("delivered as %v", archive) } if archive["source"] != "http://anchor.internal:5101/v2/nftables/tools/blobs/"+bundleDigest { t.Errorf("fetched from %v, not through the store as this network reaches it", archive["source"]) } if fileNamed(out, "zsh."+BundleID("tools")) == nil { t.Errorf("zsh's tools bundle was not delivered: %v", ids(out)) } // The runtime's own bundle is run, not loaded: its process delivers it, not an archive. if fileNamed(out, RuntimeModule+"."+BundleID("runtime")) != nil { t.Error("the runtime's own bundle was delivered as an archive beside its process") } }) t.Run("without the runtime, nothing changes", func(t *testing.T) { r := Resolution{Node: "anchor", Modules: []Manifest{nftables, zsh}} out, err := r.Declaration(store) if err != nil { t.Fatal(err) } for _, id := range ids(out) { if strings.Contains(id, BundleID("")) { t.Errorf("%s was delivered to a machine running no runtime to load it", id) } } }) } func ids(out []map[string]any) []string { var names []string for _, r := range out { names = append(names, r["id"].(string)) } return names } // One process per machine runs the runtime from its own bundle, told what it serves and from where, // where its credential is, and who the operator is — restarted when any of that changes. func TestTheMachineRunsOneRuntimeLoadingEveryDeliveredBundle(t *testing.T) { with := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}} nftables := aToolsModule(t, "nftables", "tools/index.js") // A bundle carrying a daemon beside its tools says which files the runtime loads. showcase := Manifest{Module: "showcase", Version: "1", Tools: []string{"greet"}, Build: &Build{Artifacts: []Artifact{{Name: "code", Kind: ArtifactBundle, Language: "typescript", Entrypoints: []string{"daemon/index.js", "tools/index.js"}, Loads: []string{"tools/index.js"}}}}} showcase, err := showcase.Resolve([]Built{{Name: "code", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + "showcase/code/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } r := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{nftables, showcase, theRuntime(t)}} out, err := r.Declaration(with) if err != nil { t.Fatal(err) } process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID()) if process == nil { t.Fatalf("no runtime process was composed: %v", ids(out)) } if process["type"] != "process" || process["name"] != RuntimeModule || process["digest"] != bundleDigest || process["source"] != "http://anchor.internal:5101/v2/"+RuntimeModule+"/runtime/blobs/"+bundleDigest { t.Errorf("the runtime's process is %v", process) } if fmt.Sprint(process["run"]) != "[node src/main.js]" { t.Errorf("the runtime is run as %v; its bundle's one entrypoint, by its language's interpreter", process["run"]) } env := process["env"].(map[string]string) if env[RuntimeToolModules] != "nftables="+BundleRoot+"/nftables/tools/tools/index.js,"+ "showcase="+BundleRoot+"/showcase/code/tools/index.js" { t.Errorf("the runtime is told to serve %q: every loaded file, by module, and nothing a bundle runs", env[RuntimeToolModules]) } if env[RuntimeBrokerFile] != "/var/lib/mesh/"+RuntimeModule+"/broker" { t.Errorf("the runtime reads its credential at %q, not where the module's own secret is placed", env[RuntimeBrokerFile]) } if env[RuntimeOperatorAccount] != "ops" || env[RuntimeOperatorHome] != "/home/ops" || process["user"] != "ops" { t.Errorf("the operator is not handed to the runtime: %v as %v", env, process["user"]) } // The credential the process reads belongs to the account it runs as, or it could not read it // (to-be 38 WP3); other modules' secrets are left as their manifests say. if credential := fileNamed(out, RuntimeModule+"."+NeedID("broker")); credential == nil || credential["owner"] != "ops" { t.Errorf("the runtime's credential is not the account's to read: %v", credential) } restarts := fmt.Sprint(process["restart-on"]) for _, want := range []string{"nftables." + BundleID("tools"), "showcase." + BundleID("code"), RuntimeModule + "." + NeedID("broker")} { if !strings.Contains(restarts, want) { t.Errorf("the runtime is not restarted when %s changes: %s", want, restarts) } } // After every bundle and the credential, so both exist before it starts. names := ids(out) if names[len(names)-1] != RuntimeModule+"."+RuntimeProcessID() { t.Errorf("the runtime's process is not last: %v", names) } t.Run("a machine with no account runs it as root without the operator words", func(t *testing.T) { out, err := Resolution{Node: "anchor", Modules: []Manifest{nftables, theRuntime(t)}}.Declaration(with) if err != nil { t.Fatal(err) } process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID()) env := process["env"].(map[string]string) if _, set := env[RuntimeOperatorAccount]; set { t.Error("an operator account was named on a machine that has none") } if _, set := process["user"]; set { t.Error("a user was set on a machine with no account") } if credential := fileNamed(out, RuntimeModule+"."+NeedID("broker")); credential == nil || credential["owner"] != nil { t.Errorf("the runtime's credential was given an owner on a machine with no account: %v", credential) } }) t.Run("a runtime module built wrong is refused by name", func(t *testing.T) { two := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/b"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "typescript", Entrypoints: []string{"a.js", "b.js"}}}}} resolved, err := two.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + "x/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } _, err = Resolution{Node: "anchor", Modules: []Manifest{resolved}}.Declaration(with) if err == nil || !strings.Contains(err.Error(), "entrypoint") { t.Errorf("a runtime bundle with two entrypoints was composed: %v", err) } }) } // novox/hq ADR 0193: the runtime is told the launcher a build wrote, and the entrypoint itself for a // build from before launchers — so the move needs no flag day. func TestTheRuntimeStartsTheLauncherWhereTheBuildWroteOne(t *testing.T) { with := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}} launched := Manifest{Module: "dash", Version: "1", Tools: []string{"status"}, Build: &Build{Artifacts: []Artifact{{Name: "tools", Kind: ArtifactBundle, Language: "typescript", Entrypoints: []string{"tools/index.js"}}}}} launched, err := launched.Resolve([]Built{{Name: "tools", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + "dash/tools/blobs/" + bundleDigest, Digest: bundleDigest, Launchers: map[string]string{"tools/index.js": "tools/index.serve.mjs"}}}) if err != nil { t.Fatal(err) } older := aToolsModule(t, "nftables", "tools/index.js") out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{launched, older, theRuntime(t)}}.Declaration(with) if err != nil { t.Fatal(err) } env := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())["env"].(map[string]string) want := "dash=" + BundleRoot + "/dash/tools/tools/index.serve.mjs,nftables=" + BundleRoot + "/nftables/tools/tools/index.js" if env[RuntimeToolModules] != want { t.Errorf("the runtime is told %q, want %q", env[RuntimeToolModules], want) } } // novox/hq ADR 0193: a runtime compiled to a binary runs itself from its own unpacked bundle. func TestARuntimeCompiledToABinaryRunsItself(t *testing.T) { with := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}} goRuntime := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go", System: "arch", From: "cmd/node-tools"}}}} goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{aToolsModule(t, "nftables", "tools/index.js"), goRuntime}}.Declaration(with) if err != nil { t.Fatal(err) } process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID()) if fmt.Sprint(process["run"]) != "[./node-tools]" { t.Errorf("a Go runtime is run as %v, want its own binary", process["run"]) } env := process["env"].(map[string]string) if env[RuntimeToolModules] == "" || process["user"] != "ops" { t.Errorf("the Go runtime is not told what to serve or whose it is: %v %v", env, process["user"]) } } // novox/hq ADR 0259 §8: a module of its own account is served by a runtime of its own — the machine's // runtime program, as that account, on that module's own credential — and never by the machine's runtime, // which runs as the operator's account and is given none of its words. func TestAModuleOfItsOwnAccountIsServedByARuntimeOfItsOwn(t *testing.T) { with := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}, "telegram": {"broker": "own"}}} goRuntime := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go", System: "arch", From: "cmd/node-tools"}}}} goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } telegram := aToolsModule(t, "telegram", "tools/index.js") telegram.RunsAs, telegram.SecretsOwner = "telegram", "telegram" telegram.OwnSecrets = OwnSecrets{"broker": {Path: "/var/lib/telegram/broker"}} out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{aToolsModule(t, "nftables", "tools/index.js"), telegram, goRuntime}}.Declaration(with) if err != nil { t.Fatal(err) } machine := fileNamed(out, RuntimeModule+"."+RuntimeProcessID()) if served := machine["env"].(map[string]string)[RuntimeToolModules]; strings.Contains(served, "telegram") || !strings.Contains(served, "nftables") { t.Errorf("the machine's runtime serves %q", served) } own := fileNamed(out, "telegram."+OwnRuntimeID()) if own == nil { t.Fatalf("telegram has no runtime of its own: %v", ids(out)) } env := own["env"].(map[string]string) if own["user"] != "telegram" || fmt.Sprint(own["run"]) != "[./node-tools]" || env[RuntimeBrokerFile] != "/var/lib/telegram/broker" || env[RuntimeToolModules] != "telegram="+BundleRoot+"/telegram/tools/tools/index.js" { t.Errorf("its own runtime: user %v run %v env %v", own["user"], own["run"], env) } if _, told := env[RuntimeOperatorAccount]; told { t.Error("a runtime of a module's own account is told the operator's account") } if _, yields := own["yields"]; yields { t.Errorf("telegram holds no build seat and yields: %v", own["yields"]) } // The build seats' holders yield (ADR 0553): idle, under the node's one share from the node-engine's build-cpu-share, // and their steps are told it. builder := func(name, seat string) Manifest { b := aToolsModule(t, name, "tools/index.js") b.RunsAs, b.SecretsOwner = name, name b.OwnSecrets = OwnSecrets{"broker": {Path: "/var/lib/" + name + "/broker"}} b.Claims = []Claim{{Name: seat}} return b } gob, img := builder("gob", "node-build-go"), builder("img", "node-build-image") with.Needed["gob"], with.Needed["img"] = map[string]string{"broker": "own"}, map[string]string{"broker": "own"} for _, c := range []struct { settings SettingsBy share string }{ {nil, "50"}, {SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(75)}}}}, "75"}, {SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(0)}}}}, "50"}, } { with.Settings = c.settings runtimes, err := Resolution{Node: "anchor", Modules: []Manifest{gob, img, telegram, goRuntime}}.ownRuntimes(with) if err != nil { t.Fatal(err) } if len(runtimes) != 3 { t.Fatalf("%d runtimes of their own, want gob's, img's and telegram's", len(runtimes)) } for _, p := range runtimes { name := fmt.Sprint(p["name"]) words := map[string]map[string]string{} _ = json.Unmarshal([]byte(p["env"].(map[string]string)[RuntimeToolEnv]), &words) mod := strings.TrimSuffix(name, "-runtime") if mod == "telegram" { if p["yields"] != nil || words[mod]["MESH_BUILD_CPU_SHARE"] != "" { t.Errorf("telegram yields under %v: %v %v", c.settings, p["yields"], words) } continue } if got := fmt.Sprint(p["yields"]); got != "map[cpu-share:"+c.share+" priority:idle]" || words[mod]["MESH_BUILD_CPU_SHARE"] != c.share { t.Errorf("%s under %v yields %s with words %v; want share %s", name, c.settings, got, words[mod], c.share) } } } with.Settings = nil // Without the machine's runtime to run it from, it is refused in words. if _, err := (Resolution{Node: "anchor", Modules: []Manifest{telegram}}).ownRuntimes(with); err == nil { t.Error("a module of its own account composed without a runtime program") } } // The review of 2026-10-09 (L4): a module of its own account never runs as the node's operator account, nor // as the account agents run as there — either would hand what it holds back to the accounts it is kept from. func TestAModuleOfItsOwnAccountIsRefusedTheOperatorsAndTheAgentsAccount(t *testing.T) { with := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}, "telegram": {"broker": "own"}}} goRuntime := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go", System: "arch", From: "cmd/node-tools"}}}} goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } for _, account := range []string{"ops", "agent"} { telegram := aToolsModule(t, "telegram", "tools/index.js") telegram.RunsAs, telegram.SecretsOwner = account, account telegram.OwnSecrets = OwnSecrets{"broker": {Path: "/var/lib/telegram/broker"}} _, err := Resolution{Node: "anchor", Account: "ops", AgentAccount: "agent", Modules: []Manifest{telegram, goRuntime}}.ownRuntimes(with) if err == nil || !strings.Contains(err.Error(), account) { t.Errorf("telegram running as %s was composed: %v", account, err) } } } // Issue 723: the vault runs as its own account, so what the mesh writes for it — the export it keeps, the grant // files it holds for its consumers, and what its words name — belongs to that account, never the operator's. func TestWhatAModuleOfItsOwnAccountReadsIsItsAccounts(t *testing.T) { with := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}, "vault": {"broker": "own"}}, Kept: &KeptExport{Export: 1, OperatorKey: "k"}, Grants: []Grant{{Provision: "secret", Consumer: "anchor", From: "telegram", Sealed: "sealed-to-anchor", Values: map[string]any{"name": "telegram"}}}} goRuntime := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go", System: "arch", From: "cmd/node-tools"}}}} goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } vault := aToolsModule(t, "vault", "tools/index.js") vault.RunsAs, vault.SecretsOwner = "vault", "vault" vault.OwnSecrets = OwnSecrets{"broker": {Path: "/var/lib/vault/broker"}} vault.Keeps = "/var/lib/vault/root" vault.Grants = map[string]string{"secret": "/var/lib/vault/grants"} vault.Receives = map[string]string{"secret": "/var/lib/vault/grants/mesh.json"} vault.Bundles[0].Env = map[string]string{"MESH_RECEIVES": "/var/lib/vault/grants/mesh.json"} out, err2 := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{vault, goRuntime}}.Declaration(with) if err = err2; err != nil { t.Fatal(err) } seen := 0 for _, r := range out { path := fmt.Sprint(r["path"]) if !strings.HasPrefix(path, "/var/lib/vault/") || r["type"] != "file" { continue } seen++ if r["owner"] != "vault" { t.Errorf("%s (%v) belongs to %v, not the vault's own account", path, r["id"], r["owner"]) } } if seen < 4 { t.Errorf("only %d of the vault's files were composed: %v", seen, ids(out)) } // Carried by the machine's runtime instead, nothing of it changes: the operator's account reads them. carried := vault carried.RunsAs, carried.SecretsOwner = "", "" out, err = Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{carried, goRuntime}}.Declaration(with) if err != nil { t.Fatal(err) } for _, r := range out { if path := fmt.Sprint(r["path"]); strings.HasPrefix(path, "/var/lib/vault/") && r["owner"] == "vault" { t.Errorf("%s belongs to the vault's account though it runs as none", path) } } }