From a938febdc1b6eb633dab871d376f8c27e20d585c Mon Sep 17 00:00:00 2001 From: jaxperro Date: Fri, 10 Jul 2026 11:25:52 -0400 Subject: [PATCH] docs: HANDOFF rev 6 + gotcha 17 (delayed-fill incident, disarm state) Co-Authored-By: Claude Fable 5 --- HANDOFF.md | 30 +++++++++++++++++++++++++++++- README.md | 11 +++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/HANDOFF.md b/HANDOFF.md index bceb2739..d32f7b13 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,4 +1,32 @@ -# Session handoff — 2026-07-10 (rev 5.1: live app on PUSH mode — awaiting first organic fill) +# Session handoff — 2026-07-10 (rev 6: delayed-fill incident fixed — DISARMED, re-arm is the user's call) + +## INCIDENT 2026-07-10 (resolved in code; bot left DISARMED) + +Push mode worked (~6s detection) — and exposed an executor bug: **in-play +markets accept orders with status `delayed` and ZERO matched at response +time**. The port read matched==0 as a rejection, logged an honest miss, and +forgot the order — six such orders then **filled at the exchange untracked** +(09:10–13:14Z, $30 real spend vs $5 booked; caps stopped binding; exits never +mirrored; CASH≠CHAIN alarmed exactly as designed). Damage net **≈ −$2.7** +(G2 g2/g3 and PlayTime g1 lost; Rune Eaters +$7.50 and Aurora g1 +$3.77 won, +platform auto-redeemed; Aurora g2 won, redeem pending). First diagnosis +wrongly blamed a second bot instance — the missed ledger's +"order rejected: {'order_id': …}" rows matching on-chain fills to the second +settled it: it was THIS bot. + +**Fix (copybot.py, unit-tested with a stub client, 5 paths):** the executor +invariant is now *no order outlives `_order()` untracked* — zero-matched +acceptances poll `get_order`, cancel the remainder, and measure truth via the +exchange's CONDITIONAL balance diff; exception paths sweep all open orders on +the token the same way. State surgery done disarmed: cash = chain ($10.03), +six fills adopted as bets, spend tracker = the real $35 today (over cap → a +re-arm today places nothing new), false miss rows purged, −$0.59 adjustment +documented. + +**DISARMED via `flyctl secrets unset LIVE_CONFIRM` (survives restarts — +note `machine stop` does NOT: http_service auto-restarts the machine). +Re-arm = user types `flyctl secrets set LIVE_CONFIRM="TRADE LIVE"` after +reviewing the fix. Recommended: re-run a probe on an IN-PLAY market first.** Self-contained pickup for a fresh session (human or AI). Read [README.md](README.md) gotchas 1–16 (16 is the new-stack one), diff --git a/README.md b/README.md index 5078f7ce..01a8bdec 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,17 @@ runner is retired (GitHub throttled `*/5` to ~2h in practice — it copied 1 of — revoke in-process before exit (`atexit` in the probe) or they pile up unrevocable-but-inert on the account (9 accumulated during bring-up; the UI at polymarket.com/settings?tab=builder can clean those). +17. **In-play order acceptance reports ZERO matched — it is NOT a rejection.** + In-play (esports/live-game) markets return `AcceptedOrder` with status + `delayed` and `making/taking = 0`; the match happens seconds-to-minutes + later at the exchange. Treating zero-matched as a miss caused the + 2026-07-10 incident: six $5 copies were logged as misses and filled + untracked — the book went blind, caps stopped binding, exits were never + mirrored (net −$2.7; the CASH≠CHAIN alarm caught it). The executor's + invariant since: **no order outlives the placing call untracked** — + poll `get_order`, cancel the remainder, then measure the fill as the + exchange's CONDITIONAL-balance diff (chain truth beats response parsing). + Same sweep runs on exception paths (a timed-out POST may still fill). ---