Merge remote-tracking branch 'origin/main' into copilot/feat-reconcile-unmanaged-position-durable-lifecycl

# Conflicts:
#	.proofline/criteria/ac-0029.md
#	.proofline/identities.json
#	tests/adapters/persistence/test_migration_and_drift.py

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-13 02:53:22 +00:00
committed by GitHub
co-authored by genichin
40 changed files with 1254 additions and 276 deletions
+24 -10
View File
@@ -742,7 +742,7 @@ def sync_slot_to_broker(
Broker qty == 0 for slot → skipped with WARN (slot-side A-case guard).
Broker-only (no slot) → NOT handled here; caller filters (FR-3).
After update, if slot.qty == broker_qty: clear_warned(slot_id).
After update, if slot.qty == broker_qty: clear_warned(slot_id, case_code="B").
"""
from uuid import UUID
@@ -983,14 +983,15 @@ def sync_slot_to_broker(
after_avg = float(refreshed.avg_price) if (refreshed and refreshed.avg_price is not None) else None
after = {"qty": after_qty, "status": after_status, "avg_price": after_avg}
# Clear warning when qty now matches broker
# Clear B-case warning when qty now matches broker
warning_cleared = False
if after_qty is not None and int(after_qty) == broker_qty:
try:
warn_store.clear_warned(slot_id)
warn_store.clear_warned(slot_id, case_code="B")
warning_cleared = True
_log.info(
"sync: reconcile-warning cleared slot=%s symbol=%s broker_qty=%d actor=sync",
"sync: reconcile-warning cleared slot=%s symbol=%s case_code=B "
"broker_qty=%d actor=sync",
slot_id, sym, broker_qty,
)
except Exception: # noqa: BLE001
@@ -1075,7 +1076,7 @@ def verify_slot_reconcile(
"reason": "no active reconcile-warning (already cleared or never set)",
}
# Check case_code — verify is C-only (REQ-106 OQ-C).
# Check case set — verify is C-only (REQ-106 OQ-C).
info = warn_store.get_warning_info(slot_id_str)
if info is None:
return {
@@ -1087,8 +1088,13 @@ def verify_slot_reconcile(
"reason": "warning exists but get_warning_info returned None (unexpected)",
}
case_code = info.get("case_code", "")
if case_code != "C":
case_codes_raw = info.get("case_codes")
if isinstance(case_codes_raw, list):
case_codes = {str(code) for code in case_codes_raw if code}
else:
case_code = str(info.get("case_code", ""))
case_codes = {case_code} if case_code else set()
if "C" not in case_codes:
return {
"portfolio_id": portfolio_id,
"slot_id": slot_id_str,
@@ -1096,7 +1102,7 @@ def verify_slot_reconcile(
"hub_qty": None,
"broker_qty": None,
"reason": (
f"warning case_code='{case_code}' is not 'C' — "
f"warning case_codes={sorted(case_codes)} do not include 'C' — "
"use 'trex-core sync' to resolve B-case (slot↔broker qty mismatch)"
),
}
@@ -1127,9 +1133,17 @@ def verify_slot_reconcile(
continue
sid_str = str(s.slot_id)
warn_info = warn_store.get_warning_info(sid_str)
if warn_info is None or warn_info.get("case_code") != "C":
if warn_info is None:
continue
warn_store.clear_warned(sid_str)
warned_codes_raw = warn_info.get("case_codes")
if isinstance(warned_codes_raw, list):
warned_codes = {str(code) for code in warned_codes_raw if code}
else:
case_code = str(warn_info.get("case_code", ""))
warned_codes = {case_code} if case_code else set()
if "C" not in warned_codes:
continue
warn_store.clear_warned(sid_str, case_code="C")
store.set_warning(s.portfolio_id, False)
cleared_slots.append(sid_str)
_log.info(