RN17 (fill): a bundle reads a state's fill through KeptState.Info

A bundle's state call mesh/state.info is served by the tool runner (node-tools launch.go:425, runtime.go:1093); the module library had no wrapper for it.

Builds: design 47 G §623, §366, A-214

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 06:07:17 +01:00
parent 631654eec6
commit 6b6cdcc25e
2 changed files with 66 additions and 0 deletions
+32
View File
@@ -126,6 +126,38 @@ func (s KeptState) Page(after string, maxBytes int, budget time.Duration) (State
return p, json.Unmarshal(raw, &p)
}
// StateInfo is how full a state's bucket is against its caps, as the tool runner answers `mesh/state.info`
// (design 47 G §623): a bundle's tools say fill against cap from it. A cap of -1 is none.
type StateInfo struct {
State string `json:"state"`
Bucket string `json:"bucket"`
// Bytes is what the bucket holds now; Keys how many keys it holds.
Bytes uint64 `json:"bytes"`
Keys uint64 `json:"keys"`
// MaxBytes and MaxValueBytes are the bucket's caps.
MaxBytes int64 `json:"max_bytes"`
MaxValueBytes int32 `json:"max_value_bytes"`
// History is how many values a key keeps; TTLSeconds the bucket's age for a value (0 none); KeyTTL whether a key
// may carry its own time to live.
History int64 `json:"history"`
TTLSeconds int64 `json:"ttl_seconds"`
KeyTTL bool `json:"key_ttl"`
}
// Info is the bucket's fill against its caps: runtime verb `mesh/state.info`. A read, like Get: the owner's
// instances and its readers may ask it, and it writes nothing.
func (s KeptState) Info() (*StateInfo, error) {
raw, err := s.ask("info", map[string]any{})
if err != nil {
return nil, err
}
var i StateInfo
if err := json.Unmarshal(raw, &i); err != nil {
return nil, err
}
return &i, nil
}
// History is every value the bucket still keeps for key, oldest first — as many as the state declared it
// keeps, deletes included — and empty for a key it holds nothing for (novox/hq ADR 0307). A read, like Get:
// the owner's instances and its readers may ask it, and it writes nothing.
+34
View File
@@ -141,3 +141,37 @@ func TestHistoryIsEveryKeptValueOldestFirst(t *testing.T) {
t.Fatalf("a state not issued answered %v", refused)
}
}
// Info asks mesh/state.info with the state alone and hands back the bucket's fill against its caps, as the tool
// runner answers it (design 47 G §623); a refusal is the runtime's error.
func TestInfoIsTheBucketsFillAgainstItsCaps(t *testing.T) {
var got *StateInfo
var refused error
asked := historying(t, func(m message) map[string]any {
var p map[string]any
_ = json.Unmarshal(m.Params, &p)
if p["state"] != "issues" {
return map[string]any{"error": map[string]any{"code": -32000, "message": "issues was issued no state"}}
}
return map[string]any{"result": map[string]any{"state": "issues", "bucket": "issues_issues", "bytes": 300, "keys": 4,
"max_bytes": 1000, "max_value_bytes": 262144, "history": 1, "ttl_seconds": 0, "key_ttl": true}}
}, func() {
var err error
if got, err = State("issues").Info(); err != nil {
t.Errorf("info: %v", err)
}
var i *StateInfo
if i, refused = State("elsewhere.issues").Info(); i != nil {
t.Errorf("a refused info handed back %v", i)
}
})
if len(asked) != 2 || asked[0].Method != "mesh/state.info" {
t.Fatalf("asked %+v", asked)
}
if got == nil || got.Bytes != 300 || got.MaxBytes != 1000 || got.Keys != 4 || got.Bucket != "issues_issues" || !got.KeyTTL {
t.Errorf("info is %+v", got)
}
if refused == nil {
t.Error("an info of a state the module was not issued is not refused")
}
}