File size: 22,724 Bytes
12f1e35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da15be0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12f1e35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da15be0
12f1e35
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
#!/usr/bin/env python3
"""
scripts/test_hotpath.py — the v1.71 send-path fixes, asserted.

    venv/bin/python scripts/test_hotpath.py

Every check here corresponds to a bug that was live in production and that
no existing test could have caught, because each one failed SILENTLY:

  • the signal announce was awaited on the path between finding an edge and
    acting on it, contradicting its own comment
  • no gate anywhere compared a quote's age against a budget
  • quote age, once measured, must not be booked as a pipeline STAGE —
    it overlaps the trace instead of adding to it, and would corrupt
    unaccounted_ms()
  • rate_governor.interval_secs was CALLED as a method though it is a
    property, raising TypeError into a bare `except: pass`
  • `tip` and `diversify` were declared in STAGES and never recorded
  • scanner and executor disagreed about the floor on the same env var
  • a new CSV column must not throw away the existing trace history

Dependency-free (no pytest) and runs in about a second, so it can go in
scripts/check.sh and be run from a phone — same contract as the other
test_*.py files here.
"""

from __future__ import annotations

import ast
import inspect
import os
import sys
import tempfile
import time
from pathlib import Path
from typing import Any

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

FAILURES: list[str] = []
CHECKS = 0


def check(label: str, condition: bool, detail: str = "") -> None:
    global CHECKS
    CHECKS += 1
    if condition:
        print(f"  \033[32m✓\033[0m {label}")
    else:
        FAILURES.append(f"{label}{f' — {detail}' if detail else ''}")
        print(f"  \033[31m✗\033[0m {label}" + (f" — {detail}" if detail else ""))


ROOT = Path(__file__).resolve().parent.parent


def code_of(rel: str) -> str:
    """Source with comment lines stripped.

    These checks assert on what the code DOES, and this file's fixes are
    heavily commented with the very strings being searched for — the first
    run of this suite failed on its own explanatory comment quoting the bug
    it had just fixed. Comments describe history; only code is behaviour.
    """
    out = []
    for line in (ROOT / rel).read_text(encoding="utf-8").splitlines():
        stripped = line.lstrip()
        if stripped.startswith("#"):
            continue
        out.append(line.split("  # ")[0] if "  # " in line else line)
    return "\n".join(out)


# ── 1. the announce must not be awaited on the hot path ──────────────────
def test_announce_not_awaited() -> None:
    print("\n\033[1m1. the signal announce is off the hot path\033[0m")
    src = code_of("modules/solana_arb.py")

    check("_fire_and_forget helper exists", "def _fire_and_forget(" in src)

    # The precise regression: `await self._announce(` anywhere in the file.
    # Both call sites are now scheduled, so this substring must be gone.
    check(
        "no `await self._announce(` remains",
        "await self._announce(" not in src,
        "an awaited announce is a Telegram round trip between signal and send",
    )
    check(
        "signal announce is scheduled via _fire_and_forget",
        "_fire_and_forget(" in src and "_format_signal_found(opp)" in src,
    )

    # A strong reference must be held, or asyncio may collect the task
    # mid-flight and the message is silently never sent.
    check("background tasks are strongly referenced", "_BG_ANNOUNCES" in src)


# ── 2. the quote-age gate ────────────────────────────────────────────────
def test_quote_age_gate() -> None:
    print("\n\033[1m2. a stale signal is refused before anything is built\033[0m")
    src = code_of("modules/solana_executor.py")

    check("SOLANA_MAX_QUOTE_AGE_MS is read", "SOLANA_MAX_QUOTE_AGE_MS" in src)
    check("the gate exists", "_MAX_QUOTE_AGE_MS > 0 and quote_age_ms > _MAX_QUOTE_AGE_MS" in src)
    check("it aborts with a named outcome", 'declined:quote_too_old' in src)

    # THE ORDERING CLAIM — and the whole point of the gate. It must sit
    # before the work it exists to avoid paying for. Compare source offsets:
    # everything expensive has to come after it.
    gate_at = src.find("declined:quote_too_old")
    check("gate is present to locate", gate_at > 0)
    if gate_at > 0:
        for stage in ("swap_ix", "blockhash", '"build"', '"sim"', '"send"'):
            first_after = src.find(f'trace.stage({stage})', gate_at)
            first_any = src.find(f'trace.stage({stage})')
            check(
                f"  ...before the `{stage.strip(chr(34))}` stage",
                first_any == -1 or first_after == first_any,
                "an expired quote must not reach this stage",
            )

    # 400ms default == one Solana slot. Not arbitrary; assert it stays.
    from modules import solana_executor as ex

    check(
        "default budget is one 400ms slot",
        abs(ex._MAX_QUOTE_AGE_MS - 400.0) < 1e-9,
        f"got {ex._MAX_QUOTE_AGE_MS}",
    )


# ── 3. quote age must not corrupt unaccounted_ms ─────────────────────────
def test_quote_age_is_not_a_stage() -> None:
    print("\n\033[1m3. quote age is a field, not a stage\033[0m")
    from modules.pipeline_trace import STAGES, PipelineTrace

    check("quote_age is NOT in STAGES", "quote_age" not in STAGES and
          "quote_age_ms" not in STAGES,
          "a stage is subtracted from wall clock; quote age overlaps it")

    t = PipelineTrace(route="USDC->SOL->USDC")
    check("PipelineTrace has a quote_age_ms field", hasattr(t, "quote_age_ms"))

    # The real invariant: booking a large quote age must not eat the
    # unaccounted figure, which is the only signal that instrumentation is
    # incomplete.
    with t.stage("quote1"):
        time.sleep(0.02)
    t.quote_age_ms = 5_000.0          # absurdly stale, far exceeds wall clock
    unacc = t.unaccounted_ms()
    check(
        "a 5,000ms quote age does not zero unaccounted_ms",
        unacc > 0.0,
        f"unaccounted collapsed to {unacc:.1f}ms — quote age is being subtracted",
    )
    check(
        "unaccounted_ms stays below total",
        unacc <= t.total_ms() + 1e-6,
    )


# ── 4. the governor property/method bug ──────────────────────────────────
def test_governor_property_not_called() -> None:
    print("\n\033[1m4. rate governor is actually reachable from the send path\033[0m")
    from modules.rate_governor import get_governor

    gov = get_governor("jupiter")
    check(
        "interval_secs is a property, not a method",
        isinstance(gov.interval_secs, float),
        f"got {type(gov.interval_secs).__name__}",
    )

    # Calling it is the bug. Prove it still raises, so the assertion below
    # is meaningful rather than tautological.
    raised = False
    try:
        gov.interval_secs()          # type: ignore[operator]
    except TypeError:
        raised = True
    check("calling it raises TypeError (the original bug)", raised)

    src = code_of("modules/solana_executor.py")
    check(
        "solana_executor does NOT call it",
        ".interval_secs()" not in src,
        "TypeError swallowed by a bare except == governor silently unused",
    )
    check("solana_executor reads it as a property", ".interval_secs" in src)


# ── 5. declared stages are actually recorded ─────────────────────────────
def test_declared_stages_are_recorded() -> None:
    print("\n\033[1m5. every declared stage is written by someone\033[0m")
    from modules.pipeline_trace import STAGES

    src = code_of("modules/solana_executor.py")

    # `pace` is recorded via trace.add(), not trace.stage() — accept either.
    for stage in STAGES:
        recorded = (f'trace.stage("{stage}")' in src) or (f'trace.add("{stage}"' in src)
        check(f"`{stage}` is recorded", recorded,
              "declared in STAGES but never written — lands in unaccounted_ms")


# ── 6. the two files agree about one env var ─────────────────────────────
def test_pacing_floor_agrees() -> None:
    print("\n\033[1m6. scanner and executor agree on the pacing floor\033[0m")
    from modules import solana_arb, solana_executor

    check(
        "same effective SOLANA_JUPITER_MIN_INTERVAL_SECS",
        abs(solana_arb._JUPITER_MIN_CALL_INTERVAL_SECS
            - solana_executor._JUPITER_MIN_CALL_INTERVAL_SECS) < 1e-9,
        f"arb={solana_arb._JUPITER_MIN_CALL_INTERVAL_SECS} "
        f"executor={solana_executor._JUPITER_MIN_CALL_INTERVAL_SECS}",
    )
    check(
        "executor honours the 2.0s floor",
        solana_executor._JUPITER_MIN_CALL_INTERVAL_SECS >= 2.0
        or solana_executor._JUPITER_ALLOW_FAST_PACING,
    )


# ── 7. the tip bid no longer awaits the network ──────────────────────────
def test_tip_bid_is_non_blocking() -> None:
    print("\n\033[1m7. the tip bid does not await an HTTP call\033[0m")
    from modules.jito_tip_engine import JitoTipEngine

    check("decide_fast exists", hasattr(JitoTipEngine, "decide_fast"))
    check(
        "decide_fast is NOT a coroutine",
        not inspect.iscoroutinefunction(JitoTipEngine.decide_fast),
        "a synchronous bid cannot wait on bundles.jito.wtf",
    )

    src = code_of("modules/solana_executor.py")
    check(
        "executor no longer awaits decide_live",
        "await _get_tip_engine().decide_live(" not in src,
    )
    check("executor uses decide_fast", "decide_fast(" in src)
    check("the tip bid is timed", 'trace.stage("tip")' in src)


# ── 8. the CSV schema change keeps old history ───────────────────────────
def test_trace_history_survives_schema_change() -> None:
    print("\n\033[1m8. adding a column did not throw away the history\033[0m")
    from modules.pipeline_trace import _COLUMNS, _COLUMNS_LEGACY

    check(
        "legacy column set is exactly one narrower",
        len(_COLUMNS) == len(_COLUMNS_LEGACY) + 1,
    )
    check("quote_age_ms is the added column", "quote_age_ms" in _COLUMNS
          and "quote_age_ms" not in _COLUMNS_LEGACY)

    with tempfile.TemporaryDirectory() as tmp:
        path = Path(tmp) / "legacy.csv"
        n_stage_cols = len(_COLUMNS_LEGACY) - 6
        path.write_text(
            ",".join(_COLUMNS_LEGACY) + "\n"
            + "2026-08-01T10:00:00+00:00,USDC->SOL->USDC,sent,912.4,120.0,350000,"
            + ",".join(["5.0"] * n_stage_cols) + "\n",
            encoding="utf-8",
        )
        os.environ["PIPELINE_TRACE_PATH"] = str(path)
        import importlib

        from modules import pipeline_trace as pt
        importlib.reload(pt)
        log = pt.PipelineLog()
        check("an old-format row still loads", len(log._rows) == 1,
              f"loaded {len(log._rows)} rows — history would be lost")
        if log._rows:
            row = log._rows[0]
            check("its data is intact", row["route"] == "USDC->SOL->USDC"
                  and abs(float(row["total_ms"]) - 912.4) < 1e-6)
            check(
                "missing quote_age reads as absent, not as zero",
                row.get("quote_age_ms") == "",
                "0.0 would claim it was measured and instantaneous",
            )
        os.environ.pop("PIPELINE_TRACE_PATH", None)
        importlib.reload(pt)


# ── 9. an edge that cannot pay the auction is refused early ──────────────
def test_auction_affordability_gate() -> None:
    print("\n\033[1m9. an edge that cannot pay to land is refused before re-quoting\033[0m")
    import types

    os.environ["SOLANA_JITO_ENABLED"] = "true"
    os.environ["SOLANA_MIN_REAL_NET_MARGIN"] = "0.35"

    import modules.solana_arb as sa
    from modules import tip_memory as tm

    eng = sa.SolanaArbEngine.__new__(sa.SolanaArbEngine)
    eng._last_sol_usd_price = 73.0             # from the operator's report

    def opp(net_usd: float) -> Any:
        return types.SimpleNamespace(net_profit_usd=net_usd)

    original = tm.remembered_floor
    try:
        # 0.0032 SOL — the p99 competitive floor the operator was shown.
        tm.remembered_floor = lambda max_age_secs=21600.0: (int(0.0032 * 1e9), 1800.0)

        # THE REGRESSION CASE: the actual best signal of 2026-08-04. It was
        # re-quoted, priced and declined 106 times. It must now not get that far.
        check(
            "the real +$0.0614 signal is declined",
            bool(eng._unaffordable_auction_reason(opp(0.0614))),
            "tip alone is ~$0.23 — landing this loses money",
        )
        check(
            "an edge below tip+margin is declined",
            bool(eng._unaffordable_auction_reason(opp(0.58))),
        )
        check(
            "an edge above tip+margin passes",
            not eng._unaffordable_auction_reason(opp(1.50)),
            "the gate must only ever decline, never over-decline",
        )

        # Rule 2: unknown must never collapse into free.
        tm.remembered_floor = lambda max_age_secs=21600.0: (0, 0.0)
        check(
            "an UNKNOWN floor abstains rather than assuming zero",
            not eng._unaffordable_auction_reason(opp(0.0614)),
            "treating unknown as free makes the safest state the most permissive",
        )

        # No bundling means no auction, so no tip to clear.
        tm.remembered_floor = lambda max_age_secs=21600.0: (int(0.0032 * 1e9), 1800.0)
        os.environ["SOLANA_JITO_ENABLED"] = "false"
        check(
            "with Jito off the gate abstains",
            not eng._unaffordable_auction_reason(opp(0.0614)),
        )
    finally:
        tm.remembered_floor = original
        os.environ.pop("SOLANA_JITO_ENABLED", None)
        os.environ.pop("SOLANA_MIN_REAL_NET_MARGIN", None)


# ── 10. discover.py screens and walks the same size ──────────────────────
def test_discover_ladder_starts_at_probe() -> None:
    print("\n\033[1m10. discover walks the size it screened at\033[0m")
    import importlib.util

    spec = importlib.util.spec_from_file_location(
        "_discover_probe", ROOT / "scripts" / "discover.py")
    src = (ROOT / "scripts" / "discover.py").read_text(encoding="utf-8")
    ns: dict = {}
    exec(src[src.index("def _build_ladder"):src.index("async def main")], ns)
    build = ns["_build_ladder"]

    check(
        "the screening size is the first rung",
        build([250, 1000, 2500, 5000], 100.0)[0] == 100.0,
        "screening at 100 and starting the ladder at 250 drops every pair "
        "whose edge is real at 100",
    )
    check("existing rungs are preserved",
          build([250, 1000], 100.0) == [100.0, 250.0, 1000.0])
    check("a probe already present is not duplicated",
          build([100, 250], 100.0) == [100.0, 250.0])
    check("rungs come back sorted",
          build([5000, 250, 1000], 100.0) == [100.0, 250.0, 1000.0, 5000.0])

    # The silent-drop bug: a pair that dies must be reported, not skipped.
    check("pairs that die on the ladder are recorded",
          "near_misses" in src and "died_at" in src,
          "a bare `continue` made 'examined and rejected' look like "
          "'nothing to examine'")


# ── 11. the tip is anchored to the floor, not to a share of the edge ─────
def test_tip_is_floor_anchored() -> None:
    print("\n\033[1m11. the bid beats the clearing price, it does not donate the edge\033[0m")
    from modules.jito_tip_engine import JitoTipEngine

    e = JitoTipEngine()
    # The operator's real attempt, at $73/SOL: a $0.0643 edge against a live
    # 1,122-lamport floor. The old formula bid 397,910 — 355x the clearing
    # price — and the margin gate then refused to send, every time, which is
    # why /jito has reported `Bundles: 0/0` for the life of this deployment.
    R, C, floor = 880_821, 85_000, 1_122
    d = e.decide(R, C, floor, min_keep_lamports=int(0.02 / 73.0 * 1e9))

    check("it bids to WIN, not to donate", d.tip_lamports < 10_000,
          f"bid {d.tip_lamports:,} lamports against a {floor:,} floor")
    check("the bid still beats the floor", d.tip_lamports > floor)
    check("and it actually bundles now", d.should_bundle,
          "this exact trade produced 0 bundles under the old formula")

    kept = (R - C - d.tip_lamports) / 1e9 * 73.0
    check("it keeps most of the edge", kept > 0.05,
          f"kept ${kept:.4f} of a $0.0643 edge")

    # γ must remain a real ceiling — a huge floor on a small edge must not
    # produce a bid that loses money.
    d2 = e.decide(20_000, 5_000, 100_000, min_keep_lamports=0)
    check("γ still caps: a small edge is priced out, not overbid",
          not d2.should_bundle and d2.tip_lamports == 0)

    # An unknown floor must not collapse the bid to the minimum.
    d3 = e.decide(R, C, 0, min_keep_lamports=0)
    check("no known floor falls back to share-of-edge",
          d3.tip_lamports > 100_000,
          "bidding the bare minimum into an unknown auction would never land")


# ── 12. learned compute-unit sizing survives a restart ───────────────────
def test_cu_sizing_persists() -> None:
    print("\n\033[1m12. the priority fee is sized to what simulation measured\033[0m")
    src = code_of("modules/solana_executor.py")
    check("CU observations are persisted", "_save_cu_observations" in src
          and "_load_cu_observations" in src,
          "an in-memory learner that needs 5 rare samples never engages")
    check("they are loaded at import", "_load_cu_observations()" in src)
    check("the write is atomic", "tmp.replace(path)" in src,
          "a torn state file would be unreadable and silently reset sizing")


# ── 13. the scoreboard must not depend on machine uptime ────────────────
def test_route_scorer_computes_on_a_fresh_boot() -> None:
    print("\n\033[1m13. route scoring works on a machine that just booted\033[0m")
    from modules.route_scorer import _REFRESH_SECS, _Cache

    fresh = _Cache()
    check(
        "the 'never computed' sentinel is -inf",
        fresh.computed_at == float("-inf"),
        f"got {fresh.computed_at!r}",
    )

    # THE BUG, stated as arithmetic. scores() recomputes when
    # `time.monotonic() - computed_at > _REFRESH_SECS`, and on Linux
    # time.monotonic() is seconds since boot. With a 0.0 sentinel that
    # condition asks "has this machine been up for 3 minutes?" instead of
    # "has the scoreboard ever been computed?" — so for the first
    # _REFRESH_SECS after any reboot, scores() returns {}, focus_set()
    # returns None, and plan_cycle applies neither focus nor pruning.
    for uptime in (0.5, 30.0, 179.0):
        check(
            f"  ...at {uptime:.0f}s uptime it still computes",
            (uptime - fresh.computed_at) > _REFRESH_SECS,
            "an empty scoreboard silently disables all pruning and focus",
        )
    check(
        "the old 0.0 sentinel really was broken (so this test means something)",
        not ((30.0 - 0.0) > _REFRESH_SECS),
    )


# ── 14. a 429 costs a full 10 seconds, everywhere ────────────────────────
def test_rate_limit_backoff_is_ten_seconds() -> None:
    print("\n\033[1m14. a 429 pauses 10s in all three paths\033[0m")
    import asyncio

    # Triton: "you MUST pause all requests from that IP for 10 seconds.
    # Attempting to retry immediately will only result in more 429 errors."
    # Three code paths had their own shorter ladder; all three are asserted
    # here because fixing one and leaving two is the same bug.

    # (1) the executor's Jupiter retry
    from modules import solana_executor as ex

    check("executor has a 429-specific backoff",
          ex._RATE_LIMIT_BACKOFF_SECS >= 10.0,
          f"got {ex._RATE_LIMIT_BACKOFF_SECS}")
    check("and it is longer than the ordinary ladder",
          ex._RATE_LIMIT_BACKOFF_SECS > ex._JUPITER_RETRY_MAX_DELAY_SECS,
          "a 429 must not share the timeout ladder")

    class _429(Exception):
        pass

    check("a 429 is recognised", ex._is_rate_limit_error(Exception("HTTP 429")))
    check("a timeout is NOT treated as a 429",
          not ex._is_rate_limit_error(Exception("read timeout")),
          "paying 10s for a network blip costs more signals than it saves")

    # (2) transport.py's retry ladder
    from modules.transport import Transport, TransportConfig

    t = Transport.__new__(Transport)
    t.config = TransportConfig()
    check("transport pauses >=10s on 429", t._backoff_delay(1, 429) >= 10.0,
          f"got {t._backoff_delay(1, 429):.1f}s")
    check("transport stays fast on a 500", t._backoff_delay(1, 500) < 2.0,
          "a server error is not a rate limit")

    # (3) the rate governor — halving the rate is not pausing
    from modules.rate_governor import RateGovernor

    async def _probe() -> float:
        import time as _t

        g = RateGovernor("test-429")
        await g.wait()
        g.rate_limited("simulated")
        t0 = _t.monotonic()
        await g.wait()
        return _t.monotonic() - t0

    waited = asyncio.run(_probe())
    check("governor serves out the full pause", waited >= 10.0,
          f"waited {waited:.1f}s — halving the rate alone still releases "
          f"the next call in ~2s, which re-arms the limit")


def main() -> int:
    print("\n\033[1m══════ v1.71 send-path regressions ══════\033[0m")
    test_announce_not_awaited()
    test_quote_age_gate()
    test_quote_age_is_not_a_stage()
    test_governor_property_not_called()
    test_declared_stages_are_recorded()
    test_pacing_floor_agrees()
    test_tip_bid_is_non_blocking()
    test_trace_history_survives_schema_change()
    test_auction_affordability_gate()
    test_discover_ladder_starts_at_probe()
    test_tip_is_floor_anchored()
    test_cu_sizing_persists()
    test_route_scorer_computes_on_a_fresh_boot()
    test_rate_limit_backoff_is_ten_seconds()

    print()
    if FAILURES:
        print(f"\033[31m❌ {len(FAILURES)} of {CHECKS} checks FAILED\033[0m")
        for f in FAILURES:
            print(f"   • {f}")
        print()
        return 1
    print(f"\033[32m✅ all {CHECKS} checks passed\033[0m\n")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())