[object Object]

← back to Costa Rica

cycle 12 docs: YOLO_NOTES ledger — webhook idempotency-release fix + poll rescue

11478abd7422f9b56ca28b30f02e61f04725fe43 · 2026-09-23 21:41:36 -0700 · Steve

Records the marker-claimed-before-processing bug (transient failure lost the
event to dedupe), the release-on-failure fix, and the Cody-caught poll
blind-spot (succeeded+pending) it exposed + fixed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic

Files touched

Diff

commit 11478abd7422f9b56ca28b30f02e61f04725fe43
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Sep 23 21:41:36 2026 -0700

    cycle 12 docs: YOLO_NOTES ledger — webhook idempotency-release fix + poll rescue
    
    Records the marker-claimed-before-processing bug (transient failure lost the
    event to dedupe), the release-on-failure fix, and the Cody-caught poll
    blind-spot (succeeded+pending) it exposed + fixed.
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
---
 YOLO_NOTES.md | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/YOLO_NOTES.md b/YOLO_NOTES.md
index c4facaf..6a69397 100644
--- a/YOLO_NOTES.md
+++ b/YOLO_NOTES.md
@@ -297,3 +297,20 @@ Canonical apply order now: `004_marketplace → 005_apple → 006_contacts → 0
 **Cost:** $0 (local tests + one Cody pass — hand-traced the real chain, then required the test suite actually prove what was claimed).
 
 **Backlog:** approval-enforcement decision still on Steve's desk (`REQUIRE_HOST_APPROVAL` flag pre-written). Provider-honored idempotency key still live-only. Safe local reversible work across 11 cycles has now covered: full payment-provider timeout/idempotency hardening, admin.js + host-listing auth/race audit, Plaid bank-linking hardening, and the systemic async-error fix. Next cycle: continue the coverage sweep (`routes/webhooks.js` full signature/replay audit, `routes/build.js`, `lib/apple.js`) or pivot to a fresh area if those are clean.
+
+## /yoloforever CYCLE 12 (2026-09-23, yf-costa) — routes/webhooks.js audit → idempotency-ordering bug + a poll blind-spot it exposed
+
+**Audited `routes/webhooks.js`** (payment webhook money path). Existing coverage was solid (signature 401s, dedup P3, malformed 400 P6, cross-provider 401 P7). The gap was in the idempotency ORDERING.
+
+**BUG (fixed):** `firstTime()` claims the idempotency marker (INSERT webhook_events) BEFORE processing. If processing then throws (transient DB error on the payments UPDATE / confirmBooking), the outer catch returns 500 — but the marker is already committed, so the provider's retry (on our 500) hits the dedupe gate → 'dup' 200 → **the confirmation is lost forever** (payment succeeded, booking never confirmed). A 500 says "retry me" but the retry was swallowed. **Fix:** wrap processing in try/catch; on failure RELEASE the marker (DELETE webhook_events) so the retry re-processes, then surface the 500. True duplicates still dedupe (they succeed + keep the marker); concurrent double-delivery still deduped (ON CONFLICT DO NOTHING).
+
+**Cody gate — caught a defense-in-depth regression the marker fix INTRODUCED (fixed same commit):**
+- The release creates a NEW valid state: `payments.status='succeeded'` while its booking is still `'pending'` (webhook marked the payment, confirmBooking failed, marker released, 500 for retry). `GET /payments/:id` only reconciled when `status==='processing'`, so a client poll landing before the provider retry would return 'succeeded' while the booking sat pending — invisible to the only in-repo rescue path (no cron exists). **Fix:** the poll now calls `confirmBooking(booking_id)` whenever the payment is `'succeeded'` (idempotent), not just in the 'processing' branch.
+- The release DELETE's `.catch(()=>{})` swallowed a failed release silently (regressing to the original bug with no trace). Now logs distinctly.
+- Cody CLEARED (verified): double-processing on retry (confirmBooking guarded by status='pending' → no 2nd WhatsApp; refund UPDATE is a flat idempotent SET); evId/ref key drift (release deletes by the same evId firstTime inserted); the 0-rows case (correctly NOT released — payments.status stays 'processing' so the existing poll covers it; releasing would only hammer retry budgets on unmatchable test/ping events).
+
+**Tests (+5, suite 170 → 175):** webhooks-route.test.js (+2: failure→500+DELETE marker; success→no release); payment-poll-rescue.test.js (NEW, 3: succeeded+pending rescued by poll; already-confirmed no-op no-double-notify; processing still polls).
+
+**Cost:** $0 (local tests + one Cody pass — which caught that my marker fix silently unlatched the poll's rescue coverage).
+
+**Backlog:** approval-enforcement decision still on Steve's desk. Provider-honored idempotency key still live-only. Coverage sweep continues — `routes/build.js`, `lib/apple.js`, `lib/whatsapp.js` deeper edge cases remain; or a fresh Cody audit for a new issue class.

← ce0a607 costa-rica: webhook idempotency-marker release on failure +  ·  back to Costa Rica  ·  costa-rica: bound WhatsApp Graph API fetch with fetchT (Cody 3089df8 →