feat(matrix-bridge-dagi): add operator allowlist for control commands (M3.0)
New: app/control.py
- ControlConfig: operator_allowlist + control_rooms (frozensets)
- parse_control_config(): validates @user:server + !room:server formats, fail-fast
- parse_command(): parses !verb subcommand [args] [key=value] up to 512 chars
- check_authorization(): AND(is_control_room, is_operator) → (bool, reason)
- Reply helpers: not_implemented, unknown_command, unauthorized, help
- KNOWN_VERBS: runbook, status, help (M3.1+ stubs)
- MAX_CMD_LEN=512, MAX_CMD_TOKENS=20
ingress.py:
- _try_control(): dispatch for control rooms (authorized → audit + reply, unauthorized → audit + optional ⛔)
- join control rooms on startup
- _enqueue_from_sync: control rooms processed first, never forwarded to agents
- on_control_command(sender, verb, subcommand) metric callback
- CONTROL_UNAUTHORIZED_BEHAVIOR: "ignore" | "reply_error"
Audit events:
matrix.control.command — authorised command (verb, subcommand, args, kwargs)
matrix.control.unauthorized — rejected by allowlist (reason: not_operator | not_control_room)
matrix.control.unknown_cmd — authorised but unrecognised verb
Config + main:
- bridge_operator_allowlist, bridge_control_rooms, control_unauthorized_behavior
- matrix_bridge_control_commands_total{sender,verb,subcommand} counter
- /health: control_channel section (enabled, rooms_count, operators_count, behavior)
- /bridge/mappings: control_rooms + control_operators_count
- docker-compose: BRIDGE_OPERATOR_ALLOWLIST, BRIDGE_CONTROL_ROOMS, CONTROL_UNAUTHORIZED_BEHAVIOR
Tests: 40 new → 148 total pass
Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
matrix-bridge-dagi — configuration and validation (M2.1 + M2.2: mixed rooms + guard rails)
|
||||
matrix-bridge-dagi — configuration and validation (M2.1 + M2.2 + M3.0)
|
||||
"""
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
@@ -46,6 +46,14 @@ class BridgeConfig:
|
||||
unknown_agent_behavior: str # "ignore" | "reply_error"
|
||||
mixed_concurrency_cap: int # max parallel invokes per (room, agent); 0 = unlimited
|
||||
|
||||
# M3.0: Operator control channel
|
||||
# "@ivan:daarion.space,@sergiy:daarion.space"
|
||||
bridge_operator_allowlist: str
|
||||
# "!opsroom:server,!opsroom2:server2"
|
||||
bridge_control_rooms: str
|
||||
# "ignore" | "reply_error" (send ⛔ to room on unauthorized attempt)
|
||||
control_unauthorized_behavior: str
|
||||
|
||||
# Service identity
|
||||
node_id: str
|
||||
build_sha: str
|
||||
@@ -88,6 +96,9 @@ def load_config() -> BridgeConfig:
|
||||
max_slash_len=max(4, int(_optional("MAX_SLASH_LEN", "32"))),
|
||||
unknown_agent_behavior=_optional("UNKNOWN_AGENT_BEHAVIOR", "ignore"),
|
||||
mixed_concurrency_cap=max(0, int(_optional("MIXED_CONCURRENCY_CAP", "1"))),
|
||||
bridge_operator_allowlist=_optional("BRIDGE_OPERATOR_ALLOWLIST", ""),
|
||||
bridge_control_rooms=_optional("BRIDGE_CONTROL_ROOMS", ""),
|
||||
control_unauthorized_behavior=_optional("CONTROL_UNAUTHORIZED_BEHAVIOR", "ignore"),
|
||||
node_id=_optional("NODE_ID", "NODA1"),
|
||||
build_sha=_optional("BUILD_SHA", "dev"),
|
||||
build_time=_optional("BUILD_TIME", "local"),
|
||||
|
||||
Reference in New Issue
Block a user