Isolate execution observations and preserve injected error categories

This commit is contained in:
2026-09-23 17:56:15 +00:00
parent 48378a3269
commit 0c08038b4f
8 changed files with 291 additions and 18 deletions

View File

@@ -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)

View File

@@ -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