Clarify artifact reader ownership transfer
This commit is contained in:
43
artifact_ownership_contract_test.go
Normal file
43
artifact_ownership_contract_test.go
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
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
|
||||
|
||||
Use `errors.Is` with the
|
||||
|
||||
@@ -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.
|
||||
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
|
||||
|
||||
**Catalog:** 16, 18. **Files:** runner/prepared state and domain session normalization.
|
||||
|
||||
8
types.go
8
types.go
@@ -295,15 +295,17 @@ type Artifact struct {
|
||||
//
|
||||
// Read may be called concurrently. It must honor ctx cancellation to make
|
||||
// Prepare, PrepareExecution, and Run responsive to cancellation. The engine
|
||||
// passes a copied ref and immediately copies the returned Artifact.Body; it
|
||||
// does not retain either value. Readers supply artifact metadata, and the
|
||||
// passes a copied ref and defensively copies the returned Artifact.Body.
|
||||
// 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
|
||||
// empty.
|
||||
//
|
||||
// An injected reader owns any application-specific path containment,
|
||||
// authorization, content-size, and content-type policy. It must protect
|
||||
// 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
|
||||
// ErrArtifactLoad while preserving the reader error through errors.Is.
|
||||
|
||||
Reference in New Issue
Block a user