Pure refactor (Task 5, branch autotrade-foundation): bump_breaker extracted from execute.rs into its own breaker.rs module, SQL moved into repo.rs. Zero behavior change. 282 tests pass, gate green, zero clippy warnings.

What Changed

New: crates/polymarket-fetch/src/pmv2/autotrade/breaker.rs

Owns the circuit breaker domain:

  • BREAKER_THRESHOLD: u32 = 5 — number of consecutive failures before entries are halted
  • pub async fn bump_breaker(pool, error_class: &str) — the public entry point; call sites in execute.rs now route here via crate::pmv2::autotrade::breaker::bump_breaker
  • The §3.4 structural regression test (see below)

Modified: crates/polymarket-fetch/src/pmv2/repo.rs

Added pub async fn bump_breaker_count(pool, threshold: u32).

The transaction in bump_breaker_count touches only the config row (pmv2_autotrade_config WHERE id=1 FOR UPDATE). It runs after the order-row transaction has already committed, which is the invariant the §3.4 structural test enforces.

Modified: crates/polymarket-fetch/src/pmv2/autotrade/execute.rs

  • Removed local bump_breaker fn and BREAKER_THRESHOLD const
  • Updated call sites to crate::pmv2::autotrade::breaker::bump_breaker
  • Moved breaker_counts import from top-level into the #[cfg(test)] module (was only used in tests)

Modified: crates/polymarket-fetch/src/pmv2/autotrade/mod.rs

Added pub mod breaker;.

§3.4 Lock Order Invariant

Safety Invariant — Config Row Before Order Row

The config row (pmv2_autotrade_config WHERE id=1) must always be locked BEFORE any order row (pmv2_autotrade_orders ... FOR UPDATE). Violating this order risks an ABBA deadlock. The breaker transaction is config-only and runs after the order-row tx commits, which is the correct sequencing.

This is not just a convention — it is enforced by a structural regression test baked into breaker.rs that runs on every cargo test.

Structural Test: first_lock_order_violation

fn first_lock_order_violation(src: &str) -> Option<String>
  • Parses Rust source text as a string
  • Tracks function body boundaries via brace-depth matching (not an AST parser — pattern-based, sufficient for the narrow lock-order check)
  • Within each function body, scans for the first occurrence of an order-row lock (pmv2_autotrade_orders ... FOR UPDATE) and a config-row lock (pmv2_autotrade_config ... FOR UPDATE)
  • Returns Some(fn_name) if an order-row lock precedes a config-row lock in the same function

The integration test in breaker.rs reads every real source file in src/pmv2/autotrade/*.rs plus src/pmv2/repo.rs on every cargo test run and asserts first_lock_order_violation returns None for each.

For Agents

The structural test is a regression guard, not a linter. It does not prevent adding new lock sites — it prevents adding them in the wrong order. Any future function that locks both a config row and an order row must lock config first or this test will fail and block the build.

Why a Separate Module

Keeping bump_breaker in execute.rs mixed execution logic (signing, submitting) with failure-accounting logic. breaker.rs now has a single responsibility: track failures and trip the breaker. The separation makes it easier to extend (e.g., per-source breakers, reset endpoints) without touching the execution path.

Test Results

  • 282 tests pass
  • gate.sh exit 0
  • Zero clippy warnings