Andrey Epifancev 534e43865b Архив OpenSpec: change-history (#70)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:06:16 +04:00

11 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Схема базы хаба

База хаба SHALL быть одним файлом SQLite (WAL) с таблицами issue, issue_link, issue_comment, issue_change, issue_note, batch, command, sync_run, agent_status, setting, project. Схема SHALL создаваться и мигрироваться самим хабом при старте по номеру версии в setting.schema_version (текущая — 5). Миграция существующей базы SHALL выполняться в одной транзакции с блокировкой записи; в той же транзакции, после повторной проверки версии, хаб SHALL скопировать файл базы в backups/<дата-время>-schema-v<старая>.sqlite рядом с базой — одна копия на миграцию, сколько бы соединений ни открывалось одновременно. Каталог данных хаба и backups/ SHALL иметь права 0700 (в базе токены): хаб выставляет их при создании и при каждом старте; если выставить не удалось — предупреждение в лог, работа продолжается. Денежных и дробных полей нет; даты — ISO-8601 строки в UTC, кроме duedate и плановых дат (даты без времени), строк времени Jira как есть (issue_comment.created/updated, issue_change.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.

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

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

  • WHEN хаб стартует с базой schema_version = 2, в которой есть задачи, связи и заметки
  • THEN в backups/ копия базы v2; добавлена пустая issue_comment; данные на месте; schema_version = 5

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

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; schema_version = 5

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

ADDED Requirements

Requirement: Журнал изменений задач

Таблица issue_change SHALL хранить журнал изменений задач Jira по строке на поле записи журнала: history_id (id записи журнала Jira), seq (номер поля в записи, с 0), key, at (время записи в ISO-8601 UTC, приведённое хабом), created (время Jira как есть), author, field (логическое имя), field_id, field_name, from_value, from_string, to_value, to_string, fetched_at; PK (history_id, seq), индексы по at и по (key, at). Записывать в issue_change SHALL только приём журнала от агента (POST /agent/history); повторно присланная строка с тем же (history_id, seq) MUST NOT менять сохранённую. HTTP API интерфейсов и MCP MUST NOT писать в issue_change.

Логическое имя field SHALL вычисляться хабом при приёме: status, description, summary, assignee, priority, duedate, labels, resolution — если так называется field_id, а при его отсутствии — field_name; planned_start, planned_end — если field_id равен id этого поля в настройке field_ids; epic_link — если field_id равен epic_link.field_id; иначе — field_name как есть.

Время Jira (created журнала, created комментария) SHALL приводиться к UTC разбором ISO-8601 со смещением вида +0300, +03:00 или Z, с дробной частью секунд или без; время без смещения считается UTC; неразбираемое — NULL в приведённой колонке (строка журнала с at = NULL в ленту не попадает).

Таблица issue_comment SHALL хранить также created_at — created, приведённое к UTC, заполняемое при каждой записи комментариев.

Scenario: Повтор записи журнала

  • WHEN агент дважды прислал элемент {history_id: "5001", seq: 0, key: X, field_name: "status", …}
  • THEN в issue_change одна строка (5001, 0); второй приём ничего не изменил

Scenario: Плановая дата по id поля

  • WHEN в настройках field_ids.planned_end = "customfield_10201", агент прислал элемент с field_id = "customfield_10201", field_name = "Плановое окончание"
  • THEN у строки field = "planned_end"

Scenario: Неизвестное поле

  • WHEN агент прислал элемент с field_id = null, field_name = "Fix Version"
  • THEN у строки field = "Fix Version"

Scenario: Время со смещением

  • WHEN элемент журнала с created = "2026-10-22T09:00:00.000+0300"
  • THEN у строки at = "2026-10-22T06:00:00+00:00", created — как прислано

Requirement: Отметка журнала в журнале синхронизаций

Таблица sync_run SHALL хранить history_at — время, когда хаб принял итог журнала по этому прогону (NULL — журнал по прогону не принят). Хаб SHALL проставлять history_at при отчёте полного прогона (refresh по базовой JQL) со статусом done и result.history без error; у прочих refresh, при failed, cancelled, без result.history или с history.error — оставлять NULL. history.backfill_error и непустой history.incomplete отметку не держат.

Scenario: Журнал принят

  • WHEN refresh по базовой JQL завершился done с result.history = {issues: 3, changes: 7, incomplete: []}
  • THEN у его sync_run проставлен history_at

Scenario: Ошибка стартовой загрузки не держит отметку

  • WHEN refresh по базовой JQL завершился done с result.history = {issues: 1, changes: 2, incomplete: [], backfill_error: "HTTP 400 …"}
  • THEN у его sync_run проставлен history_at

Scenario: Ошибка журнала

  • WHEN refresh завершился done с result.history = {error: "HTTP 500 …"}
  • THEN у его sync_run history_at = NULL

Scenario: Старый агент

  • WHEN агент v12 завершил refresh done без result.history
  • THEN у его sync_run history_at = NULL