## MODIFIED Requirements ### Requirement: Журнал синхронизаций и статус агента Таблица `sync_run` SHALL хранить каждую загрузку состояния: `started_at`, `finished_at`, `kind` (`keys | jql`), `query`, `issues_count`, `links_count`, `jira_calls`, `rate_limited`, `command_id`. При повторной выдаче команды `refresh` хаб SHALL обнулить `issues_count` и `links_count` её `sync_run` (`started_at` не меняется); при переводе `refresh` в `failed` по пределу выдач — проставить `finished_at`. Таблица `agent_status` SHALL хранить по строке на агента (ключ `agent` — имя модуля, см. `modules`, «Агенты модулей»): `last_seen_at`, `agent_version`, `settings_version`, `current_command_id`, `last_call_at` (время последнего обращения агента к внешней системе; от jira-agent приходит полем `last_jira_call_at`). Через API и MCP статус агента SHALL отдаваться с признаком `online` (истина, если `last_seen_at` моложе 5 минут) и списком `redelivered` — команд его модуля в `claimed`, выданных больше одного раза (`id`, `type`, `claimed_at`, `deliveries`). #### Scenario: Агент давно не выходил на связь - **WHEN** `last_seen_at` агента `jira` старше 5 минут - **THEN** `agent_status` через API и MCP отдаётся с `online = false` #### Scenario: Довыдача refresh не удваивает счётчики - **WHEN** `refresh` загрузил 150 задач, отчёт потерян, команда выдана снова и загрузила те же 150 - **THEN** у её `sync_run` `issues_count = 150` #### Scenario: refresh сдался по пределу выдач - **WHEN** `refresh` переведён в `failed` «агент не отчитался после 3 выдач» - **THEN** у его `sync_run` проставлен `finished_at` #### Scenario: Статусы агентов раздельно - **WHEN** агент `jira` выходил на связь минуту назад, агент `wiki` — никогда - **THEN** `GET /agents` отдаёт `jira` с `online = true` и `wiki` с `online = false`, `last_seen_at = null` ### Requirement: Проекты Таблица `project` SHALL хранить проекты моста — подмножества задач Jira-проекта `project_key`, выделенные меткой: `label` (PK, метка Jira), `name`, `created_at`. Метка SHALL быть непустой строкой до 100 символов без пробельных и управляющих символов (категория Unicode `Cc`), запятых, кавычек, скобок и обратной косой черты; две метки, совпадающие без учёта регистра, MUST NOT существовать одновременно. Название SHALL быть непустым после обрезки пробелов, до 100 символов. Метку и название после создания хаб не меняет (переименования и удаления нет). Доска проекта SHALL храниться в таблице модуля jira `jira_project`: `label` (PK, ссылка на проект), `board_id` (номер доски Jira или `NULL`), `board_type` (`scrum` | `kanban` | `NULL` — тип ещё не известен). `board_id` — целое больше нуля или `NULL`, после создания не меняется; `board_type` SHALL записываться только по результату `refresh` от агента (см. `agent`). Миграция v7 SHALL перенести `board_id` и `board_type` всех проектов из `project` в `jira_project`. Базовая JQL SHALL вычисляться из проектов: `project = AND labels in ("<метка>", …)` — каждая метка в двойных кавычках, метки по created_at, затем label, через `, `; без проектов базовой JQL нет (`null`). Для проекта P задачи зеркала делятся на *свои* — с меткой P в `labels` (точное совпадение) — и *соседей* — задачи без метки P, связанные со своей связью `issue_link` в любую сторону, и эпики своих (`epic_key` своей задачи), если они есть в зеркале. Признак `neighbor` в выдаче в разрезе проекта SHALL считаться относительно P: `false` у своих, `true` у соседей. #### Scenario: Базовая JQL из двух проектов - **WHEN** заведены проекты `AWPCA` (раньше) и `STOCKS_DRRK`, `project_key = AURORA` - **THEN** базовая JQL — `project = AURORA AND labels in ("AWPCA", "STOCKS_DRRK")` #### Scenario: Свои и соседи - **WHEN** у AURORA-1 метка `STOCKS_DRRK` и эпик AURORA-9 (метка `AWPCA`), связь AURORA-1 → AURORA-2 (метка `AWPCA`), у AURORA-3 метка `AWPCA` без связей с проектом `STOCKS_DRRK` - **THEN** в разрезе `STOCKS_DRRK` свои — AURORA-1, соседи — AURORA-2 и AURORA-9; AURORA-3 в разрез не входит #### Scenario: Метка с пробелом - **WHEN** создаётся проект с меткой `STOCKS DRRK` - **THEN** отказ с указанием поля `label`, проекта нет #### Scenario: Метка с обратной косой чертой или управляющим символом - **WHEN** создаётся проект с меткой `A\B` или `A<перевод строки>B` - **THEN** отказ с указанием поля `label`, проекта нет #### Scenario: Метка — зарезервированное слово JQL - **WHEN** заведён проект с меткой `EMPTY` - **THEN** базовая JQL — `project = AURORA AND labels in ("EMPTY")`, метка не расширяет выборку #### Scenario: Та же метка другим регистром - **WHEN** есть проект `AWPCA`, создаётся проект `awpca` - **THEN** отказ «проект с такой меткой уже есть», проект один #### Scenario: Доски после миграции v7 - **WHEN** база v6 с проектами `AWPCA` (`board_id = 4216`, `scrum`) и `sfm_kedr` (без доски) мигрирует в v7 - **THEN** в `project` нет колонок `board_id`, `board_type`; в `jira_project` — `AWPCA` 4216 `scrum` и `sfm_kedr` `NULL` `NULL`; перед миграцией сделана копия базы