SDK and error contracts
The catalogue
The catalogue catalogue/codes.json (version 1) is the single source of truth for the fleet's failure vocabulary. codegen/generate.mjs is what turns it into code, and the generated modules are the only copies the runtimes carry. The tables o
The catalogue
catalogue/codes.json (version 1) is the single source of truth for the
fleet's failure vocabulary. codegen/generate.mjs is what turns it into
code, and the generated modules are the only copies the runtimes carry. The
tables on this page restate the catalogue for reading convenience; if this
page and the catalogue ever disagree, the catalogue is right, and
node codegen/generate.mjs --check is the command that notices generated
code drifting from it.
The semantics were extracted verbatim from
wisent-compute/stado-rs/src/failure.rs, the one implementation whose
derived status ladder had stayed correct; the HTTP status map and the
exit-code rule each arrived from three identical copies held by other
products. The catalogue's provenance field records exactly this.
The seven codes
Derived fields — severity, retryable, outage, HTTP status — come from the catalogue, never from a call site.
| code | severity | retryable | outage | http_status | operator summary |
|---|---|---|---|---|---|
config |
critical | no | yes | 503 | our deployment configuration is incomplete or wrong |
auth |
warning | no | no | 401 | the credentials this command used were rejected |
not_found |
warning | no | no | 404 | what the command asked for is not there |
rate_limit |
warning | yes | no | 429 | an upstream is throttling us |
timeout |
error | yes | yes | 504 | an upstream did not answer in time |
infra_down |
critical | yes | yes | 503 | infrastructure we depend on is unreachable |
unknown |
error | no | no | 500 | the command failed and we could not attribute the failure |
What each code means, from the catalogue's own meaning fields:
config— a value the process needs was absent or malformed. Ours to fix, and no amount of retrying changes it.auth— an upstream refused the identity we presented. Retrying with the same credential repeats the refusal.not_found— the request named something absent. A server error is never classified here: collapsing 5xx into "nothing there" is what let a storage outage read as an empty queue.rate_limit— the request was well formed and refused for pace. Worth repeating later, unchanged.timeout— no verdict arrived. The operation may or may not have happened, which is why it counts as our outage.infra_down— a dependency did not answer at all. Nothing about the request was wrong.unknown— the last resort. A failure that staysunknownis a gap in classification, not a kind of failure, and worth reading as a defect in whoever emitted it.
Severities are exactly three: warning, error, critical. There are
exactly seven codes; why an eighth was declined is in
boundary.
Classifying an upstream status
fromUpstreamStatus / from_upstream_status / Code::from_upstream_status
/ Code.fromUpstream(status:) classify the HTTP status an upstream answered
one of our calls with, identically in all four runtimes. Exact matches are
checked before ranges; ranges are inclusive; anything unmatched is
unknown.
| status | code |
|---|---|
| 401, 403, 407 | auth |
| 404, 410 | not_found |
| 408, 504 | timeout |
| 429 | rate_limit |
| 501, 505 | config |
| 500–599 (any other) | infra_down |
| anything else | unknown |
501 and 505 sit ahead of the 5xx range on purpose. The catalogue defines
infra_down as "a dependency did not answer at all" — and a 501 or a 505
did answer. A server saying it does not implement the method, or does not
speak the version, is our deployment being incomplete: retrying cannot help,
so config is the honest code. A 5xx is infra_down and never not_found.
The exit-code rule
RETRY_EXIT is 69 (EX_UNAVAILABLE). Retryable codes exit with it; every
other code keeps the exit code the caller already chose, so a CLI's own
conventions survive adoption. The rule is exposed as exitCode(code, chosen)
/ exit_code(code, chosen) / Code::exit_code(chosen) /
Code.exitCode(chosen:).
The failure-point grammar
A failure_point is a dotted lowercase path, one segment or more:
^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*(?:\.[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*)*$
The depth carries no meaning. The catalogue's own examples run from one
segment (cli) to four (cli.host.user.create), because real products emit
both, and a grammar that refuses what five products already emit is the
package being wrong. What is validated is the shape — lowercase,
dot-separated, no empty segment — because that is all a shared rule can
honestly check. The product is not required as a segment because service
is already a field of every envelope. Which points exist is each product's
own business; see boundary.
The pattern is stated once: ci/check.mjs fails if the catalogue and the
schema disagree on it. The envelope the codes travel in is
envelope.