Compose a node's own modules from their builds and read what providers elsewhere serve, so a self-composing node hands moved modules and settles served settings as the controller does (issue 1126)

frombus composed every module from the indexer's raw manifest: no moved module
was handed, so the node stood in for own secrets the vault leaves to the
provisioner (the seed's refusals on all four nodes), and a provider elsewhere
had no config values, so mailu's served domain refused shanks.

- takein: the controller's take-in of a done key (artifacts, launchers, the
  templates archive's texts, Resolve) as one SDK rule; catalogue.Launchers holds
  the launcher rule buildstep now delegates to.
- frombus: each module placed on the composing node, and each kept at another
  build (Scope.KeptAt, a fingerprint), composes from its built manifest; another
  node's modules give the keys their served values fill (Reader.Served).
- buscompose: Declared.Served and Principal.ServedReads grant every node the read
  of exactly those keys of mesh-config_resolved.
- compose.ArtifactStoreAddress: the store as the node's composition routes to it.
This commit is contained in:
2026-10-27 21:11:20 +01:00
parent 9f92233a28
commit 04de3e5da2
16 changed files with 890 additions and 45 deletions
+26
View File
@@ -325,3 +325,29 @@ func NodeComposeReads(node string) []string {
}
return append(out, readGrants(ResolvedBucket, node+".>")...)
}
// ServedReadGrants is the read of each key of mesh-config's resolved values named, `<node>.<module>.<key>`
// (Principal.ServedReads, issue 1126): a get and a watch of that key alone. Each part is checked as every subject token
// is, so a key cannot widen the grant.
func ServedReadGrants(keys []string) ([]string, error) {
var out []string
seen := map[string]bool{}
for _, k := range keys {
parts := strings.Split(k, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("%q is not <node>.<module>.<key>", k)
}
for _, part := range parts {
if !safeSubject.MatchString(part) {
return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", part)
}
}
for _, g := range readGrants(ResolvedBucket, k) {
if !seen[g] {
seen[g] = true
out = append(out, g)
}
}
}
return out, nil
}