How failures reach the client
Every failure reaches the client in one envelope: code, message,
request_id, and the failure's client fields under details
(Failures). The status comes from the failure's kind, and
the x-request-id header repeats the id.
Bad input lists each problem without its value. It names the field by its
path in the Input, with list positions as numbers, the names the OpenAPI
schema uses:
{ "error": { "code": "invalid_input", "message": "Request input is invalid.", "request_id": "req_0b1e…", "details": { "problems": [ { "field": "lines.2.quantity", "rule": "max" }, { "field": "colour", "rule": "unknown_field" } ] } }}
The runtime answers these itself, before or around what the route runs:
| Condition | Status and code |
|---|---|
The body is not JSON, isn't sent as JSON (Content-Type), or the query string cannot be read |
400 invalid_request |
| A header's value isn't printable ASCII | 400 invalid_request |
| A field is missing, unknown or breaks a rule (body, query, header or placeholder), or the route reads no query string or body and one came | 422 invalid_input |
| No route matches | 404 route_not_found |
| The path exists but not for this method | 405 method_not_allowed, with Allow |
| The body is over the limit | 413 request_too_large |
| The deadline passed | 504 deadline_exceeded |
An Idempotency-Key is missing where the API requires one, or isn't 1 to 255 printable ASCII characters |
400 invalid_request |
An Idempotency-Key comes again with a different request |
422 idempotency_key_reused |
An Idempotency-Key comes again while its first request is running |
409 conflict |
When a repeat is the same request
A request with a key that came before is the same request when its route (its method and declared path) and what it says, decoded, are the same. What it says is each placeholder and the input: from the query string, the headers it reads and the body. The runtime writes these back through the route's own codecs as the contract's JSON, and keeps a digest of that.
That JSON writes every secret value (Password, BearerToken and the
rest, see Standard library) as its kind alone. So
the digest the runtime keeps says nothing of a secret, whether it came in
the body or the path, and whoever can read the key table can't test
guessed passwords against it. A repeat that differs only in a secret, or
only in its JSON's spacing or member order, is the same request, answered
as the first was.
A response that starts a session or carries a credential is never kept.
The digest isn't keyed (no secret is mixed into the hash), since a key would need a secret every deployment holds, and Jadpo has none (Configuration and time). So a value that isn't secret, sent and neither stored nor answered, can still be confirmed by guessing, by whoever reads the table. That person reads the kept responses and the application's own tables as well.
When the host has no clock
A deployable, a program jadpo build makes from the app such as a native
server (Declared deployables),
answers under its host's clock. When the clock can't be read (the system
clock is before 1970), every request, health checks included, is 500
internal_fault and nothing runs. Its fault, for the host's log, says the
host has no clock. jadpo dev doesn't start (cli.dev_start_failed, "The
system clock can't be read").
Wording for people
The envelope's message stays fixed English. Text for people in client
apps comes from the app's language files
(Failures for client apps).
Every failure a route can produce has wording in every supported locale,
keyed by its code, with its client fields as placeholders. Client fields
that are dates or numbers stay values on the wire, for the client to
format in its own language.
Markdown for agents: /docs/how-failures-reach-the-client.md.Built from Jadpo main at a707efb.