Preserve resource field presence in lists

This commit is contained in:
2026-11-02 03:54:14 +00:00
parent aba890aa86
commit b7565affe5
3 changed files with 53 additions and 24 deletions

View File

@@ -271,6 +271,13 @@ Scope: backend/profile field capture, normalization, and defaulting in
Validation: owning tests demonstrate explicit zeros/nulls, backend completion, Validation: owning tests demonstrate explicit zeros/nulls, backend completion,
and recorded normalization at the correct list entries. No new readiness rule. and recorded normalization at the correct list entries. No new readiness rule.
Completion: backend and profile presence capture and resolution now use list
indexes and mutate the effective slice elements while retaining full-ID state
keys. Converted focused normalization, null/default, profile-presence, and
same-ID replacement fixtures. Their execution remains deferred: the package
still does not compile until the subsequent indexing, validation, and prompt
list migrations. Formatting and `git diff --check` passed.
## Stage 5: Validate Final Records And Build Execution Indexes ## Stage 5: Validate Final Records And Build Execution Indexes
Scope: `validateEffective`, `indexedBackends`, `indexedProfiles`, and Scope: `validateEffective`, `indexedBackends`, `indexedProfiles`, and

View File

@@ -247,8 +247,8 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
Sensitive: secretProviderPath, Sensitive: secretProviderPath,
Resolve: func(config *document, resolution *configkit.Resolution) error { Resolve: func(config *document, resolution *configkit.Resolution) error {
for _, resources := range resourceSets(config) { for _, resources := range resourceSets(config) {
captureFields(resources.backends, resources.bases.backends, resources.namespace, backendFields, resolution, state.backends) captureFields(resources.backends, func(v backendConfig) string { return v.ID }, resources.bases.backends, resources.namespace, backendFields, resolution, state.backends)
captureFields(resources.profiles, resources.bases.profiles, resources.namespace, profileFields, resolution, state.profiles) 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) capturePromptMetadata(resources.prompts, resources.bases.prompts, resources.namespace, resolution, state.promptMetadata)
resolveBackends(resources.backends, resources.bases.backends, resolution) resolveBackends(resources.backends, resources.bases.backends, resolution)
resolveProfiles(resources.profiles, resources.bases.profiles, resolution) resolveProfiles(resources.profiles, resources.bases.profiles, resolution)
@@ -351,11 +351,11 @@ func captureFieldState(path configkit.Path, resolution *configkit.Resolution) fi
} }
} }
func captureFields[T any](entries map[string]T, basePath configkit.Path, namespace string, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) { func captureFields[T any](entries []T, identity func(T) string, basePath configkit.Path, namespace string, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) {
for id := range entries { for index, entry := range entries {
states := make(map[string]fieldState, len(fields)) states := make(map[string]fieldState, len(fields))
for _, field := range fields { for _, field := range fields {
info, exists := resolution.EntryInfo(basePath.Key(id).Key(field)) info, exists := resolution.EntryInfo(basePath.Index(index).Key(field))
switch { switch {
case !exists || !info.Explicit: case !exists || !info.Explicit:
states[field] = fieldAbsent states[field] = fieldAbsent
@@ -365,7 +365,7 @@ func captureFields[T any](entries map[string]T, basePath configkit.Path, namespa
states[field] = fieldValue states[field] = fieldValue
} }
} }
destination[indexedResourceID(namespace, id)] = states destination[indexedResourceID(namespace, identity(entry))] = states
} }
} }
@@ -386,10 +386,10 @@ func normalizeString(value **string, path configkit.Path, resolution *configkit.
return false return false
} }
func resolveBackends(backends map[string]backendConfig, basePath configkit.Path, resolution *configkit.Resolution) { func resolveBackends(backends []backendConfig, basePath configkit.Path, resolution *configkit.Resolution) {
for _, id := range sortedKeys(backends) { for index := range backends {
backend := backends[id] backend := &backends[index]
base := basePath.Key(id) base := basePath.Index(index)
if backend.Endpoint != nil { if backend.Endpoint != nil {
endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*backend.Endpoint) endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*backend.Endpoint)
if err != nil { if err != nil {
@@ -424,7 +424,6 @@ func resolveBackends(backends map[string]backendConfig, basePath configkit.Path,
configkit.Default(resolution, base.Key("queue_capacity"), &backend.QueueCapacity, &capacity, "select default queue capacity") configkit.Default(resolution, base.Key("queue_capacity"), &backend.QueueCapacity, &capacity, "select default queue capacity")
} }
} }
backends[id] = backend
} }
} }
@@ -443,10 +442,10 @@ func normalizeDiagnosticEndpoint(value **string, path configkit.Path, resolution
normalizeString(value, path, resolution, normalized) normalizeString(value, path, resolution, normalized)
} }
func resolveProfiles(profiles map[string]profileConfig, basePath configkit.Path, resolution *configkit.Resolution) { func resolveProfiles(profiles []profileConfig, basePath configkit.Path, resolution *configkit.Resolution) {
for _, id := range sortedKeys(profiles) { for index := range profiles {
profile := profiles[id] profile := &profiles[index]
base := basePath.Key(id) base := basePath.Index(index)
if profile.Endpoint != nil && *profile.Endpoint != "" { if profile.Endpoint != nil && *profile.Endpoint != "" {
endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*profile.Endpoint) endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*profile.Endpoint)
if err != nil { if err != nil {
@@ -467,7 +466,6 @@ func resolveProfiles(profiles map[string]profileConfig, basePath configkit.Path,
normalizeString(field.value, base.Key(field.name), resolution, strings.TrimSpace(**field.value)) normalizeString(field.value, base.Key(field.name), resolution, strings.TrimSpace(**field.value))
} }
} }
profiles[id] = profile
} }
} }

View File

@@ -230,14 +230,14 @@ func TestLoadRejectsInvalidCollectionAndResourceKeys(t *testing.T) {
} }
func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) { func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) {
loaded, err := load(mapping("backend", map[string]any{"backends": map[string]any{ loaded, err := load(mapping("backend", map[string]any{"backends": []any{
"local": map[string]any{"endpoint": " HTTP://localhost:8080/v1 ", "api_key_env": " TOKEN_NAME ", map[string]any{"id": "local", "endpoint": " HTTP://localhost:8080/v1 ", "api_key_env": " TOKEN_NAME ",
"concurrency_limit": 2, "queue_capacity": nil}, "concurrency_limit": 2, "queue_capacity": nil},
}}), nil) }}), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
backend := loaded.result.Config.Backends["local"] backend := loaded.result.Config.Backends[0]
if backend.Endpoint == nil || *backend.Endpoint != "http://localhost:8080/v1" || if backend.Endpoint == nil || *backend.Endpoint != "http://localhost:8080/v1" ||
backend.APIKeyEnv == nil || *backend.APIKeyEnv != "TOKEN_NAME" || backend.APIKeyEnv == nil || *backend.APIKeyEnv != "TOKEN_NAME" ||
backend.QueueCapacity == nil || *backend.QueueCapacity != 1024 { backend.QueueCapacity == nil || *backend.QueueCapacity != 1024 {
@@ -246,6 +246,10 @@ func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) {
if loaded.backends["local"]["queue_capacity"] != fieldNull { if loaded.backends["local"]["queue_capacity"] != fieldNull {
t.Fatalf("explicit null was not retained: %+v", loaded.backends["local"]) 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)
}
} }
func TestLoadBackendSmokeTestsOmissionAndNull(t *testing.T) { func TestLoadBackendSmokeTestsOmissionAndNull(t *testing.T) {
@@ -402,15 +406,35 @@ func TestLoadRejectsInvalidFinalBackendSmokeTests(t *testing.T) {
} }
func TestLoadDefaultsAbsentQueueCapacity(t *testing.T) { func TestLoadDefaultsAbsentQueueCapacity(t *testing.T) {
loaded, err := load(mapping("backend", map[string]any{"backends": map[string]any{ underlay := mapping("defaults", map[string]any{"backends": []any{
"local": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 1}, map[string]any{"id": "local", "endpoint": "http://localhost:8080/v1", "api_key_env": "OLD_KEY"},
}})
loaded, err := load(mapping("backend", map[string]any{"backends": []any{
map[string]any{"id": "local", "endpoint": "http://localhost:8080/v1", "concurrency_limit": 1},
}}), []configkit.Underlay{{Inputs: []configkit.Input{underlay}}})
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 {
t.Fatalf("absent queue capacity did not receive its default: %+v", backend)
}
}
func TestLoadCapturesProfilePresence(t *testing.T) {
loaded, err := load(mapping("profile", map[string]any{"profiles": []any{
map[string]any{"id": "local", "temperature": 0, "top_p": nil, "api_key_required": false},
}}), nil) }}), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
backend := loaded.result.Config.Backends["local"] profile := loaded.result.Config.Profiles[0]
if backend.QueueCapacity == nil || *backend.QueueCapacity != 1024 || loaded.backends["local"]["queue_capacity"] != fieldAbsent { if profile.Temperature == nil || *profile.Temperature != 0 || profile.TopP != nil || profile.APIKeyRequired == nil || *profile.APIKeyRequired {
t.Fatalf("absent queue capacity did not receive its default: %+v", backend) t.Fatalf("profile values were not preserved: %+v", profile)
}
fields := loaded.profiles["local"]
if fields["temperature"] != fieldValue || fields["top_p"] != fieldNull || fields["api_key_required"] != fieldValue || fields["model"] != fieldAbsent {
t.Fatalf("profile presence was not preserved: %+v", fields)
} }
} }