The manifest validator refuses a catch-up tool the module does not declare #193

Merged
mesh-tools merged 3 commits from fix/manifest-form-reader into main 2026-11-09 21:27:40 +00:00
4 changed files with 38 additions and 2 deletions
@@ -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
@@ -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\""}},
@@ -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() {
@@ -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>.<name>`},
{`{"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 == "" {