BBramaDocs
Browse documentation

Envelope

The record Brama logs beside every failure, in the fleet's wisent-errors shape. Brama speaks two error vocabularies to two audiences, and the split is deliberate: what a client reads is the HTTP error body — a stable type and code that predate the fleet vocabulary and do not change — and what an operator reads is the envelope. Where the two readings differ, both are on the line; neither stands in for the other.

Fields

Observed shape (captured from a 0.2.38 dispatch_refused log line):

{
  "failure_point": "brama.dispatch.credential-selection",
  "error_code": "rate_limit",
  "service": "brama",
  "impact": "one model request",
  "severity": "warning",
  "retryable": true,
  "outage": false,
  "detail": "no active 'openai' credential for agent",
  "context": {"model": "openai/stub-ok"}
}
  • service is always brama.
  • failure_point — exactly one of the nine seams (failure-point).
  • error_code — the fleet's code, derived as below.
  • impact — what this failure costs its caller.
  • detail — the reason the layer below gave, verbatim. Nothing is paraphrased; the provider's text is data.
  • severity, retryable, outage — always derived by the wisent-errors crate, never decided at a call site.

How the fleet code is derived

Derivation is centralised in src/core/failure.rs. Where an upstream HTTP status exists, the code is classified from that status by the crate's catalogue — the catalogue is finer than Brama's kinds, and being finer is the point. Otherwise Brama's own kind is translated exactly once:

Brama kind Fleet code
provider_authentication, unauthenticated, credential_unauthorized, subscription_reauthorization_required Auth
provider_rate_limited, subscription_unavailable RateLimit
dependency_unavailable InfraDown
provider_quota_exhausted Config — a spent quota is not a busy provider; no wait repairs it, and the account it names is ours
invalid_request NotFound — the route, selector, or evidence asked for does not exist
context_length_exceeded Refused — the prompt is longer than the model takes (the provider answered 413, or its error object's code or type is context_length_exceeded); HTTP 400, retryable: false. A client promotes the route or compacts; no wait or other credential repairs it
provider_failure, internal_error Unknown

Every arm preserves what the client contract already said about retrying: retryable kinds map to retryable codes and permanent ones do not. The kind is the refusal's class, stated where the refusal was built and carried with it to the HTTP edge, so the envelope's code and the client's answer are read from the same class and never from the words of the message.

Brama's provider layer still writes the kind as a message prefix — the captured provider-authentication failure reads provider_authentication: Incorrect API key provided: sk-… — for the reader of the sentence; no answer to a model request is decided from it.

Rendering stays local

The envelope travels beside the shapes readers already parse, never inside them:

  • the HTTP error body keeps its exact client contract (errors) — a new key there would be a wire change;
  • log fields keep their names (event, model, …) with the envelope as one structured field;
  • the usage ledger keeps its own schema; a definitive refresh refusal lands there as credential.state: needs_reauthorization with the provider's sentence as cause (subscription).

Where envelopes are not allowed

Logs and envelopes never include bearer values, HMAC signatures, capability IDs, raw credentials, request bodies, prompt text, raw provider payloads, or donated secrets. /readyz reports a refusal in the words of whatever refused it — provider name and verdict only, no secret in the body.

Not to be confused with

  • The client error body. That is the wire contract in errors; the envelope is the operator's record.
  • The journal. journal.jsonl records decisions (retirement, refresh verdicts, quality checks); the envelope records failures.