diff --git a/tests/application/test_account_snapshot.py b/tests/application/test_account_snapshot.py index 45332ec..7152495 100644 --- a/tests/application/test_account_snapshot.py +++ b/tests/application/test_account_snapshot.py @@ -209,3 +209,15 @@ def test_slots_rpc_normalizes_snapshot_store_read_failure(): with pytest.raises(AccountSnapshotUnavailable) as raised: service.holdings_for_slots_rpc() assert raised.value.code == "read_failed" + + +def test_sell_stale_snapshot_preserves_transient_live_failure_detail_code(): + broker, _, service, now = _wire() + assert service.refresh() + broker.balance_error = "timeout" + now[0] += timedelta(seconds=3601) + + with pytest.raises(AccountSnapshotUnavailable) as raised: + service.holdings_for_sell() + assert raised.value.code == "expired" + assert raised.value.detail_code == "timeout" diff --git a/trex_core/application/account_snapshot.py b/trex_core/application/account_snapshot.py index a31a82b..ae90af6 100644 --- a/trex_core/application/account_snapshot.py +++ b/trex_core/application/account_snapshot.py @@ -214,6 +214,7 @@ class AccountSnapshotService: def holdings_for_sell(self) -> list[BalanceItem]: """Prefer a live balance; fallback only on transient transport failure.""" + transient_code: str | None = None try: return self._broker.get_balance() except BrokerBalanceError as exc: @@ -221,8 +222,14 @@ class AccountSnapshotService: raise AccountSnapshotUnavailable( "live_read_failed", detail_code=exc.code ) from None + transient_code = exc.code snapshot = self._load_current() - age = self._age(snapshot.holdings_captured_at, self._sell_max_age) + try: + age = self._age(snapshot.holdings_captured_at, self._sell_max_age) + except AccountSnapshotUnavailable as exc: + raise AccountSnapshotUnavailable( + exc.code, detail_code=transient_code + ) from None _log.warning( "sell balance using account snapshot: age_sec=%.0f", age, diff --git a/trex_core/application/execution.py b/trex_core/application/execution.py index 3e2eab3..777037b 100644 --- a/trex_core/application/execution.py +++ b/trex_core/application/execution.py @@ -443,9 +443,11 @@ class ExecutionService: try: holdings = self._sell_dispatch_balance_reader() except AccountSnapshotUnavailable as exc: - code = "stale_snapshot" if exc.code == "expired" else ( - getattr(exc, "detail_code", None) or exc.code - ) + detail_code = getattr(exc, "detail_code", None) + if exc.code == "expired" and detail_code is None: + code = "stale_snapshot" + else: + code = detail_code or exc.code lifecycle, backoff = self._balance_health.record_failure( self._balance_worker, code=code )