Interpret backend and profile settings without changing source values

This commit is contained in:
2026-11-02 12:45:53 +00:00
parent cb83b8aca6
commit eaca50763b
5 changed files with 157 additions and 74 deletions

View File

@@ -570,6 +570,14 @@ inspection does not acquire synthesized whole-list origins/defaults. Record
unrelated old-fixture failures for their assigned stages rather than restoring
mutating resolution to satisfy them.
Completion: backend and profile normalization and defaults now operate on
detached interpreted records during validation, with presence captured from the
unchanged ConfigKit tree. Runtime indexes consume those interpreted values.
Focused real-loader tests passed for padded endpoints, nested diagnostics,
profile settings, omitted/null queue defaults, inherited targets, and preserved
composed values and origins on Go 1.27.0. Prompt interpretation follows in the
next assignment; older fixtures remain assigned to later integration work.
## Stage 12: Recover Prompt Interpretation And Preserve Inspection Origins
Scope: prompt interpretation/materialization in `load.go`/`prompts.go` and the

View File

@@ -0,0 +1,46 @@
package configuration
import (
"context"
"testing"
"go.maximumdirect.net/configkit"
)
func TestTargetInterpretationPreservesComposedNestedValues(t *testing.T) {
state, err := load(mapping("padded target", map[string]any{
"backends": []any{map[string]any{
"id": "local", "endpoint": " HTTPS://example.invalid/v1 ", "concurrency_limit": 1,
"smoke_tests": map[string]any{
"reachability": map[string]any{"endpoint": " https://example.invalid/health "},
"authentication": map[string]any{"kind": " NONE ", "endpoint": " https://example.invalid/key "},
},
}},
"profiles": []any{map[string]any{"id": "partial", "backend": "local", "service_tier": " standard ", "api_key_env": " TOKEN "}},
}), nil)
if err != nil {
t.Fatal(err)
}
backend, err := state.GetBackend("local")
if err != nil || backend.Endpoint != "https://example.invalid/v1" || backend.QueueCapacity != 1024 ||
backend.SmokeTests == nil || *backend.SmokeTests.Reachability.Endpoint != "https://example.invalid/health" ||
*backend.SmokeTests.Authentication.Kind != "none" || *backend.SmokeTests.Authentication.Endpoint != "https://example.invalid/key" {
t.Fatalf("interpreted backend = %+v, %v", backend, err)
}
profile, err := state.GetProfile(context.Background(), "partial")
if err != nil || profile.ResolvedTarget.ServiceTier != "standard" || profile.ResolvedTarget.APIKeyEnv != "TOKEN" {
t.Fatalf("interpreted profile = %+v, %v", profile, err)
}
composed := state.result.Config.Backends[0]
if *composed.Endpoint != " HTTPS://example.invalid/v1 " || composed.QueueCapacity != nil ||
*composed.SmokeTests.Reachability.Endpoint != " https://example.invalid/health " ||
*composed.SmokeTests.Authentication.Kind != " NONE " || *composed.SmokeTests.Authentication.Endpoint != " https://example.invalid/key " ||
*state.result.Config.Profiles[0].ServiceTier != " standard " || *state.result.Config.Profiles[0].APIKeyEnv != " TOKEN " {
t.Fatalf("composed values were mutated: %+v", state.result.Config)
}
path := configkit.Keys("backends").Index(0).Key("smoke_tests").Key("authentication").Key("endpoint")
field, found := state.result.Metadata().Lookup(path)
if !found || len(field.Origin.Locations) == 0 || field.Origin.Operation == configkit.ChangeNormalization {
t.Fatalf("nested field origin changed: %+v", field)
}
}

View File

@@ -120,6 +120,7 @@ const (
type loaded struct {
result *configkit.Result[document]
interpreted document
backends map[string]map[string]fieldState
profiles map[string]map[string]fieldState
promptMetadata map[string][]promptInputPresence
@@ -221,6 +222,36 @@ func Load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
return load(main, underlays)
}
func configurationDefinition(state *loaded) configkit.Definition[document] {
return configkit.Definition[document]{
SequenceIdentities: []configkit.SequenceIdentity{
configkit.SequenceIdentityAt(configkit.Keys("backends"), "id"),
configkit.SequenceIdentityAt(configkit.Keys("profiles"), "id"),
configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id"),
configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("backends")}, "id"),
configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("profiles")}, "id"),
configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("prompts")}, "id"),
},
Version: &configkit.VersionRule{
Path: configkit.Keys("version"),
Check: checkConfigurationVersion,
},
Sensitive: secretProviderPath,
Resolve: func(config *document, resolution *configkit.Resolution) error {
for _, resources := range resourceSets(config) {
captureFields(resources.backends, func(v backendConfig) string { return v.ID }, resources.bases.backends, resources.namespace, backendFields, resolution, state.backends)
captureFields(resources.profiles, func(v profileConfig) string { return v.ID }, resources.bases.profiles, resources.namespace, profileFields, resolution, state.profiles)
capturePromptMetadata(resources.prompts, resources.bases.prompts, resources.namespace, resolution, state.promptMetadata)
}
return nil
},
Validate: func(config *document, validation *configkit.Validation) {
state.interpreted = interpretTargets(config, validation)
validateEffective(&state.interpreted, validation)
},
}
}
// load performs one ConfigKit lifecycle over the selected source tiers.
func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error) {
state := &loaded{
@@ -231,35 +262,7 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
result, err := configkit.LoadLayered(main, configkit.LayeredOptions[document]{
Underlays: underlays,
SourceVersionMode: configkit.SourceVersionModeIfPresent,
Definition: configkit.Definition[document]{
SequenceIdentities: []configkit.SequenceIdentity{
configkit.SequenceIdentityAt(configkit.Keys("backends"), "id"),
configkit.SequenceIdentityAt(configkit.Keys("profiles"), "id"),
configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id"),
configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("backends")}, "id"),
configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("profiles")}, "id"),
configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("prompts")}, "id"),
},
Version: &configkit.VersionRule{
Path: configkit.Keys("version"),
Check: checkConfigurationVersion,
},
Sensitive: secretProviderPath,
Resolve: func(config *document, resolution *configkit.Resolution) error {
for _, resources := range resourceSets(config) {
captureFields(resources.backends, func(v backendConfig) string { return v.ID }, resources.bases.backends, resources.namespace, backendFields, resolution, state.backends)
captureFields(resources.profiles, func(v profileConfig) string { return v.ID }, resources.bases.profiles, resources.namespace, profileFields, resolution, state.profiles)
capturePromptMetadata(resources.prompts, resources.bases.prompts, resources.namespace, resolution, state.promptMetadata)
resolveBackends(resources.backends, resources.bases.backends, resolution)
resolveProfiles(resources.profiles, resources.bases.profiles, resolution)
resolvePrompts(resources.prompts, resources.bases.prompts, resolution)
}
return nil
},
Validate: func(config *document, validation *configkit.Validation) {
validateEffective(config, validation)
},
},
Definition: configurationDefinition(state),
})
if err != nil {
return nil, fmt.Errorf("load configuration: %w", err)
@@ -271,7 +274,7 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
if err := state.buildExecutionProfiles(); err != nil {
return nil, fmt.Errorf("load configuration: %w", err)
}
state.backendIDs = sortedKeys(indexedBackends(&result.Config))
state.backendIDs = sortedKeys(indexedBackends(&state.interpreted))
state.profileIDs = sortedKeys(state.indexedProfiles)
for _, id := range sortedKeys(state.prompts) {
state.promptIDs = append(state.promptIDs, domain.PromptIdentity{ID: id})
@@ -386,48 +389,71 @@ func normalizeString(value **string, path configkit.Path, resolution *configkit.
return false
}
func resolveBackends(backends []backendConfig, basePath configkit.Path, resolution *configkit.Resolution) {
func interpretTargets(config *document, validation *configkit.Validation) document {
interpreted := *config
interpreted.Backends = interpretBackends(config.Backends, configkit.Keys("backends"), validation)
interpreted.Profiles = interpretProfiles(config.Profiles, configkit.Keys("profiles"), validation)
if config.Collections != nil {
interpreted.Collections = make(map[string]collectionConfig, len(config.Collections))
for namespace, original := range config.Collections {
collection := original
base := configkit.Keys("collections", namespace)
collection.Backends = interpretBackends(original.Backends, base.Key("backends"), validation)
collection.Profiles = interpretProfiles(original.Profiles, base.Key("profiles"), validation)
interpreted.Collections[namespace] = collection
}
}
return interpreted
}
func interpretBackends(source []backendConfig, basePath configkit.Path, validation *configkit.Validation) []backendConfig {
if source == nil {
return nil
}
backends := append([]backendConfig{}, source...)
for index := range backends {
backend := &backends[index]
base := basePath.Index(index)
if backend.Endpoint != nil {
endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*backend.Endpoint)
if err != nil {
resolution.Error(base.Key("endpoint"), err.Error())
validation.Error(base.Key("endpoint"), err.Error())
} else {
normalizeString(&backend.Endpoint, base.Key("endpoint"), resolution, endpoint)
backend.Endpoint = &endpoint
}
}
if backend.APIKeyEnv != nil {
normalizeString(&backend.APIKeyEnv, base.Key("api_key_env"), resolution, strings.TrimSpace(*backend.APIKeyEnv))
value := strings.TrimSpace(*backend.APIKeyEnv)
backend.APIKeyEnv = &value
}
if smokeTests := backend.SmokeTests; smokeTests != nil {
if backend.SmokeTests != nil {
smokeTests := *backend.SmokeTests
backend.SmokeTests = &smokeTests
smokePath := base.Key("smoke_tests")
if smokeTests.Reachability != nil {
normalizeDiagnosticEndpoint(&smokeTests.Reachability.Endpoint, smokePath.Key("reachability").Key("endpoint"), resolution)
reachability := *smokeTests.Reachability
smokeTests.Reachability = &reachability
normalizeDiagnosticEndpoint(&reachability.Endpoint, smokePath.Key("reachability").Key("endpoint"), validation)
}
if smokeTests.Authentication != nil {
if smokeTests.Authentication.Kind != nil {
normalizeString(&smokeTests.Authentication.Kind,
smokePath.Key("authentication").Key("kind"), resolution,
strings.ToLower(strings.TrimSpace(*smokeTests.Authentication.Kind)))
authentication := *smokeTests.Authentication
smokeTests.Authentication = &authentication
if authentication.Kind != nil {
kind := strings.ToLower(strings.TrimSpace(*authentication.Kind))
authentication.Kind = &kind
}
normalizeDiagnosticEndpoint(&smokeTests.Authentication.Endpoint,
smokePath.Key("authentication").Key("endpoint"), resolution)
normalizeDiagnosticEndpoint(&authentication.Endpoint, smokePath.Key("authentication").Key("endpoint"), validation)
}
}
if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit > 0 && backend.QueueCapacity == nil {
capacity := 1024
if resolution.IsExplicit(base.Key("queue_capacity")) {
configkit.Normalize(resolution, base.Key("queue_capacity"), &backend.QueueCapacity, &capacity, "select default queue capacity")
} else {
configkit.Default(resolution, base.Key("queue_capacity"), &backend.QueueCapacity, &capacity, "select default queue capacity")
}
backend.QueueCapacity = &capacity
}
}
return backends
}
func normalizeDiagnosticEndpoint(value **string, path configkit.Path, resolution *configkit.Resolution) {
func normalizeDiagnosticEndpoint(value **string, path configkit.Path, validation *configkit.Validation) {
if value == nil || *value == nil {
return
}
@@ -436,37 +462,36 @@ func normalizeDiagnosticEndpoint(value **string, path configkit.Path, resolution
if normalized == "" || err != nil || request.URL == nil ||
(strings.ToLower(request.URL.Scheme) != "http" && strings.ToLower(request.URL.Scheme) != "https") ||
request.URL.Hostname() == "" {
resolution.Error(path, "diagnostic endpoint must be an absolute HTTP(S) URL with a host")
validation.Error(path, "diagnostic endpoint must be an absolute HTTP(S) URL with a host")
return
}
normalizeString(value, path, resolution, normalized)
*value = &normalized
}
func resolveProfiles(profiles []profileConfig, basePath configkit.Path, resolution *configkit.Resolution) {
func interpretProfiles(source []profileConfig, basePath configkit.Path, validation *configkit.Validation) []profileConfig {
if source == nil {
return nil
}
profiles := append([]profileConfig{}, source...)
for index := range profiles {
profile := &profiles[index]
base := basePath.Index(index)
if profile.Endpoint != nil && *profile.Endpoint != "" {
endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*profile.Endpoint)
if err != nil {
resolution.Error(base.Key("endpoint"), err.Error())
validation.Error(base.Key("endpoint"), err.Error())
} else {
normalizeString(&profile.Endpoint, base.Key("endpoint"), resolution, endpoint)
profile.Endpoint = &endpoint
}
}
for _, field := range []struct {
name string
value **string
}{
{"service_tier", &profile.ServiceTier},
{"reasoning_effort", &profile.ReasoningEffort},
{"api_key_env", &profile.APIKeyEnv},
} {
if *field.value != nil {
normalizeString(field.value, base.Key(field.name), resolution, strings.TrimSpace(**field.value))
for _, value := range []**string{&profile.ServiceTier, &profile.ReasoningEffort, &profile.APIKeyEnv} {
if *value != nil {
normalized := strings.TrimSpace(**value)
*value = &normalized
}
}
}
return profiles
}
func validateEffective(config *document, validation *configkit.Validation) {

View File

@@ -229,7 +229,7 @@ func TestLoadRejectsInvalidCollectionAndResourceKeys(t *testing.T) {
}
}
func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) {
func TestLoadInterpretsBackendNormalizationAndNullDefault(t *testing.T) {
loaded, err := load(mapping("backend", map[string]any{"backends": []any{
map[string]any{"id": "local", "endpoint": " HTTP://localhost:8080/v1 ", "api_key_env": " TOKEN_NAME ",
"concurrency_limit": 2, "queue_capacity": nil},
@@ -237,18 +237,22 @@ func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) {
if err != nil {
t.Fatal(err)
}
backend := loaded.result.Config.Backends[0]
if backend.Endpoint == nil || *backend.Endpoint != "http://localhost:8080/v1" ||
backend.APIKeyEnv == nil || *backend.APIKeyEnv != "TOKEN_NAME" ||
backend.QueueCapacity == nil || *backend.QueueCapacity != 1024 {
backend, err := loaded.GetBackend("local")
if err != nil || backend.Endpoint != "http://localhost:8080/v1" ||
backend.APIKeyEnv != "TOKEN_NAME" || backend.QueueCapacity != 1024 {
t.Fatalf("backend normalization/default was not applied: %+v", backend)
}
composed := loaded.result.Config.Backends[0]
if composed.Endpoint == nil || *composed.Endpoint != " HTTP://localhost:8080/v1 " ||
composed.APIKeyEnv == nil || *composed.APIKeyEnv != " TOKEN_NAME " || composed.QueueCapacity != nil {
t.Fatalf("composed backend was changed: %+v", composed)
}
if loaded.backends["local"]["queue_capacity"] != fieldNull {
t.Fatalf("explicit null was not retained: %+v", loaded.backends["local"])
}
field, found := loaded.result.Metadata().Lookup(configkit.Keys("backends").Index(0).Key("endpoint"))
if !found || field.Origin.Operation != configkit.ChangeNormalization {
t.Fatalf("endpoint normalization was not recorded at the list entry: %+v", field)
if !found || field.Origin.Operation == configkit.ChangeNormalization || len(field.Origin.Locations) == 0 {
t.Fatalf("endpoint origin was not preserved: %+v", field)
}
}
@@ -415,8 +419,8 @@ func TestLoadDefaultsAbsentQueueCapacity(t *testing.T) {
if err != nil {
t.Fatal(err)
}
backend := loaded.result.Config.Backends[0]
if backend.APIKeyEnv != nil || backend.QueueCapacity == nil || *backend.QueueCapacity != 1024 || loaded.backends["local"]["queue_capacity"] != fieldAbsent {
backend, err := loaded.GetBackend("local")
if err != nil || backend.APIKeyEnv != "" || backend.QueueCapacity != 1024 || loaded.result.Config.Backends[0].QueueCapacity != nil || loaded.backends["local"]["queue_capacity"] != fieldAbsent {
t.Fatalf("absent queue capacity did not receive its default: %+v", backend)
}
}

View File

@@ -12,8 +12,8 @@ import (
)
func (state *loaded) buildExecutionProfiles() error {
backendConfigs := indexedBackends(&state.result.Config)
profileConfigs := indexedProfiles(&state.result.Config)
backendConfigs := indexedBackends(&state.interpreted)
profileConfigs := indexedProfiles(&state.interpreted)
definitions := make(map[string]domain.Backend, len(backendConfigs))
for _, id := range sortedKeys(backendConfigs) {
config := backendConfigs[id]