Build1 publisher3 min readPublished
A hash-checked JSON file decides which status codes an agent's mapper may return
A dev.to case study freezes four error codes, with their HTTP status, retry flag, message key and log level, into one JSON file, hash-checks it in CI, and then lets the generator rewrite the mapper as often as it likes.
The Engineer · Build desk

What happened
- The service in the case study sits behind a queue worker and answers three clients: a web app, a mobile app, and a partner integration.
- Whether a failure was retryable, and what the user should be shown, had never been written down; those answers lived in the heads of the people who wrote the original catch blocks.
- Asked to generate the mapper from that state, the agent picked 422 for one validation failure and 400 for its sibling.
- The same generated mapper forwarded err.message into the response body, where the partner client could read a stack trace.
- The fix commits four codes to contract/error-cases.json, each with an HTTP status, a retry flag, a message key and a log level, stamped version 1 and frozen_at 2026-09-15.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint With the mapper reduced to a pure function of a file it cannot edit, discarding and regenerating it stops being a review event, and the diff a human has to read shrinks to the JSON.
- exposure The digest lives in the same directory as the contract, so an agent with commit rights to contract/ can refreeze the taxonomy and still get a green build. The guard is branch protection.
- decision Splitting a second validation status out of the single 422 entry becomes a change to the contract, which all three clients read at once, instead of a branch someone edits inside the mapper.
- cost Adoption costs one JSON file, one digest file and one dependency-free test on Node 20, so nobody has to fund a schema registry to get a stable retry flag.
Run the suite and it does three things in order. It hashes `contract/error-cases.json` and compares the hex digest against the string stored in `contract/error-cases.sha256` [19]. It walks the four entries in the file, calls `toProblem` with each code, and asserts the returned `type`, `status`, `retryable` and `message_key` match the file exactly [20]. Then it calls `toProblem` with `ECONNRESET` and a message carrying a database host and port, and asserts the result is type `INTERNAL`, status 500, with `detail` undefined [21]. One assertion for the hash, four fields across four cases, three for the unknown code: twenty in all [23].
Twenty assertions is also the coverage. The loop runs in one direction, from the file into the mapper [20]. A mapper that grows an extra branch for a code the file does not list, with an invented status and retry flag, passes everything unless the code it invents is `ECONNRESET` [26]. The freeze removes the generator's room to invent policy for the codes you enumerated [13].
The digest guard is a change detector. `sha256sum` writes the hash into a file sitting beside the one it covers, in the same working tree [17]. Any process that can write `contract/` can write both and get a green build, so the case study's control is procedural: regenerate the digest only through a reviewed pull request [17][27].
The drift being fixed started with an agent picking 422 for one validation failure and 400 for its sibling [7]. It also marked a rate-limit response non-retryable because the word "limit" read like a permanent condition [8], which is a fair inference about English and a wrong one about HTTP. The frozen file holds one validation entry, `VALIDATION_FAILED` at 422, non-retryable, log level info [16]. Both siblings now return 422 until someone adds a second entry in review [16][17]. The case study blames the brief: "The problem is that you handed it a task with no acceptance criteria, so it optimized for readability instead of contract stability" [10].
`log_level` is in the contract too, so pager noise is a reviewed decision [11][13]. Two of the four cases are retryable [24]. The two non-retryable ones log at info, `RATE_LIMITED` logs at warn, and `UPSTREAM_TIMEOUT` logs at error [16][25].
Before that number transfers to your service, a few conditions have to hold. Your clients have to branch on a retryable flag, the way the web app, mobile app and partner integration in the example each decide retries for themselves [5]. The failure modes have to be enumerable before generation, and this project starts from four known ones [3]. And your runner has to read the contract with no dependency, which the listing does with `node:test`, `node:assert`, `node:fs` and `node:crypto` on Node 20 or newer [18][15].
The post gives no numbers. Six slightly different 4xx answers for one failure is asserted as the outcome of ungoverned generation, and nobody counted it across runs [1]. It also tells you to treat the listings as a reference implementation and run them in your own repository before adopting any of it [22].
What to watch
- Whether the follow-up publishes the mapper listing these twenty assertions constrain.
- Whether the digest check is replaced by something an agent cannot regenerate, such as a hash pinned in CI configuration outside the repository tree.
- Whether the suite grows a reverse check that enumerates the mapper's branches and fails on any code absent from the contract.