DLV-G3: the grants part raises no history-full; fill is the bus module's, and the old key is cleared once at start (ADR 0595 §6, design 25 § 12.3)

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 12:34:50 +01:00
parent ae31b66a9e
commit e33cf8459b
4 changed files with 27 additions and 107 deletions
+9 -19
View File
@@ -703,8 +703,8 @@ func TestGrants(t *testing.T) {
t.Errorf("a reader of a held module's object store lost its grant: %v", heldWeb.Publish)
}
b.manifestOf("mesh-knowledge", knowledge) // accepted again for the rest of the row
// ADR 0592 §6, §25: the bucket past 75 percent of its cap is said, in one warning, and cleared once below; the history
// stream at 10 percent is not.
// ADR 0595 §6, design 25 § 12.3 (DLV-G3): fill is the bus module's; the part raises no history-full, however full a
// bucket is, and clears the old key once after its start.
var said []asks.Condition
p.Say = func(c asks.Condition) error {
if c.Key == grants.FullCondition {
@@ -712,25 +712,15 @@ func TestGrants(t *testing.T) {
}
return nil
}
size := func(stream string, bytes int64) {
for _, s := range b.streams {
if s["name"] == stream {
s["bytes"] = bytes
}
for _, s := range b.streams {
if s["name"] == "OBJ_mesh-knowledge_sections" {
s["bytes"] = int64(3500 << 20)
}
}
size("OBJ_mesh-knowledge_sections", 3500<<20)
size("VERSIONS", 200<<20)
if res := p.Compose(); len(res.Full) != 1 || res.Full[0].Stream != "OBJ_mesh-knowledge_sections" || res.Full[0].Percent() != 85 ||
len(said) != 1 || said[0].State != asks.StateRaised || !strings.Contains(said[0].Summary, "3500 of 4096 MiB (85%)") {
t.Fatalf("full = %+v, said %+v", res.Full, said)
}
if p.Compose(); len(said) != 1 {
t.Errorf("the same fill was said again: %+v", said)
}
size("OBJ_mesh-knowledge_sections", 1<<20)
if res := p.Compose(); len(res.Full) != 0 || len(said) != 2 || said[1].State != asks.StateCleared {
t.Errorf("below its mark: full %+v, said %+v", res.Full, said)
p.Compose()
p.Compose()
if len(said) != 1 || said[0].State != asks.StateCleared {
t.Errorf("history-full: said %+v, want one clear and no raise", said)
}
})
@@ -11,8 +11,7 @@ import (
// A module's own object bucket (novox/hq ADR 0592 §5, §9). The grants part creates it once, create-only, kept with no
// age limit and refusing a new object when full; only its module writes it; another module reads it only through a
// `reads` entry naming it and the owner's trusted config value of the bucket's grant naming that module, read-only, as a
// restricted stream is heard (ADR 0363 §4, §5). The condition when it passes 75 percent of its cap is the grants part's
// (grants.FullCondition).
// restricted stream is heard (ADR 0363 §4, §5). The condition when it fills is the bus module's (ADR 0595 §6).
// ObjectBucket is one object bucket a module owns, as the bus holds it.
type ObjectBucket struct {
+13 -76
View File
@@ -1,95 +1,32 @@
package grants
import (
"fmt"
"strings"
"git.novox.be/novox/mesh-sdk/go/asks"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// **A history stream or an object bucket past 75 percent of its cap is said, in one place for every module** (novox/hq
// ADR 0592 §6, §25; ADR 0593 §10 gives the controller's part to this part). Such a stream or bucket refuses a new message
// or object when full rather than dropping its oldest, so filling up is the one way it fails: the part reads every
// stream's size from the bus module's nats_streams at each pass (HeldStreams) and raises the warning FullCondition naming
// each one past catalogue.FullWarnPercent, cleared once none is.
// **The fill of a module's kept data is the bus module's** (novox/hq ADR 0595 §6, design 25 § 12.3): its one periodic
// read raises `full` at its raise mark and clears it below its clear mark, for every entry that refuses when full. The
// grants part no longer reads any stream's size and raises nothing of fill: `mesh-grants.history-full` moved to the bus
// module, its new condition raised first, then this old key cleared. So the part clears the old key once, at its first
// pass after a start (DLV-G3), whether or not one is held: a clear of a condition not raised is a no-op at the keeper.
// FullCondition is the key of the warning raised while a history stream or an object bucket is past its mark.
// FullCondition is the key of the warning this part raised before ADR 0595 §6 moved fill to the bus module: only ever
// cleared now.
const FullCondition = "mesh-grants.history-full"
// Fill is how full one history stream or object bucket is: its module, its stream on the bus, its bytes and its cap.
type Fill struct {
Module string `json:"module"`
Stream string `json:"stream"`
Bytes int64 `json:"bytes"`
Cap int64 `json:"cap"`
}
// Percent is its bytes in percent of its cap, rounded down.
func (f Fill) Percent() int64 { return f.Bytes * 100 / f.Cap }
// Full is every history stream and object bucket of a placed module past catalogue.FullWarnPercent of its cap, by the
// sizes the bus holds (bytes, by stream name), in module and stream order. One the bus does not hold is not full. Pure.
func Full(in Inputs, bytes map[string]int64) []Fill {
var out []Fill
for _, module := range sortedKeys(in.Manifests) {
if !assigned(in, module) {
continue
}
m := in.Manifests[module]
var capped []Fill
for _, s := range m.Streams {
if s.KeepsHistory() {
capped = append(capped, Fill{Module: module, Stream: s.BusName(), Cap: int64(s.Cap()) << 20})
}
}
for _, o := range objectsOf(m) {
capped = append(capped, Fill{Module: module, Stream: "OBJ_" + o.Bucket(), Cap: int64(o.MaxMiB) << 20})
}
for _, f := range capped {
held, ok := bytes[f.Stream]
if !ok || f.Cap <= 0 {
continue
}
f.Bytes = held
if f.Bytes*100 >= catalogue.FullWarnPercent*f.Cap {
out = append(out, f)
}
}
}
return out
}
// sayFull raises FullCondition while a history stream or an object bucket is past its mark, and clears it once none is:
// said once per set of streams, again when that set changes, and at the first pass after a start.
func (p *Part) sayFull(full []Fill) {
// clearFull clears FullCondition once after a start; a clear refused is tried again at the next pass.
func (p *Part) clearFull() {
if p.Say == nil {
return
}
var names, lines []string
for _, f := range full {
names = append(names, f.Stream)
lines = append(lines, fmt.Sprintf("%s of %s: %d of %d MiB (%d%%)", f.Stream, f.Module, f.Bytes>>20, f.Cap>>20, f.Percent()))
}
said := strings.Join(names, " ")
p.holdMu.Lock()
defer p.holdMu.Unlock()
if p.fullKnown && said == p.fullSaid {
if p.fullCleared {
return
}
c := asks.Condition{Key: FullCondition, Subject: asks.Scope{Scope: "module", ID: Module}}
if said == "" {
c.State = asks.StateCleared
} else {
c.Severity, c.State = asks.SeverityWarning, asks.StateRaised
c.Summary = fmt.Sprintf("%d history stream(s) or object bucket(s) past %d%% of their cap: %s", len(full),
catalogue.FullWarnPercent, strings.Join(lines, "; "))
c.Headline, c.Needs = "A stream that keeps its history is filling up", "a larger cap in the module's manifest, or data removed"
c.Explanation = asks.NeedsYou + "a decision before it is full: such a stream or bucket refuses new messages when full " +
"rather than dropping its oldest (novox/hq ADR 0592 §6). " + strings.Join(lines, "; ")
c.Resolved = "every history stream and object bucket is below its mark"
}
c := asks.Condition{Key: FullCondition, Subject: asks.Scope{Scope: "module", ID: Module}, State: asks.StateCleared,
Resolved: "the fill of a module's kept data is the bus module's own condition now (novox/hq ADR 0595 §6)"}
if p.Say(c) == nil {
p.fullSaid, p.fullKnown = said, true
p.fullCleared = true
}
}
+4 -10
View File
@@ -96,8 +96,6 @@ func (s UserListState) inputs() string {
// Result is what one composition did.
type Result struct {
// Full is every history stream and object bucket past its mark at this pass (full.go, ADR 0592 §6).
Full []Fill `json:"full,omitempty"`
UserList UserListState `json:"user_list"`
Revision uint64 `json:"revision"`
Written bool `json:"written"`
@@ -183,9 +181,8 @@ type Part struct {
behindSaid string
behindKnown bool
behindSeen map[string]bool
// fullSaid is the history streams and object buckets past their mark last said as a condition, once fullKnown (full.go).
fullSaid string
fullKnown bool
// fullCleared is whether the old history-full key was cleared since the start (full.go, ADR 0595 §6).
fullCleared bool
// refusedSaid is the refused principals last said as a condition, once refusedKnown (refused.go).
refusedSaid string
refusedKnown bool
@@ -380,11 +377,8 @@ func (p *Part) Compose() Result {
res.Retired[key] = m.Retired
}
}
held, sizes, heldErr := HeldStreamsAndBytes(p.Call)
if heldErr == nil {
res.Full = Full(in, sizes)
p.sayFull(res.Full)
}
p.clearFull()
held, heldErr := HeldStreams(p.Call)
var ask Ask
if p.Create != nil || p.Consumers != nil {
ask = p.asked()