Failure semantics

Nothing in Belora stalls silently. Every failure has a code and every wait has a limit; this page lists the ones a client can observe.

Errors and what to do

There are 19 error codes, all in one envelope; the errors reference lists which operations can answer each. Match on code, never on message. A mutating call is safe to retry with the same Idempotency-Key for 1 day; a retry while the first is still running answers 409 until the first finishes, or 1 minute pass and it is presumed crashed.

CodeStatusThe client
unauthorized401Stop. The key is missing, malformed, unknown, revoked or expired, or its person is gone. Mint a new key.
forbidden403Stop. A conformance key called an operation outside its fence, or a production key called a conformance-only one. Do not retry.
not_found404Stop. The route or the id does not exist in your org. An id from another org looks the same.
validation_failed400Fix the request. Each entry of issues names a path under params, query or body. Do not retry unchanged.
conflict409Read details.reason or the message. Retry only a conflict that says it is in flight, with the same Idempotency-Key.
rate_limited429Wait for the Retry-After header, then retry the same request.
internal500Retry with the same Idempotency-Key. If it persists, report the request.
person_not_found404The person named when minting a key does not exist or is inactive. Choose an active person.
person_not_verified403The person has no verified login link. Verify it, then mint again.
person_mismatch403A person key can be minted only by the person it is for.
key_not_found404The key id does not exist in your org.
key_lacks_capability403Use a key whose tier and role hold the capability named in details. A service key can never hold a judgment capability.
key_cannot_commit_judgment403Use a person key. A service key never commits a verdict, proposal or claim.
answerer_not_a_person403Only a person can answer. Use a person key.
answerer_unlinked403The answerer has no Belora identity in this org. Link the identity.
answerer_not_verified403The answerer's identity is not verified. Verify it, then answer.
answerer_lacks_capability403The answerer's role lacks the capability the decision needs; details names it. Ask someone who holds it.
not_a_judge403The person holds no judge seat, so their key cannot commit a verdict. Ask an owner for a judge seat or use a judge's key.
decision_not_found404No such decision in your org.

Proved by errors.envelope

Expiries and escalation

Waiting states are swept by one registry that ticks every 6 hours, so most steps land within a tick of their threshold; a lease and a quick bet also have a timer of their own. Most decays are recorded as a decay event; the few that only bound bookkeeping are not. An unanswered decision is nudged, then re-routed, and is never silently dropped.

PolicyAfterWhat happens
hypotheses.expire30 days after its newest evidenceAn open or corroborated hypothesis expires and leaves the open list. New evidence reopens it.
proposals.staleits theme shipped or was archivedA pending proposal on that theme is marked stale.
decisions.nudge2 days after the decision is raisedIts owner is nudged once. decision.escalated, step nudge.
decisions.reroute2 days after the nudgeThe org owner is brought in once. The owner keeps it, so either may answer. decision.escalated, step reroute.
tasks.pauseEscalate2 days, then 2 daysA job paused on a question escalates on the same clocks.
tasks.quickDecisionExpirethe quick bet's window, 1 minute to 4 hoursThe bet settles as it was declared: the job pauses for a person (block) or reopens on the stated assumption (proceed).
tasks.leaseExpire2 hours after the claimThe job returns to open. When its attempts have reached 10 it fails instead; a release does not count. job.expired, reason lease.
tasks.closingExpire1 day in closingA closing job whose pull request has not merged fails. job.expired, reason closing.
proposals.review3 days without a verdictThe decider is reminded, once per quiet period.
sessions.expire30 minutes without a messageThe session expires. Its messages and context are pruned 7 days after it ends; the session and its commit stay.
webhookDeliveries.attemptLost5 minutes with an attempt that never reportedThe delivery fails with lastError attempt lost, and replay accepts it.

Events and webhook deliveries are kept for 90 days; a delivery whose event passed that fails.

Stated, not run by the suite: decision.escalation hypothesis.expire jobs.expiry

Webhook failure

A delivery is pending, delivered, failed and gets at most 6 attempts. An endpoint is disabled with reason key_revoked, key_lapsed, deleted. The schedule, the reasons and replay are in the webhooks guide.

Stated, not run by the suite: webhooks.retry webhooks.key_lapsed

Proved by

Every claim on this page is one of these checks; those marked stated are documented behaviour the suite cannot drive. Run them against your org:

npx @belora/conformance --key $BELORA_CONFORMANCE_KEY --api $API --mcp https://mcp.belora.dev/mcp
  • errors.envelope Every failure is the registry envelope

    Each error the registry declares for an operation answers its published status in the envelope { error: { code, message } } and nothing else.

  • decision.escalation An unanswered decision escalatesstated, not run by the suite

    An open decision is nudged to its owner once after the published nudge window and re-routed to the org owner once after the published re-route window, each emitting decision.escalated.

  • hypothesis.expire An unsupported hypothesis expiresstated, not run by the suite

    An open or corroborated hypothesis with no new evidence for the published window becomes expired and leaves the open list; new evidence reopens it.

  • jobs.expiry A lapsed lease requeues the jobstated, not run by the suite

    A job whose lease passes its term without a close or release returns to open with attempts kept and a job.expired event with outcome requeued, and fails with outcome failed once the published reclaim cap is reached.

  • webhooks.retry A failed delivery retries on the published schedulestated, not run by the suite

    An endpoint that answers non-2xx sees the same Belora-Delivery id again after each published backoff, a fresh signature each time, and the delivery ends failed with its attempt count after the last try.

  • webhooks.key_lapsed An endpoint stops with its keystated, not run by the suite

    Revoking the creating key disables its endpoints with disabledReason key_revoked; a key that expires or loses webhook:manage disables them with key_lapsed at the next event; a live key brings them back with POST /v1/webhooks/{id}/enable.