Andrey Epifancev 9aaf3cee50 Устаревшие ключи настроек удаляет ensure_defaults; выкатка схемы v8 в документах (#104)
I-1, I-2 финального ревью: старые MCP-сессии возвращали status_labels/transitions_map
после миграции; окно до kickstart описано для v8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:22:44 +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, batch, command, sync_run, agent_status, setting, project. Схема SHALL создаваться и мигрироваться самим хабом при старте по номеру версии в setting.schema_version (текущая — 8). Миграция существующей базы 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 менять.

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 = 8

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_note SHALL хранить поля, которых нет в Jira: key (PK; ключ Jira или временный new:<n>), work_kind, done_text, rest_text, owner_note, updated_at, updated_by. Обновление зеркала MUST NOT менять issue_note. При завершении ensure_issue с временным ключом хаб SHALL перенести заметку на настоящий ключ — в том числе по позднему отчёту done с ключом о команде, отменённой или переведённой в failed до отчёта. Если под настоящим ключом заметка уже есть, хаб SHALL слить их: каждое поле берётся из временной заметки, если там оно непустое, иначе остаётся под настоящим ключом; updated_at/updated_by — от более поздней; временная строка удаляется.

Scenario: Заметка переживает refresh

  • WHEN у задачи есть issue_note.work_kind = "Аналитика" и приходит полное обновление зеркала
  • THEN work_kind не изменился

Scenario: Слияние с заметкой под ключом

  • WHEN у new:5 заметка work_kind = "Аналитика", owner_note пусто; у AURORA-1640 заметка owner_note = "спросить ИБ"; ensure_issue для new:5 завершилась с AURORA-1640
  • THEN у AURORA-1640 work_kind = "Аналитика" и owner_note = "спросить ИБ"; строки new:5 нет

Requirement: Настройки

Таблица setting SHALL хранить парами ключ–JSON: project_key, field_ids, issue_types, priority_map, link_types, pacing, tokens (агентский и клиентский), settings_version (увеличивается при любой правке), backup_keep, auto_refresh_minutes, relay_dir (абсолютный путь папки реле или null, по умолчанию null; относительный путь и не-строка при записи — 422; агенту не передаётся), closed_statuses (список имён статусов Jira, считающихся закрытыми, по умолчанию ["Готово", "Снято"]; не-список при записи — 422; агенту не передаётся; на существующей базе появляется со значением по умолчанию при открытии хаба). Базовая JQL вычисляется из проектов, доска — свойство проекта (требование «Проекты»); на новой базе ключей base_jql и board_id нет. Оставшиеся от v3 ключи хаб SHALL NOT использовать, кроме передачи board_id агенту как запасной доски для команд без доски в параметрах (поставленных до миграции); Ключи, которые модуль объявил устаревшими (у jira — status_labels, transitions_map), хаб SHALL удалять при каждом заполнении умолчаний, не меняя settings_version. GET /settings SHALL отдавать вычисленную base_jql только для чтения, а запись base_jql или board_id через PUT /settings — 422 с пояснением. Спринты агент читает с досок проектов, отдельной настройки имён спринтов нет. Начальные значения SHALL импортироваться из admin/jira-sync/config.example.json командой sdlc-hub init; пароль и логин Jira в настройках MUST NOT присутствовать. issue_types — объект {task, epic, bug}, у каждого {name, id} (id может быть null); init берёт их из ключей конфига issue_type_<тип> и issue_type_<тип>_id; bug попадает в issue_types, только если в конфиге есть хотя бы один из ключей issue_type_bug, issue_type_bug_id (task и epic — как раньше, всегда).

Scenario: Правка настроек поднимает версию

  • WHEN через API изменён pacing
  • THEN settings_version увеличен, следующий claim отдаёт агенту новую версию

Scenario: Конфиг без ключей ошибки

  • WHEN sdlc-hub init --config с issue_type_task, issue_type_epic, без issue_type_bug
  • THEN issue_types содержит task и epic, bug нет; постановка ensure_issue с issue_type = bug — 422

Scenario: Неверный relay_dir

  • WHEN PUT /settings с relay_dir = "rshb/jira-bridge"
  • THEN 422, значение не записано, settings_version не изменился

Scenario: Закрытые статусы по умолчанию

  • WHEN хаб открыл базу, в которой нет ключа closed_statuses
  • THEN GET /settings отдаёт closed_statuses = ["Готово", "Снято"]; claim агенту этого ключа не передаёт

Scenario: Запись базовой JQL

  • WHEN PUT /settings {base_jql: "project = AURORA"}
  • THEN 422 «базовая JQL собирается из проектов»; settings_version не изменился

Scenario: Создание проекта не трогает версию настроек

  • WHEN создан проект STOCKS_DRRK
  • THEN settings_version прежний; GET /settings отдаёт base_jql с обеими метками

Scenario: Ключи вернул процесс прежней версии

  • WHEN база v8, процесс со старым кодом (MCP-сессия до reconnect) вставил status_labels = {} и transitions_map = {}, затем вызван ensure_defaults текущего кода (старт хаба, новая MCP-сессия)
  • THEN ключей в setting нет, settings_version прежний

Scenario: Ключей статуса по факту нет

  • WHEN хаб открыл базу v8
  • THEN GET /settings не содержит status_labels и transitions_map; GET /agent/settings агенту jira их не отдаёт