Status: Stable.
Why this exists
Every error a v2 host returns is a row in one registry. A client routes on error, never on message. A code that is not registered is not a protocol error.
The registry
spec/v2/errors.json holds one row per code, defined by spec/v2/errors.schema.json. It registers 124 codes. schemas/v2/error-envelope.schema.json is GENERATED from it and MUST NOT be edited by hand.
- A host MUST emit a registered code, or a vendor code, wherever it emits an error code: the
errorof every error response, anderror.codeonrun.failed,node.failedand the snapshot'serror(overview.md §0). A recorded event re-emitted by replay or:forkis carried as recorded (replay.md). - A vendor code MUST match
^(?!openwop\.)[a-z][a-z0-9](-[a-z0-9]+)\.[a-z][a-z0-9_]*$, with its first segment an org registered inspec/v2/declaration.json.openwop.is reserved. - The registry grows by overview.md §0.
The envelope
Every error response body MUST be { error, message, details? } and nothing else (additionalProperties: false).
erroris the registered code or a vendor code.messageis a non-empty string.detailsis an object shaped by the row'sdetailsschema. A row whosedetailsisnullaccepts any object. When a row registers a schema, the generated envelope applies it todetailsonly whenerrornames that code.- Contextual data (conflict refs, trace ids, validation paths) MUST live under
details, never at a new top level. - When present,
details.correlationIdMUST be a non-empty string.
The same envelope is the per-id error of bulkCancelRuns (runs.md). x-openwop-http-status and x-openwop-retriable in the generated schema mirror the registry; a host MUST answer with the registered status.
Retry timing
Retry timing lives in the Retry-After header only.
- A host MUST NOT emit
details.retryAfter,details.retryAfterMsordetails.retryAfterSeconds. - A
429 rate_limitedresponse MUST setRetry-After.
The retriable rows are residency_unavailable, rate_limited, internal_error, pack_registry_unreachable, runner_unavailable, service_unavailable, upstream_unavailable.
One code per state
An interrupt has one code per state (interrupt.md, identity.md):
- A token or run-scoped resolve against an interrupt that is already resolved, or whose run is cancelled or completed, MUST return
409 interrupt_already_resolved. - A signed token past its
expiresAtMUST return410 interrupt_expired. - A token whose
algorkidthe host does not accept MUST return401 interrupt_token_invalid. interrupt_cancelledis registered but names no state of the core resolve surfaces. A host MUST NOT emit it fromresolveInterruptByRun,inspectInterruptByTokenorresolveInterruptByToken.
The idempotency mismatch code is idempotency_key_mismatch only (idempotency.md).
Host-service refusals
A ctx.* call that rejects MUST use a registered or vendor code. A host MAY carry an uncaught rejection unchanged as the node.failed code.
- A generic code (
not_found,forbidden,validation_error,rate_limited,credential_not_found,credential_forbidden) MUST carrydetails.service, the family key. details.reasonMAY name a finer cause in lower-kebab. A client MUST NOT route on it.- A vendor code MUST NOT stand for a state a registered code names.
Unadvertised operations
An operation gated on a family or facet the host does not advertise MUST answer 404 not_found.
Codes by HTTP status
Every registered code, by HTTP status, is listed in error-codes.md, generated from spec/v2/errors.json (124 codes).
Sources: RFCs 0171, 0213, 0227, 0228.