From 5b76c2b0497d93afc7970688ff9fbab93cf2b646 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 04:40:02 +0000 Subject: [PATCH] docs(client-sdk): state the two-tier error-code vocabulary and add the VALIDATION_FAILED row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `### Error Codes` table listed nine codes, all members of the closed `StandardErrorCode` enum, with no statement of what it enumerates. It omitted `VALIDATION_FAILED` — the ledger-registered code both of the page's own error-handling examples branch on — so a reader working from the table concludes a per-field validation failure arrives as `VALIDATION_ERROR` and writes a branch that never matches. Measured first, because adding the row to a table of enum members would have made the table false: the table is 9 of the enum's 50 members, so it was never "the enum" — it is a curated subset with an unstated contract. The fix is therefore the contract plus the row, not the row alone. - State the two-tier vocabulary (ADR-0112): the closed `StandardErrorCode` catalog plus the per-package `ERROR_CODE_LEDGER`, with the exported `ErrorCode` schema as their union and `packages/spec` as the authority. - Add a `Tier` column so each row says which set it comes from, and add the `VALIDATION_FAILED` row (ledger, 400). - Sharpen `VALIDATION_ERROR`'s description to the distinction that actually holds on the wire: request-shape refusals (repeated query parameter, disallowed filter, malformed argument) versus record-level validation, which answers `VALIDATION_FAILED` and carries `fields[]`. - Note that `Category`/`Retryable` are semantic classification, not wire guarantees — `packages/rest` sets neither on the per-field envelope. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6 --- content/docs/api/client-sdk.mdx | 46 +++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx index 769c82c22d..bc0c02d897 100644 --- a/content/docs/api/client-sdk.mdx +++ b/content/docs/api/client-sdk.mdx @@ -666,17 +666,41 @@ field-anchored". ### Error Codes -| Code | HTTP | Category | Retryable | Description | -|:-----|:-----|:---------|:----------|:------------| -| `VALIDATION_ERROR` | 400 | validation | No | Input validation failed | -| `INVALID_QUERY` | 400 | validation | No | Malformed query expression | -| `UNAUTHENTICATED` | 401 | authentication | No | Authentication required | -| `PERMISSION_DENIED` | 403 | authorization | No | Insufficient permissions | -| `RESOURCE_NOT_FOUND` | 404 | not_found | No | Resource does not exist | -| `RATE_LIMIT_EXCEEDED` | 429 | rate_limit | Yes | Too many requests | -| `INTERNAL_ERROR` | 500 | server | Yes | Unexpected server error | -| `SERVICE_UNAVAILABLE` | 503 | server | Yes | Service temporarily unavailable | -| `NOT_IMPLEMENTED` | 501 | server | No | Service not installed (plugin missing) | +`error.code` is drawn from a **two-tier vocabulary** (ADR-0112), and +`packages/spec` is the authority for the full set: + +- **Standard catalog** — the closed `StandardErrorCode` enum + (`packages/spec/src/api/errors.zod.ts`): generic conditions with + platform-wide HTTP semantics. It does not grow when a service invents a code. +- **Ledger-registered codes** — the service-specific codes each package + registers in `ERROR_CODE_LEDGER` + (`packages/spec/src/api/error-code-ledger.zod.ts`). + +The exported `ErrorCode` schema is the **union** of the two, so a code being +absent from `StandardErrorCode` does not make it unofficial or unsupported: +`VALIDATION_FAILED` — the code the examples above branch on — is a +ledger-registered code, as are several others this page's examples use. The +table below is a hand-picked subset of the codes you are most likely to branch +on, **not** either tier in full; the **Tier** column says which set a row comes +from. + +| Code | Tier | HTTP | Category | Retryable | Description | +|:-----|:-----|:-----|:---------|:----------|:------------| +| `VALIDATION_ERROR` | standard | 400 | validation | No | The **request** was refused before any record was validated — a repeated query parameter, a filter outside the allowlist, a malformed argument | +| `VALIDATION_FAILED` | ledger | 400 | validation | No | A **record** failed validation on a write; carries `fields[]`. This — not `VALIDATION_ERROR` — is what a per-field failure arrives as | +| `INVALID_QUERY` | standard | 400 | validation | No | Malformed query expression | +| `UNAUTHENTICATED` | standard | 401 | authentication | No | Authentication required | +| `PERMISSION_DENIED` | standard | 403 | authorization | No | Insufficient permissions | +| `RESOURCE_NOT_FOUND` | standard | 404 | not_found | No | Resource does not exist | +| `RATE_LIMIT_EXCEEDED` | standard | 429 | rate_limit | Yes | Too many requests | +| `INTERNAL_ERROR` | standard | 500 | server | Yes | Unexpected server error | +| `SERVICE_UNAVAILABLE` | standard | 503 | server | Yes | Service temporarily unavailable | +| `NOT_IMPLEMENTED` | standard | 501 | server | No | Service not installed (plugin missing) | + +`Category` and `Retryable` above are the **semantic** classification of each +condition. Neither is guaranteed on the wire — as the narrowing example shows, +`error.category` and `error.retryable` are present only when the server sent +them, and the REST server's per-field validation envelope sends neither. ---