Share immutable prepared execution metadata

This commit is contained in:
2026-11-05 20:11:26 +00:00
parent c5f3de13f3
commit f5c900cb0a
6 changed files with 36 additions and 120 deletions

View File

@@ -172,14 +172,13 @@ func TestRunnerRunRejectsInvalidOutputContractBeforeAdmission(t *testing.T) {
func assertOutputContractCompletionSkipped(t *testing.T, collaborators outputContractTestCollaborators) {
t.Helper()
if collaborators.artifacts.calls != 0 || collaborators.renderer.calls != 0 ||
collaborators.validator.prepareCalls != 0 || collaborators.validator.directValidateCalls != 0 ||
collaborators.validator.prepareCalls != 0 ||
len(collaborators.admitter.backendIDs) != 0 || collaborators.llm.calls != 0 {
t.Fatalf(
"invalid output contract reached downstream work: artifacts=%d renderer=%d prepare_validation=%d validation=%d admissions=%d generation=%d",
"invalid output contract reached downstream work: artifacts=%d renderer=%d prepare_validation=%d admissions=%d generation=%d",
collaborators.artifacts.calls,
collaborators.renderer.calls,
collaborators.validator.prepareCalls,
collaborators.validator.directValidateCalls,
len(collaborators.admitter.backendIDs),
collaborators.llm.calls,
)

View File

@@ -7,7 +7,6 @@ import (
"time"
"go.maximumdirect.net/promptkit/internal/domain"
"go.maximumdirect.net/promptkit/internal/jsonvalue"
"go.maximumdirect.net/promptkit/internal/observation"
"go.maximumdirect.net/promptkit/internal/validate"
)
@@ -50,24 +49,12 @@ func (r *Runner) PrepareExecution(ctx context.Context, req domain.RunRequest) (*
return nil, err
}
executionSnapshot := operation.run
extraParams, err := jsonvalue.CopyMap(executionSnapshot.EffectiveModelParams.ExtraParams)
if err != nil {
return nil, fmt.Errorf("%w: failed to copy prepared execution: %v", ErrInvalidRequest, err)
}
executionSnapshot.EffectiveModelParams.ExtraParams = extraParams
details, err := clonePreparedRun(executionSnapshot)
if err != nil {
return nil, fmt.Errorf("%w: failed to copy prepared execution details: %v", ErrInvalidRequest, err)
}
return &PreparedExecution{
owner: r.identity,
state: preparedExecutionReady,
details: details,
details: operation.run,
payload: &preparedExecutionPayload{
prepared: executionSnapshot,
prepared: operation.run,
validation: operation.validation,
directKey: state.effectiveModel.APIKey,
transientRetries: state.transientRetries,
@@ -76,7 +63,8 @@ func (r *Runner) PrepareExecution(ctx context.Context, req domain.RunRequest) (*
}, nil
}
// Details returns a fresh credential-redacted copy of the prepared run.
// Details returns immutable, credential-redacted metadata borrowed from the
// handle. Callers must copy it before exposing it to mutable external code.
func (p *PreparedExecution) Details() *domain.PreparedRun {
if p == nil {
return nil
@@ -85,11 +73,7 @@ func (p *PreparedExecution) Details() *domain.PreparedRun {
detailsSnapshot := p.details
p.mu.Unlock()
details, err := clonePreparedRun(detailsSnapshot)
if err != nil {
return nil
}
return details
return detailsSnapshot
}
// Discard invalidates an unclaimed execution and drops its private payload.
@@ -189,9 +173,6 @@ func (p *preparedExecutionPayload) clear() {
if p == nil {
return
}
if p.prepared != nil {
p.prepared.EffectiveModelParams.APIKey = ""
}
p.prepared = nil
p.validation = nil
p.directKey = ""
@@ -222,35 +203,3 @@ func (p noOpPreparedValidation) Validate(
func (noOpPreparedValidation) SchemaDocument() any {
return nil
}
func clonePreparedRun(source *domain.PreparedRun) (*domain.PreparedRun, error) {
if source == nil {
return nil, nil
}
copied := *source
extraParams, err := jsonvalue.CopyMap(source.EffectiveModelParams.ExtraParams)
if err != nil {
return nil, err
}
copied.EffectiveModelParams.ExtraParams = extraParams
if source.InputHashes != nil {
copied.InputHashes = make(map[string]string, len(source.InputHashes))
for name, hash := range source.InputHashes {
copied.InputHashes[name] = hash
}
}
copied.Messages = domain.CloneRenderedMessages(source.Messages)
if source.StructuredOutput != nil {
structuredOutput := *source.StructuredOutput
copied.StructuredOutput = &structuredOutput
if source.StructuredOutput.JSONSchema != nil {
jsonSchema := *source.StructuredOutput.JSONSchema
copied.StructuredOutput.JSONSchema = &jsonSchema
copied.StructuredOutput.JSONSchema.Schema = jsonvalue.CloneTrusted(source.StructuredOutput.JSONSchema.Schema)
}
}
return &copied, nil
}

View File

@@ -8,6 +8,7 @@ import (
"testing"
"go.maximumdirect.net/promptkit/internal/domain"
"go.maximumdirect.net/promptkit/internal/jsonvalue"
"go.maximumdirect.net/promptkit/internal/llm"
"go.maximumdirect.net/promptkit/internal/validate"
)
@@ -47,10 +48,9 @@ func (p *recordingPreparedValidation) SchemaDocument() any {
}
type recordingValidationPreparer struct {
plan *recordingPreparedValidation
prepareErr error
prepareCalls int
directValidateCalls int
plan *recordingPreparedValidation
prepareErr error
prepareCalls int
}
type nilPlanPreparer struct{}
@@ -59,15 +59,6 @@ func (nilPlanPreparer) PrepareValidation(context.Context, domain.OutputContract)
return nil, nil
}
func (v *recordingValidationPreparer) Validate(
context.Context,
*domain.Artifact,
domain.OutputContract,
) (domain.ValidationResult, error) {
v.directValidateCalls++
return domain.ValidationResult{}, errors.New("live validation must not be used")
}
func (v *recordingValidationPreparer) PrepareValidation(
_ context.Context,
contract domain.OutputContract,
@@ -129,12 +120,8 @@ func TestRunnerPrepareExecutionCompletesWithoutAdmissionOrGeneration(t *testing.
}
defer prepared.Discard()
if validator.prepareCalls != 1 || validator.directValidateCalls != 0 {
t.Fatalf(
"validation calls=(prepare=%d direct=%d), want (1, 0)",
validator.prepareCalls,
validator.directValidateCalls,
)
if validator.prepareCalls != 1 {
t.Fatalf("validation preparations = %d, want 1", validator.prepareCalls)
}
if reader.calls != 1 || renderer.calls != 1 {
t.Fatalf("completion calls=(artifact=%d render=%d), want (1, 1)", reader.calls, renderer.calls)
@@ -156,18 +143,13 @@ func TestRunnerPrepareExecutionCompletesWithoutAdmissionOrGeneration(t *testing.
t.Fatalf("prepared details have unexpected structured output: %#v", first.StructuredOutput)
}
first.Messages[0].Content = "caller mutation"
first.InputHashes["input"] = "caller mutation"
first.EffectiveModelParams.ExtraParams["metadata"].(map[string]any)["source"] = "caller mutation"
first.StructuredOutput.JSONSchema.Schema.(map[string]any)["type"] = "string"
renderer.rendered.Messages[0].Content = "source mutation"
second := prepared.Details()
if second.Messages[0].Content != "original message" ||
second.InputHashes["input"] == "caller mutation" ||
if second != first || second.Messages[0].Content != "original message" ||
second.EffectiveModelParams.ExtraParams["metadata"].(map[string]any)["source"] != "original" ||
second.StructuredOutput.JSONSchema.Schema.(map[string]any)["type"] != "object" {
t.Fatalf("details did not preserve an independent snapshot: %#v", second)
t.Fatalf("borrowed details did not preserve the snapshot: %#v", second)
}
}
@@ -192,11 +174,6 @@ func TestRunnerPreparationDetachesDeepPreparedSchema(t *testing.T) {
if _, ok := first["annotation"].(map[string]any)["child"].(map[string]any); !ok {
t.Fatal("prepared schema aliases the validator document")
}
first["annotation"].(map[string]any)["child"] = "caller mutation"
second := prepared.Details().StructuredOutput.JSONSchema.Schema.(map[string]any)
if _, ok := second["annotation"].(map[string]any)["child"].(map[string]any); !ok {
t.Fatal("prepared schema details share mutable storage")
}
}
func TestRunnerRunPreparedCredentialAvailabilityBeforeAdmission(t *testing.T) {
@@ -378,8 +355,8 @@ func TestRunnerRunPreparedUsesFrozenValidationForInitialAndRepairOutputs(t *test
if !reflect.DeepEqual(validator.plan.artifacts, []string{`{"broken":true}`, `{"repaired":true}`}) {
t.Fatalf("prepared validation artifacts=%#v", validator.plan.artifacts)
}
if validator.directValidateCalls != 0 || repairer.calls != 1 {
t.Fatalf("validation/repair calls=(direct=%d repair=%d), want (0, 1)", validator.directValidateCalls, repairer.calls)
if repairer.calls != 1 {
t.Fatalf("repair calls = %d, want 1", repairer.calls)
}
if len(client.requests) != 1 || len(repairer.reqs) != 1 ||
!reflect.DeepEqual(repairer.reqs[0].OriginalMessages, client.requests[0].Prompt.Messages) {
@@ -653,7 +630,7 @@ func TestRunnerPreparedExecutionDetachesTransferredSnapshotSources(t *testing.T)
}
wantLabel := "profile source"
if test.requestOverride {
request.Execution = &domain.ExecutionTargetOverride{ExtraParams: requestExtras}
request.Execution = &domain.ExecutionTargetOverride{ExtraParams: jsonvalue.CloneTrusted(requestExtras).(map[string]any)}
wantLabel = "request source"
}
@@ -677,11 +654,6 @@ func TestRunnerPreparedExecutionDetachesTransferredSnapshotSources(t *testing.T)
details.StructuredOutput.JSONSchema.Schema.(map[string]any)["properties"].(map[string]any)["value"].(map[string]any)["type"] != "string" {
t.Fatalf("prepared details retained a source alias: %#v", details)
}
details.Messages[0].Content = "mutated details"
if again := prepared.Details(); again == nil || len(again.Messages) != 1 || again.Messages[0].Content != "captured rendering" {
t.Fatalf("details mutation escaped into the retained snapshot: %+v", again)
}
result, err := runner.RunPrepared(context.Background(), prepared)
if err != nil || result == nil || !result.Validation.IsValid || result.InputHashes["transcript"] != "captured-input-hash" {
t.Fatalf("run prepared snapshot=(%+v, %v)", result, err)
@@ -703,22 +675,6 @@ func TestRunnerPreparedExecutionDetachesTransferredSnapshotSources(t *testing.T)
}
}
func TestRunnerPrepareExecutionClassifiesExtraCopyFailureAsInvalidRequest(t *testing.T) {
profile := defaultExecutionProfile()
profile.ResolvedTarget.ExtraParams = map[string]any{"unsupported": func() {}}
runner := NewRunner(
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": profile}},
defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil,
)
prepared, err := runner.PrepareExecution(context.Background(), domain.RunRequest{
PromptID: "p", ProfileID: "exec",
})
if prepared != nil || !errors.Is(err, ErrInvalidRequest) {
t.Fatalf("prepared execution copy failure=(%v, %v), want ErrInvalidRequest", prepared, err)
}
}
func TestRunnerPreparedExecutionWithoutValidatorSkipsValidation(t *testing.T) {
runner := NewRunner(
&fakePromptRepo{def: promptDef(domain.FormatJSON, domain.ValidationJSON, 0)},

View File

@@ -20,6 +20,7 @@ import (
"go.maximumdirect.net/promptkit/internal/defaults"
"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/prompt"
"go.maximumdirect.net/promptkit/internal/promptdef"
@@ -144,6 +145,11 @@ func (f *fakeExecutionProfileRepo) GetProfile(ctx context.Context, id string) (*
}
if p, ok := f.profiles[id]; ok {
cp := *p
if cp.ResolvedTarget != nil {
target := *cp.ResolvedTarget
target.ExtraParams = jsonvalue.CloneTrusted(target.ExtraParams).(map[string]any)
cp.ResolvedTarget = &target
}
return &cp, nil
}
return nil, errors.New("profile not found")