Add mesh-knowledge, the knowledge base's first slice (hq issue 425, ADR 0290)
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
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.
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user