diff --git a/docs/internal/llm.md b/docs/internal/llm.md index d818f42..5369914 100644 --- a/docs/internal/llm.md +++ b/docs/internal/llm.md @@ -77,7 +77,8 @@ not retain headers, credentials, raw request or response bodies, or a redirect trace. These are observations at Promptkit's RoundTripper boundary, not proof that bytes reached a peer or that a custom transport did no hidden dispatch. Reachability and named authentication probes use the same configured built-in -client but discard their response bodies after local recognition. The +client. The OpenRouter adapter owns endpoint derivation, bounded account-response +recognition, and body closure; the smoke coordinator interprets its evidence. The [integration contract](../integrations/openai-compatible-chat.md#explicit-smoke-diagnostics) owns their observable HTTP semantics. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index cf6e49c..44ec7aa 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -602,6 +602,12 @@ and public smoke tests pass on Go 1.27.3. unchanged tri-state facts, neutral outcomes, no ordinary observer events, target redaction, and independent provider maps. Update owning internal documentation. +Completion: diagnostic targets detach provider values directly, and unused +request/report cloning helpers and their dedicated tests have been removed. +Authentication routing was completed in the preceding unit. Focused domain, +adapter, smoke, and facade tests pass on Go 1.27.3; internal adapter ownership +documentation is updated. + ## Stage 23: Extract Final-Configuration Record Validators **Catalog:** 22, validation part. **Files:** configuration validation and tests. diff --git a/internal/domain/smoke_diagnostics.go b/internal/domain/smoke_diagnostics.go index d6b5133..a7dab0a 100644 --- a/internal/domain/smoke_diagnostics.go +++ b/internal/domain/smoke_diagnostics.go @@ -154,105 +154,6 @@ func CloneBackendSmokeTestConfig(config *BackendSmokeTestConfig) *BackendSmokeTe return &cloned } -// CloneSmokeTestRequest returns a copy that owns its optional settings. -func CloneSmokeTestRequest(request SmokeTestRequest) (SmokeTestRequest, error) { - cloned := request - cloned.TransientRetries = cloneInt(request.TransientRetries) - if request.Execution == nil { - return cloned, nil - } - execution := *request.Execution - execution.Temperature = cloneFloat64(request.Execution.Temperature) - execution.MaxTokens = cloneInt(request.Execution.MaxTokens) - execution.TopP = cloneFloat64(request.Execution.TopP) - execution.TimeoutSeconds = cloneInt(request.Execution.TimeoutSeconds) - execution.ReasoningEffort = cloneString(request.Execution.ReasoningEffort) - var err error - execution.ExtraParams, err = jsonvalue.CopyMap(request.Execution.ExtraParams) - if err != nil { - return SmokeTestRequest{}, err - } - cloned.Execution = &execution - return cloned, nil -} - -// CloneSmokeTestReport returns a copy with independent optional facts and maps. -func CloneSmokeTestReport(report SmokeTestReport) (SmokeTestReport, error) { - cloned := report - cloned.Applicable = cloneBool(report.Applicable) - cloned.Facts = cloneSmokeTestFacts(report.Facts) - cloned.Timing = cloneSmokeTestTiming(report.Timing) - var err error - cloned.Target.EffectiveSettings.ExtraParams, err = jsonvalue.CopyMap(report.Target.EffectiveSettings.ExtraParams) - if err != nil { - return SmokeTestReport{}, err - } - if report.Attempts != nil { - cloned.Attempts = make([]SmokeTestAttempt, len(report.Attempts)) - for index, attempt := range report.Attempts { - cloned.Attempts[index] = attempt - cloned.Attempts[index].HTTPStatus = cloneInt(attempt.HTTPStatus) - cloned.Attempts[index].Facts = cloneSmokeTestFacts(attempt.Facts) - cloned.Attempts[index].Timing = cloneSmokeTestTiming(attempt.Timing) - } - } - return cloned, nil -} - -func cloneSmokeTestFacts(facts SmokeTestFacts) SmokeTestFacts { - facts.CredentialsSupplied = cloneBool(facts.CredentialsSupplied) - facts.EndpointReachable = cloneBool(facts.EndpointReachable) - facts.CredentialsAccepted = cloneBool(facts.CredentialsAccepted) - facts.ResponseReceived = cloneBool(facts.ResponseReceived) - facts.GenerationCompleted = cloneBool(facts.GenerationCompleted) - facts.TextReturned = cloneBool(facts.TextReturned) - facts.ExpectedTextMatched = cloneBool(facts.ExpectedTextMatched) - facts.StructuredOutputRequested = cloneBool(facts.StructuredOutputRequested) - facts.ValidJSON = cloneBool(facts.ValidJSON) - facts.SchemaMatched = cloneBool(facts.SchemaMatched) - return facts -} - -func cloneSmokeTestTiming(timing SmokeTestTiming) SmokeTestTiming { - timing.CapacityWait = cloneDuration(timing.CapacityWait) - timing.RequestDuration = cloneDuration(timing.RequestDuration) - timing.RetryDelay = cloneDuration(timing.RetryDelay) - timing.EvaluationDuration = cloneDuration(timing.EvaluationDuration) - return timing -} - -func cloneBool(value *bool) *bool { - if value == nil { - return nil - } - cloned := *value - return &cloned -} - -func cloneDuration(value *time.Duration) *time.Duration { - if value == nil { - return nil - } - cloned := *value - return &cloned -} - -func cloneFloat64(value *float64) *float64 { - if value == nil { - return nil - } - cloned := *value - return &cloned -} - -func cloneInt(value *int) *int { - if value == nil { - return nil - } - cloned := *value - return &cloned -} - func cloneString(value *string) *string { if value == nil { return nil diff --git a/internal/domain/smoke_diagnostics_test.go b/internal/domain/smoke_diagnostics_test.go index 3e198d3..14d9273 100644 --- a/internal/domain/smoke_diagnostics_test.go +++ b/internal/domain/smoke_diagnostics_test.go @@ -1,9 +1,6 @@ package domain -import ( - "testing" - "time" -) +import "testing" func TestCloneBackendCopiesDiagnosticMetadataAndProviderValues(t *testing.T) { profile := "profile" @@ -40,73 +37,3 @@ func TestCloneBackendCopiesDiagnosticMetadataAndProviderValues(t *testing.T) { t.Fatalf("source provider parameter changed through clone: %v", got) } } - -func TestCloneSmokeTestRequestOwnsOptionalSettings(t *testing.T) { - temperature := 0.25 - retries := 2 - reasoning := "low" - request := SmokeTestRequest{ - APIKey: "secret", - TransientRetries: &retries, - Execution: &ExecutionTargetOverride{ - Temperature: &temperature, - ReasoningEffort: &reasoning, - ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}}, - }, - } - - cloned, err := CloneSmokeTestRequest(request) - if err != nil { - t.Fatal(err) - } - *cloned.TransientRetries = 3 - *cloned.Execution.Temperature = 0.5 - *cloned.Execution.ReasoningEffort = "high" - cloned.Execution.ExtraParams["nested"].(map[string]any)["value"] = "changed" - - if retries != 2 || temperature != 0.25 || reasoning != "low" { - t.Fatal("cloned request shared optional scalar pointers") - } - if got := request.Execution.ExtraParams["nested"].(map[string]any)["value"]; got != "original" { - t.Fatalf("source provider parameter changed through clone: %v", got) - } -} - -func TestCloneSmokeTestReportOwnsMutableValues(t *testing.T) { - applicable := true - reachable := true - status := 200 - wait := 3 * time.Millisecond - report := SmokeTestReport{ - Applicable: &applicable, - Target: SmokeTestTarget{ - EffectiveSettings: ExecutionTarget{ - ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}}, - }, - }, - Facts: SmokeTestFacts{EndpointReachable: &reachable}, - Attempts: []SmokeTestAttempt{{ - HTTPStatus: &status, - Facts: SmokeTestFacts{EndpointReachable: &reachable}, - Timing: SmokeTestTiming{CapacityWait: &wait}, - }}, - } - - cloned, err := CloneSmokeTestReport(report) - if err != nil { - t.Fatal(err) - } - *cloned.Applicable = false - *cloned.Facts.EndpointReachable = false - *cloned.Attempts[0].HTTPStatus = 503 - *cloned.Attempts[0].Facts.EndpointReachable = false - *cloned.Attempts[0].Timing.CapacityWait = time.Second - cloned.Target.EffectiveSettings.ExtraParams["nested"].(map[string]any)["value"] = "changed" - - if !applicable || !reachable || status != 200 || wait != 3*time.Millisecond { - t.Fatal("cloned report shared optional scalar pointers") - } - if got := report.Target.EffectiveSettings.ExtraParams["nested"].(map[string]any)["value"]; got != "original" { - t.Fatalf("source provider parameter changed through clone: %v", got) - } -} diff --git a/internal/usecase/smoke_tester.go b/internal/usecase/smoke_tester.go index afdd501..83d462f 100644 --- a/internal/usecase/smoke_tester.go +++ b/internal/usecase/smoke_tester.go @@ -8,6 +8,7 @@ import ( "go.maximumdirect.net/promptkit/internal/domain" "go.maximumdirect.net/promptkit/internal/generation" + "go.maximumdirect.net/promptkit/internal/jsonvalue" "go.maximumdirect.net/promptkit/internal/llm" "go.maximumdirect.net/promptkit/internal/observation" ) @@ -438,11 +439,11 @@ func httpExecutionFacts(evidence llm.HTTPExecutionEvidence) domain.SmokeTestFact } func smokeReportTarget(target domain.SmokeTestTarget) (domain.SmokeTestTarget, error) { - cloned, err := domain.CloneSmokeTestReport(domain.SmokeTestReport{Target: target}) + params, err := jsonvalue.CopyMap(target.EffectiveSettings.ExtraParams) if err != nil { return domain.SmokeTestTarget{}, fmt.Errorf("clone smoke-test target settings: %w", err) } - target = cloned.Target + target.EffectiveSettings.ExtraParams = params target.EffectiveSettings.APIKey = "" target.Endpoint, err = endpointWithoutUserInfo(target.Endpoint) if err != nil { diff --git a/internal/usecase/smoke_tester_test.go b/internal/usecase/smoke_tester_test.go index 7adc844..c2765a7 100644 --- a/internal/usecase/smoke_tester_test.go +++ b/internal/usecase/smoke_tester_test.go @@ -393,3 +393,27 @@ func (b cancelOnCloseSmokeBody) Close() error { b.cancel() return b.ReadCloser.Close() } + +func TestSmokeReportTargetOwnsProviderValuesAndRedactsCredentials(t *testing.T) { + target := domain.SmokeTestTarget{ + Endpoint: "https://user:password@example.invalid/key", + EffectiveSettings: domain.ExecutionTarget{ + Endpoint: "https://user:password@example.invalid/v1", + APIKey: "secret", + ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}}, + }, + } + got, err := smokeReportTarget(target) + if err != nil { + t.Fatal(err) + } + got.EffectiveSettings.ExtraParams["nested"].(map[string]any)["value"] = "changed" + if target.EffectiveSettings.ExtraParams["nested"].(map[string]any)["value"] != "original" || + got.EffectiveSettings.APIKey != "" || got.Endpoint != "https://example.invalid/key" || got.EffectiveSettings.Endpoint != "https://example.invalid/v1" { + t.Fatalf("report target not detached or redacted: %+v", got) + } + target.EffectiveSettings.ExtraParams = map[string]any{"invalid": func() {}} + if _, err := smokeReportTarget(target); err == nil { + t.Fatal("invalid provider values were accepted") + } +}