Compare commits

..
Author SHA1 Message Date
jschoubben a4426190b0 Merge pull request 'Accept the credential form none in a provisioner's plan, so design 47's release delivers (issue 774)' (#483) from fix/774-none-form into main
Reviewed-on: #483
2026-10-30 11:23:41 +00:00
jschoubben 2154ea8e3c Accept the credential form none in a provisioner's plan, so the release's catalogue, which states it on every offer without a credential, no longer has every plan refused and every module held (issue 774)
mesh/check checking 2154ea8e3c4e on a4426190b0c7
mesh/delivery merged: source.main moved to novox/mesh-controller@a4426190b0c7
2026-10-30 12:18:36 +01:00
mesh-tools 812c528479 Merge pull request 'Pin the release's mesh-sdk in the controller, so it reads design 47's catalog until its lease is handed away (issue 774)' (#482) from feat/774-l3-reland into main 2026-10-30 11:01:57 +00:00
jschoubben 8b32e04e2f Judge the controller's module check against the catalogue it is released with, captured at the group's catalogue head f3a57dcc, so a group that brings a word the trunk's catalogue lacks does not fail on the trunk (issue 774)
mesh/check checking 8b32e04e2f8b on 6fe9efa71219 | novox/mesh-controller#482 in group feat/774-l3-reland
mesh/delivery merged: source.main moved to novox/mesh-controller@812c528479e1
mesh/delivery-group group feat/774-l3-reland merged: every member is merged
The release's fourth check failed TestModuleCheckPassesTheCatalogue with 27 problems. The checker clones the catalogue
beside a controller's tests at its trunk, even in a group holding a catalogue pull request (mesh-delivery checker
internal/check/beside.go besideFor, ADR 0484 clause 4). The release's sdk refuses an offer stating no credential form,
and the trunk's catalogue states none until the release's catalogue merges.

- Quick fix (not chosen): recapture alone, which the test never reads while MESH_CHECK_BESIDE is set.
- Chosen: the test reads the captured copy, now at the group's catalogue f3a57dcc, through beside.CapturedDir. Every other
  beside test still reads the clone. The check itself is unchanged: every rule, every manifest. Moving CAPTURED to the
  catalogue a change is released with is part of that change.

Run as the checker runs it (catalogue at main, mesh-host at main, mesh-sdk at the pin, mesh-controller's trunk
beside): the whole unit and integration suite passes, 1706 tests, 8 skipped. The test also passes with the catalogue at
f3a57dcc beside, and with nothing beside.
2026-10-30 10:38:29 +01:00
jschoubben 79f44386d2 Merge main (delivery 1, the parse-only sdk) into L3's re-land, and pin mesh-sdk 2da0de7, the release's final head (issue 774)
mesh/check failed: unit: github.com/novox/mesh-controller/cmd/mesh-controller.TestModuleCheckPassesTheCatalogue: the catalogue does not pass its own c…
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (8bad7efb3e7a): unit: github.com/novox/me…
mesh/delivery closed: a newer head of the same pull request
The conflicts were go.mod, go.sum and testdata/beside/CAPTURED alone, taken from the release. 2da0de7 is 6e4a301's code;
its conformance events are unchanged.
2026-10-30 01:13:41 +01:00
jschoubben 80df1b6361 Pin mesh-sdk 6e4a301, the release's final sdk (issue 774)
The sdk's conformance events are unchanged at 6e4a301 and captured so. The whole integration suite passes at this head
against a local store and bus: 1706 tests, 8 skipped for a registry, a container, a refusing bus or a lab's run.
2026-10-30 00:20:10 +01:00
jschoubben 816cf96459 Pin mesh-sdk 007b6fd, the release's final sdk, so the controller reads the release's manifests and composes as the group does (issue 774)
The sdk now refuses an offer that says no credential form (6b8c694, ADR 0570). So:
- the catalogue copy in testdata/beside is captured again at the release branch's catalogue 8ae8e88d, whose offers each
  say one, and the sdk's conformance events at 007b6fd, as CAPTURED asks;
- the fixtures of identity_bound_test and issued_test say the form their rows meant: pair for a login of each
  consumer's own, none for a keyless provision. No behaviour of the controller changed.
2026-10-30 00:07:04 +01:00
jschoubben 3f05df5d1a Pin mesh-sdk 2e5cc4f, so the controller reads the release's new manifest words (issue 774)
The controller on dbcaa29 parses manifests strictly and refuses `bus` on an artifact and `max-age-days` on a log, both
in the re-land group's catalogue. The SDK's conformance events are captured again at the pinned commit, as
testdata/beside/CAPTURED asks; they are unchanged.
2026-10-29 22:19:39 +01:00
68 changed files with 987 additions and 197 deletions
+8 -2
View File
@@ -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) { func TestModuleCheckPassesTheCatalogue(t *testing.T) {
root := beside.Catalogue(t) root := filepath.Join(beside.CapturedDir(t, "mesh-catalog"), "modules")
paths, err := manifestsUnder(root) paths, err := manifestsUnder(root)
if err != nil || len(paths) == 0 { if err != nil || len(paths) == 0 {
t.Fatalf("no manifests under %s: %v", root, err) t.Fatalf("no manifests under %s: %v", root, err)
+2 -2
View File
@@ -23,10 +23,10 @@ func TestModuleCheckRefusesAnIdentityThatOverflowsWhatItRequires(t *testing.T) {
return p return p
} }
objects := write("objects", `{"module":"objects","version":"1", 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"}}`) "receives":{"s3-bucket":"/var/lib/mesh/objects/mesh.json"}}`)
resolver := write("resolver", `{"module":"resolver","version":"1", 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"]}`) album := write("photoalbum", `{"module":"photoalbum","version":"1","requires":["s3-bucket"]}`)
nm := write("networkmanager", `{"module":"networkmanager","version":"1","requires":["wildcard-resolution"]}`) nm := write("networkmanager", `{"module":"networkmanager","version":"1","requires":["wildcard-resolution"]}`)
+7 -2
View File
@@ -182,6 +182,9 @@ func TestANeedIsIssuedToItsHolderOnlyWhereTheProviderIsMovedAndItsSoftwareIssues
offer := catalogue.Offer{Name: "gitea-read", Scope: catalogue.ScopeMesh} offer := catalogue.Offer{Name: "gitea-read", Scope: catalogue.ScopeMesh}
if issued { if issued {
offer.Credential = &catalogue.OfferCredential{IssuedBy: catalogue.IssuedBySoftware, Tool: "gitea_agent_token"} 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}} 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. // marked moved; another module is not.
func TestTheModuleHoldingTheVaultSeatCountsAsMovedForItsConsumers(t *testing.T) { func TestTheModuleHoldingTheVaultSeatCountsAsMovedForItsConsumers(t *testing.T) {
vault := catalogue.Manifest{Module: "mesh-vault", Version: "1", Claims: []catalogue.Claim{{Name: "mesh-vault", Scope: catalogue.ScopeMesh}}, 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}}} Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh,
other := catalogue.Manifest{Module: "mesh-vault", Version: "1", 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 { 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) 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 go 1.26.0
require ( require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0
github.com/jackc/pgx/v5 v5.10.0 github.com/jackc/pgx/v5 v5.10.0
github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0 github.com/nats-io/nats.go v1.54.0
+2 -6
View File
@@ -1,11 +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 h1:E5stfIeCN6uYgA9TCv+4K8JgaURg3zQppvUgJLaDMP4=
git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE= 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.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA=
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/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= 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= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+11 -2
View File
@@ -53,6 +53,16 @@ func Dir(t testing.TB, repository string) string {
t.Logf("judged against %s as cloned beside this check", dir) t.Logf("judged against %s as cloned beside this check", dir)
return 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) from := filepath.Join(Captured(), repository)
if _, err := os.Stat(from); err != nil { if _, err := os.Stat(from); err != nil {
t.Fatalf("no captured copy of %s at %s: %v", repository, from, err) 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 { if err != nil {
t.Fatalf("the captured copy of %s could not be laid out: %v", repository, err) 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 "+ t.Logf("judged against the copy of %s captured in testdata/beside (see CAPTURED)", repository)
"judges it against the repository's own clone", repository)
return dir return dir
} }
+3 -2
View File
@@ -127,9 +127,10 @@ var (
// begin. // begin.
var PlacementKinds = []string{"dir", "access", "port", "seat", "machine", "backup"} var PlacementKinds = []string{"dir", "access", "port", "seat", "machine", "backup"}
// credentialForms are the forms a provider's offer may declare for its credential (issue 768). // 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).
var credentialForms = []string{catalogue.CredentialFormShared, catalogue.CredentialFormPair, var credentialForms = []string{catalogue.CredentialFormShared, catalogue.CredentialFormPair,
catalogue.CredentialFormIssued, catalogue.CredentialFormGiven} catalogue.CredentialFormIssued, catalogue.CredentialFormGiven, catalogue.CredentialFormNone}
var ( var (
digestShape = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) digestShape = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
+4 -2
View File
@@ -316,14 +316,16 @@ func TestAPlanNamesTheCredentialFormOfEachProvidersOffer(t *testing.T) {
p := aPlan() p := aPlan()
p.Provisions = []ProvisionedCredential{ p.Provisions = []ProvisionedCredential{
{Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres", CredentialForm: "pair"}, {Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres", CredentialForm: "pair"},
{Provision: "sonarr-api", Provider: "ace", ProviderModule: "sonarr", CredentialForm: "shared"}} {Provision: "sonarr-api", Provider: "ace", ProviderModule: "sonarr", CredentialForm: "shared"},
{Provision: "artifact-store", Provider: "novox", ProviderModule: "distribution", CredentialForm: "none"}}
values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p}) values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if want := `{"provision":"postgres-database","provider":"novox","provider-module":"postgres",` + if want := `{"provision":"postgres-database","provider":"novox","provider-module":"postgres",` +
`"credential-form":"pair"},{"provision":"sonarr-api","provider":"ace","provider-module":"sonarr",` + `"credential-form":"pair"},{"provision":"sonarr-api","provider":"ace","provider-module":"sonarr",` +
`"credential-form":"shared"}`; !strings.Contains(string(values["g14.dunst"]), want) { `"credential-form":"shared"},{"provision":"artifact-store","provider":"novox","provider-module":"distribution",` +
`"credential-form":"none"}`; !strings.Contains(string(values["g14.dunst"]), want) {
t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want) t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want)
} }
bad := aPlan() 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 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. 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-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
mesh-sdk c2ec8b6b2fdece8dd76a9d2d1f29d3ae4579f174 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: To move them, from this repository's root, with the two repositories checked out beside it:
@@ -0,0 +1 @@
${seat:mesh-artifact-store:reach}
@@ -1,12 +1,38 @@
{ {
"module": "artifact-store-tools", "module": "artifact-store-tools",
"version": "1", "version": "1",
"claims": [
{
"name": "mesh-artifact-sweep",
"scope": "mesh",
"serves": [
"artifacts",
"held",
"collect",
"mirrors",
"images"
]
}
],
"requires": [
"artifact-store"
],
"invokes": [ "invokes": [
"seat:mesh-controller.artifacts", "seat:mesh-hand-acts.hand-act",
"seat:mesh-controller.collect",
"mesh-catalog.catalog_modules", "mesh-catalog.catalog_modules",
"mesh-catalog.catalog_module" "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": [ "tools": [
"artifact_store_repositories", "artifact_store_repositories",
"artifact_store_usage", "artifact_store_usage",
@@ -36,6 +62,14 @@
"mode": "0600", "mode": "0600",
"trusted": false, "trusted": false,
"template": "files/store-names" "template": "files/store-names"
},
{
"id": "store-address",
"type": "file",
"path": "${dir:state}/store-address",
"mode": "0600",
"trusted": false,
"template": "files/store-address"
} }
], ],
"build": { "build": {
@@ -52,7 +86,8 @@
], ],
"env": { "env": {
"MESH_ARTIFACT_STORE_CONTAINER": "mesh-registry", "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"
} }
}, },
{ {
@@ -8,7 +8,10 @@
"provides": [ "provides": [
{ {
"name": "rootless-image-build", "name": "rootless-image-build",
"reach": "machine" "reach": "machine",
"credential": {
"form": "none"
}
} }
], ],
"resources": [ "resources": [
@@ -7,7 +7,9 @@
"disk_filesystems", "disk_filesystems",
"disk_folders", "disk_folders",
"disk_devices", "disk_devices",
"cpu_top" "cpu_top",
"disk_readings",
"disk_measure"
], ],
"build": { "build": {
"artifacts": [ "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"
]
} }
@@ -4,7 +4,10 @@
"provides": [ "provides": [
{ {
"name": "artifact-store", "name": "artifact-store",
"scope": "mesh" "scope": "mesh",
"credential": {
"form": "none"
}
} }
], ],
"claims": [ "claims": [
@@ -1,14 +1,15 @@
# Written by the mesh (module dnsmasq, novox/hq issue 348). Replaced on every push. # 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 # 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 # machine's private address is there: a start that finds it missing fails after at most two seconds and
# missing is tried again every ten seconds rather than left failed. # 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] [Unit]
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=0 StartLimitIntervalSec=0
[Service] [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 Restart=on-failure
RestartSec=10s RestartSec=10s
@@ -9,7 +9,10 @@
{ {
"name": "wildcard-resolution", "name": "wildcard-resolution",
"scope": "mesh", "scope": "mesh",
"identity": false "identity": false,
"credential": {
"form": "none"
}
} }
], ],
"requires": [ "requires": [
@@ -7,7 +7,7 @@
"privileged" "privileged"
], ],
"invokes": [ "invokes": [
"seat:mesh-controller.images" "seat:mesh-artifact-sweep.images"
], ],
"claims": [ "claims": [
{ {
@@ -40,6 +40,7 @@
], ],
"emits": [ "emits": [
"repo.created", "repo.created",
"repo.writable",
"issue.opened", "issue.opened",
"pull.merged", "pull.merged",
"pull.updated", "pull.updated",
@@ -181,6 +182,12 @@
"trusted": true, "trusted": true,
"default": {} "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": { "main-mergers": {
"type": "list", "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.", "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": { "env": {
"DB_TYPE": "postgres", "DB_TYPE": "postgres",
"USER_UID": "1000", "USER_UID": "1000",
"USER_GID": "1000" "USER_GID": "1000",
"SSH_MAX_STARTUPS": "100:30:200"
}, },
"env-file": [ "env-file": [
"${dir:state}/server.env" "${dir:state}/server.env"
@@ -316,7 +324,8 @@
"takes-config": [ "takes-config": [
"url", "url",
"keep-main-rules", "keep-main-rules",
"main-mergers" "main-mergers",
"check-gates-merge"
] ]
}, },
{ {
@@ -391,6 +400,7 @@
"provides": [ "provides": [
{ {
"name": "npm-package-registry", "name": "npm-package-registry",
"credential": {"form": "pair"},
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"max": 40, "max": 40,
@@ -399,6 +409,7 @@
}, },
{ {
"name": "git", "name": "git",
"credential": {"form": "none"},
"scope": "mesh" "scope": "mesh"
}, },
{ {
@@ -411,6 +422,7 @@
}, },
{ {
"name": "forge-account", "name": "forge-account",
"credential": {"form": "none"},
"scope": "mesh" "scope": "mesh"
} }
], ],
@@ -537,6 +549,7 @@
"MESH_GITEA_ADMIN_PASSWORD_FILE": "${dir:state}/admin.secret", "MESH_GITEA_ADMIN_PASSWORD_FILE": "${dir:state}/admin.secret",
"MESH_GITEA_ORIGINS_FILE": "${dir:mesh-state}/origins", "MESH_GITEA_ORIGINS_FILE": "${dir:mesh-state}/origins",
"MESH_GITEA_SSH_PORT": "222", "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" "MESH_FORGE_HOST_FILE": "/etc/gitea/forge-host"
} }
} }
@@ -554,11 +567,14 @@
"announced" "announced"
], ],
"reads": [ "reads": [
"mesh-controller.assignments", "mesh-provisioner.plans",
"mesh-provisioner.provisions",
"ssh-client.keys", "ssh-client.keys",
"mesh-build-go.keys", "mesh-build-go.keys",
"mesh-build-node.keys", "mesh-build-node.keys",
"mesh-build-image.keys", "mesh-build-image.keys",
"mesh-delivery-checker.owed" "mesh-delivery-checker.owed",
"mesh-delivery-indexer.modules",
"mesh-delivery-gate.source"
] ]
} }
@@ -7,7 +7,10 @@
"provides": [ "provides": [
{ {
"name": "go-toolchain", "name": "go-toolchain",
"reach": "machine" "reach": "machine",
"credential": {
"form": "none"
}
} }
], ],
"resources": [ "resources": [
@@ -7,6 +7,9 @@
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"in": "an InfluxDB v1 authorization" "in": "an InfluxDB v1 authorization"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -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
@@ -100,6 +100,25 @@
"id": "linux-lts-headers", "id": "linux-lts-headers",
"type": "package", "type": "package",
"package": "linux-lts-headers" "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": { "build": {
@@ -9,6 +9,7 @@
"provides": [ "provides": [
{ {
"name": "oidc-client", "name": "oidc-client",
"credential": {"form": "pair"},
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"max": 255, "max": 255,
@@ -240,6 +241,7 @@
"bound" "bound"
], ],
"reads": [ "reads": [
"mesh-controller.assignments" "mesh-provisioner.plans",
"mesh-provisioner.provisions"
] ]
} }
@@ -665,7 +665,14 @@
"path": "${dir:mesh-state}/config.json", "path": "${dir:mesh-state}/config.json",
"mode": "0600", "mode": "0600",
"template": "files/config.json", "template": "files/config.json",
"takes-config": ["domain", "proxy-address", "sitename", "website", "url", "container"], "takes-config": [
"domain",
"proxy-address",
"sitename",
"website",
"url",
"container"
],
"merge": "json" "merge": "json"
}, },
{ {
@@ -758,6 +765,9 @@
"identity": { "identity": {
"max": 64, "max": 64,
"in": "a mailbox's local part" "in": "a mailbox's local part"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -1,3 +1,5 @@
{ {
"capacity": "shanks=3,ace=2,g14=0,novox=0" "capacity": 0,
"cores-per-step": 6,
"build-hours": ""
} }
@@ -10,7 +10,7 @@
"state": [ "state": [
"toolchains", "toolchains",
"keys", "keys",
"steps" {"name": "steps", "ttl-seconds": 172800}, "pauses"
], ],
"claims": [ "claims": [
{ {
@@ -20,14 +20,27 @@
} }
], ],
"reads": [ "reads": [
"mesh-delivery-checker.kept" "mesh-delivery-checker.kept",
"mesh-delivery-indexer.manifests"
], ],
"config": { "config": {
"capacity": { "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", "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.", "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": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", "pattern": "|([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]",
"default": "shanks=3,ace=2,g14=0,novox=0" "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": [ "tools": [
@@ -38,6 +51,7 @@
"build_go_release", "build_go_release",
"build_go_step", "build_go_step",
"build_go_steps", "build_go_steps",
"build_go_step_end",
"build_go_toolchains", "build_go_toolchains",
"build_go_status", "build_go_status",
"build_go_health" "build_go_health"
@@ -105,7 +119,7 @@
"path": "${dir:state}/settings.json", "path": "${dir:state}/settings.json",
"mode": "0600", "mode": "0600",
"owner": "mesh-build-go", "owner": "mesh-build-go",
"takes-config": ["capacity"], "takes-config": ["capacity", "cores-per-step", "build-hours"],
"merge": "json", "merge": "json",
"template": "files/settings.json" "template": "files/settings.json"
}, },
@@ -1,3 +1,5 @@
{ {
"capacity": "shanks=3,ace=2,g14=0,novox=0" "capacity": 0,
"cores-per-step": 2,
"build-hours": ""
} }
@@ -9,7 +9,7 @@
], ],
"state": [ "state": [
"keys", "keys",
"steps" {"name": "steps", "ttl-seconds": 172800}, "pauses"
], ],
"claims": [ "claims": [
{ {
@@ -20,10 +20,22 @@
], ],
"config": { "config": {
"capacity": { "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", "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.", "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": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", "pattern": "|([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]",
"default": "shanks=3,ace=2,g14=0,novox=0" "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": [ "tools": [
@@ -33,6 +45,7 @@
"build_image_release", "build_image_release",
"build_image_step", "build_image_step",
"build_image_steps", "build_image_steps",
"build_image_step_end",
"build_image_status", "build_image_status",
"build_image_health" "build_image_health"
], ],
@@ -77,7 +90,7 @@
"path": "${dir:state}/settings.json", "path": "${dir:state}/settings.json",
"mode": "0600", "mode": "0600",
"owner": "mesh-build-image", "owner": "mesh-build-image",
"takes-config": ["capacity"], "takes-config": ["capacity", "cores-per-step", "build-hours"],
"merge": "json", "merge": "json",
"template": "files/settings.json" "template": "files/settings.json"
}, },
@@ -1,3 +1,5 @@
{ {
"capacity": "shanks=3,ace=2,g14=0,novox=0" "capacity": 0,
"cores-per-step": 2,
"build-hours": ""
} }
@@ -10,7 +10,7 @@
"state": [ "state": [
"toolchains", "toolchains",
"keys", "keys",
"steps" {"name": "steps", "ttl-seconds": 172800}, "pauses"
], ],
"claims": [ "claims": [
{ {
@@ -21,10 +21,22 @@
], ],
"config": { "config": {
"capacity": { "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", "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.", "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": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", "pattern": "|([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]",
"default": "shanks=3,ace=2,g14=0,novox=0" "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": [ "tools": [
@@ -34,6 +46,7 @@
"build_node_release", "build_node_release",
"build_node_step", "build_node_step",
"build_node_steps", "build_node_steps",
"build_node_step_end",
"build_node_toolchains", "build_node_toolchains",
"build_node_status", "build_node_status",
"build_node_health" "build_node_health"
@@ -99,7 +112,7 @@
"path": "${dir:state}/settings.json", "path": "${dir:state}/settings.json",
"mode": "0600", "mode": "0600",
"owner": "mesh-build-node", "owner": "mesh-build-node",
"takes-config": ["capacity"], "takes-config": ["capacity", "cores-per-step", "build-hours"],
"merge": "json", "merge": "json",
"template": "files/settings.json" "template": "files/settings.json"
}, },
@@ -25,9 +25,7 @@
"secrets": { "secrets": {
"postgres-database": "${dir:state}/database.secret" "postgres-database": "${dir:state}/database.secret"
}, },
"consumes": [ "consumes": [],
"mesh-controller.built-before"
],
"emits": [ "emits": [
"registered", "registered",
"upgraded", "upgraded",
@@ -92,5 +90,12 @@
"from": "files" "from": "files"
} }
] ]
} },
"invokes": [
"mesh-delivery-builder.builder_builds_since"
],
"reads": [
"mesh-delivery-builder.builds",
"mesh-delivery-indexer.manifests"
]
} }
@@ -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"
]
}
]
}
}
@@ -7,7 +7,10 @@
"name": "mesh-config", "name": "mesh-config",
"scope": "mesh", "scope": "mesh",
"serves": [ "serves": [
"config" "config",
"compare",
"take-over",
"hand-back"
] ]
} }
], ],
@@ -23,14 +26,30 @@
"gitea.forge.pull-request.closed" "gitea.forge.pull-request.closed"
], ],
"invokes": [ "invokes": [
"seat:mesh-hand-acts.hand-act",
"seat:mesh-hand-acts.warranted",
"seat:mesh-controller.hand-act", "seat:mesh-controller.hand-act",
"seat:mesh-controller.warranted", "seat:mesh-controller.warranted",
"gitea.gitea_get_pull_request" "gitea.gitea_get_pull_request"
], ],
"reads": [ "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.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": [ "state": [
"values", "values",
@@ -38,7 +57,9 @@
"predefined", "predefined",
"proposals", "proposals",
"pulls", "pulls",
"kept" "kept",
"declared",
"takeover"
], ],
"logs": [ "logs": [
"history" "history"
@@ -1,4 +1,5 @@
{ {
"refusal-stall-floor-minutes": 10, "refusal-stall-floor-minutes": 10,
"author-new-head-minutes": 120 "author-new-head-minutes": 120,
"check-gates-merge": true
} }
@@ -14,7 +14,8 @@
"table", "table",
"stalled", "stalled",
"recheck", "recheck",
"close" "close",
"settle"
] ]
} }
], ],
@@ -26,13 +27,17 @@
"gitea.forge.pull-request.edited", "gitea.forge.pull-request.edited",
"gitea.forge.pull-request.closed", "gitea.forge.pull-request.closed",
"mesh-delivery-forge-bridge.forge.**", "mesh-delivery-forge-bridge.forge.**",
"mesh-config.config-changed" "mesh-config.config-changed",
"gitea.repo.writable"
], ],
"emits": [ "emits": [
"transition", "transition",
"group", "group",
"merged" "merged"
], ],
"uses": [
"operator-channel"
],
"invokes": [ "invokes": [
"gitea.gitea_commit_statuses", "gitea.gitea_commit_statuses",
"gitea.gitea_contains", "gitea.gitea_contains",
@@ -57,14 +62,23 @@
{ {
"name": "source", "name": "source",
"history": 64 "history": 64
},
"conditions",
{
"name": "turns",
"history": 5
} }
], ],
"reads": [ "reads": [
"mesh-delivery-teller.answers", "mesh-delivery-teller.answers",
"mesh-delivery-checker.verdicts" "mesh-delivery-checker.verdicts",
"mesh-delivery-view.reach"
], ],
"tools": [ "tools": [
"delivery_status" "delivery_hand_now",
"delivery_merge_again",
"delivery_status",
"delivery_turns"
], ],
"config": { "config": {
"refusal-stall-floor-minutes": { "refusal-stall-floor-minutes": {
@@ -82,6 +96,12 @@
"maximum": 10080, "maximum": 10080,
"trusted": false, "trusted": false,
"default": 120 "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": [ "resources": [
@@ -99,7 +119,8 @@
"mode": "0600", "mode": "0600",
"takes-config": [ "takes-config": [
"refusal-stall-floor-minutes", "refusal-stall-floor-minutes",
"author-new-head-minutes" "author-new-head-minutes",
"check-gates-merge"
], ],
"merge": "json", "merge": "json",
"template": "files/settings.json" "template": "files/settings.json"
@@ -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"
]
}
]
}
}
@@ -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"
]
}
]
}
}
@@ -62,10 +62,13 @@
"mesh-catalog modules/mesh-build-node", "mesh-catalog modules/mesh-build-node",
"mesh-catalog modules/mesh-catalog", "mesh-catalog modules/mesh-catalog",
"mesh-catalog modules/mesh-cli", "mesh-catalog modules/mesh-cli",
"mesh-catalog modules/mesh-conditions",
"mesh-catalog modules/mesh-config", "mesh-catalog modules/mesh-config",
"mesh-catalog modules/mesh-delivery", "mesh-catalog modules/mesh-delivery",
"mesh-catalog modules/mesh-delivery-gate", "mesh-catalog modules/mesh-delivery-gate",
"mesh-catalog modules/mesh-files", "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-issues",
"mesh-catalog modules/mesh-knowledge", "mesh-catalog modules/mesh-knowledge",
"mesh-catalog modules/mesh-mail", "mesh-catalog modules/mesh-mail",
@@ -75,6 +78,7 @@
"mesh-catalog modules/mesh-provisioner", "mesh-catalog modules/mesh-provisioner",
"mesh-catalog modules/mesh-provisioner-client", "mesh-catalog modules/mesh-provisioner-client",
"mesh-catalog modules/mesh-reads", "mesh-catalog modules/mesh-reads",
"mesh-catalog modules/mesh-self-check",
"mesh-catalog modules/mesh-vault", "mesh-catalog modules/mesh-vault",
"mesh-catalog modules/mesh-watcher", "mesh-catalog modules/mesh-watcher",
"mesh-catalog modules/messenger", "mesh-catalog modules/messenger",
@@ -13,12 +13,17 @@
"move", "move",
"note", "note",
"link", "link",
"milestones",
"milestone-set",
"milestone-move",
"import", "import",
"groom", "groom",
"set", "set",
"tracking", "tracking",
"reserve", "reserve",
"fix-times" "fix-times",
"learn-fixes",
"changes"
] ]
} }
], ],
@@ -33,12 +38,17 @@
"move", "move",
"note", "note",
"link", "link",
"milestones",
"milestone-set",
"milestone-move",
"import", "import",
"groom", "groom",
"set", "set",
"tracking", "tracking",
"reserve", "reserve",
"fix-times" "fix-times",
"learn-fixes",
"changes"
] ]
} }
], ],
@@ -52,7 +62,9 @@
"triaged", "triaged",
"ready", "ready",
"ready-to-verify", "ready-to-verify",
"reserved" "reserved",
"milestone-set",
"milestone-moved"
], ],
"consumes": [ "consumes": [
"mesh-issues.opened", "mesh-issues.opened",
@@ -66,6 +78,7 @@
"seat:operator-channel.notify", "seat:operator-channel.notify",
"gitea.gitea_pull_reviews", "gitea.gitea_pull_reviews",
"gitea.gitea_contains", "gitea.gitea_contains",
"gitea.gitea_get_pull_request",
"records.records_status", "records.records_status",
"records.records_list", "records.records_list",
"records.records_read", "records.records_read",
@@ -81,7 +94,11 @@
}, },
"counters", "counters",
"grooming", "grooming",
"reservations" "reservations",
{
"name": "milestones",
"history": 5
}
], ],
"config": { "config": {
"first-number": { "first-number": {
@@ -157,10 +174,13 @@
"mesh-catalog modules/mesh-build-node", "mesh-catalog modules/mesh-build-node",
"mesh-catalog modules/mesh-catalog", "mesh-catalog modules/mesh-catalog",
"mesh-catalog modules/mesh-cli", "mesh-catalog modules/mesh-cli",
"mesh-catalog modules/mesh-conditions",
"mesh-catalog modules/mesh-config", "mesh-catalog modules/mesh-config",
"mesh-catalog modules/mesh-delivery", "mesh-catalog modules/mesh-delivery",
"mesh-catalog modules/mesh-delivery-gate", "mesh-catalog modules/mesh-delivery-gate",
"mesh-catalog modules/mesh-files", "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-issues",
"mesh-catalog modules/mesh-knowledge", "mesh-catalog modules/mesh-knowledge",
"mesh-catalog modules/mesh-mail", "mesh-catalog modules/mesh-mail",
@@ -170,6 +190,7 @@
"mesh-catalog modules/mesh-provisioner", "mesh-catalog modules/mesh-provisioner",
"mesh-catalog modules/mesh-provisioner-client", "mesh-catalog modules/mesh-provisioner-client",
"mesh-catalog modules/mesh-reads", "mesh-catalog modules/mesh-reads",
"mesh-catalog modules/mesh-self-check",
"mesh-catalog modules/mesh-vault", "mesh-catalog modules/mesh-vault",
"mesh-catalog modules/mesh-watcher", "mesh-catalog modules/mesh-watcher",
"mesh-catalog modules/messenger", "mesh-catalog modules/messenger",
@@ -32,7 +32,8 @@
"propose", "propose",
"import", "import",
"return", "return",
"withdraw" "withdraw",
"progress"
] ]
} }
], ],
@@ -52,13 +53,15 @@
"propose", "propose",
"import", "import",
"return", "return",
"withdraw" "withdraw",
"progress"
] ]
} }
], ],
"tools": [ "tools": [
"knowledge_health", "knowledge_health",
"knowledge_highest" "knowledge_highest",
"knowledge_marks"
], ],
"emits": [ "emits": [
"reserved", "reserved",
@@ -81,7 +84,14 @@
"records.records_status", "records.records_status",
"records.records_list", "records.records_list",
"records.records_read", "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": { "config": {
"judge-model": { "judge-model": {
@@ -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": [ "state": [
"registry", "registry",
"needs", "needs",
"applied", "applied",
"collect" "collect",
"provisions",
"plans"
], ],
"own-secrets": { "own-secrets": {
"broker": "${dir:state}/broker" "broker": "${dir:state}/broker"
@@ -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
}
}
}
@@ -5,6 +5,9 @@
"provides": [ "provides": [
{ {
"name": "secret", "name": "secret",
"credential": {
"form": "pair"
},
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"in": "a vault entry" "in": "a vault entry"
@@ -14,10 +17,27 @@
"emits": [ "emits": [
"provisioned", "provisioned",
"rotated", "rotated",
"secret-changed" "secret-changed",
"account-hashed",
"own-replaced",
"account-issued",
"signed"
], ],
"reads": [ "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": { "receives": {
"secret": "${dir:grants}/mesh.json" "secret": "${dir:grants}/mesh.json"
@@ -135,7 +155,8 @@
"MESH_VAULT_LEDGER": "${dir:ledger}", "MESH_VAULT_LEDGER": "${dir:ledger}",
"MESH_VAULT_ROOT": "${dir:root}", "MESH_VAULT_ROOT": "${dir:root}",
"MESH_VAULT_STORE": "${dir:store}", "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_holders",
"secret_verify", "secret_verify",
"secret_handed", "secret_handed",
"secret_own",
"account_hash",
"account_adopt",
"account_retire",
"secret_asks", "secret_asks",
"secret_vault_export", "secret_vault_export",
"provisioner_retirement", "provisioner_retirement",
@@ -152,7 +177,12 @@
"provisioner_retire_reject", "provisioner_retire_reject",
"provisioner_delete", "provisioner_delete",
"vault_status", "vault_status",
"vault_health" "vault_health",
"own_drift",
"own_take_switch",
"vault_signers",
"vault_signed",
"signers_reassert"
], ],
"claims": [ "claims": [
{ {
@@ -166,7 +196,10 @@
"rotate", "rotate",
"move-in", "move-in",
"node-key", "node-key",
"move-asks" "move-asks",
"own",
"issue-account",
"sign"
] ]
} }
] ]
@@ -3,11 +3,15 @@
"version": "1", "version": "1",
"slug": "watch", "slug": "watch",
"consumes": [ "consumes": [
"mesh-controller.doctor-heartbeat" "mesh-controller.doctor-heartbeat",
"mesh-self-check.checked"
],
"reads": [
"mesh-conditions.takeover",
"mesh-delivery-assigner.seat-holders"
], ],
"invokes": [ "invokes": [
"mesh-watcher.watcher_ping", "mesh-watcher.watcher_ping"
"seat:mesh-controller.seats"
], ],
"own-secrets": { "own-secrets": {
"telegram-token": "${dir:state}/telegram-token" "telegram-token": "${dir:state}/telegram-token"
@@ -39,7 +43,9 @@
"type": "file", "type": "file",
"path": "${dir:state}/settings.json", "path": "${dir:state}/settings.json",
"mode": "0600", "mode": "0600",
"takes-config": ["telegram-chat-id"], "takes-config": [
"telegram-chat-id"
],
"merge": "json", "merge": "json",
"template": "files/settings.json" "template": "files/settings.json"
} }
@@ -77,6 +77,9 @@
"mesh-controller.condition-raised", "mesh-controller.condition-raised",
"mesh-controller.condition-changed", "mesh-controller.condition-changed",
"mesh-controller.condition-cleared", "mesh-controller.condition-cleared",
"mesh-conditions.condition-raised",
"mesh-conditions.condition-changed",
"mesh-conditions.condition-cleared",
"mesh-delivery-view.moving", "mesh-delivery-view.moving",
"mesh-delivery-view.settled", "mesh-delivery-view.settled",
"mesh-delivery-view.stuck", "mesh-delivery-view.stuck",
@@ -90,9 +93,13 @@
], ],
"invokes": [ "invokes": [
"seat:mesh-controller.conditions", "seat:mesh-controller.conditions",
"seat:mesh-controller.root-free", "seat:mesh-conditions.conditions",
"seat:node-engine.root-free",
"seat:issue-tracker.tracking" "seat:issue-tracker.tracking"
], ],
"reads": [
"mesh-conditions.takeover"
],
"state": [ "state": [
"open", "open",
"sent", "sent",
@@ -12,6 +12,9 @@
"identity": { "identity": {
"max": 20, "max": 20,
"in": "an S3 access key" "in": "an S3 access key"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -36,8 +39,8 @@
] ]
} }
], ],
"invokes": [ "reads": [
"seat:mesh-controller.seats" "mesh-delivery-assigner.seat-holders"
], ],
"contributes": { "contributes": {
"route": { "route": {
@@ -12,6 +12,9 @@
"identity": { "identity": {
"max": 63, "max": 63,
"in": "a MongoDB database name" "in": "a MongoDB database name"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -8,6 +8,9 @@
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"in": "a Mosquitto client and topic prefix" "in": "a Mosquitto client and topic prefix"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -5,11 +5,9 @@
"nfs-share" "nfs-share"
], ],
"reads": [ "reads": [
"disk-load.readings",
"nfs-server.exports" "nfs-server.exports"
], ],
"invokes": [
"seat:mesh-controller.data"
],
"capabilities": [ "capabilities": [
"package-manager", "package-manager",
"service-manager" "service-manager"
@@ -12,6 +12,9 @@
"identity": { "identity": {
"max": 128, "max": 128,
"in": "a SQL Server login and database name" "in": "a SQL Server login and database name"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -8,7 +8,10 @@
"provides": [ "provides": [
{ {
"name": "mesh-bus", "name": "mesh-bus",
"scope": "mesh" "scope": "mesh",
"credential": {
"form": "none"
}
} }
], ],
"claims": [ "claims": [
@@ -19,12 +22,16 @@
], ],
"bus-users": "/var/lib/nats-module/conf/accounts.conf", "bus-users": "/var/lib/nats-module/conf/accounts.conf",
"own-secrets": { "own-secrets": {
"broker": "${dir:mesh-state}/broker" "broker": "${dir:mesh-state}/broker",
"broker-nats-tools": "${dir:tools-secret}/broker-nats-tools"
}, },
"capabilities": [ "capabilities": [
"container-runtime" "container-runtime"
], ],
"emits": [], "emits": [
"consumer-removed",
"bucket-keys-removed"
],
"consumes": [], "consumes": [],
"listens": [ "listens": [
{ {
@@ -45,7 +52,9 @@
"nats_buckets", "nats_buckets",
"nats_users", "nats_users",
"nats_user_can", "nats_user_can",
"nats_snapshot_verify" "nats_snapshot_verify",
"nats_consumer_remove",
"nats_bucket_remove"
], ],
"guards": [ "guards": [
8222 8222
@@ -86,6 +95,13 @@
"mode": "0700", "mode": "0700",
"unknown": "program" "unknown": "program"
}, },
{
"id": "tools-secret",
"type": "directory",
"path": "/var/lib/mesh/nats-tools",
"mode": "0711",
"unknown": "program"
},
{ {
"id": "snapshots", "id": "snapshots",
"type": "directory", "type": "directory",
@@ -190,11 +206,18 @@
"loads": [ "loads": [
"nats-tools" "nats-tools"
], ],
"bus": {
"removes": [
"DEAD_LETTER_NOTICES.controller",
"mesh-host_acts"
]
},
"env": { "env": {
"MESH_NATS_MONITOR": "http://127.0.0.1:8222", "MESH_NATS_MONITOR": "http://127.0.0.1:8222",
"MESH_NATS_CONTAINER": "mesh-broker-nats", "MESH_NATS_CONTAINER": "mesh-broker-nats",
"MESH_NATS_USERS_FILE": "/etc/nats/accounts.conf", "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"
} }
}, },
{ {
@@ -72,7 +72,7 @@
}, },
"user": { "user": {
"type": "string", "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, "trusted": true,
"default": "" "default": ""
} }
@@ -13,7 +13,10 @@
{ {
"name": "nfs-share", "name": "nfs-share",
"scope": "mesh", "scope": "mesh",
"identity": false "identity": false,
"credential": {
"form": "none"
}
} }
], ],
"data": { "data": {
@@ -45,11 +48,9 @@
} }
], ],
"reads": [ "reads": [
"disk-load.readings",
"mesh-delivery-assigner.seat-holders",
"mounts.shares" "mounts.shares"
],
"invokes": [
"seat:mesh-controller.seats",
"seat:mesh-controller.data"
], ],
"config": { "config": {
"shares": { "shares": {
@@ -7,7 +7,10 @@
"provides": [ "provides": [
{ {
"name": "node-toolchain", "name": "node-toolchain",
"reach": "machine" "reach": "machine",
"credential": {
"form": "none"
}
} }
], ],
"resources": [ "resources": [
@@ -7,7 +7,10 @@
"provides": [ "provides": [
{ {
"name": "model-access", "name": "model-access",
"scope": "node" "scope": "node",
"credential": {
"form": "none"
}
} }
], ],
"listens": [ "listens": [
@@ -9,6 +9,7 @@
"provides": [ "provides": [
{ {
"name": "postgres-database", "name": "postgres-database",
"credential": {"form": "pair"},
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"max": 63, "max": 63,
@@ -195,6 +196,7 @@
"bound" "bound"
], ],
"reads": [ "reads": [
"mesh-controller.assignments" "mesh-provisioner.plans",
"mesh-provisioner.provisions"
] ]
} }
@@ -314,6 +314,48 @@
"mode": "0644", "mode": "0644",
"template": "templates/systemd/system/suspend-then-hibernate.target.d/mesh-power.conf" "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", "id": "udev-rule",
"type": "file", "type": "file",
@@ -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
@@ -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
@@ -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
@@ -7,6 +7,9 @@
"scope": "mesh", "scope": "mesh",
"identity": { "identity": {
"in": "a Redis ACL user and key prefix" "in": "a Redis ACL user and key prefix"
},
"credential": {
"form": "pair"
} }
} }
], ],
@@ -139,22 +139,17 @@
"MESH_BACKUP_DATA": "${dir:state}/data.conf" "MESH_BACKUP_DATA": "${dir:state}/data.conf"
}, },
"health": { "health": {
"kind": "tool", "kind": "unit"
"tool": "backup_health",
"interval": "60s",
"timeout": "20s",
"looks": 2,
"grace": "60s"
} }
}, },
{ {
"id": "measure", "id": "awake",
"type": "process", "type": "process",
"name": "restic-measure", "name": "restic-awake",
"artifact": "tools", "artifact": "tools",
"run": [ "run": [
"./restic-backups", "./restic-backups",
"measure" "awake"
], ],
"user": "${machine:account}", "user": "${machine:account}",
"env": { "env": {
@@ -165,7 +160,12 @@
"MESH_BACKUP_DATA": "${dir:state}/data.conf" "MESH_BACKUP_DATA": "${dir:state}/data.conf"
}, },
"health": { "health": {
"kind": "unit" "kind": "tool",
"tool": "backup_health",
"interval": "60s",
"timeout": "20s",
"looks": 2,
"grace": "60s"
} }
} }
], ],
@@ -195,5 +195,11 @@
"from": "files" "from": "files"
} }
] ]
} },
"reads": [
"disk-load.readings"
],
"state": [
"good"
]
} }
@@ -9,6 +9,7 @@
"provides": [ "provides": [
{ {
"name": "route", "name": "route",
"credential": {"form": "none"},
"scope": "mesh", "scope": "mesh",
"identity": false "identity": false
} }
@@ -129,6 +130,7 @@
"bound" "bound"
], ],
"reads": [ "reads": [
"mesh-controller.assignments" "mesh-provisioner.plans",
"mesh-provisioner.provisions"
] ]
} }
@@ -9,7 +9,10 @@
"provides": [ "provides": [
{ {
"name": "greeting", "name": "greeting",
"scope": "mesh" "scope": "mesh",
"credential": {
"form": "none"
}
} }
], ],
"serves": { "serves": {
@@ -7,7 +7,10 @@
"provides": [ "provides": [
{ {
"name": "internal-acme-ca", "name": "internal-acme-ca",
"scope": "mesh" "scope": "mesh",
"credential": {
"form": "none"
}
} }
], ],
"serves": { "serves": {
@@ -8,7 +8,10 @@
"provides": [ "provides": [
{ {
"name": "split-dns", "name": "split-dns",
"reach": "machine" "reach": "machine",
"credential": {
"form": "none"
}
} }
], ],
"requires": [ "requires": [
@@ -32,7 +32,10 @@
"provides": [ "provides": [
{ {
"name": "analytics", "name": "analytics",
"scope": "mesh" "scope": "mesh",
"credential": {
"form": "none"
}
} }
], ],
"serves": { "serves": {
@@ -9,7 +9,10 @@
"provides": [ "provides": [
{ {
"name": "x11-display", "name": "x11-display",
"reach": "machine" "reach": "machine",
"credential": {
"form": "none"
}
} }
], ],
"claims": [ "claims": [