diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index b79f12c5..5aae9322 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -875,6 +875,14 @@ func configPlaced(m catalogue.Manifest) map[string]bool { for _, u := range configLinesUses(m) { placed[u.key] = true } + // A file holding ${config-json} writes every config value of the module (design 42 zfs-owns-zfs §9, A-15). + for _, r := range m.Resources { + if content, _ := r["content"].(string); fmt.Sprint(r["type"]) == "file" && ofConfigJSON.MatchString(content) { + for key := range m.Settings { + placed[key] = true + } + } + } return placed } diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index e920e46e..77e31198 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -3594,6 +3594,25 @@ func caseConfigLinesNamesADeclaredValueAndAPlainPrefix(t *testing.T) { refused(t, withLines("${config-lines:nowhere:o=}"), `no value "nowhere"`) } +// Design 42 zfs-owns-zfs §9, V3/AC1 (A-15, A-24): a file holding ${config-json} places every config value. +func caseConfigJSONPlacesEveryValueOfTheModule(t *testing.T) { + m := map[string]any{"module": "m", "version": "1", + "config": map[string]any{ + "a": map[string]any{"type": "string", "description": "a word nothing else says", "default": "x"}, + "b": map[string]any{"type": "number", "description": "a count nothing else says", "default": 1}, + }, + "resources": []any{map[string]any{"id": "c", "type": "file", "path": "/x/c.json", "content": "# none\n"}}} + refused(t, m, `"a"`, `"b"`, "placed nowhere") + resourceOf(m, "c")["content"] = "${config-json}" + if _, err := parseRaw(t, m); err != nil { + t.Fatal(err) + } + // Only in a file's content. + m["resources"] = append(m["resources"].([]any), map[string]any{"id": "u", "type": "service", "unit": "u.service", "restart-on": []any{"c"}}) + resourceOf(m, "u")["unit"] = "x ${config-json}" + refused(t, m, "${config-json}") +} + // Consumer data said to live in a provision the module requires is protected only as well as that // provision's provider protects its consumers' data. func caseConsumerDataInARequiredProvisionIsJudgedAgainstItsProvider(t *testing.T) { @@ -6190,6 +6209,7 @@ func TestRulesConfig(t *testing.T) { t.Run("ConfigStillRefusesAValueNoItemPlaces", caseConfigStillRefusesAValueNoItemPlaces) t.Run("IfConfigNamesADeclaredValueAndPlacesIt", caseIfConfigNamesADeclaredValueAndPlacesIt) t.Run("ConfigLinesNamesADeclaredValueAndAPlainPrefix", caseConfigLinesNamesADeclaredValueAndAPlainPrefix) + t.Run("ConfigJSONPlacesEveryValueOfTheModule", caseConfigJSONPlacesEveryValueOfTheModule) } // TestRulesClaims runs the tests of one kind as subtests, each by its former name.