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.
48 lines
2.0 KiB
Go
48 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// Every catalogue module that runs something long-lived declares how it is ready (novox/hq ADR 0240 rule
|
|
// 8): `module check` warns and counts the undeclared before the date, and refuses them from it.
|
|
func TestModuleCheckCountsTheUndeclaredAndRefusesThemFromTheDate(t *testing.T) {
|
|
dir := t.TempDir()
|
|
digest := "@sha256:" + strings.Repeat("a", 64)
|
|
path := filepath.Join(dir, "module.json")
|
|
os.WriteFile(path, []byte(`{"module":"web","listens":[{"name":"web","port":80,"from":"mesh"}],"resources":[
|
|
{"id":"server","type":"container","name":"web","image":"registry.example/web`+digest+`","ports":["80"],
|
|
"health":{"kind":"http","endpoint":"web"}},
|
|
{"id":"worker","type":"container","name":"web-worker","image":"registry.example/web`+digest+`"},
|
|
{"id":"seed","type":"container","name":"web-seed","image":"registry.example/web`+digest+`","run-once":true}]}`), 0o600)
|
|
defer func() { checkNow = time.Now }()
|
|
|
|
checkNow = func() time.Time { return catalogue.HealthRequiredFrom.Add(-time.Hour) }
|
|
var out bytes.Buffer
|
|
if err := moduleCheck([]string{path}, &out); err != nil {
|
|
t.Fatalf("refused before the date: %v\n%s", err, out.String())
|
|
}
|
|
for _, want := range []string{"ready: server by http / on web every 30s", "WARNING: worker stay(s) up",
|
|
catalogue.HealthRequiredFrom.Format("2006-01-02"), UndeclaredHealthLine + " 1"} {
|
|
if !strings.Contains(out.String(), want) {
|
|
t.Errorf("the check does not say %q:\n%s", want, out.String())
|
|
}
|
|
}
|
|
|
|
checkNow = func() time.Time { return catalogue.HealthRequiredFrom }
|
|
out.Reset()
|
|
if err := moduleCheck([]string{path}, &out); err == nil {
|
|
t.Fatalf("a long-running resource without health passed after the date:\n%s", out.String())
|
|
}
|
|
if !strings.Contains(out.String(), "web: worker stays up and does not say how it is ready") {
|
|
t.Errorf("the refusal does not name the resource:\n%s", out.String())
|
|
}
|
|
}
|