Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/review_test.go
T
jschoubben ebba63d71f
mesh/merge-gate pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
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
mesh-knowledge: review every proposal in a judgement of its own, and serve no review verb (hq issue 425, ADR 0290 as updated)
The mesh proves which machine called, not which agent on it (hq issue 365), so a review verb could not tell a
reviewer from the author. Each version proposed is now judged by claude-code.claude_code_judge in a separate
call that sees only the record, the records it links and the rules: approve accepts (or asks the operator for a
needs-operator record), changes keeps it proposed with the findings for its author, and a judgement that cannot
be had or read leaves it proposed, says so, and never counts as approval.
2026-10-11 12:06:18 +02:00

416 lines
18 KiB
Go

package main
import (
"context"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// to-be 55 §10: "A record is accepted only after the knowledge base's own judgement approves it, and, when it needs
// the operator, only on the warrant — the module's tests: a proposal whose judgement answers changes, or cannot be
// had, stays proposed; a needs operator record unaccepted without the warrant, accepted with it."
func proposed(t *testing.T, w *world, author string, needsOperator bool) int {
t.Helper()
n := w.reserved(t, author)
p := valid(n)
p.NeedsOperator = needsOperator
if _, err := w.k.Propose(author, p); err != nil {
t.Fatal(err)
}
return n
}
func TestAProposalIsReviewedByTheKnowledgeBaseItselfAndAcceptedOnApprove(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
w := imported(t, s)
n := proposed(t, w, "author@g14", false)
// Proposed, waiting for its review: nothing of it holds yet.
e := mustGet(t, s, Key{KindDecision, n})
if e.Current.Status != StatusProposed || e.Current.Judging == nil || e.Current.Judging.State != JudgingDue || len(w.judge.reviews) != 0 {
t.Fatalf("after propose: %s, %+v", e.Current.Status, e.Current.Judging)
}
said := w.reviewAll(t)
if len(said) != 1 || len(w.judge.reviews) != 1 {
t.Fatalf("reviews: %v, %d judgements", said, len(w.judge.reviews))
}
// The judgement saw the record, the records it links and the rules — nothing else of the knowledge base.
q := w.judge.reviews[0]
for _, want := range []string{"The window closes after ninety seconds of quiet", "ADR 0276", "a decision has at least one clause",
"Merges land sooner."} {
if !strings.Contains(q, want) {
t.Errorf("the review's question lacks %q", want)
}
}
for _, never := range []string{"ADR 0131", "ADR 0011", "author@g14"} {
if strings.Contains(q, never) {
t.Errorf("the review's question holds %q, which the record does not link", never)
}
}
e = mustGet(t, s, Key{KindDecision, n})
if e.Current.Status != StatusAccepted || e.Current.Clauses[0].Status != ClauseInForce || e.Current.AcceptedAt.IsZero() ||
e.Current.Judging != nil || len(e.Current.Reviews) != 1 || e.Current.Reviews[0].Verdict != VerdictApprove ||
e.Current.Reviews[0].By != "the agent on test (sonnet)" || e.Current.Reviews[0].Version != 2 {
t.Fatalf("not accepted: %+v", e.Current)
}
// 0276 §1 is changed by the new record's clause 1, as its changes link says.
c := mustGet(t, s, Key{KindDecision, 276}).Current.Clause(1)
if c.Status != ClauseChanged || len(c.ChangedBy) != 1 || c.ChangedBy[0] != itoaKey(n)+" §1" {
t.Fatalf("0276 §1: %+v", c)
}
if len(w.said.of(EventReviewed)) != 1 || len(w.said.of(EventAccepted)) != 1 || len(w.said.of(EventChanged)) != 1 {
t.Fatalf("events: %v", w.said.events)
}
h, err := w.k.Holds("", &Key{KindDecision, 276}, 0)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(h.Clauses[0].Holds, "changed by "+itoaKey(n)) {
t.Fatalf("holds: %+v", h.Clauses[0])
}
// Reviewed once: nothing waits any more.
if said := w.reviewAll(t); len(said) != 0 || len(w.judge.reviews) != 1 {
t.Fatalf("reviewed again: %v", said)
}
})
}
func itoaKey(n int) string { return Key{KindDecision, n}.String() }
// On changes the record stays proposed, the findings kept with the version they judged; only its author revises
// it, and the revision is reviewed again.
func TestOnChangesTheRecordStaysProposedAndItsAuthorsRevisionIsReviewedAgain(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
w := imported(t, s)
n := proposed(t, w, "author@g14", false)
w.judge.review = `{"verdict":"changes","findings":["§2: name the bound","Consequences: say who waits"]}`
w.reviewAll(t)
e := mustGet(t, s, Key{KindDecision, n})
if e.Current.Status != StatusProposed || e.Current.Judging != nil || len(e.Current.Reviews) != 1 ||
e.Current.Reviews[0].Verdict != VerdictChanges || e.Current.Reviews[0].Findings != "§2: name the bound\nConsequences: say who waits" ||
e.Current.Reviews[0].Version != 2 {
t.Fatalf("after changes: %+v", e.Current)
}
g, _ := w.k.Get(GetAsk{Number: n})
if len(g.Reviews) != 1 || g.Clauses[0].Holds != "proposed: not in force" {
t.Fatalf("get after changes: %+v", g)
}
// Not judged again until it is revised.
if said := w.reviewAll(t); len(said) != 0 || len(w.judge.reviews) != 1 {
t.Fatalf("judged again unrevised: %v", said)
}
p := valid(n)
p.Sections[2].Text = "Merges land sooner; nobody waits longer than ninety seconds."
if _, err := w.k.Propose("reviewer@novox", p); err == nil || !strings.Contains(err.Error(), "only its author") {
t.Fatalf("a revision by someone else: %v", err)
}
w.judge.review = ""
if _, err := w.k.Propose("author@g14", p); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
e = mustGet(t, s, Key{KindDecision, n})
if e.Current.Status != StatusAccepted || len(e.Current.Reviews) != 2 || e.Current.Reviews[1].Verdict != VerdictApprove ||
e.Current.Reviews[1].Version != 4 || e.Current.Reviews[0].Version != 2 {
t.Fatalf("after the revision: %s, %+v", e.Current.Status, e.Current.Reviews)
}
})
}
// A judgement that cannot be had — the judge unreachable, or its answer unreadable — leaves the record proposed,
// says so, and is never taken as an approval.
func TestAJudgementThatCannotBeHadNeverApproves(t *testing.T) {
for _, c := range []struct{ name, answer string }{
{"unreachable", ""},
{"not json", `the record looks fine to me`},
{"no verdict", `{"findings":[]}`},
{"another verdict", `{"verdict":"approved","findings":[]}`},
{"a verdict in capitals", `{"verdict":"APPROVE","findings":[]}`},
{"changes naming none", `{"verdict":"changes","findings":[" "]}`},
} {
t.Run(c.name, func(t *testing.T) {
w := imported(t, newMemStore())
n := proposed(t, w, "author@g14", false)
w.judge.review, w.judge.reviewFail = c.answer, c.answer == ""
for try := 1; try <= MostReviewTries; try++ {
if said := w.reviewAll(t); len(said) != 1 || !strings.Contains(said[0], "not reviewed") {
t.Fatalf("try %d: %v", try, said)
}
e := mustGet(t, w.k.Store, Key{KindDecision, n})
if e.Current.Status != StatusProposed || e.Current.Judging == nil || e.Current.Judging.State != JudgingFailed ||
e.Current.Judging.Tries != try || len(e.Current.Reviews) != 0 {
t.Fatalf("try %d: %s, %+v", try, e.Current.Status, e.Current.Judging)
}
g, _ := w.k.Get(GetAsk{Number: n})
if !strings.Contains(g.Review, "not reviewed") || !strings.Contains(g.Review, "counts as an approval") {
t.Fatalf("get does not say it: %q", g.Review)
}
// Not asked again at once: after a pause that grows with each try.
if said := w.reviewAll(t); len(said) != 0 {
t.Fatalf("asked again at once: %v", said)
}
w.clock = w.clock.Add(time.Duration(try) * 10 * time.Minute)
}
// Tried enough: it stays proposed, saying its author proposes it again.
w.clock = w.clock.Add(24 * time.Hour)
if said := w.reviewAll(t); len(said) != 0 {
t.Fatalf("asked after its last try: %v", said)
}
e := mustGet(t, w.k.Store, Key{KindDecision, n})
g, _ := w.k.Get(GetAsk{Number: n})
if e.Current.Status != StatusProposed || !strings.Contains(g.Review, "proposes it again") || len(w.said.of(EventAccepted)) != 0 {
t.Fatalf("after the last try: %s, %q", e.Current.Status, g.Review)
}
h, _ := w.k.Holds("", &Key{KindDecision, n}, 0)
if len(h.Clauses) != 0 {
t.Fatalf("a record never reviewed holds %v", h.Clauses)
}
// Its author proposes it again, and that version is reviewed.
w.judge.review, w.judge.reviewFail = "", false
w.accepted(t, "author@g14", valid(n))
})
}
}
// A record revised while its judgement was asked is not decided on the version judged: the newest is judged next.
func TestARecordRevisedWhileItIsJudgedIsJudgedAgain(t *testing.T) {
w := imported(t, newMemStore())
n := proposed(t, w, "author@g14", false)
p := valid(n)
p.Sections[2].Text = "Merges land sooner, and the walk is shorter."
w.judge.during = func() {
if _, err := w.k.Propose("author@g14", p); err != nil {
t.Error(err)
}
}
said := w.reviewAll(t)
if len(said) != 2 || !strings.Contains(said[0], "changed while it was judged") || len(w.judge.reviews) != 2 {
t.Fatalf("%v", said)
}
e := mustGet(t, w.k.Store, Key{KindDecision, n})
if e.Current.Status != StatusAccepted || e.Current.Section("Consequences") != p.Sections[2].Text || e.Current.Reviews[0].Version != 3 {
t.Fatalf("%s %+v", e.Current.Status, e.Current.Reviews)
}
}
// A record with a version written while the runtime proved no caller is never reviewed, and so never accepted:
// who wrote it cannot be told, and only its proven author revises it. Its author proposes it again.
func TestARecordProposedUnprovenIsNeverReviewed(t *testing.T) {
w := imported(t, newMemStore())
n := w.reserved(t, Unproven)
got, err := w.k.Propose(Unproven, valid(n))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(got.Says, "proposes it again under a new number") {
t.Fatalf("propose says %q", got.Says)
}
if said := w.reviewAll(t); len(said) != 0 || len(w.judge.reviews) != 0 {
t.Fatalf("an unproven proposal was judged: %v", said)
}
g, _ := w.k.Get(GetAsk{Number: n})
if g.Status != StatusProposed || !strings.Contains(g.Review, "proved no caller") {
t.Fatalf("%+v", g)
}
}
// A record that supersedes another moves it and every clause of it to superseded.
func TestASupersedingRecordSupersedesTheOlderOnAcceptance(t *testing.T) {
w := imported(t, newMemStore())
n := w.reserved(t, "author@g14")
p := valid(n)
p.Links = []LinkAsk{{Kind: LinkSupersedes, To: "0276"}}
p.Sections[0].Text = "The merge window is replaced."
w.accepted(t, "author@g14", p)
old := mustGet(t, w.k.Store, Key{KindDecision, 276}).Current
if old.Status != StatusSuperseded || old.Clause(4).Status != ClauseSuperseded || old.Extra["superseded-by"] != itoaKey(n) {
t.Fatalf("0276: %s, §4 %+v", old.Status, old.Clause(4))
}
h, _ := w.k.Holds("", &Key{KindDecision, 276}, 0)
if len(h.Clauses) != 0 {
t.Fatalf("a superseded record holds %v", h.Clauses)
}
}
func TestARecordThatNeedsTheOperatorIsAcceptedOnlyOnTheWarrant(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
w := imported(t, s)
n := proposed(t, w, "author@g14", true)
w.reviewAll(t)
if e := mustGet(t, s, Key{KindDecision, n}); e.Current.Status != StatusProposed || len(w.router.asked) != 1 {
t.Fatalf("after the review: %s, asks %d", e.Current.Status, len(w.router.asked))
}
ask := w.router.asked[0]
if err := ask.Check(w.clock); err != nil || ask.Options[0].Level != asks.Approve || ask.Options[1].Level != asks.Approve ||
!strings.Contains(ask.Explanation, "the knowledge base's own review") {
t.Fatalf("the ask: %v %+v", err, ask)
}
// Without the warrant: nothing is accepted, however often the router's record is read or the reviews run.
for i := 0; i < 2; i++ {
if _, err := w.k.Reconcile(); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
}
if e := mustGet(t, s, Key{KindDecision, n}); e.Current.Status != StatusProposed || len(w.judge.reviews) != 1 {
t.Fatalf("accepted with no warrant: %s", e.Current.Status)
}
h, _ := w.k.Holds("", &Key{KindDecision, n}, 0)
if len(h.Clauses) != 0 {
t.Fatalf("a record waiting for the operator holds %v", h.Clauses)
}
// The operator's Approve, on a channel that proves who answered: accepted, the warrant kept.
w.clock = w.clock.Add(10 * time.Minute)
warrant := w.router.answer(ask.ID, OptionApprove, w.clock)
said, err := w.k.Reconcile()
if err != nil {
t.Fatal(err)
}
e := mustGet(t, s, Key{KindDecision, n})
if e.Current.Status != StatusAccepted || e.Current.Ask == nil || e.Current.Ask.Warrant != warrant.Says() ||
e.Current.Ask.Said != AskApproved || len(said) != 1 {
t.Fatalf("on the warrant: %+v, ask %+v, said %v", e.Current.Status, e.Current.Ask, said)
}
vs, _ := s.Versions(context.Background(), Key{KindDecision, n})
if last := vs[len(vs)-1]; !strings.Contains(last.Why, "warrant") || last.By != "the operator" {
t.Fatalf("the accepting version: %+v", last)
}
if said, _ := w.k.Reconcile(); len(said) != 0 {
t.Fatalf("decided twice: %v", said)
}
})
}
// An ask that cannot be published is taken back: the record stays proposed and is reviewed again later.
func TestAnAskThatCannotBePublishedLeavesTheRecordProposed(t *testing.T) {
w := imported(t, newMemStore())
w.k.Asks = nil
n := proposed(t, w, "author@g14", true)
said := w.reviewAll(t)
e := mustGet(t, w.k.Store, Key{KindDecision, n})
if len(said) != 1 || !strings.Contains(said[0], "cannot be asked") || e.Current.Status != StatusProposed || e.Current.Ask != nil ||
e.Current.Judging == nil || e.Current.Judging.State != JudgingFailed {
t.Fatalf("%v: %s %+v", said, e.Current.Status, e.Current.Judging)
}
}
// A warrant for an ask whose record changed after it was asked does not hold: the act it bound is another version.
func TestAWarrantForAnotherVersionDoesNotAccept(t *testing.T) {
w := imported(t, newMemStore())
n := proposed(t, w, "author@g14", true)
w.reviewAll(t)
// The record is changed behind the ask, keeping the ask: what the operator was shown is not what is held.
ctx := context.Background()
e := mustGet(t, w.k.Store, Key{KindDecision, n})
r := e.Current.Clone()
r.Clauses[1].Text = "**A batch is delivered by many walks.** One commit per repository."
if err := w.k.Store.Save(ctx, Version{By: "someone", Why: "changed behind the ask", Record: r}, true, false, e.Revision); err != nil {
t.Fatal(err)
}
w.router.answer(w.router.asked[0].ID, OptionApprove, w.clock.Add(time.Minute))
if _, err := w.k.Reconcile(); err != nil {
t.Fatal(err)
}
e = mustGet(t, w.k.Store, Key{KindDecision, n})
if e.Current.Status != StatusProposed || !strings.Contains(e.Current.Ask.Said, "does not hold") {
t.Fatalf("accepted on a warrant for another version: %s, %+v", e.Current.Status, e.Current.Ask)
}
}
func TestTheOperatorsDeclineLeavesTheRecordProposed(t *testing.T) {
w := imported(t, newMemStore())
n := proposed(t, w, "author@g14", true)
w.reviewAll(t)
w.router.answer(w.router.asked[0].ID, OptionDecline, w.clock.Add(time.Minute))
if _, err := w.k.Reconcile(); err != nil {
t.Fatal(err)
}
e := mustGet(t, w.k.Store, Key{KindDecision, n})
if e.Current.Status != StatusProposed || e.Current.Ask.Said != "declined by the operator" {
t.Fatalf("%s %+v", e.Current.Status, e.Current.Ask)
}
}
// An accepted record changes only by a progressive insight, which is reviewed like a proposal while the accepted
// version is served.
func TestAnAcceptedRecordChangesOnlyByAnInsightReviewedLikeAProposal(t *testing.T) {
w := imported(t, newMemStore())
n := w.reserved(t, "author@g14")
w.accepted(t, "author@g14", valid(n))
p := valid(n)
p.Sections[2].Text = "Merges land sooner, by about a minute."
if _, err := w.k.Propose("author@g14", p); err == nil || !strings.Contains(err.Error(), "progressive insight") {
t.Fatalf("an accepted record rewritten: %v", err)
}
p.Insight = "it said only that merges land sooner"
w.judge.review = `{"verdict":"changes","findings":["Consequences: say what a minute is measured from"]}`
if _, err := w.k.Propose("author@g14", p); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
e := mustGet(t, w.k.Store, Key{KindDecision, n})
if e.Pending == nil || e.Current.Section("Consequences") != "Merges land sooner." || len(e.Pending.Reviews) != 1 ||
e.Pending.Reviews[0].Verdict != VerdictChanges {
t.Fatalf("the insight was served before its review approved it: %+v", e.Current.Sections)
}
w.judge.review = ""
p.Sections[2].Text = "Merges land sooner, by about a minute from the last merge."
if _, err := w.k.Propose("author@g14", p); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
e = mustGet(t, w.k.Store, Key{KindDecision, n})
if e.Pending != nil || e.Current.Status != StatusAccepted || len(e.Current.Insights) != 1 ||
e.Current.Section("Consequences") != p.Sections[2].Text {
t.Fatalf("after the insight's review: %+v", e.Current)
}
// 0276 §1 was changed once, by the acceptance; the insight keeps the link and changes it no further.
if c := mustGet(t, w.k.Store, Key{KindDecision, 276}).Current.Clause(1); len(c.ChangedBy) != 1 {
t.Fatalf("0276 §1 changed again by an insight: %v", c.ChangedBy)
}
}
// No verb takes a review from anyone: the review is the knowledge base's own.
func TestNoVerbTakesAReview(t *testing.T) {
for _, tool := range Tools(&KB{}, callerOf) {
if strings.HasSuffix(tool.Name, ".review") || strings.Contains(tool.Name, "review") {
t.Errorf("%s is served", tool.Name)
}
}
for _, v := range Verbs {
if v == "review" {
t.Error("review is a verb")
}
}
}
func TestReserveAsksTheTrackerAndANumberNeverProposedIsWithdrawn(t *testing.T) {
w := newWorld(t, newMemStore(), fixture())
r, err := w.k.ReserveNumber("author@g14", KindResearch, "how a knowledge base is asked")
if err != nil {
t.Fatal(err)
}
if r.Number != 50 || len(w.tracker.asked) != 1 || w.tracker.asked[0]["kind"] != KindResearch ||
w.tracker.asked[0]["for"] != "how a knowledge base is asked" {
t.Fatalf("%+v %v", r, w.tracker.asked)
}
if _, err := w.k.ReserveNumber("author@g14", "design", "x"); err == nil {
t.Fatal("a design number reserved")
}
w.clock = w.clock.Add(DefaultReservedFor + time.Hour)
if n, err := w.k.Withdraw(); err != nil || n != 1 {
t.Fatalf("withdrew %d (%v)", n, err)
}
e := mustGet(t, w.k.Store, Key{KindResearch, 50})
if e.Current.Status != StatusWithdrawn || e.Current.ReservedBy != "author@g14" {
t.Fatalf("%+v", e.Current)
}
if _, err := w.k.Propose("author@g14", ProposeAsk{Kind: KindResearch, Number: 50, Title: "late", Status: StatusActive,
Sections: []Section{{Name: "00-overview.md", Text: "x"}}}); err == nil {
t.Fatal("a withdrawn number took a proposal")
}
}