"""Мандат ИИ-агента: что агенту можно и как он берёт задачу в работу (ТЗ 3.20).
Без флага `ai_eligible` агент задачу не меняет — только читает. Это не охрана:
держатель токена и так действует как владелец (3.20), а рамка нужна, чтобы агент не
трогал то, что владелец ему не поручал.
Ошибки — доменным исключением: REST отдаёт по нему 403, MCP — текст с подсказкой
следующего шага (стиль 3.10), а правила остаются одними для обоих путей.
"""
from datetime import timedelta
from app.actor import Actor
from app.models import Task
# Аренда (взятие в работу) живёт два часа: агент продлевает её повторным claim,
# пропал — задача сама вернулась в свободные (ленивое протухание, без крона)
CLAIM_TTL = timedelta(hours=2)
class MandateError(Exception):
"""Действие агента вне мандата: владелец задачу ему не поручал."""
def ensure_agent_can_mutate(task: Task, actor: Actor) -> None:
"""Агент меняет только задачи с флагом доступности; владелец — любые."""
if actor.is_agent and not task.ai_eligible:
raise MandateError(f"Task {task.id} is not available to an AI agent")
def ensure_owner_sets_eligible(actor: Actor) -> None:
"""Флаг доступности ставит владелец: агент не выдаёт себе мандат задним числом.
Своя задача — другое дело: при создании агент вправе попросить мандат (3.20).
"""
if actor.is_agent:
raise MandateError("ai_eligible is set by the owner")