Skip to content

UNKNOWN and Reconcile

Marcus Richards edited this page Oct 7, 2026 · 2 revisions

UNKNOWN and Reconcile

A timed-out money call has unknown provider state. Permit never guesses.

What makes an escrow UNKNOWN

Trigger Receipt reason
PayPalTimeout on capture capture_timeout
Capture returned PENDING capture_pending

While an escrow is UNKNOWN, release() returns unknown_reconcile_first and makes no PayPal calls.

reconcile(escrow_id)

POST /api/escrows/<id>/reconcile → ReleaseVerifier.reconcile():

  1. If the escrow is not UNKNOWN → already_resolved, with no side effects (idempotent).
  2. Query provider truth (get_authorization). On timeout → still_unknown (retry later).
  3. If truth is CAPTURED (or the mock has a capture under the escrow's idempotency key) → record CAPTURED with reconciled: true. This holds even if the permit has since been revoked: a completed capture cannot be voided.
  4. Otherwise, fail closed: void the hold → VOIDED, reservation freed. On a void timeout → still_unknown.

Outcomes: captured, voided, still_unknown, already_resolved.

CLEANUP_PENDING and retry_cleanup

If a void times out after a refusal, a failed admission, a broken chain or a failed capture, the escrow goes CLEANUP_PENDING and the reservation stays held. It is never silently released. POST /api/escrows/<id>/retry-cleanup retries the void. It returns cleanup_cleared: true on success.

What counts as a timeout

  • Sandbox: transport failures only (URLError that is not an HTTPError, socket.timeout, TimeoutError). Each request uses urlopen(..., timeout=30) (30 s). HTTP error responses are not timeouts.
  • Mock: fault injection. inject_capture_timeout = "before_apply" | "after_apply", inject_void_timeout = True, capture_status = "PENDING".

Proof

  • demo_six_beat.py beat 6 (mock, after_apply): UNKNOWN → reconcile → one capture.
  • Sandbox: no recorded sandbox run is in the tree at 9900817. A 2026-10-05 run log was removed from the public tree in PR #5, so it is not cited here.
  • Tests: tests/test_reconcile.py (6) and tests/test_server.py::test_reconcile_route_converges_unknown.

Clone this wiki locally