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
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.
244 lines
7.6 KiB
Go
244 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// to-be 55 §10: "A drafted clause is never served as in force — the module's test: holds over a record with only
|
|
// drafted clauses answers none, and ask marks them." And §7: two separate judgements, the second seeing only the
|
|
// record and the drafts; the operator told once per topic; return drafts again.
|
|
|
|
// draftUntil takes drafting steps until the record's drafting reaches a state, at most a few.
|
|
func draftUntil(t *testing.T, w *world, k Key, state string) *Record {
|
|
t.Helper()
|
|
for i := 0; i < 40; i++ {
|
|
r := mustGet(t, w.k.Store, k).Current
|
|
if r.Drafting != nil && r.Drafting.State == state {
|
|
return r
|
|
}
|
|
if _, err := w.k.DraftStep(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
t.Fatalf("%s's drafting never reached %s", k, state)
|
|
return nil
|
|
}
|
|
|
|
func TestADraftedClauseIsNeverServedAsInForce(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
k := Key{KindDecision, 240}
|
|
r := draftUntil(t, w, k, DraftDrafted)
|
|
if len(r.Clauses) == 0 || r.Clauses[0].Source != SourceDrafted {
|
|
t.Fatalf("drafted: %+v", r.Clauses)
|
|
}
|
|
h, err := w.k.Holds("", &k, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(h.Clauses) != 0 || !strings.Contains(h.NotYet, "drafted clause(s) are not served") {
|
|
t.Fatalf("holds over a record with only drafted clauses: %+v", h)
|
|
}
|
|
// The topic's holds leaves them out too.
|
|
ht, _ := w.k.Holds(r.Topic, nil, 64<<10)
|
|
for _, c := range ht.Clauses {
|
|
if c.Record == k.String() {
|
|
t.Fatalf("the topic's holds served a drafted clause: %+v", c)
|
|
}
|
|
}
|
|
a, err := w.k.Ask("a module says how it is healthy and the node-engine judges it", 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
marked := false
|
|
for _, rec := range a.Records {
|
|
for _, c := range rec.Clauses {
|
|
if rec.Record == k.String() {
|
|
if !strings.HasPrefix(c.Holds, "drafted, not confirmed") {
|
|
t.Fatalf("ask served a drafted clause as %q", c.Holds)
|
|
}
|
|
marked = true
|
|
}
|
|
}
|
|
if rec.Record == k.String() && len(rec.Clauses) == 0 && strings.Contains(rec.Note, "drafted") {
|
|
marked = true
|
|
}
|
|
}
|
|
if !marked {
|
|
t.Fatalf("ask did not show 0240's drafted clauses as drafted: %+v", a)
|
|
}
|
|
// get marks each one.
|
|
g, _ := w.k.Get(GetAsk{Kind: KindDecision, Number: 240})
|
|
for _, c := range g.Clauses {
|
|
if !strings.HasPrefix(c.Holds, "drafted") {
|
|
t.Fatalf("get: %+v", c)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestClausesAreDraftedThenConfirmedByASeparateJudgementThatSeesOnlyTheRecordAndTheDrafts(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
k := Key{KindDecision, 240}
|
|
r := draftUntil(t, w, k, DraftDone)
|
|
var draftQ, confirmQ string
|
|
n := 0
|
|
for _, q := range w.judge.questions {
|
|
if !strings.Contains(q, "ADR 0240") {
|
|
continue
|
|
}
|
|
n++
|
|
if strings.Contains(q, "drafted from it by someone else") {
|
|
confirmQ = q
|
|
} else {
|
|
draftQ = q
|
|
}
|
|
}
|
|
if n != 2 || draftQ == "" || confirmQ == "" {
|
|
t.Fatalf("0240 was not drafted and confirmed by two separate judgements: %d questions", n)
|
|
}
|
|
section, _ := decisionSection(r)
|
|
if !strings.Contains(confirmQ, section) || !strings.Contains(confirmQ, r.Clauses[0].Text) {
|
|
t.Fatal("the confirming judgement was not shown the record and the drafts")
|
|
}
|
|
// It sees only those: nothing of the mesh but the record, and none of the first judgement's own words.
|
|
for _, other := range []string{"Write what it decides as numbered", "An earlier draft"} {
|
|
if strings.Contains(confirmQ, other) {
|
|
t.Fatalf("the confirming judgement was shown %q", other)
|
|
}
|
|
}
|
|
if !strings.Contains(draftQ, section) || strings.Contains(draftQ, "The drafted clauses") {
|
|
t.Fatal("the drafting judgement's question is not the section alone")
|
|
}
|
|
for _, c := range r.Clauses {
|
|
if c.Source != SourceConfirmed {
|
|
t.Fatalf("not confirmed: %+v", c)
|
|
}
|
|
}
|
|
if len(w.said.of(EventDrafted)) == 0 || len(w.said.of(EventConfirmed)) == 0 {
|
|
t.Fatalf("events: %v", w.said.events)
|
|
}
|
|
// Now confirmed, holds serves them, marked.
|
|
h, _ := w.k.Holds("", &k, 0)
|
|
if len(h.Clauses) != len(r.Clauses) || !strings.Contains(h.Clauses[0].Holds, "confirmed") {
|
|
t.Fatalf("holds after confirming: %+v", h)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAClauseTheSecondJudgementRejectsStaysDraftedWithTheReason(t *testing.T) {
|
|
w := imported(t, newMemStore())
|
|
k := Key{KindDecision, 240}
|
|
r := draftUntil(t, w, k, DraftDrafted)
|
|
ctx := context.Background()
|
|
e := mustGet(t, w.k.Store, k)
|
|
r = e.Current.Clone()
|
|
r.Clauses[0].Text += " NOT EXACT"
|
|
if err := w.k.Store.Save(ctx, Version{By: "test", Why: "a draft the checker will reject", Record: r}, true, false, e.Revision); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r = draftUntil(t, w, k, DraftDone)
|
|
if r.Clauses[0].Source != SourceDrafted || r.Clauses[0].Unconfirmed == "" {
|
|
t.Fatalf("a rejected clause: %+v", r.Clauses[0])
|
|
}
|
|
h, _ := w.k.Holds("", &k, 0)
|
|
for _, c := range h.Clauses {
|
|
if c.N == 1 {
|
|
t.Fatal("a clause the second judgement rejected is served")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheOperatorIsToldOncePerTopicAndReturnDraftsAgain(t *testing.T) {
|
|
w := imported(t, newMemStore())
|
|
for i := 0; i < 40; i++ {
|
|
said, err := w.k.DraftStep()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if said == "" {
|
|
break
|
|
}
|
|
}
|
|
// Every prose record of the fixture's topics drafted and confirmed; each topic told once.
|
|
topics := map[string]int{}
|
|
for _, s := range w.told {
|
|
topics[s]++
|
|
}
|
|
if len(w.told) == 0 {
|
|
t.Fatal("the operator was told of no topic")
|
|
}
|
|
for s, n := range topics {
|
|
if n != 1 {
|
|
t.Fatalf("told %d times: %s", n, s)
|
|
}
|
|
}
|
|
told := len(w.told)
|
|
k := Key{KindDecision, 240}
|
|
if _, err := w.k.Return("operator@shanks", Key{KindDecision, 276}, "x"); err == nil {
|
|
t.Fatal("a record whose author wrote its clauses was returned")
|
|
}
|
|
if _, err := w.k.Return("operator@shanks", k, "clause 2 says more than the section"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := mustGet(t, w.k.Store, k).Current
|
|
if len(r.Clauses) != 0 || r.Drafting.State != DraftWaiting || len(w.said.of(EventReturned)) != 1 {
|
|
t.Fatalf("returned: %+v", r)
|
|
}
|
|
r = draftUntil(t, w, k, DraftDone)
|
|
last := ""
|
|
for _, q := range w.judge.questions {
|
|
if strings.Contains(q, "An earlier draft was sent back") {
|
|
last = q
|
|
}
|
|
}
|
|
if !strings.Contains(last, "clause 2 says more than the section") {
|
|
t.Fatal("the drafting after a return did not read its reason")
|
|
}
|
|
if len(w.told) != told {
|
|
t.Fatal("the operator was told of the topic again")
|
|
}
|
|
}
|
|
|
|
// A supersedes-in-part hq wrote names the record; which clause it changes is judged, then confirmed, and only then
|
|
// is the older clause changed.
|
|
func TestWhichClauseAPartialSupersessionChangesIsJudgedThenConfirmed(t *testing.T) {
|
|
w := imported(t, newMemStore())
|
|
for i := 0; i < 40; i++ {
|
|
if said, err := w.k.DraftStep(); err != nil {
|
|
t.Fatal(err)
|
|
} else if said == "" {
|
|
break
|
|
}
|
|
}
|
|
r := mustGet(t, w.k.Store, Key{KindDecision, 282}).Current
|
|
var l *Link
|
|
for i := range r.Links {
|
|
if r.Links[i].Kind == LinkChanges && r.Links[i].To.Number == 276 {
|
|
l = &r.Links[i]
|
|
}
|
|
}
|
|
if l == nil || l.Source != SourceConfirmed || l.To.Clause != 1 {
|
|
t.Fatalf("0282's change of 0276: %+v", l)
|
|
}
|
|
c := mustGet(t, w.k.Store, Key{KindDecision, 276}).Current.Clause(1)
|
|
if c.Status != ClauseChanged || c.ChangedBy[0] != "ADR 0282" {
|
|
t.Fatalf("0276 §1: %+v", c)
|
|
}
|
|
}
|
|
|
|
func TestADraftingThatFailsIsSaidAndTriedAgainLater(t *testing.T) {
|
|
w := imported(t, newMemStore())
|
|
w.judge.fail = true
|
|
if _, err := w.k.DraftStep(); err == nil {
|
|
t.Fatal("a failed judgement was not said")
|
|
}
|
|
r := mustGet(t, w.k.Store, Key{KindDecision, 11}).Current
|
|
if r.Drafting.State != DraftFailed || r.Drafting.Tries != 1 || !strings.Contains(r.Drafting.Reason, "licence") {
|
|
t.Fatalf("%+v", r.Drafting)
|
|
}
|
|
}
|