Compare commits

..
68 changed files with 197 additions and 987 deletions
+2 -8
View File
@@ -56,15 +56,9 @@ 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 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.
// The real catalogue passes the command, the way it passes the test that used to be the only check.
func TestModuleCheckPassesTheCatalogue(t *testing.T) {
root := filepath.Join(beside.CapturedDir(t, "mesh-catalog"), "modules")
root := beside.Catalogue(t)
paths, err := manifestsUnder(root)
if err != nil || len(paths) == 0 {
t.Fatalf("no manifests under %s: %v", root, err)
+2 -2
View File
@@ -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"},"credential":{"form":"pair"}}],
"provides":[{"name":"s3-bucket","scope":"mesh","identity":{"max":20,"in":"an S3 access key"}}],
"receives":{"s3-bucket":"/var/lib/mesh/objects/mesh.json"}}`)
resolver := write("resolver", `{"module":"resolver","version":"1",
"provides":[{"name":"wildcard-resolution","scope":"mesh","identity":false,"credential":{"form":"none"}}]}`)
"provides":[{"name":"wildcard-resolution","scope":"mesh","identity":false}]}`)
album := write("photoalbum", `{"module":"photoalbum","version":"1","requires":["s3-bucket"]}`)
nm := write("networkmanager", `{"module":"networkmanager","version":"1","requires":["wildcard-resolution"]}`)
+2 -7
View File
@@ -182,9 +182,6 @@ 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}}
}
@@ -219,10 +216,8 @@ 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,
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}}}}
Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh}}}
other := catalogue.Manifest{Module: "mesh-vault", Version: "1", Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh}}}
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)
}
+1 -1
View File
@@ -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.20261030001116-2da0de75c3f0
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde
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
+6 -2
View File
@@ -1,7 +1,11 @@
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.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.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.20261030095816-530d8d3007dc h1:Wdjl2gomooHswJ5qpkwZPq/ureZgXVw6FMCsgzPJhPo=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095816-530d8d3007dc/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde h1:eKXSllHCS31GgMazfvZ7BeiLoXIUibnUmzxWVkj+Vcw=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde/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=
+2 -11
View File
@@ -53,16 +53,6 @@ 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)
@@ -91,7 +81,8 @@ func CapturedDir(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)", repository)
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)
return dir
}
+2 -3
View File
@@ -127,10 +127,9 @@ var (
// begin.
var PlacementKinds = []string{"dir", "access", "port", "seat", "machine", "backup"}
// credentialForms are the forms a provider's offer may declare for its credential (issue 768); "none" is an offer with
// no credential, which the release's catalogue states on every such offer (issue 774).
// credentialForms are the forms a provider's offer may declare for its credential (issue 768).
var credentialForms = []string{catalogue.CredentialFormShared, catalogue.CredentialFormPair,
catalogue.CredentialFormIssued, catalogue.CredentialFormGiven, catalogue.CredentialFormNone}
catalogue.CredentialFormIssued, catalogue.CredentialFormGiven}
var (
digestShape = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
+2 -4
View File
@@ -316,16 +316,14 @@ func TestAPlanNamesTheCredentialFormOfEachProvidersOffer(t *testing.T) {
p := aPlan()
p.Provisions = []ProvisionedCredential{
{Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres", CredentialForm: "pair"},
{Provision: "sonarr-api", Provider: "ace", ProviderModule: "sonarr", CredentialForm: "shared"},
{Provision: "artifact-store", Provider: "novox", ProviderModule: "distribution", CredentialForm: "none"}}
{Provision: "sonarr-api", Provider: "ace", ProviderModule: "sonarr", CredentialForm: "shared"}}
values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p})
if err != nil {
t.Fatal(err)
}
if want := `{"provision":"postgres-database","provider":"novox","provider-module":"postgres",` +
`"credential-form":"pair"},{"provision":"sonarr-api","provider":"ace","provider-module":"sonarr",` +
`"credential-form":"shared"},{"provision":"artifact-store","provider":"novox","provider-module":"distribution",` +
`"credential-form":"none"}`; !strings.Contains(string(values["g14.dunst"]), want) {
`"credential-form":"shared"}`; !strings.Contains(string(values["g14.dunst"]), want) {
t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want)
}
bad := aPlan()
+2 -2
View File
@@ -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 f3a57dcc56fb6c198095cafd11d0decff9f720c4 modules/*/module.json, modules/*/files, modules/*/templates, predefined.json
mesh-catalog 3a1017f5594217092a654dd1e2d72226766bc16a modules/*/module.json, modules/*/files, modules/*/templates, predefined.json
mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
mesh-sdk 2da0de75c3f0087cff9c2f5ef789ca6dc3c59045 conformance/events/module-event.json
mesh-sdk c2ec8b6b2fdece8dd76a9d2d1f29d3ae4579f174 conformance/events/module-event.json
To move them, from this repository's root, with the two repositories checked out beside it:
@@ -1 +0,0 @@
${seat:mesh-artifact-store:reach}
@@ -1,52 +1,26 @@
{
"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-hand-acts.hand-act",
"seat:mesh-controller.artifacts",
"seat:mesh-controller.collect",
"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",
@@ -62,14 +36,6 @@
"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": {
@@ -86,8 +52,7 @@
],
"env": {
"MESH_ARTIFACT_STORE_CONTAINER": "mesh-registry",
"MESH_ARTIFACT_STORE_NAMES_FILE": "${dir:state}/store-names",
"MESH_ARTIFACT_STORE_ADDRESS_FILE": "${dir:state}/store-address"
"MESH_ARTIFACT_STORE_NAMES_FILE": "${dir:state}/store-names"
}
},
{
@@ -8,10 +8,7 @@
"provides": [
{
"name": "rootless-image-build",
"reach": "machine",
"credential": {
"form": "none"
}
"reach": "machine"
}
],
"resources": [
@@ -7,9 +7,7 @@
"disk_filesystems",
"disk_folders",
"disk_devices",
"cpu_top",
"disk_readings",
"disk_measure"
"cpu_top"
],
"build": {
"artifacts": [
@@ -25,16 +23,5 @@
]
}
]
},
"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"
]
}
}
@@ -4,10 +4,7 @@
"provides": [
{
"name": "artifact-store",
"scope": "mesh",
"credential": {
"form": "none"
}
"scope": "mesh"
}
],
"claims": [
@@ -1,15 +1,14 @@
# 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: 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).
# 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.
[Unit]
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=0
[Service]
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'
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'
Restart=on-failure
RestartSec=10s
@@ -9,10 +9,7 @@
{
"name": "wildcard-resolution",
"scope": "mesh",
"identity": false,
"credential": {
"form": "none"
}
"identity": false
}
],
"requires": [
@@ -7,7 +7,7 @@
"privileged"
],
"invokes": [
"seat:mesh-artifact-sweep.images"
"seat:mesh-controller.images"
],
"claims": [
{
@@ -40,7 +40,6 @@
],
"emits": [
"repo.created",
"repo.writable",
"issue.opened",
"pull.merged",
"pull.updated",
@@ -182,12 +181,6 @@
"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.",
@@ -266,8 +259,7 @@
"env": {
"DB_TYPE": "postgres",
"USER_UID": "1000",
"USER_GID": "1000",
"SSH_MAX_STARTUPS": "100:30:200"
"USER_GID": "1000"
},
"env-file": [
"${dir:state}/server.env"
@@ -324,8 +316,7 @@
"takes-config": [
"url",
"keep-main-rules",
"main-mergers",
"check-gates-merge"
"main-mergers"
]
},
{
@@ -400,7 +391,6 @@
"provides": [
{
"name": "npm-package-registry",
"credential": {"form": "pair"},
"scope": "mesh",
"identity": {
"max": 40,
@@ -409,7 +399,6 @@
},
{
"name": "git",
"credential": {"form": "none"},
"scope": "mesh"
},
{
@@ -422,7 +411,6 @@
},
{
"name": "forge-account",
"credential": {"form": "none"},
"scope": "mesh"
}
],
@@ -549,7 +537,6 @@
"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"
}
}
@@ -567,14 +554,11 @@
"announced"
],
"reads": [
"mesh-provisioner.plans",
"mesh-provisioner.provisions",
"mesh-controller.assignments",
"ssh-client.keys",
"mesh-build-go.keys",
"mesh-build-node.keys",
"mesh-build-image.keys",
"mesh-delivery-checker.owed",
"mesh-delivery-indexer.modules",
"mesh-delivery-gate.source"
"mesh-delivery-checker.owed"
]
}
@@ -7,10 +7,7 @@
"provides": [
{
"name": "go-toolchain",
"reach": "machine",
"credential": {
"form": "none"
}
"reach": "machine"
}
],
"resources": [
@@ -7,9 +7,6 @@
"scope": "mesh",
"identity": {
"in": "an InfluxDB v1 authorization"
},
"credential": {
"form": "pair"
}
}
],
@@ -1,4 +0,0 @@
# 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
@@ -100,25 +100,6 @@
"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": {
@@ -9,7 +9,6 @@
"provides": [
{
"name": "oidc-client",
"credential": {"form": "pair"},
"scope": "mesh",
"identity": {
"max": 255,
@@ -241,7 +240,6 @@
"bound"
],
"reads": [
"mesh-provisioner.plans",
"mesh-provisioner.provisions"
"mesh-controller.assignments"
]
}
@@ -665,14 +665,7 @@
"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"
},
{
@@ -765,9 +758,6 @@
"identity": {
"max": 64,
"in": "a mailbox's local part"
},
"credential": {
"form": "pair"
}
}
],
@@ -1,5 +1,3 @@
{
"capacity": 0,
"cores-per-step": 6,
"build-hours": ""
"capacity": "shanks=3,ace=2,g14=0,novox=0"
}
@@ -10,7 +10,7 @@
"state": [
"toolchains",
"keys",
{"name": "steps", "ttl-seconds": 172800}, "pauses"
"steps"
],
"claims": [
{
@@ -20,27 +20,14 @@
}
],
"reads": [
"mesh-delivery-checker.kept",
"mesh-delivery-indexer.manifests"
"mesh-delivery-checker.kept"
],
"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": "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
"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"
}
},
"tools": [
@@ -51,7 +38,6 @@
"build_go_release",
"build_go_step",
"build_go_steps",
"build_go_step_end",
"build_go_toolchains",
"build_go_status",
"build_go_health"
@@ -119,7 +105,7 @@
"path": "${dir:state}/settings.json",
"mode": "0600",
"owner": "mesh-build-go",
"takes-config": ["capacity", "cores-per-step", "build-hours"],
"takes-config": ["capacity"],
"merge": "json",
"template": "files/settings.json"
},
@@ -1,5 +1,3 @@
{
"capacity": 0,
"cores-per-step": 2,
"build-hours": ""
"capacity": "shanks=3,ace=2,g14=0,novox=0"
}
@@ -9,7 +9,7 @@
],
"state": [
"keys",
{"name": "steps", "ttl-seconds": 172800}, "pauses"
"steps"
],
"claims": [
{
@@ -20,22 +20,10 @@
],
"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": "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
"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"
}
},
"tools": [
@@ -45,7 +33,6 @@
"build_image_release",
"build_image_step",
"build_image_steps",
"build_image_step_end",
"build_image_status",
"build_image_health"
],
@@ -90,7 +77,7 @@
"path": "${dir:state}/settings.json",
"mode": "0600",
"owner": "mesh-build-image",
"takes-config": ["capacity", "cores-per-step", "build-hours"],
"takes-config": ["capacity"],
"merge": "json",
"template": "files/settings.json"
},
@@ -1,5 +1,3 @@
{
"capacity": 0,
"cores-per-step": 2,
"build-hours": ""
"capacity": "shanks=3,ace=2,g14=0,novox=0"
}
@@ -10,7 +10,7 @@
"state": [
"toolchains",
"keys",
{"name": "steps", "ttl-seconds": 172800}, "pauses"
"steps"
],
"claims": [
{
@@ -21,22 +21,10 @@
],
"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": "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
"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"
}
},
"tools": [
@@ -46,7 +34,6 @@
"build_node_release",
"build_node_step",
"build_node_steps",
"build_node_step_end",
"build_node_toolchains",
"build_node_status",
"build_node_health"
@@ -112,7 +99,7 @@
"path": "${dir:state}/settings.json",
"mode": "0600",
"owner": "mesh-build-node",
"takes-config": ["capacity", "cores-per-step", "build-hours"],
"takes-config": ["capacity"],
"merge": "json",
"template": "files/settings.json"
},
@@ -25,7 +25,9 @@
"secrets": {
"postgres-database": "${dir:state}/database.secret"
},
"consumes": [],
"consumes": [
"mesh-controller.built-before"
],
"emits": [
"registered",
"upgraded",
@@ -90,12 +92,5 @@
"from": "files"
}
]
},
"invokes": [
"mesh-delivery-builder.builder_builds_since"
],
"reads": [
"mesh-delivery-builder.builds",
"mesh-delivery-indexer.manifests"
]
}
}
@@ -1,87 +0,0 @@
{
"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"
]
}
]
}
}
@@ -7,10 +7,7 @@
"name": "mesh-config",
"scope": "mesh",
"serves": [
"config",
"compare",
"take-over",
"hand-back"
"config"
]
}
],
@@ -26,30 +23,14 @@
"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-delivery-assigner.placement",
"mesh-delivery-assigner.wants",
"mesh-delivery-builder.builds",
"mesh-delivery-indexer.modules",
"mesh-delivery-indexer.manifests",
"mesh-controller.predefined",
"mesh-controller.assignments",
"mesh-controller.declared-config",
"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"
"mesh-controller.predefined"
],
"state": [
"values",
@@ -57,9 +38,7 @@
"predefined",
"proposals",
"pulls",
"kept",
"declared",
"takeover"
"kept"
],
"logs": [
"history"
@@ -1,5 +1,4 @@
{
"refusal-stall-floor-minutes": 10,
"author-new-head-minutes": 120,
"check-gates-merge": true
"author-new-head-minutes": 120
}
@@ -14,8 +14,7 @@
"table",
"stalled",
"recheck",
"close",
"settle"
"close"
]
}
],
@@ -27,17 +26,13 @@
"gitea.forge.pull-request.edited",
"gitea.forge.pull-request.closed",
"mesh-delivery-forge-bridge.forge.**",
"mesh-config.config-changed",
"gitea.repo.writable"
"mesh-config.config-changed"
],
"emits": [
"transition",
"group",
"merged"
],
"uses": [
"operator-channel"
],
"invokes": [
"gitea.gitea_commit_statuses",
"gitea.gitea_contains",
@@ -62,23 +57,14 @@
{
"name": "source",
"history": 64
},
"conditions",
{
"name": "turns",
"history": 5
}
],
"reads": [
"mesh-delivery-teller.answers",
"mesh-delivery-checker.verdicts",
"mesh-delivery-view.reach"
"mesh-delivery-checker.verdicts"
],
"tools": [
"delivery_hand_now",
"delivery_merge_again",
"delivery_status",
"delivery_turns"
"delivery_status"
],
"config": {
"refusal-stall-floor-minutes": {
@@ -96,12 +82,6 @@
"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": [
@@ -119,8 +99,7 @@
"mode": "0600",
"takes-config": [
"refusal-stall-floor-minutes",
"author-new-head-minutes",
"check-gates-merge"
"author-new-head-minutes"
],
"merge": "json",
"template": "files/settings.json"
@@ -1,65 +0,0 @@
{
"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"
]
}
]
}
}
@@ -1,82 +0,0 @@
{
"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"
]
}
]
}
}
@@ -62,13 +62,10 @@
"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",
@@ -78,7 +75,6 @@
"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",
@@ -13,17 +13,12 @@
"move",
"note",
"link",
"milestones",
"milestone-set",
"milestone-move",
"import",
"groom",
"set",
"tracking",
"reserve",
"fix-times",
"learn-fixes",
"changes"
"fix-times"
]
}
],
@@ -38,17 +33,12 @@
"move",
"note",
"link",
"milestones",
"milestone-set",
"milestone-move",
"import",
"groom",
"set",
"tracking",
"reserve",
"fix-times",
"learn-fixes",
"changes"
"fix-times"
]
}
],
@@ -62,9 +52,7 @@
"triaged",
"ready",
"ready-to-verify",
"reserved",
"milestone-set",
"milestone-moved"
"reserved"
],
"consumes": [
"mesh-issues.opened",
@@ -78,7 +66,6 @@
"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",
@@ -94,11 +81,7 @@
},
"counters",
"grooming",
"reservations",
{
"name": "milestones",
"history": 5
}
"reservations"
],
"config": {
"first-number": {
@@ -174,13 +157,10 @@
"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",
@@ -190,7 +170,6 @@
"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",
@@ -32,8 +32,7 @@
"propose",
"import",
"return",
"withdraw",
"progress"
"withdraw"
]
}
],
@@ -53,15 +52,13 @@
"propose",
"import",
"return",
"withdraw",
"progress"
"withdraw"
]
}
],
"tools": [
"knowledge_health",
"knowledge_highest",
"knowledge_marks"
"knowledge_highest"
],
"emits": [
"reserved",
@@ -84,14 +81,7 @@
"records.records_status",
"records.records_list",
"records.records_read",
"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"
"seat:operator-channel.notify"
],
"config": {
"judge-model": {
@@ -11,36 +11,11 @@
]
}
],
"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",
"provisions",
"plans"
"collect"
],
"own-secrets": {
"broker": "${dir:state}/broker"
@@ -1,87 +0,0 @@
{
"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
}
}
}
@@ -5,9 +5,6 @@
"provides": [
{
"name": "secret",
"credential": {
"form": "pair"
},
"scope": "mesh",
"identity": {
"in": "a vault entry"
@@ -17,27 +14,10 @@
"emits": [
"provisioned",
"rotated",
"secret-changed",
"account-hashed",
"own-replaced",
"account-issued",
"signed"
"secret-changed"
],
"reads": [
"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"
"mesh-controller.assignments"
],
"receives": {
"secret": "${dir:grants}/mesh.json"
@@ -155,8 +135,7 @@
"MESH_VAULT_LEDGER": "${dir:ledger}",
"MESH_VAULT_ROOT": "${dir:root}",
"MESH_VAULT_STORE": "${dir:store}",
"MESH_VAULT_STATE": "${dir:state}",
"MESH_VAULT_SECRET_ASK_CALLERS": "node-tools"
"MESH_VAULT_STATE": "${dir:state}"
}
}
]
@@ -166,10 +145,6 @@
"secret_holders",
"secret_verify",
"secret_handed",
"secret_own",
"account_hash",
"account_adopt",
"account_retire",
"secret_asks",
"secret_vault_export",
"provisioner_retirement",
@@ -177,12 +152,7 @@
"provisioner_retire_reject",
"provisioner_delete",
"vault_status",
"vault_health",
"own_drift",
"own_take_switch",
"vault_signers",
"vault_signed",
"signers_reassert"
"vault_health"
],
"claims": [
{
@@ -196,10 +166,7 @@
"rotate",
"move-in",
"node-key",
"move-asks",
"own",
"issue-account",
"sign"
"move-asks"
]
}
]
@@ -3,15 +3,11 @@
"version": "1",
"slug": "watch",
"consumes": [
"mesh-controller.doctor-heartbeat",
"mesh-self-check.checked"
],
"reads": [
"mesh-conditions.takeover",
"mesh-delivery-assigner.seat-holders"
"mesh-controller.doctor-heartbeat"
],
"invokes": [
"mesh-watcher.watcher_ping"
"mesh-watcher.watcher_ping",
"seat:mesh-controller.seats"
],
"own-secrets": {
"telegram-token": "${dir:state}/telegram-token"
@@ -43,9 +39,7 @@
"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"
}
@@ -77,9 +77,6 @@
"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",
@@ -93,13 +90,9 @@
],
"invokes": [
"seat:mesh-controller.conditions",
"seat:mesh-conditions.conditions",
"seat:node-engine.root-free",
"seat:mesh-controller.root-free",
"seat:issue-tracker.tracking"
],
"reads": [
"mesh-conditions.takeover"
],
"state": [
"open",
"sent",
@@ -12,9 +12,6 @@
"identity": {
"max": 20,
"in": "an S3 access key"
},
"credential": {
"form": "pair"
}
}
],
@@ -39,8 +36,8 @@
]
}
],
"reads": [
"mesh-delivery-assigner.seat-holders"
"invokes": [
"seat:mesh-controller.seats"
],
"contributes": {
"route": {
@@ -12,9 +12,6 @@
"identity": {
"max": 63,
"in": "a MongoDB database name"
},
"credential": {
"form": "pair"
}
}
],
@@ -8,9 +8,6 @@
"scope": "mesh",
"identity": {
"in": "a Mosquitto client and topic prefix"
},
"credential": {
"form": "pair"
}
}
],
@@ -5,9 +5,11 @@
"nfs-share"
],
"reads": [
"disk-load.readings",
"nfs-server.exports"
],
"invokes": [
"seat:mesh-controller.data"
],
"capabilities": [
"package-manager",
"service-manager"
@@ -36,20 +38,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"
"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://[<user>@]<host>/<share>@<mountpoint> or name=disk:<uuid>@<mountpoint>, 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-<name> (novox/hq ADR 0283).",
"trusted": true,
"default": "none"
}
},
"sources": {
"type": "string",
"description": "The occasional sources this machine mounts: space-separated name=smb://[<user>@]<host>/<share>@<mountpoint> or name=disk:<uuid>@<mountpoint>, 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-<name> (novox/hq ADR 0283).",
"trusted": true,
"default": "none"
}
},
"own-secrets": {
"smb-password-*": {
"path": "${dir:state}/smb-password-*.secret",
@@ -12,9 +12,6 @@
"identity": {
"max": 128,
"in": "a SQL Server login and database name"
},
"credential": {
"form": "pair"
}
}
],
@@ -8,10 +8,7 @@
"provides": [
{
"name": "mesh-bus",
"scope": "mesh",
"credential": {
"form": "none"
}
"scope": "mesh"
}
],
"claims": [
@@ -22,16 +19,12 @@
],
"bus-users": "/var/lib/nats-module/conf/accounts.conf",
"own-secrets": {
"broker": "${dir:mesh-state}/broker",
"broker-nats-tools": "${dir:tools-secret}/broker-nats-tools"
"broker": "${dir:mesh-state}/broker"
},
"capabilities": [
"container-runtime"
],
"emits": [
"consumer-removed",
"bucket-keys-removed"
],
"emits": [],
"consumes": [],
"listens": [
{
@@ -52,9 +45,7 @@
"nats_buckets",
"nats_users",
"nats_user_can",
"nats_snapshot_verify",
"nats_consumer_remove",
"nats_bucket_remove"
"nats_snapshot_verify"
],
"guards": [
8222
@@ -95,13 +86,6 @@
"mode": "0700",
"unknown": "program"
},
{
"id": "tools-secret",
"type": "directory",
"path": "/var/lib/mesh/nats-tools",
"mode": "0711",
"unknown": "program"
},
{
"id": "snapshots",
"type": "directory",
@@ -206,18 +190,11 @@
"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}",
"NATS_TOOLS_CREDENTIAL": "${dir:tools-secret}/broker-nats-tools"
"MESH_NATS_SNAPSHOTS_DIR": "${dir:snapshots}"
}
},
{
@@ -72,7 +72,7 @@
},
"user": {
"type": "string",
"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.",
"description": "The Nextcloud account the module's tools act as; empty is admin.",
"trusted": true,
"default": ""
}
@@ -13,10 +13,7 @@
{
"name": "nfs-share",
"scope": "mesh",
"identity": false,
"credential": {
"form": "none"
}
"identity": false
}
],
"data": {
@@ -48,24 +45,26 @@
}
],
"reads": [
"disk-load.readings",
"mesh-delivery-assigner.seat-holders",
"mounts.shares"
],
"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
"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"
}
},
"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",
@@ -7,10 +7,7 @@
"provides": [
{
"name": "node-toolchain",
"reach": "machine",
"credential": {
"form": "none"
}
"reach": "machine"
}
],
"resources": [
@@ -7,10 +7,7 @@
"provides": [
{
"name": "model-access",
"scope": "node",
"credential": {
"form": "none"
}
"scope": "node"
}
],
"listens": [
@@ -9,7 +9,6 @@
"provides": [
{
"name": "postgres-database",
"credential": {"form": "pair"},
"scope": "mesh",
"identity": {
"max": 63,
@@ -196,7 +195,6 @@
"bound"
],
"reads": [
"mesh-provisioner.plans",
"mesh-provisioner.provisions"
"mesh-controller.assignments"
]
}
@@ -314,48 +314,6 @@
"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",
@@ -1,8 +0,0 @@
# 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
@@ -1,8 +0,0 @@
# 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
@@ -1,8 +0,0 @@
# 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
@@ -7,9 +7,6 @@
"scope": "mesh",
"identity": {
"in": "a Redis ACL user and key prefix"
},
"credential": {
"form": "pair"
}
}
],
@@ -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
"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
}
},
"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,17 +139,22 @@
"MESH_BACKUP_DATA": "${dir:state}/data.conf"
},
"health": {
"kind": "unit"
"kind": "tool",
"tool": "backup_health",
"interval": "60s",
"timeout": "20s",
"looks": 2,
"grace": "60s"
}
},
{
"id": "awake",
"id": "measure",
"type": "process",
"name": "restic-awake",
"name": "restic-measure",
"artifact": "tools",
"run": [
"./restic-backups",
"awake"
"measure"
],
"user": "${machine:account}",
"env": {
@@ -160,12 +165,7 @@
"MESH_BACKUP_DATA": "${dir:state}/data.conf"
},
"health": {
"kind": "tool",
"tool": "backup_health",
"interval": "60s",
"timeout": "20s",
"looks": 2,
"grace": "60s"
"kind": "unit"
}
}
],
@@ -195,11 +195,5 @@
"from": "files"
}
]
},
"reads": [
"disk-load.readings"
],
"state": [
"good"
]
}
}
@@ -9,7 +9,6 @@
"provides": [
{
"name": "route",
"credential": {"form": "none"},
"scope": "mesh",
"identity": false
}
@@ -130,7 +129,6 @@
"bound"
],
"reads": [
"mesh-provisioner.plans",
"mesh-provisioner.provisions"
"mesh-controller.assignments"
]
}
@@ -9,10 +9,7 @@
"provides": [
{
"name": "greeting",
"scope": "mesh",
"credential": {
"form": "none"
}
"scope": "mesh"
}
],
"serves": {
@@ -7,10 +7,7 @@
"provides": [
{
"name": "internal-acme-ca",
"scope": "mesh",
"credential": {
"form": "none"
}
"scope": "mesh"
}
],
"serves": {
@@ -8,10 +8,7 @@
"provides": [
{
"name": "split-dns",
"reach": "machine",
"credential": {
"form": "none"
}
"reach": "machine"
}
],
"requires": [
@@ -32,10 +32,7 @@
"provides": [
{
"name": "analytics",
"scope": "mesh",
"credential": {
"form": "none"
}
"scope": "mesh"
}
],
"serves": {
@@ -9,10 +9,7 @@
"provides": [
{
"name": "x11-display",
"reach": "machine",
"credential": {
"form": "none"
}
"reach": "machine"
}
],
"claims": [