Пакет D хаба: публикация агента, статус, настройки, тесты без утечек (#32) #36

Merged
aep merged 12 commits from feat/32-hub-hygiene into main 2026-10-15 10:04:25 +03:00
Owner

Пакет D хаба: follow-ups #25, #16, #4 одним PR. Change openspec/changes/hub-hygiene-d/.

Closes #32

Что сделано

  • Публикация агента (#25 M-1, M-3, M-5, M-6, M-7). _write_atomic делает fsync и удаляет .tmp при сбое. Перед записью прежний agent.py переименовывается в agent.py.prev; если запись не удалась, он возвращается на место, а cli выводит «не удалось записать публикацию: …» с кодом 1 без трейсбека. Манифест проверяется строго: version — целое, не bool, ≥ 1; sha256 — 64 hex-символа. Предупреждение о битом манифесте выводится один раз на каждый новый текст ошибки. agent.py не в UTF-8 → 404 no_release.
  • Статус агента (#25 M-2). Новое поле agent_reported в GET /agent. До первого claim после перезапуска хаба MCP пишет «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска хаба)», а подсказки «без run.py» нет.
  • PUT /settings (#16 O-11) отвечает всеми настройками, как GET /settings.
  • Очередь (#16 O-10). Если создание задачи отменено, зависимая команда пропускается с текстом «создание задачи new:N отменено».
  • Подтверждение по состоянию (#16 minor-1). Строковые поля ensure_fields сравниваются без различия \r\n и \n.
  • Реле (#16 O-9) сравнивает папки по resolve().
  • _type_error (#16 T3.1) переписан отдельными ветками по типу значения.
  • Тесты (#16 T3.2, T5.2, M-8, minor-3; #4 M-10): bool и [] в параметрах, журнал при пределе выдач, 501 на DELETE/PATCH, эпик без ключа, «подготовлена заново», предупреждение «обновите агента» и офлайн-агент, эффект tick.
  • ResourceWarning (#4 M-9/O-4). Все 88 утечек соединений SQLite в тестах закрыты. Утечка теперь ломает набор: в filterwarnings стоят error::ResourceWarning и error::pytest.PytestUnraisableExceptionWarning.

Проверки

  • uv run pytest: 318 passed, 0 warnings (после слияния main с #29; до слияния — 309).
  • openspec validate hub-hygiene-d --strict: valid.
  • Ревью: каждую задачу проверял ревьюер на sonnet, всю ветку — финальный ревьюер на opus.

Rulings I made

  • R-6 (design #14): в filterwarnings два фильтра. Утечка соединения рождается в __del__, поэтому одного ResourceWarning pytest не ловит.
  • R-7 (design #14): соединения закрывают фикстуры и хелперы (open_db в conftest). Autouse-подмену db.connect не использовали: она спрятала ��ы будущие утечки в src/.
  • R-8 (design #2): если упала самая первая публикация, новый agent.py удаляется и release/ остаётся без публикации.
  • R-9 (design #2): agent.py.prev удаляется после любой успешной публикации.
  • R-10 (design #2): secure_dir вызывается внутри try.
  • R-11 (design #7): у release.hint(..., reported=True) есть значение по умолчанию.
  • R-12 (design #5): переменная _warned защищена threading.Lock.
  • R-13 (design #14): первый абзац CLAUDE.md не меняли, чтобы не конфликтовать с #29. После слияния #29 этот абзац общий, при архивации переписывать его не нужно.
  • R-14 (merge): main с #29 влит в ветку до PR. Конфликт в pyproject.toml разрешён, оставлены обе секции: filterwarnings и [tool.uv].
  • R-15 (design #2): T2-I1 исправлен в задаче 2. Новый agent.py удаляется только при первой публикации, прежний не теряется.
  • R-16 (план): дешёвые minor-замечания исправлены одной волной перед PR.

Находки

Маркер Суть Статус
T1.1 test_cli: close() в теле теста, а не в фикстуре fixed 7d3f648
T1.2 test_settings: то же fixed 7d3f648
T2-I1 откат удалял прежний agent.py при сбое до переименования в .prev fixed 5793438
T2.1 окно 404 при параллельном GET /agent/release во время публикации deferred
T2.2 после падения процесса (agent.py нет, .prev есть) неудачная следующая публикация не возвращает .prev deferred
T2.3 тест сбоя возврата не проверял итоговое состояние fixed 7d3f648
T2.4 нет fsync каталога после os.replace deferred
T2.5 = M-4 README: нет пустой строки перед абзацем fixed bc377ed
T3.1 = M-1 гонка reported/launcher_version в note_agent_update fixed 7d3f648
T3.2 тест правит agent_status прямым UPDATE rejected: так сделано и в других тестах API, состояние «до перезапуска» через API не воспроизвести
T4.1 CRLF-тест не проверял расхождение в summary fixed 7d3f648
T4.2 _norm_text через value or None схлопывает falsy-значения rejected: та же семантика, что у прежнего (x or None); для строковых полей безвредно
T5.1 тест импорта не проверяет new:* напрямую rejected: покрыто SELECT по issue_note
I-1 конфликт pyproject.toml с main после #29 fixed 0cf71e7 (R-14)
M-2 design.md: неточная фраза про влияние .prev на load fixed bc377ed
M-3 README: неточное «64 hex-символа» fixed bc377ed
M-5 «до 30 с» → «до первого claim агента» fixed bc377ed
M-6 если возврат .prev не удался, следующая сбойная публикация перезапишет .prev новым содержимым deferred
O-1 исполнитель задачи 4 создал пустой файл в основном checkout и сам его удалил rejected: последствий нет, main чист
O-2 системная подсказка о строке Co-Authored-By расходилась с инструкцией; в комми��ах строка из инструкции rejected: следовали инструкции
O-3 ruff не запускался rejected: ruff не входит в проверки проекта (make check — pytest и lint фронта; фронт не менялся)

Deferred (уйдут в follow-up при закрытии)

  • T2.1: окно 404 / несовпадения sha у параллельного GET /agent/release во время публикации. Агент повторит запрос через 5 минут; на main окно то же.
  • T2.2: если в начале publish есть .prev, а agent.py нет, стоит восстанавливать .prev.
  • T2.4: fsync каталога после os.replace.
  • M-6: сбой поверх сбоя перезаписывает .prev.

🤖 Generated with Claude Code

Пакет D хаба: follow-ups #25, #16, #4 одним PR. Change `openspec/changes/hub-hygiene-d/`. Closes #32 ## Что сделано - **Публикация агента** (#25 M-1, M-3, M-5, M-6, M-7). `_write_atomic` делает `fsync` и удаляет `.tmp` при сбое. Перед записью прежний `agent.py` переименовывается в `agent.py.prev`; если запись не удалась, он возвращается на место, а `cli` выводит «не удалось записать публикацию: …» с кодом 1 без трейсбека. Манифест проверяется строго: `version` — целое, не bool, ≥ 1; `sha256` — 64 hex-символа. Предупреждение о битом манифесте выводится один раз на каждый новый текст ошибки. `agent.py` не в UTF-8 → 404 `no_release`. - **Статус агента** (#25 M-2). Новое поле `agent_reported` в `GET /agent`. До первого `claim` после перезапуска хаба MCP пишет «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска хаба)», а подсказки «без run.py» нет. - **`PUT /settings`** (#16 O-11) отвечает всеми настройками, как `GET /settings`. - **Очередь** (#16 O-10). Если создание задачи отменено, зависимая команда пропускается с текстом «создание задачи new:N отменено». - **Подтверждение по состоянию** (#16 minor-1). Строковые поля `ensure_fields` сравниваются без различия `\r\n` и `\n`. - **Реле** (#16 O-9) сравнивает папки по `resolve()`. - **`_type_error`** (#16 T3.1) переписан отдельными ветками по типу значения. - **Тесты** (#16 T3.2, T5.2, M-8, minor-3; #4 M-10): bool и `[]` в параметрах, журнал при пределе выдач, 501 на DELETE/PATCH, эпик без ключа, «подготовлена заново», предупреждение «обновите агента» и офлайн-агент, эффект `tick`. - **ResourceWarning** (#4 M-9/O-4). Все 88 утечек соединений SQLite в тестах закрыты. Утечка теперь ломает набор: в `filterwarnings` стоят `error::ResourceWarning` и `error::pytest.PytestUnraisableExceptionWarning`. ## Проверки - `uv run pytest`: **318 passed, 0 warnings** (после слияния main с #29; до слияния — 309). - `openspec validate hub-hygiene-d --strict`: valid. - Ревью: каждую задачу проверял ревьюер на sonnet, всю ветку — финальный ревьюер на opus. ## Rulings I made - R-6 (design #14): в `filterwarnings` два фильтра. Утечка соединения рождается в `__del__`, поэтому одного `ResourceWarning` pytest не ловит. - R-7 (design #14): соединения закрывают фикстуры и хелперы (`open_db` в conftest). Autouse-подмену `db.connect` не использовали: она спрятала ��ы будущие утечки в `src/`. - R-8 (design #2): если упала самая первая публикация, новый `agent.py` удаляется и `release/` остаётся без публикации. - R-9 (design #2): `agent.py.prev` удаляется после любой успешной публикации. - R-10 (design #2): `secure_dir` вызывается внутри `try`. - R-11 (design #7): у `release.hint(..., reported=True)` есть значение по умолчанию. - R-12 (design #5): переменная `_warned` защищена `threading.Lock`. - R-13 (design #14): первый абзац `CLAUDE.md` не меняли, чтобы не конфликтовать с #29. После слияния #29 этот абзац общий, при архивации переписывать его не нужно. - R-14 (merge): main с #29 влит в ветку до PR. Конфликт в `pyproject.toml` разрешён, оставлены обе секции: `filterwarnings` и `[tool.uv]`. - R-15 (design #2): T2-I1 исправлен в задаче 2. Новый `agent.py` удаляется только при первой публикации, прежний не теряется. - R-16 (план): дешёвые minor-замечания исправлены одной волной перед PR. ## Находки | Маркер | Суть | Статус | |---|---|---| | T1.1 | test_cli: `close()` в теле теста, а не в фикстуре | fixed 7d3f648 | | T1.2 | test_settings: то же | fixed 7d3f648 | | T2-I1 | откат удалял прежний `agent.py` при сбое до переименования в `.prev` | fixed 5793438 | | T2.1 | окно 404 при параллельном `GET /agent/release` во время публикации | deferred | | T2.2 | после падения процесса (`agent.py` нет, `.prev` есть) неудачная следующая публикация не возвращает `.prev` | deferred | | T2.3 | тест сбоя возврата не проверял итоговое состояние | fixed 7d3f648 | | T2.4 | нет `fsync` каталога после `os.replace` | deferred | | T2.5 = M-4 | README: нет пустой строки перед абзацем | fixed bc377ed | | T3.1 = M-1 | гонка `reported`/`launcher_version` в `note_agent_update` | fixed 7d3f648 | | T3.2 | тест правит `agent_status` прямым UPDATE | rejected: так сделано и в других тестах API, состояние «до перезапуска» через API не воспроизвести | | T4.1 | CRLF-тест не проверял расхождение в `summary` | fixed 7d3f648 | | T4.2 | `_norm_text` через `value or None` схлопывает falsy-значения | rejected: та же семантика, что у прежнего `(x or None)`; для строковых полей безвредно | | T5.1 | тест импорта не проверяет `new:*` напрямую | rejected: покрыто SELECT по `issue_note` | | I-1 | конфликт `pyproject.toml` с main после #29 | fixed 0cf71e7 (R-14) | | M-2 | design.md: неточная фраза про влияние `.prev` на `load` | fixed bc377ed | | M-3 | README: неточное «64 hex-символа» | fixed bc377ed | | M-5 | «до 30 с» → «до первого claim агента» | fixed bc377ed | | M-6 | если возврат `.prev` не удался, следующая сбойная публикация перезапишет `.prev` новым содержимым | deferred | | O-1 | исполнитель задачи 4 создал пустой файл в основном checkout и сам его удалил | rejected: последствий нет, main чист | | O-2 | системная подсказка о строке Co-Authored-By расходилась с инструкцией; в комми��ах строка из инструкции | rejected: следовали инструкции | | O-3 | ruff не запускался | rejected: ruff не входит в проверки проекта (`make check` — pytest и lint фронта; фронт не менялся) | ## Deferred (уйдут в follow-up при закрытии) - T2.1: окно 404 / несовпадения sha у параллельного `GET /agent/release` во время публикации. Агент повторит запрос через 5 минут; на main окно то же. - T2.2: если в начале `publish` есть `.prev`, а `agent.py` нет, стоит восстанавливать `.prev`. - T2.4: `fsync` каталога после `os.replace`. - M-6: сбой поверх сбоя перезаписывает `.prev`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 12 commits 2026-10-15 10:02:50 +03:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Фикстура open_db в tests/conftest.py, фикстуры conn вместо хелперов fresh()/_mirror(); filterwarnings: error::ResourceWarning и error::pytest.PytestUnraisableExceptionWarning (решение 14, R-5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Сбой записи — «не удалось записать публикацию: …», код 1, прежняя версия остаётся; манифест годен при целой version ≥ 1 и sha256 из 64 hex; предупреждение о негодном — одно на ошибку; не-UTF-8 agent.py — 404 (решения 1–6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Откат удаляет agent.py только при первой публикации; при сбое до переименования прежний файл не трогается. Тест: os.replace падает на agent.py.prev — release/ байт-в-байт прежний.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /agent отдаёт agent_reported; release.hint(reported=); MCP agent_status: «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска хаба)» (решения 7–8, R-1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PUT /settings отвечает всеми настройками (R-3); пропуск при cancelled-создателе — «создание задачи new:N отменено» (R-4); ensure_fields сравнивает строки без различия CRLF/LF; _cleaned по resolve(); _type_error по веткам (решения 9–12, 15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Решение 13: тесты фиксируют уже верное поведение; каждый проверен мутацией.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Конфликт pyproject.toml: оставлены обе секции — filterwarnings (hub-hygiene-d, решение 14)
и [tool.uv] python-preference (#29). Рулинги R-14 (переписан), R-15, R-16 в design.md.
318 passed, 0 warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep merged commit cbc86becca into main 2026-10-15 10:04:25 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: aep/sdlc-hub#36
No description provided.