diff --git a/modules/mesh-delivery-indexer/judge/busstate.go b/modules/mesh-delivery-indexer/judge/busstate.go index 46598667..bd7c0cf6 100644 --- a/modules/mesh-delivery-indexer/judge/busstate.go +++ b/modules/mesh-delivery-indexer/judge/busstate.go @@ -171,6 +171,22 @@ func BusStateProblems(m catalogue.Manifest) []string { return problems } +// KeptFormProblems is what is wrong with the catch-up tool each kept data declares, in an old section or in `requires` by +// template (novox/hq design 47, *Its form is declared*): the tool a restore of it is followed by is one of the module's +// own tools, since the node agent calls it from the restore point and the delivery system names no module's tool. A form +// below 0 is refused by the reader. +func KeptFormProblems(m catalogue.Manifest) []string { + var problems []string + for _, k := range catalogue.KeptDeclarations(m) { + if k.CatchUp != "" && !slices.Contains(m.Tools, k.CatchUp) { + problems = append(problems, fmt.Sprintf("%s's kept %s %q: its catch-up tool %q is not among the module's tools (%s); a "+ + "restore of it is followed by a tool of the module's own (novox/hq design 47)", m.Module, k.Kind, k.Name, k.CatchUp, + orNoneWords(m.Tools))) + } + } + return problems +} + // busEntryProblems judges one entry's storage type, template, options and configuration. func busEntryProblems(at string, e catalogue.BusStateEntry, terminal []string) []string { var problems []string diff --git a/modules/mesh-delivery-indexer/judge/busstate_test.go b/modules/mesh-delivery-indexer/judge/busstate_test.go index a38f7943..8f68a4b7 100644 --- a/modules/mesh-delivery-indexer/judge/busstate_test.go +++ b/modules/mesh-delivery-indexer/judge/busstate_test.go @@ -28,6 +28,24 @@ func TestBusStateIsJudgedOnce(t *testing.T) { {"within bounds", manifest(`{"name":"cache","storage":"key-value","template":"state","options":{"history":8,"key-ttl":true}},` + `{"name":"audit","storage":"stream","template":"log","options":{"max-mib":64,"max-age-days":365}},` + `{"name":"files","storage":"object-store","template":"objects","options":{"max-mib":2048}},` + ledger), nil}, + // Design 47: every kept data may say its form and its catch-up tool, in an old section and in `requires` by template, + // and is accepted as read; one saying neither reads as before. + {"a form and a catch-up tool on every kept kind", `{"module":"a","version":"1","tools":["a_take_in"],` + + `"state":["seen",{"name":"modules","form":1,"catch-up":"a_take_in"}],"logs":[{"name":"acts","form":2}],` + + `"streams":[{"name":"forge","events":["merged"],"form":1,"catch-up":"a_take_in"}],` + + `"objects":[{"name":"revisions","max-mib":64,"form":1,"catch-up":"a_take_in"}],"requires":[` + + `{"name":"holds","storage":"key-value","template":"state","form":1,"catch-up":"a_take_in"},` + + `{"name":"history","storage":"stream","template":"log","options":{"max-mib":64},"form":1},` + + `{"name":"marks","storage":"stream","template":"events","options":{"events":["seen"]},"form":1},` + + `{"name":"blobs","storage":"object-store","template":"objects","options":{"max-mib":64},"form":4,"catch-up":"a_take_in"},` + + `{"name":"records","storage":"ledger","template":"ledger","options":{"max-mib":512},"form":3,"catch-up":"a_take_in","types":[` + + `{"name":"decision","schema":{"title":{"type":"text"}},"operations":[{"name":"propose","effect":"set-fields","fields":["title"]}]}]}]}`, nil}, + {"a catch-up tool the module lacks", `{"module":"a","version":"1","tools":["a_read"],"logs":[{"name":"acts","form":1,"catch-up":"a_take_in"}]}`, + []string{"a's kept log \"acts\"", "catch-up tool \"a_take_in\" is not among the module's tools (a_read)"}}, + {"a catch-up tool the module lacks, in requires", `{"module":"a","version":"1","tools":["a_read"],"requires":[` + + `{"name":"holds","storage":"key-value","template":"state","form":1,"catch-up":"nonexistent"}]}`, + []string{"a's kept state \"holds\"", "catch-up tool \"nonexistent\""}}, + {"a form below 0", manifest(`{"name":"x","storage":"key-value","template":"state","form":-1}`), []string{"requires entry \"x\"", "its form is -1"}}, // 1966/AC7: each refused case, naming the module, the entry and the field {"unknown storage type", manifest(`{"name":"x","storage":"tape"}`), []string{"a's bus state \"x\"", "storage type \"tape\""}}, {"unknown template", manifest(`{"name":"x","storage":"stream","template":"pile"}`), []string{"\"x\"", "template \"pile\""}}, diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index dfd42f6d..b2e091a9 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -2760,6 +2760,8 @@ func parseManifest(raw []byte, predefined catalogue.PredefinedList) (catalogue.M problems = append(problems, WorkQueueStreamProblems(m)...) // And the bus state it asks for in `requires`, by template (busstate.go, novox/hq ADR 0595; issue 1966 D1). problems = append(problems, BusStateProblems(m)...) + // And the catch-up tool each kept data names (busstate.go, novox/hq design 47). + problems = append(problems, KeptFormProblems(m)...) // And the defaults it gives its settings (setting_defaults.go, novox/hq ADR 0262), or, written in the // config form, the whole declaration of each value (config.go, novox/hq ADR 0348). if m.DeclaresConfig() { diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index d81283a3..113c1610 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -909,7 +909,7 @@ func caseALogIsNamedLocallyAndCapped(t *testing.T) { {`{"module":"a","version":"1","logs":[{"name":"c","max-mib":8193}]}`, `between 1 and 8192 MiB`}, {`{"module":"a","version":"1","logs":[{"name":"c","max-mib":-1}]}`, `between 1 and 8192 MiB`}, {`{"module":"a","version":"1","logs":[{"name":"c","max-mib":0}]}`, `max-mib is between 1 and 8192, not 0`}, - {`{"module":"a","version":"1","logs":[{"name":"c","stream":"LOG_x"}]}`, `{name, max-mib, max-age-days}`}, + {`{"module":"a","version":"1","logs":[{"name":"c","stream":"LOG_x"}]}`, `{name, max-mib, max-age-days, form, catch-up}`}, {`{"module":"a","version":"1","logs":[{"name":"c","max-age-days":0}]}`, `max-age-days is between 1 and 3650, not 0`}, {`{"module":"a.b","version":"1","logs":["c"]}`, `no dot`}, } { @@ -2929,7 +2929,7 @@ func caseAStateNameIsLocalAndOneToken(t *testing.T) { {`{"module":"a.b","version":"1","state":["s"]}`, `no dot`}, {`{"module":"a","version":"1","reads":["bindings"]}`, `a read is .`}, {`{"module":"a","version":"1","reads":["a.s"]}`, `which is its own state`}, - {`{"module":"a","version":"1","state":[{"name":"s","shared":true}]}`, `{name, history, ttl-seconds, per-machine, key-ttl, read-only}`}, + {`{"module":"a","version":"1","state":[{"name":"s","shared":true}]}`, `{name, history, ttl-seconds, per-machine, key-ttl, read-only, form, catch-up}`}, } { _, err := ParseManifest([]byte(c.manifest)) if c.says == "" {