From ab56b2aa4bba5041026893242ff2d11afc42c8a3 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Sun, 19 Jul 2026 16:46:28 -0400 Subject: [PATCH] =?UTF-8?q?reconcile=5Fentries=20cadence=205m=E2=86=9230m?= =?UTF-8?q?=20(closes=20#12);=20tape=20transport=20sftp-first=20with=20bas?= =?UTF-8?q?e64=20fallback=20(closes=20#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- copybot.py | 8 ++++++++ recorder/ingest.py | 29 ++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/copybot.py b/copybot.py index 496fd060..d861474f 100644 --- a/copybot.py +++ b/copybot.py @@ -2669,6 +2669,10 @@ def main(): bot.settle_resolved() # recycle capital at resolution if cycle % 5 == 0: bot.reconcile_exits() + if cycle % 30 == 0: + # closes #12: entries-reconcile is miss BOOKKEEPING, not a + # trading decision — the dominant steady-state API load + # (positions?limit=500/wallet) needs no 5-min cadence bot.reconcile_entries() for w in cfg.get("watchlist", []): try: @@ -2708,6 +2712,10 @@ def main(): bot.sweep_dust(cycle) # reclaim untracked exit residue (live) if cycle % 5 == 0: bot.reconcile_exits() + if cycle % 30 == 0: + # closes #12: entries-reconcile is miss BOOKKEEPING, not a + # trading decision — the dominant steady-state API load + # (positions?limit=500/wallet) needs no 5-min cadence bot.reconcile_entries() for w in cfg.get("watchlist", []): bot.on_wallet_activity(w) diff --git a/recorder/ingest.py b/recorder/ingest.py index c5c07f33..3d161c8e 100644 --- a/recorder/ingest.py +++ b/recorder/ingest.py @@ -63,12 +63,31 @@ def main(): ping(f"📼 tape ingest started — {len(segs)} segment(s) queued") total = 0 for s in sorted(segs): - raw = box(f"base64 {SEG}/{s}") + # closes #11: sftp moves the gz BINARY in one session (base64-over- + # console was +33% bytes and needed 900s timeouts at busy-hour sizes); + # the old path stays as the fallback because sftp exits 0 even on + # some failures — the gunzip is the integrity check either way. + lines = None + local = os.path.join("/tmp", s) try: - lines = gzip.decompress(base64.b64decode(raw)).decode().splitlines() - except Exception as e: - print(f"[ingest] {s}: fetch/decode failed ({e}) — left on box") - continue + subprocess.run([FLYCTL, "ssh", "sftp", "get", f"{SEG}/{s}", local, + "-a", APP], capture_output=True, timeout=300) + with gzip.open(local, "rb") as fh: + lines = fh.read().decode().splitlines() + except Exception: + lines = None + finally: + try: + os.remove(local) + except OSError: + pass + if lines is None: + raw = box(f"base64 {SEG}/{s}") + try: + lines = gzip.decompress(base64.b64decode(raw)).decode().splitlines() + except Exception as e: + print(f"[ingest] {s}: fetch/decode failed ({e}) — left on box") + continue rows, aux = [], [] for ln in lines: try: