Clarify artifact reader ownership transfer

This commit is contained in:
2026-11-05 13:51:29 +00:00
parent 7be677c80b
commit 7d500ee62f
4 changed files with 57 additions and 3 deletions

View File

@@ -0,0 +1,43 @@
package promptkit_test
import (
"context"
"fmt"
"sync"
"testing"
"go.maximumdirect.net/configkit"
"go.maximumdirect.net/promptkit"
)
type independentArtifactReader struct{}
func (independentArtifactReader) Read(_ context.Context, ref promptkit.ArtifactRef) (*promptkit.Artifact, error) {
return &promptkit.Artifact{Body: []byte(ref.Body), Hash: ref.Body}, nil
}
func TestConcurrentPreparationWithTransferredArtifacts(t *testing.T) {
input := configkit.Input{Source: configkit.Mapping("ownership", map[string]any{
"profiles": []any{map[string]any{"id": "profile", "endpoint": "https://example.invalid/v1", "model": "model"}},
"prompts": []any{map[string]any{"id": "prompt", "default_profile": "profile", "inputs": []any{map[string]any{"name": "document", "required": true}}, "messages": []any{map[string]any{"role": "user", "content": `{{input "document"}}`}}, "output": map[string]any{"format": "text", "validation_mode": "none"}}},
})}
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &input}}, promptkit.WithArtifactReader(independentArtifactReader{}))
if err != nil {
t.Fatal(err)
}
var group sync.WaitGroup
for i := range 24 {
group.Go(func() {
value := fmt.Sprintf("body-%d", i)
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{PromptID: "prompt", Inputs: map[string]promptkit.ArtifactRef{"document": promptkit.Inline(value)}})
if err != nil {
t.Errorf("prepare: %v", err)
return
}
if len(prepared.Messages) != 1 || prepared.Messages[0].Content != value || prepared.InputHashes["document"] != value {
t.Errorf("prepared = %+v", prepared)
}
})
}
group.Wait()
}

View File

@@ -891,6 +891,11 @@ of collaborator errors. Implementations must honor cancellation, safely manage
copies they retain, avoid unsafe logging of content or credentials, and enforce copies they retain, avoid unsafe logging of content or credentials, and enforce
the application policy that motivated the injection. the application policy that motivated the injection.
A successful `ArtifactReader.Read` transfers the returned artifact and mutable
body storage to Promptkit. Readers that previously reused a returned buffer
must instead return independent storage and stop mutating it after return;
private scratch buffers may still be reused when the returned bytes are copied.
## Handle Errors ## Handle Errors
Use `errors.Is` with the Use `errors.Is` with the

View File

@@ -244,6 +244,10 @@ Focused authentication and public smoke tests pass on Go 1.27.3.
the accepted handoff, without promising safety for contract-violating readers. the accepted handoff, without promising safety for contract-violating readers.
Explain migration in consumer guidance; no borrowing/release protocol is added. Explain migration in consumer guidance; no borrowing/release protocol is added.
Completion: ArtifactReader ownership and migration guidance require independent
transferred storage. Focused concurrent preparation, adapter isolation, and
cause-propagation tests pass with the race detector on Go 1.27.3.
## Stage 9: Detach Prepared Ownership And Session Storage ## Stage 9: Detach Prepared Ownership And Session Storage
**Catalog:** 16, 18. **Files:** runner/prepared state and domain session normalization. **Catalog:** 16, 18. **Files:** runner/prepared state and domain session normalization.

View File

@@ -295,15 +295,17 @@ type Artifact struct {
// //
// Read may be called concurrently. It must honor ctx cancellation to make // Read may be called concurrently. It must honor ctx cancellation to make
// Prepare, PrepareExecution, and Run responsive to cancellation. The engine // Prepare, PrepareExecution, and Run responsive to cancellation. The engine
// passes a copied ref and immediately copies the returned Artifact.Body; it // passes a copied ref and defensively copies the returned Artifact.Body.
// does not retain either value. Readers supply artifact metadata, and the // A successful Read transfers the returned artifact and its mutable body
// storage to Promptkit. The reader must not mutate them through retained
// aliases after return. Private buffers may be reused only when the returned
// storage is independent. Readers supply artifact metadata, and the
// engine assigns an input-map name only when the returned artifact name is // engine assigns an input-map name only when the returned artifact name is
// empty. // empty.
// //
// An injected reader owns any application-specific path containment, // An injected reader owns any application-specific path containment,
// authorization, content-size, and content-type policy. It must protect // authorization, content-size, and content-type policy. It must protect
// sensitive references and bodies in its logging and in any copies it retains. // sensitive references and bodies in its logging and in any copies it retains.
// It may reuse or mutate the returned artifact and body after Read returns.
// //
// Returning a non-nil error makes the engine return an error matching // Returning a non-nil error makes the engine return an error matching
// ErrArtifactLoad while preserving the reader error through errors.Is. // ErrArtifactLoad while preserving the reader error through errors.Is.