Files
pad/internal/server/testdata
xarmian 3201342d3d feat(cloud): a Stripe event marker is a claim with a lease, so a handler that dies does not lose the event (BUG-3561, pad half) (#2043)
* feat(cloud): a Stripe event marker is a claim with a lease, so a handler that dies does not lose the event (BUG-3561, pad half)

pad-cloud marked a webhook event processed BEFORE its handler ran, and the
mark was final. A sidecar crash, OOM or redeploy mid-handler, or a failed
unmark, left the event 'processed' forever: every Stripe retry was
acknowledged as a duplicate. On an annual plan cancelled early, the user kept
Pro until the period end.

The marker now has two states (migration 136 / PG 108; existing rows read
as 'done', which is what they meant):
- ClaimStripeEvent inserts a new event 'processing' with a 5-minute lease
  (claimed). A 'done' event is a duplicate. A 'processing' event with a
  live lease is in_progress. One with an EXPIRED lease is taken over by one
  conditional UPDATE with a new token: of several racing retries exactly
  one wins.
- POST /api/v1/admin/stripe-event-complete {event_id, processed_at} marks it
  done after the handler succeeds. It is token-scoped, so a handler whose
  lease was taken over cannot complete it for the one that took it.
- POST /admin/stripe-event-processed keeps already_processed with its old
  meaning (anything but a fresh claim) and adds 'state', which a newer
  sidecar feature-detects (lead ruling). A sidecar that predates this never
  completes; its rows stay 'processing', and a redelivery after the lease
  re-runs a handler that re-syncs from current Stripe state, which is
  harmless.

Wired like its siblings: cloudAdminPaths, requireCloudMode, the cloud-admin
rate limiter, BUG-3361's cloud-only route list, and the cloud contract
goldens (processed gains 'state'; complete is new). The NUL census records
the two new server-written columns. Test: claim, in-progress, done, an
expired lease taken over exactly once by 4 racing claimers, and a
stale-token complete as a no-op. Without the takeover, 0 of 4 claim.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* test(cloud): the contract test gets a roomy cloud-admin limiter (BUG-3561)

It calls every sidecar route once from one address to pin response shapes,
and with billing-users and stripe-event-complete the list outgrew the real
limiter's burst of 10 (the 11th call answered 429). Rate limiting is not what
it tests.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-11 00:02:50 -04:00
..