R-1 rest, R-12, R-13: the capability words are mesh-sdk's, the ledger's fill and removal are answered, the artifact states the words
sent-get is under sent-objects-calls; mesh/ledger.fill answers the history's fill against its cap and mesh/ledger.remove removes an instance's operations, snapshot, state and index entries, both under ledger-upkeep-calls; the runtime artifact's `states` is exactly the words the runner states. Builds: design 47 G §462, §488, §545, §587, §622, §155 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -58,6 +58,10 @@ type History interface {
|
||||
// Read is the operations of the instances keys names (a key, with `*` for one name and `>` for the rest) from a
|
||||
// sequence on, oldest first, at most limit of them (every one when limit is 0).
|
||||
Read(keys string, from uint64, limit int) ([]Kept, error)
|
||||
// Purge removes every operation of the instance key names, and nothing of another (an instance's removal).
|
||||
Purge(key string) error
|
||||
// Fill is the history's fill against its cap, the ledger's cap (buscalls.LedgerFill; design 47 G §587).
|
||||
Fill() (buscalls.Fill, error)
|
||||
}
|
||||
|
||||
// State is a ledger's state bucket: each instance's record under its key.
|
||||
@@ -69,6 +73,8 @@ type State interface {
|
||||
Write(key string, value []byte, revision uint64) (uint64, error)
|
||||
// Keys is every key the bucket holds.
|
||||
Keys() ([]string, error)
|
||||
// Delete removes the key; a key it does not hold is no fault.
|
||||
Delete(key string) error
|
||||
}
|
||||
|
||||
// Snapshots are a ledger's snapshots: the latest record of each instance, on a subject of its own, never expiring.
|
||||
@@ -77,6 +83,8 @@ type Snapshots interface {
|
||||
Get(key string) ([]byte, uint64, error)
|
||||
// Put writes the key's snapshot, expecting its last to be at expect (0: none); ErrRaced when it is not.
|
||||
Put(key string, value []byte, expect uint64) error
|
||||
// Purge removes the key's snapshot; one it does not hold is no fault.
|
||||
Purge(key string) error
|
||||
}
|
||||
|
||||
// Ledger is one ledger of one module: its types and where it is kept.
|
||||
@@ -495,3 +503,74 @@ func (l *Ledger) Apply(r buscalls.ApplyRequest, author buscalls.Author) (buscall
|
||||
}
|
||||
return buscalls.Applied{Taken: true, Sequence: seq, StateBehind: behind}, nil
|
||||
}
|
||||
|
||||
// Fill is the fill of the ledger's history against the ledger's cap (buscalls.LedgerFill; design 47 G §587): its messages,
|
||||
// its bytes and the cap in bytes. The history holds every operation, and the cap is its alone: neither the fullest of the
|
||||
// stores nor their sum is answered. A history that cannot say its fill is an error naming the ledger, never a fill of
|
||||
// nothing.
|
||||
func (l *Ledger) Fill(buscalls.LedgerRequest) (buscalls.Fill, error) {
|
||||
f, err := l.History.Fill()
|
||||
if err != nil {
|
||||
return buscalls.Fill{}, fmt.Errorf("%s's ledger %s cannot say the fill of its history: %w", l.Module, l.Name, err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Remove removes one instance with everything it kept (buscalls.LedgerRemove; design 47 G §587, §155): its operations, its
|
||||
// snapshot, its key in the state bucket and its keys in every declared index, and answers how many operations and how many
|
||||
// bytes of the history that was. An instance the ledger does not hold answers 0 and 0, and a removal that left something
|
||||
// (said, and answered as an error) is made again by calling it again, which removes the rest.
|
||||
func (l *Ledger) Remove(r buscalls.LedgerRemoveRequest) (buscalls.LedgerRemoved, error) {
|
||||
key, err := KeyOf(r.Instance)
|
||||
if err != nil {
|
||||
return buscalls.LedgerRemoved{}, err
|
||||
}
|
||||
out := buscalls.LedgerRemoved{Instance: r.Instance}
|
||||
before, err := l.History.Fill()
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("%s's ledger %s cannot say the fill of its history, so a removal is not made: %w", l.Module, l.Name, err)
|
||||
}
|
||||
ops, err := l.History.Read(key, 1, 0)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for _, step := range []struct {
|
||||
what string
|
||||
remove func(string) error
|
||||
}{{"operations", l.History.Purge}, {"snapshot", l.Snapshots.Purge}, {"state", l.State.Delete}, {"index entries", l.dropFromIndexes}} {
|
||||
if err := step.remove(key); err != nil {
|
||||
l.say("[mesh-tools] %s's ledger %s removes %s and its %s could not be removed: %v; removing it again removes what "+
|
||||
"is left", l.Module, l.Name, r.Instance, step.what, err)
|
||||
return out, fmt.Errorf("%s's ledger %s removed part of %s and its %s are left: %w", l.Module, l.Name, r.Instance, step.what, err)
|
||||
}
|
||||
}
|
||||
after, err := l.History.Fill()
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("%s's ledger %s removed %s and cannot say the fill of its history after: %w", l.Module, l.Name, r.Instance, err)
|
||||
}
|
||||
out.Operations = uint64(len(ops))
|
||||
if before.Bytes > after.Bytes {
|
||||
out.Bytes = before.Bytes - after.Bytes
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// dropFromIndexes removes the entries of the instance key names from every index bucket the ledger is bound to.
|
||||
func (l *Ledger) dropFromIndexes(key string) error {
|
||||
instance := InstanceOf(key)
|
||||
for name, ix := range l.Indexes {
|
||||
held, err := ix.Entries("", 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the index %s: %w", name, err)
|
||||
}
|
||||
for _, e := range held {
|
||||
var v buscalls.IndexEntry
|
||||
if json.Unmarshal(e.Value, &v) == nil && v.Instance == instance {
|
||||
if err := ix.Delete(e.Key); err != nil {
|
||||
return fmt.Errorf("the index %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,3 +100,50 @@ func TestApplyAppendsDerivesAndSnapshots(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// design 47 G §587: a ledger's fill is its history's against the ledger's cap, a store that cannot say it is an error naming
|
||||
// the ledger; a removal takes an instance's operations, snapshot, state and index entries and answers what that was, an
|
||||
// unknown instance answers none, and nothing of another instance goes.
|
||||
func TestTheLedgerSaysItsFillAndRemovesAnInstanceWhole(t *testing.T) {
|
||||
l, f := newLedger()
|
||||
f.history.MaxBytes = 1 << 20
|
||||
for _, n := range []string{"a", "b"} {
|
||||
applied(t, l, "decision/0290", "note", `{"says":"`+n+`"}`, owner)
|
||||
}
|
||||
applied(t, l, "decision/0291", "note", `{"says":"other"}`, owner)
|
||||
entry, _ := json.Marshal(buscalls.IndexEntry{Instance: "decision/0290", Sequence: 2})
|
||||
other, _ := json.Marshal(buscalls.IndexEntry{Instance: "decision/0291", Sequence: 1})
|
||||
_ = f.index.Put("t.decision.0290", entry)
|
||||
_ = f.index.Put("t.decision.0291", other)
|
||||
|
||||
fill, err := l.Fill(buscalls.LedgerRequest{Ledger: "records"})
|
||||
if err != nil || fill.Count != 3 || fill.Bytes == 0 || fill.MaxBytes != 1<<20 {
|
||||
t.Fatalf("the fill is %+v (%v), want 3 messages against 1 MiB", fill, err)
|
||||
}
|
||||
got, err := l.Remove(buscalls.LedgerRemoveRequest{Ledger: "records", Instance: "decision/0290"})
|
||||
if err != nil || got.Instance != "decision/0290" || got.Operations != 2 || got.Bytes == 0 {
|
||||
t.Fatalf("the removal answered %+v (%v), want 2 operations and their bytes", got, err)
|
||||
}
|
||||
if kept, _ := f.history.Read("decision.0290", 1, 0); len(kept) != 0 {
|
||||
t.Errorf("operations are left: %d", len(kept))
|
||||
}
|
||||
if raw, _, _ := f.state.Get("decision.0290"); raw != nil {
|
||||
t.Errorf("the state is left: %s", raw)
|
||||
}
|
||||
if raw, _, _ := f.snapshots.Get("decision.0290"); raw != nil {
|
||||
t.Errorf("the snapshot is left: %s", raw)
|
||||
}
|
||||
if held, _ := f.index.Entries("", 0); len(held) != 1 || held[0].Key != "t.decision.0291" {
|
||||
t.Errorf("the index holds %v, want the other instance's entry alone", held)
|
||||
}
|
||||
if kept, _ := f.history.Read("decision.0291", 1, 0); len(kept) != 1 {
|
||||
t.Errorf("another instance lost operations: %d left", len(kept))
|
||||
}
|
||||
if again, err := l.Remove(buscalls.LedgerRemoveRequest{Ledger: "records", Instance: "decision/0290"}); err != nil || again.Operations != 0 || again.Bytes != 0 {
|
||||
t.Errorf("a removal said twice answered %+v (%v), want none", again, err)
|
||||
}
|
||||
f.history.FillFails = true
|
||||
if _, err := l.Fill(buscalls.LedgerRequest{}); err == nil || !strings.Contains(err.Error(), "ledger records cannot say the fill") {
|
||||
t.Errorf("a history that cannot say its fill answered %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package ledger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
)
|
||||
|
||||
// The memory stores keep what the bus keeps, as the bus answers it, for a test of a ledger without a bus: a stream whose
|
||||
@@ -12,9 +15,13 @@ import (
|
||||
|
||||
// MemoryHistory is a history in memory.
|
||||
type MemoryHistory struct {
|
||||
mu sync.Mutex
|
||||
seq uint64
|
||||
kept []struct {
|
||||
// MaxBytes is the cap its fill is said against.
|
||||
MaxBytes uint64
|
||||
// FillFails makes Fill an error, as a store that cannot say its fill.
|
||||
FillFails bool
|
||||
mu sync.Mutex
|
||||
seq uint64
|
||||
kept []struct {
|
||||
key string
|
||||
Kept
|
||||
}
|
||||
@@ -73,6 +80,32 @@ func (h *MemoryHistory) Read(keys string, from uint64, limit int) ([]Kept, error
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *MemoryHistory) Purge(key string) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
kept := h.kept[:0]
|
||||
for _, k := range h.kept {
|
||||
if k.key != key {
|
||||
kept = append(kept, k)
|
||||
}
|
||||
}
|
||||
h.kept = kept
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MemoryHistory) Fill() (buscalls.Fill, error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.FillFails {
|
||||
return buscalls.Fill{}, errors.New("the history does not say its fill")
|
||||
}
|
||||
f := buscalls.Fill{Count: uint64(len(h.kept)), MaxBytes: h.MaxBytes}
|
||||
for _, k := range h.kept {
|
||||
f.Bytes += uint64(len(k.Data))
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Expire drops every operation of the history, as a ledger's keeping days end them.
|
||||
func (h *MemoryHistory) Expire() {
|
||||
h.mu.Lock()
|
||||
@@ -129,6 +162,13 @@ func (s *MemoryState) Keys() ([]string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *MemoryState) Delete(key string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.kv, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MemorySnapshots are snapshots in memory.
|
||||
type MemorySnapshots struct {
|
||||
mu sync.Mutex
|
||||
@@ -157,6 +197,13 @@ func (s *MemorySnapshots) Put(key string, value []byte, expect uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemorySnapshots) Purge(key string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.kv, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MemoryIndex is an index's bucket in memory.
|
||||
type MemoryIndex struct {
|
||||
mu sync.Mutex
|
||||
|
||||
Reference in New Issue
Block a user