fix(api): revert deterministic retry billing id (free-scan on redelivery)
Follow-up to #3400. The deterministic pending:retry:<parent> billing reservation id it introduced has a realistic bug, so this reverts it to a unique random id.
The bug
Billing consumption is idempotent on sourceResourceId, and a refund creates a separate refund event while leaving the original consume event in place (billing-included-usage-refunds.ts). With a deterministic per-parent reservation id:
- A retry reserves (debits)
pending:retry:<parent>. - The Maced create fails transiently → we refund it (net zero, but the
consumeidempotency key remains). - Standard webhook redelivery re-runs the retry → reserves the same id → billing sees the existing consume key → returns "consumed" without debiting → the scan runs for free.
This is the normal sequential retry-on-failure path, so it's realistic — a worse trade than the (unverified, concurrent-only) over-charge the deterministic id was meant to prevent.
Fix
Revert to a unique random reservation id per create call, so a redelivered retry re-debits correctly. Concurrent duplicate scans are still deduped by the Maced idempotency key (unchanged); the residual concurrent-only reservation over-charge requires Maced to deliver duplicate webhooks concurrently (unverified) and isn't worth reintroducing a realistic free-scan to close.
Testing
npx jest src/security-penetration-tests → 154 green, typecheck + prettier clean.
Summary by cubic
Reverts the retry billing reservation id to a unique random id to prevent free scans on webhook redelivery after a refunded failure. Concurrent duplicate scans are still deduped by the Maced idempotency key, and billing now re-debits on redelivery.
- Bug Fixes
- Always generate a unique
pending:<uuid>sourceResourceId; removepending:retry:<parent>. - Avoids idempotency skip when a refund leaves the original consume event in place.
- Updated tests to expect a non-deterministic billing id while keeping the same Maced idempotency behavior.
- Always generate a unique