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 +}