Flow
Keeping a hashed receipt exact
1Test with awkward numbers
Write repeating fractions through the real database and read them back.
2Keep the check strict
Never loosen the hash comparison or hash what comes back from the table.
3Write the JSON yourself
Turn the payload into text in your code and let the database parse it.
4Use one helper
Send every insert and completion step through the same small function.
Saving a receipt that didn't match itself was something the ordering system did. The copy of the request in the row no longer matched the hash saved next to it.
Why the receipt matters
When a buyer turns a plan into an order, the request gets a hash and is saved as a receipt. If the same request comes again, the system sends back the first answer, so a retry can't make a second order. As far as I know, a different request with the same key is refused. Anyone can also hash the saved copy later to check that it hasn't changed. That check is what failed.
How I found it
I had a big test that runs one supplier's whole catalog across every store, from the first draft to the next plan. It checks that the saved copy hashes to the saved hash. When it didn't, the test listed the field that changed. It was a repeating fraction that came back with one digit fewer.
Why one digit counts
A computer stores repeating fractions as a close guess. To read that exact guess back, you need every digit. Drop one and you get a slightly different number. Round numbers never show this, so clean test data hides it. You would never see the gap by eye, but a hash treats it as a whole new input.
Where the digit went
The database library rounded the number when it wrote the JSON column. The database kept what it was given. The test was right to fail.
Writing the JSON text yourself
Don't loosen the check, and don't hash what comes back from the table. Write the JSON text yourself and let the database parse it:
await db.run(
`INSERT INTO receipts (key, hash, payload) VALUES ($1, $2, $3::jsonb)`,
[key, hash, JSON.stringify(payload)])
Put the insert and the completion step in one small helper, and make every writer use it. The completion step should only change a row that is still in progress, and throw if it changes anything else. I didn't touch old receipts or add a migration.
Which tests count
Fast tests that fake the database can't prove digits survive. Only tests against the real database can. Mine caught this because its full test data had a fraction in it. Put awkward numbers in on purpose.
Other JSON columns
Other JSON columns may round the same way, and I haven't looked at them yet.