|
| 1 | +# Shared Platform Errors |
| 2 | + |
| 3 | +Define shared error identities for conditions that have the same meaning across domains. A shared identity enables consistent `errors.Is` checks and, where appropriate, a common platform classification. Sharing an error identity does not by itself make the error retryable. |
| 4 | + |
| 5 | +## Problem |
| 6 | + |
| 7 | +SubmitQueue and Stovepipe define duplicate sentinels for common conditions such as a missing resource and an optimistic version conflict. Callers must know which domain package produced the error, and platform code cannot apply a common policy without importing domain packages or adding domain-specific classifiers. |
| 8 | + |
| 9 | +| Concern | Current | Proposed | |
| 10 | +|---|---|---| |
| 11 | +| Error identity | Each domain defines equivalent sentinels | `platform/errs` owns shared semantic sentinels | |
| 12 | +| Usage | Callers check domain aliases such as `storage.ErrVersionMismatch` | Callers use `platformerrs.ErrVersionMismatch` directly | |
| 13 | +| Retry policy | Classification is repeated in controllers or domain wiring | The platform classifies only errors whose recovery policy is universal | |
| 14 | +| Domain contracts | Storage and configuration contracts own both APIs and common error identities | Domain contracts remain domain-owned; only shared error identities move | |
| 15 | + |
| 16 | +## Proposal |
| 17 | + |
| 18 | +An error belongs in `platform/errs` when: |
| 19 | + |
| 20 | +1. It has the same meaning across multiple domains. |
| 21 | +2. Callers benefit from one `errors.Is` identity. |
| 22 | +3. Its meaning is independent of a domain entity or extension API. |
| 23 | +4. Any default classification applied by the platform is valid for every use. |
| 24 | + |
| 25 | +Domains use shared errors directly rather than re-exporting them through permanent aliases: |
| 26 | + |
| 27 | +```go |
| 28 | +if errors.Is(err, platformerrs.ErrVersionMismatch) { |
| 29 | + // Handle workflow-specific convergence. |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +Implementations preserve context with wrapping: |
| 34 | + |
| 35 | +```go |
| 36 | +return fmt.Errorf("update batch %s: %w", batch.ID, platformerrs.ErrVersionMismatch) |
| 37 | +``` |
| 38 | + |
| 39 | +A domain may define a more specific error that wraps a platform error when callers need both identities. Domain storage and configuration interfaces remain in their existing packages. |
| 40 | + |
| 41 | +## Initial errors |
| 42 | + |
| 43 | +| Error | Meaning | Default platform classification | |
| 44 | +|---|---|---| |
| 45 | +| `ErrVersionMismatch` | An optimistic conditional update lost a concurrent race | `InfraRetryable` | |
| 46 | +| `ErrNotFound` | A requested resource does not exist | None; remains non-retryable by default | |
| 47 | + |
| 48 | +`ErrVersionMismatch` has a universal recovery policy: reload current state and retry or converge. `ErrNotFound` does not. Depending on the call site, absence may be an expected result, a user error, or an infrastructure invariant violation. Controllers may add a contextual classification only when they have information the shared error does not carry. |
| 49 | + |
| 50 | +The generic classifier recognizes `ErrVersionMismatch` through the existing error-chain walk. This proposal does not change retry limits, backoff, DLQ policy, or delivery guarantees. |
0 commit comments