feat: add safe miner control foundation
This commit is contained in:
1 parent
6cd876b4b3
commit
f5965af914
39 files changed
+2952
-491
No files matched your search
@@ -0,0 +1,165 @@
|
||||
"""Business rules for typed, Agent-routed miner controls."""
|
||||
|
||||
from typing import Any
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from app.miner_control.exceptions import (
|
||||
ControlledMinerNotFound,
|
||||
MinerControlActorRequired,
|
||||
MinerControlUnavailable,
|
||||
)
|
||||
from app.miner_control.models import (
|
||||
ControlAgentAvailability,
|
||||
ControlCapability,
|
||||
MinerControlAccepted,
|
||||
MinerControlOperation,
|
||||
MinerControlProjection,
|
||||
)
|
||||
from app.miner_control.repository import MinerControlRepository
|
||||
|
||||
|
||||
KNOWN_MODES = {"low", "normal", "high"}
|
||||
RUNNING_STATES = {"online", "running"}
|
||||
STOPPED_STATES = {"suspended", "stopped"}
|
||||
|
||||
|
||||
class MinerControlService:
|
||||
def __init__(self, repository: MinerControlRepository):
|
||||
self.repository = repository
|
||||
|
||||
@staticmethod
|
||||
def _capability(context: dict) -> ControlCapability:
|
||||
if not context.get("site_id"):
|
||||
return ControlCapability(state="unavailable", reason="site_unassigned")
|
||||
if not context.get("agent_id"):
|
||||
return ControlCapability(state="unavailable", reason="agent_unassigned")
|
||||
if not context.get("current_ip"):
|
||||
return ControlCapability(state="unavailable", reason="current_ip_missing")
|
||||
if context.get("api3_supported") is False:
|
||||
return ControlCapability(state="unavailable", reason="api3_unsupported")
|
||||
if context.get("api3_supported") is not True:
|
||||
return ControlCapability(state="unknown", reason="api3_support_unknown")
|
||||
|
||||
payload = context.get("last_payload") or {}
|
||||
if payload.get("control_available") is False:
|
||||
reason = payload.get("control_unavailable_reason")
|
||||
return ControlCapability(
|
||||
state="unavailable",
|
||||
reason=reason if reason == "credentials_missing" else "agent_unavailable",
|
||||
)
|
||||
if payload.get("control_available") is not True:
|
||||
return ControlCapability(
|
||||
state="unknown", reason="agent_capability_unknown"
|
||||
)
|
||||
return ControlCapability(state="supported")
|
||||
|
||||
@staticmethod
|
||||
def _operation(miner_id: UUID, row: dict) -> MinerControlOperation:
|
||||
request = row.get("request_payload") or {}
|
||||
result = row.get("result_payload") or {}
|
||||
mode = result.get("observed_performance_mode")
|
||||
return MinerControlOperation(
|
||||
operation_id=row["operation_id"],
|
||||
miner_id=miner_id,
|
||||
action=request.get("action"),
|
||||
requested_value=request.get("requested_value"),
|
||||
status=row["status"],
|
||||
verification_status=result.get("verification_status"),
|
||||
command_accepted=result.get("command_accepted"),
|
||||
observed_performance_mode=mode if mode in KNOWN_MODES else None,
|
||||
failure_code=result.get("failure_code"),
|
||||
created_at=row["created_at"],
|
||||
claimed_at=row.get("claimed_at"),
|
||||
completed_at=row.get("completed_at"),
|
||||
)
|
||||
|
||||
def get_controls(self, miner_id: UUID) -> MinerControlProjection:
|
||||
context = self.repository.get_control_context(miner_id)
|
||||
if not context:
|
||||
raise ControlledMinerNotFound("Miner not found")
|
||||
capability = self._capability(context)
|
||||
runtime = str(context.get("runtime_state") or "").strip().lower() or None
|
||||
service_action = None
|
||||
if capability.state == "supported":
|
||||
if runtime in RUNNING_STATES:
|
||||
service_action = "suspend"
|
||||
elif runtime in STOPPED_STATES:
|
||||
service_action = "resume"
|
||||
configured_mode = str(context.get("configured_performance_mode") or "").lower()
|
||||
return MinerControlProjection(
|
||||
miner_id=miner_id,
|
||||
runtime_state=runtime,
|
||||
performance_mode=(configured_mode if configured_mode in KNOWN_MODES else None),
|
||||
agent=ControlAgentAvailability(
|
||||
state=capability.state,
|
||||
reason=capability.reason,
|
||||
last_checkin_at=context.get("last_checkin_at"),
|
||||
version=context.get("agent_version"),
|
||||
),
|
||||
service_control=capability,
|
||||
performance_mode_control=capability,
|
||||
available_service_action=service_action,
|
||||
recent_operations=[
|
||||
self._operation(miner_id, row)
|
||||
for row in self.repository.list_recent(miner_id, limit=5)
|
||||
],
|
||||
)
|
||||
|
||||
def queue_control(
|
||||
self,
|
||||
miner_id: UUID,
|
||||
action: str,
|
||||
*,
|
||||
requested_value: str | None,
|
||||
actor_user_id: UUID,
|
||||
actor_username: str,
|
||||
) -> MinerControlAccepted:
|
||||
if not actor_user_id:
|
||||
raise MinerControlActorRequired("Authenticated actor UUID is required")
|
||||
context = self.repository.get_control_context(miner_id)
|
||||
if not context:
|
||||
raise ControlledMinerNotFound("Miner not found")
|
||||
capability = self._capability(context)
|
||||
if capability.state != "supported":
|
||||
raise MinerControlUnavailable(
|
||||
capability.reason or "control_capability_not_supported"
|
||||
)
|
||||
|
||||
runtime = str(context.get("runtime_state") or "").strip().lower()
|
||||
if action == "suspend" and runtime not in RUNNING_STATES:
|
||||
raise MinerControlUnavailable("miner_not_factually_running")
|
||||
if action == "resume" and runtime not in STOPPED_STATES:
|
||||
raise MinerControlUnavailable("miner_not_factually_stopped")
|
||||
if action == "performance_mode" and requested_value not in KNOWN_MODES:
|
||||
raise MinerControlUnavailable("invalid_performance_mode")
|
||||
|
||||
operation_id = str(uuid4())
|
||||
payload: dict[str, Any] = {
|
||||
"schema_version": 1,
|
||||
"miner_id": str(miner_id),
|
||||
"action": action,
|
||||
"requested_value": requested_value,
|
||||
"ip_address": context["current_ip"],
|
||||
}
|
||||
operation = self.repository.enqueue_with_audit(
|
||||
operation_id=operation_id,
|
||||
miner_id=miner_id,
|
||||
agent_id=context["agent_id"],
|
||||
actor_user_id=actor_user_id,
|
||||
actor_username=actor_username,
|
||||
request_payload=payload,
|
||||
audit_payload={
|
||||
"operation_id": operation_id,
|
||||
"miner_id": str(miner_id),
|
||||
"action": action,
|
||||
"requested_value": requested_value,
|
||||
},
|
||||
)
|
||||
return MinerControlAccepted(
|
||||
operation_id=operation["operation_id"],
|
||||
miner_id=miner_id,
|
||||
action=action,
|
||||
requested_value=requested_value,
|
||||
status=operation["status"],
|
||||
created_at=operation["created_at"],
|
||||
)
|
||||
Reference in new issue
Block a user