Why
Staging evidence on 2026-10-05:
-
Card fee lost (A). For Open_card
CC261005QYMFP2IO, the Pending webhook arrived withtime …523178ms, then the Success webhook withtime …523000ms: KeySecure truncated the Success time to whole seconds. The strict millisecond guard treated Success as older, so the 2 USD card fee was never posted. -
Card_update never parsed (B). Real
Card_updatepayloads have nobefore_status, so every one failed withbad_card_update. -
Card details failed intermittently (C). The one-time client-token call got
1007(Access-Token invalid or expired) and was never retried, so the app showed a 502.
Changes
-
card-consumption.service.ts- Consumption events are compared at whole-second precision.
- Within the same second a Success wins over a Fail: a Fail after a stored Success is ignored, and a Success after a stored Fail still posts.
- A late Pending still never downgrades a settled row.
- Posting stays delta-based under the row lock, so nothing can double-post.
-
keysecure-webhook.parser.ts:before_statusis optional. -
keysecure-http.client.ts- An
issue-oncerequest (client token) is retried once after a 1006/1007 auth rejection. - This is safe because any token minted by the rejected attempt never reached us and expires unused.
-
write-unkeyedis still never retried, and a 5xx or timeout is still outcome-unknown.
- An
Tests
- Unit tests:
- the truncated Success posts;
- an event from an earlier second is ignored;
- same-second Fail-after-Success is ignored, and Success-after-Fail posts;
- the real
Card_updatepayload parses; -
issue-once1007 is retried once.
- Postgres spec reproducing the staging sequence for an Open_card.
-
yarn jest src/modules/card-account src/modules/keysecure: 607 pass, 12 skipped (DB specs). -
card-consumption.postgres.spec.tsonmoanapay_test: 6/6 pass. - eslint and
yarn buildare clean. - Each new assertion was mutation-checked.
- Opus review gate: PASS after 3 rounds.
Known gap (pre-existing, out of scope)
A Fail arriving in a later second than a Success still flips the status without reversing the debit. KeySecure does not document that sequence.
After deploy
The lost staging fee can be re-posted by setting webhook CN2610054D2EIHOG back to FAILED so the replay scheduler reprocesses it. That step needs separate approval.