15 KiB
MODIFIED Requirements
Requirement: Схема базы хаба
База хаба SHALL быть одним файлом SQLite (WAL) с таблицами issue, issue_link, issue_comment, issue_change, issue_attachment,
attachment_file, issue_note, issue_sprint, jira_board, jira_sprint, batch, command, sync_run, agent_status, setting, project. Схема SHALL создаваться и мигрироваться самим хабом при
старте по номеру версии в setting.schema_version (текущая — 9). Миграция существующей базы SHALL выполняться в одной
транзакции с блокировкой записи; в той же транзакции, после повторной проверки версии, хаб SHALL скопировать файл базы в
backups/<дата-время>-schema-v<старая>.sqlite рядом с базой — одна копия на миграцию, сколько бы соединений ни
открывалось одновременно. Каталог данных хаба и backups/ SHALL иметь права 0700 (в базе токены): хаб выставляет их
при создании и при каждом старте; если выставить не удалось — предупреждение в лог, работа продолжается. Денежных и
дробных полей нет; даты — ISO-8601 строки в UTC, кроме duedate и плановых дат (даты без времени), строк времени
Jira как есть (issue_comment.created/updated, issue_change.created, issue_attachment.created) и значений полей журнала.
Миграция v3 → v4 SHALL: создать project и колонку batch.project (у существующих пакетов NULL); разобрать прежнюю
настройку base_jql вида … labels = <M> или … labels in (<M1>, …) и завести проекты с этими метками (название проекта
AWPCA — «АРМ КА», остальных — метка; board_type = NULL); отдать прежний board_id проекту, если проект один, иначе
никому, с предупреждением в лог; у строк sync_run с query, равной прежней base_jql, и у команд refresh в
queued/claimed с params.jql, равной прежней base_jql, записать базовую JQL, вычисленную из проектов. Ключи
base_jql и board_id в setting миграция MUST NOT удалять: базу может открыть процесс новой версии раньше, чем
перезапущен хаб прежней версии, который их читает. Не разобранная base_jql — проектов нет, предупреждение в лог с
прежним значением.
Миграция v4 → v5 SHALL: создать issue_change с индексами; добавить колонки issue_comment.created_at и
sync_run.history_at; заполнить issue_comment.created_at у имеющихся комментариев приведением created к UTC (не
разобралось — NULL). У имеющихся sync_run history_at = NULL.
Миграция v5 → v6 SHALL: создать пустые issue_attachment (с индексом по key) и attachment_file. Списки вложений
появляются с первым refresh агента v14.
Миграция v7 → v8 SHALL: удалить колонку issue_note.fact_status (если она есть; значения удаляются безвозвратно, копия —
в backups/ по общему правилу) и ключи status_labels и transitions_map из setting, если они есть, и поднять
settings_version, если хотя бы один из ключей был удалён. Прочие поля заметок, задачи и настройки миграция MUST NOT
менять.
Миграция v8 → v9 SHALL: добавить колонки issue.rank и issue.story_points (у имеющихся задач NULL); таблицы
jira_board, jira_sprint, issue_sprint создаются схемой пустыми. Данные появляются с первым refresh агента v15.
Scenario: Первый запуск
- WHEN хаб стартует с несуществующим файлом базы
- THEN файл создаётся, таблицы и индексы на месте,
setting.schema_version= текущая; каталог данных с правами0700
Scenario: Миграция базы v1
- WHEN хаб стартует с базой
schema_version = 1, в которой есть задачи, заметки и команда вclaimed - THEN в
backups/копия базы v1; добавленаcommand.deliveries(уclaimed— 1, у остальных — 0); добавлена пустаяissue_comment; данные на месте;schema_version= текущая
Scenario: Миграция базы v2
- WHEN хаб стартует с базой
schema_version = 2, в которой есть задачи, связи и заметки - THEN в
backups/копия базы v2; добавлена пустаяissue_comment; данные на месте;schema_version= текущая
Scenario: Миграция базы v3
- WHEN хаб стартует с базой
schema_version = 3,base_jql = "project = AURORA AND labels = AWPCA",board_id = 4216, вsync_runполный прогон с этойquery - THEN в
backups/копия базы v3; проектAWPCA«АРМ КА» сboard_id = 4216,board_type = NULL; ключиbase_jqlиboard_idвsettingна месте;queryтого прогона —project = AURORA AND labels in ("AWPCA"); задачи, заметки, пакеты на месте;schema_version= текущая
Scenario: Миграция базы v4
- WHEN хаб стартует с базой
schema_version = 4, у задачи X комментарий сcreated = "2026-10-22T13:05:11.000+0300", вsync_runполный прогон - THEN в
backups/копия базы v4; есть пустаяissue_change; у комментарияcreated_at = "2026-10-22T10:05:11+00:00",createdпрежний; у прогонаhistory_at = NULL; затем миграция v5 → v6;schema_version= текущая
Scenario: Миграция базы v5
- WHEN хаб стартует с базой
schema_version = 5 - THEN в
backups/копия базы v5; есть пустыеissue_attachmentиattachment_file; прочие таблицы не тронуты;schema_version= текущая
Scenario: Миграция базы v7
- WHEN хаб стартует с базой
schema_version = 7, у задачи X заметкаfact_status = "В работе",work_kind = "Аналитика", вsettingестьstatus_labelsиtransitions_map,settings_version = 12 - THEN в
backups/копия базы v7; вissue_noteнет колонкиfact_status, у Xwork_kind = "Аналитика",updated_atпрежний; ключейstatus_labelsиtransitions_mapвsettingнет;settings_version = 13;schema_version= текущая
Scenario: Миграция базы v8
- WHEN хаб стартует с базой
schema_version = 8, в зеркале задача X со спринтом «Спринт 2» - THEN в
backups/копия базы v8; у Xrankиstory_points—NULL,sprintпрежний; таблицыjira_board,jira_sprint,issue_sprintпусты;schema_version = 9
Scenario: refresh в очереди при миграции
- WHEN в базе v3 стоит
queuedrefresh {jql: "project = AURORA AND labels = AWPCA"} - THEN после миграции его
params.jql—project = AURORA AND labels in ("AWPCA"), и по завершении он считается полным прогоном
Scenario: Два соединения к базе v1
- WHEN к базе
schema_version = 1одновременно открываются два соединения - THEN миграция выполнена один раз, в
backups/одна копияschema-v1
Scenario: Открытый каталог данных
- WHEN хаб стартует, каталог данных существует с правами
0755 - THEN права каталога и
backups/—0700
Requirement: Зеркало задачи
Таблица issue SHALL хранить задачу Jira как есть: key (PK), id, issue_type, summary, description (wiki),
status, priority, assignee, reporter, duedate, planned_start, planned_end, epic_key, labels (JSON-список),
sprint, rank (ранг Jira строкой как есть или NULL), story_points (оценка, число или NULL),
jira_updated, fetched_at, neighbor (0/1 — вне базовой JQL, дочитана по связи), created. Таблица issue_link
SHALL хранить связи (source_key, target_key, link_type) с направлением по семантике Jira: source блокирует/ссылается
на target (у source в GET это outwardIssue). Таблица issue_comment SHALL хранить комментарии задачи как есть:
key, comment_id (PK вместе с key), author, created, updated, body (wiki). Таблица issue_attachment SHALL
хранить список вложений задачи без содержимого: attachment_id (PK, id вложения Jira), key, filename, size (байт
или NULL), mime_type, author, created (время Jira как есть), created_at (UTC, приведённое из created; не
разобралось — NULL). Обновлять issue, issue_link, issue_comment и
issue_attachment SHALL только загрузка состояния от агента; HTTP API интерфейсов и MCP MUST NOT писать в них. Присланные
комментарии задачи SHALL заменять все её прежние комментарии; задача, присланная без поля comments, свои комментарии
SHALL сохранять. Так же со вложениями: присланное поле attachments заменяет все прежние вложения задачи, задача без
поля (агент до v14) свои вложения сохраняет. Полный прогон (refresh по базовой JQL), завершившийся done, SHALL помечать neighbor = 1 задачи, не
обновлённые им, — только если прогон загрузил хотя бы одну задачу.
Scenario: Загрузка состояния заменяет связи
- WHEN агент прислал состояние задачи X с двумя связями
- THEN все строки
issue_linkсsource_key = Xзаменены на присланные; строки сtarget_key = X, не присланные со стороны X, не трогаются до загрузки их источника
Scenario: Соседняя задача
- WHEN в
issuelinksзадачи из базовой JQL есть ключ Y вне JQL - THEN Y сохраняется с
neighbor = 1; при следующем полномrefresh, если Y вошла в JQL,neighbor = 0
Scenario: Пустой полный прогон
- WHEN полный
refreshзавершилсяdone, загрузив 0 задач - THEN признак
neighborзадач зеркала не меняется
Scenario: Комментарий удалён в Jira
- WHEN у X в зеркале комментарии 101 и 102, агент прислал X с
comments = [{id: "102", …}] - THEN у X в
issue_commentтолько 102
Scenario: Состояние от агента до v10
- WHEN у X в зеркале три комментария, агент v9 прислал X без поля
comments - THEN поля X обновлены, три комментария на месте
Scenario: Вложения в состоянии
- WHEN у X в зеркале вложения #1 и #2, агент v14 прислал X с
attachments = [{id: "2", …}, {id: "3", …}] - THEN у X в
issue_attachment#2 (поля из состояния) и #3; #1 удалено;attachment_fileне тронута
Scenario: Состояние от агента до v14
- WHEN у X в зеркале два вложения, агент v13 прислал X без поля
attachments - THEN поля X обновлены, два вложения на месте
ADDED Requirements
Requirement: Доски, спринты и членство задач
Модуль jira SHALL хранить данные досок проектов, присланные агентом в refresh (см. hub-api, «Приём досок и
спринтов»):
jira_board:board_id(PK),type(scrum|kanban|NULL),columns(JSON-список[{name, statuses: [имя статуса]}]в порядке доски илиNULL),kanban_backlog(1 / 0 /NULL— неизвестно),fetched_at(UTC);jira_sprint:sprint_id(PK, id спринта Jira),board_id,name,state(active|future|closed),start_date,end_date,complete_date(строки Jira как есть илиNULL),goal;issue_sprint:key,sprint_id(PK — пара) — все спринты задачи любого состояния. Писать их SHALL только загрузка состояния от агента. Выполненной на доске SHALL считаться задача, чейstatusвходит вstatusesпоследней колонки доски; колонок нет (NULL) — выполненных нет.
Scenario: Задача прошла два спринта
- WHEN агент прислал задачу X с
sprint_ids: [101, 102], спринт 101 закрыт, 102 активен - THEN у X две строки
issue_sprint; X в составе закрытого спринта 101 и активного 102
Scenario: Колонки доски
- WHEN у доски 4216 колонки «К выполнению» [
Backlog,Ожидает анализа], «В работе» […], «Готово» [Готово,Протестировано,Снято] - THEN задача в статусе
Протестировановыполнена, в статусеBacklog— нет