From 4aeb58f9134b173ead1d1c5157c805c655be74b6 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 5 Nov 2026 16:00:39 +0000 Subject: [PATCH] Avoid duplicating nested error causes --- docs/roadmap/remediation.md | 7 ++++++- errors.go | 9 +++------ nested_error_contract_test.go | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index b6c21a0..733c9c1 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -121,7 +121,12 @@ the selected self-contained contract is not satisfied. ### 3. Avoid Duplicating An Already-Retained Error Cause [P3] -**Status:** Open. **Stages:** 3–4. +**Status:** Complete. **Stages:** 3–4. + +Completion: ordinary operation projection retains its mapped cause once, without +joining the original subtree a second time. Multi-engine regression coverage +checks exact cause/owner matching and one occurrence of an independent leaf; +specialized projection and redaction tests remain intact. **Location:** [public error projection](../../errors.go), `mapPublicError`, particularly lines 26–38. diff --git a/errors.go b/errors.go index 77ffa6e..99974bd 100644 --- a/errors.go +++ b/errors.go @@ -30,12 +30,9 @@ func mapPublicError(err error) error { if errors.Is(operation.Cause, llm.ErrMalformedResponse) { mapped = errors.Join(ErrMalformedResponse, mapped) } - var internalCapacity *usecase.CapacityError - var internalProvider *llm.ProviderHTTPError - if errors.As(operation.Cause, &internalCapacity) || errors.As(operation.Cause, &internalProvider) { - return errors.Join(owner, mapped) - } - return errors.Join(owner, mapped, operation.Cause) + // The projection already retains ordinary causes; adding the original + // again would duplicate its entire subtree at each enclosing operation. + return errors.Join(owner, mapped) } } return mapUnownedPublicError(err) diff --git a/nested_error_contract_test.go b/nested_error_contract_test.go index 7690064..687d97b 100644 --- a/nested_error_contract_test.go +++ b/nested_error_contract_test.go @@ -3,11 +3,27 @@ package promptkit_test import ( "context" "errors" + "strings" "testing" "go.maximumdirect.net/promptkit" ) +func TestNestedErrorsRetainEachCauseWithoutDuplicatingDiagnostics(t *testing.T) { + cause := errors.New("independent-leaf-diagnostic") + for range 10 { + parent := newSimpleEngine(t, "parent", promptkit.WithLLMClient(&fakeLLMClient{err: cause})) + _, err := parent.Run(context.Background(), promptkit.RunRequest{PromptID: "prompt"}) + if !errors.Is(err, cause) || !errors.Is(err, promptkit.ErrLLMGenerate) { + t.Fatalf("lost owner or cause: %v", err) + } + if strings.Count(err.Error(), "independent-leaf-diagnostic") != 1 { + t.Fatalf("duplicated leaf: %v", err) + } + cause = err + } +} + type nestedErrorReader struct{ cause error } func (r nestedErrorReader) Read(context.Context, promptkit.ArtifactRef) (*promptkit.Artifact, error) {