diff --git a/cmd/mesh-controller/check_test.go b/cmd/mesh-controller/check_test.go index 608b6b69..6d323986 100644 --- a/cmd/mesh-controller/check_test.go +++ b/cmd/mesh-controller/check_test.go @@ -56,9 +56,15 @@ func TestModuleCheckJudgesBetweenTheManifestsGiven(t *testing.T) { } } -// The real catalogue passes the command, the way it passes the test that used to be the only check. +// The real catalogue passes the command, the way it passes the test that used to be the only check: the catalogue this +// controller is released with, captured in testdata/beside at the commit CAPTURED names. Not the clone a merge check puts +// beside, which is the catalogue's trunk even in a delivery group holding a catalogue pull request (ADR 0484 clause 4): +// a controller whose check reads a word that group's catalogue brings would fail on the trunk's catalogue before the +// group merges both (issue 774: the release's sdk refuses an offer that states no credential form, and the catalogue's +// trunk states none until the release's catalogue merges). Moving CAPTURED to the catalogue a change is released with is +// part of that change. func TestModuleCheckPassesTheCatalogue(t *testing.T) { - root := beside.Catalogue(t) + root := filepath.Join(beside.CapturedDir(t, "mesh-catalog"), "modules") paths, err := manifestsUnder(root) if err != nil || len(paths) == 0 { t.Fatalf("no manifests under %s: %v", root, err) diff --git a/cmd/mesh-controller/identity_bound_test.go b/cmd/mesh-controller/identity_bound_test.go index a3160af9..d8a5e0c9 100644 --- a/cmd/mesh-controller/identity_bound_test.go +++ b/cmd/mesh-controller/identity_bound_test.go @@ -23,10 +23,10 @@ func TestModuleCheckRefusesAnIdentityThatOverflowsWhatItRequires(t *testing.T) { return p } objects := write("objects", `{"module":"objects","version":"1", - "provides":[{"name":"s3-bucket","scope":"mesh","identity":{"max":20,"in":"an S3 access key"}}], + "provides":[{"name":"s3-bucket","scope":"mesh","identity":{"max":20,"in":"an S3 access key"},"credential":{"form":"pair"}}], "receives":{"s3-bucket":"/var/lib/mesh/objects/mesh.json"}}`) resolver := write("resolver", `{"module":"resolver","version":"1", - "provides":[{"name":"wildcard-resolution","scope":"mesh","identity":false}]}`) + "provides":[{"name":"wildcard-resolution","scope":"mesh","identity":false,"credential":{"form":"none"}}]}`) album := write("photoalbum", `{"module":"photoalbum","version":"1","requires":["s3-bucket"]}`) nm := write("networkmanager", `{"module":"networkmanager","version":"1","requires":["wildcard-resolution"]}`) diff --git a/cmd/mesh-controller/issued_test.go b/cmd/mesh-controller/issued_test.go index baa1b90a..48e045bd 100644 --- a/cmd/mesh-controller/issued_test.go +++ b/cmd/mesh-controller/issued_test.go @@ -182,6 +182,9 @@ func TestANeedIsIssuedToItsHolderOnlyWhereTheProviderIsMovedAndItsSoftwareIssues offer := catalogue.Offer{Name: "gitea-read", Scope: catalogue.ScopeMesh} if issued { offer.Credential = &catalogue.OfferCredential{IssuedBy: catalogue.IssuedBySoftware, Tool: "gitea_agent_token"} + } else { + // A login of each consumer's own, said: an offer naming no form has no credential (mesh-sdk 6b8c694). + offer.Credential = &catalogue.OfferCredential{Form: catalogue.CredentialFormPair} } return catalogue.Manifest{Module: "gitea", Version: "1", Moved: moved, Provides: []catalogue.Offer{offer}} } @@ -216,8 +219,10 @@ func TestANeedIsIssuedToItsHolderOnlyWhereTheProviderIsMovedAndItsSoftwareIssues // marked moved; another module is not. func TestTheModuleHoldingTheVaultSeatCountsAsMovedForItsConsumers(t *testing.T) { vault := catalogue.Manifest{Module: "mesh-vault", Version: "1", Claims: []catalogue.Claim{{Name: "mesh-vault", Scope: catalogue.ScopeMesh}}, - Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh}}} - other := catalogue.Manifest{Module: "mesh-vault", Version: "1", Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh}}} + Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh, + Credential: &catalogue.OfferCredential{Form: catalogue.CredentialFormPair}}}} + other := catalogue.Manifest{Module: "mesh-vault", Version: "1", Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh, + Credential: &catalogue.OfferCredential{Form: catalogue.CredentialFormPair}}}} if got := needOf(t, map[string]catalogue.Manifest{"mesh-vault": vault}, "mesh-vault", "gitea", "secret"); !got.IssuedToHolder || !got.ProviderMoved { t.Errorf("the vault's consumer is not answered by a moved provider: %+v", got) } diff --git a/go.mod b/go.mod index 27ffed8f..f29fb348 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-controller go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 github.com/jackc/pgx/v5 v5.10.0 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/go.sum b/go.sum index 394bec26..0b60d1bd 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5 h1:E5stfIeCN6uYgA9TCv+4K8JgaURg3zQppvUgJLaDMP4= git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771 h1:Tt16HJk8JZRce1i3JzzDSNdCWOrv7dw8FQ/bckNQ5Ig= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +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= 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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/beside/beside.go b/internal/beside/beside.go index 3ae47abf..159d8867 100644 --- a/internal/beside/beside.go +++ b/internal/beside/beside.go @@ -53,6 +53,16 @@ func Dir(t testing.TB, repository string) string { t.Logf("judged against %s as cloned beside this check", dir) return dir } + t.Logf("no %s beside this check: a merge check judges %s against its own clone", Env, repository) + return CapturedDir(t, repository) +} + +// CapturedDir is the copy of the named repository captured in testdata/beside, laid out as Dir lays it out, whatever +// MESH_CHECK_BESIDE says: for a test that judges this repository against the commit it was released with, which a +// merge check does not clone (it clones the catalogue at its trunk even in a delivery group holding a catalogue pull +// request, ADR 0484 clause 4). +func CapturedDir(t testing.TB, repository string) string { + t.Helper() from := filepath.Join(Captured(), repository) if _, err := os.Stat(from); err != nil { t.Fatalf("no captured copy of %s at %s: %v", repository, from, err) @@ -81,8 +91,7 @@ func Dir(t testing.TB, repository string) string { if err != nil { t.Fatalf("the captured copy of %s could not be laid out: %v", repository, err) } - t.Logf("judged against the copy of %s captured in testdata/beside (see CAPTURED); a merge check "+ - "judges it against the repository's own clone", repository) + t.Logf("judged against the copy of %s captured in testdata/beside (see CAPTURED)", repository) return dir } diff --git a/testdata/beside/CAPTURED b/testdata/beside/CAPTURED index 6604b70d..9fb61a4d 100644 --- a/testdata/beside/CAPTURED +++ b/testdata/beside/CAPTURED @@ -7,9 +7,9 @@ The copy carries the catalogue's private details: domains, first names in module private addresses. That is acceptable while mesh-controller is private; if it ever goes public, this copy is a second place to clean besides the catalogue itself. -mesh-catalog 3a1017f5594217092a654dd1e2d72226766bc16a modules/*/module.json, modules/*/files, modules/*/templates, predefined.json +mesh-catalog f3a57dcc56fb6c198095cafd11d0decff9f720c4 modules/*/module.json, modules/*/files, modules/*/templates, predefined.json mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock -mesh-sdk 78c9ec8817710eae7f1a068aef68924293fdef59 conformance/events/module-event.json +mesh-sdk 2da0de75c3f0087cff9c2f5ef789ca6dc3c59045 conformance/events/module-event.json To move them, from this repository's root, with the two repositories checked out beside it: diff --git a/testdata/beside/mesh-catalog/modules/artifact-store-tools/files/store-address b/testdata/beside/mesh-catalog/modules/artifact-store-tools/files/store-address new file mode 100644 index 00000000..1bcadab2 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/artifact-store-tools/files/store-address @@ -0,0 +1 @@ +${seat:mesh-artifact-store:reach} diff --git a/testdata/beside/mesh-catalog/modules/artifact-store-tools/module.json.captured b/testdata/beside/mesh-catalog/modules/artifact-store-tools/module.json.captured index 208329cd..f1ef7a43 100644 --- a/testdata/beside/mesh-catalog/modules/artifact-store-tools/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/artifact-store-tools/module.json.captured @@ -1,26 +1,52 @@ { "module": "artifact-store-tools", "version": "1", + "claims": [ + { + "name": "mesh-artifact-sweep", + "scope": "mesh", + "serves": [ + "artifacts", + "held", + "collect", + "mirrors", + "images" + ] + } + ], + "requires": [ + "artifact-store" + ], "invokes": [ - "seat:mesh-controller.artifacts", - "seat:mesh-controller.collect", + "seat:mesh-hand-acts.hand-act", "mesh-catalog.catalog_modules", "mesh-catalog.catalog_module" ], + "reads": [ + "mesh-delivery-assigner.wants", + "mesh-delivery-builder.builds", + "mesh-delivery-builder.build-log", + "mesh-delivery-indexer.manifests", + "node-engine.have", + "mesh-controller.lease" + ], + "state": [ + "findings" + ], "tools": [ "artifact_store_repositories", "artifact_store_usage", "artifact_store_references", "artifact_store_collect" ], - "config": { - "older-store-names": { - "type": "string", - "description": "The names the artifact store had before, as host names or host:port separated by spaces, or none when it had no other. artifact_store_references reads every definition under these as well as under the store's current name, so an image a definition pins under one is said to be named by a definition (novox/hq issue 323).", - "pattern": "none|[A-Za-z0-9.:-]+( [A-Za-z0-9.:-]+)*", - "default": "none" - } - }, + "config": { + "older-store-names": { + "type": "string", + "description": "The names the artifact store had before, as host names or host:port separated by spaces, or none when it had no other. artifact_store_references reads every definition under these as well as under the store's current name, so an image a definition pins under one is said to be named by a definition (novox/hq issue 323).", + "pattern": "none|[A-Za-z0-9.:-]+( [A-Za-z0-9.:-]+)*", + "default": "none" + } + }, "resources": [ { "id": "state", @@ -36,6 +62,14 @@ "mode": "0600", "trusted": false, "template": "files/store-names" + }, + { + "id": "store-address", + "type": "file", + "path": "${dir:state}/store-address", + "mode": "0600", + "trusted": false, + "template": "files/store-address" } ], "build": { @@ -52,7 +86,8 @@ ], "env": { "MESH_ARTIFACT_STORE_CONTAINER": "mesh-registry", - "MESH_ARTIFACT_STORE_NAMES_FILE": "${dir:state}/store-names" + "MESH_ARTIFACT_STORE_NAMES_FILE": "${dir:state}/store-names", + "MESH_ARTIFACT_STORE_ADDRESS_FILE": "${dir:state}/store-address" } }, { diff --git a/testdata/beside/mesh-catalog/modules/buildah/module.json.captured b/testdata/beside/mesh-catalog/modules/buildah/module.json.captured index 70ddd0a4..bdfbe96b 100644 --- a/testdata/beside/mesh-catalog/modules/buildah/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/buildah/module.json.captured @@ -8,7 +8,10 @@ "provides": [ { "name": "rootless-image-build", - "reach": "machine" + "reach": "machine", + "credential": { + "form": "none" + } } ], "resources": [ diff --git a/testdata/beside/mesh-catalog/modules/disk-load/module.json.captured b/testdata/beside/mesh-catalog/modules/disk-load/module.json.captured index 9b48041d..4b4313ca 100644 --- a/testdata/beside/mesh-catalog/modules/disk-load/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/disk-load/module.json.captured @@ -7,7 +7,9 @@ "disk_filesystems", "disk_folders", "disk_devices", - "cpu_top" + "cpu_top", + "disk_readings", + "disk_measure" ], "build": { "artifacts": [ @@ -23,5 +25,16 @@ ] } ] - } + }, + "state": [ + "readings" + ], + "reads": [ + "mesh-config.resolved", + "mesh-delivery-assigner.placement", + "mesh-delivery-builder.builds", + "mesh-delivery-indexer.manifests", + "mesh-delivery-indexer.modules", + "node-engine.have" + ] } diff --git a/testdata/beside/mesh-catalog/modules/distribution/module.json.captured b/testdata/beside/mesh-catalog/modules/distribution/module.json.captured index eb9f7c24..5037f429 100644 --- a/testdata/beside/mesh-catalog/modules/distribution/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/distribution/module.json.captured @@ -4,7 +4,10 @@ "provides": [ { "name": "artifact-store", - "scope": "mesh" + "scope": "mesh", + "credential": { + "form": "none" + } } ], "claims": [ diff --git a/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf b/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf index 96d8d7d6..7f86d122 100644 --- a/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf +++ b/testdata/beside/mesh-catalog/modules/dnsmasq/files/mesh.conf @@ -1,14 +1,15 @@ # Written by the mesh (module dnsmasq, novox/hq issue 348). Replaced on every push. # # The resolver binds its addresses once, at start (bind-interfaces), so it starts only once this -# machine's private address is there: it waits up to 80 seconds for it, and a start that found it -# missing is tried again every ten seconds rather than left failed. +# machine's private address is there: a start that finds it missing fails after at most two seconds and +# is tried again every ten seconds (Restart=on-failure, no start limit) until the tunnel has brought it. +# It does not order itself After= or Wants= network-online.target, and its wait is short: the packaged +# unit is Before= network-online.target and nss-lookup.target, and the tunnel is ordered after them, so +# a long wait would hold the tunnel back and a drop-in ordering would be a cycle (issue 1126). [Unit] -After=network-online.target -Wants=network-online.target StartLimitIntervalSec=0 [Service] -ExecStartPre=/usr/bin/sh -c 'for i in $(seq 1 80); do ip -4 -o addr show to ${machine:address} | grep -q . && exit 0; sleep 1; done; echo "${machine:address} is not on this machine" >&2; exit 1' +ExecStartPre=/usr/bin/sh -c 'for i in $(seq 1 2); do ip -4 -o addr show to ${machine:address} | grep -q . && exit 0; sleep 1; done; echo "${machine:address} is not on this machine" >&2; exit 1' Restart=on-failure RestartSec=10s diff --git a/testdata/beside/mesh-catalog/modules/dnsmasq/module.json.captured b/testdata/beside/mesh-catalog/modules/dnsmasq/module.json.captured index 58cc0da5..7e832067 100644 --- a/testdata/beside/mesh-catalog/modules/dnsmasq/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/dnsmasq/module.json.captured @@ -9,7 +9,10 @@ { "name": "wildcard-resolution", "scope": "mesh", - "identity": false + "identity": false, + "credential": { + "form": "none" + } } ], "requires": [ diff --git a/testdata/beside/mesh-catalog/modules/docker/module.json.captured b/testdata/beside/mesh-catalog/modules/docker/module.json.captured index fae954c3..500faea6 100644 --- a/testdata/beside/mesh-catalog/modules/docker/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/docker/module.json.captured @@ -7,7 +7,7 @@ "privileged" ], "invokes": [ - "seat:mesh-controller.images" + "seat:mesh-artifact-sweep.images" ], "claims": [ { diff --git a/testdata/beside/mesh-catalog/modules/gitea/module.json.captured b/testdata/beside/mesh-catalog/modules/gitea/module.json.captured index 1ac1aeb8..2b197288 100644 --- a/testdata/beside/mesh-catalog/modules/gitea/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/gitea/module.json.captured @@ -40,6 +40,7 @@ ], "emits": [ "repo.created", + "repo.writable", "issue.opened", "pull.merged", "pull.updated", @@ -181,6 +182,12 @@ "trusted": true, "default": {} }, + "check-gates-merge": { + "type": "boolean", + "description": "Whether gitea_merge_pull_request judges mesh/check before it merges. True (the default): a head merges only on a passing mesh/check judged against the trunk's head (ADR 0447, ADR 0534 §4). False (the operator, 2026-10-25 22:56: \"run the check locally only, but not as a gate, it loses us too much time\"): a head the operator's Approve names in approved_heads merges whatever its mesh/check says (pending, failed, missing or judged on an older trunk); a head that moved, a branch no rule guards and a merge git refuses are still refused. Set together with mesh-delivery-gate's check-gates-merge.", + "trusted": true, + "default": true + }, "main-mergers": { "type": "list", "description": "The forge logins that may merge into main beside the tools' account, on every repository of the organisation (ADR 0534 §2): the operator's own, so the operator can always merge by hand at the forge. The tools' account is always on main's merge whitelist; no agent's account belongs here.", @@ -259,7 +266,8 @@ "env": { "DB_TYPE": "postgres", "USER_UID": "1000", - "USER_GID": "1000" + "USER_GID": "1000", + "SSH_MAX_STARTUPS": "100:30:200" }, "env-file": [ "${dir:state}/server.env" @@ -316,7 +324,8 @@ "takes-config": [ "url", "keep-main-rules", - "main-mergers" + "main-mergers", + "check-gates-merge" ] }, { @@ -391,6 +400,7 @@ "provides": [ { "name": "npm-package-registry", + "credential": {"form": "pair"}, "scope": "mesh", "identity": { "max": 40, @@ -399,6 +409,7 @@ }, { "name": "git", + "credential": {"form": "none"}, "scope": "mesh" }, { @@ -411,6 +422,7 @@ }, { "name": "forge-account", + "credential": {"form": "none"}, "scope": "mesh" } ], @@ -537,6 +549,7 @@ "MESH_GITEA_ADMIN_PASSWORD_FILE": "${dir:state}/admin.secret", "MESH_GITEA_ORIGINS_FILE": "${dir:mesh-state}/origins", "MESH_GITEA_SSH_PORT": "222", + "MESH_GITEA_HOST_KEY_FILE": "${dir:data}/ssh/ssh_host_ed25519_key.pub", "MESH_FORGE_HOST_FILE": "/etc/gitea/forge-host" } } @@ -554,11 +567,14 @@ "announced" ], "reads": [ - "mesh-controller.assignments", + "mesh-provisioner.plans", + "mesh-provisioner.provisions", "ssh-client.keys", "mesh-build-go.keys", "mesh-build-node.keys", "mesh-build-image.keys", - "mesh-delivery-checker.owed" + "mesh-delivery-checker.owed", + "mesh-delivery-indexer.modules", + "mesh-delivery-gate.source" ] } diff --git a/testdata/beside/mesh-catalog/modules/go/module.json.captured b/testdata/beside/mesh-catalog/modules/go/module.json.captured index 48a6bf57..9783eda4 100644 --- a/testdata/beside/mesh-catalog/modules/go/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/go/module.json.captured @@ -7,7 +7,10 @@ "provides": [ { "name": "go-toolchain", - "reach": "machine" + "reach": "machine", + "credential": { + "form": "none" + } } ], "resources": [ diff --git a/testdata/beside/mesh-catalog/modules/influxdb/module.json.captured b/testdata/beside/mesh-catalog/modules/influxdb/module.json.captured index 53389a5f..13a7905f 100644 --- a/testdata/beside/mesh-catalog/modules/influxdb/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/influxdb/module.json.captured @@ -7,6 +7,9 @@ "scope": "mesh", "identity": { "in": "an InfluxDB v1 authorization" + }, + "credential": { + "form": "pair" } } ], diff --git a/testdata/beside/mesh-catalog/modules/kernel/files/modules-load.conf b/testdata/beside/mesh-catalog/modules/kernel/files/modules-load.conf new file mode 100644 index 00000000..eccad6ce --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/kernel/files/modules-load.conf @@ -0,0 +1,4 @@ +# Written by the mesh's kernel module (systemd-modules-load reads it at boot). +# tun: buildah's network for a RUN line (pasta) and every other user-space tunnel open /dev/net/tun. +# Loaded at boot, so an upgrade that removes the running kernel's modules cannot keep it out later. +tun diff --git a/testdata/beside/mesh-catalog/modules/kernel/module.json.captured b/testdata/beside/mesh-catalog/modules/kernel/module.json.captured index 2a63563e..8fc90d09 100644 --- a/testdata/beside/mesh-catalog/modules/kernel/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/kernel/module.json.captured @@ -100,6 +100,25 @@ "id": "linux-lts-headers", "type": "package", "package": "linux-lts-headers" + }, + { + "id": "modules-load", + "type": "file", + "path": "/etc/modules-load.d/mesh-kernel.conf", + "mode": "0644", + "template": "files/modules-load.conf" + }, + { + "id": "modules-keep", + "type": "package", + "package": "kernel-modules-hook" + }, + { + "id": "modules-cleanup", + "type": "service", + "unit": "linux-modules-cleanup.service", + "state": "stopped", + "boot": "enabled" } ], "build": { diff --git a/testdata/beside/mesh-catalog/modules/keycloak/module.json.captured b/testdata/beside/mesh-catalog/modules/keycloak/module.json.captured index 847d5f4f..84c08f10 100644 --- a/testdata/beside/mesh-catalog/modules/keycloak/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/keycloak/module.json.captured @@ -9,6 +9,7 @@ "provides": [ { "name": "oidc-client", + "credential": {"form": "pair"}, "scope": "mesh", "identity": { "max": 255, @@ -240,6 +241,7 @@ "bound" ], "reads": [ - "mesh-controller.assignments" + "mesh-provisioner.plans", + "mesh-provisioner.provisions" ] } diff --git a/testdata/beside/mesh-catalog/modules/mailu/module.json.captured b/testdata/beside/mesh-catalog/modules/mailu/module.json.captured index 93d72adb..0faccc58 100644 --- a/testdata/beside/mesh-catalog/modules/mailu/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mailu/module.json.captured @@ -665,7 +665,14 @@ "path": "${dir:mesh-state}/config.json", "mode": "0600", "template": "files/config.json", - "takes-config": ["domain", "proxy-address", "sitename", "website", "url", "container"], + "takes-config": [ + "domain", + "proxy-address", + "sitename", + "website", + "url", + "container" + ], "merge": "json" }, { @@ -758,6 +765,9 @@ "identity": { "max": 64, "in": "a mailbox's local part" + }, + "credential": { + "form": "pair" } } ], diff --git a/testdata/beside/mesh-catalog/modules/mesh-build-go/files/settings.json b/testdata/beside/mesh-catalog/modules/mesh-build-go/files/settings.json index 3175fe39..3b65dd1c 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-build-go/files/settings.json +++ b/testdata/beside/mesh-catalog/modules/mesh-build-go/files/settings.json @@ -1,3 +1,5 @@ { - "capacity": "shanks=3,ace=2,g14=0,novox=0" + "capacity": 0, + "cores-per-step": 6, + "build-hours": "" } diff --git a/testdata/beside/mesh-catalog/modules/mesh-build-go/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-build-go/module.json.captured index 4d2b080a..88fbba9b 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-build-go/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-build-go/module.json.captured @@ -10,7 +10,7 @@ "state": [ "toolchains", "keys", - "steps" + {"name": "steps", "ttl-seconds": 172800}, "pauses" ], "claims": [ { @@ -20,14 +20,27 @@ } ], "reads": [ - "mesh-delivery-checker.kept" + "mesh-delivery-checker.kept", + "mesh-delivery-indexer.manifests" ], "config": { "capacity": { + "type": "number", + "description": "How many asks of the Go build seat this module runs at once on this node (novox/hq ADR 0525 §5); 0 pulls nothing. It is set per node in the mesh's configuration; a node with no value builds nothing.", + "minimum": 0, + "default": 0 + }, + "build-hours": { "type": "string", - "description": "How many asks of the Go build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.", - "pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", - "default": "shanks=3,ace=2,g14=0,novox=0" + "description": "When this node may take this module's build asks: one window HH:MM-HH:MM in the node's local time (an end before its start crosses midnight, 18:00-06:00). Outside it the node takes no new step and finishes the ones it runs; an empty value builds at any hour. It is set per node in the mesh's configuration.", + "pattern": "|([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]", + "default": "" + }, + "cores-per-step": { + "type": "number", + "description": "How many processors one Go step of this module needs (a full check of a large repository takes what it is given: three at about 4 cores each ran past the 30-minute step bound on 2026-10-26). The capacity above is an upper bound: on each node the module runs at most floor(the cores of its build slice's CPU quota / this) steps at once, the quota read live from its cgroup (issue 1806), so a lowered build CPU share never starves its steps. Above 0.", + "minimum": 0.1, + "default": 6 } }, "tools": [ @@ -38,6 +51,7 @@ "build_go_release", "build_go_step", "build_go_steps", + "build_go_step_end", "build_go_toolchains", "build_go_status", "build_go_health" @@ -105,7 +119,7 @@ "path": "${dir:state}/settings.json", "mode": "0600", "owner": "mesh-build-go", - "takes-config": ["capacity"], + "takes-config": ["capacity", "cores-per-step", "build-hours"], "merge": "json", "template": "files/settings.json" }, diff --git a/testdata/beside/mesh-catalog/modules/mesh-build-image/files/settings.json b/testdata/beside/mesh-catalog/modules/mesh-build-image/files/settings.json index 3175fe39..4a445c34 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-build-image/files/settings.json +++ b/testdata/beside/mesh-catalog/modules/mesh-build-image/files/settings.json @@ -1,3 +1,5 @@ { - "capacity": "shanks=3,ace=2,g14=0,novox=0" + "capacity": 0, + "cores-per-step": 2, + "build-hours": "" } diff --git a/testdata/beside/mesh-catalog/modules/mesh-build-image/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-build-image/module.json.captured index cfe8489f..54bc2fb0 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-build-image/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-build-image/module.json.captured @@ -9,7 +9,7 @@ ], "state": [ "keys", - "steps" + {"name": "steps", "ttl-seconds": 172800}, "pauses" ], "claims": [ { @@ -20,10 +20,22 @@ ], "config": { "capacity": { + "type": "number", + "description": "How many asks of the image build seat this module runs at once on this node (novox/hq ADR 0525 §5); 0 pulls nothing. It is set per node in the mesh's configuration; a node with no value builds nothing.", + "minimum": 0, + "default": 0 + }, + "build-hours": { "type": "string", - "description": "How many asks of the image build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.", - "pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", - "default": "shanks=3,ace=2,g14=0,novox=0" + "description": "When this node may take this module's build asks: one window HH:MM-HH:MM in the node's local time (an end before its start crosses midnight, 18:00-06:00). Outside it the node takes no new step and finishes the ones it runs; an empty value builds at any hour. It is set per node in the mesh's configuration.", + "pattern": "|([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]", + "default": "" + }, + "cores-per-step": { + "type": "number", + "description": "How many processors one image step of this module needs (buildah, whose RUN lines are mostly fetches). The capacity above is an upper bound: on each node the module runs at most floor(the cores of its build slice's CPU quota / this) steps at once, the quota read live from its cgroup (issue 1806), so a lowered build CPU share never starves its steps. Above 0.", + "minimum": 0.1, + "default": 2 } }, "tools": [ @@ -33,6 +45,7 @@ "build_image_release", "build_image_step", "build_image_steps", + "build_image_step_end", "build_image_status", "build_image_health" ], @@ -77,7 +90,7 @@ "path": "${dir:state}/settings.json", "mode": "0600", "owner": "mesh-build-image", - "takes-config": ["capacity"], + "takes-config": ["capacity", "cores-per-step", "build-hours"], "merge": "json", "template": "files/settings.json" }, diff --git a/testdata/beside/mesh-catalog/modules/mesh-build-node/files/settings.json b/testdata/beside/mesh-catalog/modules/mesh-build-node/files/settings.json index 3175fe39..4a445c34 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-build-node/files/settings.json +++ b/testdata/beside/mesh-catalog/modules/mesh-build-node/files/settings.json @@ -1,3 +1,5 @@ { - "capacity": "shanks=3,ace=2,g14=0,novox=0" + "capacity": 0, + "cores-per-step": 2, + "build-hours": "" } diff --git a/testdata/beside/mesh-catalog/modules/mesh-build-node/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-build-node/module.json.captured index 25f858eb..25f8611b 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-build-node/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-build-node/module.json.captured @@ -10,7 +10,7 @@ "state": [ "toolchains", "keys", - "steps" + {"name": "steps", "ttl-seconds": 172800}, "pauses" ], "claims": [ { @@ -21,10 +21,22 @@ ], "config": { "capacity": { + "type": "number", + "description": "How many asks of the Node build seat this module runs at once on this node (novox/hq ADR 0525 §5); 0 pulls nothing. It is set per node in the mesh's configuration; a node with no value builds nothing.", + "minimum": 0, + "default": 0 + }, + "build-hours": { "type": "string", - "description": "How many asks of the Node build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.", - "pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", - "default": "shanks=3,ace=2,g14=0,novox=0" + "description": "When this node may take this module's build asks: one window HH:MM-HH:MM in the node's local time (an end before its start crosses midnight, 18:00-06:00). Outside it the node takes no new step and finishes the ones it runs; an empty value builds at any hour. It is set per node in the mesh's configuration.", + "pattern": "|([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]", + "default": "" + }, + "cores-per-step": { + "type": "number", + "description": "How many processors one Node step of this module needs (tsc, esbuild and a test runner; a median step takes 15 s). The capacity above is an upper bound: on each node the module runs at most floor(the cores of its build slice's CPU quota / this) steps at once, the quota read live from its cgroup (issue 1806), so a lowered build CPU share never starves its steps. Above 0.", + "minimum": 0.1, + "default": 2 } }, "tools": [ @@ -34,6 +46,7 @@ "build_node_release", "build_node_step", "build_node_steps", + "build_node_step_end", "build_node_toolchains", "build_node_status", "build_node_health" @@ -99,7 +112,7 @@ "path": "${dir:state}/settings.json", "mode": "0600", "owner": "mesh-build-node", - "takes-config": ["capacity"], + "takes-config": ["capacity", "cores-per-step", "build-hours"], "merge": "json", "template": "files/settings.json" }, diff --git a/testdata/beside/mesh-catalog/modules/mesh-catalog/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-catalog/module.json.captured index b8f45096..c5ad61a4 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-catalog/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-catalog/module.json.captured @@ -25,9 +25,7 @@ "secrets": { "postgres-database": "${dir:state}/database.secret" }, - "consumes": [ - "mesh-controller.built-before" - ], + "consumes": [], "emits": [ "registered", "upgraded", @@ -92,5 +90,12 @@ "from": "files" } ] - } + }, + "invokes": [ + "mesh-delivery-builder.builder_builds_since" + ], + "reads": [ + "mesh-delivery-builder.builds", + "mesh-delivery-indexer.manifests" + ] } diff --git a/testdata/beside/mesh-catalog/modules/mesh-conditions/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-conditions/module.json.captured new file mode 100644 index 00000000..d676b3cf --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/mesh-conditions/module.json.captured @@ -0,0 +1,87 @@ +{ + "module": "mesh-conditions", + "version": "1", + "runs-as": "mesh-conditions", + "claims": [ + { + "name": "mesh-conditions", + "scope": "mesh", + "serves": [ + "conditions", + "silence", + "compare", + "take-over", + "hand-back", + "resync" + ] + } + ], + "uses": [ + "operator-channel" + ], + "reads": [ + "mesh-delivery-watchman.findings", + "mesh-self-check.findings", + "mesh-healers.findings", + "mesh-delivery-assigner.findings", + "messenger.findings", + "mesh-provisioner.findings", + "artifact-store-tools.findings", + "mesh-healers.attempts", + "mesh-controller.conditions", + "mesh-controller.lease", + "mesh-delivery-assigner.wants", + "node-engine.have", + "mesh-delivery-assigner.placement" + ], + "state": [ + "conditions", + { + "name": "history", + "ttl-seconds": 7776000 + }, + "takeover", + "held", + { + "name": "bounds", + "key-ttl": true + }, + "asked" + ], + "own-secrets": { + "broker": "${dir:state}/broker" + }, + "secrets-owner": "mesh-conditions", + "resources": [ + { + "id": "account", + "type": "user", + "name": "mesh-conditions", + "shell": "/usr/bin/nologin", + "home": "/var/lib/mesh-conditions" + }, + { + "id": "state", + "type": "directory", + "mode": "0700", + "owner": "mesh-conditions", + "place": ".", + "unknown": "program" + } + ], + "build": { + "artifacts": [ + { + "name": "tools", + "kind": "bundle", + "language": "go", + "system": "arch", + "from": "cmd/mesh-conditions", + "binary": "mesh-conditions", + "loads": [ + "mesh-conditions" + ] + } + ] + } +} diff --git a/testdata/beside/mesh-catalog/modules/mesh-config/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-config/module.json.captured index f93497e1..9554db29 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-config/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-config/module.json.captured @@ -7,7 +7,10 @@ "name": "mesh-config", "scope": "mesh", "serves": [ - "config" + "config", + "compare", + "take-over", + "hand-back" ] } ], @@ -23,14 +26,30 @@ "gitea.forge.pull-request.closed" ], "invokes": [ + "seat:mesh-hand-acts.hand-act", + "seat:mesh-hand-acts.warranted", "seat:mesh-controller.hand-act", "seat:mesh-controller.warranted", "gitea.gitea_get_pull_request" ], "reads": [ - "mesh-controller.assignments", + "mesh-delivery-assigner.placement", + "mesh-delivery-assigner.wants", + "mesh-delivery-builder.builds", + "mesh-delivery-indexer.modules", + "mesh-delivery-indexer.manifests", + "mesh-controller.predefined", "mesh-controller.declared-config", - "mesh-controller.predefined" + "mesh-delivery-indexer.predefined", + "mesh-delivery-indexer.config-candidates", + "mesh-controller.lease" + ], + "tools": [ + "config_compare", + "config_take_over", + "config_hand_back", + "config_declared", + "config_resync" ], "state": [ "values", @@ -38,7 +57,9 @@ "predefined", "proposals", "pulls", - "kept" + "kept", + "declared", + "takeover" ], "logs": [ "history" diff --git a/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/files/settings.json b/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/files/settings.json index 848f5158..66c66f87 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/files/settings.json +++ b/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/files/settings.json @@ -1,4 +1,5 @@ { "refusal-stall-floor-minutes": 10, - "author-new-head-minutes": 120 + "author-new-head-minutes": 120, + "check-gates-merge": true } diff --git a/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/module.json.captured index 74da6dc0..ef5d1b80 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-delivery-gate/module.json.captured @@ -14,7 +14,8 @@ "table", "stalled", "recheck", - "close" + "close", + "settle" ] } ], @@ -26,13 +27,17 @@ "gitea.forge.pull-request.edited", "gitea.forge.pull-request.closed", "mesh-delivery-forge-bridge.forge.**", - "mesh-config.config-changed" + "mesh-config.config-changed", + "gitea.repo.writable" ], "emits": [ "transition", "group", "merged" ], + "uses": [ + "operator-channel" + ], "invokes": [ "gitea.gitea_commit_statuses", "gitea.gitea_contains", @@ -57,14 +62,23 @@ { "name": "source", "history": 64 + }, + "conditions", + { + "name": "turns", + "history": 5 } ], "reads": [ "mesh-delivery-teller.answers", - "mesh-delivery-checker.verdicts" + "mesh-delivery-checker.verdicts", + "mesh-delivery-view.reach" ], "tools": [ - "delivery_status" + "delivery_hand_now", + "delivery_merge_again", + "delivery_status", + "delivery_turns" ], "config": { "refusal-stall-floor-minutes": { @@ -82,6 +96,12 @@ "maximum": 10080, "trusted": false, "default": 120 + }, + "check-gates-merge": { + "type": "boolean", + "description": "Whether mesh/check gates the merge. True (the default): the operator's Approve is asked, and a merge handed, only on a passing verdict judged against the trunk's head, and a trunk's move puts a verdict out of date (ADR 0447, ADR 0505 §4). False (the operator, 2026-10-25 22:56: \"run the check locally only, but not as a gate, it loses us too much time\"; developers run the unit and integration levels locally before a pull request): a pull request reviewed at its head is ready, and its Approve is asked once the check at its heads (a group: of all its heads together) has a verdict, a pass or a failure said in the ask, or after 60 minutes with none, said so (the operator, 2026-10-27 23:54: \"if you don't want me to approve it, don't send the approval request\"); an approved merge waits for no verdict; no merge is refused for a check judged on an older trunk; a later verdict is shown on the pull request and the delivery, and asks a pending Approve again in its words; a group still merges all its members in one pass. Set it false together with the gitea module's check-gates-merge, which lets its merge tool merge an approved head without a passing mesh/check.", + "trusted": false, + "default": true } }, "resources": [ @@ -99,7 +119,8 @@ "mode": "0600", "takes-config": [ "refusal-stall-floor-minutes", - "author-new-head-minutes" + "author-new-head-minutes", + "check-gates-merge" ], "merge": "json", "template": "files/settings.json" diff --git a/testdata/beside/mesh-catalog/modules/mesh-hand-acts/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-hand-acts/module.json.captured new file mode 100644 index 00000000..96aff8b1 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/mesh-hand-acts/module.json.captured @@ -0,0 +1,65 @@ +{ + "module": "mesh-hand-acts", + "version": "1", + "runs-as": "mesh-hand-acts", + "claims": [ + { + "name": "mesh-hand-acts", + "scope": "mesh", + "serves": [ + "hand-act", + "hand-acts", + "warranted" + ] + } + ], + "reads": [ + "mesh-controller.hand-acts", + "messenger.asks" + ], + "logs": [ + { + "name": "acts", + "max-age-days": 400 + } + ], + "state": [ + "warrants" + ], + "own-secrets": { + "broker": "${dir:state}/broker" + }, + "secrets-owner": "mesh-hand-acts", + "resources": [ + { + "id": "account", + "type": "user", + "name": "mesh-hand-acts", + "shell": "/usr/bin/nologin", + "home": "/var/lib/mesh-hand-acts" + }, + { + "id": "state", + "type": "directory", + "mode": "0700", + "owner": "mesh-hand-acts", + "place": ".", + "unknown": "program" + } + ], + "build": { + "artifacts": [ + { + "name": "tools", + "kind": "bundle", + "language": "go", + "system": "arch", + "from": "cmd/mesh-hand-acts", + "binary": "mesh-hand-acts", + "loads": [ + "mesh-hand-acts" + ] + } + ] + } +} diff --git a/testdata/beside/mesh-catalog/modules/mesh-healers/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-healers/module.json.captured new file mode 100644 index 00000000..634dd388 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/mesh-healers/module.json.captured @@ -0,0 +1,82 @@ +{ + "module": "mesh-healers", + "version": "1", + "runs-as": "mesh-healers", + "claims": [ + { + "name": "mesh-healers", + "scope": "mesh", + "serves": [ + "healers", + "heal", + "drill" + ] + } + ], + "consumes": [ + "mesh-conditions.condition-raised", + "mesh-conditions.condition-changed", + "mesh-hand-acts.act-recorded" + ], + "invokes": [ + "mesh-grants.grants_assert", + "seat:mesh-hand-acts.hand-act", + "seat:mesh-controller.drill" + ], + "reads": [ + "mesh-controller.lease", + "mesh-conditions.conditions", + "mesh-controller.hand-acts" + ], + "state": [ + { + "name": "attempts", + "key-ttl": true + }, + { + "name": "would", + "key-ttl": true + }, + "findings", + { + "name": "wakes", + "key-ttl": true + } + ], + "own-secrets": { + "broker": "${dir:state}/broker" + }, + "secrets-owner": "mesh-healers", + "resources": [ + { + "id": "account", + "type": "user", + "name": "mesh-healers", + "shell": "/usr/bin/nologin", + "home": "/var/lib/mesh-healers" + }, + { + "id": "state", + "type": "directory", + "mode": "0700", + "owner": "mesh-healers", + "place": ".", + "unknown": "program" + } + ], + "build": { + "artifacts": [ + { + "name": "tools", + "kind": "bundle", + "language": "go", + "system": "arch", + "from": "cmd/mesh-healers", + "binary": "mesh-healers", + "loads": [ + "mesh-healers" + ] + } + ] + } +} diff --git a/testdata/beside/mesh-catalog/modules/mesh-issues/files/settings.json b/testdata/beside/mesh-catalog/modules/mesh-issues/files/settings.json index 0231a55d..265fe2da 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-issues/files/settings.json +++ b/testdata/beside/mesh-catalog/modules/mesh-issues/files/settings.json @@ -62,10 +62,13 @@ "mesh-catalog modules/mesh-build-node", "mesh-catalog modules/mesh-catalog", "mesh-catalog modules/mesh-cli", + "mesh-catalog modules/mesh-conditions", "mesh-catalog modules/mesh-config", "mesh-catalog modules/mesh-delivery", "mesh-catalog modules/mesh-delivery-gate", "mesh-catalog modules/mesh-files", + "mesh-catalog modules/mesh-hand-acts", + "mesh-catalog modules/mesh-healers", "mesh-catalog modules/mesh-issues", "mesh-catalog modules/mesh-knowledge", "mesh-catalog modules/mesh-mail", @@ -75,6 +78,7 @@ "mesh-catalog modules/mesh-provisioner", "mesh-catalog modules/mesh-provisioner-client", "mesh-catalog modules/mesh-reads", + "mesh-catalog modules/mesh-self-check", "mesh-catalog modules/mesh-vault", "mesh-catalog modules/mesh-watcher", "mesh-catalog modules/messenger", diff --git a/testdata/beside/mesh-catalog/modules/mesh-issues/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-issues/module.json.captured index ee14150a..196c586c 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-issues/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-issues/module.json.captured @@ -13,12 +13,17 @@ "move", "note", "link", + "milestones", + "milestone-set", + "milestone-move", "import", "groom", "set", "tracking", "reserve", - "fix-times" + "fix-times", + "learn-fixes", + "changes" ] } ], @@ -33,12 +38,17 @@ "move", "note", "link", + "milestones", + "milestone-set", + "milestone-move", "import", "groom", "set", "tracking", "reserve", - "fix-times" + "fix-times", + "learn-fixes", + "changes" ] } ], @@ -52,7 +62,9 @@ "triaged", "ready", "ready-to-verify", - "reserved" + "reserved", + "milestone-set", + "milestone-moved" ], "consumes": [ "mesh-issues.opened", @@ -66,6 +78,7 @@ "seat:operator-channel.notify", "gitea.gitea_pull_reviews", "gitea.gitea_contains", + "gitea.gitea_get_pull_request", "records.records_status", "records.records_list", "records.records_read", @@ -81,7 +94,11 @@ }, "counters", "grooming", - "reservations" + "reservations", + { + "name": "milestones", + "history": 5 + } ], "config": { "first-number": { @@ -157,10 +174,13 @@ "mesh-catalog modules/mesh-build-node", "mesh-catalog modules/mesh-catalog", "mesh-catalog modules/mesh-cli", + "mesh-catalog modules/mesh-conditions", "mesh-catalog modules/mesh-config", "mesh-catalog modules/mesh-delivery", "mesh-catalog modules/mesh-delivery-gate", "mesh-catalog modules/mesh-files", + "mesh-catalog modules/mesh-hand-acts", + "mesh-catalog modules/mesh-healers", "mesh-catalog modules/mesh-issues", "mesh-catalog modules/mesh-knowledge", "mesh-catalog modules/mesh-mail", @@ -170,6 +190,7 @@ "mesh-catalog modules/mesh-provisioner", "mesh-catalog modules/mesh-provisioner-client", "mesh-catalog modules/mesh-reads", + "mesh-catalog modules/mesh-self-check", "mesh-catalog modules/mesh-vault", "mesh-catalog modules/mesh-watcher", "mesh-catalog modules/messenger", diff --git a/testdata/beside/mesh-catalog/modules/mesh-knowledge/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-knowledge/module.json.captured index 92b15f2d..335a1a19 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-knowledge/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-knowledge/module.json.captured @@ -32,7 +32,8 @@ "propose", "import", "return", - "withdraw" + "withdraw", + "progress" ] } ], @@ -52,13 +53,15 @@ "propose", "import", "return", - "withdraw" + "withdraw", + "progress" ] } ], "tools": [ "knowledge_health", - "knowledge_highest" + "knowledge_highest", + "knowledge_marks" ], "emits": [ "reserved", @@ -81,7 +84,14 @@ "records.records_status", "records.records_list", "records.records_read", - "seat:operator-channel.notify" + "seat:operator-channel.notify", + "seat:issue-tracker.list", + "seat:issue-tracker.get", + "seat:issue-tracker.milestones", + "seat:issue-tracker.changes", + "seat:mesh-delivery.deliveries", + "seat:mesh-delivery.show", + "seat:mesh-delivery.groups" ], "config": { "judge-model": { diff --git a/testdata/beside/mesh-catalog/modules/mesh-provisioner/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-provisioner/module.json.captured index 650425b3..1aba09af 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-provisioner/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-provisioner/module.json.captured @@ -11,11 +11,36 @@ ] } ], + "uses": [ + "operator-channel" + ], + "reads": [ + "mesh-delivery-assigner.placement", + "mesh-delivery-assigner.wants", + "mesh-delivery-indexer.modules", + "mesh-delivery-indexer.manifests", + "mesh-delivery-builder.builds", + "node-engine.have", + "mesh-config.resolved", + "gitea.bound", + "keycloak.bound", + "postgres.bound", + "route-adapter.bound", + "mesh-vault.bound" + ], + "tools": [ + "provisioner_provisions", + "provisioner_plans", + "provisioner_compose", + "provisioner_decide" + ], "state": [ "registry", "needs", "applied", - "collect" + "collect", + "provisions", + "plans" ], "own-secrets": { "broker": "${dir:state}/broker" diff --git a/testdata/beside/mesh-catalog/modules/mesh-self-check/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-self-check/module.json.captured new file mode 100644 index 00000000..e4037e98 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/mesh-self-check/module.json.captured @@ -0,0 +1,87 @@ +{ + "module": "mesh-self-check", + "version": "1", + "runs-as": "mesh-self-check", + "claims": [ + { + "name": "mesh-self-check", + "scope": "mesh", + "serves": [ + "doctor", + "probes" + ] + } + ], + "invokes": [ + "nats.nats_streams", + "nats.nats_closed_connections", + "seat:mesh-watchdogs.signals", + "seat:node-intrusion-prevention.banned" + ], + "reads": [ + "mesh-delivery-assigner.placement", + "mesh-delivery-assigner.seat-holders", + "mesh-delivery-watchman.presence", + "mesh-host.health", + "disk-load.readings", + "restic.good", + "mesh-delivery-indexer.modules", + "mesh-delivery-indexer.manifests" + ], + "state": [ + "findings", + "verdict", + { + "name": "verdicts", + "ttl-seconds": 604800 + } + ], + "own-secrets": { + "broker": "${dir:state}/broker" + }, + "secrets-owner": "mesh-self-check", + "resources": [ + { + "id": "account", + "type": "user", + "name": "mesh-self-check", + "shell": "/usr/bin/nologin", + "home": "/var/lib/mesh-self-check" + }, + { + "id": "state", + "type": "directory", + "mode": "0700", + "owner": "mesh-self-check", + "place": ".", + "unknown": "program" + } + ], + "build": { + "artifacts": [ + { + "name": "tools", + "kind": "bundle", + "language": "go", + "system": "arch", + "from": "cmd/mesh-self-check", + "binary": "mesh-self-check", + "loads": [ + "mesh-self-check" + ] + } + ] + }, + "config": { + "probes": { + "type": "list", + "description": "The ids of the probes that run, each judging one thing of the mesh (mesh-self-check.probes lists them with what each asserts). A probe not listed runs nothing and raises nothing, and what it raised before is cleared. D8 and DA are in the code and off by default.", + "trusted": true, + "default": [ + "D13", + "D-fill" + ], + "runtime": true + } + } +} diff --git a/testdata/beside/mesh-catalog/modules/mesh-vault/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-vault/module.json.captured index 44944c95..efea0cb1 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-vault/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-vault/module.json.captured @@ -5,6 +5,9 @@ "provides": [ { "name": "secret", + "credential": { + "form": "pair" + }, "scope": "mesh", "identity": { "in": "a vault entry" @@ -14,10 +17,27 @@ "emits": [ "provisioned", "rotated", - "secret-changed" + "secret-changed", + "account-hashed", + "own-replaced", + "account-issued", + "signed" ], "reads": [ - "mesh-controller.assignments" + "node-engine.have", + "mesh-grants.accounts", + "mesh-controller.lease", + "mesh-delivery-assigner.placement", + "mesh-provisioner.plans", + "mesh-provisioner.provisions" + ], + "state": [ + { + "name": "bus-accounts", + "history": 2 + }, + "signers", + "bound" ], "receives": { "secret": "${dir:grants}/mesh.json" @@ -135,7 +155,8 @@ "MESH_VAULT_LEDGER": "${dir:ledger}", "MESH_VAULT_ROOT": "${dir:root}", "MESH_VAULT_STORE": "${dir:store}", - "MESH_VAULT_STATE": "${dir:state}" + "MESH_VAULT_STATE": "${dir:state}", + "MESH_VAULT_SECRET_ASK_CALLERS": "node-tools" } } ] @@ -145,6 +166,10 @@ "secret_holders", "secret_verify", "secret_handed", + "secret_own", + "account_hash", + "account_adopt", + "account_retire", "secret_asks", "secret_vault_export", "provisioner_retirement", @@ -152,7 +177,12 @@ "provisioner_retire_reject", "provisioner_delete", "vault_status", - "vault_health" + "vault_health", + "own_drift", + "own_take_switch", + "vault_signers", + "vault_signed", + "signers_reassert" ], "claims": [ { @@ -166,7 +196,10 @@ "rotate", "move-in", "node-key", - "move-asks" + "move-asks", + "own", + "issue-account", + "sign" ] } ] diff --git a/testdata/beside/mesh-catalog/modules/mesh-watcher/module.json.captured b/testdata/beside/mesh-catalog/modules/mesh-watcher/module.json.captured index e9543a5d..ac05e105 100644 --- a/testdata/beside/mesh-catalog/modules/mesh-watcher/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mesh-watcher/module.json.captured @@ -3,11 +3,15 @@ "version": "1", "slug": "watch", "consumes": [ - "mesh-controller.doctor-heartbeat" + "mesh-controller.doctor-heartbeat", + "mesh-self-check.checked" + ], + "reads": [ + "mesh-conditions.takeover", + "mesh-delivery-assigner.seat-holders" ], "invokes": [ - "mesh-watcher.watcher_ping", - "seat:mesh-controller.seats" + "mesh-watcher.watcher_ping" ], "own-secrets": { "telegram-token": "${dir:state}/telegram-token" @@ -39,7 +43,9 @@ "type": "file", "path": "${dir:state}/settings.json", "mode": "0600", - "takes-config": ["telegram-chat-id"], + "takes-config": [ + "telegram-chat-id" + ], "merge": "json", "template": "files/settings.json" } diff --git a/testdata/beside/mesh-catalog/modules/messenger/module.json.captured b/testdata/beside/mesh-catalog/modules/messenger/module.json.captured index 4aceaebe..eec860f0 100644 --- a/testdata/beside/mesh-catalog/modules/messenger/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/messenger/module.json.captured @@ -77,6 +77,9 @@ "mesh-controller.condition-raised", "mesh-controller.condition-changed", "mesh-controller.condition-cleared", + "mesh-conditions.condition-raised", + "mesh-conditions.condition-changed", + "mesh-conditions.condition-cleared", "mesh-delivery-view.moving", "mesh-delivery-view.settled", "mesh-delivery-view.stuck", @@ -90,9 +93,13 @@ ], "invokes": [ "seat:mesh-controller.conditions", - "seat:mesh-controller.root-free", + "seat:mesh-conditions.conditions", + "seat:node-engine.root-free", "seat:issue-tracker.tracking" ], + "reads": [ + "mesh-conditions.takeover" + ], "state": [ "open", "sent", diff --git a/testdata/beside/mesh-catalog/modules/minio/module.json.captured b/testdata/beside/mesh-catalog/modules/minio/module.json.captured index 01a966af..60b40a4b 100644 --- a/testdata/beside/mesh-catalog/modules/minio/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/minio/module.json.captured @@ -12,6 +12,9 @@ "identity": { "max": 20, "in": "an S3 access key" + }, + "credential": { + "form": "pair" } } ], @@ -36,8 +39,8 @@ ] } ], - "invokes": [ - "seat:mesh-controller.seats" + "reads": [ + "mesh-delivery-assigner.seat-holders" ], "contributes": { "route": { diff --git a/testdata/beside/mesh-catalog/modules/mongodb/module.json.captured b/testdata/beside/mesh-catalog/modules/mongodb/module.json.captured index cbb12681..fc7d5212 100644 --- a/testdata/beside/mesh-catalog/modules/mongodb/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mongodb/module.json.captured @@ -12,6 +12,9 @@ "identity": { "max": 63, "in": "a MongoDB database name" + }, + "credential": { + "form": "pair" } } ], diff --git a/testdata/beside/mesh-catalog/modules/mosquitto/module.json.captured b/testdata/beside/mesh-catalog/modules/mosquitto/module.json.captured index 21058e0a..a7213d44 100644 --- a/testdata/beside/mesh-catalog/modules/mosquitto/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mosquitto/module.json.captured @@ -8,6 +8,9 @@ "scope": "mesh", "identity": { "in": "a Mosquitto client and topic prefix" + }, + "credential": { + "form": "pair" } } ], diff --git a/testdata/beside/mesh-catalog/modules/mounts/module.json.captured b/testdata/beside/mesh-catalog/modules/mounts/module.json.captured index 488ebb97..dc1e3785 100644 --- a/testdata/beside/mesh-catalog/modules/mounts/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mounts/module.json.captured @@ -5,11 +5,9 @@ "nfs-share" ], "reads": [ + "disk-load.readings", "nfs-server.exports" ], - "invokes": [ - "seat:mesh-controller.data" - ], "capabilities": [ "package-manager", "service-manager" @@ -38,20 +36,20 @@ "per-machine": true } ], - "config": { - "shares": { - "type": "string", - "description": "The shares of another machine this machine mounts: space-separated name=mountpoint, each optionally :ro, or none (novox/hq ADR 0263 rule 5).", - "trusted": true, - "default": "none" - }, - "sources": { - "type": "string", - "description": "The occasional sources this machine mounts: space-separated name=smb://[@]/@ or name=disk:@, each optionally :ro, or none (novox/hq ADR 0263 rule 6); an SMB source's username is in its entry, its password the secret smb-password- (novox/hq ADR 0283).", - "trusted": true, - "default": "none" - } + "config": { + "shares": { + "type": "string", + "description": "The shares of another machine this machine mounts: space-separated name=mountpoint, each optionally :ro, or none (novox/hq ADR 0263 rule 5).", + "trusted": true, + "default": "none" }, + "sources": { + "type": "string", + "description": "The occasional sources this machine mounts: space-separated name=smb://[@]/@ or name=disk:@, each optionally :ro, or none (novox/hq ADR 0263 rule 6); an SMB source's username is in its entry, its password the secret smb-password- (novox/hq ADR 0283).", + "trusted": true, + "default": "none" + } + }, "own-secrets": { "smb-password-*": { "path": "${dir:state}/smb-password-*.secret", diff --git a/testdata/beside/mesh-catalog/modules/mssql/module.json.captured b/testdata/beside/mesh-catalog/modules/mssql/module.json.captured index ed4e4488..35cdbe6e 100644 --- a/testdata/beside/mesh-catalog/modules/mssql/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/mssql/module.json.captured @@ -12,6 +12,9 @@ "identity": { "max": 128, "in": "a SQL Server login and database name" + }, + "credential": { + "form": "pair" } } ], diff --git a/testdata/beside/mesh-catalog/modules/nats/module.json.captured b/testdata/beside/mesh-catalog/modules/nats/module.json.captured index 106aac92..d2d02efe 100644 --- a/testdata/beside/mesh-catalog/modules/nats/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/nats/module.json.captured @@ -8,7 +8,10 @@ "provides": [ { "name": "mesh-bus", - "scope": "mesh" + "scope": "mesh", + "credential": { + "form": "none" + } } ], "claims": [ @@ -19,12 +22,16 @@ ], "bus-users": "/var/lib/nats-module/conf/accounts.conf", "own-secrets": { - "broker": "${dir:mesh-state}/broker" + "broker": "${dir:mesh-state}/broker", + "broker-nats-tools": "${dir:tools-secret}/broker-nats-tools" }, "capabilities": [ "container-runtime" ], - "emits": [], + "emits": [ + "consumer-removed", + "bucket-keys-removed" + ], "consumes": [], "listens": [ { @@ -45,7 +52,9 @@ "nats_buckets", "nats_users", "nats_user_can", - "nats_snapshot_verify" + "nats_snapshot_verify", + "nats_consumer_remove", + "nats_bucket_remove" ], "guards": [ 8222 @@ -86,6 +95,13 @@ "mode": "0700", "unknown": "program" }, + { + "id": "tools-secret", + "type": "directory", + "path": "/var/lib/mesh/nats-tools", + "mode": "0711", + "unknown": "program" + }, { "id": "snapshots", "type": "directory", @@ -190,11 +206,18 @@ "loads": [ "nats-tools" ], + "bus": { + "removes": [ + "DEAD_LETTER_NOTICES.controller", + "mesh-host_acts" + ] + }, "env": { "MESH_NATS_MONITOR": "http://127.0.0.1:8222", "MESH_NATS_CONTAINER": "mesh-broker-nats", "MESH_NATS_USERS_FILE": "/etc/nats/accounts.conf", - "MESH_NATS_SNAPSHOTS_DIR": "${dir:snapshots}" + "MESH_NATS_SNAPSHOTS_DIR": "${dir:snapshots}", + "NATS_TOOLS_CREDENTIAL": "${dir:tools-secret}/broker-nats-tools" } }, { diff --git a/testdata/beside/mesh-catalog/modules/nextcloud/module.json.captured b/testdata/beside/mesh-catalog/modules/nextcloud/module.json.captured index dd28417f..5d9f2ddc 100644 --- a/testdata/beside/mesh-catalog/modules/nextcloud/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/nextcloud/module.json.captured @@ -72,7 +72,7 @@ }, "user": { "type": "string", - "description": "The Nextcloud account the module's tools act as; empty is admin.", + "description": "The Nextcloud account the module's tools act as; empty is mesh-admin, the account the module makes and keeps for itself. Any other account is used as given and never repaired.", "trusted": true, "default": "" } diff --git a/testdata/beside/mesh-catalog/modules/nfs-server/module.json.captured b/testdata/beside/mesh-catalog/modules/nfs-server/module.json.captured index f9994d16..c7082176 100644 --- a/testdata/beside/mesh-catalog/modules/nfs-server/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/nfs-server/module.json.captured @@ -13,7 +13,10 @@ { "name": "nfs-share", "scope": "mesh", - "identity": false + "identity": false, + "credential": { + "form": "none" + } } ], "data": { @@ -45,26 +48,24 @@ } ], "reads": [ + "disk-load.readings", + "mesh-delivery-assigner.seat-holders", "mounts.shares" ], - "invokes": [ - "seat:mesh-controller.seats", - "seat:mesh-controller.data" - ], - "config": { - "shares": { - "type": "string", - "description": "The folders this machine exports, as space-separated name=path; the operator's, with no default (novox/hq ADR 0263).", - "trusted": true, - "required": true - }, - "grants": { - "type": "string", - "description": "Which nodes may mount which share, and how: share=node:rw|ro[,node:rw|ro], entries separated by spaces, or none to export to no node whatever a node wants (novox/hq ADR 0263).", - "trusted": true, - "default": "none" - } + "config": { + "shares": { + "type": "string", + "description": "The folders this machine exports, as space-separated name=path; the operator's, with no default (novox/hq ADR 0263).", + "trusted": true, + "required": true }, + "grants": { + "type": "string", + "description": "Which nodes may mount which share, and how: share=node:rw|ro[,node:rw|ro], entries separated by spaces, or none to export to no node whatever a node wants (novox/hq ADR 0263).", + "trusted": true, + "default": "none" + } + }, "facts": { "machines": { "path": "/etc/nfs-server/machines", diff --git a/testdata/beside/mesh-catalog/modules/nodejs/module.json.captured b/testdata/beside/mesh-catalog/modules/nodejs/module.json.captured index c3573444..2f4a049a 100644 --- a/testdata/beside/mesh-catalog/modules/nodejs/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/nodejs/module.json.captured @@ -7,7 +7,10 @@ "provides": [ { "name": "node-toolchain", - "reach": "machine" + "reach": "machine", + "credential": { + "form": "none" + } } ], "resources": [ diff --git a/testdata/beside/mesh-catalog/modules/ollama/module.json.captured b/testdata/beside/mesh-catalog/modules/ollama/module.json.captured index 2d5fb48c..312ef6bf 100644 --- a/testdata/beside/mesh-catalog/modules/ollama/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/ollama/module.json.captured @@ -7,7 +7,10 @@ "provides": [ { "name": "model-access", - "scope": "node" + "scope": "node", + "credential": { + "form": "none" + } } ], "listens": [ diff --git a/testdata/beside/mesh-catalog/modules/postgres/module.json.captured b/testdata/beside/mesh-catalog/modules/postgres/module.json.captured index e8a59069..8f9f0579 100644 --- a/testdata/beside/mesh-catalog/modules/postgres/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/postgres/module.json.captured @@ -9,6 +9,7 @@ "provides": [ { "name": "postgres-database", + "credential": {"form": "pair"}, "scope": "mesh", "identity": { "max": 63, @@ -195,6 +196,7 @@ "bound" ], "reads": [ - "mesh-controller.assignments" + "mesh-provisioner.plans", + "mesh-provisioner.provisions" ] } diff --git a/testdata/beside/mesh-catalog/modules/power/module.json.captured b/testdata/beside/mesh-catalog/modules/power/module.json.captured index a110d410..909499ad 100644 --- a/testdata/beside/mesh-catalog/modules/power/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/power/module.json.captured @@ -314,6 +314,48 @@ "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", diff --git a/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/halt.target.d/mesh-power.conf b/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/halt.target.d/mesh-power.conf new file mode 100644 index 00000000..569d7fd9 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/halt.target.d/mesh-power.conf @@ -0,0 +1,8 @@ +# Written by the mesh (module power, novox/hq ADR 0211; issue 1126). Replaced on every push. +# +# A shutdown is bounded at ten minutes, not systemd's thirty. A unit started again during the shutdown +# (on ace, 2026-10-28, the bus and the authorization manager) is never stopped a second time, and the +# shutdown then waits on this job's timeout with nothing on the console. The target keeps its own +# forcing action: the last phase still ends every process, unmounts and syncs. +[Unit] +JobTimeoutSec=10min diff --git a/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/poweroff.target.d/mesh-power.conf b/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/poweroff.target.d/mesh-power.conf new file mode 100644 index 00000000..569d7fd9 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/poweroff.target.d/mesh-power.conf @@ -0,0 +1,8 @@ +# Written by the mesh (module power, novox/hq ADR 0211; issue 1126). Replaced on every push. +# +# A shutdown is bounded at ten minutes, not systemd's thirty. A unit started again during the shutdown +# (on ace, 2026-10-28, the bus and the authorization manager) is never stopped a second time, and the +# shutdown then waits on this job's timeout with nothing on the console. The target keeps its own +# forcing action: the last phase still ends every process, unmounts and syncs. +[Unit] +JobTimeoutSec=10min diff --git a/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/reboot.target.d/mesh-power.conf b/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/reboot.target.d/mesh-power.conf new file mode 100644 index 00000000..569d7fd9 --- /dev/null +++ b/testdata/beside/mesh-catalog/modules/power/templates/systemd/system/reboot.target.d/mesh-power.conf @@ -0,0 +1,8 @@ +# Written by the mesh (module power, novox/hq ADR 0211; issue 1126). Replaced on every push. +# +# A shutdown is bounded at ten minutes, not systemd's thirty. A unit started again during the shutdown +# (on ace, 2026-10-28, the bus and the authorization manager) is never stopped a second time, and the +# shutdown then waits on this job's timeout with nothing on the console. The target keeps its own +# forcing action: the last phase still ends every process, unmounts and syncs. +[Unit] +JobTimeoutSec=10min diff --git a/testdata/beside/mesh-catalog/modules/redis/module.json.captured b/testdata/beside/mesh-catalog/modules/redis/module.json.captured index 58e11064..4cc6c4e5 100644 --- a/testdata/beside/mesh-catalog/modules/redis/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/redis/module.json.captured @@ -7,6 +7,9 @@ "scope": "mesh", "identity": { "in": "a Redis ACL user and key prefix" + }, + "credential": { + "form": "pair" } } ], diff --git a/testdata/beside/mesh-catalog/modules/restic/module.json.captured b/testdata/beside/mesh-catalog/modules/restic/module.json.captured index ea5ec8b9..05927fe2 100644 --- a/testdata/beside/mesh-catalog/modules/restic/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/restic/module.json.captured @@ -41,40 +41,40 @@ } ] }, - "config": { - "keep-daily": { - "type": "number", - "description": "How many nightly restore points are kept, a count from 1 to 9999: two weeks undo a mistake noticed within a fortnight. Read by the night, which thins nothing while it does not read (novox/hq issue 416); lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.", - "minimum": 1, - "maximum": 9999, - "trusted": true, - "default": 14 - }, - "keep-weekly": { - "type": "number", - "description": "How many weekly restore points are kept behind the dailies, a count from 1 to 9999, refused by the night like keep-daily; lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.", - "minimum": 1, - "maximum": 9999, - "trusted": true, - "default": 8 - }, - "keep-monthly": { - "type": "number", - "description": "How many monthly restore points are kept behind the weeklies, a count from 1 to 9999, refused by the night like keep-daily; lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.", - "minimum": 1, - "maximum": 9999, - "trusted": true, - "default": 6 - }, - "keep-hand-taken-days": { - "type": "number", - "description": "How many days a restore point taken by hand (node-backup.now, or the mesh's before a module moves) is kept at least, a count from 1 to 9999; it is kept until a later good night of its module exists, and this many days in any case, so the next thinning never takes the one taken against a risky change (novox/hq issue 453). Lowering it lets go of the older ones taken by hand at the next thinning, and that cannot be undone.", - "minimum": 1, - "maximum": 9999, - "trusted": true, - "default": 7 - } + "config": { + "keep-daily": { + "type": "number", + "description": "How many nightly restore points are kept, a count from 1 to 9999: two weeks undo a mistake noticed within a fortnight. Read by the night, which thins nothing while it does not read (novox/hq issue 416); lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.", + "minimum": 1, + "maximum": 9999, + "trusted": true, + "default": 14 }, + "keep-weekly": { + "type": "number", + "description": "How many weekly restore points are kept behind the dailies, a count from 1 to 9999, refused by the night like keep-daily; lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.", + "minimum": 1, + "maximum": 9999, + "trusted": true, + "default": 8 + }, + "keep-monthly": { + "type": "number", + "description": "How many monthly restore points are kept behind the weeklies, a count from 1 to 9999, refused by the night like keep-daily; lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.", + "minimum": 1, + "maximum": 9999, + "trusted": true, + "default": 6 + }, + "keep-hand-taken-days": { + "type": "number", + "description": "How many days a restore point taken by hand (node-backup.now, or the mesh's before a module moves) is kept at least, a count from 1 to 9999; it is kept until a later good night of its module exists, and this many days in any case, so the next thinning never takes the one taken against a risky change (novox/hq issue 453). Lowering it lets go of the older ones taken by hand at the next thinning, and that cannot be undone.", + "minimum": 1, + "maximum": 9999, + "trusted": true, + "default": 7 + } + }, "resources": [ { "id": "state", @@ -139,22 +139,17 @@ "MESH_BACKUP_DATA": "${dir:state}/data.conf" }, "health": { - "kind": "tool", - "tool": "backup_health", - "interval": "60s", - "timeout": "20s", - "looks": 2, - "grace": "60s" + "kind": "unit" } }, { - "id": "measure", + "id": "awake", "type": "process", - "name": "restic-measure", + "name": "restic-awake", "artifact": "tools", "run": [ "./restic-backups", - "measure" + "awake" ], "user": "${machine:account}", "env": { @@ -165,7 +160,12 @@ "MESH_BACKUP_DATA": "${dir:state}/data.conf" }, "health": { - "kind": "unit" + "kind": "tool", + "tool": "backup_health", + "interval": "60s", + "timeout": "20s", + "looks": 2, + "grace": "60s" } } ], @@ -195,5 +195,11 @@ "from": "files" } ] - } + }, + "reads": [ + "disk-load.readings" + ], + "state": [ + "good" + ] } diff --git a/testdata/beside/mesh-catalog/modules/route-adapter/module.json.captured b/testdata/beside/mesh-catalog/modules/route-adapter/module.json.captured index 5234c78b..2827462c 100644 --- a/testdata/beside/mesh-catalog/modules/route-adapter/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/route-adapter/module.json.captured @@ -9,6 +9,7 @@ "provides": [ { "name": "route", + "credential": {"form": "none"}, "scope": "mesh", "identity": false } @@ -129,6 +130,7 @@ "bound" ], "reads": [ - "mesh-controller.assignments" + "mesh-provisioner.plans", + "mesh-provisioner.provisions" ] } diff --git a/testdata/beside/mesh-catalog/modules/showcase/module.json.captured b/testdata/beside/mesh-catalog/modules/showcase/module.json.captured index e657e2ab..6b0db672 100644 --- a/testdata/beside/mesh-catalog/modules/showcase/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/showcase/module.json.captured @@ -9,7 +9,10 @@ "provides": [ { "name": "greeting", - "scope": "mesh" + "scope": "mesh", + "credential": { + "form": "none" + } } ], "serves": { diff --git a/testdata/beside/mesh-catalog/modules/step-ca/module.json.captured b/testdata/beside/mesh-catalog/modules/step-ca/module.json.captured index b11d2b99..3f44ead7 100644 --- a/testdata/beside/mesh-catalog/modules/step-ca/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/step-ca/module.json.captured @@ -7,7 +7,10 @@ "provides": [ { "name": "internal-acme-ca", - "scope": "mesh" + "scope": "mesh", + "credential": { + "form": "none" + } } ], "serves": { diff --git a/testdata/beside/mesh-catalog/modules/systemd-resolved/module.json.captured b/testdata/beside/mesh-catalog/modules/systemd-resolved/module.json.captured index b850afcf..24b3bc85 100644 --- a/testdata/beside/mesh-catalog/modules/systemd-resolved/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/systemd-resolved/module.json.captured @@ -8,7 +8,10 @@ "provides": [ { "name": "split-dns", - "reach": "machine" + "reach": "machine", + "credential": { + "form": "none" + } } ], "requires": [ diff --git a/testdata/beside/mesh-catalog/modules/umami/module.json.captured b/testdata/beside/mesh-catalog/modules/umami/module.json.captured index 22ead419..e3b9aa0d 100644 --- a/testdata/beside/mesh-catalog/modules/umami/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/umami/module.json.captured @@ -32,7 +32,10 @@ "provides": [ { "name": "analytics", - "scope": "mesh" + "scope": "mesh", + "credential": { + "form": "none" + } } ], "serves": { diff --git a/testdata/beside/mesh-catalog/modules/xorg/module.json.captured b/testdata/beside/mesh-catalog/modules/xorg/module.json.captured index 7b0b6b1e..1685dcb4 100644 --- a/testdata/beside/mesh-catalog/modules/xorg/module.json.captured +++ b/testdata/beside/mesh-catalog/modules/xorg/module.json.captured @@ -9,7 +9,10 @@ "provides": [ { "name": "x11-display", - "reach": "machine" + "reach": "machine", + "credential": { + "form": "none" + } } ], "claims": [