Andrey Epifancev f8867939d5 docs: рулинги R-6, R-7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
2026-10-11 22:12:19 +04:00

74 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
Две машины в одной локальной сети: Mac mini (`192.168.0.106`, Python 3.13, uv, Docker не всегда запущен) и виртуалка
с macOS и системным Python 3.9.6 (`192.168.0.112`, входящих портов нет, `pip3` есть, доступ к Jira банка
`https://sdlc.go.rshbank.ru/jira` по логину/паролю из `config.json`). Общая папка гипервизора — запасной канал.
Исходный код для переноса: `admin/jira-sync/jira_sync.py` (клиент REST v2 + Agile, темп `Pacer`, карта переходов,
исправленная семантика связей: в POST `/issueLink` `inwardIssue` — источник, в GET `outwardIssue` у X — цель X),
`config.example.json` (id полей, типы, карта переходов, типы связей, доска 4216).
## Goals / Non-Goals
Goals: убрать ручной перенос файлов; один источник ключей; журнал и повторяемость изменений; безопасный повтор любой
команды; темп, при котором Jira не отвечает 429; MCP, через который сессия ставит пакеты и читает состояние.
Non-goals — см. proposal.md.
## Decisions
1. **Два репозитория, один change.** Спека и план лежат в `jira-hub`; `jira-agent` ссылается на них из CLAUDE.md.
Агент меняется редко и живёт на другой машине, поэтому отдельный репозиторий без зависимостей от хаба.
2. **Хаб — один процесс: SQLite + HTTP + MCP.** Один писатель в базу, нет зависимости от Docker, база — один файл.
Агент и интерфейсы ходят только через HTTP; виртуалке ничего не ставить, у неё нет входящих портов.
3. **Команды — желаемый результат** (`ensure_*`), каждая на одной задаче, выполняется как «прочитать из Jira → если результат
уже есть, `done` без записи → иначе записать». Повтор безопасен по построению.
4. **Хаб не повторяет команды.** Взятая команда без ответа видна как «нет ответа от агента с …»; повторяет её только сам
агент после перезапуска по своему журналу, и только через проверку. Отменить взятую команду может только человек.
5. **Истина в Jira.** После каждого пакета хаб добавляет `refresh` по затронутым ключам; команда без ответа, результат
которой виден в зеркале, помечается `confirmed_by_state`.
6. **Политика темпа в хабе, пауза на команде.** При выдаче команды хаб ставит «не раньше, чем через N с после предыдущей
операции в Jira» по типу команды и типу предыдущей. Агент помнит только время последнего вызова Jira. Нет следующей
команды — нет паузы. 429/503 — Retry-After в агенте (инструкция сервера, не политика).
7. **Пакет = одно действие пользователя.** Команды пакета — по порядку, пакеты — по порядку постановки, одна команда в полёте.
Упавшая команда не останавливает пакет; команды, ссылающиеся на её результат, — `skipped`.
8. **Временные ключи `new:<n>`** живут в хабе: `ensure_issue` с временным ключом, после `done` хаб подменяет `new:<n>`
в параметрах следующих команд пакета и в `issue_note`. У агента соответствия нет.
9. **Зеркало — JQL из настроек** (`project = AURORA AND labels = AWPCA`); задачи, на которые ведут связи, но вне JQL,
дочитываются по ключу и помечаются `neighbor`.
10. **Служебные заметки отдельно от зеркала** (`issue_note`), чтобы обновление из Jira их не затирало.
11. **Настройки агента приходят от хаба** (id полей, карта переходов, типы связей, политика темпа, версия настроек);
локально у агента — адрес хаба, токен, Jira и учётные данные.
## Risks / Trade-offs
- Чтение перед каждой записью — лишний вызов на команду. Цена мала (пауза перед чтением 2 с), а гарантия идемпотентности
стоит дороже.
- Поиск дубля при `ensure_issue` по точной теме и автору за 7 дней может не найти задачу, если тему успели поправить
в Jira руками. Остаточный риск принят: окно между ответом Jira и записью журнала — миллисекунды.
- Один агент и одна команда в полёте — медленно для сотни операций (как и сейчас: ~4,5 ч на полную актуализацию).
Это цена темпа, не архитектуры.
- Хаб должен быть запущен для MCP; если нет — инструменты возвращают понятную ошибку, не падают.
- SQLite по сети не используется; при переезде хаба на другую машину — переносится файл.
## Decisions made without discussion
| Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|
| 2026-10-11 | Хаб: SQLite в одном процессе с HTTP, без PostgreSQL | один писатель, Docker здесь не всегда поднят, виртуалке ничего не ставить; владелец отдал плумбинг исполнителю | смена хранилища — замена одного модуля хаба, API не меняется |
| 2026-10-11 | HTTP-библиотека хаба — stdlib `http.server` + `ThreadingHTTPServer`, без FastAPI | API маленький, зависимостей ноль, тесты через `urllib`; MCP — пакет `mcp` (FastMCP) через uv | при росте API — переезд на FastAPI внутри того же репозитория |
| 2026-10-11 | Порт хаба 8790, привязка к `0.0.0.0` | свободный порт; виртуалка в LAN с другим адресом, `127.0.0.1` ей недоступен | пересечение портов — поправить настройку |
| 2026-10-11 | `claim` — долгий запрос до 30 с, затем сразу следующий | минимальная задержка выдачи без постоянного трафика | — |
| 2026-10-11 | Поиск дубля в `ensure_issue`: `project = AURORA AND reporter = currentUser() AND created >= -7d` + точное сравнение темы на клиенте | JQL `summary ~` нечёткий; точное сравнение в агенте надёжнее | дубль при смене темы руками в первые 7 дней; хаб показывает такие команды отдельно |
| 2026-10-11 | Исполнитель входит в `ensure_fields` | 11.10 исполнители ставились руками, в просмотрщике это нужная операция; владелец согласился | — |
| 2026-10-11 | Паузы по умолчанию: изменение задачи 120 с, комментарий и переход 30 с, чтение 2 с, создание связи 30 с | значения `pacing` из `config.example.json`, проверенные на Jira банка 10–11.10 | настройки, правятся без кода |
| 2026-10-11 | Резервная копия файла базы перед каждым пакетом в `backups/`, хранить последние 30 | заменяет историю версий зеркала | место на диске; число в настройках |
| 2026-10-11 | Хранение базы и настроек хаба — `~/.local/share/jira-hub/` | вне git, переживает пересоздание worktree | — |
| 2026-10-11 | Регистрация MCP — в `~/.claude.json` для проекта `awp-ca` (stdio, `uv run jira-hub mcp`) | как остальные MCP этого проекта | — |
| 2026-10-11 | Полная синхронизация по расписанию: хаб ставит `refresh` по базовой JQL раз в 60 мин, если агент на связи и очередь пуста | свежее зеркало без участия человека | лишние чтения; интервал в настройках, 0 отключает |
| 2026-10-11 | R-1: Review Focus #5 плана ссылается на Task 7, тест `test_settings_put_rejects_protected` лежит в Task 6 — тест остаётся в Task 6 | опечатка плана; спека о месте теста молчит | нулевая |
| 2026-10-11 | R-2: T3.9 (транзакции на общем соединении между потоками) — не дефект; в `set_note` правится только вводящий в заблуждение комментарий; в Task 6 проверить, что API открывает соединение на запрос | Architecture плана: каждый запрос открывает своё соединение SQLite (WAL, busy_timeout) | если соединение будут делить потоки — ошибки вложенного BEGIN; чинится локом в db.transaction |
| 2026-10-11 | R-3: `commands.check_confirmed(conn, cmd, link_types, reporter=None)` — необязательный фильтр по `reporter` для `ensure_issue`; Task 5 берёт значение из настройки `reporter`, если она задана | спека требует «задача с той же темой от нас», а хаб не знает логин агента | без настройки возможно ложное `confirmed_by_state` чужой задачей с той же темой за 7 дней (только при потерянном отчёте) |
| 2026-10-11 | R-4: временный ключ `new:<n>` должен быть объявлен `ensure_issue` раньше по списку пакета (строже спеки «вне пакета») | иначе зависимая команда с меньшим `seq` блокирует очередь | при реальной потребности в обратном порядке — снять проверку и сортировать по зависимостям |
| 2026-10-11 | R-5: `db.transaction` становится вложенной (если `conn.in_transaction` — блок входит во внешнюю транзакцию без своих BEGIN/COMMIT) | после исправления T3.1/T3.2 `mirror.rename_note`/`set_note` сами открывают транзакцию, а `queue.report_result` по плану вызывает их внутри своей — вложенный `BEGIN IMMEDIATE` падал бы | вложенный блок не откатывается отдельно от внешнего (savepoint не нужен: ошибка откатывает всю внешнюю транзакцию) |
| 2026-10-11 | R-6: сбой резервной копии в `queue.claim` не блокирует выдачу команды: исключение ловится, пишется предупреждение в лог | копия защищает базу хаба и не является условием записи в Jira; иначе команда повисает в `claimed` на 10 минут | при сбое диска команда выполнится без свежей копии; строгий вариант — откат claim |
| 2026-10-11 | R-7 (уточняет R-5): вложенный `db.transaction` использует `SAVEPOINT`; соединение не делится между потоками | внутренний блок, чья ошибка перехвачена вызывающим, не должен фиксировать частичные записи | лишние SAVEPOINT на каждый вложенный вызов — дёшево |