Andrey Epifancev 30833a466a Архив OpenSpec: sprints-backlog (#108)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:24:17 +04:00

15 KiB
Raw Blame History

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, у X work_kind = "Аналитика", updated_at прежний; ключей status_labels и transitions_map в setting нет; settings_version = 13; schema_version = текущая

Scenario: Миграция базы v8

  • WHEN хаб стартует с базой schema_version = 8, в зеркале задача X со спринтом «Спринт 2»
  • THEN в backups/ копия базы v8; у X rank и story_points — NULL, sprint прежний; таблицы jira_board, jira_sprint, issue_sprint пусты; schema_version = 9

Scenario: refresh в очереди при миграции

  • WHEN в базе v3 стоит queued refresh {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 — нет