diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index fd8ff2b..6e7825b 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -200,6 +200,8 @@ Resource tests; defer unrelated stale structural fixtures to Stage 6. ## Stage 6 — Migrate engine structural and inspection fixtures +**Status:** Complete. The engine suite passes, including composition, inheritance, and source-evidence regressions. + **Scope:** remaining engine composition, collection, inheritance, lifecycle, history, provenance, explanation, sensitivity, and rendering tests/benchmarks. diff --git a/internal/engine/collection_ancestor_test.go b/internal/engine/collection_ancestor_test.go index e059fc3..ee95067 100644 --- a/internal/engine/collection_ancestor_test.go +++ b/internal/engine/collection_ancestor_test.go @@ -7,6 +7,28 @@ import ( "testing/fstest" ) +func TestNestedConfigDataSurvivesCompositionAndPrecedence(t *testing.T) { + loaded, err := testGroupedLoad(loadRequest[map[string]any]{ + main: loadInput{source: Bytes("main", []byte("service: {config: {enabled: false, count: 0}}\n"))}, + underlays: []loadGroup{{inputs: []loadInput{ + {source: Bytes("left", []byte("service: {config: {enabled: true, empty: ''}}\n"))}, + {source: Bytes("right", []byte("service: {config: {count: 3, optional: null}}\n"))}, + }}}, + }) + if err != nil { + t.Fatal(err) + } + values := loaded.Config["service"].(map[string]any)["config"].(map[string]any) + if values["enabled"] != false || values["count"] != int64(0) || values["empty"] != "" || values["optional"] != nil { + t.Fatalf("composed data = %#v", values) + } + for _, key := range []string{"enabled", "count", "empty", "optional"} { + if info, ok := loaded.Metadata().Lookup(Keys("service", "config", key)); !ok || !info.Explicit { + t.Errorf("presence for %s = %#v, %t", key, info, ok) + } + } +} + func TestNestedCollectionInitializationIsIndependentOfAncestorPresence(t *testing.T) { t.Parallel() for _, ancestor := range []string{"{}", "group: {}", "group: null"} { @@ -24,7 +46,7 @@ func TestNestedCollectionInitializationIsIndependentOfAncestorPresence(t *testin {mode: "upsert", key: ", key: id", value: "[]", valid: true}, } { t.Run(fmt.Sprintf("%s/%s/%s/%s", ancestor, test.mode, test.key, test.value), func(t *testing.T) { - main := "configkit:\n sequences: [{destination: [group, items], mode: " + test.mode + test.key + "}]\ngroup: {items: " + test.value + "}\n" + main := "config:\n sequences: [{destination: [group, items], mode: " + test.mode + test.key + "}]\ngroup: {items: " + test.value + "}\n" loaded, err := testGroupedLoad(loadRequest[map[string]any]{ main: loadInput{source: Bytes("main", []byte(main))}, underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte(ancestor))}}}}, @@ -60,13 +82,13 @@ func TestAddedAncestorTransfersNestedRecordPolicies(t *testing.T) { } `yaml:"group"` } files := fstest.MapFS{ - "main.yml": {Data: []byte(`configkit: + "main.yml": {Data: []byte(`config: sequences: [{destination: [group, items], key: id}] imports: [{path: base.yml, destination: [group, items], placement: sequence-item}] group: items: [{id: child, extends: {id: base}, steps: [{name: child}]}] `)}, - "base.yml": {Data: []byte("configkit:\n sequences: [{destination: [steps], key: name}]\nid: base\nsteps: [{name: base}]\n")}, + "base.yml": {Data: []byte("config:\n sequences: [{destination: [steps], key: name}]\nid: base\nsteps: [{name: base}]\n")}, } loaded, err := testGroupedLoad(loadRequest[config]{ main: loadInput{source: NewFilesystem("catalog", files).File("main.yml"), imports: true}, @@ -88,7 +110,7 @@ group: func TestInheritanceChecksCollectionOperationUnderAddedAncestor(t *testing.T) { t.Parallel() - _, err := testLoad[map[string]any](Bytes("inheritance", []byte(`configkit: + _, err := testLoad[map[string]any](Bytes("inheritance", []byte(`config: sequences: [{destination: [child, group, items], mode: append}] base: {} child: diff --git a/internal/engine/collection_policy_test.go b/internal/engine/collection_policy_test.go index c97ac3e..1611bc4 100644 --- a/internal/engine/collection_policy_test.go +++ b/internal/engine/collection_policy_test.go @@ -6,7 +6,7 @@ import ( ) func TestResolveCollectionPolicyReconcilesDeclarationsBeforeComposition(t *testing.T) { - declarations := extractSequenceDeclarationsYAML(t, `configkit: + declarations := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] key: [namespace, id] @@ -40,7 +40,7 @@ func TestResolveCollectionPolicyRejectsConflictingPeersAtBothSites(t *testing.T) }{ { name: "identity fields", - body: `configkit: + body: `config: sequences: - destination: [prompts] key: id @@ -50,7 +50,7 @@ func TestResolveCollectionPolicyRejectsConflictingPeersAtBothSites(t *testing.T) }, { name: "explicit modes", - body: `configkit: + body: `config: sequences: - destination: [prompts] mode: append @@ -85,7 +85,7 @@ func TestResolveCollectionPolicySelectsCurrentThenRetainedExplicitMode(t *testin explicitModePresent: true, modeLocation: &Location{SourceID: "lower", Line: 2}, } - current := extractSequenceDeclarationsYAML(t, `configkit: + current := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] mode: upsert @@ -105,7 +105,7 @@ func TestResolveCollectionPolicySelectsCurrentThenRetainedExplicitMode(t *testin } func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) { - keyOnly := extractSequenceDeclarationsYAML(t, `configkit: + keyOnly := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] key: id @@ -120,7 +120,7 @@ func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) { t.Fatalf("application identity was not kept distinct from YAML policy: %#v, %#v", resolved, diagnostics) } - noIdentity := extractSequenceDeclarationsYAML(t, `configkit: + noIdentity := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] `, rootDirectiveContextSource).sequences @@ -129,7 +129,7 @@ func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) { t.Fatalf("unkeyed mode inference = %#v, %#v", resolved, diagnostics) } - explicitDefault := extractSequenceDeclarationsYAML(t, `configkit: + explicitDefault := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] mode: replace @@ -141,7 +141,7 @@ func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) { } func TestResolveCollectionPolicyChecksKeyOnlyAgainstApplicationConstraint(t *testing.T) { - declarations := extractSequenceDeclarationsYAML(t, `configkit: + declarations := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] key: [namespace, id] @@ -169,7 +169,7 @@ func TestResolveCollectionPolicyChecksRetainedIdentityAgainstApplicationConstrai } func TestResolveCollectionPolicyRequiresCurrentIdentityToAgreeWithRetainedIdentity(t *testing.T) { - declarations := extractSequenceDeclarationsYAML(t, `configkit: + declarations := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] key: name diff --git a/internal/engine/collection_retirement_test.go b/internal/engine/collection_retirement_test.go index 8adb03f..3ca865b 100644 --- a/internal/engine/collection_retirement_test.go +++ b/internal/engine/collection_retirement_test.go @@ -12,8 +12,8 @@ func TestSourceReplacementRetiresIdentityForEveryValueKind(t *testing.T) { t.Parallel() for _, value := range []string{"null", "42", "{}", "[{name: new}]"} { t.Run(value, func(t *testing.T) { - lower := "configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n" - main := "configkit:\n sequences: [{destination: [items], key: name, mode: replace}]\nitems: " + value + "\n" + lower := "config:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n" + main := "config:\n sequences: [{destination: [items], key: name, mode: replace}]\nitems: " + value + "\n" loaded, err := testGroupedLoad(loadRequest[map[string]any]{ main: loadInput{source: Bytes("main", []byte(main))}, underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte(lower))}}}}, @@ -36,7 +36,7 @@ func TestAncestorRemovalCanEstablishPendingReplacementIdentity(t *testing.T) { var loaded *testResult[map[string]any] var err error if inheritance { - loaded, err = testLoad[map[string]any](Bytes("source", []byte(`configkit: + loaded, err = testLoad[map[string]any](Bytes("source", []byte(`config: sequences: - {destination: [base, group, items], key: id} - {destination: [child, group, items], key: name} @@ -52,8 +52,8 @@ grandchild: loaded, err = testGroupedLoad(loadRequest[map[string]any]{ main: loadInput{source: Bytes("main", []byte("group: {items: [{name: new}]}\n"))}, underlays: []loadGroup{ - {inputs: []loadInput{{source: Bytes("base", []byte("configkit:\n sequences: [{destination: [group, items], key: id}]\ngroup: {items: [{id: old}]}\n"))}}}, - {inputs: []loadInput{{source: Bytes("remove", []byte("configkit:\n sequences: [{destination: [group, items], key: name}]\ngroup: null\n"))}}}, + {inputs: []loadInput{{source: Bytes("base", []byte("config:\n sequences: [{destination: [group, items], key: id}]\ngroup: {items: [{id: old}]}\n"))}}}, + {inputs: []loadInput{{source: Bytes("remove", []byte("config:\n sequences: [{destination: [group, items], key: name}]\ngroup: null\n"))}}}, }, }) } @@ -74,7 +74,7 @@ grandchild: func TestInheritanceAncestorRetirementDoesNotResurrectOldIdentity(t *testing.T) { t.Parallel() - _, err := testLoad[map[string]any](Bytes("source", []byte(`configkit: + _, err := testLoad[map[string]any](Bytes("source", []byte(`config: sequences: [{destination: [base, group, items], key: id}] base: {group: {items: [{id: old}]}} child: @@ -92,7 +92,7 @@ grandchild: func TestReplacementRetirementPreservesApplicationConstraintsAndRetainedKeys(t *testing.T) { t.Parallel() for _, withValue := range []bool{false, true} { - main := "configkit:\n sequences: [{destination: [items], key: name, mode: replace}]\n" + main := "config:\n sequences: [{destination: [items], key: name, mode: replace}]\n" if withValue { main += "items: null\n" } @@ -104,7 +104,7 @@ func TestReplacementRetirementPreservesApplicationConstraintsAndRetainedKeys(t * } _, err := testGroupedLoad(loadRequest[map[string]any]{ main: loadInput{source: Bytes("main", []byte(main))}, definition: definition, - underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"))}}}}, + underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("config:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"))}}}}, }) var loadErr *LoadError if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 || loadErr.Diagnostics()[0].Code != CodeSequenceConflict { diff --git a/internal/engine/explain_test.go b/internal/engine/explain_test.go index 8169730..5dbb463 100644 --- a/internal/engine/explain_test.go +++ b/internal/engine/explain_test.go @@ -24,7 +24,7 @@ type explanationConcurrency struct { func TestExplainShowsDefaultBaseAndEffectiveOverlayValue(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "config.yml", "configkit:\n imports: [{path: transcription.yml}]\n") + mainPath := writeImportFile(t, directory, "config.yml", "config:\n imports: [{path: transcription.yml}]\n") writeImportFile(t, directory, "transcription.yml", "transcription:\n concurrency: 8\n") writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), "transcription:\n concurrency: 1\n") result, err := testLoad[explanationConcurrency](File(mainPath), Options[explanationConcurrency]{ diff --git a/internal/engine/history_sequence_test.go b/internal/engine/history_sequence_test.go index 893b2a0..647079f 100644 --- a/internal/engine/history_sequence_test.go +++ b/internal/engine/history_sequence_test.go @@ -235,7 +235,7 @@ func TestLayeredUpsertExplanationFollowsStructuralOrder(t *testing.T) { Records []map[string]any `yaml:"records"` } loaded, err := testGroupedLoad(loadRequest[config]{ - main: loadInput{source: Bytes("main", []byte("configkit:\n sequences: [{destination: [records], key: id}]\nrecords: [{id: b, value: new-b}, {id: c, value: new-c}, {id: a, value: new-a}]\n"))}, + main: loadInput{source: Bytes("main", []byte("config:\n sequences: [{destination: [records], key: id}]\nrecords: [{id: b, value: new-b}, {id: c, value: new-c}, {id: a, value: new-a}]\n"))}, underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("records: [{id: a, value: old-a}, {id: b, value: old-b, removed: old}]\n"))}}}}, }) if err != nil { diff --git a/internal/engine/import_evidence_test.go b/internal/engine/import_evidence_test.go index 53f4762..9ccfa08 100644 --- a/internal/engine/import_evidence_test.go +++ b/internal/engine/import_evidence_test.go @@ -8,6 +8,27 @@ import ( "testing" ) +func TestControlEvidenceAndApplicationValuesSharePathText(t *testing.T) { + directory := t.TempDir() + main := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: missing.yml}, {path: data.yml, destination: [config]}]}\n") + writeImportFile(t, directory, "data.yml", "imports: [ordinary]\n") + loaded, err := testLoad[map[string]any](File(main), Options[map[string]any]{Imports: true}) + if err != nil { + t.Fatal(err) + } + sources := loaded.Sources() + if len(sources) != 2 || len(sources[0].importSelections) != 1 || sources[0].importSelections[0].path.String() != "/config/imports/0" || sources[0].importSelections[0].location.SourceID != sources[0].ID { + t.Fatalf("source control evidence = %#v", sources) + } + info, ok := loaded.Metadata().Lookup(Keys("config", "imports").Index(0)) + if !ok || len(info.Origin.Locations) == 0 || info.Origin.Locations[0].SourceID != sources[1].ID { + t.Fatalf("application value attribution = %#v, %t", info, ok) + } + if got := loaded.Config["config"].(map[string]any)["imports"].([]any)[0]; got != "ordinary" { + t.Fatalf("application value = %#v", got) + } +} + func TestBroadEmptyImportSelectionsKeepDeclaringSourceOwnership(t *testing.T) { t.Parallel() diff --git a/internal/engine/inheritance_collection_test.go b/internal/engine/inheritance_collection_test.go index 05f35e1..64d0e68 100644 --- a/internal/engine/inheritance_collection_test.go +++ b/internal/engine/inheritance_collection_test.go @@ -76,7 +76,7 @@ func TestInheritanceReplacementResetsOnlyReplacedYamlIdentity(t *testing.T) { func TestLoadInheritanceReplacementUsesNewYamlKey(t *testing.T) { t.Parallel() - loaded, err := testLoad[map[string]any](Bytes("replacement", []byte(`configkit: + loaded, err := testLoad[map[string]any](Bytes("replacement", []byte(`config: sequences: - {destination: [base, items], key: id} - {destination: [child, items], key: name, mode: replace} @@ -150,7 +150,7 @@ child: } func TestLoadAppliesYamlCollectionIdentityDuringSiblingInheritance(t *testing.T) { - input := `configkit: + input := `config: sequences: - destination: [base, items] key: id diff --git a/internal/engine/inheritance_identity_history_test.go b/internal/engine/inheritance_identity_history_test.go index a1e23d8..e4b6e54 100644 --- a/internal/engine/inheritance_identity_history_test.go +++ b/internal/engine/inheritance_identity_history_test.go @@ -16,7 +16,7 @@ func TestRecordInheritanceHistoryPreservesChildIdentity(t *testing.T) { key, reference = "[id, tenant]", "{id: base, tenant: parent-team}" identities["tenant"] = "child-team" } - body := "configkit:\n sequences: [{destination: [items], key: " + key + "}]\nitems:\n - id: base\n tenant: parent-team\n value: inherited\n - id: child\n tenant: child-team\n extends: " + reference + "\n" + body := "config:\n sequences: [{destination: [items], key: " + key + "}]\nitems:\n - id: base\n tenant: parent-team\n value: inherited\n - id: child\n tenant: child-team\n extends: " + reference + "\n" loaded, err := testLoad(Bytes("records", []byte(body)), Options[map[string]any]{}) if err != nil { t.Fatal(err) diff --git a/internal/engine/inheritance_policy_index_test.go b/internal/engine/inheritance_policy_index_test.go index cae34e9..8e7b6a5 100644 --- a/internal/engine/inheritance_policy_index_test.go +++ b/internal/engine/inheritance_policy_index_test.go @@ -76,7 +76,7 @@ func TestInheritancePolicyIndexReplacesOnlyTheChangedScope(t *testing.T) { func TestInheritanceWideIndependentYamlCollectionScopes(t *testing.T) { var body strings.Builder - body.WriteString("configkit:\n sequences:\n") + body.WriteString("config:\n sequences:\n") for index := range 64 { fmt.Fprintf(&body, " - {destination: [groups, group-%03d, base, items], key: id, mode: append}\n", index) } diff --git a/internal/engine/inheritance_test.go b/internal/engine/inheritance_test.go index 110a8c5..6418bab 100644 --- a/internal/engine/inheritance_test.go +++ b/internal/engine/inheritance_test.go @@ -71,6 +71,21 @@ func TestSiblingInheritanceResolvesReferencesAndKeepsInputs(t *testing.T) { } } +func TestSiblingInheritancePreservesNestedConfigData(t *testing.T) { + input := parseCompositionDocument(t, "source-1", "parent: {config: {enabled: false, count: 0, empty: '', optional: null}}\nchild: {extends: parent}\n") + result, diagnostics := expandSiblingDefinitions(input, Path{}) + if len(diagnostics) != 0 { + t.Fatal(diagnostics) + } + for _, key := range []string{"enabled", "count", "empty", "optional"} { + parent, ok := documentAtPath(result.root, Keys("parent", "config", key)) + child, found := documentAtPath(result.root, Keys("child", "config", key)) + if !ok || !found || parent.Kind != child.Kind || parent.Text != child.Text { + t.Fatalf("inherited %s = %#v, parent = %#v", key, child, parent) + } + } +} + func TestSiblingInheritanceTracksTransferAndIndependentChildren(t *testing.T) { input := parseCompositionDocument(t, "source-1", "first: {value: parent}\nsecond: {extends: first}\nthird: {extends: first}\n") result, diagnostics := expandSiblingDefinitions(input, Keys("catalog")) diff --git a/internal/engine/load_test.go b/internal/engine/load_test.go index 4809e00..6bc6028 100644 --- a/internal/engine/load_test.go +++ b/internal/engine/load_test.go @@ -584,7 +584,7 @@ func TestLoadRejectsUnavailableOptionsAndDisabledDirectives(t *testing.T) { }) } - result, err := testLoad[config](Bytes("directive.yml", []byte("nested:\n configkit: {}\n"))) + result, err := testLoad[config](Bytes("directive.yml", []byte("config: {imports: []}\n"))) if result != nil || err == nil { t.Fatalf("result, err = %#v, %v", result, err) } @@ -593,7 +593,7 @@ func TestLoadRejectsUnavailableOptionsAndDisabledDirectives(t *testing.T) { t.Fatal(err) } diagnostic := loadError.Diagnostics()[0] - if diagnostic.Code != CodeImportDisabled || diagnostic.Path.String() != "/nested/configkit" || diagnostic.Primary == nil || diagnostic.Primary.Line != 2 { + if diagnostic.Code != CodeImportDisabled || diagnostic.Path.String() != "/config" || diagnostic.Primary == nil || diagnostic.Primary.Line != 1 { t.Fatalf("diagnostic = %#v", diagnostic) } } diff --git a/internal/engine/metadata_test.go b/internal/engine/metadata_test.go index 22c1a3f..e371ab0 100644 --- a/internal/engine/metadata_test.go +++ b/internal/engine/metadata_test.go @@ -129,7 +129,7 @@ func TestHistoricalPathsExposeHiddenBaseOwnershipWithoutResolvingBaseSecrets(t * for _, credential := range []string{"CONFIGKIT_HISTORY_TEST_KEY", "null"} { t.Run(credential, func(t *testing.T) { dir := t.TempDir() - root := writeImportFile(t, dir, "main.yml", "configkit:\n imports: [{path: first.yml}, {path: second.yml}]\ncredential: CONFIGKIT_HISTORY_UNUSED_PROD_KEY\n") + root := writeImportFile(t, dir, "main.yml", "config:\n imports: [{path: first.yml}, {path: second.yml}]\ncredential: CONFIGKIT_HISTORY_UNUSED_PROD_KEY\n") writeImportFile(t, dir, "first.yml", "definitions: {recap: {prompt: recap}}\n") writeImportFile(t, dir, "second.yml", "definitions: {recap: {output: recap.md}}\n") writeImportFile(t, dir, "overlays/testing.yml", "definitions: null\ncredential: "+credential+"\n") diff --git a/internal/engine/ordinary_sequence_test.go b/internal/engine/ordinary_sequence_test.go index 3f3b7be..75e4e2a 100644 --- a/internal/engine/ordinary_sequence_test.go +++ b/internal/engine/ordinary_sequence_test.go @@ -25,7 +25,7 @@ type ordinarySequenceConfig struct { } func TestOrdinaryLoadAcceptsDeclarationsWithoutImports(t *testing.T) { - result, err := testLoad[ordinarySequenceConfig](Bytes("catalog.yml", []byte(`configkit: + result, err := testLoad[ordinarySequenceConfig](Bytes("catalog.yml", []byte(`config: sequences: - destination: [items] key: id @@ -48,7 +48,7 @@ items: } func TestOrdinaryLoadKeepsDisabledImportRejection(t *testing.T) { - _, err := testLoad[ordinarySequenceConfig](Bytes("disabled.yml", []byte(`configkit: + _, err := testLoad[ordinarySequenceConfig](Bytes("disabled.yml", []byte(`config: imports: [{path: fragment.yml}] sequences: [{destination: [items], key: id}] `))) @@ -62,7 +62,7 @@ func TestOrdinaryLoadKeepsDisabledImportRejection(t *testing.T) { } func TestOrdinaryLoadValidatesSingletonCollectionIdentity(t *testing.T) { - _, err := testLoad[ordinarySequenceConfig](Bytes("missing-identity.yml", []byte(`configkit: + _, err := testLoad[ordinarySequenceConfig](Bytes("missing-identity.yml", []byte(`config: sequences: - destination: [items] key: id @@ -90,7 +90,7 @@ func TestOrdinaryLoadCombinesImportedCollectionPeers(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: imports: - path: first.yml - path: second.yml @@ -123,7 +123,7 @@ func TestOrdinaryLoadCombinesImportedCollectionPeers(t *testing.T) { if !reflect.DeepEqual(result.Config.Items, test.want) { t.Fatalf("items = %#v, want %#v", result.Config.Items, test.want) } - inline, err := testLoad[ordinarySequenceConfig](Bytes("inline.yml", []byte(`configkit: + inline, err := testLoad[ordinarySequenceConfig](Bytes("inline.yml", []byte(`config: sequences: - destination: [items] key: id @@ -142,14 +142,14 @@ items: [{id: first}, {id: second}] func TestImportedCollectionDeclarationsUseRelativePlacement(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: imports: - path: first.yml destination: [catalog] - path: second.yml destination: [catalog] `) - fragment := `configkit: + fragment := `config: sequences: - destination: [items] key: id @@ -171,13 +171,13 @@ items: [{id: ITEM}] func TestSequenceItemSourceAppliesItsCollectionDeclarations(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: imports: - path: item.yml destination: [records] placement: sequence-item `) - writeImportFile(t, directory, "item.yml", `configkit: + writeImportFile(t, directory, "item.yml", `config: sequences: - destination: [children] key: id @@ -196,7 +196,7 @@ children: [{id: child}] } func TestOrdinaryLoadAllowsDeclarationsWithoutValues(t *testing.T) { - result, err := testLoad[ordinarySequenceConfig](Bytes("policy.yml", []byte(`configkit: + result, err := testLoad[ordinarySequenceConfig](Bytes("policy.yml", []byte(`config: sequences: - destination: [items] key: id diff --git a/internal/engine/override_history_order_test.go b/internal/engine/override_history_order_test.go index ec7ff85..b251afa 100644 --- a/internal/engine/override_history_order_test.go +++ b/internal/engine/override_history_order_test.go @@ -22,7 +22,7 @@ func TestCLIHistoryFollowsInheritanceAndPrecedesResolution(t *testing.T) { } override := Override{Path: query, Value: cliValue, Flag: "--value"} if test.records { - body = "configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: base, value: parent}, {id: child, extends: {id: base}}]\n" + body = "config:\n sequences: [{destination: [items], key: id}]\nitems: [{id: base, value: parent}, {id: child, extends: {id: base}}]\n" query, parent = Keys("items").Index(1).Key("value"), Keys("items").Index(0).Key("value") if test.equal { cliValue = "parent" diff --git a/internal/engine/override_lifecycle_test.go b/internal/engine/override_lifecycle_test.go index c0a7f2f..c0084c5 100644 --- a/internal/engine/override_lifecycle_test.go +++ b/internal/engine/override_lifecycle_test.go @@ -178,7 +178,7 @@ func TestCLISequenceAssignmentReplacesAppendAndUpsertResults(t *testing.T) { Items []item `yaml:"items"` } declaration := func(mode string, id string) Source { - return Bytes(id, []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: "+mode+"\nitems: [{id: "+id+"}]\n")) + return Bytes(id, []byte("config:\n sequences:\n - destination: [items]\n key: id\n mode: "+mode+"\nitems: [{id: "+id+"}]\n")) } result, err := testGroupedLoad(loadRequest[config]{ main: loadInput{source: declaration("upsert", "main")}, diff --git a/internal/engine/override_test.go b/internal/engine/override_test.go index a54d276..bc4e4eb 100644 --- a/internal/engine/override_test.go +++ b/internal/engine/override_test.go @@ -30,7 +30,7 @@ type overrideLifecycleConfig struct { func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports: [{path: base.yml}]\nversion: v1\n") + mainPath := writeImportFile(t, directory, "main.yml", "config:\n imports: [{path: base.yml}]\nversion: v1\n") writeImportFile(t, directory, "base.yml", `settings: target: base count: 4 diff --git a/internal/engine/selected_overlay_test.go b/internal/engine/selected_overlay_test.go index fbc5b01..74cc416 100644 --- a/internal/engine/selected_overlay_test.go +++ b/internal/engine/selected_overlay_test.go @@ -101,7 +101,7 @@ func TestNoSourceSelectedOverlayUsesCompleteNativeContext(t *testing.T) { func TestLoadAppliesSelectedOverlayAfterBaseAndBeforeDefaults(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: imports: [{path: base.yml}] settings: kept: main @@ -191,7 +191,7 @@ func TestLoadSelectedOverlayAppliesCollectionOperationsAndEvidence(t *testing.T) { name: "append", mode: "append", - overlay: `configkit: + overlay: `config: sequences: - destination: [items] key: id @@ -203,7 +203,7 @@ items: [{id: c, value: added}] { name: "upsert replaces complete records at retained positions", mode: "upsert", - overlay: `configkit: + overlay: `config: sequences: - destination: [items] key: id @@ -215,7 +215,7 @@ items: [{id: a, value: new}] { name: "replace", mode: "replace", - overlay: `configkit: + overlay: `config: sequences: - destination: [items] key: id @@ -283,7 +283,7 @@ func TestLoadSelectedOverlayReconcilesPendingAndRetainedCollectionPolicy(t *test } } t.Run("key only validates retained records", func(t *testing.T) { - requireCode(t, "items: [{id: duplicate}, {id: duplicate}]\n", `configkit: + requireCode(t, "items: [{id: duplicate}, {id: duplicate}]\n", `config: sequences: - destination: [items] key: id @@ -292,14 +292,14 @@ func TestLoadSelectedOverlayReconcilesPendingAndRetainedCollectionPolicy(t *test t.Run("mode only retains lower identity", func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: sequences: - destination: [items] key: id mode: append items: [{id: base}] `) - writeImportFile(t, directory, "overlays/testing.yml", `configkit: + writeImportFile(t, directory, "overlays/testing.yml", `config: sequences: - destination: [items] mode: append @@ -316,7 +316,7 @@ items: [{id: overlay}] t.Run("lower explicit mode remains active when overlay omits it", func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: sequences: - destination: [items] key: id @@ -331,12 +331,12 @@ items: [{id: base}] }) t.Run("overlay identity must agree with retained identity", func(t *testing.T) { - requireCode(t, `configkit: + requireCode(t, `config: sequences: - destination: [items] key: id items: [{id: base}] -`, `configkit: +`, `config: sequences: - destination: [items] key: slug @@ -352,7 +352,7 @@ items: [{id: overlay}] {mode: "replace"}, {mode: "append", want: CodeSequenceInvalid}, } { - overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: null\n" + overlay := "config:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: null\n" if test.want != "" { requireCode(t, "items: [{id: base}]\n", overlay, test.want) continue @@ -379,7 +379,7 @@ items: [{id: overlay}] {name: "append mapping", mode: "append", value: "{id: overlay}", wantCode: CodeSequenceInvalid}, } { t.Run(test.name, func(t *testing.T) { - overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: " + test.value + "\n" + overlay := "config:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: " + test.value + "\n" if test.wantCode != "" { requireCode(t, "items: [{id: base}]\n", overlay, test.wantCode) return @@ -399,7 +399,7 @@ items: [{id: overlay}] func TestLoadSelectedOverlayKeepsCollectionPoliciesIndependent(t *testing.T) { t.Parallel() directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", `configkit: + mainPath := writeImportFile(t, directory, "main.yml", `config: sequences: - destination: [items] key: id @@ -410,7 +410,7 @@ func TestLoadSelectedOverlayKeepsCollectionPoliciesIndependent(t *testing.T) { items: [{id: base}] catalog: {items: [{id: catalog-base}]} `) - writeImportFile(t, directory, "overlays/testing.yml", `configkit: + writeImportFile(t, directory, "overlays/testing.yml", `config: sequences: - destination: [items] mode: upsert @@ -477,8 +477,7 @@ func TestLoadOverlayRejectsDirectivesVersionsAndBaseAliases(t *testing.T) { overlay string code Code }{ - {name: "root directive", overlay: "configkit: {}\n", code: CodeOverlayDirective}, - {name: "nested directive", overlay: "settings: {configkit: {}}\n", code: CodeOverlayDirective}, + {name: "root directive", overlay: "config: {}\n", code: CodeOverlayDirective}, {name: "equal integer version", overlay: "schema: {version: 4}\n", code: CodeOverlayVersion}, {name: "ancestor scalar", overlay: "schema: null\n", code: CodeOverlayVersion}, } { @@ -502,7 +501,7 @@ func TestLoadOverlayRejectsDirectivesVersionsAndBaseAliases(t *testing.T) { t.Run("overlay aliases imported base", func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: overlays/testing.yml}]}\n") + mainPath := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: overlays/testing.yml}]}\n") writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: value}\n") requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Imports: true, Overlay: "testing"}, CodeOverlayRepeated) }) diff --git a/internal/engine/sequence_combine_test.go b/internal/engine/sequence_combine_test.go index add14b2..8e6b966 100644 --- a/internal/engine/sequence_combine_test.go +++ b/internal/engine/sequence_combine_test.go @@ -177,7 +177,7 @@ func TestCombineSequenceReplacementResetsLowerIdentity(t *testing.T) { } func TestCombineSequenceDifferentIdentityDeclarationsConflict(t *testing.T) { - declarations := extractSequenceDeclarationsYAML(t, `configkit: + declarations := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [items] key: id @@ -256,7 +256,7 @@ func TestCombineSequenceClonesInputsAndDoesNotMutatePolicy(t *testing.T) { func TestCombineSequenceEquivalentIdentityFieldSets(t *testing.T) { lower := sequenceFromYAML(t, "items: [{id: a, namespace: one}]\n") higher := sequenceFromYAML(t, "items: [{namespace: one, id: b}]\n") - policy, diagnostics := resolveCollectionPolicy(Keys("items"), extractSequenceDeclarationsYAML(t, `configkit: + policy, diagnostics := resolveCollectionPolicy(Keys("items"), extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [items] key: [namespace, id, id] diff --git a/internal/engine/sequence_declaration_test.go b/internal/engine/sequence_declaration_test.go index 35be78c..5144f33 100644 --- a/internal/engine/sequence_declaration_test.go +++ b/internal/engine/sequence_declaration_test.go @@ -23,12 +23,12 @@ func TestParseSequenceDeclarationPresenceAndRelativeDestination(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - extracted := extractSequenceDeclarationsYAML(t, "configkit:\n sequences:\n - "+test.declaration+"\n", rootDirectiveContextSource) + extracted := extractSequenceDeclarationsYAML(t, "config:\n sequences:\n - "+test.declaration+"\n", rootDirectiveContextSource) if len(extracted.sequences) != 1 { t.Fatalf("extraction = %#v", extracted) } declaration := extracted.sequences[0] - if declaration.destination.String() != "/prompts" || declaration.path.String() != "/configkit/sequences/0" { + if declaration.destination.String() != "/prompts" || declaration.path.String() != "/config/sequences/0" { t.Fatalf("relative destination or declaration path changed: %#v", declaration) } if declaration.location.SourceID != "sequence-declarations" || declaration.location.Line == 0 { @@ -51,7 +51,7 @@ func TestParseSequenceDeclarationPresenceAndRelativeDestination(t *testing.T) { } func TestParseSequenceDeclarationNormalizesEquivalentFieldSets(t *testing.T) { - extracted := extractSequenceDeclarationsYAML(t, `configkit: + extracted := extractSequenceDeclarationsYAML(t, `config: sequences: - destination: [prompts] key: [namespace, id, id] @@ -100,17 +100,17 @@ func TestParseSequenceDeclarationRejectsMalformedForms(t *testing.T) { body string path string }{ - {name: "sequences wrong type", body: "configkit: {sequences: nope}\n", path: "/configkit/sequences"}, - {name: "declaration wrong type", body: "configkit: {sequences: [nope]}\n", path: "/configkit/sequences/0"}, - {name: "missing destination", body: "configkit: {sequences: [{key: id}]}\n", path: "/configkit/sequences/0/destination"}, - {name: "destination wrong type", body: "configkit: {sequences: [{destination: nope}]}\n", path: "/configkit/sequences/0/destination"}, - {name: "reserved destination", body: "configkit: {sequences: [{destination: [configkit]}]}\n", path: "/configkit/sequences/0/destination/0"}, - {name: "key wrong type", body: "configkit: {sequences: [{destination: [prompts], key: true}]}\n", path: "/configkit/sequences/0/key"}, - {name: "empty key set", body: "configkit: {sequences: [{destination: [prompts], key: []}]}\n", path: "/configkit/sequences/0/key"}, - {name: "key member wrong type", body: "configkit: {sequences: [{destination: [prompts], key: [id, null]}]}\n", path: "/configkit/sequences/0/key/1"}, - {name: "mode wrong type", body: "configkit: {sequences: [{destination: [prompts], mode: true}]}\n", path: "/configkit/sequences/0/mode"}, - {name: "unknown mode", body: "configkit: {sequences: [{destination: [prompts], mode: merge}]}\n", path: "/configkit/sequences/0/mode"}, - {name: "unknown declaration field", body: "configkit: {sequences: [{destination: [prompts], append: true}]}\n", path: "/configkit/sequences/0/append"}, + {name: "sequences wrong type", body: "config: {sequences: nope}\n", path: "/config/sequences"}, + {name: "declaration wrong type", body: "config: {sequences: [nope]}\n", path: "/config/sequences/0"}, + {name: "missing destination", body: "config: {sequences: [{key: id}]}\n", path: "/config/sequences/0/destination"}, + {name: "destination wrong type", body: "config: {sequences: [{destination: nope}]}\n", path: "/config/sequences/0/destination"}, + {name: "reserved destination", body: "config: {sequences: [{destination: [extends]}]}\n", path: "/config/sequences/0/destination/0"}, + {name: "key wrong type", body: "config: {sequences: [{destination: [prompts], key: true}]}\n", path: "/config/sequences/0/key"}, + {name: "empty key set", body: "config: {sequences: [{destination: [prompts], key: []}]}\n", path: "/config/sequences/0/key"}, + {name: "key member wrong type", body: "config: {sequences: [{destination: [prompts], key: [id, null]}]}\n", path: "/config/sequences/0/key/1"}, + {name: "mode wrong type", body: "config: {sequences: [{destination: [prompts], mode: true}]}\n", path: "/config/sequences/0/mode"}, + {name: "unknown mode", body: "config: {sequences: [{destination: [prompts], mode: merge}]}\n", path: "/config/sequences/0/mode"}, + {name: "unknown declaration field", body: "config: {sequences: [{destination: [prompts], append: true}]}\n", path: "/config/sequences/0/append"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/internal/engine/sequence_destination_projection_test.go b/internal/engine/sequence_destination_projection_test.go index 7926708..0525bd1 100644 --- a/internal/engine/sequence_destination_projection_test.go +++ b/internal/engine/sequence_destination_projection_test.go @@ -17,10 +17,10 @@ func TestLayeredSequenceProjectsNestedSourceDestinations(t *testing.T) { t.Run(fmt.Sprintf("%s/underlay=%t", mode, underlay), func(t *testing.T) { files := fstest.MapFS{} for _, id := range []string{"a", "b", "c"} { - files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("configkit: {imports: [{path: helper.yml}]}\nid: " + id + "\n")} + files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("config: {imports: [{path: helper.yml}]}\nid: " + id + "\n")} files[id+"/helper.yml"] = &fstest.MapFile{Data: []byte("value: " + id + "\n")} } - files["main.yml"] = &fstest.MapFile{Data: []byte("configkit:\n sequences: [{destination: [items], key: id, mode: " + mode + "}]\n imports: [{glob: '*/record.yml', destination: [items], placement: sequence-item}]\n")} + files["main.yml"] = &fstest.MapFile{Data: []byte("config:\n sequences: [{destination: [items], key: id, mode: " + mode + "}]\n imports: [{glob: '*/record.yml', destination: [items], placement: sequence-item}]\n")} lower := "items: [{id: b}, {id: a}, {id: keep}]\n" want := map[string]int{"a": 1, "b": 0, "c": 3} if mode == "append" { diff --git a/internal/engine/sequence_origin_test.go b/internal/engine/sequence_origin_test.go index fcc5517..4e3045b 100644 --- a/internal/engine/sequence_origin_test.go +++ b/internal/engine/sequence_origin_test.go @@ -34,11 +34,11 @@ func TestSequenceUpsertProjectsIncomingOrigins(t *testing.T) { t.Run(name, func(t *testing.T) { files := fstest.MapFS{} for _, id := range []string{"a", "b", "c"} { - files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("configkit: {imports: [{path: helper.yml}]}\nid: " + id + "\ndetails: {label: " + id + "}\n")} + files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("config: {imports: [{path: helper.yml}]}\nid: " + id + "\ndetails: {label: " + id + "}\n")} files[id+"/helper.yml"] = &fstest.MapFile{Data: []byte("details: {resource: asset.txt}\n")} files[id+"/asset.txt"] = &fstest.MapFile{Data: []byte(id + " asset")} } - main := "configkit:\n sequences:\n - destination: " + destination + "\n key: id\n imports:\n - glob: '*/record.yml'\n destination: " + destination + "\n placement: sequence-item\n" + main := "config:\n sequences:\n - destination: " + destination + "\n key: id\n imports:\n - glob: '*/record.yml'\n destination: " + destination + "\n placement: sequence-item\n" if inheritance { main += "base:\n items: [{id: b}, {id: a}, {id: keep}]\nchild:\n extends: base\n" } diff --git a/internal/engine/sequence_registration_test.go b/internal/engine/sequence_registration_test.go index 612fd6d..baedeaa 100644 --- a/internal/engine/sequence_registration_test.go +++ b/internal/engine/sequence_registration_test.go @@ -349,7 +349,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) { t.Run("ordinary imports retain registered composition", func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\nitems: [{id: authored}]\n") + mainPath := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: fragment.yml}]}\nitems: [{id: authored}]\n") writeImportFile(t, directory, "fragment.yml", "items: [{id: imported}]\n") result, err := testLoad[config](File(mainPath), Options[config]{Imports: true, Definition: definition}) if err != nil || len(result.Config.Items) != 2 || result.Config.Items[0].ID != "imported" || result.Config.Items[1].ID != "authored" {