From 6cf6a4b02a108c204c4bc5dd0774acf3911f4a80 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Mon, 9 Nov 2026 22:23:07 +0100 Subject: [PATCH 1/2] docs(common): describe ErrRefusedBeforePurchase by its contract The sentinel doc listed only the approved-upfront and count-bound checks as its sources. Reword it as "refused before the purchase request was built or sent" and name the other real causes (service ownership, duplicate-purchase guard), so callers do not read it as "approval missing". Closes #693 Co-Authored-By: Claude Sonnet 5.5 --- pkg/common/types.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/common/types.go b/pkg/common/types.go index 6c07d9c7..5ce93d8d 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -39,13 +39,15 @@ var ErrCurrencyNotAccepted = errors.New("currency not accepted") // contract until the platform persists a typed flag (platform #778). var ErrOutcomeUnknown = errors.New("purchase outcome unknown") -// ErrRefusedBeforePurchase wraps any error from the approved-upfront check or -// the purchase-count bounds check, both of which run before the purchase -// request is built or sent. Its presence is -// sufficient to know that this call sent no purchase request, but its absence -// proves nothing: other refusals do not carry it. It describes only the call -// that returned it and says nothing about earlier calls with the same -// idempotency token. A wrapper that combines several purchase calls must not +// ErrRefusedBeforePurchase wraps an error from a check that refused the +// purchase before the purchase request was built or sent. It says nothing +// about why: the cause may be a missing upfront approval, a count bound, a +// service-ownership rule, or a duplicate-purchase guard, and retrying or +// asking for approval may not help, so callers that act on the cause must +// inspect the wrapped error. Its presence is sufficient to know that this call +// sent no purchase request, but its absence proves nothing: other refusals do +// not carry it. It describes only the call that returned it and says nothing +// about earlier calls with the same idempotency token. A wrapper that combines several purchase calls must not // propagate it unless none of them sent a request. Detect it with // errors.Is(err, ErrRefusedBeforePurchase). var ErrRefusedBeforePurchase = errors.New("refused before purchase") From f3b9bb239c803b9ed032972cf3e54cc7f2234bb9 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Mon, 9 Nov 2026 22:38:48 +0100 Subject: [PATCH 2/2] docs(common): state the real upfront-check and unwrap limits on ErrRefusedBeforePurchase The upfront check refuses when the offering upfront exceeds the approved amount or cannot be compared, not when approval is missing, and the Azure cache refusal wraps only the sentinel, so callers cannot rely on unwrapping to tell the causes apart. Co-Authored-By: Claude Sonnet 5.5 --- pkg/common/types.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/common/types.go b/pkg/common/types.go index 5ce93d8d..a0b47ba7 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -41,13 +41,15 @@ var ErrOutcomeUnknown = errors.New("purchase outcome unknown") // ErrRefusedBeforePurchase wraps an error from a check that refused the // purchase before the purchase request was built or sent. It says nothing -// about why: the cause may be a missing upfront approval, a count bound, a -// service-ownership rule, or a duplicate-purchase guard, and retrying or -// asking for approval may not help, so callers that act on the cause must -// inspect the wrapped error. Its presence is sufficient to know that this call -// sent no purchase request, but its absence proves nothing: other refusals do -// not carry it. It describes only the call that returned it and says nothing -// about earlier calls with the same idempotency token. A wrapper that combines several purchase calls must not +// about why: the cause may be an offering upfront above the approved amount +// (or one that cannot be compared with it), a count bound, a service-ownership +// rule, or a duplicate-purchase guard, and retrying or asking for approval may +// not help. Some producers wrap only the sentinel and carry the cause in the +// message, so callers cannot rely on unwrapping to tell the causes apart. Its +// presence is sufficient to know that this call sent no purchase request, but +// its absence proves nothing: other refusals do not carry it. It describes only +// the call that returned it and says nothing about earlier calls with the same +// idempotency token. A wrapper that combines several purchase calls must not // propagate it unless none of them sent a request. Detect it with // errors.Is(err, ErrRefusedBeforePurchase). var ErrRefusedBeforePurchase = errors.New("refused before purchase")