[object Object]

← back to Costa Rica

cycle 20 docs: YOLO_NOTES ledger — stale-payment reconciler + gated cron memo

1ca7bf7bbc46c881cd775671145e75138cbc5251 · 2026-09-24 01:51:23 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

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

Files touched

Diff

commit 1ca7bf7bbc46c881cd775671145e75138cbc5251
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Sep 24 01:51:23 2026 -0700

    cycle 20 docs: YOLO_NOTES ledger — stale-payment reconciler + gated cron memo
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
---
 YOLO_NOTES.md | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/YOLO_NOTES.md b/YOLO_NOTES.md
index 007cb78..684e35a 100644
--- a/YOLO_NOTES.md
+++ b/YOLO_NOTES.md
@@ -445,3 +445,26 @@ Canonical apply order now: `004_marketplace → 005_apple → 006_contacts → 0
 **Prod-apply gated** (go-live migration pass; runbook has the pre-check + CONCURRENTLY). Dev-applied (reversible).
 
 **Backlog / NEW ticket (pre-existing, Cody-flagged):** nothing (webhook/poll aside) moves a truly-STUCK `'processing'` payment off that status — a dropped webhook + an abandoned app session = a permanently-unpayable booking. Needs a reconciler/TTL. The in-flight SELECT gate already had this exposure; the index hardens it, doesn't introduce it. Plus: approval-enforcement decision on Steve's desk; provider-honored idempotency key + sig-encoding (live-only); processorFee schema prerequisite; per-event webhook auto-reply try/catch. The cold audit is proof the loop still surfaces real bugs past cycle 18 — next cycle: the stuck-payment reconciler, or another cold audit.
+
+## /yoloforever CYCLE 20 (2026-09-24, yf-costa) — stale-payment reconciler (Cody cycle-19 follow-up)
+
+**Built the reconciler for the stuck-'processing'-payment gap Cody flagged in cycle 19.** A payment leaves 'processing' only via a webhook or the traveler's GET /payments/:id poll → a dropped webhook + abandoned session strands it forever, and (since cycle-19's one-in-flight index) the booking becomes un-payable.
+
+`lib/reconcile.js` `reconcileStalePayments()` — exported, idempotent, NOT cron-wired (gated). **Pass A** polls the provider (getCharge, fetchT-bounded) for stale in-flight payments and applies what it RESOLVED (succeeded→confirm, failed→free the slot, refunded→booking refunded); a getCharge timeout is left untouched (never fail a possibly-succeeded charge). **Pass B** confirms bookings orphaned by a succeeded-payment-but-confirmBooking-failed.
+
+**Cody gate — FIX FIRST, all applied:**
+- **Force-fail is now OPT-IN (default `null`), was default-ON at 1440min.** Cody: force-failing a still-'processing' charge frees the in-flight slot → booking payable again → if the traveler re-pays AND the original later lands (the webhook UPDATE has no status guard), they're charged TWICE. So it defaults OFF (stuck payments surfaced via return counts, not auto-failed); when explicitly enabled it requires an EXACT 'processing' status and logs each hit for ops.
+- **Pass B (Cody hole #2 — a real orphan the module existed for but couldn't see):** the reconciler's SELECT filtered `status='processing'`, so a payment the webhook durably marked 'succeeded' whose confirmBooking then threw (retry never landed, abandoned session never polled) left the booking 'pending' FOREVER, invisible to Pass A. Added Pass B: JOIN succeeded-payment + pending-booking → confirmBooking.
+- refund's booking UPDATE now guarded `WHERE status IN ('confirmed','pending')` (can't clobber completed/cancelled).
+
+**Residual (documented):** the adapters' `mapStatus` coerces an UNKNOWN provider status to 'processing', so an enabled force-fail could mislabel a disputed charge — mitigated by force-fail off-by-default + the per-hit ops log.
+
+**Tests (+4, suite 193 → 197, real DB, self-cleaning, unique far-future date windows):** succeeded/failed/refunded resolution + fresh/unreachable left alone + opt-in force-fail; idempotent 2nd run; force-fail OFF-by-default leaves a past-TTL payment 'processing'; Pass B confirms a succeeded-but-pending booking.
+
+**GATED (drafted, not installed):** `pending-approval/2026-09-24-TK-10346-costa-reconciler-cron-GATED.md` — how/whether to schedule it (recommend standalone every-10m, force-fail OFF). Nothing scheduled.
+
+**Cost:** $0 (local + two Cody passes — the 2nd caught that force-fail-default-on was a double-charge invitation and that the module couldn't see its own headline orphan case).
+
+**Known flake (test-hygiene follow-up):** the real-DB test files occasionally (≈1/3 runs) collide under `node --test` parallelism on `place_id=1` + overlapping date windows (`bookings_no_overlap_stay` EXCLUDE) — intermittent, clears on rerun; the deterministic result is 197/197. New tests use unique far-future windows to avoid ADDING to it, but a cycle to give each real-DB test file its own place/window (or serialize them) would remove the flake.
+
+**Backlog:** approval-enforcement decision + reconciler-cron decision both on Steve's desk (2 gated). Provider-honored idempotency key + sig-encoding (live-only); processorFee schema prerequisite; per-event webhook auto-reply try/catch; the test-parallelism flake. Next cycle: fix the flake (deterministic green matters for the loop), or another cold audit.

← b86e7b6 costa-rica: stale-payment reconciler (dropped-webhook rescue  ·  back to Costa Rica  ·  costa-rica: make the test suite deterministic — serialize + b6fb0a1 →