The bus keeper reads a bare stream's max-age-days "none" as kept for good, as the judge accepts it
mesh/delivery merged: source.main moved to novox/mesh-catalog@7fd4ed589806
mesh/check checking bc8cb3b153cf on 7fd4ed589806

mesh-knowledge's stream reviews says max-age-days "none", which the indexer's judge accepts
(judge/busstate.go noAge), but the keeper refused it ("not a whole number"), so the stream was
never made and the keeper stayed unhealthy (2026-11-08, issue 1966). Quick fix; the thorough one,
one shared reader of the template options in mesh-sdk catalogue for the judge and the keeper, is
noted on issue 1966.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
2026-11-08 23:52:51 +01:00
parent 9480557537
commit bc8cb3b153
2 changed files with 21 additions and 1 deletions
@@ -216,6 +216,8 @@ func TestEachTemplateIsPlannedUnderTheModulesNames(t *testing.T) {
ledger.Types = []catalogue.LedgerType{{Name: "decision", Indexes: []catalogue.LedgerIndex{{Name: "by-topic", On: []string{"topic"}}}}}
bare := entry("raw", catalogue.StorageStream, "", nil)
bare.Configuration = map[string]json.RawMessage{"when-full": json.RawMessage(`"drop-oldest"`), "max-mib": json.RawMessage("8"), "max-age-days": json.RawMessage("3")}
forGood := entry("reviews", catalogue.StorageStream, "", nil)
forGood.Configuration = map[string]json.RawMessage{"when-full": json.RawMessage(`"refuse"`), "max-mib": json.RawMessage("512"), "max-age-days": json.RawMessage(`"none"`)}
for _, row := range []struct {
name string
entry catalogue.BusStateEntry
@@ -247,6 +249,9 @@ func TestEachTemplateIsPlannedUnderTheModulesNames(t *testing.T) {
{name: "bare stream", entry: bare,
objects: "stream mesh-knowledge_raw [mesh.bus.mesh-knowledge.raw.>] {8 MiB, age 259200, history 0, old, size 0}",
binding: "stream mesh-knowledge_raw on mesh.bus.mesh-knowledge.raw"},
{name: "a bare stream kept for good (max-age-days none, as the judge accepts it)", entry: forGood,
objects: "stream mesh-knowledge_reviews [mesh.bus.mesh-knowledge.reviews.>] {512 MiB, age 0, history 0, new, size 0}",
binding: "stream mesh-knowledge_reviews on mesh.bus.mesh-knowledge.reviews"},
{name: "a local name holding the separator", entry: entry("my_drafts", catalogue.StorageKeyValue, catalogue.TemplateState, nil),
refused: `the local name "my_drafts" is empty or holds "_"`},
{name: "objects with no cap", entry: entry("files", catalogue.StorageObjectStore, catalogue.TemplateObjects, nil),
+16 -1
View File
@@ -226,7 +226,7 @@ func PlanOf(module string, e catalogue.BusStateEntry) (Plan, error) {
if err != nil {
return Plan{}, err
}
age, err := conf.int(catalogue.OptionAgeDays, -1)
age, err := conf.ageDays(-1)
if err != nil {
return Plan{}, err
}
@@ -283,6 +283,21 @@ func days(n int64) int64 {
return int64((time.Duration(n) * 24 * time.Hour) / time.Second)
}
// noAge is how a bare stream says it keeps its messages for good, as the indexer's judge accepts it
// (mesh-delivery modules/mesh-delivery-indexer/judge/busstate.go noAge): kept as no age limit.
const noAge = "none"
// ageDays is a bare stream's max-age-days: a whole number of days, or "none" for no limit (0).
func (o options) ageDays(fallback int64) (int64, error) {
if raw, ok := o.raw[catalogue.OptionAgeDays]; ok {
var word string
if json.Unmarshal(raw, &word) == nil && word == noAge {
return 0, nil
}
}
return o.int(catalogue.OptionAgeDays, fallback)
}
// options reads a template's options as the manifest wrote them.
type options struct{ raw map[string]json.RawMessage }