cadrumo.core.errors.error_codes module

Structured error-code registry and CLI rendering helpers.

Centralises Cadrumo’s stable CLI error taxonomy. Every core.errors.hierarchy.CadrumoError subclass binds to a predeclared ErrorCode row through bind_error_code(), so the public contract stays explicit, reviewable, and grep-stable. Rendering helpers render_error_text() and render_error_json() produce the human-readable and machine-readable stderr payloads that downstream tools consume; build_error_envelope() constructs the underlying ErrorEnvelope.

Secret-looking context keys (matching _SECRET_FIELD_PATTERN) are redacted before they ever reach stderr — see scrub_error_context(). Non-secret context values are also passed through core.redaction.rules.redact_for_log() so NIF, URL, and bearer-token shapes share the same rule vocabulary as logs and observability.

ERROR_CONTEXT_LABEL_KEYS: Final[tuple[str, ...]]

Finite vocabulary translated for human-facing error context labels.

Error contexts themselves remain extensible structured data. Only this declared operator-facing label subset is sent through the locale catalogue; unknown context keys use the same human-readable fallback shape without constructing an unregistered translation key.

class ErrorCategory(*values)[source]

Bases: StrEnum

Closed catalogue of stable CLI error categories.

ERROR
REFUSED
AUTH
INTEGRITY
FAIL
INTERNAL
LOCKED
class ErrorCode(**data)[source]

Bases: BaseModel

Stable metadata attached to an core.errors.hierarchy.CadrumoError type.

Parameters:
code: str
category: ErrorCategory
message_key: str
retryable: bool

Whether repeating the IDENTICAL call may succeed with nothing else changed.

True means time alone, or another party finishing, can make the same request work: a held lock, a network failure, a rate limit, a compare-and-swap conflict, a login throttle. False means the call cannot succeed until something else changes – a different argument, an operator action, a state that only another command can create.

This is not decoration. A caller of this CLI may be non-interactive, and this field is the instruction it acts on, so “retryable” on a permanently-failing call is an invitation to loop with no terminating condition. The field carried no stated meaning for a long time, and a refusal for a profile label already bound to a committed capsule inherited True from a sibling condition that genuinely was a stale-witness conflict; the identical restore could never have succeeded.

“Retryable after the operator fixes something” is FALSE by this definition. The agent cannot distinguish it from the transient case, and telling it to retry is what produces the loop.

public_message_from_registry: bool

Ignore positional diagnostic text when rendering a public envelope.

runbook_id: str | None
class ErrorEnvelope(**data)[source]

Bases: BaseModel

Machine-readable error body nested under the shared envelope spine.

Rendered as the error member of the stderr error document. The document-level spine (schema_version, command, status, notices) is added by render_error_json() so the error document and the success core.json_contract.SchemaEnvelope share one outer shape.

Parameters:
code: str
category: str
message: str
action: ResolvedPreconditionAction | None
retryable: bool
runbook_id: str | None
context: dict[str, str] | None
trace_id: str | None
complete_error_envelope_model()[source]

Resolve the canonical action DTO into the envelope model.

ErrorEnvelope names ResolvedPreconditionAction, which lives in cadrumo.core.json_contract. This module cannot import that at definition time: the exception hierarchy binds its codes here while it is still being defined, so reaching for json_contract from module scope re-enters a half-built hierarchy.

json_contract calls this at the bottom of its own module, once both halves exist. build_error_envelope() calls it too, so a caller that never touches json_contract still gets a complete model; it is idempotent and returns immediately when already complete.

Return type:

None

register(code)[source]

Register code in the global catalogue.

Parameters:

code (ErrorCode) – The ErrorCode record to add.

Return type:

ErrorCode

Returns:

The same ErrorCode object for fluent use at declaration sites.

Raises:

InternalInvariantError – If a duplicate code identifier is encountered.

declared_error_codes_by_qualname()[source]

Return the declared-code catalogue, loading and registering it on first use.

Return type:

Mapping[str, ErrorCode]

get_registered_error_code_by_code(code)[source]

Resolve one stable code through the sole declared ErrorCode authority.

Return type:

ErrorCode

Parameters:

code (str)

bind_error_code(error_type)[source]

Bind a stable ErrorCode to error_type.

Called from CadrumoError.__init_subclass__ at class-creation time. Until the declared-code catalogue has been loaded, including while it is loading, the class is added to _DEFERRED_BIND and bound on first use via get_registered_error_code(); once it is loaded, an undeclared class is refused at creation.

Parameters:

error_type (type[BaseException]) – Error class being declared.

Return type:

ErrorCode | None

Returns:

The registered ErrorCode for error_type.

Raises:

InternalInvariantError – When the mapping is available but contains no entry for this class.

get_registered_error_code(error)[source]

Return the registered ErrorCode for error.

Loads the declared-code catalogue and drains the deferred binds before the lookup, so classes created before the catalogue loaded are bound here on first use.

Return type:

ErrorCode

Parameters:

error (BaseException | type[BaseException])

scrub_error_context(context)[source]

Redact secret-looking keys and strip internal keys from context.

Keys matching _SECRET_FIELD_PATTERN are replaced with "<redacted>". Keys in _INTERNAL_CONTEXT_KEYS are dropped entirely — they are implementation detail (e.g. widget prompt identifiers) and must not appear in operator-facing output.

Return type:

dict[str, str] | None

Parameters:

context (Mapping[str, object] | None)

build_error_envelope(error, *, action=None, context=None, trace_id=None)[source]

Build the deterministic JSON stderr envelope for error.

Return type:

ErrorEnvelope

Returns:

A frozen ErrorEnvelope suitable for serialisation to the machine-readable stderr payload.

Parameters:
render_error_text(error, *, context=None)[source]

Render the human-readable stderr payload for error.

Return type:

str

Parameters:
render_error_json(error, *, action=None, context=None, trace_id=None, active_profile=None, command=None, notices=())[source]

Serialize error to a deterministic single-line JSON document.

The document carries the shared envelope spine (schema_version, command, active_profile, status, notices) so it is shape-compatible with the success core.json_contract.SchemaEnvelope. The error detail is nested under error. command is the dotted identifier of the command that failed (byte-identical to the command= its success envelope would emit), resolved by the CLI boundary from the live click context and passed here; it is None only when no command has resolved yet — an argv parse failure before dispatch, or a direct render without a command. The core layer never touches the click context, so the CLI boundary resolves the identifier and passes it. active_profile is the human label of the active taxpayer profile (the identity anchor), None for a non-profile-bound failure or when the CLI error boundary cannot resolve it; the core layer never scans profile manifests, so the CLI boundary resolves the label and passes it here. notices carries the same typed diagnostics the success envelope’s notices channel does — the sandbox-active indicator above all, so a failure inside a discardable sandbox bucket is distinguishable from the same failure against the operator’s real profile. core never resolves them for the same reason it never resolves active_profile: it may not scan buckets. status stays EnvelopeStatus.ERROR regardless of notice severity — this document reports a failure, and no notice can soften that. The core.json_contract.ENVELOPE_SCHEMA_VERSION import is function-local to avoid the json_contract <-> errors import cycle (json_contract imports CadrumoError).

Return type:

str

Parameters:
get_error_exit_code(category)[source]

Return the canonical process exit code for category.

The exit-code family is the operator’s coarse outcome signal:

  • ERROR -> 1: an expected, operator-actionable failure or refusal of a domain outcome. A modelo verification that resolves not-granted — whether BLOCKED (a blocking-rule finding) or INCOMPLETE (missing required casillas) — exits 1: both are expected verification verdicts, surfaced through typer.Exit(code=1) in the verify handler, never as an INTERNAL crash.

  • REFUSED -> 2, AUTH -> 3, INTEGRITY -> 4, FAIL -> 5, LOCKED -> 7: the remaining expected, registered refusal classes.

  • INTERNAL -> 6 is reserved exclusively for an unexpected internal crash (the INTERNAL_* registry codes: INTERNAL_CLI_UNEXPECTED_BOUNDARY, INTERNAL_WORKFLOW_UNHANDLED, etc.). An expected domain outcome MUST NOT map to INTERNAL — a not-granted verification verdict is a result the operator must act on, not a program defect, so it never exits 6.

Return type:

int

Parameters:

category (ErrorCategory)

resolve_error_message(error, code=None, *, locale=None)[source]

Resolve the user-facing message for error.

translated_message is a translation key (e.g. "profile.errors.not_configured") by convention; it is rendered through the i18n backend, which falls back to the key itself when no matching translation exists.

Return type:

str

Parameters: