From 80a7cee6f35e08aa876210b210c1c23f6716d18a Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 11:47:59 +0100 Subject: [PATCH] CAT-K3: each operation of the records' ledger types has its effect on a head, tested against the manifest's own types Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../cmd/mesh-knowledge/ledgertype_test.go | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 modules/mesh-knowledge/cmd/mesh-knowledge/ledgertype_test.go diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/ledgertype_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/ledgertype_test.go new file mode 100644 index 00000000..0449bc1e --- /dev/null +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/ledgertype_test.go @@ -0,0 +1,87 @@ +package main + +import ( + "encoding/json" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/buscalls" +) + +// Each operation of the ledger's types has the effect on a head the design says (ADR 0597 §2, to-be 55 §2.1): a +// write-section puts the section's title and digest, never its text, among the pending sections; an operation that +// makes a version sets the head and empties them; the others set the head and leave them. +func TestEachOperationHasItsEffectOnAHead(t *testing.T) { + text := "the section's text" + for _, c := range []struct { + op string + body map[string]any + pendingKept bool // the pending sections stand after it + clears bool // the pending sections are emptied by it + }{ + {OpReserve, map[string]any{"entry": map[string]any{"v": 1}}, false, false}, + {OpPropose, map[string]any{"entry": map[string]any{"v": 1}, "pending-sections": map[string]any{}}, false, true}, + {OpSubmit, map[string]any{"entry": map[string]any{"v": 1}, "pending-sections": map[string]any{}}, false, true}, + {OpDrop, map[string]any{"entry": map[string]any{"v": 1}, "pending-sections": map[string]any{}}, false, true}, + {OpImport, map[string]any{"entry": map[string]any{"v": 1}, "pending-sections": map[string]any{}}, false, true}, + {OpForm, map[string]any{"entry": map[string]any{"v": 1}, "pending-sections": map[string]any{}}, false, true}, + {OpTakeOver, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpReview, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpAsk, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpWarrant, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpAccept, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpWithdraw, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpRetract, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + {OpDraft, map[string]any{"entry": map[string]any{"v": 1}}, true, false}, + } { + f := newFakeLedger(t) + apply := func(op string, body map[string]any) { + t.Helper() + raw, _ := json.Marshal(body) + var got buscalls.Applied + if err := f.Call(buscalls.LedgerApply, buscalls.ApplyRequest{Ledger: LedgerName, Instance: "decision/0001", Operation: op, Body: raw}, &got); err != nil { + t.Fatalf("%s: %v", op, err) + } + } + apply(OpWriteSection, map[string]any{"title": "Context", "text": text, "digest": SectionDigest(text)}) + body := map[string]any{"kind": "decision", "number": 1} + for k, v := range c.body { + body[k] = v + } + apply(c.op, body) + var st map[string]any + _ = json.Unmarshal(mustJSON(t, f.state["decision/0001"]), &st) + pending, _ := st["pending-sections"].(map[string]any) + switch { + case c.clears && len(pending) != 0: + t.Errorf("%s leaves pending sections %v", c.op, pending) + case c.pendingKept && len(pending) != 1: + t.Errorf("%s does not keep the pending sections: %v", c.op, pending) + } + if c.op == OpReserve && len(pending) != 1 { + t.Errorf("reserve touched the pending sections: %v", pending) + } + if st["entry"] == nil || st["sequence"] != float64(2) { + t.Errorf("%s: head %v", c.op, st) + } + if a, _ := st["author"].(map[string]any); a["account"] != "mesh-knowledge" { + t.Errorf("%s: author %v", c.op, st["author"]) + } + if raw, _ := json.Marshal(st); json.Valid(raw) && strings.Contains(string(raw), text) { + t.Errorf("%s: the head holds the section's text", c.op) + } + } +} + +// A write-section whose digest is not its text's is refused by apply, and an operation the type does not allow is too. +func TestTheLedgerRefusesADigestThatIsNotTheTextsAndAnOperationTheTypeLacks(t *testing.T) { + f := newFakeLedger(t) + var got buscalls.Applied + bad, _ := json.Marshal(map[string]any{"title": "Context", "text": "a", "digest": SectionDigest("b")}) + if err := f.Call(buscalls.LedgerApply, buscalls.ApplyRequest{Ledger: LedgerName, Instance: "decision/0001", Operation: OpWriteSection, Body: bad}, &got); err == nil { + t.Error("a digest of another text was taken") + } + if err := f.Call(buscalls.LedgerApply, buscalls.ApplyRequest{Ledger: LedgerName, Instance: "decision/0001", Operation: "delete"}, &got); err == nil { + t.Error("an operation the type lacks was taken") + } +}