From 208acf939ba429a381ed35cedfec4d4836bc120a Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 11:06:49 +0100 Subject: [PATCH] catalogue.InvokedVerbs reads a manifest's invokes in both forms for a part that holds no Manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G A-275, §558 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/catalogue/login_shell_execute_test.go | 13 +++++++++++++ go/catalogue/manifest.go | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/go/catalogue/login_shell_execute_test.go b/go/catalogue/login_shell_execute_test.go index 146e0604..39ac5f4a 100644 --- a/go/catalogue/login_shell_execute_test.go +++ b/go/catalogue/login_shell_execute_test.go @@ -76,3 +76,16 @@ func TestAHolderOfAMarkedVerbDeclaresItsApprovalsStateWithKeyTTL(t *testing.T) { t.Errorf("a seat with no marked verb: approvals unserved is %v, want it named", got) } } + +// Design 47 G A-275: InvokedVerbs reads both forms of an `invokes` entry and refuses one of neither. +func TestInvokedVerbsReadsBothFormsAndRefusesAnother(t *testing.T) { + got, err := InvokedVerbs(json.RawMessage(`["seat:a.b",{"verb":"seat:c.d","fields":["x"]}]`)) + if err != nil || len(got) != 2 || got[0] != "seat:a.b" || got[1] != "seat:c.d" { + t.Fatalf("%v, %v", got, err) + } + for _, bad := range []string{`[3]`, `{"verb":"x"}`, `[{"verb":""}]`} { + if _, err := InvokedVerbs(json.RawMessage(bad)); err == nil { + t.Errorf("%s was read", bad) + } + } +} diff --git a/go/catalogue/manifest.go b/go/catalogue/manifest.go index 9da7c658..f737f9f1 100644 --- a/go/catalogue/manifest.go +++ b/go/catalogue/manifest.go @@ -2265,3 +2265,17 @@ const InstancesInterchangeable = "interchangeable" // WhileStopped is the resource key naming the containers a scheduled step holds still while it // runs (novox/hq ADR 0189). Carried to the host unchanged, like `schedule`. const WhileStopped = "while-stopped" + +// InvokedVerbs is the verbs of a manifest's `invokes` in either form, a verb or {verb, fields} (design 47 G §558, A-275): the +// one reader of the field for a part that holds no Manifest. An entry of neither form is an error, never none. +func InvokedVerbs(raw json.RawMessage) ([]string, error) { + keys := map[string]json.RawMessage{"invokes": raw} + if _, err := readInvokes(keys); err != nil { + return nil, err + } + var verbs []string + if err := json.Unmarshal(keys["invokes"], &verbs); err != nil { + return nil, fmt.Errorf("invokes: a list of verbs, or of {verb, fields}: %w", err) + } + return verbs, nil +}