Harden public ownership and diagnostic contracts

This commit is contained in:
2026-08-11 21:45:54 +00:00
parent c8b6d5c490
commit 57f2ce1ce4
4 changed files with 213 additions and 38 deletions

View File

@@ -203,26 +203,44 @@ func TestPreparedRunJSONDoesNotExposeSecretOrTargetPresence(t *testing.T) {
}
func TestRunRequestFormattingRedactsDirectAPIKey(t *testing.T) {
const secret = "run-request-secret"
const (
secret = "run-request-api-key-sentinel"
inputURI = "memory://run-request-uri-sentinel"
inputBody = "run-request-body-sentinel"
variableValue = "run-request-variable-sentinel"
)
req := promptkit.RunRequest{
PromptID: frameworkMarkdownSummaryPromptID,
ProfileID: frameworkFastProfileID,
APIKey: secret,
Inputs: map[string]promptkit.ArtifactRef{
"transcript": promptkit.Inline("Rin opens the gate."),
"transcript": promptkit.InlineWithURI(inputURI, inputBody),
},
Vars: map[string]string{"audience": variableValue},
}
for _, formatted := range []string{
fmt.Sprint(req),
req.String(),
req.GoString(),
fmt.Sprintf("%v", req),
fmt.Sprintf("%+v", req),
fmt.Sprintf("%#v", req),
} {
if strings.Contains(formatted, secret) {
t.Fatalf("formatted RunRequest leaked API key: %s", formatted)
for _, privateValue := range []string{secret, inputURI, inputBody, variableValue} {
if strings.Contains(formatted, privateValue) {
t.Fatalf("formatted RunRequest leaked private value %q: %s", privateValue, formatted)
}
}
if !strings.Contains(formatted, "APIKeySet:true") {
t.Fatalf("formatted RunRequest should indicate an API key is set, got %s", formatted)
for _, summary := range []string{
`PromptID:"` + frameworkMarkdownSummaryPromptID + `"`,
`ProfileID:"` + frameworkFastProfileID + `"`,
"APIKeySet:true",
"Inputs:1",
"Vars:1",
} {
if !strings.Contains(formatted, summary) {
t.Fatalf("formatted RunRequest omitted structural summary %q: %s", summary, formatted)
}
}
}
@@ -1868,6 +1886,46 @@ func TestOpenAICompatibleProfileRunsThroughNormalProfilePath(t *testing.T) {
}
}
func TestOpenAICompatibleProfileMapsEveryField(t *testing.T) {
extraParams := map[string]any{"provider_option": "distinct-extra-params"}
got := promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
ID: "distinct-id",
BackendID: "distinct-backend",
Endpoint: "https://distinct.example/v1",
Model: "distinct-model",
APIKeyRequired: true,
Temperature: 0.25,
MaxTokens: 321,
TopP: 0.75,
TimeoutSeconds: 43,
ServiceTier: "distinct-service-tier",
ReasoningEffort: "distinct-reasoning-effort",
ExtraParams: extraParams,
})
want := promptkit.Profile{
ID: "distinct-id",
BackendID: "distinct-backend",
Endpoint: "https://distinct.example/v1",
Model: "distinct-model",
Temperature: 0.25,
MaxTokens: 321,
TopP: 0.75,
TimeoutSeconds: 43,
ServiceTier: "distinct-service-tier",
ReasoningEffort: "distinct-reasoning-effort",
APIKeyRequired: true,
ExtraParams: map[string]any{"provider_option": "distinct-extra-params"},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("profile mapping:\ngot %#v\nwant %#v", got, want)
}
extraParams["added_after_construction"] = true
if _, ok := got.ExtraParams["added_after_construction"]; ok {
t.Fatal("profile retained the configuration map")
}
}
func TestEngineRunLayersTransportAndGenerationTimeouts(t *testing.T) {
intPointer := func(value int) *int {
return &value

View File

@@ -0,0 +1,96 @@
package promptkit
import (
"context"
"reflect"
"strconv"
"sync"
"testing"
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
)
func TestPublicLLMClientAdapterGivesClientOwnedNestedValues(t *testing.T) {
source := adapterOwnershipRequest()
want := adapterOwnershipRequest()
client := &retainingMutatingLLMClient{}
adapter := publicLLMClientAdapter{client: client}
if _, err := adapter.Generate(context.Background(), source); err != nil {
t.Fatalf("generate: %v", err)
}
if !reflect.DeepEqual(source, want) {
t.Fatalf("client mutation changed prepared source:\ngot %#v\nwant %#v", source, want)
}
var mutations sync.WaitGroup
mutations.Add(1)
go func() {
defer mutations.Done()
for i := 0; i < 10_000; i++ {
mutateGenerateRequest(&client.retained, strconv.Itoa(i))
}
}()
for i := 0; i < 10_000; i++ {
laterRequest := fromDomainGenerateRequest(source)
if laterRequest.Prompt.Messages[0].Content != "source-message" ||
laterRequest.Prompt.Messages[0].CacheControl.TTL != "source-ttl" ||
laterRequest.Target.ExtraParams["nested"].([]any)[0] != "source-extra" ||
laterRequest.StructuredOutput.JSONSchema.Schema.(map[string]any)["enum"].([]any)[0] != "source-schema" {
t.Fatal("retained client mutation reached a later execution request")
}
}
mutations.Wait()
if !reflect.DeepEqual(source, want) {
t.Fatalf("retained client mutation changed prepared source:\ngot %#v\nwant %#v", source, want)
}
}
type retainingMutatingLLMClient struct {
retained GenerateRequest
}
func (c *retainingMutatingLLMClient) Generate(_ context.Context, request GenerateRequest) (*GenerateResponse, error) {
c.retained = request
mutateGenerateRequest(&c.retained, "client-mutation")
return &GenerateResponse{Content: "generated"}, nil
}
func adapterOwnershipRequest() domain.GenerateRequest {
return domain.GenerateRequest{
Prompt: domain.RenderedPrompt{
SessionID: "source-session",
Messages: []domain.RenderedMessage{{
Role: "user",
Content: "source-message",
CacheControl: &domain.CacheControl{
Type: domain.CacheControlEphemeral,
TTL: "source-ttl",
},
}},
},
Target: domain.ExecutionTarget{
Model: "source-model",
APIKey: "source-api-key",
ExtraParams: map[string]any{
"nested": []any{"source-extra"},
},
},
StructuredOutput: &domain.StructuredOutputSpec{
Type: domain.StructuredOutputJSONSchema,
JSONSchema: &domain.StructuredOutputJSONSpec{
Name: "source-schema-name",
Strict: true,
Schema: map[string]any{"enum": []any{"source-schema"}},
},
},
}
}
func mutateGenerateRequest(request *GenerateRequest, value string) {
request.Prompt.Messages[0].Content = value
request.Prompt.Messages[0].CacheControl.TTL = value
request.Target.ExtraParams["nested"].([]any)[0] = value
request.StructuredOutput.JSONSchema.Schema.(map[string]any)["enum"].([]any)[0] = value
}

View File

@@ -44,12 +44,12 @@ func (p *PreparedExecution) Discard() {
// String returns a constant representation that exposes no retained request,
// rendered content, or credential data.
func (p *PreparedExecution) String() string {
func (p PreparedExecution) String() string {
return preparedExecutionString
}
// GoString returns a constant Go-syntax representation that exposes no
// retained request, rendered content, or credential data.
func (p *PreparedExecution) GoString() string {
func (p PreparedExecution) GoString() string {
return preparedExecutionString
}

View File

@@ -409,14 +409,35 @@ func TestPreparedExecutionDiscardAndFormattingDoNotExposePrivateState(t *testing
t.Fatalf("prepare execution: %v", err)
}
formattedValues := []string{
fmt.Sprint(prepared),
fmt.Sprintf("%+v", prepared),
fmt.Sprintf("%#v", prepared),
copied := *prepared
zeroValue := promptkit.PreparedExecution{}
var nilHandle *promptkit.PreparedExecution
for name, value := range map[string]any{
"original pointer": prepared,
"copied value": copied,
"zero value": zeroValue,
"zero pointer": &zeroValue,
} {
for format, formatted := range map[string]string{
"String": fmt.Sprintf("%s", value),
"GoString": fmt.Sprintf("%#v", value),
"v": fmt.Sprintf("%v", value),
"+v": fmt.Sprintf("%+v", value),
} {
if formatted != "promptkit.PreparedExecution{opaque}" {
t.Fatalf("%s %s formatting = %q, want opaque representation", name, format, formatted)
}
assertPreparedPrivateValuesAbsent(t, formatted, directCredential, renderedContent)
}
}
for _, formatted := range formattedValues {
if formatted != "promptkit.PreparedExecution{opaque}" {
t.Fatalf("unexpected opaque formatting: %q", formatted)
for format, formatted := range map[string]string{
"String": fmt.Sprintf("%s", nilHandle),
"GoString": fmt.Sprintf("%#v", nilHandle),
"v": fmt.Sprintf("%v", nilHandle),
"+v": fmt.Sprintf("%+v", nilHandle),
} {
if formatted != "<nil>" {
t.Fatalf("nil pointer %s formatting = %q, want <nil>", format, formatted)
}
assertPreparedPrivateValuesAbsent(t, formatted, directCredential, renderedContent)
}
@@ -433,27 +454,9 @@ func TestPreparedExecutionDiscardAndFormattingDoNotExposePrivateState(t *testing
}
assertPreparedPrivateValuesAbsent(t, string(detailsJSON), directCredential)
prepared.Discard()
prepared.Discard()
result, lifecycleErr := engine.RunPrepared(context.Background(), prepared)
if result != nil || !errors.Is(lifecycleErr, promptkit.ErrInvalidRequest) {
t.Fatalf("discarded execution result=(%+v, %v), want ErrInvalidRequest", result, lifecycleErr)
}
assertPreparedPrivateValuesAbsent(t, lifecycleErr.Error(), directCredential, renderedContent)
if !reflect.DeepEqual(prepared.Details(), detailsBefore) {
t.Fatal("details changed after discard")
}
executed, err := engine.PrepareExecution(context.Background(), promptkit.RunRequest{
PromptID: "prepared",
APIKey: directCredential,
})
executionResult, err := engine.RunPrepared(context.Background(), &copied)
if err != nil {
t.Fatalf("prepare execution for request inspection: %v", err)
}
executionResult, err := engine.RunPrepared(context.Background(), executed)
if err != nil {
t.Fatalf("run execution for request inspection: %v", err)
t.Fatalf("run copied execution after formatting: %v", err)
}
requests := client.snapshot()
if len(requests) != 1 || requests[0].APIKey != directCredential {
@@ -478,16 +481,34 @@ func TestPreparedExecutionDiscardAndFormattingDoNotExposePrivateState(t *testing
}
assertPreparedPrivateValuesAbsent(t, string(resultJSON), directCredential)
var nilHandle *promptkit.PreparedExecution
nilHandle.Discard()
if !reflect.DeepEqual(nilHandle.Details(), promptkit.PreparedRun{}) {
t.Fatalf("nil handle details=%+v, want zero value", nilHandle.Details())
}
zeroHandle := &promptkit.PreparedExecution{}
zeroHandle := &zeroValue
zeroHandle.Discard()
if !reflect.DeepEqual(zeroHandle.Details(), promptkit.PreparedRun{}) {
t.Fatalf("zero handle details=%+v, want zero value", zeroHandle.Details())
}
discarded, err := engine.PrepareExecution(context.Background(), promptkit.RunRequest{
PromptID: "prepared",
APIKey: directCredential,
})
if err != nil {
t.Fatalf("prepare execution for discard: %v", err)
}
discardedDetails := discarded.Details()
discarded.Discard()
discarded.Discard()
result, lifecycleErr := engine.RunPrepared(context.Background(), discarded)
if result != nil || !errors.Is(lifecycleErr, promptkit.ErrInvalidRequest) {
t.Fatalf("discarded execution result=(%+v, %v), want ErrInvalidRequest", result, lifecycleErr)
}
assertPreparedPrivateValuesAbsent(t, lifecycleErr.Error(), directCredential, renderedContent)
if !reflect.DeepEqual(discarded.Details(), discardedDetails) {
t.Fatal("details changed after discard")
}
}
func TestPreparedExecutionCredentialCapacityAndTimingBoundaries(t *testing.T) {