diff --git a/configuration_sources_test.go b/configuration_sources_test.go index 541fb61..9571d69 100644 --- a/configuration_sources_test.go +++ b/configuration_sources_test.go @@ -40,7 +40,7 @@ func TestCollectionInputLoadsNativeImportsAndLaterOverride(t *testing.T) { if err := os.WriteFile(mainPath, []byte("config:\n imports:\n - path: defaults.yaml\n required: true\n"), 0o600); err != nil { t.Fatal(err) } - if err := os.WriteFile(defaultsPath, []byte("prompts:\n greeting:\n version: imported\n messages:\n - role: user\n content: Hello\n output:\n format: text\n validation_mode: none\n"), 0o600); err != nil { + if err := os.WriteFile(defaultsPath, []byte("prompts:\n - id: greeting\n messages:\n - role: user\n content: Hello\n output:\n format: text\n validation_mode: none\n"), 0o600); err != nil { t.Fatal(err) } input, err := promptkit.CollectionInput("docs", configkit.File(mainPath)) @@ -50,7 +50,7 @@ func TestCollectionInputLoadsNativeImportsAndLaterOverride(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{ Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}}, Main: &configkit.Input{ - Source: configkit.Mapping("later collection override", map[string]any{"prompts": map[string]any{"greeting": map[string]any{"version": "overridden"}}}), + Source: configkit.Mapping("later collection override", map[string]any{"prompts": []any{map[string]any{"id": "greeting", "inputs": []any{map[string]any{"name": "overridden"}}, "messages": []any{map[string]any{"role": "user", "content": "Updated"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}}}}), Destination: configkit.Keys("collections", "docs"), }, }}) @@ -58,7 +58,7 @@ func TestCollectionInputLoadsNativeImportsAndLaterOverride(t *testing.T) { t.Fatalf("construct engine with native collection import: %v", err) } inspection, err := engine.InspectPrompt(context.Background(), "docs/greeting") - if err != nil || inspection.PromptVersion == nil || *inspection.PromptVersion != "overridden" { + if err != nil || inspection.PromptID != "docs/greeting" || len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "overridden" { t.Fatalf("inspect imported and overlaid prompt = (%+v, %v)", inspection, err) } } @@ -66,7 +66,7 @@ func TestCollectionInputLoadsNativeImportsAndLaterOverride(t *testing.T) { func TestCollectionInputLoadsEmbeddedImportsAndLaterOverride(t *testing.T) { files := fstest.MapFS{ "collection.yaml": {Data: []byte("config:\n imports:\n - path: defaults.yaml\n required: true\n")}, - "defaults.yaml": {Data: []byte("prompts:\n greeting:\n version: imported\n messages:\n - role: user\n content: Hello\n output:\n format: text\n validation_mode: none\n")}, + "defaults.yaml": {Data: []byte("prompts:\n - id: greeting\n messages:\n - role: user\n content: Hello\n output:\n format: text\n validation_mode: none\n")}, } filesystem := configkit.NewFilesystem("embedded collection", files) input, err := promptkit.CollectionInput("docs", filesystem.File("collection.yaml")) @@ -77,7 +77,7 @@ func TestCollectionInputLoadsEmbeddedImportsAndLaterOverride(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{ Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}}, Main: &configkit.Input{ - Source: configkit.Mapping("later collection override", map[string]any{"prompts": map[string]any{"greeting": map[string]any{"version": "overridden"}}}), + Source: configkit.Mapping("later collection override", map[string]any{"prompts": []any{map[string]any{"id": "greeting", "inputs": []any{map[string]any{"name": "overridden"}}, "messages": []any{map[string]any{"role": "user", "content": "Updated"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}}}}), Destination: configkit.Keys("collections", "docs"), }, }}) @@ -85,7 +85,7 @@ func TestCollectionInputLoadsEmbeddedImportsAndLaterOverride(t *testing.T) { t.Fatalf("construct engine with embedded collection import: %v", err) } inspection, err := engine.InspectPrompt(context.Background(), "docs/greeting") - if err != nil || inspection.PromptVersion == nil || *inspection.PromptVersion != "overridden" { + if err != nil || inspection.PromptID != "docs/greeting" || len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "overridden" { t.Fatalf("inspect imported and overlaid prompt = (%+v, %v)", inspection, err) } } diff --git a/construction_contract_test.go b/construction_contract_test.go index 54f0779..3abecd7 100644 --- a/construction_contract_test.go +++ b/construction_contract_test.go @@ -73,8 +73,8 @@ func TestProfileEnvironmentLookupAcceptsNonShellNames(t *testing.T) { func TestPublicConstructionWithLocalBackendAndProgrammaticProfile(t *testing.T) { backendID, model := "workstation", "offline-model" prompt := configkit.Input{Source: configkit.Mapping("prompt", map[string]any{ - "prompts": map[string]any{"hello": map[string]any{ - "version": "1", + "prompts": []any{map[string]any{ + "id": "hello", "default_profile": "offline", "messages": []any{map[string]any{"role": "user", "content": "Hello"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}, @@ -143,12 +143,11 @@ func TestPublicConstructionCombinesEmbeddedOperatorAndProgrammaticInputs(t *test embedded, err := promptkit.FilesystemInputs("embedded defaults", fstest.MapFS{ "config/default.yaml": &fstest.MapFile{Data: []byte(` profiles: - application: + - id: application endpoint: https://example.invalid/v1 model: default-model prompts: - hello: - version: "1" + - id: hello default_profile: application messages: - role: user @@ -162,16 +161,16 @@ prompts: t.Fatalf("select embedded defaults: %v", err) } operatorDirectory := t.TempDir() - if err := os.WriteFile(filepath.Join(operatorDirectory, "override.yaml"), []byte("profiles:\n application:\n model: operator-model\n"), 0o644); err != nil { + if err := os.WriteFile(filepath.Join(operatorDirectory, "override.yaml"), []byte("profiles:\n - id: application\n endpoint: https://example.invalid/v1\n model: operator-model\n"), 0o644); err != nil { t.Fatalf("write operator input: %v", err) } operator, err := promptkit.DirectoryInputs(operatorDirectory) if err != nil { t.Fatalf("select operator inputs: %v", err) } - programmaticModel := "programmatic-model" + programmaticModel, programmaticEndpoint := "programmatic-model", "https://example.invalid/v1" programmatic := configkit.Input{Source: promptkit.ProfileSource("programmatic settings", []promptkit.ProfileConfig{ - {ID: "application", Model: &programmaticModel}, + {ID: "application", Endpoint: &programmaticEndpoint, Model: &programmaticModel}, })} for _, test := range []struct { name string @@ -207,7 +206,7 @@ prompts: func TestPublicConstructionPreservesConfigKitLoadError(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ - Source: configkit.Mapping("invalid", map[string]any{"profiles": map[string]any{"broken": map[string]any{"unknown": true}}}), + Source: configkit.Mapping("invalid", map[string]any{"profiles": []any{map[string]any{"id": "broken", "unknown": true}}}), }}}) var loadErr *configkit.LoadError if engine != nil || !errors.Is(err, promptkit.ErrInvalidConfig) || !errors.As(err, &loadErr) { @@ -291,7 +290,7 @@ func TestBackendSourceOwnsSmokeTestValuesAndDefersDefaultLookup(t *testing.T) { "https://backend.example/v1", "not-yet-listed", "https://probe.example/ready?check=1", - "openrouter_key", + " OPENROUTER_KEY ", "https://probe.example/key?scope=current", } { if !strings.Contains(contents, want) { diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index b937bca..f6d09c0 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -701,6 +701,13 @@ inspection, listing, and namespace contract tests. Validation: run the affected external-package contract tests using real ConfigKit composition and offline sources. Do not add index/list-position guarantees. +Completion: public source, construction, profile inheritance/inspection, +namespace, and inventory fixtures now use explicit-ID lists and identity +inheritance. Layered replacements supply complete records, and configuration +inspection assertions reflect composed values. Focused offline contract tests +passed on Go 1.27.0. Tests depending on the shared legacy execution fixture and +the full root package remain assigned to subsequent fixture migrations. + ## Stage 16: Migrate Internal Execution And Rendering Fixtures Scope: remaining `internal/usecase`, `internal/prompt`, `internal/domain`, and diff --git a/listing_contract_test.go b/listing_contract_test.go index 58a4223..6687cef 100644 --- a/listing_contract_test.go +++ b/listing_contract_test.go @@ -17,8 +17,8 @@ import ( func TestListIdentitiesBasicContract(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ Source: configkit.Mapping("listing", map[string]any{ - "profiles": map[string]any{"configured-profile": map[string]any{"model": "example-model"}}, - "prompts": map[string]any{"example-prompt": promptDefinitionWithVersion("10", listingContractPrompt())}, + "profiles": []any{map[string]any{"id": "configured-profile", "model": "example-model"}}, + "prompts": []any{listingContractPrompt("example-prompt")}, }), }}}) if err != nil { @@ -38,13 +38,13 @@ func TestListIdentitiesBasicContract(t *testing.T) { t.Fatal(err) } want := []promptkit.PromptIdentity{ - {ID: "example-prompt", Version: stringPointerForTest("10")}, + {ID: "example-prompt"}, } if len(prompts) != len(want) { t.Fatalf("prompt listing = %#v, want %#v", prompts, want) } for i := range want { - if prompts[i].ID != want[i].ID || prompts[i].Version == nil || *prompts[i].Version != *want[i].Version { + if prompts[i].ID != want[i].ID { t.Fatalf("prompt listing = %#v, want %#v", prompts, want) } } @@ -54,8 +54,7 @@ func TestListIdentitiesBasicContract(t *testing.T) { t.Fatalf("listed profile could not be inspected: (%+v, %v)", profile, err) } prompt, err := engine.InspectPrompt(context.Background(), prompts[0].ID) - if err != nil || prompt == nil || prompt.PromptID != prompts[0].ID || prompt.PromptVersion == nil || - *prompt.PromptVersion != *prompts[0].Version { + if err != nil || prompt == nil || prompt.PromptID != prompts[0].ID { t.Fatalf("listed prompt could not be inspected: (%+v, %v)", prompt, err) } } @@ -135,16 +134,15 @@ func TestScopedIdentityListingsApplyNamespaceFilters(t *testing.T) { } prompts, err := scoped.ListPrompts(context.Background(), promptkit.NamespaceFilter{Mode: promptkit.NamespaceNamed, Name: "team"}) - if err != nil || len(prompts) != 1 || prompts[0].ID != "team/prompt" || prompts[0].Version == nil || *prompts[0].Version != "team-v1" { + if err != nil || len(prompts) != 1 || prompts[0].ID != "team/prompt" { t.Fatalf("named prompt identity = %#v, %v", prompts, err) } if _, err := scoped.InspectPrompt(context.Background(), prompts[0].ID); err != nil { t.Fatalf("listed full prompt ID did not round trip: %v", err) } - *prompts[0].Version = "caller change" prompts[0].ID = "caller change" prompts, err = scoped.ListPrompts(context.Background(), promptkit.NamespaceFilter{Mode: promptkit.NamespaceNamed, Name: "team"}) - if err != nil || len(prompts) != 1 || prompts[0].ID != "team/prompt" || prompts[0].Version == nil || *prompts[0].Version != "team-v1" { + if err != nil || len(prompts) != 1 || prompts[0].ID != "team/prompt" { t.Fatalf("mutating filtered prompt inventory escaped: %#v, %v", prompts, err) } profiles, err := scoped.ListProfiles(context.Background(), promptkit.NamespaceFilter{Mode: promptkit.NamespaceNamed, Name: "team"}) @@ -214,9 +212,9 @@ func newNamespaceListingEngine(t *testing.T) *promptkit.Engine { t.Helper() collection := func(namespace string) map[string]any { return map[string]any{ - "backends": map[string]any{"backend": map[string]any{"endpoint": "https://example.test/v1"}}, - "profiles": map[string]any{"profile": map[string]any{"backend": namespace + "/backend", "model": "example-model"}}, - "prompts": map[string]any{"prompt": promptDefinitionWithVersion("team-v1", listingContractPrompt())}, + "backends": []any{map[string]any{"id": "backend", "endpoint": "https://example.test/v1"}}, + "profiles": []any{map[string]any{"id": "profile", "backend": namespace + "/backend", "model": "example-model"}}, + "prompts": []any{listingContractPrompt("prompt")}, } } input := configkit.Input{Source: configkit.Mapping("namespaced listing", map[string]any{ @@ -224,9 +222,9 @@ func newNamespaceListingEngine(t *testing.T) *promptkit.Engine { "team": collection("team"), "team-blue": collection("team-blue"), }, - "backends": map[string]any{"root-backend": map[string]any{"endpoint": "https://root.example.test/v1"}}, - "profiles": map[string]any{"root-profile": map[string]any{"backend": "root-backend", "model": "root-model"}}, - "prompts": map[string]any{"root-prompt": promptDefinitionWithVersion("root-v1", listingContractPrompt())}, + "backends": []any{map[string]any{"id": "root-backend", "endpoint": "https://root.example.test/v1"}}, + "profiles": []any{map[string]any{"id": "root-profile", "backend": "root-backend", "model": "root-model"}}, + "prompts": []any{listingContractPrompt("root-prompt")}, })} engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &input}}) if err != nil { @@ -241,18 +239,18 @@ func newNamespaceListingEngine(t *testing.T) *promptkit.Engine { func TestResourceAndCollectionInventories(t *testing.T) { underlay := configkit.Input{Source: configkit.Mapping("inventory defaults", map[string]any{ - "backends": map[string]any{"root-backend": map[string]any{"endpoint": "https://root.example.test/v1"}}, - "profiles": map[string]any{"root-profile": map[string]any{"endpoint": "https://root.example.test/v1", "model": "root-model"}}, - "prompts": map[string]any{"root-prompt": listingContractPrompt()}, + "backends": []any{map[string]any{"id": "root-backend", "endpoint": "https://root.example.test/v1"}}, + "profiles": []any{map[string]any{"id": "root-profile", "endpoint": "https://root.example.test/v1", "model": "root-model"}}, + "prompts": []any{listingContractPrompt("root-prompt")}, "collections": map[string]any{ "alpha": map[string]any{ - "backends": map[string]any{"api": map[string]any{"endpoint": "https://alpha.example.test/v1"}}, - "profiles": map[string]any{"profile": map[string]any{"backend": "alpha/api", "model": "alpha-model"}}, - "prompts": map[string]any{"prompt": listingContractPrompt()}, + "backends": []any{map[string]any{"id": "api", "endpoint": "https://alpha.example.test/v1"}}, + "profiles": []any{map[string]any{"id": "profile", "backend": "alpha/api", "model": "alpha-model"}}, + "prompts": []any{listingContractPrompt("prompt")}, }, - "alpha-beta": map[string]any{"prompts": map[string]any{"prompt": listingContractPrompt()}}, - "beta": map[string]any{"backends": map[string]any{"api": map[string]any{"endpoint": "https://beta.example.test/v1"}}}, - "resource-only": map[string]any{"profiles": map[string]any{"profile": map[string]any{"endpoint": "https://resource.example.test/v1", "model": "resource-model"}}}, + "alpha-beta": map[string]any{"prompts": []any{listingContractPrompt("prompt")}}, + "beta": map[string]any{"backends": []any{map[string]any{"id": "api", "endpoint": "https://beta.example.test/v1"}}}, + "resource-only": map[string]any{"profiles": []any{map[string]any{"id": "profile", "endpoint": "https://resource.example.test/v1", "model": "resource-model"}}}, "metadata-only": map[string]any{"release_version": "v1"}, "empty-label": map[string]any{"release_version": ""}, "null-label": map[string]any{"release_version": nil}, @@ -363,8 +361,8 @@ func TestListIdentitiesInvalidEngine(t *testing.T) { func TestListIdentitiesPreserveContextErrorsWithoutPartialResults(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ Source: configkit.Mapping("listing", map[string]any{ - "profiles": map[string]any{"profile": map[string]any{}}, - "prompts": map[string]any{"example": promptDefinitionWithVersion("v1", listingContractPrompt())}, + "profiles": []any{map[string]any{"id": "profile"}}, + "prompts": []any{listingContractPrompt("example")}, }), }}}) if err != nil { @@ -399,26 +397,20 @@ func TestListIdentitiesPreserveContextErrorsWithoutPartialResults(t *testing.T) func TestListIdentitiesUseEffectiveLayeredMembership(t *testing.T) { underlay := configkit.Input{Source: configkit.Mapping("consumer defaults", map[string]any{ "collections": map[string]any{"shared": map[string]any{ - "profiles": map[string]any{ - "base": map[string]any{"endpoint": "https://example.test/v1", "model": "base-model"}, - "child": map[string]any{"extends": "base"}, - "overlay": map[string]any{"endpoint": "https://example.test/v1", "model": "old-model"}, - }, - "prompts": map[string]any{ - "prompt": promptDefinitionWithVersion("v1", listingContractPrompt()), + "profiles": []any{ + map[string]any{"id": "base", "endpoint": "https://example.test/v1", "model": "base-model"}, + map[string]any{"id": "child", "extends": map[string]any{"id": "base"}}, + map[string]any{"id": "overlay", "endpoint": "https://example.test/v1", "model": "old-model"}, }, + "prompts": []any{listingContractPrompt("prompt")}, }}, - "prompts": map[string]any{ - "underlay-only": promptDefinitionWithVersion("v1", listingContractPrompt()), - }, + "prompts": []any{listingContractPrompt("underlay-only")}, })} main := configkit.Input{Source: configkit.Mapping("application overlay", map[string]any{ - "collections": map[string]any{"shared": map[string]any{"profiles": map[string]any{ - "overlay": map[string]any{"model": "effective-model"}, + "collections": map[string]any{"shared": map[string]any{"profiles": []any{ + map[string]any{"id": "overlay", "model": "effective-model"}, }}}, - "prompts": map[string]any{ - "main-only": promptDefinitionWithVersion("v3", listingContractPrompt()), - }, + "prompts": []any{listingContractPrompt("main-only")}, })} engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{ Underlays: []configkit.Underlay{{Inputs: []configkit.Input{underlay}}}, @@ -459,14 +451,14 @@ func TestListIdentitiesUseEffectiveLayeredMembership(t *testing.T) { t.Fatal(err) } wantPrompts := []promptkit.PromptIdentity{ - {ID: "main-only", Version: stringPointerForTest("v3")}, - {ID: "shared/prompt", Version: stringPointerForTest("v1")}, - {ID: "underlay-only", Version: stringPointerForTest("v1")}, + {ID: "main-only"}, + {ID: "shared/prompt"}, + {ID: "underlay-only"}, } if !reflect.DeepEqual(prompts, wantPrompts) { t.Fatalf("effective prompt identities = %#v, want %#v", prompts, wantPrompts) } - if got := countListingPrompt(prompts, "shared/prompt", "v1"); got != 1 { + if got := countListingPrompt(prompts, "shared/prompt"); got != 1 { t.Fatalf("overlaid prompt identity appears %d times, want 1", got) } } @@ -488,8 +480,8 @@ func TestListIdentitiesEmptyEffectiveConfiguration(t *testing.T) { func TestListIdentitiesAreOwnedAndSafeForConcurrentUse(t *testing.T) { engine, err := newListingContractEngine(t, map[string]any{ - "profiles": map[string]any{"profile": map[string]any{"model": "partial-model"}}, - "prompts": map[string]any{"prompt": promptDefinitionWithVersion("v1", listingContractPrompt())}, + "profiles": []any{map[string]any{"id": "profile", "model": "partial-model"}}, + "prompts": []any{listingContractPrompt("prompt")}, }) if err != nil { t.Fatal(err) @@ -511,9 +503,6 @@ func TestListIdentitiesAreOwnedAndSafeForConcurrentUse(t *testing.T) { for i := range prompts { if prompts[i].ID == "prompt" { prompts[i].ID = "caller mutation" - if prompts[i].Version != nil { - *prompts[i].Version = "caller mutation" - } } } profiles, err = engine.ListProfiles(context.Background()) @@ -521,7 +510,7 @@ func TestListIdentitiesAreOwnedAndSafeForConcurrentUse(t *testing.T) { t.Fatalf("profile result mutation escaped into engine: %#v, %v", profiles, err) } prompts, err = engine.ListPrompts(context.Background()) - if err != nil || countListingPrompt(prompts, "prompt", "v1") != 1 { + if err != nil || countListingPrompt(prompts, "prompt") != 1 { t.Fatalf("prompt result mutation escaped into engine: %#v, %v", prompts, err) } @@ -557,9 +546,6 @@ func TestListIdentitiesAreOwnedAndSafeForConcurrentUse(t *testing.T) { return } prompts[0].ID = "worker mutation" - if prompts[0].Version != nil { - *prompts[0].Version = "worker mutation" - } if _, err := engine.InspectProfile(context.Background(), "profile"); err != nil { results <- fmt.Errorf("inspect profile: %w", err) @@ -582,7 +568,7 @@ func TestListIdentitiesAreOwnedAndSafeForConcurrentUse(t *testing.T) { func TestListIdentitiesUseCapturedSourcesWithoutExecutionWork(t *testing.T) { files := fstest.MapFS{ - "config/listing.yaml": &fstest.MapFile{Data: []byte("profiles:\n captured-partial:\n model: partial-model\nprompts:\n captured-prompt:\n version: v1\n messages:\n - role: user\n content: Hello.\n output:\n format: text\n validation_mode: basic\n")}, + "config/listing.yaml": &fstest.MapFile{Data: []byte("profiles:\n - id: captured-partial\n model: partial-model\nprompts:\n - id: captured-prompt\n messages:\n - role: user\n content: Hello.\n output:\n format: text\n validation_mode: basic\n")}, } inputs, err := promptkit.FilesystemInputs("application listing", files, "config") if err != nil { @@ -594,20 +580,21 @@ func TestListIdentitiesUseCapturedSourcesWithoutExecutionWork(t *testing.T) { if err != nil { t.Fatal(err) } - files["config/listing.yaml"].Data = []byte("profiles:\n replacement-profile: {}\nprompts:\n replacement-prompt:\n version: v2\n messages:\n - role: user\n content: Changed.\n output:\n format: text\n validation_mode: basic\n") + files["config/listing.yaml"].Data = []byte("profiles:\n - id: replacement-profile\nprompts:\n - id: replacement-prompt\n messages:\n - role: user\n content: Changed.\n output:\n format: text\n validation_mode: basic\n") profiles, err := engine.ListProfiles(context.Background()) if err != nil || !containsListingProfile(profiles, "captured-partial") || containsListingProfile(profiles, "replacement-profile") { t.Fatalf("profile listing did not use captured source: %#v, %v", profiles, err) } prompts, err := engine.ListPrompts(context.Background()) - if err != nil || countListingPrompt(prompts, "captured-prompt", "v1") != 1 || countListingPrompt(prompts, "replacement-prompt", "v2") != 0 { + if err != nil || countListingPrompt(prompts, "captured-prompt") != 1 || countListingPrompt(prompts, "replacement-prompt") != 0 { t.Fatalf("prompt listing did not use captured source: %#v, %v", prompts, err) } } -func listingContractPrompt() map[string]any { +func listingContractPrompt(id string) map[string]any { return map[string]any{ + "id": id, "messages": []any{map[string]any{"role": "user", "content": "Hello."}}, "output": map[string]any{"format": "text", "validation_mode": "basic"}, } @@ -632,10 +619,10 @@ func countListingProfile(profiles []string, id string) int { return count } -func countListingPrompt(prompts []promptkit.PromptIdentity, id, version string) int { +func countListingPrompt(prompts []promptkit.PromptIdentity, id string) int { count := 0 for _, prompt := range prompts { - if prompt.ID == id && prompt.Version != nil && *prompt.Version == version { + if prompt.ID == id { count++ } } diff --git a/namespaces_contract_test.go b/namespaces_contract_test.go index acebd61..813a595 100644 --- a/namespaces_contract_test.go +++ b/namespaces_contract_test.go @@ -268,12 +268,11 @@ func newNamespacedContractEngine(t *testing.T, client promptkit.LLMClient) *prom for _, namespace := range []string{"alpha", "beta"} { endpoint := "https://" + namespace + ".example/v1" input, err := promptkit.CollectionInput(namespace, configkit.Mapping(namespace+" resources", map[string]any{ - "backends": map[string]any{"backend": map[string]any{"endpoint": endpoint}}, - "profiles": map[string]any{"shared": map[string]any{ + "backends": []any{map[string]any{"id": "backend", "endpoint": endpoint}}, + "profiles": []any{map[string]any{"id": "shared", "backend": namespace + "/backend", "model": namespace + "-model", }}, - "prompts": map[string]any{"shared": map[string]any{ - "version": "1", + "prompts": []any{map[string]any{"id": "shared", "default_profile": "root-only", "messages": []any{map[string]any{"role": "user", "content": "Hello"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}, @@ -286,11 +285,10 @@ func newNamespacedContractEngine(t *testing.T, client promptkit.LLMClient) *prom inputs = append(inputs, input) } inputs = append(inputs, configkit.Input{Source: configkit.Mapping("root resources", map[string]any{ - "profiles": map[string]any{"root-only": map[string]any{ + "profiles": []any{map[string]any{"id": "root-only", "endpoint": "https://root.example/v1", "model": "root-model", }}, - "prompts": map[string]any{"root-only": map[string]any{ - "version": "1", + "prompts": []any{map[string]any{"id": "root-only", "messages": []any{map[string]any{"role": "user", "content": "Root prompt"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}, }}, diff --git a/profile_inheritance_contract_test.go b/profile_inheritance_contract_test.go index 9152f5f..b06af9c 100644 --- a/profile_inheritance_contract_test.go +++ b/profile_inheritance_contract_test.go @@ -61,7 +61,7 @@ func TestProfileInheritanceConfiguredBaseAliasWorkflow(t *testing.T) { func TestProfileInheritanceYAMLAliasOfConfiguredProfile(t *testing.T) { inputs, err := promptkit.FilesystemInputs("profile alias", fstest.MapFS{ - "config/alias.yaml": &fstest.MapFile{Data: []byte("profiles:\n base:\n model: synthetic-model\n yaml-alias:\n extends: base\n")}, + "config/alias.yaml": &fstest.MapFile{Data: []byte("profiles:\n - id: base\n model: synthetic-model\n - id: yaml-alias\n extends: {id: base}\n")}, }, "config") if err != nil { t.Fatal(err) @@ -79,12 +79,12 @@ func TestProfileInheritanceYAMLAliasOfConfiguredProfile(t *testing.T) { func TestProfileInheritanceRetainsRequiredCredentialBehavior(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ Source: configkit.Mapping("credentials", map[string]any{ - "backends": map[string]any{"credential-backend": map[string]any{ + "backends": []any{map[string]any{"id": "credential-backend", "endpoint": "https://credential.example/v1", "api_key_env": "OPTIONAL_BACKEND_KEY", }}, - "profiles": map[string]any{ - "credential-base": map[string]any{"backend": "credential-backend", "model": "model", "api_key_required": true}, - "credential-child": map[string]any{"extends": "credential-base"}, + "profiles": []any{ + map[string]any{"id": "credential-base", "backend": "credential-backend", "model": "model", "api_key_required": true}, + map[string]any{"id": "credential-child", "extends": map[string]any{"id": "credential-base"}}, }, }), }}}) @@ -106,8 +106,8 @@ func TestProfileInheritancePreservesPublicErrorIdentities(t *testing.T) { t.Fatalf("missing profile = (%+v, %v)", inspection, err) } for _, document := range []map[string]any{ - {"profiles": map[string]any{"child": map[string]any{"extends": "missing"}}}, - {"profiles": map[string]any{"a": map[string]any{"extends": "b"}, "b": map[string]any{"extends": "a"}}}, + {"profiles": []any{map[string]any{"id": "child", "extends": map[string]any{"id": "missing"}}}}, + {"profiles": []any{map[string]any{"id": "a", "extends": map[string]any{"id": "b"}}, map[string]any{"id": "b", "extends": map[string]any{"id": "a"}}}}, } { invalid, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ Source: configkit.Mapping("invalid inheritance", document), @@ -120,7 +120,7 @@ func TestProfileInheritancePreservesPublicErrorIdentities(t *testing.T) { func TestProfileInheritanceFreezesPreparedExecution(t *testing.T) { profiles := &mutableInheritanceProfileFS{files: fstest.MapFS{ - "config/profiles.yaml": &fstest.MapFile{Data: []byte("profiles:\n base:\n endpoint: https://base.example/v1\n model: first-model\n child:\n extends: base\n")}, + "config/profiles.yaml": &fstest.MapFile{Data: []byte("profiles:\n - id: base\n endpoint: https://base.example/v1\n model: first-model\n - id: child\n extends: {id: base}\n")}, }} inputs, err := promptkit.FilesystemInputs("profiles", profiles, "config") if err != nil { @@ -138,7 +138,7 @@ func TestProfileInheritanceFreezesPreparedExecution(t *testing.T) { if err != nil { t.Fatal(err) } - profiles.set("config/profiles.yaml", "profiles:\n base:\n endpoint: https://base.example/v1\n model: second-model\n child:\n extends: base\n") + profiles.set("config/profiles.yaml", "profiles:\n - id: base\n endpoint: https://base.example/v1\n model: second-model\n - id: child\n extends: {id: base}\n") result, err := engine.RunPrepared(context.Background(), prepared) if err != nil || result == nil || client.model != "first-model" { t.Fatalf("prepared execution = (%+v, %v), model %q", result, err, client.model) diff --git a/profile_inspection_contract_test.go b/profile_inspection_contract_test.go index 35a1f24..5370e8b 100644 --- a/profile_inspection_contract_test.go +++ b/profile_inspection_contract_test.go @@ -12,7 +12,7 @@ import ( func TestInspectProfileUsesLiteralFileKeyForPreparation(t *testing.T) { profiles, err := promptkit.FilesystemInputs("literal profile", fstest.MapFS{ - "config/profile.yaml": &fstest.MapFile{Data: []byte("profiles:\n ' literal ':\n endpoint: https://example.test/v1\n model: model\n")}, + "config/profile.yaml": &fstest.MapFile{Data: []byte("profiles:\n - id: ' literal '\n endpoint: https://example.test/v1\n model: model\n")}, }, "config") if err != nil { t.Fatal(err) @@ -20,8 +20,8 @@ func TestInspectProfileUsesLiteralFileKeyForPreparation(t *testing.T) { engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{ Underlays: []configkit.Underlay{{Inputs: profiles}}, Main: &configkit.Input{Source: configkit.Mapping("prompt", map[string]any{ - "prompts": map[string]any{"prompt": map[string]any{ - "version": "1", + "prompts": []any{map[string]any{ + "id": "prompt", "default_profile": " literal ", "messages": []any{map[string]any{"role": "user", "content": "hello"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}, @@ -50,8 +50,8 @@ func TestInspectProfileUsesLiteralFileKeyForPreparation(t *testing.T) { func TestInspectProfileIsIndependentAndDoesNotInvokeProvider(t *testing.T) { client := &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "unexpected"}} engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ - Source: configkit.Mapping("inspection", map[string]any{"profiles": map[string]any{ - "profile": map[string]any{ + Source: configkit.Mapping("inspection", map[string]any{"profiles": []any{ + map[string]any{"id": "profile", "endpoint": "https://example.test/v1", "model": "model", "api_key_required": true, "extra_params": map[string]any{"nested": map[string]any{"value": "original"}}, }, @@ -77,7 +77,7 @@ func TestInspectProfilePublicErrorsAndPartialTarget(t *testing.T) { t.Fatalf("nil engine: %v", err) } engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{ - Source: configkit.Mapping("partial", map[string]any{"profiles": map[string]any{"profile": map[string]any{"model": "model"}}}), + Source: configkit.Mapping("partial", map[string]any{"profiles": []any{map[string]any{"id": "profile", "model": "model"}}}), }}}) if err != nil { t.Fatal(err)