Рулинг R-3 дописан в design.md; CLAUDE.md больше не ссылается на активный change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
6.3 KiB
Markdown
79 lines
6.3 KiB
Markdown
# data-model Specification
|
||
|
||
## Purpose
|
||
TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Схема базы хаба
|
||
База хаба SHALL быть одним файлом SQLite (WAL) с таблицами `issue`, `issue_link`, `issue_note`, `batch`, `command`,
|
||
`sync_run`, `agent_status`, `setting`. Схема SHALL создаваться и мигрироваться самим хабом при старте по номеру версии
|
||
в `setting`. Денежных и дробных полей нет; даты — ISO-8601 строки в UTC, кроме `duedate` и плановых дат (даты без времени).
|
||
|
||
#### Scenario: Первый запуск
|
||
- **WHEN** хаб стартует с несуществующим файлом базы
|
||
- **THEN** файл создаётся, таблицы и индексы на месте, `setting.schema_version` = текущая
|
||
|
||
### 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`, `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` и `issue_link` SHALL только загрузка состояния от
|
||
агента; HTTP API интерфейсов и MCP MUST NOT писать в них.
|
||
|
||
#### 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`
|
||
|
||
### Requirement: Служебные заметки
|
||
Таблица `issue_note` SHALL хранить поля, которых нет в Jira: `key` (PK; ключ Jira или временный `new:<n>`),
|
||
`fact_status`, `work_kind`, `done_text`, `rest_text`, `owner_note`, `updated_at`, `updated_by`. Обновление зеркала
|
||
MUST NOT менять `issue_note`. При завершении `ensure_issue` с временным ключом хаб SHALL переименовать строку заметок
|
||
на настоящий ключ.
|
||
|
||
#### Scenario: Заметка переживает refresh
|
||
- **WHEN** у задачи есть `issue_note.fact_status = "В работе"` и приходит полное обновление зеркала
|
||
- **THEN** `fact_status` не изменился
|
||
|
||
### Requirement: Пакеты и команды
|
||
Таблица `batch` SHALL хранить `id`, `title`, `author`, `created_at`, `cancelled_at`, `estimate_seconds`.
|
||
Таблица `command` SHALL хранить `id`, `batch_id`, `seq`, `type`, `params` (JSON), `status`
|
||
(`queued | claimed | done | failed | skipped | cancelled | confirmed_by_state`), `claimed_at`, `finished_at`, `result` (JSON),
|
||
`error`, `jira_calls`, `rate_limited` (число 429/503), `pause_seconds` (выданная пауза), `depends_on` (id команды,
|
||
чей результат подставляется). Статус пакета SHALL вычисляться из команд, не храниться.
|
||
|
||
#### Scenario: Статус пакета
|
||
- **WHEN** в пакете 3 команды: `done`, `claimed`, `queued`
|
||
- **THEN** статус пакета «выполняется»; когда все в терминальных статусах — «завершён», с числом `failed`/`skipped`
|
||
|
||
### Requirement: Журнал синхронизаций и статус агента
|
||
Таблица `sync_run` SHALL хранить каждую загрузку состояния: `started_at`, `finished_at`, `kind` (`keys | jql`), `query`,
|
||
`issues_count`, `links_count`, `jira_calls`, `rate_limited`, `command_id`. Таблица `agent_status` (одна строка) SHALL
|
||
хранить `last_seen_at`, `agent_version`, `settings_version`, `current_command_id`, `last_jira_call_at`.
|
||
|
||
#### Scenario: Агент давно не выходил на связь
|
||
- **WHEN** `last_seen_at` старше 5 минут
|
||
- **THEN** `agent_status` через API и MCP отдаётся с признаком `offline`
|
||
|
||
### Requirement: Настройки
|
||
Таблица `setting` SHALL хранить парами ключ–JSON: `base_jql`, `field_ids`, `issue_types`, `priority_map`, `link_types`,
|
||
`transitions_map`, `pacing`, `board_id`, `sprint_names`, `tokens` (агентский и клиентский), `settings_version`
|
||
(увеличивается при любой правке), `backup_keep`, `auto_refresh_minutes`, `relay_dir` (абсолютный путь папки реле или
|
||
`null`, по умолчанию `null`; относительный путь и не-строка при записи — 422; агенту не передаётся). Начальные значения
|
||
SHALL импортироваться из `admin/jira-sync/config.example.json` командой `jira-hub init`; пароль и логин Jira в настройках
|
||
MUST NOT присутствовать.
|
||
|
||
#### Scenario: Правка настроек поднимает версию
|
||
- **WHEN** через API изменён `pacing`
|
||
- **THEN** `settings_version` увеличен, следующий `claim` отдаёт агенту новую версию
|
||
|
||
#### Scenario: Неверный relay_dir
|
||
- **WHEN** `PUT /settings` с `relay_dir = "rshb/jira-bridge"`
|
||
- **THEN** 422, значение не записано, `settings_version` не изменился
|