"""Operator command bus consumer (plan 007). The dashboard / qabot emit ``operator_command`` events (a guarded kill switch); the live loop polls this consumer on its 5s guard cadence and executes each new command through the execution service's existing protective-close helpers, then echoes an ``operator_command_done`` event so a restart never re-runs it. Replay safety (three layers): 1. an in-session consumed-nonce set (deduped within the process), 2. persisted ``operator_command_done`` events reloaded on boot (deduped across restarts), 3. a staleness window — commands older than ``stale_seconds`` are ignored, so a command issued while the loop was down does not fire on the next boot. Deliberately minimal: a closed command vocabulary (OPERATOR_COMMANDS), no parameters beyond an optional symbol. flatten_all routes through ``close_session`` DIRECTLY (never ``step_eod_flatten``) so it can never trip plan 003's daily-restart exit. """ from __future__ import annotations import json import logging from datetime import datetime, timedelta from storage.event_schema import EventMode, OperatorCommandEvent, EventType logger = logging.getLogger(__name__) STALE_SECONDS = 600 # ignore commands older than 10 minutes (boot-replay guard) class OperatorCommandConsumer: def __init__(self, store, execution, *, session_id=None, now_fn=None, stale_seconds: float = STALE_SECONDS, mode: EventMode = EventMode.PAPER): self.store = store self.execution = execution self.session_id = session_id self._now = now_fn or datetime.now self._stale = float(stale_seconds) self._mode = mode # nonces already handled — seeded from persisted _done events so a restart # doesn't re-execute a command the previous process already ran. self._consumed: set[str] = self._load_done_nonces() def _cutoff_iso(self) -> str: return (self._now() - timedelta(seconds=self._stale)).isoformat() def _load_done_nonces(self) -> set[str]: done: set[str] = set() try: for e in self.store.query_events( event_type=EventType.OPERATOR_COMMAND_DONE.value, since=self._cutoff_iso(), limit=None, ): nonce = json.loads(e.get("payload_json") or "{}").get("nonce") if nonce: done.add(str(nonce)) except Exception: # noqa: BLE001 — a rebuild failure must not block boot logger.warning("operator-command done-nonce reload skipped", exc_info=True) return done def poll(self) -> list[str]: """Execute any new, in-window operator commands. Returns nonces executed.""" try: events = self.store.query_events( event_type=EventType.OPERATOR_COMMAND.value, since=self._cutoff_iso(), limit=None, ) except Exception: # noqa: BLE001 logger.warning("operator-command poll failed", exc_info=True) return [] executed: list[str] = [] for e in events: p = json.loads(e.get("payload_json") or "{}") nonce = str(p.get("nonce") or "") if not nonce or nonce in self._consumed: continue self._consumed.add(nonce) # mark BEFORE executing so a raising command can't loop result = self._execute(p) self._emit_done(p, result) executed.append(nonce) logger.info("operator command %s (%s) -> %s", p.get("command"), p.get("symbol") or "-", result) return executed def _execute(self, p: dict) -> str: cmd = p.get("command") sym = p.get("symbol") ex = self.execution if ex is None: return "error: no execution service" try: if cmd == "flatten_all": # DIRECT close_session — never step_eod_flatten, so this cannot # request plan 003's daily-restart exit. close_session also blocks # re-entry for the session (the kill-switch semantic). res = ex.close_session("operator_flatten") errs = res.get("errors") if isinstance(res, dict) else None closed = ", ".join(res.get("closed_positions") or []) if isinstance(res, dict) else "" return f"error: {errs}" if errs else f"ok: flattened [{closed or 'none'}]" if cmd == "flatten_symbol": if not sym: return "error: flatten_symbol requires a symbol" res = ex.flatten_symbol(sym) return "ok" if res.get("ok") else f"error: {res.get('error')}" if cmd == "pause_entries": ex.set_operator_paused(True) return "ok: entries paused" if cmd == "resume_entries": ex.set_operator_paused(False) return "ok: entries resumed" return f"error: unknown command {cmd!r}" except Exception as exc: # noqa: BLE001 — one bad command must not kill the loop logger.warning("operator command %s failed", cmd, exc_info=True) return f"error: {exc}" def _emit_done(self, p: dict, result: str) -> None: try: self.store.emit(OperatorCommandEvent( timestamp=self._now(), mode=self._mode, correlation_id=self.session_id, event_type=EventType.OPERATOR_COMMAND_DONE, message=f"operator command {p.get('command')} done: {result}", command=str(p.get("command") or ""), symbol=p.get("symbol"), requested_by=str(p.get("requested_by") or "operator"), nonce=str(p.get("nonce") or ""), result=result, )) except Exception: # noqa: BLE001 logger.warning("operator-command done-echo emit failed", exc_info=True)