diff --git a/.changeset/adr-0112-envelope-refusal-declaration.md b/.changeset/adr-0112-envelope-refusal-declaration.md new file mode 100644 index 0000000000..01b968d9f2 --- /dev/null +++ b/.changeset/adr-0112-envelope-refusal-declaration.md @@ -0,0 +1,17 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec): the ADR-0112 error envelope gains a producer-side `refusal` declaration, so a deliberate 5xx refusal can keep its caller-authored `message` (#16335) + +`ApiErrorSchema` and `EnhancedApiErrorSchema` declare one new optional key, **`refusal: true`** — the producer's declaration that the 5xx it named is a deliberate REFUSAL whose `message` is authored for the caller, so the boundary keeps that message verbatim instead of withholding it. Director ruling, decision batch #58 (2026-09-06, option C): the refusal/fault distinction is a producer-side declaration on the published envelope — not a status heuristic and not a second allow-list. + +The three cases are now documented side by side on the envelope's TSDoc: + +- **undeclared 5xx** (no `status` on the throw) — unchanged: the leak heuristic decides per message. +- **declared fault** (`status >= 500` + `code`, nothing declared here) — unchanged, and still the DEFAULT: `message` is withheld from the body and logged for the operator. +- **declared refusal** (`status >= 500` + `code` + `refusal: true`) — new: `message` is kept verbatim, bounded exactly as a 4xx message is. + +Purely additive: a producer that says nothing here gets exactly the previous behaviour. `true` is the only value — `refusal: false` fails parse instead of becoming a third state consumers would have to interpret. `userMessage` is orthogonal (end-user text; it never replaces `message`) and may ride the same envelope; the TSDoc reconciles this flag with the recorded reason `userMessage` is a text-carrying field rather than "a boolean beside `message`". + +This is the spec half. The relay half — the three withhold arms reading the declaration (two in `@objectstack/rest`: `declaredServerFaultAnswer`, and `resolveErrorResponse`'s own 5xx passthrough arm, which the `/references` door reaches; one at `@objectstack/runtime`'s dispatcher exit, `errorResponseBase`, which `objectstack serve` mounts and which never consults the first), plus retiring the route-local patch from PR #16143 on `/meta/:type/:name/references` — is #16146 for the REST pair and its sub-issue #17153 for the runtime exit; until they land, a declared refusal is still withheld at the wire. diff --git a/content/docs/api/error-catalog.mdx b/content/docs/api/error-catalog.mdx index 1220ba4caa..6a529b3d11 100644 --- a/content/docs/api/error-catalog.mdx +++ b/content/docs/api/error-catalog.mdx @@ -664,6 +664,7 @@ Every error response follows the `EnhancedApiError` schema: interface EnhancedApiError { code: StandardErrorCode; // Machine-readable error code message: string; // Human-readable description + refusal?: true; // Producer-declared refusal: keeps message on a 5xx category?: ErrorCategory; // Error category httpStatus?: number; // HTTP status code retryable: boolean; // Whether retry may succeed diff --git a/content/docs/references/api/analytics.mdx b/content/docs/references/api/analytics.mdx index 2d21bfea2d..b6e3689ad4 100644 --- a/content/docs/references/api/analytics.mdx +++ b/content/docs/references/api/analytics.mdx @@ -56,6 +56,7 @@ const result = AnalyticsEndpoint.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -121,6 +122,7 @@ const result = AnalyticsEndpoint.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -157,6 +159,7 @@ const result = AnalyticsEndpoint.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/auth.mdx b/content/docs/references/api/auth.mdx index 19414b421c..fcd947676f 100644 --- a/content/docs/references/api/auth.mdx +++ b/content/docs/references/api/auth.mdx @@ -129,6 +129,7 @@ const result = AuthProvider.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -186,6 +187,7 @@ const result = AuthProvider.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/automation-api.mdx b/content/docs/references/api/automation-api.mdx index 183ac040e1..0ddae87017 100644 --- a/content/docs/references/api/automation-api.mdx +++ b/content/docs/references/api/automation-api.mdx @@ -192,6 +192,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -258,6 +259,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -321,6 +323,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -388,6 +391,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -448,6 +452,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -498,6 +503,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -559,6 +565,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -609,6 +616,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -693,6 +701,7 @@ const result = AutomationApiErrorCode.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/batch.mdx b/content/docs/references/api/batch.mdx index baebda6792..9044729b36 100644 --- a/content/docs/references/api/batch.mdx +++ b/content/docs/references/api/batch.mdx @@ -77,6 +77,7 @@ const result = BatchConfigSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -187,6 +188,7 @@ A write-path strip event: caller-supplied fields legally dropped from the payloa | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/contract.mdx b/content/docs/references/api/contract.mdx index 79112e1085..105a2d8d69 100644 --- a/content/docs/references/api/contract.mdx +++ b/content/docs/references/api/contract.mdx @@ -31,6 +31,7 @@ const result = ApiErrorSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -392,6 +393,7 @@ const result = ApiErrorSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -445,6 +447,7 @@ const result = ApiErrorSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -511,6 +514,7 @@ const result = ApiErrorSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -630,6 +634,7 @@ const result = ApiErrorSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -669,6 +674,7 @@ const result = ApiErrorSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -743,6 +749,7 @@ Key-value map of record data | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/errors.mdx b/content/docs/references/api/errors.mdx index 1aabfa964f..94ee9cbd38 100644 --- a/content/docs/references/api/errors.mdx +++ b/content/docs/references/api/errors.mdx @@ -43,6 +43,7 @@ const result = EnhancedApiErrorSchema.parse(data); | **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +43 more>` | ✅ | Machine-readable error code | | **message** | `string` | ✅ | Human-readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim — see ApiErrorSchema.userMessage. Present only when the producer opted in at throw time; unmarked errors keep the generic consumer substitution. | +| **refusal** | `true` | optional | Producer-declared deliberate refusal — see ApiErrorSchema.refusal. Present only when the producer declared it at throw time; a declared 5xx without it is a fault whose `message` is withheld. Until the withhold arms read the declaration, a declared refusal is still withheld. | | **category** | `Enum<'validation' \| 'authentication' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'server' \| 'external' \| 'maintenance'>` | optional | Error category | | **httpStatus** | `number` | optional | HTTP status code | | **retryable** | `boolean` | optional (default: `false`) | Whether the request can be retried | @@ -149,7 +150,7 @@ const result = EnhancedApiErrorSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **success** | `false` | ✅ | Always false for error responses | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; message: string; userMessage?: string; category?: Enum<'validation' \| 'authentication' \| 'authorization' \| 'not_found' \| 'conflict' \| …>; … }` | ✅ | Error details | +| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; message: string; userMessage?: string; refusal?: true; … }` | ✅ | Error details | | **meta** | `{ timestamp?: string; requestId?: string; traceId?: string }` | optional | Response metadata | ### Nested Shape: `ErrorResponse.error` @@ -159,6 +160,7 @@ const result = EnhancedApiErrorSchema.parse(data); | **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Machine-readable error code | | **message** | `string` | ✅ | Human-readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim — see ApiErrorSchema.userMessage. Present only when the producer opted in at throw time; unmarked errors keep the generic consumer substitution. | +| **refusal** | `true` | optional | Producer-declared deliberate refusal — see ApiErrorSchema.refusal. Present only when the producer declared it at throw time; a declared 5xx without it is a fault whose `message` is withheld. Until the withhold arms read the declaration, a declared refusal is still withheld. | | **category** | `Enum<'validation' \| 'authentication' \| 'authorization' \| 'not_found' \| 'conflict' \| …>` | optional | Error category | | **httpStatus** | `number` | optional | HTTP status code | | **retryable** | `boolean` | optional (default: `false`) | Whether the request can be retried | diff --git a/content/docs/references/api/export.mdx b/content/docs/references/api/export.mdx index 4d4cc244b5..9803a1a0bc 100644 --- a/content/docs/references/api/export.mdx +++ b/content/docs/references/api/export.mdx @@ -76,6 +76,7 @@ const result = CreateExportJobRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -221,6 +222,7 @@ const result = CreateExportJobRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -325,6 +327,7 @@ const result = CreateExportJobRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -611,6 +614,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -674,6 +678,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -787,6 +792,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index ccc34fe4db..fc08372a6a 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -63,6 +63,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -125,6 +126,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -173,6 +175,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -227,6 +230,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -261,6 +265,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -298,6 +303,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -335,6 +341,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -381,6 +388,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -422,6 +430,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -449,6 +458,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -484,6 +494,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -511,6 +522,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -561,6 +573,7 @@ Metadata query with filtering, sorting, and pagination | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -641,6 +654,7 @@ Metadata query with filtering, sorting, and pagination | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -680,6 +694,7 @@ Metadata query with filtering, sorting, and pagination | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -719,6 +734,7 @@ Metadata query with filtering, sorting, and pagination | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -754,6 +770,7 @@ Metadata query with filtering, sorting, and pagination | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 6de0850dfd..a19f88e447 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -71,6 +71,7 @@ Get installed package response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -133,6 +134,7 @@ List installed packages response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -249,6 +251,7 @@ Install package response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -362,6 +365,7 @@ Upgrade package response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -447,6 +451,7 @@ Resolve dependencies response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -497,6 +502,7 @@ Uninstall package response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -566,6 +572,7 @@ Upload artifact response | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/protocol.mdx b/content/docs/references/api/protocol.mdx index 07ea246918..937726dcab 100644 --- a/content/docs/references/api/protocol.mdx +++ b/content/docs/references/api/protocol.mdx @@ -429,6 +429,7 @@ Canonical cross-paradigm action/node descriptor (ADR-0018) | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -655,6 +656,7 @@ A write-path strip event: caller-supplied fields legally dropped from the payloa | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -2832,6 +2834,7 @@ A write-path strip event: caller-supplied fields legally dropped from the payloa | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/content/docs/references/api/storage.mdx b/content/docs/references/api/storage.mdx index 8e9bdc5f47..11fba345d8 100644 --- a/content/docs/references/api/storage.mdx +++ b/content/docs/references/api/storage.mdx @@ -65,6 +65,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -115,6 +116,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -163,6 +165,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -235,6 +238,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -273,6 +277,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -311,6 +316,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -357,6 +363,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | @@ -392,6 +399,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data); | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | +| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | | **details** | `any` | optional | Additional error context (e.g. field validation errors) | diff --git a/packages/spec/authorable-surface/api.json b/packages/spec/authorable-surface/api.json index 2973d16f3b..9c236c9bf9 100644 --- a/packages/spec/authorable-surface/api.json +++ b/packages/spec/authorable-surface/api.json @@ -136,6 +136,7 @@ "api/ApiError:details", "api/ApiError:httpStatus", "api/ApiError:message", + "api/ApiError:refusal", "api/ApiError:requestId", "api/ApiError:userMessage", "api/ApiMapping:source", @@ -582,6 +583,7 @@ "api/EnhancedApiError:helpText", "api/EnhancedApiError:httpStatus", "api/EnhancedApiError:message", + "api/EnhancedApiError:refusal", "api/EnhancedApiError:requestId", "api/EnhancedApiError:retryAfter [RETIRED]", "api/EnhancedApiError:retryAfterSeconds", diff --git a/packages/spec/src/api/contract.test.ts b/packages/spec/src/api/contract.test.ts index 5a220e3d18..60c1560ad8 100644 --- a/packages/spec/src/api/contract.test.ts +++ b/packages/spec/src/api/contract.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; import { ApiErrorSchema, makeApiErrorSchema, @@ -76,6 +77,89 @@ describe('ApiErrorSchema', () => { userMessage: true, }).success).toBe(false); }); + + // [#16335] The producer-side REFUSAL declaration (director ruling, decision + // batch #58, option C): presence says the 5xx's `message` is authored for + // the caller and boundaries keep it; absence keeps the #5811 withhold — the + // default is a fault. The measured producer is the `/references` door's + // ADR-0110 D3 `501`, which reached the wire as "Internal server error". + const REFERENCES_REFUSAL = + 'References to a `field` item cannot be computed. Ask the owning object instead: ' + + 'GET /api/v1/meta/object/account/references'; + + it('carries a producer-declared `refusal` beside the message it keeps', () => { + const error = ApiErrorSchema.parse({ + code: 'NOT_IMPLEMENTED', + message: REFERENCES_REFUSAL, + httpStatus: 501, + refusal: true, + }); + + expect(error.refusal).toBe(true); + // The declaration rides BESIDE `message`; it never replaces or carries it. + expect(error.message).toBe(REFERENCES_REFUSAL); + }); + + it('a declared 5xx that says nothing parses with the key ABSENT — the default is a fault', () => { + const error = ApiErrorSchema.parse({ + code: 'NOT_IMPLEMENTED', + message: REFERENCES_REFUSAL, + httpStatus: 501, + }); + expect('refusal' in error).toBe(false); + }); + + it('refuses `refusal: false` — presence is the declaration, and a fault has its own spelling', () => { + const result = ApiErrorSchema.safeParse({ + code: 'NOT_IMPLEMENTED', + message: REFERENCES_REFUSAL, + httpStatus: 501, + refusal: false, + }); + expect(result.success).toBe(false); + expect(result.error?.issues[0]?.path).toEqual(['refusal']); + expect(result.error?.issues[0]?.code).toBe('invalid_value'); + }); + + it('refuses a non-boolean declaration — the flag is not a text channel', () => { + const result = ApiErrorSchema.safeParse({ + code: 'NOT_IMPLEMENTED', + message: REFERENCES_REFUSAL, + refusal: 'yes', + }); + expect(result.success).toBe(false); + expect(result.error?.issues[0]?.path).toEqual(['refusal']); + }); + + it('`userMessage` and `refusal` are orthogonal — both ride one envelope, neither replaces `message`', () => { + const error = ApiErrorSchema.parse({ + code: 'SERVICE_UNAVAILABLE', + message: 'service-messaging is not installed on this deployment', + httpStatus: 503, + refusal: true, + userMessage: '此部署未安装消息服务。', + }); + expect(error.refusal).toBe(true); + expect(error.userMessage).toBe('此部署未安装消息服务。'); + expect(error.message).toBe('service-messaging is not installed on this deployment'); + }); + + // [#16335] The value rule as the tarball ships it. `json-schema/api/ApiError.json` + // is built from this schema by `scripts/build-schemas.ts` with + // `z.toJSONSchema(schema, { target: 'draft-2020-12' })`, and `build-docs.ts` + // renders each generated reference row's type column from `prop.const` — so + // the shipped JSON and the checked-in reference rows both carry `true`, and + // `check:docs` (a required check with no paths filter) compares the rows. + // This pin reads the JSON directly, on the generator's own options. + it('ships `refusal` as `{ type: boolean, const: true }` in the JSON Schema the tarball carries', () => { + const json = z.toJSONSchema(ApiErrorSchema, { target: 'draft-2020-12' }) as { + properties?: Record; + }; + expect(json.properties?.refusal).toMatchObject({ type: 'boolean', const: true }); + // Lit control on a neighbour: a plain string key carries no `const`. + expect(json.properties?.userMessage).toMatchObject({ type: 'string' }); + expect(json.properties?.userMessage?.const).toBeUndefined(); + }); }); describe('BaseResponseSchema', () => { @@ -665,6 +749,27 @@ describe('makeApiErrorSchema (federated ledger, #4805)', () => { expect(parsed.httpStatus).toBe(402); expect(parsed.requestId).toBe('req_1'); }); + + // [#16335] The docblock's promise, pinned: "a field added to the base + // envelope reaches every downstream ledger with it" — the refusal + // declaration parses through the factory with the base's own value rule. + it('a field added to the base envelope reaches the downstream ledger with it', () => { + const parsed = DownstreamApiError.parse({ + code: 'CONTACT_SALES_PLAN', + message: 'Upgrade required', + httpStatus: 501, + refusal: true, + }); + expect(parsed.refusal).toBe(true); + + const rejected = DownstreamApiError.safeParse({ + code: 'CONTACT_SALES_PLAN', + message: 'Upgrade required', + refusal: false, + }); + expect(rejected.success).toBe(false); + expect(rejected.error?.issues[0]?.path).toEqual(['refusal']); + }); }); // #15677 (stack card 2/6 of #14478) — ruling B: the unit of a duration-shaped diff --git a/packages/spec/src/api/contract.zod.ts b/packages/spec/src/api/contract.zod.ts index 24c524804a..a7fcb0510e 100644 --- a/packages/spec/src/api/contract.zod.ts +++ b/packages/spec/src/api/contract.zod.ts @@ -90,6 +90,139 @@ export const ApiErrorSchema = lazySchema(() => z.object({ + 'producer opted in at throw time; consumers render it to end users and keep their ' + 'generic substitution for anything unmarked. Status-agnostic; never replaces `message`.', ), + /** + * The producer's declaration that the 5xx it named is a deliberate REFUSAL + * whose `message` is authored for the caller — so the boundary keeps that + * message verbatim instead of withholding it (#16335; director ruling, + * decision batch #58, 2026-09-06, option C: the refusal/fault distinction + * is a producer-side declaration on this envelope, not a status heuristic + * and not a second allow-list). + * + * ## The three cases, side by side + * + * The 5xx message discipline (#5811 / #5667 / #5437) has one rule per + * case. This field adds the third row; the first two are unchanged, and + * the second is still the DEFAULT: + * + * | the throw declares | `message` on the wire | + * |---|---| + * | **undeclared 5xx** — no `status`; the boundary's fallback 500 | HEURISTIC: `looksLikeInternalErrorLeak` (`@objectstack/types`) decides per message — our own bare `Error` stays legible (#5667), driver prose is withheld | + * | **declared fault** — `status >= 500`, with or without a `code`, nothing here | WITHHELD at every arm that reads the declaration (three, named below): `INTERNAL_ERROR_MESSAGE` in the body, `code` survives when declared, the full text reaches the operator's log (`logWithheldServerFault`, #5811) | + * | **declared refusal** — `status >= 500`, a `code`, and `refusal: true` | KEPT verbatim, bounded exactly as a 4xx message is (#5423), and not logged as an unhandled fault — once the three arms below read the field | + * + * A 4xx is addressed to the caller already, so the field is redundant on + * it and boundaries ignore it there. A throw that declares no `status` + * qualifies nothing — the field qualifies a DECLARED status, it never + * invents one — so the heuristic row runs. + * + * ## Semantics + * + * - **Producer-side, at throw time.** The producer that composes a 5xx + * refusal for its caller sets `refusal: true` on the thrown error beside + * `status` and `code`. The measured case is the + * `/meta/:type/:name/references` door's ADR-0110 D3 `501` ("Ask the + * owning object instead: …"), which reached the wire as + * `"Internal server error"` until the route-local patch in PR #16143, + * which #16146 retires. Platform and driver code never sets it on a + * fault. + * - **Presence IS the declaration.** `true` is the only value. A fault has + * its own spelling already (`status` + `code`, nothing here), so + * `refusal: false` fails parse rather than becoming a third state every + * consumer would have to interpret. + * - **Read once, at the withhold arms — THREE, and not all in one + * package.** Measured on the tree at #16335, the arms that withhold a + * declared 5xx's prose BECAUSE it was declared are: + * 1. `@objectstack/rest` `declaredServerFaultAnswer` + * (`error-response.ts`; its only two callers are the `/data` + * classifier, #11718, which wraps it in `withDeclaredUserMessage`, + * and the analytics dataset door, which calls it bare — so + * `userMessage` rides the first and not the second); + * 2. `@objectstack/rest` `resolveErrorResponse`'s own 5xx passthrough + * arm, reached through `handleRouteError` / `sendThrownError`. The + * `/references` throw spells `status`, so it takes that function's + * status passthrough into this arm; a `statusCode`-spelled 5xx + * falls to `mapDataError` and arm 1 instead. Arms 1 and 2 compose + * the same bytes; + * 3. `@objectstack/runtime` `errorResponseBase` + * (`dispatcher-plugin.ts`), the kernel dispatcher plugin's thrown + * exit, gated on `serverFaultProvenance(thrown) === 'declared'` + * (`@objectstack/types`): any 5xx with a declared `status` or + * `statusCode`, `code` or not. `objectstack serve` mounts it + * (`createDispatcherPlugin`), it answers `POST /analytics/query` + * among the dispatcher's routes, and it emits `ErrorResponseSchema` + * — the envelope `EnhancedApiErrorSchema` describes. It never + * consults arm 1, so moving the REST pair leaves it withholding. + * Every other 5xx door on the tree (`HttpDispatcher`'s returned exit, + * `endpointErrorAnswer`, `package-routes`' `sendThrownError`, the hono + * auth door) reads no declaration and withholds by the leak heuristic + * alone — the second row of the table above is the rule at these three + * arms, not a platform-wide invariant. Each of the three keeps `message` + * when the field is present and withholds it otherwise; the relay half + * must move ALL THREE — #16146 for the REST pair, its sub-issue #17153 + * for the runtime exit. Until they land, a declared refusal is still + * withheld at the wire, and this key is the contract they land against. + * + * ## Why a flag beside `message` is the right shape HERE, when + * ## `userMessage` above refused exactly that shape + * + * `userMessage` is a text-carrying field rather than a boolean because its + * consumer is the END-USER renderer — across the wire and every boundary + * in between — and #3821's protection (generic substitution unless marked) + * has to hold against a boundary that rewraps or substitutes `message` on + * the way: a mark separated from its text could be promoted onto platform + * prose. This field marks something else, and that argument does not + * transfer: + * + * - **It qualifies the STATUS declaration, not a text.** It says "the + * 5xx I declared is a refusal", the way `code` already qualifies + * `status` for `declaresServerFault`. Its only consumers are the three + * withhold arms named above, each of which reads `status`, `code` and + * this flag off the SAME thrown object in ONE read, before it composes a + * body — the mark and the message it releases are never apart. There is no second channel to + * promote prose into: the flag only switches the withhold off, and what + * then reaches the wire is the same `message` a declared 4xx already + * discloses. + * - **A rewrap is fail-closed.** A boundary that rewraps `message` into a + * NEW error drops the flag with it, and the 5xx is withheld as a fault. + * The one rewrap that could carry the flag is one that also copies + * `status` onto the rewrapped text — and a copied 4xx `status` already + * discloses that text today, so this field adds no exposure a declared + * status does not have. Measured on the tree at #16335: the QuickJS door + * carries a CLOSED list of fields out of the VM + * (`SANDBOX_ERROR_PASSTHROUGH`, `quickjs-runner.ts`) and this field is + * not on it, so a sandboxed body's flag never leaves the VM and its 5xx + * stays withheld; of the `Object.assign` error-composition sites under + * `packages/**` (non-test), the ones that copy anything off a caught + * error copy only `code` and/or `cause` (`drivers/driver-sql/src/sql-driver.ts`, + * `objectql/src/engine.ts`'s autonumber-collision rewrap) and never + * `status`, so the withhold still applies to them; the two overlay-delete + * rewraps in `metadata-protocol/src/protocol.ts` DO copy `status` (with + * a 500 fallback) onto a rewritten `message` and carry `code` and + * `userMessage` through `carryCatalogedErrorCode` / + * `carryDeclaredUserMessage` — nothing carries this flag, so a refusal + * crossing them is withheld as a fault, and ⛔ the relay must not add a + * `carryRefusal` there: that would put `overlayDeleteFailureMessage`'s + * platform prose on the flag channel, the promotion the `userMessage` + * note above refused; + * and the one in-place rewrite of `message` on an error that keeps its + * `status` and `code` (`runtime/src/domains/actions.ts`, installing the + * sandbox `innerMessage`) is on a `SandboxError`, which cannot carry + * the flag. + * - **`userMessage` is orthogonal, not a fourth row.** Its audience is the + * end user, it never replaces `message`, and it already rides a withheld + * 5xx at the `/data` and passthrough arms (`withDeclaredUserMessage`; + * the analytics dataset door calls arm 1 bare). A producer may set both — + * `userMessage` for the console, `refusal` to keep `message` for the + * caller — and neither read consults the other. + */ + refusal: z.literal(true).optional().describe( + 'Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is ' + + 'authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the ' + + 'withhold arms read it, a declared refusal is still withheld). Absent (the default) on a ' + + 'declared fault, whose ' + + '`message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence ' + + 'is the declaration — `true` is the only value.', + ), category: z.string().optional().describe('Error category (e.g. validation, authorization)'), /** * The numeric HTTP status, when a producer chooses to mirror it into the body. diff --git a/packages/spec/src/api/errors.test.ts b/packages/spec/src/api/errors.test.ts index 60b6af343c..155846e720 100644 --- a/packages/spec/src/api/errors.test.ts +++ b/packages/spec/src/api/errors.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; import { ErrorCategory, StandardErrorCode, @@ -161,6 +162,48 @@ describe('EnhancedApiErrorSchema', () => { expect('userMessage' in unmarked).toBe(false); }); + // [#16335] Same field, same semantics as `ApiErrorSchema.refusal` — the + // producer-side refusal declaration of decision batch #58 (option C). + it('carries a producer-declared `refusal`, stays absent when undeclared, and refuses `false`', () => { + const declared = EnhancedApiErrorSchema.parse({ + code: 'NOT_IMPLEMENTED', + message: 'References to a `field` item cannot be computed. Ask the owning object instead.', + httpStatus: 501, + refusal: true, + }); + expect(declared.refusal).toBe(true); + expect(declared.message).toBe('References to a `field` item cannot be computed. Ask the owning object instead.'); + + const undeclared = EnhancedApiErrorSchema.parse({ + code: 'NOT_IMPLEMENTED', + message: 'withheld as a fault', + httpStatus: 501, + }); + expect('refusal' in undeclared).toBe(false); + + const rejected = EnhancedApiErrorSchema.safeParse({ + code: 'NOT_IMPLEMENTED', + message: 'x', + httpStatus: 501, + refusal: false, + }); + expect(rejected.success).toBe(false); + expect(rejected.error?.issues[0]?.path).toEqual(['refusal']); + }); + + // [#16335] Same pin as `ApiErrorSchema`'s: the shipped + // `json-schema/api/EnhancedApiError.json` carries the value rule as + // `const: true`, on the generator's own `toJSONSchema` options. + it('ships `refusal` as `{ type: boolean, const: true }` in the JSON Schema the tarball carries', () => { + const json = z.toJSONSchema(EnhancedApiErrorSchema, { target: 'draft-2020-12' }) as { + properties?: Record; + }; + expect(json.properties?.refusal).toMatchObject({ type: 'boolean', const: true }); + // Lit control on a neighbour: `retryable` is a plain boolean, no `const`. + expect(json.properties?.retryable).toMatchObject({ type: 'boolean' }); + expect(json.properties?.retryable?.const).toBeUndefined(); + }); + it('should accept rate limit error with retry info', () => { const error = EnhancedApiErrorSchema.parse({ code: 'RATE_LIMIT_EXCEEDED', diff --git a/packages/spec/src/api/errors.zod.ts b/packages/spec/src/api/errors.zod.ts index 32af58595d..3c0dcdec28 100644 --- a/packages/spec/src/api/errors.zod.ts +++ b/packages/spec/src/api/errors.zod.ts @@ -387,6 +387,23 @@ export const EnhancedApiErrorSchema = lazySchema(() => z.object({ + 'Present only when the producer opted in at throw time; unmarked errors keep the generic ' + 'consumer substitution.', ), + /** + * The producer's refusal declaration — the same field, with the same + * semantics and the same three-case table, as `ApiErrorSchema.refusal` + * (`contract.zod.ts`, which carries the full rationale and the + * reconciliation with `userMessage`'s "not a boolean beside `message`" + * note): present exactly when the producer declared, at throw time, that + * the 5xx it named is a deliberate refusal whose `message` is authored for + * the caller, so the boundary keeps it verbatim (#16335; director ruling, + * decision batch #58, option C). Absent means the default — a declared 5xx + * is a fault and its `message` is withheld. `true` is the only value. + */ + refusal: z.literal(true).optional().describe( + 'Producer-declared deliberate refusal — see ApiErrorSchema.refusal. Present only when the ' + + 'producer declared it at throw time; a declared 5xx without it is a fault whose `message` ' + + 'is withheld. Until the withhold arms read the declaration, a declared refusal is still ' + + 'withheld.', + ), category: ErrorCategory.optional().describe('Error category'), httpStatus: z.number().optional().describe('HTTP status code'), retryable: z.boolean().default(false).describe('Whether the request can be retried'),