Skip to main content

Error contract

Structured operation errors use {"error": {"code": "...", "message": "..."}}. The generic code is shared across plugins. A plugin chooses the closest generic classification and its own domain_code; Arbiter does not guess from exception messages. Classified plugin errors also identify plugin and may include deliberately selected details.

Generic classifications​

HTTP status is a coarse signal. Several error codes can share a status; callers should inspect the JSON code for precise handling.

CodeHTTP statusMeaning
validation_error400, 413, or 422Invalid request syntax, oversized operation request body, or invalid operation arguments.
not_found404Requested operation or resource does not exist.
policy_denied403Configured policy or a core artifact size limit prohibits the operation.
rate_limited429Arbiter's configured operation rate limit was reached.
resource_exhausted503Server artifact storage has insufficient remaining capacity; retry later when replay is safe. An artifact too large for an empty store is instead policy_denied with transient: no.
conflict409Request conflicts with current state, including an idempotency reservation.
upstream_auth_failed500Configured service credentials were rejected; this is not caller authentication to Arbiter.
upstream_timeout504A required upstream service did not respond in time.
upstream_unavailable503An upstream service is temporarily unavailable.
upstream_rejected422Upstream service rejected the requested operation.
upstream_protocol_error502Upstream response could not be interpreted correctly.
partial_failure500Some operation steps completed before another required step failed.
plugin_error500Expected plugin-domain failure without a closer generic match.
internal_error500Unexpected implementation failure; public diagnostics are withheld.

Existing artifact_not_found (404), artifact_expired (410), and artifact_consumed (410) codes retain their meanings.

Retry guidance and partial outcomes​

Structured plugin errors include retry.transient: yes, no, or unknown, and retry.replay: safe, unsafe, unknown, or same_idempotency_key. Transience does not imply safe replay. same_idempotency_key requires the same key and payload while the original idempotency record remains valid. There are no automatic operation retries.

For example, SMTP submission can succeed before a required IMAP Sent-copy fails:

{
"error": {
"code": "partial_failure",
"plugin": "smtp",
"domain_code": "sent_copy_failed",
"message": "SMTP submission succeeded but saving the Sent copy failed.",
"details": {
"steps": {
"smtp_submission": "succeeded",
"sent_copy": "failed"
}
},
"retry": {"transient": "unknown", "replay": "same_idempotency_key"}
}
}

The existing SMTP sent_copy.on_failure policy remains authoritative: warn returns a successful result containing the Sent-copy outcome; fail returns an error with explicit completed and failed steps. Without a usable idempotency key, resubmitting after confirmed submission is unsafe. An upstream timeout during a write can leave the outcome unknown. An uncertain Sent-copy append is reported as unknown; replaying a cached SMTP result does not repeat that append. Inspect the destination before attempting recovery, since the copy may already exist.

CLI and diagnostics​

CLI exit statuses are 0 for success, 1 for operation or transport failure, and 2 for CLI usage errors. Explicit --json or --yaml selects structured errors on stderr; stdout is reserved for successful results. Server error fields are preserved, including plugin codes, details, and retry guidance. Local transport and usage failures use the same envelope without claiming to know a plugin operation's outcome. For artifact commands, these flags select errors only; successful artifact bytes and child-command output keep their existing form. Flags after -- belong to the child. Artifact commands reject a conflicting --json and --yaml pair before configuration resolution, network access, file creation, or child execution; repeating the same format flag follows the existing format parser rule.

The config url and bootstrap client commands accept --json or --yaml after their subcommand to format errors on stderr. Their successful plain-text output and bootstrap config writes are unchanged.

Plugins raise arbiter_server.errors.OperationError with public-safe messages and JSON-compatible details. They own the classification and must not include credentials, message contents, raw upstream text, or exception strings.

Unexpected exceptions during plugin operation execution become internal_error with the plugin identity and unknown retry guidance. Arbiter does not invent a plugin-domain code for an unexpected implementation failure. The operation boundary logs controlled exception types and stack locations while omitting exception text, source lines, and local values. This boundary applies to operation execution; configuration/account checks and successful results keep their existing output formats.