Skip to content
Code Recycle

Component · for humans & their agents

Postgres Lost Update Verdict

verified · first-partyactively maintained$0 during beta (was $79)

Two writers added 50 and 30 to a balance of 100. The result was 130. Neither transaction raised anything.

by ringbuffer · Code Recycle admin

Get it free — beta

Every claim on this page is refundable if it is untrue — refund policy.

Verified: 21 tests

Decides whether a database write can SILENTLY LOSE a concurrent update. Pure function over the shape of the statement and the isolation level: no I/O, no driver.

Decides whether a database write can SILENTLY LOSE a concurrent update. Pure function over the shape of the statement and the isolation level: no I/O, no driver.

THE MEASUREMENT. A real PostgreSQL server. One row, balance 100. Two concurrent transactions, each reading, pausing, then writing back what it read plus its own amount. Correct answer: 180.

    default_transaction_isolation   read committed
    tx A: SELECT balance -> 100 ... UPDATE SET balance = 100 + 50
    tx B: SELECT balance -> 100 ... UPDATE SET balance = 100 + 30
    ACTUAL: 130

The +50 is gone. Both committed. NEITHER RAISED ANYTHING. Read committed guarantees you never read UNCOMMITTED data; it does not stop two transactions reading the same COMMITTED row and both writing back. That is the shape of every "add to balance", "increment counter", "append to array", "set status based on current status" written as read-then-write.

MEASURED CORRECT: `UPDATE SET balance = balance + 50` gave 180, and `SELECT ... FOR UPDATE` before the read gave 180. One statement works because there is no window between read and write -- there is no separate read. The lock works because the second transaction waits.

SERIALIZABLE MAKES IT LOUD. IT DOES NOT MAKE IT RIGHT.

    ERROR:  could not serialize access due to concurrent update
    result: 130

PostgreSQL detected the conflict and aborted one transaction -- so the silent wrong answer became a visible error. AND THE BALANCE IS STILL 130, because the aborted work simply did not happen. SERIALIZABLE converts a lost update into a RETRYABLE ERROR; only the retry makes it correct. Raising the isolation level without a retry loop trades a silent wrong number for a dropped write and a failed request. Better -- and not the fix it is usually described as. So this package reports serializable WITHOUT a retry as a finding, and credits the retry rather than the level.

ALSO MEASURED: `ON CONFLICT DO NOTHING ... RETURNING` returns NO ROW when the row already exists -- an empty result while the record was present both times. RETURNING only returns rows the statement touched, and DO NOTHING touched none. A caller reading "no row returned" as "the insert failed" takes an error path, inserts again, or threads a null id onward. Reported even when nothing is concurrent, because it is not a concurrency question.

IT REFUSES rather than guesses on unknown concurrency, saying plainly that for anything reachable from a web request or a job queue the answer is almost always yes. And a "no concurrent writers" claim is answered SAFE WITH THE CAVEAT that it covers every path to the row -- background jobs, admin tools and retries included.

VERIFIED: 21 tests, every one of 8 mutations observed FAILING before restore.

DELIVERY: signed download of a hash-verified tarball, immediately on purchase. Permissive licence: unlimited products, unlimited clients, unlimited seats, no attribution, perpetual and irrevocable. One restriction, do not republish the source as source.

Interface

What you call, and what comes back. Types and signatures only — the implementation ships with the source.

  export function evaluateWriteSite(site: WriteSite): Verdict;
  export type IsolationLevel = "read-committed" | "repeatable-read" | "serializable" | "unknown";
  export type Verdict = | { status: "SAFE";

01Capabilities

Does

  • + Data engineering
  • + Reliability
  • + Transaction isolation and concurrency

Doesn’t

  • No exclusions declared

02Requirements & stack

Depends on

No declared dependencies

Credentials needed

None declared

Stack

typescript

03Community

No endorsements yet

No verified confirmations yet — be the first.

Confirmations come from verified purchasers, installers, vetted reviewers, or an installation outcome your org reported through the agent tools. They grade quality — security is verified separately, and community votes can never override the security gate.

Open an issue

Sign in to confirm — weight comes from verified usage, not vote count.

0 open · 0 answered · 0 fixed · 1 said it worked

04Trust Passport

Full passport →
–/100

0/0 automated components pass. An automated score is never a security guarantee.

✓ Verified · first-partyreviewed Sep 20, 2026 · re-verification due Dec 19, 2026
  • publisher identity Publisher status verified; 1 verification(s) on file
  • malicious pattern scan No known malicious-behavior patterns across 10 source file(s) plus listing text
  • capability contract All 0 observed capability reference(s) match the declared manifest
  • agent safety scan No injection patterns in agent-readable content
  • provenance No release signature or provenance attestation
  • behavioral sandbox Not performed in this environment — requires the production isolated runner (docs/sandbox-requirements.md). No untrusted code is ever executed on the application host.

Every listing must pass this review before it can be sold, and it is re-run on every release. Verification describes what we checked — it is not a guarantee that the software is safe.

VersionChannelReleasedNotes
1.0.0stableAug 6, 2026First public release.