# modules Specification ## Purpose Устройство хаба из ядра и подключаемых модулей (первый — `jira`): что принадлежит ядру, как модуль подключается описанием, проверка пересечений при старте и единая схема базы. ## Requirements ### Requirement: Ядро и модули хаба Хаб SHALL состоять из ядра и подключаемых модулей. Ядро SHALL владеть базой и миграциями, настройками хаба, проектами (метка, название), хранилищем файлов, реле общей папки, релизом агента, очередью пакетов команд, HTTP-сервером с авторизацией, раздачей веб-приложения и общим слоем MCP. Модуль SHALL подключаться к ядру описанием: таблицы, типы команд и их обработчики в очереди, маршруты HTTP, ключи настроек, фоновые работы, инструменты MCP, подкоманды CLI. Первый модуль — `jira` (зеркало задач, журнал изменений, вложения, команды Jira). Ядро MUST NOT импортировать модули, кроме списка подключённых модулей и истории миграций схемы. При старте хаб SHALL проверять, что типы команд, ключи настроек и маршруты (метод и шаблон адреса) ядра и модулей не пересекаются; пересечение — ошибка старта с указанием конфликта. Схема базы SHALL оставаться одной, с одной версией `setting.schema_version` на весь хаб; таблицы модуля входят в неё. #### Scenario: Конфликт типов команд - **WHEN** два подключённых модуля объявляют тип команды `refresh` - **THEN** хаб не стартует, ошибка называет тип и оба модуля #### Scenario: Поведение без изменений - **WHEN** хаб после перестройки стартует на базе v6 с агентом v14 - **THEN** схема остаётся v6, копия базы не создаётся; адреса API, веба, ответы и инструменты MCP — прежние; агент берёт команды и отчитывается как раньше ### Requirement: Агенты модулей У каждого подключённого модуля, объявляющего типы команд, SHALL быть свой агент; имя агента — имя модуля (`jira` — jira-agent, `wiki` — eva-agent). Агент SHALL определяться по токену: `setting.tokens = {client, agents: {<модуль>: <токен>}}`, недостающий токен агента хаб создаёт при старте. Хаб SHALL для каждого агента отдельно: - выдавать в `claim` только команды типов его модуля, с одной командой в полёте на агента (агенты работают параллельно); - хранить строку `agent_status` (ключ — имя агента); - держать опубликованный релиз `release/<модуль>/` и состояние самообновления; - отдавать в `GET /agent/settings` ключи ядра и ключи для агента только своего модуля; - пускать на маршруты модуля со scope `agent` только агента этого модуля (токен другого агента — 401). Миграция v7 SHALL сохранить прежний агентский токен как токен агента `jira`, перенести строку `agent_status` в агента `jira` и прежнюю публикацию `release/agent.py` в `release/jira/`. jira-agent v14 SHALL работать без обновления. #### Scenario: jira-agent после выкатки - **WHEN** хаб v6 с агентским токеном T и публикацией v14 обновлён до v7, jira-agent v14 шлёт `claim` с токеном T - **THEN** 200; ответ содержит `agent_release {version: 14, …}`; `GET /agent` показывает агента `jira` с версией 14 #### Scenario: Агенты не берут чужие команды - **WHEN** в очереди пакет 1 с `ensure_issue` и пакет 2 с `wiki_sync`, оба `queued`; агент `wiki` шлёт `claim` - **THEN** выдан `wiki_sync`; следующий `claim` агента `jira` получает `ensure_issue`, хотя `wiki_sync` ещё `claimed` #### Scenario: Чужой маршрут агента - **WHEN** токеном агента `wiki` вызван `POST /agent/state` - **THEN** 401, зеркало не меняется #### Scenario: Настройки только своего модуля - **WHEN** токеном агента `wiki` вызван `GET /agent/settings` - **THEN** в ответе есть `wiki_pacing` и `settings_version`, ключей jira (`project_key`, `link_types`) нет ### Requirement: Поля проекта от модулей Проект ядра SHALL состоять из `label`, `name`, `created_at`; остальные поля проекта SHALL объявлять модули: имя, тип, проверку, изменяемость после создания, подпись и подсказку. Модуль SHALL сам хранить свои поля и отдавать их значения (включая вычисляемые) для списка проектов. При старте хаб SHALL отвергать два модуля с одним именем поля или поле модуля с именем поля ядра — ошибка старта с именем поля и модулей. В ядре SHALL NOT оставаться знания о полях модулей (доска, число задач, корень вики). #### Scenario: Конфликт полей - **WHEN** два модуля объявляют поле проекта `root` - **THEN** хаб не стартует, ошибка называет поле и оба модуля #### Scenario: Поля jira прежние - **WHEN** после миграции v7 вызван `GET /projects` - **THEN** у `AWPCA` `board_id = 4216`, `board_type = scrum`, `issues_count` — как до миграции ### Requirement: Подготовка отчёта команды модулем Контракт `CommandHooks` SHALL содержать два необязательных хука. `before_result(conn, cmd, status, result, files_dir)` SHALL вызываться ядром при отчёте по выданной команде до транзакции отчёта, чтобы модуль готовил тяжёлую работу (перевод, запись файлов) вне блокировки базы; `ValueError` из него SHALL отдаваться как ошибка сервера, а не как неверный статус отчёта. `abort_result(conn, cmd)` SHALL вызываться, когда отчёт не дошёл до `after_result` (ранний выход, исключение), и снимать подготовку `before_result`. Модуль без этих хуков SHALL работать как прежде. #### Scenario: Подготовка до транзакции - **WHEN** агент шлёт отчёт `done` по команде типа модуля с `before_result` - **THEN** `before_result` вызван до `BEGIN IMMEDIATE`, после фиксации транзакции — `after_result` #### Scenario: Отчёт прерван - **WHEN** `before_result` выполнен, а транзакция отчёта завершилась исключением - **THEN** вызван `abort_result`, подготовка снята, команда остаётся выданной