fix(broker): enforce operator broker truth boundary
This commit is contained in:
+54
-10
@@ -329,6 +329,29 @@ def _fail(message: str, as_json: bool) -> None:
|
||||
print(f"error: {message}", file=sys.stderr)
|
||||
|
||||
|
||||
def _broker_read_failure(exc, as_json: bool) -> int:
|
||||
"""REQ-120 command boundary: expose code only, never upstream payloads."""
|
||||
code = getattr(exc, "code", "broker_unavailable")
|
||||
payload = {"error": {"code": code, "message": "broker balance unavailable"}}
|
||||
if as_json:
|
||||
print(_json.dumps(payload), file=sys.stderr)
|
||||
else:
|
||||
print(f"broker balance unavailable ({code})", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
class _CachedBalanceBroker:
|
||||
"""One validated balance read per operator invocation; delegates everything else."""
|
||||
def __init__(self, broker, balance) -> None:
|
||||
self._broker, self._balance = broker, balance
|
||||
|
||||
def get_balance(self):
|
||||
return self._balance
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._broker, name)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# arg parsing
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -446,7 +469,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
|
||||
def _cmd_sync(args, cfg, confirm) -> int:
|
||||
"""REQ-105 (dcy-034, BAT-051): sync slot qty to broker ground truth."""
|
||||
import psycopg
|
||||
|
||||
from trex_core.adapters.portfolio.reconcile_warning_store import (
|
||||
PostgreSQLReconcileWarningStore,
|
||||
@@ -476,11 +498,18 @@ def _cmd_sync(args, cfg, confirm) -> int:
|
||||
print("aborted")
|
||||
return 1
|
||||
|
||||
warn_store = PostgreSQLReconcileWarningStore(cfg.dsn)
|
||||
# REQ-119/120: resolve and validate broker truth before opening/enumerating DB.
|
||||
from trex_core.runtime.broker_provider import (
|
||||
BrokerCredentialUnavailable, build_broker_provider,
|
||||
)
|
||||
from trex_core.application.broker_port import BrokerBalanceError
|
||||
try:
|
||||
broker_port, _source = build_broker_provider(cfg).get_broker_for_read()
|
||||
broker_port = _CachedBalanceBroker(broker_port, broker_port.get_balance())
|
||||
except (BrokerCredentialUnavailable, BrokerBalanceError) as exc:
|
||||
return _broker_read_failure(exc, args.json)
|
||||
|
||||
# Build broker port
|
||||
from trex_core.runtime.daemon import _build_broker
|
||||
broker_port = _build_broker(cfg)
|
||||
warn_store = PostgreSQLReconcileWarningStore(cfg.dsn)
|
||||
|
||||
# Build slot_repo + slot_service
|
||||
from trex_core.adapters.persistence.postgresql import PostgreSQLSlotRepository
|
||||
@@ -498,6 +527,7 @@ def _cmd_sync(args, cfg, confirm) -> int:
|
||||
)
|
||||
|
||||
# Determine target slot_ids
|
||||
import psycopg
|
||||
if args.portfolio_id:
|
||||
with psycopg.connect(cfg.dsn) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -564,7 +594,7 @@ def _cmd_sync(args, cfg, confirm) -> int:
|
||||
print(f" after: status={after.get('status')} "
|
||||
f"qty={after.get('qty')} avg_price={after.get('avg_price')}")
|
||||
if cleared:
|
||||
print(f" reconcile-warning CLEARED")
|
||||
print(" reconcile-warning CLEARED")
|
||||
|
||||
return exit_code
|
||||
|
||||
@@ -579,12 +609,20 @@ def _cmd_verify(args, cfg) -> int:
|
||||
from trex_core.application.reconciliation import verify_slot_reconcile
|
||||
|
||||
instance = _resolve_instance(args)
|
||||
# REQ-120 no-write/no-enumeration barrier also applies to verify.
|
||||
from trex_core.runtime.broker_provider import (
|
||||
BrokerCredentialUnavailable, build_broker_provider,
|
||||
)
|
||||
from trex_core.application.broker_port import BrokerBalanceError
|
||||
try:
|
||||
broker_port, _source = build_broker_provider(cfg).get_broker_for_read()
|
||||
broker_port = _CachedBalanceBroker(broker_port, broker_port.get_balance())
|
||||
except (BrokerCredentialUnavailable, BrokerBalanceError) as exc:
|
||||
return _broker_read_failure(exc, args.json)
|
||||
|
||||
warn_store = PostgreSQLReconcileWarningStore(cfg.dsn)
|
||||
slot_repo = PostgreSQLSlotRepository(cfg.dsn)
|
||||
|
||||
from trex_core.runtime.daemon import _build_broker
|
||||
broker_port = _build_broker(cfg)
|
||||
|
||||
# Build a minimal snapshot store (we only need asset_quantity for C-case check).
|
||||
# For verify we use a fresh SnapshotStore populated from warn_store metadata.
|
||||
# The hub_qty is read from the snapshot; if snapshot is empty we fall back to 0
|
||||
@@ -787,7 +825,13 @@ def main(argv: list[str] | None = None, *, rpc=None, confirm=None) -> int:
|
||||
else None
|
||||
)
|
||||
_load_dotenv(_toml, getattr(args, "env_file", None))
|
||||
cfg = DaemonConfig.from_env()
|
||||
cfg = (
|
||||
DaemonConfig.from_config(
|
||||
resolve_config_toml(args.config, args.config_path), env_file=args.env_file
|
||||
)
|
||||
if args.config
|
||||
else DaemonConfig.from_env()
|
||||
)
|
||||
confirm = confirm or _default_confirm
|
||||
|
||||
# REQ-088 (dcy-029): operator report — direct-DB point-in-time aggregate.
|
||||
|
||||
Reference in New Issue
Block a user