From 35c3f5dd2fa68e5256bc3be5214f7252aa62f489 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 9 Nov 2026 12:42:15 +0100 Subject: [PATCH] The manifest validator reads kept state in both forms; per-machine in requires is refused MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- modules/mesh-delivery-indexer/judge/busstate.go | 9 +++++++++ .../mesh-delivery-indexer/judge/busstate_test.go | 8 ++++++++ modules/mesh-delivery-indexer/judge/rules.go | 15 +++++++-------- modules/mesh-delivery-indexer/judge/rules_test.go | 14 +++++++++++--- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/busstate.go b/modules/mesh-delivery-indexer/judge/busstate.go index 46598667..495aafc0 100644 --- a/modules/mesh-delivery-indexer/judge/busstate.go +++ b/modules/mesh-delivery-indexer/judge/busstate.go @@ -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 diff --git a/modules/mesh-delivery-indexer/judge/busstate_test.go b/modules/mesh-delivery-indexer/judge/busstate_test.go index a38f7943..91ec3a8f 100644 --- a/modules/mesh-delivery-indexer/judge/busstate_test.go +++ b/modules/mesh-delivery-indexer/judge/busstate_test.go @@ -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\""}}, diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index dfd42f6d..98dcc4f4 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -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)) diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index d81283a3..976613d6 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -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