diff --git a/go/catalogue/manifest_reads.go b/go/catalogue/manifest_reads.go index fb9add96..805bf130 100644 --- a/go/catalogue/manifest_reads.go +++ b/go/catalogue/manifest_reads.go @@ -38,6 +38,9 @@ const ( var referenceForms = []string{"access", "backup", "bound", "collected", "config", "consumer", "consumers", "contribution", "dir", "machine", "port", "seat", "secret", "setting"} +// ifConfigWord is the reader capability for a resource's `if-config` (IfConfigField). +const ifConfigWord = ReadWord + "resources." + IfConfigField + // ReferenceFormSeat names the reference form a registry seat answers for a build's artifact, // `//@sha256:` (novox/hq ADR 0605 §9; the registry clients' reference forms), as the reader capability // `form:seat-reference` a node-engine built with this contract states (ADR 0618 §1, ADR 0624 §1): the builder writes it @@ -106,6 +109,7 @@ func ManifestReads() []string { set[ReadForm+f] = true } set[ReadForm+ReferenceFormSeatReach] = true + set[ifConfigWord] = true for _, s := range Seats() { set[ReadSeat+s.Name] = true } @@ -171,6 +175,13 @@ func ManifestNeeds(raw []byte) ([]string, error) { if claimsAtMesh(top, storeHolderSeat) { used[ReadForm+ReferenceFormSeatReach] = true } + for _, r := range asList(top["resources"]) { + if obj, ok := r.(map[string]any); ok { + if _, has := obj[IfConfigField]; has { + used[ifConfigWord] = true + } + } + } if _, has := top[ConfigField]; has || takesConfigKey.Match(raw) { used[ReadForm+"config"] = true } diff --git a/go/catalogue/own_manifest_test.go b/go/catalogue/own_manifest_test.go index dae109f6..602cfd58 100644 --- a/go/catalogue/own_manifest_test.go +++ b/go/catalogue/own_manifest_test.go @@ -96,6 +96,11 @@ func TestOwnManifestUsesNoWordTheRunningControllerRefuses(t *testing.T) { {"a template naming a seat's reach", `{"module":"d","version":"1","resources":[{"id":"daemon","type":"file",` + `"path":"/etc/d.json","template":"files/daemon.json"}]}`, []string{"form:seat", "form:seat-reach"}, []string{"form:seat-reach"}}, + // A resource under a config condition (design 42 zfs-owns-zfs §4, A-13). + {"a resource under if-config", `{"module":"z","version":"1","config":{"k":{"type":"string"}},"resources":[{"id":"f","type":"file",` + + `"if-config":["k"]}]}`, []string{"word:resources.if-config"}, []string{"word:resources.if-config"}}, + {"a resource without if-config", `{"module":"z","version":"1","resources":[{"id":"f","type":"file"}]}`, + []string{"word:resources"}, nil}, {"a word this contract does not know", `{"module":"u","version":"1","newer-word":{"inside":1}}`, []string{"word:module"}, nil}, } { templates := map[string]map[string]string{ // the templates' text a row's module names @@ -119,6 +124,9 @@ func TestOwnManifestUsesNoWordTheRunningControllerRefuses(t *testing.T) { if slices.ContainsFunc(needs, func(n string) bool { return n == "word:newer-word" || n == "form:count" }) { t.Errorf("%s: needs %v, naming what the contract does not read", row.why, needs) } + if slices.Contains(needs, "word:resources.if-config") != (row.why == "a resource under if-config") { + t.Errorf("%s: needs %v, if-config read wrongly", row.why, needs) + } if got := MissingManifestReads(needs, nil); !slices.Equal(got, row.missing) { t.Errorf("%s: an engine stating nothing lacks %v, want %v", row.why, got, row.missing) } diff --git a/go/catalogue/terminal_keys.go b/go/catalogue/terminal_keys.go index fc3ff722..02d7ad88 100644 --- a/go/catalogue/terminal_keys.go +++ b/go/catalogue/terminal_keys.go @@ -12,6 +12,12 @@ import ( // catalogue, never on the machine: the composer takes it out before the node-engine, which parses strictly. const TrustedField = "trusted" +// IfConfigField is the key any resource may carry to be declared only while a config value of its module holds +// (novox/hq design 42 zfs-owns-zfs §2, §4): a list of conditions, `` or `=`. Said in the catalogue and +// read by the node-engine, which states it as the reader capability `word:resources.if-config` (ManifestReads); a +// manifest whose resource carries it needs that capability (ManifestNeeds). +const IfConfigField = "if-config" + // TerminalKeys are the settings keys of a module that are set at the controller's terminal alone: places and // accesses, every key its provisions serve and every setting a served value asks for, every setting a file // asks for unless it says `"trusted": false`, and every setting a directory's owner asks for. Places and accesses first, then the rest sorted.