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>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-14 00:15:00 -04:00
1 parent fbb84ae166
commit e7c34f298f
8 files changed
+619

No files matched your search

+39
View File
@@ -227,6 +227,33 @@ def answer(store, question: str, *, host: str, model: str, timeout: int,
return f"(LLM unavailable — here's the raw snapshot)\n{context}"
# --------------------------------------------------------- operator kill switch
def parse_flatten_command(text: str):
"""Parse the ONE action the qabot performs: the explicit ``/flatten`` slash
command. Returns ``(command, symbol)`` or None. Prose like "sell everything"
is NOT matched here — it still hits the _ACTION_WORDS refusal in answer(). This
is the deliberate carve-out: an explicit slash command from the authorized chat
is allowed; free-text trade requests are not."""
parts = text.strip().split()
if not parts or parts[0].lower() != "/flatten":
return None
if len(parts) >= 2 and parts[1].strip():
return ("flatten_symbol", parts[1].strip().upper())
return ("flatten_all", None)
def emit_operator_command(store, command: str, symbol, requested_by: str) -> None:
"""Emit the same OperatorCommandEvent the dashboard does — the live loop's 5s
consumer executes it (single execution authority)."""
import uuid
from storage.event_schema import EventMode, OperatorCommandEvent
store.emit(OperatorCommandEvent(
timestamp=datetime.now(), mode=EventMode.PAPER,
message=f"telegram operator command {command}" + (f" {symbol}" if symbol else ""),
command=command, symbol=symbol, requested_by=requested_by,
nonce=uuid.uuid4().hex))
# ------------------------------------------------------------------- dead-man
def _now_et() -> datetime:
"""ET wall clock, tz-NAIVE — events.timestamp is ET-naive (RUNBOOK), so the
@@ -369,6 +396,18 @@ def main() -> None:
if not is_authorized(chat_id, allowed):
logger.warning("ignoring message from unauthorized chat %s", chat_id)
continue
# explicit /flatten slash command (authorized chat only) — the qabot's
# one action. Emits the guarded event; the loop executes it in ~5s.
cmd = parse_flatten_command(text)
if cmd is not None:
try:
emit_operator_command(store, cmd[0], cmd[1],
requested_by=f"telegram:{chat_id}")
_send(token, chat_id, "Command sent — loop executes within ~5s.")
except Exception as exc: # noqa: BLE001
logger.warning("flatten command emit failed", exc_info=True)
_send(token, chat_id, f"(command failed to send: {exc})")
continue
try:
reply = answer(store, text, host=host, model=model, timeout=timeout,
ollama_enabled=ollama_enabled)