orchestrata_ultimate.gateway.error_taxonomy

Reference › orchestrata_ultimate › gateway › error_taxonomy

error_taxonomy defines the error schema of SPEC-098 FR-006 and FR-007 for the gateway's JSON-RPC errors; server is its importer. ErrorCategory is a string enum with six values that say what kind of failure occurred: AUTH (not authenticated), AUTHZ (authenticated but not permitted), VAL (malformed or invalid request), RES (resource missing), RATE (throttled) and SYS (server failure). The category complements the numeric JSON-RPC code, which names the protocol rule that was broken. category_for maps a code to its default category and returns SYS for any code outside the reserved table. error_data builds the error.data member: a category, any members listed as top-level lifted out of the details, and a details object that is always present, even if empty, so clients never test for the key. See exceptions and gateway.

At a glance

Source src/orchestrata_ultimate/gateway/error_taxonomy.py
Lines of code 106
Classes 1
Functions 2
Imports (internal) none
Imported by orchestrata_ultimate.gateway.server
Tests that import it tests/gateway/test_spec098_api_standards.py, tests/generated_gateway/test_error_taxonomy_generated.py

Class diagram

Class diagram orchestrata_ultimate.gateway.error_taxonomy

Dependency diagram

Dependency diagram orchestrata_ultimate.gateway.error_taxonomy

API

Constants

Name Type Value
_DEFAULT_CATEGORY dict[int, ErrorCategory] {JSONRPC_PARSE_ERROR: ErrorCategory.VAL, JSONRPC_INVALID_REQUEST: ErrorCategory…
_TOP_LEVEL_MEMBERS frozenset[str] frozenset({'correlation_id'})

Classes

ErrorCategory (enum)

Bases: StrEnum · Line 32

The SPEC-098 FR-007 categories, verbatim.

Member Value
AUTH 'AUTH'
AUTHZ 'AUTHZ'
VAL 'VAL'
RES 'RES'
RATE 'RATE'
SYS 'SYS'

Functions

Function Signature Summary
category_for category_for(code: int) -> ErrorCategory Category for a JSON-RPC code.
error_data error_data(code: int, *, category: ErrorCategory | None = None, details: dict[str, Any] | None = None) -> dict[str, Any] Build the JSON-RPC error.data member SPEC-098 FR-006/007 require.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/gateway/error_taxonomy.py

"""SPEC-098 FR-006/FR-007 — the gateway's error schema and code categories.

SPEC-098 specifies a REST API this gateway does not have: its contracts section
is ``GET/POST /api/v{version}/resources``, while the gateway is JSON-RPC over
WebSocket with three HTTP endpoints. Mohamed's ruling (2026-08-09) applies the
FRs that have a structural home here and marks the REST-only ones N/A in the
spec itself.

FR-006 (every error response carries code, message, details) and FR-007 (codes
are categorized AUTH, AUTHZ, VAL, RES, RATE, SYS) DO have a home: JSON-RPC 2.0
error objects already carry ``code`` and ``message`` and permit a ``data``
member. This module supplies the taxonomy that goes in ``data``, so a client
can branch on the CLASS of failure without pattern-matching prose.

The JSON-RPC numeric codes stay exactly as they are — they are protocol-level
and clients already switch on them. The category is additive.
"""

from __future__ import annotations

from enum import StrEnum
from typing import Any

# JSON-RPC 2.0 reserved codes (https://www.jsonrpc.org/specification §5.1).
JSONRPC_PARSE_ERROR = -32700
JSONRPC_INVALID_REQUEST = -32600
JSONRPC_METHOD_NOT_FOUND = -32601
JSONRPC_INVALID_PARAMS = -32602
JSONRPC_INTERNAL_ERROR = -32603


class ErrorCategory(StrEnum):
    """The SPEC-098 FR-007 categories, verbatim.

    A category answers "what KIND of thing went wrong" — which is what a
    client needs to decide whether to re-authenticate, fix its request, back
    off, or give up. The numeric JSON-RPC code answers "which protocol rule
    was broken", which is a different question and often too coarse: an
    expired token and a malformed parameter are both ``-32600`` shaped
    failures to a naive handler, and a client must treat them differently.
    """

    AUTH = "AUTH"  # not authenticated — missing/expired/invalid credentials
    AUTHZ = "AUTHZ"  # authenticated, but not permitted
    VAL = "VAL"  # the request itself is malformed or fails validation
    RES = "RES"  # the addressed resource does not exist / is gone
    RATE = "RATE"  # throttled — retry later
    SYS = "SYS"  # the server failed; nothing the client did


# Default category for each reserved JSON-RPC code. A caller may override, and
# should whenever it knows better: -32600 covers both "your JSON is not a
# request object" (VAL) and "you are not authenticated" (AUTH) in practice.
_DEFAULT_CATEGORY: dict[int, ErrorCategory] = {
    JSONRPC_PARSE_ERROR: ErrorCategory.VAL,
    JSONRPC_INVALID_REQUEST: ErrorCategory.VAL,
    JSONRPC_METHOD_NOT_FOUND: ErrorCategory.RES,
    JSONRPC_INVALID_PARAMS: ErrorCategory.VAL,
    JSONRPC_INTERNAL_ERROR: ErrorCategory.SYS,
}


def category_for(code: int) -> ErrorCategory:
    """Category for a JSON-RPC *code*.

    Anything outside the reserved range is application-defined, and an
    application error the server chose to raise is a server-side condition
    from the client's point of view — so SYS, not VAL. A caller that knows
    the specific category passes it explicitly.
    """
    return _DEFAULT_CATEGORY.get(code, ErrorCategory.SYS)


# Members that stay at the ``data`` level rather than being folded into
# ``details``. ``details`` answers "what specifically was wrong with THIS
# request" — the field that failed validation, the id that was not found.
# Tracing metadata is about the request, not about the failure, which is why
# FR-017 carries the HTTP equivalent as a header and not a body field. Burying
# it would also break the ``error.data.correlation_id`` contract the gateway's
# clients already read.
_TOP_LEVEL_MEMBERS: frozenset[str] = frozenset({"correlation_id"})


def error_data(
    code: int,
    *,
    category: ErrorCategory | None = None,
    details: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Build the JSON-RPC ``error.data`` member SPEC-098 FR-006/007 require.

    ``details`` is always present, as an object, even when empty: a client
    that reads ``error.data.details`` must not have to branch on whether the
    key exists. ``category`` is always one of the six FR-007 values. Members
    named in ``_TOP_LEVEL_MEMBERS`` are lifted out of *details* and kept
    beside ``category``.
    """
    supplied = dict(details or {})
    payload: dict[str, Any] = {
        "category": str(category or category_for(code)),
    }
    for member in _TOP_LEVEL_MEMBERS:
        if member in supplied:
            payload[member] = supplied.pop(member)
    payload["details"] = supplied
    return payload