7.1 KiB
7.1 KiB
bridge-core (агент) — решения
Спека изменения лежит в репозитории jira-hub: jira-hub/openspec/changes/bridge-core/ (specs/agent, specs/commands, specs/hub-api, design.md). Здесь — только решения, принятые при реализации агента без обсуждения с человеком.
Decisions made without discussion
| # | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| 1 | 2026-10-11 | tests/__init__.py добавляет каталог tests/ в sys.path (R-1) |
Тесты из плана импортируют _common/fake_jira по простому имени, при discover -s tests -t . иначе ImportError |
Мелкая: переписать импорты в тестах на относительные |
| 2 | 2026-10-11 | Потолок на Retry-After не вводим (R-2) |
Спека: «ждать Retry-After»; потолок тихо нарушает требование сервера |
Агент может уснуть надолго при аномальном заголовке; лечится потолком позже |
| 3 | 2026-10-11 | Журнал: не лечим права существующего файла и не блокируем prune/append flock'ом; запрет prune при работающем агенте — в README (R-3) |
Журнал создаётся этим же кодом (0600); prune — ручная команда раз в месяц |
Редкая потеря записи журнала при гонке prune/run; лечится flock |
| 4 | 2026-10-11 | Minor-замечания ревью Task 3 (T3.1, T3.3–T3.6, T3.8: пагинация поиска дубля, флаги existed/created, режим epic_link, префикс маркера, общий except в run, тесты) чиним одной финальной волной, а не по месту (R-4) | Не блокируют Task 4–5; одна волна дешевле | Task 4–5 унаследуют общий run; ошибки будут чиниться после |
| 5 | 2026-10-11 | Неизвестный link_type берётся как сырое имя типа Jira (fallback), без failed (R-5) |
Так же делает хаб в check_confirmed; допускает ручной ввод имён Jira |
Опечатка в типе даст HTTP-ошибку Jira вместо понятной ошибки агента |
| 6 | 2026-10-11 | Спринт в refresh берётся из Agile API по активным/будущим спринтам доски, а не из поля search; закрытые спринты дают sprint = null (R-6) |
В настройках хаба нет id поля спринта (field_ids его не содержит); план Task 5 выбрал Agile |
Исторические имена спринтов затрутся при полном refresh; лечится полем спринта в настройках |
| 7 | 2026-10-11 | Бисекция чанка key in (...) при 400 принята без лимита вызовов (до ~2·m·log2(n)); если не найден ни один ключ — ошибка, а не missing (R-7) |
Иначе refresh с удалённой задачей падает целиком; лимит вызовов усложняет без выгоды при редких удалениях | При массовом удалении задач — всплеск запросов и риск 429 (агент их переживает) |
| 8 | 2026-10-11 | Общий except Exception стоит в Runner.execute (не в Executor.run); minor-находки T3.x/T5.x/T6.x чиним одной финальной волной, включая ложный failed для ensure_issue при ConnectionError из Jira (R-8) |
Исключение вне HttpError/ConnectionError не должно ронять цикл и вызывать зацикленное восстановление; одна волна дешевле | Ложный failed у ensure_issue оставит временный ключ неразрешённым, а задача в Jira уже создана |
| 9 | 2026-10-12 | В журнал пишется запись claimed сразу после claim, до паузы; unfinished_started, recover, prune считают её открывающей; started по-прежнему после паузы (R-9) |
Иначе остановка агента в паузе/claim оставляет команду claimed в хабе навсегда (хаб не переиздаёт), а лечение cancel+retry не восстанавливает зависимые команды пакета |
Формат журнала шире, чем в спеке (лишний kind); старые журналы совместимы |
| 10 | 2026-10-12 | Runner.execute повторяет команду до 3 раз через проверку при ConnectionError/HTTP 5xx, не записывая finished; затем failed «результат неизвестен, повтор безопасен» (R-10) |
Неоднозначный исход записи в Jira (обрыв после POST) нельзя отдавать как failed: для ensure_issue хаб пропустит зависимые команды; все команды идемпотентны |
Лишние задержки при недоступной Jira |
| 11 | 2026-10-12 | Запрошенное, но не настроенное поле (epic_key, planned_start/end) даёт failed, а не молчаливый done (R-11) |
Ложный успех хаб запишет в зеркало как выполненный | Команды падают, пока хаб не настроен |
| 12 | 2026-10-12 | agent_version добавляется в отчёт и в /agent/state (R-12) |
Спека: версия «в каждом отчёте»; хаб лишние поля игнорирует | Нет |
| 13 | 2026-10-12 | F-1 (повтор по R-10 не ждёт 30 с, если Jira недоступна с первого вызова и last_call_at старый) сначала отложен контроллером (вторая волна фиксов не предусмотрена), затем исправлен по команде владельца: ожидание перед повтором — фиксированное max(pause, 30) от момента отказа (Runner.wait_seconds), не от последнего успешного вызова Jira |
Обрыв соединения не обновляет last_call_at, поэтому пауза «от последнего вызова» при старом значении была нулевой и три повтора шли подряд |
Повтор ждёт полные 30 с даже если с последнего вызова Jira прошло больше; лишнее ожидание не дольше 30 с на попытку |