Verify ownership across execution and repair
This commit is contained in:
@@ -410,6 +410,17 @@ configured-default/runtime-override diagnostics through the public facade.
|
|||||||
|
|
||||||
## Stage 15: Verify Cross-Path Ownership And Runtime Compatibility
|
## Stage 15: Verify Cross-Path Ownership And Runtime Compatibility
|
||||||
|
|
||||||
|
**Completed:** Cross-path checks cover mutations by an injected client on both
|
||||||
|
initial and repair requests, detached results and details, concurrent details
|
||||||
|
with claim/discard, and typed, numeric, null, cyclic, unsupported, and deep
|
||||||
|
runtime provider values. Remaining validating copies serve configuration
|
||||||
|
acceptance/overlay or public override acceptance; outward projections and schema
|
||||||
|
detachment remain ownership boundaries. Go 1.27.3 `go test -race
|
||||||
|
./internal/usecase ./internal/configuration ./internal/jsonvalue . -run
|
||||||
|
'Prepared|Provider|Extra|Smoke|Profile|Schema'` passed (the filtered jsonvalue
|
||||||
|
package selected no tests); the full `go test -race ./internal/jsonvalue` passed
|
||||||
|
separately.
|
||||||
|
|
||||||
**Findings:** 5–8. **Scope:** focused existing public and package contract tests.
|
**Findings:** 5–8. **Scope:** focused existing public and package contract tests.
|
||||||
|
|
||||||
1. Reconcile the ownership inventory with the delivered call paths. Verify that
|
1. Reconcile the ownership inventory with the delivered call paths. Verify that
|
||||||
|
|||||||
@@ -307,6 +307,95 @@ func TestPreparedExecutionRepairsEmptyBasicOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreparedExecutionIsolatesMutatingClientAcrossRepair(t *testing.T) {
|
||||||
|
client := &mutatingRepairClient{}
|
||||||
|
engine, err := promptkit.NewEngine(preparedContractConfig("original message", false),
|
||||||
|
promptkit.WithSchemaFS(preparedSchemaSource(), "."), promptkit.WithLLMClient(client))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
prepared, err := engine.PrepareExecution(context.Background(), promptkit.RunRequest{
|
||||||
|
PromptID: "prepared",
|
||||||
|
Execution: &promptkit.ExecutionTargetOverride{ExtraParams: map[string]any{
|
||||||
|
"nested": map[string]any{"value": "original"},
|
||||||
|
}},
|
||||||
|
Validation: &promptkit.OutputContract{Format: promptkit.FormatJSON, ValidationMode: promptkit.ValidationJSONSchema, SchemaPath: "schema.json", RepairAttempts: 1},
|
||||||
|
AppendedMessages: []promptkit.RenderedMessage{{Role: "user", Content: "appended", CacheControl: &promptkit.CacheControl{Type: promptkit.CacheControlEphemeral, TTL: "1h"}}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
before := prepared.Details()
|
||||||
|
result, err := engine.RunPrepared(context.Background(), prepared)
|
||||||
|
if err != nil || result == nil || result.Validation.Status != promptkit.ValidationPassed || client.calls != 2 {
|
||||||
|
t.Fatalf("run result = %+v, error = %v, calls = %d", result, err, client.calls)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(before, prepared.Details()) {
|
||||||
|
t.Fatal("client mutation changed retained prepared metadata")
|
||||||
|
}
|
||||||
|
result.EffectiveModelParams.ExtraParams["nested"].(map[string]any)["value"] = "result mutation"
|
||||||
|
if prepared.Details().EffectiveModelParams.ExtraParams["nested"].(map[string]any)["value"] != "original" {
|
||||||
|
t.Fatal("result mutation changed retained prepared metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mutatingRepairClient struct{ calls int }
|
||||||
|
|
||||||
|
func (c *mutatingRepairClient) Generate(_ context.Context, request promptkit.GenerateRequest) (*promptkit.GenerateResponse, error) {
|
||||||
|
c.calls++
|
||||||
|
if len(request.Prompt.Messages) < 2 || request.Prompt.Messages[0].Content != "original message" ||
|
||||||
|
request.Prompt.Messages[1].Content != "appended" || request.Prompt.Messages[1].CacheControl == nil ||
|
||||||
|
request.Prompt.Messages[1].CacheControl.TTL != "1h" ||
|
||||||
|
request.Target.ExtraParams["nested"].(map[string]any)["value"] != "original" ||
|
||||||
|
request.StructuredOutput.JSONSchema.Schema.(map[string]any)["title"] != "original root" {
|
||||||
|
return nil, fmt.Errorf("generation %d received mutated request", c.calls)
|
||||||
|
}
|
||||||
|
request.Prompt.Messages[0].Content = "client mutation"
|
||||||
|
request.Prompt.Messages[1].CacheControl.TTL = "client mutation"
|
||||||
|
request.Target.ExtraParams["nested"].(map[string]any)["value"] = "client mutation"
|
||||||
|
request.StructuredOutput.JSONSchema.Schema.(map[string]any)["title"] = "client mutation"
|
||||||
|
if c.calls == 1 {
|
||||||
|
return &promptkit.GenerateResponse{Content: `{"value":"invalid"}`}, nil
|
||||||
|
}
|
||||||
|
return &promptkit.GenerateResponse{Content: `{"value":3}`}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type runtimeLabels []string
|
||||||
|
type runtimeCounts map[string]int
|
||||||
|
|
||||||
|
func TestPreparedExecutionRuntimeProviderValues(t *testing.T) {
|
||||||
|
engine := newPreparedContractEngine(t, &preparedRecordingClient{response: &promptkit.GenerateResponse{Content: "ok"}}, "message")
|
||||||
|
labels := runtimeLabels{"original"}
|
||||||
|
counts := runtimeCounts{"count": 3}
|
||||||
|
request := promptkit.RunRequest{PromptID: "prepared", Execution: &promptkit.ExecutionTargetOverride{ExtraParams: map[string]any{
|
||||||
|
"labels": labels, "counts": counts, "number": json.Number("9007199254740993"), "null": nil,
|
||||||
|
}}}
|
||||||
|
prepared, err := engine.PrepareExecution(context.Background(), request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
labels[0], counts["count"] = "changed", 9
|
||||||
|
details := prepared.Details().EffectiveModelParams.ExtraParams
|
||||||
|
if details["labels"].(runtimeLabels)[0] != "original" || details["counts"].(runtimeCounts)["count"] != 3 ||
|
||||||
|
details["number"] != json.Number("9007199254740993") || details["null"] != nil {
|
||||||
|
t.Fatalf("runtime values changed: %#v", details)
|
||||||
|
}
|
||||||
|
cycle := map[string]any{}
|
||||||
|
cycle["self"] = cycle
|
||||||
|
var deep any = "leaf"
|
||||||
|
for range 150 {
|
||||||
|
deep = map[string]any{"child": deep}
|
||||||
|
}
|
||||||
|
for name, invalid := range map[string]any{"cyclic": cycle, "unsupported": func() {}, "too deep": deep} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
_, err := engine.PrepareExecution(context.Background(), promptkit.RunRequest{PromptID: "prepared", Execution: &promptkit.ExecutionTargetOverride{ExtraParams: map[string]any{"value": invalid}}})
|
||||||
|
if !errors.Is(err, promptkit.ErrInvalidRequest) {
|
||||||
|
t.Fatalf("invalid runtime provider value error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPreparedExecutionConcurrentClaimAllowsOneGeneration(t *testing.T) {
|
func TestPreparedExecutionConcurrentClaimAllowsOneGeneration(t *testing.T) {
|
||||||
release := make(chan struct{})
|
release := make(chan struct{})
|
||||||
client := &preparedRecordingClient{
|
client := &preparedRecordingClient{
|
||||||
@@ -383,6 +472,7 @@ func TestPreparedExecutionRunAndDiscardRaceHasOneWinner(t *testing.T) {
|
|||||||
}
|
}
|
||||||
runOutcome := make(chan outcome, 1)
|
runOutcome := make(chan outcome, 1)
|
||||||
discardDone := make(chan struct{})
|
discardDone := make(chan struct{})
|
||||||
|
detailsDone := make(chan bool, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-start
|
<-start
|
||||||
@@ -394,10 +484,23 @@ func TestPreparedExecutionRunAndDiscardRaceHasOneWinner(t *testing.T) {
|
|||||||
prepared.Discard()
|
prepared.Discard()
|
||||||
close(discardDone)
|
close(discardDone)
|
||||||
}()
|
}()
|
||||||
|
go func() {
|
||||||
|
<-start
|
||||||
|
for range 20 {
|
||||||
|
if prepared.Details().PromptID != "prepared" {
|
||||||
|
detailsDone <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detailsDone <- true
|
||||||
|
}()
|
||||||
|
|
||||||
close(start)
|
close(start)
|
||||||
runResult := <-runOutcome
|
runResult := <-runOutcome
|
||||||
<-discardDone
|
<-discardDone
|
||||||
|
if !<-detailsDone {
|
||||||
|
t.Fatalf("attempt %d lost details during lifecycle race", i)
|
||||||
|
}
|
||||||
calls := len(client.snapshot())
|
calls := len(client.snapshot())
|
||||||
switch {
|
switch {
|
||||||
case runResult.err == nil:
|
case runResult.err == nil:
|
||||||
|
|||||||
Reference in New Issue
Block a user