The manifest validator reads kept state in both forms; per-machine in requires is refused

A process's bus.state, a seat contribution and a seat's records naming a
key-value entry asked in requires were refused, where the old state section
is accepted (issue 1966, ADR 0595 §11). They now read catalogue.KeptStates.
A per-machine key-value entry in requires is refused with its reason: its
binding names no machine's key, so no machine could be held to its own.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-09 14:01:41 +01:00
parent fb69c8dee6
commit 35c3f5dd2f
4 changed files with 35 additions and 11 deletions
@@ -166,6 +166,15 @@ func BusStateProblems(m catalogue.Manifest) []string {
"%s: the name is also kept in the old section %s; one local name is one form, so a module that moves an "+
"entry removes it from the old section (novox/hq ADR 0595 §11)", at, section))
}
// A key-value entry kept one bucket key per machine is not issued in the new form yet (issue 1966, the lead's
// ruling): its binding names no per-machine key, so neither the grants part nor the tool runner could hold each
// machine to its own key (ADR 0260). Refused with the way that holds, never issued as a whole bucket.
if e.Storage == catalogue.StorageKeyValue && string(e.Options[catalogue.OptionPerMachine]) == "true" {
problems = append(problems, fmt.Sprintf(
"%s: per-machine is not issued in `requires` yet, since its binding names no machine's key and each "+
"machine would reach every key; keep it in the old section state with per-machine until the binding "+
"carries it (novox/hq ADR 0260, ADR 0595 §1)", at))
}
problems = append(problems, busEntryProblems(at, e, terminal)...)
}
return problems
@@ -28,6 +28,14 @@ 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},
// Issue 1966: a process's bus state names a key-value entry of `requires` by its local name, as an old state's
// (ADR 0595 §11); one per machine is refused with the way that holds, its binding naming no machine's key (Q3).
{"a process naming a requires key-value entry", `{"module":"a","version":"1","requires":[{"name":"cache","storage":"key-value","template":"state"}],` +
`"resources":[{"id":"u","type":"user","name":"a"},{"id":"p","type":"process","user":"a","run":["./a"],"bus":{"state":["cache"]}}]}`, nil},
{"a process naming a state it keeps in neither form", `{"module":"a","version":"1","requires":[{"name":"cache","storage":"key-value","template":"state"}],` +
`"resources":[{"id":"u","type":"user","name":"a"},{"id":"p","type":"process","user":"a","run":["./a"],"bus":{"state":["other"]}}]}`, []string{"bus reads and writes state \"other\""}},
{"per-machine in requires", manifest(`{"name":"x","storage":"key-value","template":"state","options":{"per-machine":true}}`),
[]string{"\"x\"", "per-machine is not issued in `requires` yet", "old section state"}},
// 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\""}},
+7 -8
View File
@@ -3913,9 +3913,11 @@ func ProcessBusProblems(m catalogue.Manifest) []string {
for _, s := range m.Streams {
streams[s.Name] = true
}
// What the module keeps, in either form (catalogue.KeptStates, issue 1966): a process names a key-value entry of
// `requires` by its local name as it names an old state (ADR 0595 §11).
state := map[string]bool{}
for _, s := range m.State {
state[s.Name] = true
for _, s := range catalogue.KeptStates(m) {
state[s] = true
}
emits := map[string]bool{}
for _, e := range m.EmitsAll() {
@@ -4306,8 +4308,8 @@ func ownStateProblems(m catalogue.Manifest, i int, r catalogue.Receivable, c cat
return nil
}
keeps := map[string]bool{}
for _, s := range m.State {
keeps[s.Name] = true
for _, s := range catalogue.KeptStates(m) { // either form (issue 1966)
keeps[s] = true
}
var problems []string
for _, sr := range r.Shape.Reads(c.Data) {
@@ -5118,10 +5120,7 @@ func trafficProblems(m catalogue.Manifest, s catalogue.SeatDeclaration) []string
m.Module, s.Name))
}
for _, r := range s.Records {
kept := false
for _, st := range m.State {
kept = kept || st.Name == r
}
kept := slices.Contains(catalogue.KeptStates(m), r) // either form (issue 1966)
if !kept {
problems = append(problems, fmt.Sprintf(
"%s says %s's users read its records %q, which it keeps no state of", m.Module, s.Name, r))
@@ -1403,9 +1403,12 @@ func caseAModuleOffersItsOwnStateOnly(t *testing.T) {
t.Errorf("%s: accepted, or refused without %q: %v", raw, want, err)
}
}
ok := `{"module":"power","state":["draw"],"contributions":[` + fmt.Sprintf(block, "power.draw") + `]}`
if _, err := ParseManifest([]byte(ok)); err != nil {
t.Errorf("its own state was refused: %v", err)
// Issue 1966: its own state in either form, the old section's or a key-value entry of `requires` (ADR 0595 §11).
for _, ok := range []string{`{"module":"power","state":["draw"],"contributions":[` + fmt.Sprintf(block, "power.draw") + `]}`,
`{"module":"power","requires":[{"name":"draw","storage":"key-value","template":"state"}],"contributions":[` + fmt.Sprintf(block, "power.draw") + `]}`} {
if _, err := ParseManifest([]byte(ok)); err != nil {
t.Errorf("its own state was refused: %v", err)
}
}
}
@@ -4735,6 +4738,11 @@ func caseTheNewRulesAreHeldToWhatTheSeatSays(t *testing.T) {
t.Errorf("not refused: %q in %s", want, got)
}
}
// Issue 1966: records kept as a key-value entry of `requires` are kept state, as the old section's are.
m.BusState = []catalogue.BusStateEntry{{Name: "nothing", Storage: catalogue.StorageKeyValue, Template: catalogue.TemplateState}}
if got := strings.Join(declaredSeatProblems(m), "; "); strings.Contains(got, "keeps no state of") {
t.Errorf("records kept in requires refused: %s", got)
}
}
// **A build machine has no store, so it may not judge a seat.** The set is data the control plane