mesh/merge-gate out of date: judged against trunk ebf7f3773dc7, the trunk is now e846c289c878; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk ebf7f3773dc7, the trunk is now e846c289c878; judged again on a recheck or when it is merged
mesh/delivery-group group fix/145-a-consumer-is-held-under-its-provider checking: composed check out of date: novox/mesh-catalog@b62bb158398d was judged agains…
mesh/delivery superseded: a newer head of the same pull request
gitea, nextcloud, umami and n8n gain an http check that reaches their database through their own endpoint; mesh-knowledge's watch already asks the database and now names it. While postgres is unhealthy, their findings are held under its condition instead of raising one each.
211 lines
8.7 KiB
Go
211 lines
8.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The manifest says what the code does (novox/hq to-be 55 §1): the seat it declares and holds once for the mesh
|
|
// with the verbs it serves, the events it says, what it uses and invokes, the database it requires, its two
|
|
// long-running processes with their health, and its settings.
|
|
|
|
type manifest struct {
|
|
Module string `json:"module"`
|
|
Requires []string `json:"requires"`
|
|
Contributes map[string]map[string]any `json:"contributes"`
|
|
Seats []seat `json:"seats"`
|
|
Claims []seat `json:"claims"`
|
|
Tools []string `json:"tools"`
|
|
Emits []string `json:"emits"`
|
|
Consumes []string `json:"consumes"`
|
|
Uses []string `json:"uses"`
|
|
Invokes []string `json:"invokes"`
|
|
State []json.RawMessage `json:"state"`
|
|
Resources []map[string]any `json:"resources"`
|
|
Build struct {
|
|
Artifacts []struct {
|
|
From string `json:"from"`
|
|
Binary string `json:"binary"`
|
|
Loads []string `json:"loads"`
|
|
Env map[string]string `json:"env"`
|
|
} `json:"artifacts"`
|
|
} `json:"build"`
|
|
}
|
|
|
|
type seat struct {
|
|
Name string `json:"name"`
|
|
Scope string `json:"scope"`
|
|
Serves []string `json:"serves"`
|
|
}
|
|
|
|
func readManifest(t *testing.T) (manifest, string) {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var m manifest
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m, string(raw)
|
|
}
|
|
|
|
func TestItDeclaresAndHoldsTheKnowledgeBaseSeatOnceForTheMesh(t *testing.T) {
|
|
m, raw := readManifest(t)
|
|
want := []seat{{Name: Seat, Scope: "mesh", Serves: Verbs}}
|
|
if m.Module != Module || !reflect.DeepEqual(m.Seats, want) || !reflect.DeepEqual(m.Claims, want) {
|
|
t.Fatalf("module %q, seats %+v, claims %+v", m.Module, m.Seats, m.Claims)
|
|
}
|
|
if !reflect.DeepEqual(Verbs, []string{"ask", "get", "holds", "links", "history", "reserve", "propose", "import", "return"}) {
|
|
t.Fatalf("the verbs are not ADR 0290 rule 6's: %v", Verbs)
|
|
}
|
|
if !reflect.DeepEqual(m.Emits, Events) {
|
|
t.Fatalf("emits %v, the code says %v", m.Emits, Events)
|
|
}
|
|
// Its records are in a database in the store (ADR 0290 rule 7), not in buckets.
|
|
if !reflect.DeepEqual(m.Requires, []string{"postgres-database"}) || m.Contributes["postgres-database"]["name"] != "knowledge" || len(m.State) != 0 {
|
|
t.Fatalf("requires %v, contributes %v, state %v", m.Requires, m.Contributes, m.State)
|
|
}
|
|
// It asks the operator on the router, hears the router's word, and calls exactly what it calls.
|
|
if !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) || len(m.Consumes) != 0 {
|
|
t.Fatalf("uses %v, consumes %v", m.Uses, m.Consumes)
|
|
}
|
|
if !reflect.DeepEqual(m.Invokes, []string{"seat:issue-tracker.reserve", "claude-code.claude_code_judge", "claude-code.claude_code_judged", "records.records_status",
|
|
"records.records_list", "records.records_read", "seat:operator-channel.notify"}) {
|
|
t.Fatalf("invokes %v", m.Invokes)
|
|
}
|
|
src := ""
|
|
for _, f := range []string{"main.go", "hq.go"} {
|
|
src += readFile(t, f)
|
|
}
|
|
for _, inv := range m.Invokes {
|
|
if !strings.Contains(src, `"`+inv) {
|
|
t.Errorf("the manifest invokes %s, which the code never calls", inv)
|
|
}
|
|
}
|
|
if len(m.Build.Artifacts) != 1 || m.Build.Artifacts[0].From != "cmd/mesh-knowledge" || m.Build.Artifacts[0].Binary != Module ||
|
|
m.Build.Artifacts[0].Env["DATABASE_URL_FILE"] == "" || m.Build.Artifacts[0].Env["MESH_KNOWLEDGE_SETTINGS"] == "" ||
|
|
m.Build.Artifacts[0].Env["MESH_KNOWLEDGE_STATUS"] == "" || !reflect.DeepEqual(m.Build.Artifacts[0].Loads, []string{Module}) {
|
|
t.Fatalf("build %+v", m.Build)
|
|
}
|
|
for _, never := range []string{"/home/", "jochen", "shanks", "novox.be"} {
|
|
if strings.Contains(strings.ToLower(raw), never) {
|
|
t.Errorf("module.json names %q", never)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Its processes: the preparation step once, keep judged by its unit, watch by the health tool, which the module
|
|
// serves; the status file watch writes is the one the bundle's health tool reads.
|
|
func TestItsProcessesAreThePreparationTheActAndTheJudge(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
byID := map[string]map[string]any{}
|
|
for _, r := range m.Resources {
|
|
byID[r["id"].(string)] = r
|
|
}
|
|
prep, keep, watch := byID["prepare"], byID["keep"], byID["watch"]
|
|
if prep == nil || prep["run-once"] != true || prep["run"].([]any)[1] != "prepare" {
|
|
t.Fatalf("prepare %v", prep)
|
|
}
|
|
if keep == nil || keep["run"].([]any)[1] != "keep" || keep["health"].(map[string]any)["kind"] != "unit" {
|
|
t.Fatalf("keep %v", keep)
|
|
}
|
|
h, _ := watch["health"].(map[string]any)
|
|
if watch == nil || watch["run"].([]any)[1] != "watch" || h["kind"] != "tool" || h["tool"] != HealthTool ||
|
|
!reflect.DeepEqual(m.Tools, []string{HealthTool, HighestTool}) {
|
|
t.Fatalf("watch %v, tools %v", watch, m.Tools)
|
|
}
|
|
// The watch's verdict is whether the database serves, so its check names the provision it exercises: while the
|
|
// database's provider is unhealthy, this finding is held under the provider's condition (hq ADR 0240 rule 5,
|
|
// issue 145).
|
|
if h["needs"] != "postgres-database" {
|
|
t.Fatalf("watch's check needs %v; it exercises postgres-database", h["needs"])
|
|
}
|
|
if watch["env"].(map[string]any)["MESH_KNOWLEDGE_STATUS"] != m.Build.Artifacts[0].Env["MESH_KNOWLEDGE_STATUS"] {
|
|
t.Fatal("watch writes another status file than the health tool reads")
|
|
}
|
|
// The health tool reads the status file as the account the runtime runs the bundle as, and the mesh gives that
|
|
// account the state directory (and nothing a process writes there later). A watch running as root wrote the
|
|
// file root's, 0600, and the tool could not open it (hq issue 479): watch runs as that account, as zfs's does.
|
|
if watch["user"] != "${machine:account}" {
|
|
t.Fatalf("watch runs as %v, not as the account the health tool reads its verdict as", watch["user"])
|
|
}
|
|
settings := byID["settings"]
|
|
var s Settings
|
|
if err := json.Unmarshal([]byte(settings["content"].(string)), &s); err != nil || s.JudgeModel == "" || s.ReservedFor != "168h" {
|
|
t.Fatalf("the settings the manifest writes are not the module's: %v %+v", err, s)
|
|
}
|
|
if d, err := time.ParseDuration(s.ReservedFor); err != nil || d != DefaultReservedFor {
|
|
t.Fatalf("reserved-for %q is not the default the code holds", s.ReservedFor)
|
|
}
|
|
}
|
|
|
|
// The tools are the seat's verbs and the health tool, each said in plain words with what it refuses.
|
|
func TestTheToolsAreTheSeatsVerbsAndSayWhatTheyDo(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
var verbs []string
|
|
for _, tool := range Tools(&KB{}, callerOf) {
|
|
s, verb, ok := strings.Cut(tool.Name, ".")
|
|
if !ok || s != Seat {
|
|
t.Errorf("%s is not a verb of %s", tool.Name, Seat)
|
|
}
|
|
verbs = append(verbs, verb)
|
|
if len(tool.Description) < 120 {
|
|
t.Errorf("%s says too little of what it does", tool.Name)
|
|
}
|
|
for _, internal := range []string{"ADR", "to-be", "bucket", "postgres", "KV"} {
|
|
if strings.Contains(tool.Description, internal) {
|
|
t.Errorf("%s's description uses the internal name %q", tool.Name, internal)
|
|
}
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(verbs, m.Claims[0].Serves) {
|
|
t.Fatalf("verbs %v, claim %v", verbs, m.Claims[0].Serves)
|
|
}
|
|
}
|
|
|
|
// No argument can name the caller: who acted is the runtime's word, unproven until it gives one.
|
|
func TestNoArgumentNamesTheCaller(t *testing.T) {
|
|
for _, tool := range Tools(&KB{}, callerOf) {
|
|
props := tool.Input["properties"].(map[string]any)
|
|
for _, who := range []string{"by", "caller", "author", "reviewer", "who"} {
|
|
if _, ok := props[who]; ok {
|
|
t.Errorf("%s takes %s", tool.Name, who)
|
|
}
|
|
}
|
|
if tool.Input["additionalProperties"] != false {
|
|
t.Errorf("%s takes arguments it does not name", tool.Name)
|
|
}
|
|
}
|
|
if callerOf(map[string]any{"by": "someone@else"}) != Unproven {
|
|
t.Fatal("the caller was read from an argument")
|
|
}
|
|
}
|
|
|
|
func TestTheHealthToolReadsTheWatchsVerdictAndSaysWhenItIsStale(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "health.json")
|
|
now := time.Date(2026, 10, 11, 9, 0, 0, 0, time.UTC)
|
|
if a := ReadStatus(path, now); a.Healthy {
|
|
t.Fatal("healthy with no verdict")
|
|
}
|
|
if err := writeStatus(path, HealthAnswer{State: "unhealthy", Why: "the rule cited: ADR 0300 is accepted and nothing cites it", At: now}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a := ReadStatus(path, now.Add(time.Minute)); a.Healthy || !strings.Contains(a.Why, "ADR 0300") {
|
|
t.Fatalf("%+v", a)
|
|
}
|
|
_ = writeStatus(path, HealthAnswer{State: "healthy", Why: "fine", At: now})
|
|
if a := ReadStatus(path, now.Add(time.Minute)); !a.Healthy {
|
|
t.Fatalf("%+v", a)
|
|
}
|
|
if a := ReadStatus(path, now.Add(StatusStale+time.Second)); a.Healthy || !strings.Contains(a.Why, "has not looked") {
|
|
t.Fatalf("a stale verdict: %+v", a)
|
|
}
|
|
}
|