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 haltedpub async fn bump_breaker(pool, error_class: &str)— the public entry point; call sites inexecute.rsnow route here viacrate::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_breakerfn andBREAKER_THRESHOLDconst - Updated call sites to
crate::pmv2::autotrade::breaker::bump_breaker - Moved
breaker_countsimport 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.shexit 0- Zero clippy warnings
Related
- pmv2-autotrade-engine-design — overall auto-trade architecture; §3.4 lock order documented in the design spec
- autotrade-persistence-engine-config —
FOR UPDATElock on config row id=1 (same lock the breaker tx acquires) - 2026-06-15-autotrade-execute-rs —
execute.rspre-POST assertions; original home ofbump_breakerbefore this refactor - autotrade-signing-client-order-ops — CLOB place_order/cancel bindings; the execution layer that calls
bump_breakeron failure