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,87 @@
|
||||
"""Agent command transport coordination and result validation."""
|
||||
|
||||
import re
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.agent_commands.exceptions import (
|
||||
AgentCommandConflict,
|
||||
AgentCommandNotFound,
|
||||
AgentResultValidationError,
|
||||
)
|
||||
from app.agent_commands.models import (
|
||||
AgentCommand,
|
||||
AgentCommandResultRequest,
|
||||
MinerControlResultPayload,
|
||||
)
|
||||
from app.agent_commands.repository import AgentCommandRepository
|
||||
|
||||
|
||||
class AgentCommandService:
|
||||
def __init__(self, repository: AgentCommandRepository):
|
||||
self.repository = repository
|
||||
|
||||
def claim(self, agent_id: str) -> list[AgentCommand]:
|
||||
return [
|
||||
AgentCommand.model_validate(row)
|
||||
for row in self.repository.claim_pending(agent_id)
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _legacy_error(value: str | None) -> str | None:
|
||||
if not value:
|
||||
return None
|
||||
sanitized = re.sub(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "[address]", value)
|
||||
return sanitized[:255]
|
||||
|
||||
def submit_result(
|
||||
self, agent_id: str, request: AgentCommandResultRequest
|
||||
) -> None:
|
||||
command = self.repository.get_command(request.command_id)
|
||||
if not command:
|
||||
raise AgentCommandNotFound("Agent command not found")
|
||||
if command["agent_id"] != agent_id:
|
||||
raise AgentCommandConflict("Agent command belongs to another Agent")
|
||||
if command["command_status"] != "claimed":
|
||||
raise AgentCommandConflict(
|
||||
f"Agent command is {command['command_status']}, not claimed"
|
||||
)
|
||||
|
||||
result_payload = request.result_payload
|
||||
error_message = self._legacy_error(request.error_message)
|
||||
if command["command_type"] == "miner_control":
|
||||
try:
|
||||
result = MinerControlResultPayload.model_validate(request.result_payload)
|
||||
except ValidationError as exc:
|
||||
raise AgentResultValidationError(
|
||||
"Invalid typed miner-control result"
|
||||
) from exc
|
||||
if request.status == "completed" and not result.command_accepted:
|
||||
raise AgentResultValidationError(
|
||||
"Completed miner-control results must be accepted"
|
||||
)
|
||||
if request.status == "failed" and result.command_accepted:
|
||||
raise AgentResultValidationError(
|
||||
"Failed miner-control results cannot be accepted"
|
||||
)
|
||||
action = (command.get("request_payload") or {}).get("action")
|
||||
if (
|
||||
action in {"suspend", "resume"}
|
||||
and result.command_accepted
|
||||
and result.verification_status != "unconfirmed"
|
||||
):
|
||||
raise AgentResultValidationError(
|
||||
"Accepted suspend and resume results must remain unconfirmed"
|
||||
)
|
||||
result_payload = result.model_dump(mode="json")
|
||||
error_message = (
|
||||
result.failure_code.replace("_", " ") if result.failure_code else None
|
||||
)
|
||||
|
||||
self.repository.finish_command(
|
||||
request.command_id,
|
||||
agent_id,
|
||||
request.status,
|
||||
result_payload,
|
||||
error_message,
|
||||
)
|
||||
Reference in new issue
Block a user