A-277: the manifest validator reads a state's max-mib and judges an approvals state as §677 declares it
The template state takes max-mib (1 to 8192, as the old state section reads it). A state named approvals is refused without key-ttl true, with ttl-seconds other than 172800 (approval.KeptFor, 48 hours), or with max-mib above 64; with none declared its bucket takes the default 64 MiB. Builds: design 47 G A-277, A-270, §677 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/approval"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
@@ -49,6 +50,8 @@ var busTemplates = map[string]busTemplate{
|
||||
catalogue.OptionTimeToLive: {kind: optionInt, least: 1, most: 315360000},
|
||||
catalogue.OptionLifetimePerKey: {kind: optionFlag},
|
||||
catalogue.OptionPerMachine: {kind: optionFlag},
|
||||
// A-277: a state's cap is an option of its template, read as the old state section reads it.
|
||||
catalogue.OptionCapMiB: {kind: optionInt, least: catalogue.ObjectsLeastMiB, most: catalogue.ObjectsMostMiB},
|
||||
},
|
||||
},
|
||||
catalogue.TemplateEvents: {
|
||||
@@ -236,9 +239,58 @@ func busEntryProblems(at string, e catalogue.BusStateEntry, terminal []string) [
|
||||
if e.Template == catalogue.TemplateWorkQueue {
|
||||
problems = append(problems, workQueueProblems(at, e)...)
|
||||
}
|
||||
if e.Template == catalogue.TemplateState && e.Name == approval.StateName {
|
||||
problems = append(problems, approvalsStateProblems(at, e)...)
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
// approvalsMostMiB is the most an approvals state holds (design 47 G §677's 64 MiB); with none declared its bucket takes
|
||||
// the default 64 MiB.
|
||||
const approvalsMostMiB = 64
|
||||
|
||||
// approvalsStateProblems judges a holder's approvals state as §677 declares it (lead answers A-270, A-277): with key-ttl,
|
||||
// since its bound keys `unanswered.<call>` need it, a lifetime of 48 hours and at most 64 MiB.
|
||||
func approvalsStateProblems(at string, e catalogue.BusStateEntry) []string {
|
||||
var problems []string
|
||||
var keyTTL bool
|
||||
if json.Unmarshal(e.Options[catalogue.OptionLifetimePerKey], &keyTTL) != nil || !keyTTL {
|
||||
problems = append(problems, fmt.Sprintf("%s: an approvals state is declared with %s true, since its bound keys "+
|
||||
"unanswered.<call> need it (novox/hq design 47 G §677, A-270)", at, catalogue.OptionLifetimePerKey))
|
||||
}
|
||||
want := int(approval.KeptFor.Seconds())
|
||||
if ttl, ok := intOption(e.Options, catalogue.OptionTimeToLive); !ok || ttl != want {
|
||||
problems = append(problems, fmt.Sprintf("%s: an approvals state keeps a value %d seconds (48 hours), so its %s is "+
|
||||
"%d, not %s (novox/hq design 47 G §677, A-277)", at, want, catalogue.OptionTimeToLive, want, givenOr(e.Options, catalogue.OptionTimeToLive)))
|
||||
}
|
||||
if mib, ok := intOption(e.Options, catalogue.OptionCapMiB); ok && mib > approvalsMostMiB {
|
||||
problems = append(problems, fmt.Sprintf("%s: an approvals state holds at most %d MiB, so its %s is at most %d, not %d "+
|
||||
"(novox/hq design 47 G §677, A-277)", at, approvalsMostMiB, catalogue.OptionCapMiB, approvalsMostMiB, mib))
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
// intOption is an option's whole number, false where it is not given or no number.
|
||||
func intOption(options map[string]json.RawMessage, name string) (int, bool) {
|
||||
raw, given := options[name]
|
||||
if !given {
|
||||
return 0, false
|
||||
}
|
||||
var n int
|
||||
if json.Unmarshal(raw, &n) != nil {
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// givenOr is an option's value as given, or "missing".
|
||||
func givenOr(options map[string]json.RawMessage, name string) string {
|
||||
if raw, given := options[name]; given {
|
||||
return string(raw)
|
||||
}
|
||||
return "missing"
|
||||
}
|
||||
|
||||
// settingsProblems judges the given settings against the rules: an unknown one is refused (a fixed setting among them),
|
||||
// a number outside its bound, a kind mismatch, and a required one missing. A bare stream's when-full and age are read
|
||||
// here too.
|
||||
|
||||
@@ -62,6 +62,14 @@ func TestBusStateIsJudgedOnce(t *testing.T) {
|
||||
{"log age 3651", manifest(`{"name":"x","storage":"stream","template":"log","options":{"max-age-days":3651}}`), []string{"\"x\"", "\"max-age-days\" is 3651"}},
|
||||
{"objects without a cap", manifest(`{"name":"x","storage":"object-store","template":"objects"}`), []string{"\"x\"", "\"max-mib\" is missing"}},
|
||||
{"when full given as an option", manifest(`{"name":"x","storage":"stream","template":"log","options":{"when-full":"drop-oldest"}}`), []string{"\"x\"", "\"when-full\"", "fixed"}},
|
||||
// A-277: a state's max-mib is an option of its template; an approvals state is §677's (key-ttl, 48 hours, at most 64 MiB).
|
||||
{"a state capped at 256 MiB", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"max-mib":256}}`), nil},
|
||||
{"a state cap over 8192", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"max-mib":8193}}`), []string{"\"x\"", "\"max-mib\" is 8193"}},
|
||||
{"an approvals state as §677 declares it", manifest(`{"name":"approvals","storage":"key-value","template":"state","options":{"key-ttl":true,"ttl-seconds":172800,"max-mib":64}}`), nil},
|
||||
{"an approvals state with the default cap", manifest(`{"name":"approvals","storage":"key-value","template":"state","options":{"key-ttl":true,"ttl-seconds":172800}}`), nil},
|
||||
{"an approvals state lacking key-ttl", manifest(`{"name":"approvals","storage":"key-value","template":"state","options":{"ttl-seconds":172800}}`), []string{"\"approvals\"", "key-ttl true"}},
|
||||
{"an approvals state kept a day", manifest(`{"name":"approvals","storage":"key-value","template":"state","options":{"key-ttl":true,"ttl-seconds":86400}}`), []string{"\"approvals\"", "ttl-seconds is 172800, not 86400"}},
|
||||
{"an approvals state over 64 MiB", manifest(`{"name":"approvals","storage":"key-value","template":"state","options":{"key-ttl":true,"ttl-seconds":172800,"max-mib":128}}`), []string{"\"approvals\"", "max-mib is at most 64, not 128"}},
|
||||
{"another fixed setting given", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"max-value-kib":1024}}`), []string{"\"x\"", "\"max-value-kib\" is not a setting of the template state"}},
|
||||
{"a configuration beside a template", manifest(`{"name":"x","storage":"key-value","template":"state","configuration":{"max-mib":1}}`), []string{"\"x\"", "configuration"}},
|
||||
{"a name used twice across storage types", manifest(
|
||||
|
||||
Reference in New Issue
Block a user