package compose import ( "encoding/json" "sort" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/overlay" ) func fpFor(c byte) string { return delivery.SourcePrefix2 + strings.Repeat(string(c), 64) } // 1943/AC5: a node's compositions are its declaration as Compose gives it, split by the module each resource belongs // to: every resource once, each in its module's composition at the fingerprint given, the module's ownership said on // it; a module given no fingerprint is named as skipped and composed nowhere. func TestCompositionsAreTheDeclarationSplitByModule(t *testing.T) { in := aSmallMesh(t) d, _, err := Compose(in, "laptop") if err != nil { t.Fatal(err) } var want []string for _, r := range d.Resources { want = append(want, r["type"].(string)+":"+r["id"].(string)) } sort.Strings(want) fps := map[string]string{"mesh-wireguard": fpFor('a'), "files": fpFor('b')} out, skipped, err := Compositions(in, "laptop", fps, Foreseeing) if err != nil || len(skipped) != 0 { t.Fatalf("Compositions = %v, skipped %v", err, skipped) } var got []string for key, c := range out { node, module, fp, isNode, err := delivery.ParseComposedKey(key) if err != nil || node != "laptop" || module != c.Module || fp != c.FP || (isNode != (module == "")) { t.Errorf("key %s holds %s at %s (%v)", key, c.Module, c.FP, err) } if !isNode && fp != fps[module] { t.Errorf("%s is composed at %s, given %s", module, fp, fps[module]) } if _, err := delivery.EncodeComposed(c); err != nil { t.Errorf("%s: %v", key, err) } for _, raw := range c.Resources { var r struct{ ID, Type, Module string } _ = json.Unmarshal(raw, &r) if r.Module != module { t.Errorf("%s holds %s of %q", key, r.ID, r.Module) } got = append(got, r.Type+":"+r.ID) } } sort.Strings(got) if strings.Join(got, " ") != strings.Join(want, " ") { t.Errorf("the compositions hold %v, the declaration %v", got, want) } one, skipped, err := Compositions(in, "laptop", map[string]string{"mesh-wireguard": fpFor('a')}, Foreseeing) if err != nil || strings.Join(skipped, ",") != "files" { t.Fatalf("one module: %v, skipped %v", err, skipped) } for _, c := range one { if c.Module == "files" { t.Errorf("a module given no fingerprint was composed") } } // Issue 1126: a module the node runs inside its own composition (Node.InOwn), and the private network's always, // given no fingerprint of the new form, is folded into the node's own as the controller's composed writer folds it: // every resource in its place, its module taken off, its id still naming it, nothing skipped. folding := in folding.Nodes = append([]Node(nil), in.Nodes...) for i := range folding.Nodes { if folding.Nodes[i].Name == "laptop" { folding.Nodes[i].InOwn = map[string]bool{"files": true} } } all, skipped, err := Compositions(folding, "laptop", nil, Foreseeing) nodeKey, _ := delivery.ComposedNodeKey("laptop") if err != nil || len(skipped) != 0 || len(all) != 1 || len(all[nodeKey].Resources) != len(want) { t.Fatalf("folded: %v, skipped %v, %d compositions, %d of %d resources in the node's own", err, skipped, len(all), len(all[nodeKey].Resources), len(want)) } for i, raw := range all[nodeKey].Resources { var r map[string]any _ = json.Unmarshal(raw, &r) if _, says := r["module"]; says || r["id"] != d.Resources[i]["id"] { t.Errorf("folded resource %d is %v, the declaration's %v", i, r["id"], d.Resources[i]["id"]) } } if _, err := delivery.EncodeComposed(all[nodeKey]); err != nil { t.Errorf("the node's own with the folded modules: %v", err) } if _, _, err := Compositions(in, "nowhere", fps, Foreseeing); err == nil { t.Errorf("a node the mesh does not have was composed") } // Issue 1126, 2026-10-29: a module composed as what runs of it (folded at a build of the old form, or held) still // contributes to the runtime what the runtime serves of it as it runs (Node.RunsTools): its manifest at the // indexer's commit has no bundles (shanks's runtime lost the folded systemd-resolved, novox's the folded nats), and a // held module's new build's bundles are not delivered. dg := func(c byte) string { return "sha256:" + strings.Repeat(string(c), 64) } runtime := catalogue.Manifest{Module: catalogue.RuntimeModule, Version: "1", OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/var/lib/mesh/node-tools/broker"}}, Bundles: []catalogue.Bundle{{Name: "runtime", Source: catalogue.ArtifactStoreScheme + "node-tools/runtime/blobs/" + dg('a'), Digest: dg('a'), Language: "go", Binary: "node-tools"}}} store := catalogue.Manifest{Module: "artifacts", Version: "1", Serves: map[string]map[string]any{catalogue.ArtifactStoreProvision: {"port": 5100}}} lamp := catalogue.Manifest{Module: "lamp", Version: "1", Tools: []string{"on"}, Resources: []map[string]any{{"id": "state", "type": "directory", "path": "/var/lib/lamp", "mode": "0700"}}, Build: &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "tools", Kind: catalogue.ArtifactBundle, Language: "go", System: "arch", From: "cmd/lamp-tools"}}}} newLamp, err := lamp.Resolve([]catalogue.Built{{Name: "tools", Kind: catalogue.ArtifactBundle, Reference: catalogue.ArtifactStoreScheme + "lamp/tools/blobs/" + dg('c'), Digest: dg('c')}}) if err != nil { t.Fatal(err) } served := "lamp=" + catalogue.BundlePath("lamp", "tools") + "/lamp-tools" runsTools := []delivery.Composed{{Node: "anchor", Module: catalogue.RuntimeModule, Resources: []json.RawMessage{json.RawMessage( `{"id":"node-tools.runtime","module":"node-tools","type":"process","name":"node-tools","env":{"MESH_TOOL_MODULES":"` + served + `","MESH_TOOL_ENV":"{\"lamp\":{\"LAMP_CONF\":\"/var/lib/lamp/conf\"}}"}}`)}}} lampRuns := `{"id":"lamp.bundle-tools","type":"archive","source":"` + catalogue.ArtifactStoreScheme + `lamp/tools/blobs/` + dg('b') + `","digest":"` + dg('b') + `","path":"` + catalogue.BundlePath("lamp", "tools") + `"}` for _, row := range []struct { name string set func(n *Node) delivery.Composed // and what runs of lamp fps map[string]string }{ {"folded at a build of the old form", func(n *Node) delivery.Composed { n.InOwn = map[string]bool{"lamp": true} n.RunsOwn = &delivery.Composed{Node: "anchor", Resources: []json.RawMessage{json.RawMessage(lampRuns)}} return *n.RunsOwn }, map[string]string{}}, {"held, its new build not delivered", func(n *Node) delivery.Composed { n.Held = map[string]bool{"lamp": true} lr := delivery.Composed{Node: "anchor", Module: "lamp", Resources: []json.RawMessage{json.RawMessage( strings.Replace(lampRuns, `"type"`, `"module":"lamp","type"`, 1))}} n.Kept = map[string]catalogue.Manifest{"lamp": Carried(newLamp, "lamp", &lr)} return lr }, map[string]string{"lamp": fpFor('1')}}, } { tools := aMesh(t) tools.Catalogue[catalogue.RuntimeModule], tools.Catalogue["artifacts"], tools.Catalogue["lamp"] = runtime, store, lamp anchor := nodeOf(t, &tools, "anchor") anchor.Assigned = append(anchor.Assigned, "artifacts", catalogue.RuntimeModule, "lamp") tools.BusMinted = []string{"anchor." + catalogue.RuntimeModule} anchor.SealingKey = keyOf(t, "anchor") anchor.RunsTools = ToolsRunning(append(append([]delivery.Composed{}, runsTools...), row.set(anchor))) row.fps[overlay.Name], row.fps["artifacts"], row.fps[catalogue.RuntimeModule] = fpFor('a'), fpFor('5'), fpFor('e') out, _, _, err := CompositionsIn(tools, "anchor", row.fps, Foreseeing) if err != nil { t.Fatalf("%s: %v", row.name, err) } var env map[string]any for _, c := range out { for _, raw := range c.Resources { var r struct { ID string `json:"id"` Env map[string]any `json:"env"` } if json.Unmarshal(raw, &r) == nil && r.ID == "node-tools.runtime" { env = r.Env } } } if env[catalogue.RuntimeToolModules] != served || env[catalogue.RuntimeToolEnv] != `{"lamp":{"LAMP_CONF":"/var/lib/lamp/conf"}}` { t.Errorf("%s: the runtime serves %v with %v, want what runs of lamp, %s", row.name, env[catalogue.RuntimeToolModules], env[catalogue.RuntimeToolEnv], served) } } } // 1943/AC1: the upgrade policies and sources a composition's value may name are the catalogue's own words. func TestTheComposedPoliciesAreTheCatalogues(t *testing.T) { for _, p := range []string{catalogue.PolicyRoll, catalogue.PolicyTogether, catalogue.PolicyRecord} { for _, from := range []string{catalogue.FromPerson, catalogue.FromModule, catalogue.FromBus, catalogue.FromData, catalogue.FromDefault} { v := delivery.ComposedDigest{Digest: "sha256:" + strings.Repeat("a", 64), Judgement: delivery.JudgedNeither, Policy: p, PolicyFrom: from} if _, err := delivery.EncodeComposedDigest(v); err != nil { t.Errorf("%s from %s: %v", p, from, err) } } } if delivery.PolicyRecord != catalogue.PolicyRecord { t.Errorf("delivery.PolicyRecord = %q, the catalogue's %q", delivery.PolicyRecord, catalogue.PolicyRecord) } }