Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/rfc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Design documents and technical proposals, grouped by scope. Shared/cross-cutting
- [Message Queue Contract](messagequeue-contract.md) - How queue payloads are defined (Protobuf, serialized as protobuf JSON), located by audience (external in `api/{domain}/messagequeue/`, internal in `{domain}/core/messagequeue/`), bound to topics (the `topics` proto option), and enforced by Bazel visibility
- [Consumer Gate](consumer-gate.md) - Stopping and starting individual queue controllers at runtime via consumer middleware: parked deliveries held in-flight with visibility extension, gate state as a separate extension with a file-based first implementation shared by tests and operators
- [Change URIs](change-uri.md) - Identity of a code change: `scheme://{host[:port]}/{path}` per provider (GitHub PR, Phabricator Diff, git ref/commit) and canonical-form rules
- [Shared Platform Errors](shared-platform-errors.md) - Cross-domain error identities with common classification only where recovery behavior is universal

## SubmitQueue

Expand Down
50 changes: 50 additions & 0 deletions doc/rfc/shared-platform-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Shared Platform Errors

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.

## Problem

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.

| Concern | Current | Proposed |
|---|---|---|
| Error identity | Each domain defines equivalent sentinels | `platform/errs` owns shared semantic sentinels |
| Usage | Callers check domain aliases such as `storage.ErrVersionMismatch` | Callers use `platformerrs.ErrVersionMismatch` directly |
| Retry policy | Classification is repeated in controllers or domain wiring | The platform classifies only errors whose recovery policy is universal |
| Domain contracts | Storage and configuration contracts own both APIs and common error identities | Domain contracts remain domain-owned; only shared error identities move |

## Proposal

An error belongs in `platform/errs` when:

1. It has the same meaning across multiple domains.
2. Callers benefit from one `errors.Is` identity.
3. Its meaning is independent of a domain entity or extension API.
4. Any default classification applied by the platform is valid for every use.

Domains use shared errors directly rather than re-exporting them through permanent aliases:

```go
if errors.Is(err, platformerrs.ErrVersionMismatch) {
// Handle workflow-specific convergence.
}
```

Implementations preserve context with wrapping:

```go
return fmt.Errorf("update batch %s: %w", batch.ID, platformerrs.ErrVersionMismatch)
```

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.

## Initial errors

| Error | Meaning | Default platform classification |
|---|---|---|
| `ErrVersionMismatch` | An optimistic conditional update lost a concurrent race | `InfraRetryable` |
| `ErrNotFound` | A requested resource does not exist | None; remains non-retryable by default |

`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.

The generic classifier recognizes `ErrVersionMismatch` through the existing error-chain walk. This proposal does not change retry limits, backoff, DLQ policy, or delivery guarantees.
Loading