Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/rules_test.go
T
jschoubben ba7b455719
mesh/merge-gate pass: builds new: modules/mesh-knowledge, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compo…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
Add mesh-knowledge, the knowledge base's first slice (hq issue 425, ADR 0290)
An agent learns what a decision says only by reading whole records. This module
holds every decision record and research effort whole in a database in the store,
with clauses that carry their own status and source, typed links read from both
ends, and every version. It answers ask, get and holds within a stated bound, and
takes a record through reserve, propose (refused on any of the 29 per-record
rules), review by someone other than the author, and acceptance, on the
operator's proven warrant when the record needs it. The 4 whole-set rules make
the module unhealthy when they fail. The import reads hq at a commit through the
records module, never overwrites, and changes nothing when run again. Prose
decisions get drafted clauses that a second judgement must confirm before they
are served.
2026-10-11 04:58:35 +02:00

222 lines
8.7 KiB
Go

package main
import (
"context"
"fmt"
"strings"
"testing"
)
// to-be 55 §10: "A proposal that breaks a per-record rule is refused, naming every reason — the module's table
// test, one case per rule of §4." Each case takes a proposal every rule takes and breaks one rule; the proposal is
// refused, the refusal names that rule, and nothing is written. A rule with no case fails the test.
func TestEveryPerRecordRuleRefusesAProposal(t *testing.T) {
cases := []struct {
rule string
break_ func(p *ProposeAsk, w *world, t *testing.T)
}{
{"kind", func(p *ProposeAsk, w *world, t *testing.T) {
// A kind the verb refuses before the rules: judged by the rules when a record of another kind is held.
p.Kind = "design"
}},
{"reserved", func(p *ProposeAsk, w *world, t *testing.T) { p.Number = 9999 }},
{"title", func(p *ProposeAsk, w *world, t *testing.T) { p.Title = "two\nlines" }},
{"topic", func(p *ProposeAsk, w *world, t *testing.T) { p.Topic = "gardening" }},
{"date", func(p *ProposeAsk, w *world, t *testing.T) { p.Date = "11 October" }},
{"deciders", func(p *ProposeAsk, w *world, t *testing.T) { p.Deciders = nil }},
{"research-status", func(p *ProposeAsk, w *world, t *testing.T) {
p.Kind, p.Number = KindResearch, w.reservedResearch(t)
p.Status, p.Links = "wondering", nil
p.Sections = []Section{{Name: "00-overview.md", Text: "An effort."}}
p.Clauses = nil
}},
{"has-clause", func(p *ProposeAsk, w *world, t *testing.T) { p.Clauses = nil; p.Links = p.Links[:1] }},
{"clause-numbering", func(p *ProposeAsk, w *world, t *testing.T) {
p.Clauses = []string{"1. **One.** A.", "3. **Three.** C."}
}},
{"clause-lead", func(p *ProposeAsk, w *world, t *testing.T) { p.Clauses[1] = "A batch is delivered by one walk." }},
{"clause-size", func(p *ProposeAsk, w *world, t *testing.T) {
p.Clauses[1] = "**Long.** " + strings.Repeat("word ", 500)
}},
{"context", func(p *ProposeAsk, w *world, t *testing.T) { p.Sections = p.Sections[1:] }},
{"consequences", func(p *ProposeAsk, w *world, t *testing.T) { p.Sections = append(p.Sections[:2], p.Sections[3]) }},
{"checked", func(p *ProposeAsk, w *world, t *testing.T) { p.Sections = p.Sections[:3] }},
{"overview", func(p *ProposeAsk, w *world, t *testing.T) {
p.Kind, p.Number, p.Status, p.Clauses, p.Links = KindResearch, w.reservedResearch(t), StatusActive, nil, nil
p.Sections = []Section{{Name: "01-findings.md", Text: "Found."}}
}},
{"link-kind", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: "admires", To: "0276"})
}},
{"link-target", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: LinkCites, To: "0007"})
}},
{"link-fits", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: LinkBecame, To: "0276"})
}},
{"self-link", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: LinkCites, To: itoa4(p.Number)})
}},
{"rests-on", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: LinkExtends, To: "0127"})
}},
{"supersedes-accepted", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: LinkSupersedes, To: "0127"})
}},
{"changes-in-force", func(p *ProposeAsk, w *world, t *testing.T) {
// 0131 superseded 0127, so 0127's clauses are superseded; a changes link to one names a clause no longer in force.
w.supersededClause(t)
p.Links = append(p.Links, LinkAsk{Kind: LinkChanges, To: "0011 §1", From: 2})
}},
{"live-citation", func(p *ProposeAsk, w *world, t *testing.T) {
p.Links = append(p.Links, LinkAsk{Kind: LinkCites, To: "0127"})
}},
{"became", func(p *ProposeAsk, w *world, t *testing.T) {
p.Kind, p.Number, p.Status, p.Clauses, p.Links = KindResearch, w.reservedResearch(t), StatusGraduated, nil, nil
p.Sections = []Section{{Name: "00-overview.md", Text: "An effort that graduated."}}
}},
{"text-links", func(p *ProposeAsk, w *world, t *testing.T) {
p.Sections[0].Text += " See [ADR 0007](0007-a-record-nobody-holds.md)."
}},
{"retired-word", func(p *ProposeAsk, w *world, t *testing.T) {
p.Sections[2].Text = "The control plane is quicker."
}},
{"daemon-line", func(p *ProposeAsk, w *world, t *testing.T) {
p.Sections[3].Text = "Run `mesh-controller doctor` after the merge."
}},
{"deferral", func(p *ProposeAsk, w *world, t *testing.T) {
p.Sections[2].Text = "The bound itself is left for later."
}},
{"insight-form", func(p *ProposeAsk, w *world, t *testing.T) {
p.Sections[2].Text = "A progressive insight corrected the count."
}},
}
covered := map[string]bool{}
for _, c := range cases {
covered[c.rule] = true
}
for _, r := range PerRecordRules {
if !covered[r.ID] {
t.Errorf("the rule %s has no case", r.ID)
}
}
if len(PerRecordRules) != 29 {
t.Errorf("%d per-record rules; ADR 0290 counts 29", len(PerRecordRules))
}
for _, c := range cases {
t.Run(c.rule, func(t *testing.T) {
w := imported(t, newMemStore())
n := w.reserved(t, "author@test")
p := valid(n)
c.break_(&p, w, t)
if c.rule == "kind" {
// The verb refuses a kind it does not hold before the rules; the rule itself is held by JudgeRecord.
ref, _ := w.k.reference()
r := &Record{Kind: "design", Number: n, Title: "x", Date: "2026-10-11"}
if !names(JudgeRecord(r, mustGet(t, w.k.Store, Key{KindDecision, n}), w.k.lookup(context.Background()), ref), "kind") {
t.Fatal("a record of an unknown kind was not refused by the kind rule")
}
if _, err := w.k.Propose("author@test", p); err == nil {
t.Fatal("propose took a record of an unknown kind")
}
return
}
before := versionsOf(t, w, Key{p.Kind, p.Number})
_, err := w.k.Propose("author@test", p)
if err == nil {
t.Fatalf("a proposal breaking %s was taken", c.rule)
}
if !strings.Contains(err.Error(), "- "+c.rule+":") {
t.Fatalf("the refusal does not name %s: %v", c.rule, err)
}
if after := versionsOf(t, w, Key{p.Kind, p.Number}); after != before {
t.Fatalf("a refused proposal wrote a version (%d, was %d)", after, before)
}
})
}
}
// One proposal breaking three rules is refused naming all three, at once.
func TestARefusalNamesEveryReasonAtOnce(t *testing.T) {
w := imported(t, newMemStore())
p := valid(w.reserved(t, "author@test"))
p.Topic = "gardening"
p.Clauses[1] = "no lead here."
p.Sections[2].Text = "The control plane is quicker."
_, err := w.k.Propose("author@test", p)
if err == nil {
t.Fatal("taken")
}
for _, rule := range []string{"topic", "clause-lead", "retired-word"} {
if !strings.Contains(err.Error(), "- "+rule+":") {
t.Errorf("the refusal does not name %s: %v", rule, err)
}
}
}
// The proposal every case breaks is taken as it is: each case fails for its own rule, not for the base's.
func TestTheValidProposalIsTaken(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
w := imported(t, s)
n := w.reserved(t, "author@test")
got, err := w.k.Propose("author@test", valid(n))
if err != nil {
t.Fatal(err)
}
e := mustGet(t, s, Key{KindDecision, n})
if got.Status != StatusProposed || e.Current.Status != StatusProposed || len(e.Current.Clauses) != 2 ||
e.Current.Clauses[0].Status != ClauseProposed || e.Current.Author != "author@test" {
t.Fatalf("%+v / %+v", got, e.Current)
}
if len(w.said.of(EventProposed)) != 1 || len(w.tracker.asked) != 1 || w.tracker.asked[0]["kind"] != KindDecision {
t.Fatalf("events %v, tracker %v", w.said.events, w.tracker.asked)
}
})
}
func names(refusals []Refusal, rule string) bool {
for _, r := range refusals {
if r.Rule == rule {
return true
}
}
return false
}
func versionsOf(t *testing.T, w *world, k Key) int {
t.Helper()
e, err := w.k.Store.Get(context.Background(), k)
if err != nil {
t.Fatal(err)
}
if e == nil {
return 0
}
return e.Versions
}
func itoa4(n int) string { return fmt.Sprintf("%04d", n) }
func (w *world) reservedResearch(t *testing.T) int {
t.Helper()
r, err := w.k.ReserveNumber("author@test", KindResearch, "a test effort")
if err != nil {
t.Fatal(err)
}
return r.Number
}
// supersededClause gives 0011, accepted, one clause a later record superseded, so a change to that clause names
// one no longer in force.
func (w *world) supersededClause(t *testing.T) {
t.Helper()
ctx := context.Background()
e := mustGet(t, w.k.Store, Key{KindDecision, 11})
r := e.Current.Clone()
r.Clauses = []Clause{{N: 1, Text: "**Generated.** Managed files are generated.", Status: ClauseSuperseded, Source: SourceWritten,
ChangedBy: []string{"ADR 0012"}}}
if err := w.k.Store.Save(ctx, Version{By: "test", Why: "superseded for the test", Record: r}, true, false, e.Revision); err != nil {
t.Fatal(err)
}
}