Build1 distinct publisher3 min readPublished
A catch block that collapses a dead connection into a boolean asserts something about a provider you never heard from. The fix in this dev.to write-up is a six-state enum and a row committed before the side effect.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The load-bearing detail in that ordering is where the idempotency key is born. It is generated in the insert, stored on the row, reused forever, and handed to `provider.charge` as an option [16]. Its lifetime is the payment's, not the attempt's, and that is the only reason an `unknown` row is safe to touch again. Retrying is non-destructive if and only if the provider deduplicates on a key you chose before the first call. If your provider does not honour keys on that endpoint, you get the state machine and no way to advance it, and you are back to matching on the reference you stored for reconciliation [17].
The argument for putting this in the type rather than in a team convention is the one I would use at review: the enum is what queries filter on and what dashboards group by [7]. The alternative is already in most schemas, where a `failed` row with a null `provider_ref` sometimes means no response arrived and sometimes means no call was made, and the one person who knows which is which takes that knowledge with them when they change teams [4].
Count the retrofit. The old status column carried two useful values [c1b], the new type carries six [6], so four states arrive that nothing in your reporting layer has ever seen [1]. The post concedes the correction is awkward to retrofit [5], and awkward is right, because a backfill has to decide, row by historical row, which of the old `failed` records were requests that never went out at all.
That leaves `classify(err)`, and the source text breaks off at "Note what classify is allowed to r" without giving the mapping [15]. The mapping is the safety property. A 400 with a body is `rejected`, because the provider answered. A reset connection is `unknown`, because nobody did; writing `failed` there records, in durable storage, a fact about the provider's ledger that you are not in a position to state, when the money may have moved 200 ms ago [2]. I would insist the default arm return `unknown`: an error class you have not classified yet is, by construction, one you have no information about. The three histories the post describes are indistinguishable from where you stand [10], and they only stay that way if the code declines to guess.
`expectedResolutionBy` is the small field that makes the design operable. Setting it to `addBusinessHours(now, 2)` at insert time [17] gives a sweeper a WHERE clause, so `unknown` is a queue with a deadline instead of a bucket that grows. The row exists before the side effect, which is what the post means by write-ahead logging with the business as the log [14]. The payoff is that recovery becomes a scan rather than an interview.
Ranked by verification strength, evidence, and original report placement.
The supplied source text ends mid-sentence at 'Note what classify is allowed to r', so the mapping from error to state inside classify() is not given.
In the dev.to post, an HTTP client raises ETIMEDOUT on POST /charges, something has to be written to the database, and so the catch block writes 'failed'.
The post describes the existing status column as having two useful values.
The post argues 'failed' is a claim about the outside world made at a moment when you are not in a position to make any claim: the provider may have taken the money 200 ms ago, and you have asserted in durable storage that it did not.
The post states the correction as: unknown is a state your system holds, not an error it handles, and calls it small to state and awkward to retrofit.
The proposed type is a six-value enum: 'intended' (decided to pay, nothing sent), 'in_flight' (request on the wire), 'unknown' (request ended without an answer), 'confirmed' (provider says it happened), 'settled' (appeared in an ingested statement), 'rejected' (provider says it did not happen). unknown and rejected are different states and only rejected is a fact, because rejected means the provider gave an answer.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 29, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Three API calls, no rollback: the phone numbers your carrier bills and your database never saw1 distinct publisher
build
Your agent's retry logic is reading a timeout as a fact it does not have1 distinct publisher
build
Your migration script and your production app share one bucket of 1,200 requests1 distinct publisher
build
Most distributed locks are a design smell: give one thing ownership, skip the protocol1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Shows its work, proves none of it
To dev.to's credit, everything checkable is on the page: the enum, the two-line broken version, the corrected sequence, the classify rule, both queries. A reader can verify the claims that are about code because the code is right there. What cannot be verified is the part the headline leans on — that a false 'failed' produces the second charge and the refund for a payment nobody made. That chain is narrated, and the accompanying assertions that ambiguous outcomes dominate during incidents and that most systems already encode the unknown by accident arrive with no log, postmortem, or sample behind them.
No one is shown running it
Our coverage contains no user of this pattern — no repository, no migration applied anywhere, not even an anonymised 'we shipped this and unknowns now clear in four minutes'. dev.to's post argues for a design; whether any team has moved a payments table to these six states is simply not in the record, and we are not going to infer it from the confidence of the prose.
Title oversells a sound design
'Charges the customer twice' promises an outcome the post never observes; what it delivers is a state machine and a write ordering. The stretch is modest and the mechanism is plausible, but two sentences do real rhetorical work on no measurement at all — that the ambiguous outcome is the common case during incidents, and that most systems already carry this bug in a nullable provider reference. Strip those and the argument still stands on the code, which is why the gap is small rather than large.
Nothing for sale
No product, no vendor, no payments API named — the client is a placeholder called provider.charge and the schema is plain SQL anyone can copy. The only thing the author stands to gain is attention on a developer platform, where a flat title travels badly and a causal one travels well; that pressure is visible in the framing and nowhere in the technical content.
One voice, one wobble
Two things hold this down. There is a single publisher with no counterparty, so nothing in the story is corroborated by anyone who has run a payments ledger. And our own record of the post was briefly truncated mid-sentence at 'Note what classify is allowed to r' — precisely before the rule that everything ambiguous becomes unknown, which is the sentence the entire pattern depends on. The fuller text restores it, and the internal consistency of the design is high enough that the reasoning can be judged on its face.