From 8e8f61f53f450e0a524981a54e8c67472378b0cc Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 2 Nov 2026 03:55:28 +0000 Subject: [PATCH] Index and validate identified resources --- docs/roadmap/implementation.md | 7 ++++ internal/configuration/load.go | 39 ++++++++---------- internal/configuration/load_test.go | 4 +- internal/configuration/targets_test.go | 57 ++++++++++++++++++-------- 4 files changed, 68 insertions(+), 39 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index ad1b7e8..1405dbe 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -299,6 +299,13 @@ Validation: target tests cover a root resource, namespaced resources sharing a local ID, and partial-profile completion. Upstream duplicates must fail before an execution map can silently overwrite a record. +Completion: final backend, profile, and prompt IDs and references are validated +at list-entry paths; backend and profile runtime indexes use full identities. +Added root and independent namespace target coverage and converted the strict +duplicate fixture to identified lists. The focused tests remain deferred because +prompt materialization and remaining configuration fixtures have not yet been +migrated. Formatting and `git diff --check` passed. + ## Stage 6: Materialize Prompts From Effective List Entries Scope: prompt resolution/capture in `internal/configuration/prompts.go` and diff --git a/internal/configuration/load.go b/internal/configuration/load.go index 9faba13..3fec27e 100644 --- a/internal/configuration/load.go +++ b/internal/configuration/load.go @@ -194,8 +194,8 @@ func indexedResourceID(namespace, localKey string) string { func indexedBackends(config *document) map[string]backendConfig { backends := make(map[string]backendConfig) for _, resources := range resourceSets(config) { - for localKey, definition := range resources.backends { - backends[indexedResourceID(resources.namespace, localKey)] = definition + for _, definition := range resources.backends { + backends[indexedResourceID(resources.namespace, definition.ID)] = definition } } return backends @@ -204,8 +204,8 @@ func indexedBackends(config *document) map[string]backendConfig { func indexedProfiles(config *document) map[string]profileConfig { profiles := make(map[string]profileConfig) for _, resources := range resourceSets(config) { - for localKey, definition := range resources.profiles { - profiles[indexedResourceID(resources.namespace, localKey)] = definition + for _, definition := range resources.profiles { + profiles[indexedResourceID(resources.namespace, definition.ID)] = definition } } return profiles @@ -476,16 +476,15 @@ func validateEffective(config *document, validation *configkit.Validation) { if resources.isCollection && !domain.IsValidResourceNamespace(resources.namespace) { validation.Error(configkit.Keys("collections", resources.namespace), "collection namespace must be nonblank and contain no slash") } - for localKey := range resources.backends { - backendIDs[indexedResourceID(resources.namespace, localKey)] = struct{}{} + for _, backend := range resources.backends { + backendIDs[indexedResourceID(resources.namespace, backend.ID)] = struct{}{} } } for _, resources := range sets { - for _, id := range sortedKeys(resources.backends) { - backend := resources.backends[id] - base := resources.bases.backends.Key(id) - if !domain.IsValidLocalResourceKey(id) { - validation.Error(base, "backend key must be a nonblank local key without a slash") + for index, backend := range resources.backends { + base := resources.bases.backends.Index(index) + if !domain.IsValidLocalResourceKey(backend.ID) { + validation.Error(base.Key("id"), "backend ID must be a nonblank local key without a slash") } if backend.Endpoint == nil || *backend.Endpoint == "" { validation.Error(base.Key("endpoint"), "endpoint is required") @@ -526,11 +525,10 @@ func validateEffective(config *document, validation *configkit.Validation) { } } } - for _, id := range sortedKeys(resources.profiles) { - profile := resources.profiles[id] - base := resources.bases.profiles.Key(id) - if !domain.IsValidLocalResourceKey(id) { - validation.Error(base, "profile key must be a nonblank local key without a slash") + for index, profile := range resources.profiles { + base := resources.bases.profiles.Index(index) + if !domain.IsValidLocalResourceKey(profile.ID) { + validation.Error(base.Key("id"), "profile ID must be a nonblank local key without a slash") } if profile.Backend != nil && *profile.Backend != "" { if !domain.IsValidResourceID(*profile.Backend) { @@ -564,11 +562,10 @@ func validateEffective(config *document, validation *configkit.Validation) { } validateExtraParams(profile.ExtraParams, base.Key("extra_params"), false, validation) } - for _, id := range sortedKeys(resources.prompts) { - prompt := resources.prompts[id] - base := resources.bases.prompts.Key(id) - if !domain.IsValidLocalResourceKey(id) { - validation.Error(base, "prompt key must be a nonblank local key without a slash") + for index, prompt := range resources.prompts { + base := resources.bases.prompts.Index(index) + if !domain.IsValidLocalResourceKey(prompt.ID) { + validation.Error(base.Key("id"), "prompt ID must be a nonblank local key without a slash") } if prompt.DefaultProfile != nil && *prompt.DefaultProfile != "" && !domain.IsValidResourceID(*prompt.DefaultProfile) { validation.Error(base.Key("default_profile"), "default profile reference is malformed") diff --git a/internal/configuration/load_test.go b/internal/configuration/load_test.go index e7e3edb..9842a62 100644 --- a/internal/configuration/load_test.go +++ b/internal/configuration/load_test.go @@ -440,8 +440,8 @@ func TestLoadCapturesProfilePresence(t *testing.T) { func TestLoadRejectsStrictGroupConflict(t *testing.T) { _, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{ - mapping("first", map[string]any{"profiles": map[string]any{"shared": map[string]any{"model": "one"}}}), - mapping("second", map[string]any{"profiles": map[string]any{"shared": map[string]any{"model": "one"}}}), + mapping("first", map[string]any{"profiles": []any{map[string]any{"id": "shared", "model": "one"}}}), + mapping("second", map[string]any{"profiles": []any{map[string]any{"id": "shared", "model": "one"}}}), }}}) var loadError *configkit.LoadError if !errors.As(err, &loadError) { diff --git a/internal/configuration/targets_test.go b/internal/configuration/targets_test.go index 75727fd..5d14da4 100644 --- a/internal/configuration/targets_test.go +++ b/internal/configuration/targets_test.go @@ -15,30 +15,55 @@ import ( profilepkg "gitea.maximumdirect.net/eric/promptkit/internal/profile" ) +func TestLoadIndexesIdentifiedTargets(t *testing.T) { + values := map[string]any{ + "backends": []any{map[string]any{"id": "local", "endpoint": "https://root.example/v1"}}, + "profiles": []any{map[string]any{"id": "partial", "backend": "local"}}, + "collections": map[string]any{}, + } + collections := values["collections"].(map[string]any) + for _, namespace := range []string{"north", "south"} { + collections[namespace] = map[string]any{ + "backends": []any{map[string]any{"id": "local", "endpoint": "https://" + namespace + ".example/v1"}}, + "profiles": []any{map[string]any{"id": "partial", "backend": namespace + "/local"}}, + } + } + state, err := load(mapping("identified targets", values), nil) + if err != nil { + t.Fatal(err) + } + for id, backendID := range map[string]string{"partial": "local", "north/partial": "north/local", "south/partial": "south/local"} { + profile, err := state.GetProfile(context.Background(), id) + if err != nil || profile.ID != id || profile.ResolvedTarget == nil || profile.ResolvedTarget.Model != "" || profile.ResolvedTarget.BackendID != backendID { + t.Fatalf("partial profile %q = %+v, %v", id, profile, err) + } + } +} + func TestLoadBuildsEffectiveTargetsAfterInheritance(t *testing.T) { state, err := load(mapping("effective-targets", map[string]any{ - "backends": map[string]any{ - "first": map[string]any{"endpoint": "https://first.example/v1", "api_key_env": "FIRST_KEY", + "backends": []any{ + map[string]any{"id": "first", "endpoint": "https://first.example/v1", "api_key_env": "FIRST_KEY", "extra_params": map[string]any{"source": "first", "nested": map[string]any{"old": true}}}, - "second": map[string]any{"endpoint": "https://second.example/v1", "api_key_env": "SECOND_KEY", + map[string]any{"id": "second", "endpoint": "https://second.example/v1", "api_key_env": "SECOND_KEY", "concurrency_limit": 2, "queue_capacity": 0, "extra_params": map[string]any{"source": "second", "nested": map[string]any{"backend": true}}}, }, - "profiles": map[string]any{ - "base": map[string]any{"backend": "first", "model": "model", "temperature": 0.8, + "profiles": []any{ + map[string]any{"id": "base", "backend": "first", "model": "model", "temperature": 0.8, "top_p": 0.9, "max_tokens": 100, "timeout_seconds": 30, "api_key_required": true, "extra_params": map[string]any{"nested": map[string]any{"profile": true}}}, - "child": map[string]any{"extends": "base", "backend": "second", "temperature": 0.0, + map[string]any{"id": "child", "extends": map[string]any{"id": "base"}, "backend": "second", "temperature": 0.0, "top_p": nil, "max_tokens": 0, "timeout_seconds": nil, "api_key_env": nil, "api_key_required": false, "extra_params": map[string]any{"nested": map[string]any{"child": nil}}}, - "direct-key": map[string]any{"backend": "second", "api_key_required": true}, - "named-key": map[string]any{"backend": "second", "api_key_required": true, "api_key_env": "CUSTOM_KEY"}, - "no-provider-map": map[string]any{"backend": "second", "extra_params": nil}, - "no-timeout": map[string]any{"backend": "second", "timeout_seconds": 0}, - "partial": map[string]any{}, - "endpoint-only": map[string]any{"endpoint": "https://standalone.example/v1"}, - "cleared-endpoint": map[string]any{"extends": "base", "endpoint": nil}, + map[string]any{"id": "direct-key", "backend": "second", "api_key_required": true}, + map[string]any{"id": "named-key", "backend": "second", "api_key_required": true, "api_key_env": "CUSTOM_KEY"}, + map[string]any{"id": "no-provider-map", "backend": "second", "extra_params": nil}, + map[string]any{"id": "no-timeout", "backend": "second", "timeout_seconds": 0}, + map[string]any{"id": "partial"}, + map[string]any{"id": "endpoint-only", "endpoint": "https://standalone.example/v1"}, + map[string]any{"id": "cleared-endpoint", "extends": map[string]any{"id": "base"}, "endpoint": nil}, }, }), nil) if err != nil { @@ -133,9 +158,9 @@ func TestLoadedZeroControlsReachBuiltInRequest(t *testing.T) { })) defer server.Close() state, err := load(mapping("wire-controls", map[string]any{ - "backends": map[string]any{"local": map[string]any{"endpoint": server.URL + "/v1"}}, - "profiles": map[string]any{"direct": map[string]any{ - "backend": "local", "model": "example", "temperature": 0.0, "max_tokens": 0, "top_p": nil, + "backends": []any{map[string]any{"id": "local", "endpoint": server.URL + "/v1"}}, + "profiles": []any{map[string]any{ + "id": "direct", "backend": "local", "model": "example", "temperature": 0.0, "max_tokens": 0, "top_p": nil, }}, }), nil) if err != nil {