Isolate execution observations and preserve injected error categories
This commit is contained in:
@@ -191,6 +191,24 @@ func (r *Recorder) deliver(event domain.ExecutionEvent) {
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
// WithoutExecutionState starts a public operation without inheriting another
|
||||
// invocation's recorder or prepared-claim sink. Other context values and all
|
||||
// cancellation/deadline behavior remain with the caller's context.
|
||||
func WithoutExecutionState(ctx context.Context) context.Context {
|
||||
return executionBoundary{Context: ctx}
|
||||
}
|
||||
|
||||
type executionBoundary struct{ context.Context }
|
||||
|
||||
func (c executionBoundary) Value(key any) any {
|
||||
switch key.(type) {
|
||||
case contextKey, claimSinkKey:
|
||||
return nil
|
||||
default:
|
||||
return c.Context.Value(key)
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecorder carries one invocation's recorder through internal client calls.
|
||||
func WithRecorder(ctx context.Context, recorder *Recorder) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, recorder)
|
||||
|
||||
@@ -2,6 +2,7 @@ package observation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -9,6 +10,35 @@ import (
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||
)
|
||||
|
||||
func TestExecutionBoundaryPreservesCallerContext(t *testing.T) {
|
||||
type callerKey struct{}
|
||||
parent, stop := context.WithTimeout(context.WithValue(context.Background(), callerKey{}, "caller"), time.Minute)
|
||||
defer stop()
|
||||
parent, cancel := context.WithCancelCause(parent)
|
||||
defer cancel(nil)
|
||||
recorder := newRecorder("outer", time.Now, nil)
|
||||
sink := recorder
|
||||
parent = WithClaimSink(WithRecorder(parent, recorder), &sink)
|
||||
child := WithoutExecutionState(parent)
|
||||
if FromContext(child) != nil || FromContext(parent) != recorder {
|
||||
t.Fatal("execution boundary did not isolate recorder state")
|
||||
}
|
||||
RegisterClaim(child, nil)
|
||||
if sink != recorder {
|
||||
t.Fatal("execution boundary retained the parent claim sink")
|
||||
}
|
||||
wantDeadline, _ := parent.Deadline()
|
||||
gotDeadline, ok := child.Deadline()
|
||||
if !ok || gotDeadline != wantDeadline || child.Value(callerKey{}) != "caller" || child.Done() != parent.Done() {
|
||||
t.Fatal("execution boundary changed caller context")
|
||||
}
|
||||
cause := errors.New("caller canceled")
|
||||
cancel(cause)
|
||||
if !errors.Is(child.Err(), context.Canceled) || context.Cause(child) != cause {
|
||||
t.Fatal("execution boundary lost cancellation or its cause")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecorderAccountsDispatchedAttemptsAndReportedUsage(t *testing.T) {
|
||||
start := time.Date(2026, 9, 23, 12, 0, 0, 0, time.UTC)
|
||||
tick := 0
|
||||
|
||||
Reference in New Issue
Block a user