Fix unmanaged reconciliation review blockers

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-13 02:21:37 +00:00
committed by GitHub
co-authored by genichin
parent f9284d1de8
commit 591db7adb7
26 changed files with 484 additions and 87 deletions
+21
View File
@@ -23,6 +23,15 @@ class _Broker:
def reinitialize(self, credential): self.credentials.append(credential)
class _ProjectedBroker(_Broker):
def __init__(self, authoritative):
super().__init__()
self.authoritative = authoritative
def authoritative_balance_reader(self):
return self.authoritative
class _Read:
def __init__(self, ctx=None, error=None): self.ctx, self.error, self.calls = ctx, error, 0
def fetch_broker_credential(self):
@@ -72,6 +81,18 @@ def test_enabled_paper_never_fetches_kis_credentials(monkeypatch):
assert read.calls == 0
def test_reconciliation_broker_uses_authoritative_balance_reader(monkeypatch):
authoritative = _Broker()
projected = _ProjectedBroker(authoritative)
monkeypatch.setattr("trex_core.runtime.broker_provider._make_env_broker", lambda cfg: projected)
provider = ConfigBrokerProvider(_cfg(broker="paper"), read_port=_Read(_ctx()))
broker, source = provider.get_broker_for_reconciliation()
assert broker is authoritative
assert source.mode == "env"
def test_break_glass_is_distinct_and_skips_fetch(monkeypatch):
broker = _Broker()
monkeypatch.setenv("TREX_KIS_BROKER_BREAK_GLASS", "true")
+84 -4
View File
@@ -523,12 +523,15 @@ def test_verify_no_db_no_live_flag_needed(capsys, monkeypatch):
# unmanaged parser/tests
# ---------------------------------------------------------------------------
def test_unmanaged_parser_accepts_list_and_record():
def test_unmanaged_parser_accepts_list_audit_and_record():
parser = cli._build_parser()
args = parser.parse_args(["unmanaged", "list", "--status", "all", "--symbol", "008110"])
assert args.cmd == "unmanaged"
assert args.unmanaged_cmd == "list"
assert args.status == "all"
args = parser.parse_args(["unmanaged", "audit", "008110"])
assert args.unmanaged_cmd == "audit"
assert args.symbol == "008110"
args = parser.parse_args(
[
"unmanaged",
@@ -617,7 +620,6 @@ def test_unmanaged_record_requires_existing_state(monkeypatch, capsys):
action="approved_exception",
evidence_ref="kis-balance:2026-09-11",
note="confirmed",
actor="operator",
instance="inst-1",
config=None,
config_path=None,
@@ -634,6 +636,8 @@ def test_unmanaged_record_requires_existing_state(monkeypatch, capsys):
def test_unmanaged_record_persists_operator_action(monkeypatch, capsys):
monkeypatch.setattr(cli, "_operator_actor", lambda: "cli:test")
class _Store:
def __init__(self, *_args, **_kwargs):
pass
@@ -671,7 +675,6 @@ def test_unmanaged_record_persists_operator_action(monkeypatch, capsys):
action="managed_inclusion",
evidence_ref="kis-balance:2026-09-11",
note="manual inclusion planned",
actor="operator",
instance="inst-1",
config=None,
config_path=None,
@@ -687,9 +690,12 @@ def test_unmanaged_record_persists_operator_action(monkeypatch, capsys):
assert out["result"] == "recorded"
assert out["state"]["operator_action"] == "managed_inclusion"
assert out["state"]["evidence_ref"] == "kis-balance:2026-09-11"
assert out["state"]["operator_actor"] == "cli:test"
def test_unmanaged_record_text_output(monkeypatch, capsys):
monkeypatch.setattr(cli, "_operator_actor", lambda: "cli:test")
class _Store:
def __init__(self, *_args, **_kwargs):
pass
@@ -724,7 +730,6 @@ def test_unmanaged_record_text_output(monkeypatch, capsys):
action="approved_exception",
evidence_ref="kis-balance:2026-09-11",
note="confirmed",
actor="operator",
instance="inst-1",
config=None,
config_path=None,
@@ -742,6 +747,8 @@ def test_unmanaged_record_text_output(monkeypatch, capsys):
def test_unmanaged_record_main_dispatch(monkeypatch, capsys):
monkeypatch.setattr(cli, "_operator_actor", lambda: "cli:test")
class _Store:
def __init__(self, *_args, **_kwargs):
pass
@@ -788,6 +795,79 @@ def test_unmanaged_record_main_dispatch(monkeypatch, capsys):
assert out["state"]["symbol"] == "008110"
def test_unmanaged_audit_outputs_lifecycle_and_operator_history(monkeypatch, capsys):
class _Store:
def __init__(self, *_args, **_kwargs):
pass
def get_state(self, **kwargs):
assert kwargs == {"instance_key": "inst-1", "symbol": "008110"}
return {
"instance_key": "inst-1",
"symbol": "008110",
"quantity": 3,
"lifecycle_state": "active",
"first_seen_at": "2026-09-07T00:00:00+00:00",
"last_seen_at": "2026-09-11T00:00:00+00:00",
"quantity_changed_at": "2026-09-07T00:00:00+00:00",
"resolved_at": None,
"operator_action": "investigating",
"evidence_ref": "kis-balance:2026-09-11",
"operator_note": "confirmed",
"operator_actor": "cli:test",
"operator_updated_at": "2026-09-11T00:00:00+00:00",
"created_at": "2026-09-07T00:00:00+00:00",
"updated_at": "2026-09-11T00:00:00+00:00",
}
def list_audit(self, **kwargs):
assert kwargs == {"instance_key": "inst-1", "symbol": "008110"}
return [
{
"event_type": "detected",
"recorded_at": "2026-09-07T00:00:00+00:00",
"quantity": 3,
"action": None,
"actor": "reconcile",
"evidence_ref": None,
"note": "",
},
{
"event_type": "operator_action",
"recorded_at": "2026-09-11T00:00:00+00:00",
"quantity": 3,
"action": "investigating",
"actor": "cli:test",
"evidence_ref": "kis-balance:2026-09-11",
"note": "confirmed",
},
]
monkeypatch.setattr(
"trex_core.adapters.portfolio.unmanaged_position_store.PostgreSQLUnmanagedPositionStore",
_Store,
)
args = SimpleNamespace(
cmd="unmanaged",
unmanaged_cmd="audit",
symbol="008110",
instance="inst-1",
config=None,
config_path=None,
env_file=None,
json=True,
timeout=10.0,
)
rc = cli._cmd_unmanaged_audit(args, SimpleNamespace(dsn="postgresql://x/y"))
assert rc == 0
out = json.loads(capsys.readouterr().out)
assert out["symbol"] == "008110"
assert out["state"]["operator_actor"] == "cli:test"
assert [row["event_type"] for row in out["audit"]] == ["detected", "operator_action"]
# ---------------------------------------------------------------------------
# REL-010 regression: _cmd_verify broker import must not raise ImportError
# ---------------------------------------------------------------------------
@@ -73,3 +73,44 @@ def test_daemon_wires_shared_alarm_emitter_into_intent_service():
found = True
break
assert found, "Daemon must pass self.alarm_emitter into IntentService(alarm_emitter=...)"
def test_daemon_wires_reconciliation_broker_into_snapshot_scheduler():
src = (
Path(__file__).resolve().parents[2]
/ "trex_core"
/ "runtime"
/ "daemon.py"
).read_text(encoding="utf-8")
tree = ast.parse(src)
found = False
for node in ast.walk(tree):
if not isinstance(node, ast.Assign):
continue
if not any(
isinstance(t, ast.Attribute) and t.attr == "snapshot_scheduler"
for t in node.targets
):
continue
call = node.value
if not (
isinstance(call, ast.Call)
and isinstance(call.func, ast.Name)
and call.func.id == "SnapshotScheduler"
):
continue
for kw in call.keywords:
if kw.arg != "core_holdings_broker":
continue
if (
isinstance(kw.value, ast.Attribute)
and kw.value.attr == "reconciliation_broker"
and isinstance(kw.value.value, ast.Name)
and kw.value.value.id == "self"
):
found = True
break
assert found, (
"Daemon must pass self.reconciliation_broker into "
"SnapshotScheduler(core_holdings_broker=...)"
)