fix: harden skipped ledger resolution paths

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-08-24 13:38:04 +00:00
committed by GitHub
co-authored by genichin
parent b88b21b772
commit 48dfc3e76f
8 changed files with 451 additions and 96 deletions
@@ -1,5 +1,6 @@
from __future__ import annotations
import threading
from datetime import UTC, date, datetime
from uuid import uuid4
@@ -87,3 +88,76 @@ def test_ledger_skip_store_filters_and_resolves(pg_clean):
assert len(resolved) == 1
assert resolved[0].status == "resolved"
assert resolved[0].resolution_reason == "manual_ledger_backfill"
def test_ledger_skip_store_resolves_null_execution_id_on_backfill(pg_clean):
store = PostgreSQLLedgerSkipStore(pg_clean)
slot_id = uuid4()
record, _ = store.record_skip(
instance_key="inst-1",
slot_id=slot_id,
portfolio_id="PF-1",
symbol="005930",
order_id="O-3",
execution_id=None,
side="buy",
qty=1.0,
price=1010.0,
executed_at=datetime(2026, 8, 24, 3, 0, tzinfo=UTC),
order_venue="SOR",
execution_venue="unknown",
skip_reason="missing_execution_id",
)
resolved = store.resolve_for_fill(
instance_key="inst-1",
slot_id=slot_id,
side="buy",
order_id="O-3",
execution_id="E-3",
resolution_reason="manual_ledger_backfill",
)
assert [row.skip_key for row in resolved] == [record.skip_key]
assert resolved[0].execution_id == "E-3"
assert resolved[0].status == "resolved"
def test_ledger_skip_store_retries_unique_violation_during_concurrent_dedupe(pg_clean):
slot_id = uuid4()
start = threading.Barrier(2)
results = []
def worker():
store = PostgreSQLLedgerSkipStore(pg_clean)
start.wait()
results.append(
store.record_skip(
instance_key="inst-1",
slot_id=slot_id,
portfolio_id="PF-1",
symbol="005930",
order_id="O-4",
execution_id=None,
side="buy",
qty=1.0,
price=1111.0,
executed_at=datetime(2026, 8, 24, 4, 0, tzinfo=UTC),
order_venue="SOR",
execution_venue="unknown",
skip_reason="missing_execution_id",
)
)
t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)
t1.start()
t2.start()
t1.join()
t2.join()
assert len(results) == 2
assert len({record.skip_key for record, _notify in results}) == 1
assert sorted(notify for _record, notify in results) == [False, True]
listed = PostgreSQLLedgerSkipStore(pg_clean).list_records(instance_key="inst-1", status="unresolved")
assert len(listed) == 1
+101 -4
View File
@@ -6,7 +6,7 @@ critical path while the ledger write is eventually-consistent.
"""
from __future__ import annotations
from datetime import UTC
from datetime import UTC, datetime
from trex_core.adapters.broker.kis import KISBroker
from trex_core.adapters.broker.paper import PaperBroker
@@ -63,6 +63,55 @@ class _SkipStore:
def resolve_for_fill(self, **_kwargs):
return []
def resolve_for_delivery(
self,
*,
instance_key,
portfolio_id,
side,
order_id,
execution_id,
resolution_reason,
):
resolved = []
updated = {}
for key, record in self._by_key.items():
if (
record.instance_key == instance_key
and record.portfolio_id == portfolio_id
and record.side == side
and (
record.execution_id == execution_id
or (record.execution_id is None and record.order_id == order_id)
)
):
record = LedgerSkipRecord(
skip_key=record.skip_key,
instance_key=record.instance_key,
slot_id=record.slot_id,
portfolio_id=record.portfolio_id,
symbol=record.symbol,
order_id=record.order_id,
execution_id=record.execution_id or execution_id,
side=record.side,
qty=record.qty,
price=record.price,
executed_at=record.executed_at,
order_venue=record.order_venue,
execution_venue=record.execution_venue,
skip_reason=record.skip_reason,
status="resolved",
resolution_reason=resolution_reason,
resolved_at=record.executed_at,
created_at=record.created_at,
updated_at=record.executed_at,
)
resolved.append(record)
updated[key] = record
self._by_key = updated
self.records = list(self._by_key.values())
return resolved
def _wire(broker, *, ledger=None, clock=None, alarm_emitter=None, ledger_skip_store=None):
@@ -79,9 +128,13 @@ def _wire(broker, *, ledger=None, clock=None, alarm_emitter=None, ledger_skip_st
alarm_emitter=alarm_emitter,
ledger_skip_store=ledger_skip_store,
)
writer = (
LedgerWriter(outbox, ledger or InMemoryLedger(), clock=clock)
if clock else LedgerWriter(outbox, ledger or InMemoryLedger())
writer = LedgerWriter(
outbox,
ledger or InMemoryLedger(),
clock=clock,
alarm_emitter=alarm_emitter,
ledger_skip_store=ledger_skip_store,
instance_key="default",
)
repo.reconcile_universe("default", {"PF-1": "SYM"})
return repo, svc, intents, execution, outbox, writer
@@ -294,6 +347,50 @@ def test_missing_execution_id_persists_skip_record() -> None:
assert record.execution_id is None
def test_skip_resolves_only_after_writer_completes_delivery() -> None:
transport = InMemoryTransport()
skip_store = _SkipStore()
ledger = InMemoryLedger()
_repo, _, intents, execution, outbox, writer = _wire(
PaperBroker(fill_after_polls=0),
ledger=ledger,
alarm_emitter=AlarmEmitter(transport),
ledger_skip_store=skip_store,
)
sid = _buy(intents, iid="delayed-skip-resolution").slot_id
record, _ = skip_store.record_skip(
instance_key="default",
slot_id=sid,
portfolio_id="PF-1",
symbol="005930",
order_id="PAPER-000001",
execution_id=None,
side="buy",
qty=10.0,
price=1000.0,
executed_at=datetime(2026, 8, 24, 0, 0, tzinfo=UTC),
order_venue="SOR",
execution_venue="unknown",
skip_reason="missing_execution_id",
)
result = execution.run_once()
assert result.ledger_enqueued == 1
assert outbox.all_items()
assert skip_store._by_key[record.skip_key].status == "unresolved"
assert transport.alarm_events() == []
stats = writer.run_once()
assert stats.completed == 1
resolved = skip_store._by_key[record.skip_key]
assert resolved.status == "resolved"
assert resolved.execution_id is not None
assert resolved.resolution_reason == "ledger_recorded"
assert [payload["code"] for _, payload in transport.alarm_events()] == ["ledger_skip_resolved"]
def test_ledger_skip_emits_warning(caplog):
"""dcy-011 / REQ-018 FR-4 (S-3): ledger skip is logged, not silent."""
import logging
+47 -5
View File
@@ -9,8 +9,13 @@ from trex_core.application.ledger_skip import LedgerSkipRecord
from trex_core.runtime import cli
def _cfg():
return SimpleNamespace(dsn="postgresql://x/y", rabbitmq=None)
def _cfg(*, live: bool = False):
return SimpleNamespace(
dsn="postgresql://x/y",
rabbitmq=None,
broker="kis",
kis=SimpleNamespace(allow_live=live),
)
def _record(*, status="unresolved", resolution_reason=None):
@@ -46,7 +51,7 @@ def test_ledger_skips_parser_accepts_list_and_resolve():
args = parser.parse_args(["ledger", "skips", "list", "--status", "all"])
assert args.ledger_cmd == "skips"
assert args.ledger_skips_cmd == "list"
args = parser.parse_args(["ledger", "skips", "resolve", "--skip-key", "abc"])
args = parser.parse_args(["ledger", "skips", "resolve", "--skip-key", "abc", "--yes"])
assert args.ledger_skips_cmd == "resolve"
@@ -91,8 +96,9 @@ def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, capsys):
def __init__(self, *_args, **_kwargs):
pass
def resolve_by_key(self, skip_key, *, resolution_reason):
def resolve_by_key(self, skip_key, *, instance_key, resolution_reason):
assert skip_key == "abc"
assert instance_key == "inst-1"
return _record(status="resolved", resolution_reason=resolution_reason)
published = []
@@ -117,6 +123,9 @@ def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, capsys):
skip_key="abc",
reason="operator_reconciled",
instance="inst-1",
yes=True,
live=False,
audit_dir="audit-dir",
config=None,
config_path=None,
env_file=None,
@@ -124,9 +133,42 @@ def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, capsys):
timeout=10.0,
)
rc = cli._cmd_ledger_skips_resolve(args, _cfg())
rc = cli._cmd_ledger_skips_resolve(args, _cfg(), lambda _: True)
assert rc == 0
out = json.loads(capsys.readouterr().out)
assert out["result"] == "resolved"
assert out["audit_path"].endswith(".json")
assert published[0][0] == "ledger_skip_resolved"
def test_ledger_skips_resolve_live_requires_live_flag(monkeypatch, tmp_path, capsys):
monkeypatch.setattr(
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
lambda *_args, **_kwargs: object(),
)
args = SimpleNamespace(
cmd="ledger",
ledger_cmd="skips",
ledger_skips_cmd="resolve",
skip_key="abc",
reason="operator_reconciled",
instance="inst-1",
yes=True,
live=False,
audit_dir=str(tmp_path),
config=None,
config_path=None,
env_file=None,
json=True,
timeout=10.0,
)
rc = cli._cmd_ledger_skips_resolve(args, _cfg(live=True), lambda _: True)
assert rc == 2
err = capsys.readouterr().err
assert "--live" in err
audit = json.loads(next(tmp_path.glob("*.json")).read_text(encoding="utf-8"))
assert audit["request"]["instance"] == "inst-1"
assert audit["result"]["status"] == "rejected"