feat(intents): emit warn alarm on first reconcile_warned rejection

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-08-26 04:19:14 +00:00
committed by GitHub
co-authored by genichin
parent 98c38327b2
commit a9e88f9770
4 changed files with 318 additions and 0 deletions
@@ -58,6 +58,47 @@ class InMemoryIntentStore:
self._db[intent_id] = {"status": status, "slot_id": slot_id, "reason": reason}
class RecordingIntentStore(InMemoryIntentStore):
def __init__(self, trace=None, fail_finalize=False):
super().__init__()
self._trace = trace
self._fail_finalize = fail_finalize
def finalize(self, intent_id, status, slot_id, reason):
if self._trace is not None:
self._trace.append("finalize")
if self._fail_finalize:
raise RuntimeError("finalize failed")
super().finalize(intent_id, status, slot_id, reason)
class RecordingAlarmEmitter:
def __init__(self, *, trace=None, return_value=True, raise_error=False):
self.calls = []
self._trace = trace
self._return_value = return_value
self._raise_error = raise_error
def publish_alarm(self, code, title, *, fields=None, severity="critical"):
if self._trace is not None:
self._trace.append("publish")
self.calls.append((code, title, severity, fields or {}))
if self._raise_error:
raise RuntimeError("transport down")
return self._return_value
class DuplicateInFlightIntentStore:
def try_insert(self, intent_id, intent_type, payload):
return False
def get(self, intent_id):
return None
def finalize(self, intent_id, status, slot_id, reason):
raise AssertionError("finalize must not be called for duplicate_in_flight")
def _make_holding_slot(repo, svc, instance_key="default", portfolio_id="pf-A", symbol="005930", qty=10):
"""Helper: creates universe, puts slot in HOLDING state with given qty."""
repo.reconcile_universe(instance_key, {portfolio_id: symbol})
@@ -272,6 +313,206 @@ def test_intent_service_sell_rejected_when_reconcile_warned():
assert d.reason == "reconcile_warned"
def test_reconcile_warned_buy_emits_alarm_with_allowlisted_fields_after_finalize():
repo = InMemorySlotRepository()
svc = SlotService(repo)
warn_store = InMemoryReconcileWarningStore()
trace = []
intent_store = RecordingIntentStore(trace=trace)
alarms = RecordingAlarmEmitter(trace=trace)
repo.reconcile_universe("default", {"pf-A": "005930"})
slot = repo.find_slot_by_portfolio("default", "pf-A")
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="B")
intent_svc = IntentService(
svc, repo, intent_store,
instance_key="default",
warn_store=warn_store,
alarm_emitter=alarms,
)
d = intent_svc.submit_buy(BuyIntent(
intent_id="b-alert-001",
slot_id=slot.slot_id,
entry_price=100.0,
qty=10.0,
strategy_id="stg-buy",
))
assert d.status == "rejected"
assert d.reason == "reconcile_warned"
assert trace == ["finalize", "publish"]
assert len(alarms.calls) == 1
code, title, severity, fields = alarms.calls[0]
assert code == "strategy_intent_rejected"
assert severity == "warn"
assert title
assert fields["instance_key"] == "default"
assert fields["intent_id"] == "b-alert-001"
assert fields["intent_type"] == "buy"
assert fields["reason"] == "reconcile_warned"
assert fields["broker_submitted"] is False
assert fields["slot_id"] == str(slot.slot_id)
assert fields["portfolio_id"] == "pf-A"
assert fields["symbol"] == "005930"
assert fields["strategy_id"] == "stg-buy"
for blocked in ("qty", "entry_price", "limit_price", "venue", "extra", "sell_strategy_id"):
assert blocked not in fields
text = repr((title, fields))
for forbidden in ("account", "token", "password", "secret", "dsn", "connection", "raw_payload"):
assert forbidden not in text.lower()
def test_reconcile_warned_sell_emits_alarm_with_normalized_strategy_id():
repo = InMemorySlotRepository()
svc = SlotService(repo)
warn_store = InMemoryReconcileWarningStore()
intent_store = InMemoryIntentStore()
alarms = RecordingAlarmEmitter()
slot = _make_holding_slot(repo, svc, qty=10, portfolio_id="pf-A", symbol="005930")
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="C")
intent_svc = IntentService(
svc, repo, intent_store,
instance_key="default",
warn_store=warn_store,
alarm_emitter=alarms,
)
d = intent_svc.submit_sell(SellIntent(
intent_id="s-alert-001",
slot_id=slot.slot_id,
reason="strategy_exit",
sell_strategy_id="stg-sell",
))
assert d.status == "rejected"
assert d.reason == "reconcile_warned"
assert len(alarms.calls) == 1
_, _, severity, fields = alarms.calls[0]
assert severity == "warn"
assert fields["intent_type"] == "sell"
assert fields["strategy_id"] == "stg-sell"
assert fields["slot_id"] == str(slot.slot_id)
def test_reconcile_warned_alarm_dedup_by_intent_id_across_service_recreation():
repo = InMemorySlotRepository()
svc = SlotService(repo)
warn_store = InMemoryReconcileWarningStore()
intent_store = InMemoryIntentStore()
alarms = RecordingAlarmEmitter()
repo.reconcile_universe("default", {"pf-A": "005930"})
slot = repo.find_slot_by_portfolio("default", "pf-A")
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="B")
s1 = IntentService(
svc, repo, intent_store, instance_key="default", warn_store=warn_store, alarm_emitter=alarms
)
s2 = IntentService(
svc, repo, intent_store, instance_key="default", warn_store=warn_store, alarm_emitter=alarms
)
first = s1.submit_buy(BuyIntent(intent_id="b-dedup", slot_id=slot.slot_id))
second = s1.submit_buy(BuyIntent(intent_id="b-dedup", slot_id=slot.slot_id))
third = s2.submit_buy(BuyIntent(intent_id="b-dedup", slot_id=slot.slot_id))
assert first.reason == "reconcile_warned"
assert second.reason == "reconcile_warned"
assert third.reason == "reconcile_warned"
assert len(alarms.calls) == 1
def test_reconcile_warned_alarm_emits_per_distinct_intent_id_on_same_slot():
repo = InMemorySlotRepository()
svc = SlotService(repo)
warn_store = InMemoryReconcileWarningStore()
intent_store = InMemoryIntentStore()
alarms = RecordingAlarmEmitter()
repo.reconcile_universe("default", {"pf-A": "005930"})
slot = repo.find_slot_by_portfolio("default", "pf-A")
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="B")
intent_svc = IntentService(
svc, repo, intent_store, instance_key="default", warn_store=warn_store, alarm_emitter=alarms
)
d1 = intent_svc.submit_buy(BuyIntent(intent_id="b-uniq-1", slot_id=slot.slot_id))
d2 = intent_svc.submit_buy(BuyIntent(intent_id="b-uniq-2", slot_id=slot.slot_id))
assert d1.reason == "reconcile_warned"
assert d2.reason == "reconcile_warned"
assert len(alarms.calls) == 2
def test_reconcile_warned_alarm_not_published_for_duplicate_in_flight_or_other_decisions():
repo = InMemorySlotRepository()
svc = SlotService(repo)
alarms = RecordingAlarmEmitter()
duplicate = IntentService(
svc, repo, DuplicateInFlightIntentStore(), instance_key="default", alarm_emitter=alarms
)
d_dup = duplicate.submit_buy(BuyIntent(intent_id="dup", portfolio_id="pf-A"))
assert d_dup.reason == "duplicate_in_flight"
assert alarms.calls == []
repo.reconcile_universe("default", {"pf-A": "005930"})
slot = repo.find_slot_by_portfolio("default", "pf-A")
normal = IntentService(
svc, repo, InMemoryIntentStore(), instance_key="default", alarm_emitter=alarms
)
admitted = normal.submit_buy(BuyIntent(intent_id="admitted", slot_id=slot.slot_id, qty=1, entry_price=100))
assert admitted.status == "admitted"
invalid = normal.submit_buy(BuyIntent(intent_id="bad-venue", slot_id=slot.slot_id, venue="BAD"))
assert invalid.reason == "invalid_venue"
repo.reconcile_universe("default", {"pf-A": "005930", "pf-B": "000660"})
slot_b = repo.find_slot_by_portfolio("default", "pf-B")
capped = IntentService(
svc, repo, InMemoryIntentStore(), instance_key="default", max_active_slots=0, alarm_emitter=alarms
)
full = capped.submit_buy(BuyIntent(intent_id="full", slot_id=slot_b.slot_id))
assert full.reason == "capacity_full"
assert alarms.calls == []
def test_reconcile_warned_alarm_finalize_failure_and_publish_failure_are_isolated():
repo = InMemorySlotRepository()
svc = SlotService(repo)
warn_store = InMemoryReconcileWarningStore()
repo.reconcile_universe("default", {"pf-A": "005930"})
slot = repo.find_slot_by_portfolio("default", "pf-A")
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="B")
alarms = RecordingAlarmEmitter()
failing_store = RecordingIntentStore(fail_finalize=True)
failing_finalize = IntentService(
svc, repo, failing_store,
instance_key="default",
warn_store=warn_store,
alarm_emitter=alarms,
)
try:
failing_finalize.submit_buy(BuyIntent(intent_id="b-finalize-fail", slot_id=slot.slot_id))
assert False, "expected finalize failure"
except RuntimeError:
pass
assert alarms.calls == []
soft_fail_alarm = RecordingAlarmEmitter(return_value=False)
raising_alarm = RecordingAlarmEmitter(raise_error=True)
non_blocking_1 = IntentService(
svc, repo, InMemoryIntentStore(), instance_key="default", warn_store=warn_store, alarm_emitter=soft_fail_alarm
)
non_blocking_2 = IntentService(
svc, repo, InMemoryIntentStore(), instance_key="default", warn_store=warn_store, alarm_emitter=raising_alarm
)
d1 = non_blocking_1.submit_buy(BuyIntent(intent_id="b-alarm-false", slot_id=slot.slot_id))
d2 = non_blocking_2.submit_buy(BuyIntent(intent_id="b-alarm-exc", slot_id=slot.slot_id))
d3 = IntentService(
svc, repo, InMemoryIntentStore(), instance_key="default", warn_store=warn_store, alarm_emitter=None
).submit_buy(BuyIntent(intent_id="b-no-emitter", slot_id=slot.slot_id))
assert d1.reason == "reconcile_warned"
assert d2.reason == "reconcile_warned"
assert d3.reason == "reconcile_warned"
# REQ-104 AC-3: no warn, no pause => buy/sell admitted normally
def test_intent_service_no_warn_admitted_normally():
repo = InMemorySlotRepository()
@@ -7,6 +7,8 @@ daemon wiring was never run until the REL-004 live smoke timed out every buy.
"""
from __future__ import annotations
from pathlib import Path
from trex_core.application.portfolio import PortfolioItem, SnapshotStore
from trex_core.runtime.daemon import Daemon
@@ -35,3 +37,14 @@ def test_resolve_symbol_reads_item_symbol():
def test_resolve_symbol_unknown_returns_none():
holder = _StoreHolder(_store())
assert Daemon._resolve_symbol(holder, "pf-missing") is None
def test_daemon_wires_shared_alarm_emitter_into_intent_service():
src = (
Path(__file__).resolve().parents[2]
/ "trex_core"
/ "runtime"
/ "daemon.py"
).read_text(encoding="utf-8")
assert "self.intent_service = IntentService(" in src
assert "alarm_emitter=self.alarm_emitter" in src