fix strategy state persistence corrections

This commit is contained in:
Hermes Agent
2026-08-29 16:11:02 +09:00
parent 4699de61cc
commit 3854aabd0d
11 changed files with 190 additions and 85 deletions
+43 -39
View File
@@ -223,7 +223,16 @@ def make_slots_rpc_handler(
def handler(req: RpcRequest) -> RpcResponse:
body = req.payload or {}
filter_val = str(body.get("filter") or "all").lower()
spy = bool(body.get("spy", False))
spy_value = body.get("spy", False)
if not isinstance(spy_value, bool):
return RpcResponse(payload={
"slots": [],
"available_slots": None,
"degraded": False,
"reason": "bad_request:spy",
"message": "spy must be a boolean",
})
spy = spy_value
strategy_instance_id = body.get("strategy_instance_id")
if not spy and (not isinstance(strategy_instance_id, str) or not strategy_instance_id):
return RpcResponse(payload={
@@ -268,10 +277,42 @@ def make_slots_rpc_handler(
for s in slots
]
# AC-0023 exclusion must precede all optional data readers so excluded
# slots cannot expose state or make an otherwise valid response fail.
excluded_count = 0
if warn_store is not None:
try:
c_warned_ids = warn_store.list_c_case_warned()
except Exception: # noqa: BLE001
_log.exception("warn_store.list_c_case_warned() failed — fail-closed")
return RpcResponse(payload={
"slots": [],
"available_slots": None,
"degraded": True,
"reason": "warn_store_unavailable",
"message": (
"reconcile warning store unavailable; "
"returning no slots (fail-closed)"
),
"excluded_slots": {"reconcile_quantity_mismatch": 0},
})
if c_warned_ids:
included = [
(slot, row)
for slot, row in zip(slots, slot_rows, strict=True)
if not (
slot.status.value == "holding"
and str(slot.slot_id) in c_warned_ids
)
]
excluded_count = len(slots) - len(included)
slots = [slot for slot, _ in included]
slot_rows = [row for _, row in included]
broker_qty_by_symbol: dict[str, int] = {}
broker_qty_available = False
broker_qty_captured_at: Optional[datetime] = None
if broker_quantity_reader is not None:
if broker_quantity_reader is not None and slots:
try:
holdings, broker_qty_captured_at = broker_quantity_reader.holdings_for_slots_rpc()
broker_qty_by_symbol = {
@@ -361,43 +402,6 @@ def make_slots_rpc_handler(
if strategy_filter is not None:
slot_rows = [r for r in slot_rows if r.get("strategy_name") == strategy_filter]
# AC-0023: exclude holding slots with active case_code='C' reconcile warning.
# Fail-closed: if warn_store exists but throws, return degraded=True / no slots.
excluded_count = 0
if warn_store is not None:
try:
c_warned_ids = warn_store.list_c_case_warned()
except Exception: # noqa: BLE001
_log.exception("warn_store.list_c_case_warned() failed — fail-closed")
return RpcResponse(payload={
"slots": [],
"available_slots": available_slots,
"degraded": True,
"reason": "warn_store_unavailable",
"message": (
"reconcile warning store unavailable; "
"returning no slots (fail-closed)"
),
"excluded_slots": {"reconcile_quantity_mismatch": 0},
})
if c_warned_ids:
# Rebuild slot lookup from the original `slots` list (before strategy
# filter reduced slot_rows). slot_rows[i] corresponds to slots[i]
# only before filtering; use slot_id stored in each row.
slot_by_id = {str(s.slot_id): s for s in slots}
filtered_rows: list[dict] = []
for row in slot_rows:
slot_obj = slot_by_id.get(str(row.get("slot_id", "")))
if (
slot_obj is not None
and str(slot_obj.slot_id) in c_warned_ids
and slot_obj.status.value == "holding"
):
excluded_count += 1
else:
filtered_rows.append(row)
slot_rows = filtered_rows
base_payload: dict = {
"slots": slot_rows,
"available_slots": available_slots,