Read how a module says each resource is ready, and send it to engines that read it (hq ADR 0240, to-be 48 Phase B)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/health-the-field delivering: 1 of 3 delivered
mesh/delivery held for a person: merged, and the controller opened no walk for it within 10m0s — nothing it holds follows that branch, or the merge was…
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/health-the-field delivering: 1 of 3 delivered
mesh/delivery held for a person: merged, and the controller opened no walk for it within 10m0s — nothing it holds follows that branch, or the merge was…
A module could say nothing about what ready means for what it runs, so a web application with its port open and its requests hanging passed everything for eleven hours (issue 145). A long-running resource now carries `health` — the image's own check adopted by name, http, tcp, exec, unit or a module's own tool, with its timing — refused near its author when it names a port or an address, an endpoint the module does not declare, a tool it does not serve, a tool check alone, or a timing outside the record's bounds. It is composed with the endpoint as the port this machine published it on, and sent only to a node-engine whose statement says it reads it: an older one would refuse the whole declaration. The engine is granted its own machine's instance of each health tool. `module check` warns of every long-running resource without `health`, counts them for the catalogue, and refuses them from 2026-11-18. A check's findings stay out of a condition's summary. The node-engine's validator is vendored at its Phase B commit, so what is composed is judged by the words the engine takes.
This commit is contained in:
@@ -122,6 +122,10 @@ type Principal struct {
|
||||
// key, and nothing else of the bucket, so it can judge a new controller build and put the previous
|
||||
// one back.
|
||||
WitnessesController bool
|
||||
// Checks are the tools a machine principal's node-engine asks as a declared health check, each
|
||||
// `<module>.<tool>` (novox/hq ADR 0240, to-be 48 §3): asked of the instance on its own machine and
|
||||
// nowhere else.
|
||||
Checks []string
|
||||
|
||||
// PasswordHash is the bcrypt hash the mesh minted. The plaintext is sealed to the principal
|
||||
// and never appears here: this file is written to a node's disk and read by a server, and a
|
||||
@@ -1021,5 +1025,26 @@ func WitnessSubjects(p Principal) []string {
|
||||
if p.WitnessesController {
|
||||
out = append(out, lease.LeaseReadSubject(LeaseBucket))
|
||||
}
|
||||
return append(out, CheckSubjects(p)...)
|
||||
}
|
||||
|
||||
// CheckSubjects are the tools a machine's node-engine asks as declared health checks (novox/hq ADR 0240,
|
||||
// to-be 48 §3): each `<module>.<tool>` on this machine's instance — `mesh.mod.<module>.tool.<tool>.<node>`
|
||||
// — and never the plain subject, which any machine's instance may answer. Sorted and once each.
|
||||
func CheckSubjects(p Principal) []string {
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, c := range p.Checks {
|
||||
module, tool, ok := strings.Cut(c, ".")
|
||||
if !ok || !safeSubject.MatchString(module) || !safeSubject.MatchString(tool) {
|
||||
continue
|
||||
}
|
||||
subject := "mesh.mod." + module + ".tool." + tool + "." + p.Node
|
||||
if !seen[subject] {
|
||||
seen[subject] = true
|
||||
out = append(out, subject)
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ type Declared struct {
|
||||
// SnapshotsTheBus says the module holds mesh-broker — it is the bus — and so is the one module
|
||||
// granted the snapshot API, to copy the bus's streams for the night's backup (novox/hq ADR 0235).
|
||||
SnapshotsTheBus bool
|
||||
// Checks are the module's own tools its health asks, each `<module>.<tool>` (novox/hq ADR 0240, to-be
|
||||
// 48 §3): the machine's node-engine asks them of its own node tools, and is granted that and no more.
|
||||
Checks []string
|
||||
}
|
||||
|
||||
// Records is what composing a user list needs to know about the mesh, and nothing more.
|
||||
@@ -73,12 +76,14 @@ func Users(r Records) ([]Principal, error) {
|
||||
|
||||
for _, node := range sortedCopy(r.Nodes) {
|
||||
witness := false
|
||||
var checks []string
|
||||
for _, d := range r.Assigned[node] {
|
||||
if d.Module == controllerModule {
|
||||
witness = true
|
||||
}
|
||||
checks = append(checks, d.Checks...)
|
||||
}
|
||||
out = append(out, Principal{Kind: KindNode, Node: node, WitnessesController: witness})
|
||||
out = append(out, Principal{Kind: KindNode, Node: node, WitnessesController: witness, Checks: checks})
|
||||
// **Where the runtime is assigned, the machine gets one runtime principal in place of the
|
||||
// runtime module's own** (novox/hq ADR 0175, to-be 38). It carries every module on the
|
||||
// node: its serving grants are the union of theirs. Every other module keeps its own
|
||||
|
||||
@@ -35,3 +35,31 @@ func TestTheWitnessIsGrantedWhatItReadsAndNoMore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A module's health that asks one of its own tools is asked by the machine's node-engine, of the instance
|
||||
// on its own machine and nowhere else (novox/hq ADR 0240, to-be 48 §3).
|
||||
func TestTheEngineIsGrantedTheToolsItsModulesHealthAsks(t *testing.T) {
|
||||
users, err := Users(Records{Nodes: []string{"control", "edge"},
|
||||
Assigned: map[string][]Declared{"edge": {{Module: "keycloak", Checks: []string{"keycloak.keycloak_admin_health"}}}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, u := range users {
|
||||
if u.Kind != KindNode {
|
||||
continue
|
||||
}
|
||||
perms, err := PermissionsFor(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mine := "mesh.mod.keycloak.tool.keycloak_admin_health.edge"
|
||||
if got := slices.Contains(perms.Publish, mine); got != (u.Node == "edge") {
|
||||
t.Errorf("%s may ask keycloak's health tool on edge: %v", u.Node, got)
|
||||
}
|
||||
for _, p := range perms.Publish {
|
||||
if p == "mesh.mod.keycloak.tool.keycloak_admin_health" || p == "mesh.mod.keycloak.tool.>" {
|
||||
t.Errorf("%s may ask the tool of any machine: %s", u.Node, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user