From c5f3de13f39cb4b19fd2ef41372b6882dc1cee38 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 5 Nov 2026 20:09:17 +0000 Subject: [PATCH] Transfer owned provider settings during resolution --- docs/roadmap/implementation.md | 6 ++++++ internal/usecase/profile_inspection.go | 7 ------- internal/usecase/profile_inspection_test.go | 12 +++++++++++- internal/usecase/resolved_targets_test.go | 13 +++++++++++++ internal/usecase/runner.go | 17 ++++------------- internal/usecase/smoke_selection.go | 4 +++- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index f099c78..1a00bfc 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -326,6 +326,12 @@ null/empty values, and construction errors without pinning deleted copy-error br ## Stage 12: Transfer Accepted Provider Maps Through Internal Resolution +**Completed:** Internal selection transfers owned provider maps without repeat +copies, preserving empty-map inheritance and nil projections; public override +validation and outward detachment remain. Go 1.27.3 `go test ./internal/usecase +./internal/configuration . -run 'Target|Profile|Override|Smoke|Provider|ExtraParams'` +passed. + **Finding:** 5; preserve 8. **Files:** `internal/usecase/runner.go`, `profile_inspection.go`, `smoke_selection.go`, related ownership tests. diff --git a/internal/usecase/profile_inspection.go b/internal/usecase/profile_inspection.go index e9bddc3..1907053 100644 --- a/internal/usecase/profile_inspection.go +++ b/internal/usecase/profile_inspection.go @@ -7,7 +7,6 @@ import ( "strings" "go.maximumdirect.net/promptkit/internal/domain" - "go.maximumdirect.net/promptkit/internal/jsonvalue" ) type resolvedProfileSelection struct { @@ -107,12 +106,6 @@ func (r *Runner) InspectProfile( } target, _ := resolveExecutionTarget(selection.profile, nil) target.APIKey = "" - params, err := jsonvalue.CopyMap(target.ExtraParams) - if err != nil { - return nil, &OperationError{Category: ErrProfileLoad, Cause: fmt.Errorf("profile extra parameters: %w", err)} - } - target.ExtraParams = params - return &domain.ProfileInspection{ ProfileID: selection.id, EffectiveModelParams: target, diff --git a/internal/usecase/profile_inspection_test.go b/internal/usecase/profile_inspection_test.go index 37d26eb..9f8134b 100644 --- a/internal/usecase/profile_inspection_test.go +++ b/internal/usecase/profile_inspection_test.go @@ -7,6 +7,7 @@ import ( "testing" "go.maximumdirect.net/promptkit/internal/domain" + "go.maximumdirect.net/promptkit/internal/jsonvalue" "go.maximumdirect.net/promptkit/internal/profile" ) @@ -23,7 +24,16 @@ func (r *inspectionProfileRepository) GetProfile(_ context.Context, id string) ( if r.err != nil { return nil, r.err } - return r.profile, nil + if r.profile == nil { + return nil, nil + } + profile := *r.profile + if profile.ResolvedTarget != nil { + target := *profile.ResolvedTarget + target.ExtraParams = jsonvalue.CloneTrusted(target.ExtraParams).(map[string]any) + profile.ResolvedTarget = &target + } + return &profile, nil } func (r *inspectionProfileRepository) GetProfileIdentity(ctx context.Context, id string) (string, error) { diff --git a/internal/usecase/resolved_targets_test.go b/internal/usecase/resolved_targets_test.go index 81afe1f..94afb25 100644 --- a/internal/usecase/resolved_targets_test.go +++ b/internal/usecase/resolved_targets_test.go @@ -58,6 +58,19 @@ func TestPreparationCompletesResolvedTargetThroughRequestOverrides(t *testing.T) } } +func TestResolvedTargetPreservesProviderMapSelection(t *testing.T) { + profile := &domain.ExecutionProfile{ResolvedTarget: &domain.ExecutionTarget{ExtraParams: map[string]any{"configured": true}}} + selected, _ := resolveExecutionTarget(profile, &domain.ExecutionTargetOverride{ExtraParams: map[string]any{}}) + if selected.ExtraParams["configured"] != true { + t.Fatalf("empty override did not inherit provider settings: %#v", selected.ExtraParams) + } + profile.ResolvedTarget.ExtraParams = map[string]any{} + selected, _ = resolveExecutionTarget(profile, nil) + if selected.ExtraParams != nil { + t.Fatalf("empty provider settings changed projection: %#v", selected.ExtraParams) + } +} + func TestResolvedTargetCredentialAvailability(t *testing.T) { profile := &domain.ExecutionProfile{ID: "direct", ResolvedTarget: &domain.ExecutionTarget{ Endpoint: "https://provider.example/v1", Model: "model", APIKeyRequired: true, diff --git a/internal/usecase/runner.go b/internal/usecase/runner.go index 17db5c9..457ae83 100644 --- a/internal/usecase/runner.go +++ b/internal/usecase/runner.go @@ -592,14 +592,16 @@ func mergeExecutionTargetOverride(base domain.ExecutionTarget, presence domain.E out.APIKeyEnv = override.APIKeyEnv } if len(override.ExtraParams) > 0 { - out.ExtraParams = copyExtraParams(override.ExtraParams) + out.ExtraParams = override.ExtraParams } return out, presence } func resolveExecutionTarget(profileValue *domain.ExecutionProfile, override *domain.ExecutionTargetOverride) (domain.ExecutionTarget, domain.ExecutionTargetPresence) { out := *profileValue.ResolvedTarget - out.ExtraParams = copyExtraParams(out.ExtraParams) + if len(out.ExtraParams) == 0 { + out.ExtraParams = nil + } presence := profileValue.TargetPresence if override != nil { return mergeExecutionTargetOverride(out, presence, *override) @@ -624,17 +626,6 @@ func validateAPIKey(apiKeyEnv string, apiKey string, apiKeyRequired bool) error return nil } -func copyExtraParams(src map[string]any) map[string]any { - if len(src) == 0 { - return nil - } - cp := make(map[string]any, len(src)) - for k, v := range src { - cp[k] = v - } - return cp -} - func resolveOutputContract(def *domain.PromptDefinition, override *domain.OutputContract) (domain.OutputContract, error) { contract := def.Validation if override != nil { diff --git a/internal/usecase/smoke_selection.go b/internal/usecase/smoke_selection.go index fa88f7b..f557bc0 100644 --- a/internal/usecase/smoke_selection.go +++ b/internal/usecase/smoke_selection.go @@ -112,7 +112,9 @@ func (r *Runner) resolveSmokeTestTarget(ctx context.Context, req domain.SmokeTes settings.BackendID = selection.backend.ID settings.Endpoint = selection.backend.Endpoint settings.APIKeyEnv = selection.backend.APIKeyEnv - settings.ExtraParams = copyExtraParams(selection.backend.ExtraParams) + if len(selection.backend.ExtraParams) > 0 { + settings.ExtraParams = selection.backend.ExtraParams + } presence := domain.ExecutionTargetPresence{} if req.Execution != nil { settings, presence = mergeExecutionTargetOverride(settings, presence, *req.Execution)