r"""Мандат ИИ-агента: доступность задачи, аренда, приёмка, журнал (ТЗ 3.20).
Поля задачи: ai_eligible (доступно агенту; по умолчанию нет), created_by_kind /
created_by_name (кто создал — под фильтр «создано агентом»), done_by_kind,
accept_state (плоскость приёмки, отдельная от статуса, как detail_state),
claimed_by_token_id / claimed_by_name / claimed_at / claim_expires_at (аренда).
Существующие задачи бэкфиллятся server_default: агенту недоступны, созданы
пользователем. accept_state и done_by_kind остаются NULL — приёмка к старым
закрытиям не применяется, а кто их закрыл, неизвестно; выдумывать не будем.
Новая таблица task_events — журнал «кто, что и когда»: записи переживают задачу
(task_id обнуляется по ON DELETE SET NULL), заголовок хранится снапшотом.
Revision ID: a1b2c3d4e5f7
Revises: d4e5f6a7b8c9
Create Date: 2026-10-10
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "a1b2c3d4e5f7"
down_revision: str | Sequence[str] | None = "d4e5f6a7b8c9"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"tasks",
sa.Column("ai_eligible", sa.Boolean(), nullable=False, server_default=sa.false()),
)
op.add_column(
"tasks",
sa.Column("created_by_kind", sa.String(16), nullable=False, server_default="user"),
)
op.add_column("tasks", sa.Column("created_by_name", sa.String(100), nullable=True))
op.add_column("tasks", sa.Column("done_by_kind", sa.String(16), nullable=True))
op.add_column("tasks", sa.Column("accept_state", sa.String(16), nullable=True))
op.add_column(
"tasks",
sa.Column(
"claimed_by_token_id",
sa.Integer(),
sa.ForeignKey("mcp_tokens.id", ondelete="SET NULL"),
nullable=True,
),
)
op.add_column("tasks", sa.Column("claimed_by_name", sa.String(100), nullable=True))
op.add_column("tasks", sa.Column("claimed_at", sa.DateTime(timezone=True), nullable=True))
op.add_column("tasks", sa.Column("claim_expires_at", sa.DateTime(timezone=True), nullable=True))
op.create_index("ix_tasks_user_ai_eligible", "tasks", ["user_id", "ai_eligible"])
op.create_index("ix_tasks_user_accept_state", "tasks", ["user_id", "accept_state"])
op.create_index("ix_tasks_user_created_by", "tasks", ["user_id", "created_by_kind"])
op.create_table(
"task_events",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("user_id", sa.String(64), sa.ForeignKey("users.id"), nullable=True),
sa.Column(
"task_id",
sa.Integer(),
sa.ForeignKey("tasks.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("task_title", sa.String(500), nullable=False, server_default=""),
sa.Column(
"project_id",
sa.Integer(),
sa.ForeignKey("projects.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("kind", sa.String(24), nullable=False),
sa.Column("actor_kind", sa.String(16), nullable=False, server_default="user"),
sa.Column("actor_name", sa.String(100), nullable=False, server_default=""),
sa.Column("via", sa.String(8), nullable=False, server_default="ui"),
sa.Column("comment", sa.Text(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()"),
),
)
op.create_index("ix_task_events_user_id", "task_events", ["user_id"])
op.create_index("ix_task_events_user_created", "task_events", ["user_id", "created_at"])
op.create_index("ix_task_events_user_kind", "task_events", ["user_id", "kind"])
op.create_index("ix_task_events_user_task", "task_events", ["user_id", "task_id"])
op.create_index("ix_task_events_user_actor", "task_events", ["user_id", "actor_kind"])
def downgrade() -> None:
op.drop_index("ix_task_events_user_actor", table_name="task_events")
op.drop_index("ix_task_events_user_task", table_name="task_events")
op.drop_index("ix_task_events_user_kind", table_name="task_events")
op.drop_index("ix_task_events_user_created", table_name="task_events")
op.drop_index("ix_task_events_user_id", table_name="task_events")
op.drop_table("task_events")
op.drop_index("ix_tasks_user_created_by", table_name="tasks")
op.drop_index("ix_tasks_user_accept_state", table_name="tasks")
op.drop_index("ix_tasks_user_ai_eligible", table_name="tasks")
op.drop_column("tasks", "claim_expires_at")
op.drop_column("tasks", "claimed_at")
op.drop_column("tasks", "claimed_by_name")
op.drop_column("tasks", "claimed_by_token_id")
op.drop_column("tasks", "accept_state")
op.drop_column("tasks", "done_by_kind")
op.drop_column("tasks", "created_by_name")
op.drop_column("tasks", "created_by_kind")
op.drop_column("tasks", "ai_eligible")