SDK and error contracts
What is wisent-errors
What is wisent errors One failure envelope for the whole Wisent fleet: one vocabulary of seven codes, one shape, one place to look a code up. Four runtimes — Rust, Python, JavaScript, Swift — are generated from one catalogue and proven byte
What is wisent-errors
One failure envelope for the whole Wisent fleet: one vocabulary of seven codes, one shape, one place to look a code up. Four runtimes — Rust, Python, JavaScript, Swift — are generated from one catalogue and proven byte-identical to each other. The whole product is three moving parts: a catalogue that declares, a generator that derives, and a conformance harness that proves the runtimes are one behaviour.
The catalogue declares
catalogue/codes.json is the single source of truth. It names the seven
codes — config, auth, not_found, rate_limit, timeout, infra_down,
unknown — and, for each, everything derivable: severity, retryable,
outage, the HTTP status a service answers with, and an operator summary. It
also states the upstream-status classification, the exit-code rule, and the
failure_point grammar. The semantics were extracted verbatim from the
reference implementation (wisent-compute/stado-rs/src/failure.rs), so the
first migration could not change what anything does; the catalogue records
that provenance in its own provenance field.
A call site chooses where it broke (failure_point), what the layer below
said (detail), and which subject it concerns (service, impact,
context). It never chooses severity, retryable, or outage: those come
from the catalogue, which is why one code cannot come to mean different
things in different products. The full table is in catalogue;
the shape it travels in is in envelope.
The generator derives
codegen/generate.mjs is the only thing allowed to read the catalogue at
build time. It writes one generated module per language — rust/src/codes.rs,
python/wisent_errors/codes.py, js/codes.mjs,
swift/Sources/WisentErrors/Codes.swift — so every runtime carries the same
table. Generated files are committed: a consumer needs no build step, and
node codegen/generate.mjs --check fails if any generated file drifts from
the catalogue. The hand-written part of each runtime — building, salvaging,
rendering an envelope — wraps its generated module; see
runtimes.
The harness proves
tests/conformance.mjs makes each runtime emit every golden envelope in
tests/conformance/cases.tsv plus a dump of the whole derived vocabulary,
and compares all of them against the expected column and against each other.
That comparison is what turns four implementations into one behaviour — and
it is exactly the check whose absence let one product quietly lose the
vocabulary while five kept hand-rolled copies of it. node ci/check.mjs runs
that harness plus three structural checks before anything ships; see
conformance.
What travels
{
"failure_point": "brama.dispatch.bounded-rotation",
"error_code": "rate_limit",
"service": "brama",
"impact": "one model request",
"severity": "warning",
"retryable": true,
"outage": false,
"detail": "all bounded 'claude-code' credentials unavailable for agent",
"cause": {
"failure_point": "brama.gateway.oauth-refresh",
"error_code": "auth",
"service": "brama",
"impact": "one credential refresh",
"severity": "warning",
"retryable": false,
"outage": false,
"detail": "invalid_grant -- Refresh token not found or invalid"
}
}
cause is the one field this package added to what the fleet already had:
the failure underneath this one, recursively. A gateway refusing a request
because a provider refused a token because a vault refused a read is three
failures, and reporting only the outermost is how a day goes into finding
what the innermost already said.
What it is not
wisent-errors owns the vocabulary and the shape, not the sentences or the pipes. Product messages, provider text, logging transport, and each product's registry of failure points stay in the products. The boundary is stated precisely in boundary; how a product adopts the envelope is integration; the fastest path to a first envelope is quick-start.