diff --git a/go.mod b/go.mod index 3cc34da..1a43aa7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-host go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030182415-754edfae6924 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 golang.org/x/crypto v0.57.0 diff --git a/go.sum b/go.sum index a2b8e01..11d9d45 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030182415-754edfae6924 h1:aIiKj066gYFEDokUOq0i2n4vsygu5g0XMKJRRaMI4fM= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030182415-754edfae6924/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/internal/selfcompose/selfcompose_test.go b/internal/selfcompose/selfcompose_test.go index e6c36b4..4c68ffe 100644 --- a/internal/selfcompose/selfcompose_test.go +++ b/internal/selfcompose/selfcompose_test.go @@ -366,6 +366,36 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { strings.Contains((*told)[0], "held lemurs") { t.Errorf("the agent was told %q, want de-spiegel held alone, in its config form's words, without its routes", *told) } + // Issue 774, 2026-10-30 19:13: a manifest the mesh refuses (power's bar item at mesh-catalog ca8d6f33, captured, with + // attributes its bar places by none of) refused every node's composition and failed all its modules. Now power is + // held alone, naming why, and lemurs is handed. + power, err := os.ReadFile("testdata/power-ca8d6f33.module.json") + if err != nil { + t.Fatal(err) + } + c4, told4 := fresh(env.Get) + powerDig := delivery.ManifestDigest(power) + powerEnc, _ := delivery.EncodeManifest(delivery.Manifest{Module: "power", JSON: string(power)}) + powerKey, _ := delivery.ManifestsKey(powerDig) + b.mu.Lock() + b.kv[delivery.ManifestsBucket+"/"+powerKey] = powerEnc + b.mu.Unlock() + powerFP := delivery.SourcePrefix2 + strings.Repeat("c", 64) + c4.modules["power"] = delivery.Module{FP2: powerFP, Manifest: powerDig, Repository: "novox/mesh-catalog", Commit: "ca8d6f33"} + c4.wants["power"] = delivery.Want{FP: powerFP, Self: true} + p4 := c4.placements["n1"] + p4.Modules = append(append([]delivery.PlacedModule{}, p4.Modules...), delivery.PlacedModule{Module: "power"}) + c4.placements["n1"] = p4 + handed4 := map[string]agent.Update{} + for _, u := range c4.compose(ctx) { + handed4[u.Key] = u + } + if u, ok := handed4[lemursKey3]; !ok || u.Deleted { + t.Errorf("lemurs is not handed beside power's refused manifest: %v (told %q)", handed4, *told4) + } + if len(*told4) != 1 || !strings.HasPrefix((*told4)[0], "held power: ") || !strings.Contains((*told4)[0], `"shows" is none of them`) { + t.Errorf("the agent was told %q, want power held alone, naming its item", *told4) + } // A read the bus did not answer, timed out or not, is tried again later; the run's own end and a refusal are not. if !BusFailed(fmt.Errorf("the revisions of it2.mailer.domain: %w", context.DeadlineExceeded)) || !BusFailed(fmt.Errorf("x: %w", nats.ErrNoResponders)) || diff --git a/internal/selfcompose/testdata/power-ca8d6f33.module.json b/internal/selfcompose/testdata/power-ca8d6f33.module.json new file mode 100644 index 0000000..c43e601 --- /dev/null +++ b/internal/selfcompose/testdata/power-ca8d6f33.module.json @@ -0,0 +1,452 @@ +{ + "module": "power", + "version": "1", + "moved": true, + "templates": "templates", + "capabilities": [ + "package-manager", + "service-manager" + ], + "claims": [ + { + "name": "node-power", + "scope": "node" + } + ], + "emits": [ + "booted", + "sleeping", + "woke", + "shutting-down", + "on-mains", + "on-battery", + "battery-low" + ], + "state": [ + { + "name": "draw", + "ttl-seconds": 30, + "per-machine": true + } + ], + "tools": [ + "power_state", + "power_hooks", + "power_history", + "power_run", + "power_check" + ], + "config": { + "handle-power-key": { + "type": "string", + "description": "What logind does when the power key is pressed: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.", + "enum": [ + "ignore", + "poweroff", + "reboot", + "halt", + "kexec", + "suspend", + "hibernate", + "hybrid-sleep", + "suspend-then-hibernate", + "lock", + "factory-reset", + "sleep" + ], + "trusted": true, + "required": true + }, + "handle-lid-switch": { + "type": "string", + "description": "What logind does when the lid is closed on battery: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.", + "enum": [ + "ignore", + "poweroff", + "reboot", + "halt", + "kexec", + "suspend", + "hibernate", + "hybrid-sleep", + "suspend-then-hibernate", + "lock", + "factory-reset", + "sleep" + ], + "trusted": true, + "required": true + }, + "handle-lid-switch-external-power": { + "type": "string", + "description": "What logind does when the lid is closed on external power: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.", + "enum": [ + "ignore", + "poweroff", + "reboot", + "halt", + "kexec", + "suspend", + "hibernate", + "hybrid-sleep", + "suspend-then-hibernate", + "lock", + "factory-reset", + "sleep" + ], + "trusted": true, + "required": true + }, + "handle-lid-switch-docked": { + "type": "string", + "description": "What logind does when the lid is closed while docked or with another screen attached: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.", + "enum": [ + "ignore", + "poweroff", + "reboot", + "halt", + "kexec", + "suspend", + "hibernate", + "hybrid-sleep", + "suspend-then-hibernate", + "lock", + "factory-reset", + "sleep" + ], + "trusted": true, + "required": true + } + }, + "resources": [ + { + "id": "polkit", + "type": "package", + "package": "polkit" + }, + { + "id": "scripts", + "type": "archive", + "path": "/usr/local/lib/mesh-power", + "artifact": "scripts", + "unknown": "program" + }, + { + "id": "config-dir", + "type": "directory", + "path": "/etc/mesh-power", + "mode": "0755", + "unknown": "program" + }, + { + "id": "moments-dir", + "type": "directory", + "path": "/etc/mesh-power/moments", + "mode": "0755", + "unknown": "program" + }, + { + "id": "moment-after-boot", + "type": "file", + "path": "/etc/mesh-power/moments/after-boot", + "mode": "0644", + "template": "templates/mesh-power/moments/after-boot" + }, + { + "id": "moment-before-sleep", + "type": "file", + "path": "/etc/mesh-power/moments/before-sleep", + "mode": "0644", + "template": "templates/mesh-power/moments/before-sleep" + }, + { + "id": "moment-after-wake", + "type": "file", + "path": "/etc/mesh-power/moments/after-wake", + "mode": "0644", + "template": "templates/mesh-power/moments/after-wake" + }, + { + "id": "moment-before-shutdown", + "type": "file", + "path": "/etc/mesh-power/moments/before-shutdown", + "mode": "0644", + "template": "templates/mesh-power/moments/before-shutdown" + }, + { + "id": "moment-on-mains", + "type": "file", + "path": "/etc/mesh-power/moments/on-mains", + "mode": "0644", + "template": "templates/mesh-power/moments/on-mains" + }, + { + "id": "moment-on-battery", + "type": "file", + "path": "/etc/mesh-power/moments/on-battery", + "mode": "0644", + "template": "templates/mesh-power/moments/on-battery" + }, + { + "id": "logind-drop-ins", + "type": "directory", + "path": "/etc/systemd/logind.conf.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "logind-power", + "type": "file", + "path": "/etc/systemd/logind.conf.d/power.conf", + "mode": "0644", + "template": "templates/systemd/logind.conf.d/power.conf" + }, + { + "id": "logind", + "type": "service", + "unit": "systemd-logind.service", + "reload-on": [ + "logind-power" + ] + }, + { + "id": "after-boot-unit", + "type": "file", + "path": "/etc/systemd/system/mesh-power-after-boot.service", + "mode": "0644", + "template": "templates/systemd/system/mesh-power-after-boot.service" + }, + { + "id": "before-shutdown-unit", + "type": "file", + "path": "/etc/systemd/system/mesh-power-before-shutdown.service", + "mode": "0644", + "template": "templates/systemd/system/mesh-power-before-shutdown.service" + }, + { + "id": "before-sleep-unit", + "type": "file", + "path": "/etc/systemd/system/mesh-power-before-sleep.service", + "mode": "0644", + "template": "templates/systemd/system/mesh-power-before-sleep.service" + }, + { + "id": "after-wake-unit", + "type": "file", + "path": "/etc/systemd/system/mesh-power-after-wake.service", + "mode": "0644", + "template": "templates/systemd/system/mesh-power-after-wake.service" + }, + { + "id": "supply-unit", + "type": "file", + "path": "/etc/systemd/system/mesh-power-supply.service", + "mode": "0644", + "template": "templates/systemd/system/mesh-power-supply.service" + }, + { + "id": "sleep-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/sleep.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "sleep-wants", + "type": "file", + "path": "/etc/systemd/system/sleep.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/sleep.target.d/mesh-power.conf" + }, + { + "id": "suspend-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/suspend.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "suspend-wants", + "type": "file", + "path": "/etc/systemd/system/suspend.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/suspend.target.d/mesh-power.conf" + }, + { + "id": "hibernate-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/hibernate.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "hibernate-wants", + "type": "file", + "path": "/etc/systemd/system/hibernate.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/hibernate.target.d/mesh-power.conf" + }, + { + "id": "hybrid-sleep-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/hybrid-sleep.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "hybrid-sleep-wants", + "type": "file", + "path": "/etc/systemd/system/hybrid-sleep.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/hybrid-sleep.target.d/mesh-power.conf" + }, + { + "id": "suspend-then-hibernate-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/suspend-then-hibernate.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "suspend-then-hibernate-wants", + "type": "file", + "path": "/etc/systemd/system/suspend-then-hibernate.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/suspend-then-hibernate.target.d/mesh-power.conf" + }, + { + "id": "reboot-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/reboot.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "reboot-bound", + "type": "file", + "path": "/etc/systemd/system/reboot.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/reboot.target.d/mesh-power.conf" + }, + { + "id": "poweroff-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/poweroff.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "poweroff-bound", + "type": "file", + "path": "/etc/systemd/system/poweroff.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/poweroff.target.d/mesh-power.conf" + }, + { + "id": "halt-drop-ins", + "type": "directory", + "path": "/etc/systemd/system/halt.target.d", + "mode": "0755", + "unknown": "program" + }, + { + "id": "halt-bound", + "type": "file", + "path": "/etc/systemd/system/halt.target.d/mesh-power.conf", + "mode": "0644", + "template": "templates/systemd/system/halt.target.d/mesh-power.conf" + }, + { + "id": "udev-rule", + "type": "file", + "path": "/etc/udev/rules.d/90-mesh-power.rules", + "mode": "0644", + "template": "templates/udev/rules.d/90-mesh-power.rules" + }, + { + "id": "after-boot", + "type": "service", + "unit": "mesh-power-after-boot.service", + "state": "running", + "boot": "enabled" + }, + { + "id": "before-shutdown", + "type": "service", + "unit": "mesh-power-before-shutdown.service", + "state": "running", + "boot": "enabled" + }, + { + "id": "draw-dir", + "type": "directory", + "path": "/run/mesh-power", + "mode": "0755", + "unknown": "program" + }, + { + "id": "draw", + "type": "process", + "name": "mesh-power-draw", + "artifact": "tools-go", + "run": [ + "./power", + "draw" + ], + "health": { + "kind": "unit" + } + } + ], + "build": { + "artifacts": [ + { + "name": "tools-go", + "kind": "bundle", + "language": "go", + "system": "arch", + "from": "cmd/power", + "binary": "power", + "loads": [ + "power" + ] + }, + { + "name": "scripts", + "kind": "archive", + "from": "files" + }, + { + "name": "templates", + "kind": "archive", + "from": "templates" + } + ] + }, + "items": [ + { + "collection": "contribution:node-bar:block", + "name": "battery", + "if-capability": "battery", + "attributes": { + "bar": "bottom", + "place": "status", + "order": "40" + }, + "template": "templates/bar-battery" + }, + { + "collection": "contribution:node-bar:block", + "name": "power-draw", + "if-capability": "power-meter", + "attributes": { + "bar": "bottom", + "place": "status", + "order": "45", + "shows": "state", + "state": "power.draw" + }, + "template": "templates/bar-power-draw" + } + ] +}