Files
momentum-system/runtime/operator_commands.py
T
momentum devandClaude Fable 5 e7c34f298f operator-plane: guarded command bus (flatten / pause kill switch)
Adds OperatorCommandEvent + a closed command vocabulary (flatten_all,
flatten_symbol, pause_entries, resume_entries). POST /api/command emits it
(token-required: 503 when DASHBOARD_TOKEN unset — commands never run
unauthenticated); the live loop's new 5s "command" step consumes them via
OperatorCommandConsumer with three replay-safety layers (in-session nonce set,
persisted operator_command_done reload, 10-min staleness window).

flatten_all routes through close_session DIRECTLY (never step_eod_flatten) so it
cannot trip plan 003's daily-restart exit. Pause is checked at the top of BOTH
request_approvals_for_ready_signals AND submit_breakout_now (missing either
leaks pause on that path). qabot gains an authorized-chat-only /flatten slash
command (prose "flatten" still refuses via _ACTION_WORDS). Snapshot exposes
broker truth + command_bus + a defensive experiment ledger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 00:15:00 -04:00

131 lines
5.8 KiB
Python

"""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)