CAT-K10: the module's tools read each state it keeps on the bus and do by hand what it does itself
knowledge_head, _operations, _section, _draft, _parts, _asked, _fill, _rebuild_index, _review_again, _integrity, _move and _move_check, beside the health, highest and marks tools; the manifest lists them and a test compares them with the code. Issue 1966. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -106,7 +106,7 @@ func serve(k *KB, status string) {
|
||||
k.kick = make(chan struct{}, 1)
|
||||
go hear(k)
|
||||
go background(k)
|
||||
if err := stdio.Serve("", append(Tools(k, callerOf), healthTool(status, k.now), highestTool(k), marksTool(k))); err != nil {
|
||||
if err := stdio.Serve("", append(append(Tools(k, callerOf), healthTool(status, k.now), highestTool(k), marksTool(k)), moduleTools(k, callerOf)...)); err != nil {
|
||||
logf("%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func TestItsProcessesAreThePreparationTheActAndTheJudge(t *testing.T) {
|
||||
}
|
||||
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, MarksTool}) {
|
||||
!reflect.DeepEqual(m.Tools, ModuleTools) {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
package main
|
||||
|
||||
// The module's tools (novox/hq to-be 55 §1.1, the instruction tools-are-how-a-module-is-spoken-to): the one way a person or a
|
||||
// session reads and steers what the module keeps. Every state has a tool that reads it, every act the module does by itself
|
||||
// can be done by hand, and every recovery has a tool.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
)
|
||||
|
||||
// The module's tools beside the seat's verbs.
|
||||
const (
|
||||
HeadTool = "knowledge_head"
|
||||
OperationsTool = "knowledge_operations"
|
||||
SectionTool = "knowledge_section"
|
||||
DraftTool = "knowledge_draft"
|
||||
PartsTool = "knowledge_parts"
|
||||
AskedTool = "knowledge_asked"
|
||||
FillTool = "knowledge_fill"
|
||||
IndexTool = "knowledge_rebuild_index"
|
||||
AgainTool = "knowledge_review_again"
|
||||
IntegrityTool = "knowledge_integrity"
|
||||
MoveTool = "knowledge_move"
|
||||
MoveCheckTool = "knowledge_move_check"
|
||||
)
|
||||
|
||||
// ModuleTools are the module's own tools beside the health tool, in the order the manifest lists them.
|
||||
var ModuleTools = []string{HealthTool, HighestTool, MarksTool, HeadTool, OperationsTool, SectionTool, DraftTool, PartsTool,
|
||||
AskedTool, FillTool, IndexTool, AgainTool, IntegrityTool, MoveTool, MoveCheckTool}
|
||||
|
||||
var errNotOnLedger = errors.New("the records are still in the database: this tool reads the ledger, which serves them after the move")
|
||||
|
||||
// ledger is the ledger store the records are served from, or the refusal that they are not.
|
||||
func (k *KB) ledger() (*ledgerStore, error) {
|
||||
switch s := k.Store.(type) {
|
||||
case *ledgerStore:
|
||||
return s, nil
|
||||
case *movableStore:
|
||||
if s.OnLedger() {
|
||||
return s.led, nil
|
||||
}
|
||||
}
|
||||
return nil, errNotOnLedger
|
||||
}
|
||||
|
||||
func (k *KB) movable() (*movableStore, error) {
|
||||
if m, ok := k.Store.(*movableStore); ok {
|
||||
return m, nil
|
||||
}
|
||||
return nil, errors.New("this knowledge base has no database to move from")
|
||||
}
|
||||
|
||||
func keyArgs(name string, m map[string]any, more ...string) (Key, args, error) {
|
||||
a, err := readArgs(name, m, append([]string{"kind", "number"}, more...)...)
|
||||
if err != nil {
|
||||
return Key{}, args{}, err
|
||||
}
|
||||
k, err := a.key()
|
||||
return k, a, err
|
||||
}
|
||||
|
||||
func keyProps(more map[string]any) map[string]any {
|
||||
p := map[string]any{"kind": kindArg(), "number": integer("the record's number")}
|
||||
for k, v := range more {
|
||||
p[k] = v
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// moduleTools are the tools beside the seat's verbs that read and steer the records on the bus.
|
||||
func moduleTools(k *KB, caller func(map[string]any) string) []stdio.Tool {
|
||||
return []stdio.Tool{
|
||||
{Name: HeadTool,
|
||||
Description: "One record's head in the ledger, whole: its fields, clauses, links, its served and proposed versions, its sections as " +
|
||||
"title, digest and the operation that wrote each, and the sequence of the last operation it holds. Refused while the records are in the database.",
|
||||
Input: schema([]string{"number"}, keyProps(nil)),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
key, _, err := keyArgs(HeadTool, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := k.ledger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
h, err := l.fresh(key)
|
||||
if err != nil || h == nil {
|
||||
return nil, fmt.Errorf("%s is held of none (%v)", key, err)
|
||||
}
|
||||
return h, nil
|
||||
}},
|
||||
{Name: OperationsTool,
|
||||
Description: "A record's history in the ledger, oldest first from a sequence: each operation with its sequence, its name, who it was by " +
|
||||
"(the module's account and the caller the runtime proved) and when. A section's text is not shown; knowledge_section reads it.",
|
||||
Input: schema([]string{"number"}, keyProps(map[string]any{"from": integer("the sequence to read from"), "limit": integer("at most this many")})),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
key, a, err := keyArgs(OperationsTool, m, "from", "limit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
from, _ := a.num("from", false)
|
||||
limit, _ := a.num("limit", false)
|
||||
l, err := k.ledger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var page buscalls.HistoryPage
|
||||
if err := l.pipe.Call(buscalls.LedgerHistory, buscalls.HistoryRequest{Ledger: LedgerName, Instance: InstanceOf(key), From: uint64(from), Limit: limit}, &page); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range page.Operations {
|
||||
page.Operations[i].Body = nil // a section's text and a head are read by their own tools
|
||||
}
|
||||
return page, nil
|
||||
}},
|
||||
{Name: SectionTool,
|
||||
Description: "One section's text, by the sequence of the write-section operation that wrote it, checked against its digest. Refused when the " +
|
||||
"operation is no write-section of the record, or its text does not hash to its digest (the history is damaged).",
|
||||
Input: schema([]string{"number", "sequence"}, keyProps(map[string]any{"sequence": integer("the sequence of the write-section")})),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
key, a, err := keyArgs(SectionTool, m, "sequence")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seq, err := a.num("sequence", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := k.ledger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var page buscalls.HistoryPage
|
||||
if err := l.pipe.Call(buscalls.LedgerHistory, buscalls.HistoryRequest{Ledger: LedgerName, Instance: InstanceOf(key), From: uint64(seq), Limit: 1}, &page); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(page.Operations) == 0 || page.Operations[0].Sequence != uint64(seq) || page.Operations[0].Operation != OpWriteSection {
|
||||
return nil, fmt.Errorf("operation %d of %s is no write-section", seq, key)
|
||||
}
|
||||
var b struct{ Title, Text, Digest string }
|
||||
if err := json.Unmarshal(page.Operations[0].Body, &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if SectionDigest(b.Text) != b.Digest {
|
||||
return nil, fmt.Errorf("the text of operation %d does not hash to its digest %s: the history is damaged", seq, b.Digest)
|
||||
}
|
||||
return map[string]any{"title": b.Title, "digest": b.Digest, "text": b.Text, "author": page.Operations[0].Author, "at": page.Operations[0].At}, nil
|
||||
}},
|
||||
{Name: DraftTool,
|
||||
Description: "A record's draft: its author, since when, the version it starts from, its fields and its sections as title, digest and the operation that wrote each. " +
|
||||
"Answers that there is none when none is open.",
|
||||
Input: schema([]string{"number"}, keyProps(nil)),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
key, _, err := keyArgs(DraftTool, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, _, err := k.readDraft(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d == nil {
|
||||
return map[string]any{"record": key.String(), "says": "no draft is open"}, nil
|
||||
}
|
||||
return d, nil
|
||||
}},
|
||||
{Name: PartsTool,
|
||||
Description: "The latest verdict of each part of a record's version being worked on or served: the head and each section, the digest it was judged with, " +
|
||||
"its verdict and findings, who judged and when.",
|
||||
Input: schema([]string{"number"}, keyProps(nil)),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
key, _, err := keyArgs(PartsTool, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e, err := k.Store.Get(context.Background(), key)
|
||||
if err != nil || e == nil {
|
||||
return nil, fmt.Errorf("%s is held of none (%v)", key, err)
|
||||
}
|
||||
return map[string]any{"record": key.String(), "parts": e.Working().Parts}, nil
|
||||
}},
|
||||
{Name: AskedTool,
|
||||
Description: "The parts asked of the judge and not answered yet: the record, the part and since when. With clear and a key, takes one mark away " +
|
||||
"(a part whose answer will not come); the record's review asks it again.",
|
||||
Input: schema(nil, map[string]any{"clear": text("a key to take away")}),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
a, err := readArgs(AskedTool, m, "clear")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kv := k.States.State(askedState)
|
||||
if c, _ := a.str("clear"); c != "" {
|
||||
return map[string]any{"cleared": c}, kv.Delete(c)
|
||||
}
|
||||
keys, err := kv.Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Strings(keys)
|
||||
out := []any{}
|
||||
for _, key := range keys {
|
||||
if e, err := kv.Get(key); err == nil && e != nil {
|
||||
var v any
|
||||
_ = json.Unmarshal(e.Value, &v)
|
||||
out = append(out, map[string]any{"key": key, "asked": v})
|
||||
}
|
||||
}
|
||||
return map[string]any{"asked": out}, nil
|
||||
}},
|
||||
{Name: FillTool,
|
||||
Description: "How full the module's kept data is against its caps: the stream of the parts' verdicts (messages, bytes and its cap), and the number of keys of each state.",
|
||||
Input: schema(nil, map[string]any{}),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
if _, err := readArgs(FillTool, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[string]any{}
|
||||
if l, err := k.ledger(); err == nil {
|
||||
var f buscalls.StreamFilled
|
||||
if err := l.pipe.Call(buscalls.StreamFill, buscalls.StreamRequest{Stream: "reviews"}, &f); err != nil {
|
||||
out["reviews"] = "cannot be read: " + err.Error()
|
||||
} else {
|
||||
out["reviews"] = f
|
||||
}
|
||||
}
|
||||
keys := map[string]int{}
|
||||
for _, s := range []string{draftsState, askedState, findingsState, sentState} {
|
||||
if ks, err := k.States.State(s).Keys(); err == nil {
|
||||
keys[s] = len(ks)
|
||||
}
|
||||
}
|
||||
out["keys"] = keys
|
||||
return out, nil
|
||||
}},
|
||||
{Name: IndexTool,
|
||||
Description: "Build the search index again from the ledger now, and say how it stands: built, when, and how many documents it holds. ask and holds answer " +
|
||||
"\"index not built\" meanwhile.",
|
||||
Input: schema(nil, map[string]any{}),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
if _, err := readArgs(IndexTool, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := k.ledger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := l.BuildIndex(context.Background()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.IndexState(), nil
|
||||
}},
|
||||
{Name: AgainTool,
|
||||
Description: "Ask a record's review again, for a version still proposed: its parts that approved keep their verdicts and the others are judged " +
|
||||
"again; the review that ran out of tries starts its tries anew. Refused for a record that is not proposed.",
|
||||
Input: schema([]string{"number"}, keyProps(nil)),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
key, _, err := keyArgs(AgainTool, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.ReviewAgain(caller(m), key)
|
||||
}},
|
||||
{Name: IntegrityTool,
|
||||
Description: "Check that every section a head or a draft names resolves to a write-section whose text hashes to its digest, for one record or for " +
|
||||
"all of them, and say each that does not.",
|
||||
Input: schema(nil, keyProps(nil)),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
a, err := readArgs(IntegrityTool, m, "kind", "number")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := k.ledger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var keys []Key
|
||||
if _, has := m["number"]; has {
|
||||
key, err := a.key()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = []Key{key}
|
||||
} else {
|
||||
all, err := l.All(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range all {
|
||||
keys = append(keys, e.Current.Key())
|
||||
}
|
||||
}
|
||||
bad := []string{}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
h, err := l.fresh(key)
|
||||
if err != nil || h == nil {
|
||||
bad = append(bad, fmt.Sprintf("%s: %v", key, err))
|
||||
continue
|
||||
}
|
||||
for _, sr := range []*storedRecord{h.Entry.Current, h.Entry.Pending} {
|
||||
if sr == nil {
|
||||
continue
|
||||
}
|
||||
for _, ref := range sr.Sections {
|
||||
delete(l.texts, ref.Digest) // read from the history, not from what was held
|
||||
if _, err := l.text(ref, key); err != nil {
|
||||
bad = append(bad, fmt.Sprintf("%s: %v", key, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]any{"checked": len(keys), "damaged": bad}, nil
|
||||
}},
|
||||
{Name: MoveTool,
|
||||
Description: "Move the records from the database to the ledger: with dry-run, say what a copy would write and write nothing; with commit, refuse writes for " +
|
||||
"its length, copy every version of every record, check the copy record by record, and set the records to be served from the ledger only if the check is " +
|
||||
"clean. Run again after a copy it changes nothing. Refused once the records are served from the ledger.",
|
||||
Input: schema(nil, map[string]any{"dry-run": flag("count and write nothing"), "commit": flag("copy, check and switch")}),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
a, err := readArgs(MoveTool, m, "dry-run", "commit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ask MoveAsk
|
||||
if ask.DryRun, err = a.flag("dry-run"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ask.Commit, err = a.flag("commit"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err := k.movable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ms.Move(context.Background(), ask, func(s string) { k.logf("[mesh-knowledge] the move: %s", s) })
|
||||
}},
|
||||
{Name: MoveCheckTool,
|
||||
Description: "Compare the database with the ledger record by record now: the versions, the served and the proposed version whole and every section's text. " +
|
||||
"Answers every difference, at most 50, and where the records are served from; a database that is behind is named.",
|
||||
Input: schema(nil, map[string]any{}),
|
||||
Run: func(m map[string]any) (any, error) {
|
||||
if _, err := readArgs(MoveCheckTool, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err := k.movable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diffs, err := ms.Check(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"source": ms.Source(), "differences": diffs, "clean": len(diffs) == 0}, nil
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// Review is the record's review asked again.
|
||||
type ReviewedAgain struct {
|
||||
Record string `json:"record"`
|
||||
Says string `json:"says"`
|
||||
}
|
||||
|
||||
// ReviewAgain asks the review of a proposed version again: a failed review starts its tries anew, and a review whose verdict
|
||||
// was changes is made due, the parts that approved keeping their verdicts.
|
||||
func (k *KB) ReviewAgain(caller string, key Key) (*ReviewedAgain, error) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
ctx := k.ctxFor(caller)
|
||||
e, err := k.Store.Get(ctx, key)
|
||||
if err != nil || e == nil {
|
||||
return nil, fmt.Errorf("%s is held of none (%v)", key, err)
|
||||
}
|
||||
w := e.Working().Clone()
|
||||
if w.Status != StatusProposed {
|
||||
return nil, fmt.Errorf("%s is %s, not proposed: there is no review to ask again", key, w.Status)
|
||||
}
|
||||
if !proven(caller) {
|
||||
return nil, fmt.Errorf("review-again needs a caller the runtime proved; this call is by %s", caller)
|
||||
}
|
||||
w.Judging = &Judging{State: JudgingDue}
|
||||
if err := k.Store.Save(ctx, Version{By: caller, At: k.now(), Why: "its review is asked again", Record: w, Insight: e.Pending != nil && e.Pending.IsInsight()}, e.Pending == nil, false, e.Revision); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.wake()
|
||||
return &ReviewedAgain{Record: key.String(), Says: "The review of " + key.String() + " is due again; the parts that approved keep their verdicts."}, nil
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func callTool(t *testing.T, tools []toolRun, name string, args map[string]any) (map[string]any, error) {
|
||||
t.Helper()
|
||||
for _, tool := range tools {
|
||||
if tool.name == name {
|
||||
out, err := tool.run(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, _ := json.Marshal(out)
|
||||
var m map[string]any
|
||||
_ = json.Unmarshal(raw, &m)
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
t.Fatalf("no tool %s", name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type toolRun struct {
|
||||
name string
|
||||
run func(map[string]any) (any, error)
|
||||
}
|
||||
|
||||
// Every state the module keeps on the bus has a tool that reads it, and every act it does by itself one that does it by
|
||||
// hand (to-be 55 §1.1, AC54): the head, the history, a section, a draft, the parts' verdicts, the parts in flight, the fill,
|
||||
// the index, the review asked again, the integrity of the sections, the move and its check.
|
||||
func TestEveryStateTheModuleKeepsHasAToolThatReadsIt(t *testing.T) {
|
||||
w, f := ledgerWorld(t)
|
||||
var tools []toolRun
|
||||
for _, tool := range moduleTools(w.k, callerOf) {
|
||||
tools = append(tools, toolRun{tool.Name, tool.Run})
|
||||
}
|
||||
r, _ := w.k.ReserveNumber("author@g14", KindDecision, "a test record")
|
||||
p := valid(r.Number)
|
||||
p.Sections[0].Text = "The merge window of ADR 0276 §1 waits too long."
|
||||
key := map[string]any{"number": float64(r.Number)}
|
||||
if _, err := w.k.Open("author@g14", ProposeAsk{Kind: KindDecision, Number: r.Number, Title: p.Title, Topic: p.Topic, Date: p.Date, Deciders: p.Deciders, Clauses: p.Clauses, Links: p.Links, Homes: p.Homes}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := w.k.Write("author@g14", Key{KindDecision, r.Number}, "Context", p.Sections[0].Text); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d, err := callTool(t, tools, DraftTool, key)
|
||||
if err != nil || d["author"] != "author@g14" || len(d["sections"].([]any)) != 1 {
|
||||
t.Fatalf("%v %v", err, d)
|
||||
}
|
||||
for _, s := range p.Sections[1:] {
|
||||
_, _ = w.k.Write("author@g14", Key{KindDecision, r.Number}, s.Name, s.Text)
|
||||
}
|
||||
if _, err := w.k.SubmitDraft("author@g14", Key{KindDecision, r.Number}, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.reviewAll(t)
|
||||
|
||||
h, err := callTool(t, tools, HeadTool, key)
|
||||
if err != nil || h["entry"] == nil {
|
||||
t.Fatalf("%v %v", err, h)
|
||||
}
|
||||
ops, err := callTool(t, tools, OperationsTool, key)
|
||||
if err != nil || len(ops["operations"].([]any)) < 6 {
|
||||
t.Fatalf("%v %v", err, ops)
|
||||
}
|
||||
var first map[string]any
|
||||
for _, o := range ops["operations"].([]any) {
|
||||
op := o.(map[string]any)
|
||||
if op["body"] != nil {
|
||||
t.Fatalf("the history shows a body: %v", op)
|
||||
}
|
||||
if first == nil && op["operation"] == OpWriteSection {
|
||||
first = op
|
||||
}
|
||||
}
|
||||
sec, err := callTool(t, tools, SectionTool, map[string]any{"number": key["number"], "sequence": first["sequence"]})
|
||||
if err != nil || sec["text"] != p.Sections[0].Text || sec["title"] != "Context" {
|
||||
t.Fatalf("%v %v", err, sec)
|
||||
}
|
||||
parts, err := callTool(t, tools, PartsTool, key)
|
||||
if err != nil || len(parts["parts"].([]any)) != 5 {
|
||||
t.Fatalf("%v %v", err, parts)
|
||||
}
|
||||
if _, err := callTool(t, tools, AskedTool, map[string]any{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fill, err := callTool(t, tools, FillTool, map[string]any{}); err != nil || fill["keys"] == nil {
|
||||
t.Fatalf("%v %v", err, fill)
|
||||
}
|
||||
if st, err := callTool(t, tools, IndexTool, map[string]any{}); err != nil || st["built"] != true {
|
||||
t.Fatalf("%v %v", err, st)
|
||||
}
|
||||
// The integrity of the sections: all well, then one damaged text in the history is named.
|
||||
if res, err := callTool(t, tools, IntegrityTool, map[string]any{}); err != nil || len(res["damaged"].([]any)) != 0 {
|
||||
t.Fatalf("%v %v", err, res)
|
||||
}
|
||||
hist := f.hist[InstanceOf(Key{KindDecision, r.Number})]
|
||||
for i := range hist {
|
||||
if hist[i].Operation == OpWriteSection && strings.Contains(string(hist[i].Body), "Considered Options") {
|
||||
hist[i].Body = mustJSON(t, map[string]any{"title": "Considered Options", "text": "damaged", "digest": SectionDigest("1. **Keep it.** Rejected.")})
|
||||
}
|
||||
}
|
||||
if res, _ := callTool(t, tools, IntegrityTool, key); len(res["damaged"].([]any)) == 0 {
|
||||
t.Fatalf("a damaged section was not named: %v", res)
|
||||
}
|
||||
// An accepted record has no review to ask again; a proposed one does.
|
||||
if _, err := callTool(t, tools, AgainTool, key); err == nil {
|
||||
t.Fatal("a review of an accepted record was asked again")
|
||||
}
|
||||
q, _ := w.k.ReserveNumber("author@g14", KindDecision, "another")
|
||||
if _, err := w.k.Propose("author@g14", valid(q.Number)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.judge.review = `{"verdict":"changes","findings":["x"]}`
|
||||
w.reviewAll(t)
|
||||
w.judge.review = ""
|
||||
if _, err := w.k.ReviewAgain("author@g14", Key{KindDecision, q.Number}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.reviewAll(t)
|
||||
if e := mustGet(t, w.k.Store, Key{KindDecision, q.Number}); e.Current.Status != StatusAccepted {
|
||||
t.Fatalf("a review asked again: %s", e.Current.Status)
|
||||
}
|
||||
// The move's tools need the database beside the ledger.
|
||||
if _, err := callTool(t, tools, MoveCheckTool, map[string]any{}); err == nil {
|
||||
t.Fatal("a move check without a database")
|
||||
}
|
||||
if len(ModuleTools) != 15 {
|
||||
t.Fatalf("%v", ModuleTools)
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,19 @@
|
||||
"tools": [
|
||||
"knowledge_health",
|
||||
"knowledge_highest",
|
||||
"knowledge_marks"
|
||||
"knowledge_marks",
|
||||
"knowledge_head",
|
||||
"knowledge_operations",
|
||||
"knowledge_section",
|
||||
"knowledge_draft",
|
||||
"knowledge_parts",
|
||||
"knowledge_asked",
|
||||
"knowledge_fill",
|
||||
"knowledge_rebuild_index",
|
||||
"knowledge_review_again",
|
||||
"knowledge_integrity",
|
||||
"knowledge_move",
|
||||
"knowledge_move_check"
|
||||
],
|
||||
"emits": [
|
||||
"reserved",
|
||||
|
||||
Reference in New Issue
Block a user