Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/manifest_test.go
T
jochen ddf7a5ab09 Let a proposed record be withdrawn (issue 1081)
A dropped proposal or a stale reservation stayed proposed forever: there was
no way to say the operator decided against it. knowledge-base.withdraw takes
a reserved or proposed record to withdrawn as a new version naming why, ends
a waiting operator's ask and clears a pending review in the same version,
and refuses an already accepted, superseded or withdrawn record. The number
stays spent either way.

The old Withdraw (the background job for reservations never proposed) is
renamed WithdrawStale to make room for the new, caller-driven WithdrawRecord.
2026-10-18 00:02:48 +02:00

212 lines
8.9 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", "list", "findings", "holds", "links", "history", "reserve", "propose", "import", "return", "withdraw"}) {
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)
}
}
// The tools bundle, and the archive the build packs files/ into for the templates (novox/hq ADR 0349 §2).
if len(m.Build.Artifacts) != 2 || m.Build.Artifacts[1].From != "files" || 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(templateText(t, settings)), &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)
}
}