CAT-K1: mesh-knowledge asks for its records as a ledger, its states and its reviews stream, and says the new events
The manifest keeps postgres-database until the drop and asks, in the typed form, the ledger records (a type for each kind, the operations of the move), the states drafts, asked, findings and sent, and the stream reviews (kept for good, refusing when full, granted by the trusted value readers). It consumes claude-code's judged and says the eleven new events. The mesh-delivery indexer's judge accepts it (checked against main). Issue 1966. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -38,11 +38,25 @@ const (
|
||||
EventDrafted = "drafted"
|
||||
EventConfirmed = "confirmed"
|
||||
EventReturned = "returned"
|
||||
// The events of a record written in a draft, section by section, and of the move to the ledger (novox/hq
|
||||
// to-be 55 §1.1, ADR 0592 §34).
|
||||
EventOpened = "opened"
|
||||
EventWritten = "written"
|
||||
EventRemoved = "removed"
|
||||
EventOrdered = "ordered"
|
||||
EventSubmitted = "submitted"
|
||||
EventDropped = "dropped"
|
||||
EventTakenOver = "taken-over"
|
||||
EventPartReviewed = "part-reviewed"
|
||||
EventMoved = "moved"
|
||||
EventChecked = "checked"
|
||||
EventFormed = "formed"
|
||||
)
|
||||
|
||||
// Events are the events, in the order the manifest declares them.
|
||||
var Events = []string{EventReserved, EventProposed, EventReviewed, EventAccepted, EventChanged, EventImported,
|
||||
EventDrafted, EventConfirmed, EventReturned}
|
||||
EventDrafted, EventConfirmed, EventReturned, EventOpened, EventWritten, EventRemoved, EventOrdered, EventSubmitted,
|
||||
EventDropped, EventTakenOver, EventPartReviewed, EventMoved, EventChecked, EventFormed}
|
||||
|
||||
// Settings are the operator's values for this module, merged by the mesh into one JSON file.
|
||||
type Settings struct {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
// The ledger the records are kept in (novox/hq ADR 0597 §11, to-be 55 §2.1): one instance per record, a type for each
|
||||
// kind, every change an operation the module hands to the tool runner's apply.
|
||||
|
||||
// LedgerName is the local name the module asks its ledger for in `requires`.
|
||||
const LedgerName = "records"
|
||||
|
||||
// The operations the ledger's types allow (ADR 0597 §11: reserve, propose, write-section, submit, take-over, review, ask,
|
||||
// warrant, accept and withdraw; and those the module's other changes of a record are, so each leaves its own mark in
|
||||
// the history).
|
||||
const (
|
||||
OpReserve = "reserve"
|
||||
OpPropose = "propose"
|
||||
OpWriteSection = "write-section"
|
||||
OpSubmit = "submit"
|
||||
OpTakeOver = "take-over"
|
||||
OpReview = "review"
|
||||
OpAsk = "ask"
|
||||
OpWarrant = "warrant"
|
||||
OpAccept = "accept"
|
||||
OpWithdraw = "withdraw"
|
||||
OpRetract = "retract"
|
||||
OpDraft = "draft"
|
||||
OpDrop = "drop"
|
||||
OpImport = "import"
|
||||
OpForm = "form"
|
||||
)
|
||||
|
||||
// LedgerOperations are the operations, in the order the manifest's types allow them.
|
||||
var LedgerOperations = []string{OpReserve, OpPropose, OpWriteSection, OpSubmit, OpDrop, OpImport, OpForm, OpTakeOver,
|
||||
OpReview, OpAsk, OpWarrant, OpAccept, OpWithdraw, OpRetract, OpDraft}
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// The manifest says what the code does (novox/hq to-be 55 §1): the seat it declares and holds once for the mesh
|
||||
@@ -16,7 +19,7 @@ import (
|
||||
|
||||
type manifest struct {
|
||||
Module string `json:"module"`
|
||||
Requires []string `json:"requires"`
|
||||
Requires []json.RawMessage `json:"requires"`
|
||||
Contributes map[string]map[string]any `json:"contributes"`
|
||||
Seats []seat `json:"seats"`
|
||||
Claims []seat `json:"claims"`
|
||||
@@ -70,12 +73,13 @@ func TestItDeclaresAndHoldsTheKnowledgeBaseSeatOnceForTheMesh(t *testing.T) {
|
||||
if !reflect.DeepEqual(m.Emits, Events) {
|
||||
t.Fatalf("emits %v, the code says %v", m.Emits, Events)
|
||||
}
|
||||
// Its records are in a database in the store (ADR 0290 rule 7), not in buckets.
|
||||
if !reflect.DeepEqual(m.Requires, []string{"postgres-database"}) || m.Contributes["postgres-database"]["name"] != "knowledge" || len(m.State) != 0 {
|
||||
t.Fatalf("requires %v, contributes %v, state %v", m.Requires, m.Contributes, m.State)
|
||||
// Until the database is dropped on the operator's yes, the database stays asked for (novox/hq ADR 0592 §14, §15); the
|
||||
// records and the module's other kept data are asked for beside it (TestItAsksForItsBusStateInTheTypedForm).
|
||||
if string(m.Requires[0]) != `"postgres-database"` || m.Contributes["postgres-database"]["name"] != "knowledge" || len(m.State) != 0 {
|
||||
t.Fatalf("requires %s, contributes %v, state %v", m.Requires, m.Contributes, m.State)
|
||||
}
|
||||
// It asks the operator on the router, hears the router's word, and calls exactly what it calls.
|
||||
if !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) || len(m.Consumes) != 0 {
|
||||
// It asks the operator on the router, hears the router's word, and hears the judge's answers.
|
||||
if !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) || !reflect.DeepEqual(m.Consumes, []string{"claude-code.judged"}) {
|
||||
t.Fatalf("uses %v, consumes %v", m.Uses, m.Consumes)
|
||||
}
|
||||
if !reflect.DeepEqual(m.Invokes, []string{"seat:issue-tracker.reserve", "claude-code.claude_code_judge", "claude-code.claude_code_judged", "records.records_status",
|
||||
@@ -212,3 +216,60 @@ func TestTheHealthToolReadsTheWatchsVerdictAndSaysWhenItIsStale(t *testing.T) {
|
||||
t.Fatalf("a stale verdict: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// Its kept data is asked for in `requires` in the typed long form, and the manifest reads whole as the mesh reads it
|
||||
// (design 55 §1, §2.1; design 32 §4): the ledger of the records with its two types, the module's other states, and the
|
||||
// stream of the parts' verdicts, kept for good, refusing when full, granted by a trusted config value.
|
||||
func TestItAsksForItsBusStateInTheTypedForm(t *testing.T) {
|
||||
_, raw := readManifest(t)
|
||||
m, err := catalogue.Decode([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var names []string
|
||||
byName := map[string]catalogue.BusStateEntry{}
|
||||
for _, e := range m.BusState {
|
||||
names = append(names, e.Name)
|
||||
byName[e.Name] = e
|
||||
}
|
||||
if !reflect.DeepEqual(names, []string{"records", "drafts", "asked", "findings", "sent", "reviews"}) {
|
||||
t.Fatalf("bus state %v", names)
|
||||
}
|
||||
rec := byName["records"]
|
||||
if rec.Storage != catalogue.StorageLedger || rec.Template != catalogue.TemplateLedger || len(rec.Types) != 2 {
|
||||
t.Fatalf("records %+v", rec)
|
||||
}
|
||||
if !reflect.DeepEqual(rec.Types[0].Schema, rec.Types[1].Schema) || !reflect.DeepEqual(rec.Types[0].Operations, rec.Types[1].Operations) ||
|
||||
rec.Types[0].Name != KindDecision || rec.Types[1].Name != KindResearch {
|
||||
t.Fatalf("the two types differ in more than their names")
|
||||
}
|
||||
have := map[string]bool{}
|
||||
for _, op := range rec.Types[0].Operations {
|
||||
have[op.Name] = true
|
||||
}
|
||||
for _, op := range LedgerOperations {
|
||||
if !have[op] {
|
||||
t.Errorf("the code applies the operation %s, which the ledger's types do not allow", op)
|
||||
}
|
||||
}
|
||||
if len(have) != len(LedgerOperations) {
|
||||
t.Errorf("the types allow %d operations, the code names %d", len(have), len(LedgerOperations))
|
||||
}
|
||||
for _, n := range []string{"drafts", "asked", "findings", "sent"} {
|
||||
if byName[n].Storage != catalogue.StorageKeyValue || byName[n].Template != catalogue.TemplateState {
|
||||
t.Errorf("%s is %+v, a state", n, byName[n])
|
||||
}
|
||||
}
|
||||
rv := byName["reviews"]
|
||||
var grant string
|
||||
_ = json.Unmarshal(rv.Configuration[catalogue.OptionGrant], &grant)
|
||||
if rv.Storage != catalogue.StorageStream || rv.Template != "" || grant != "readers" || string(rv.Configuration[catalogue.ConfigWhenFull]) != `"refuse"` {
|
||||
t.Fatalf("reviews %+v", rv)
|
||||
}
|
||||
if !slices.Contains(catalogue.TerminalKeys(m), "readers") {
|
||||
t.Fatalf("readers is not a terminal key (%v): the grant names a value only the controller's terminal or the operator's Approve sets", catalogue.TerminalKeys(m))
|
||||
}
|
||||
if len(m.Reads) != 0 {
|
||||
t.Fatalf("it reads %v, and no module reads another's bus state (ADR 0596 §2)", m.Reads)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"readers": {}
|
||||
}
|
||||
@@ -3,7 +3,81 @@
|
||||
"version": "1",
|
||||
"slug": "knowledge",
|
||||
"requires": [
|
||||
"postgres-database"
|
||||
"postgres-database",
|
||||
{
|
||||
"name": "records", "storage": "ledger", "template": "ledger", "options": {"max-mib": 1024},
|
||||
"types": [
|
||||
{
|
||||
"name": "decision",
|
||||
"schema": {
|
||||
"kind": {"type": "string"},
|
||||
"number": {"type": "integer"},
|
||||
"entry": {"type": "object"},
|
||||
"version": {"type": "object"},
|
||||
"pending-sections": {"type": "object"},
|
||||
"title": {"type": "string"},
|
||||
"text": {"type": "string"},
|
||||
"digest": {"type": "string", "digest-of": "text"},
|
||||
"why": {"type": "string"},
|
||||
"reason": {"type": "string"}
|
||||
},
|
||||
"operations": [
|
||||
{"name": "reserve", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "propose", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "write-section", "effect": "set-entry", "into": "pending-sections", "key": "title", "fields": ["title", "digest"], "sequence": true, "author": true},
|
||||
{"name": "submit", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "drop", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "import", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "form", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "take-over", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "review", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "ask", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "warrant", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "accept", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "withdraw", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "retract", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "draft", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "research",
|
||||
"schema": {
|
||||
"kind": {"type": "string"},
|
||||
"number": {"type": "integer"},
|
||||
"entry": {"type": "object"},
|
||||
"version": {"type": "object"},
|
||||
"pending-sections": {"type": "object"},
|
||||
"title": {"type": "string"},
|
||||
"text": {"type": "string"},
|
||||
"digest": {"type": "string", "digest-of": "text"},
|
||||
"why": {"type": "string"},
|
||||
"reason": {"type": "string"}
|
||||
},
|
||||
"operations": [
|
||||
{"name": "reserve", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "propose", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "write-section", "effect": "set-entry", "into": "pending-sections", "key": "title", "fields": ["title", "digest"], "sequence": true, "author": true},
|
||||
{"name": "submit", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "drop", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "import", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "form", "effect": "set-fields", "fields": ["kind", "number", "entry", "pending-sections"], "sequence": true, "author": true},
|
||||
{"name": "take-over", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "review", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "ask", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "warrant", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "accept", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "withdraw", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "retract", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true},
|
||||
{"name": "draft", "effect": "set-fields", "fields": ["kind", "number", "entry"], "sequence": true, "author": true}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{"name": "drafts", "storage": "key-value", "template": "state", "options": {"key-ttl": true}},
|
||||
{"name": "asked", "storage": "key-value", "template": "state"},
|
||||
{"name": "findings", "storage": "key-value", "template": "state"},
|
||||
{"name": "sent", "storage": "key-value", "template": "state"},
|
||||
{"name": "reviews", "storage": "stream", "configuration": {"when-full": "refuse", "max-mib": 512, "max-age-days": "none", "grant": "readers"}}
|
||||
],
|
||||
"contributes": {
|
||||
"postgres-database": {
|
||||
@@ -74,7 +148,21 @@
|
||||
"imported",
|
||||
"drafted",
|
||||
"confirmed",
|
||||
"returned"
|
||||
"returned",
|
||||
"opened",
|
||||
"written",
|
||||
"removed",
|
||||
"ordered",
|
||||
"submitted",
|
||||
"dropped",
|
||||
"taken-over",
|
||||
"part-reviewed",
|
||||
"moved",
|
||||
"checked",
|
||||
"formed"
|
||||
],
|
||||
"consumes": [
|
||||
"claude-code.judged"
|
||||
],
|
||||
"uses": [
|
||||
"operator-channel"
|
||||
@@ -96,6 +184,11 @@
|
||||
"seat:mesh-delivery.groups"
|
||||
],
|
||||
"config": {
|
||||
"readers": {
|
||||
"type": "object",
|
||||
"description": "The modules heard on the reviews of records (the stream of each part's verdict), by name, each with why; empty grants none. Set only at the controller's terminal or on the operator's Approve.",
|
||||
"default": {}
|
||||
},
|
||||
"judge-model": {
|
||||
"type": "string",
|
||||
"description": "The model the knowledge base's drafting and confirming judgements ask, by the agent's name for it, such as sonnet; empty is sonnet.",
|
||||
@@ -138,6 +231,15 @@
|
||||
"merge": "json",
|
||||
"template": "files/settings.json"
|
||||
},
|
||||
{
|
||||
"id": "readers",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/readers.json",
|
||||
"mode": "0600",
|
||||
"takes-config": ["readers"],
|
||||
"merge": "json",
|
||||
"template": "files/readers.json"
|
||||
},
|
||||
{
|
||||
"id": "prepare",
|
||||
"type": "process",
|
||||
|
||||
Reference in New Issue
Block a user