Бэкенд хаба: нормализация параметров, 422 вместо 500, файлы, публикация агента, уборка настроек (#129) #130

Merged
aep merged 12 commits from feat/129-hub-backend into main 2026-10-26 09:19:52 +03:00
Owner

Closes #129.

Пакет C из сверки follow-ups 25.10: дефекты бэкенда хаба. Только Python-код хаба. Агенты, веб и схема базы не меняются. Спека — openspec/changes/hub-backend-fixes/, план — docs/superpowers/plans/2026-10-26-hub-backend-fixes.md.

Что исправлено

  • Параметры команд (#111 T-F1, #49 M-4):
    • при постановке пакета идентификаторы и элементы списков обрезаются по краям, текст (summary, description, body, goal) хранится как задан;
    • ensure_rank {before: " A-2 "} больше не уходит агенту с пробелами;
    • временный ключ " new:1 " подставляется так же, как new:1;
    • ключ из одних пробелов даёт «нет key»;
    • поле ensure_fields из одних пробелов даёт 422 и не очищает поле в Jira;
    • filename приводится к NFC, поиск вложения по имени в MCP сравнивает имена в NFC.
  • Цифровые id (#86 F.1): attachment_id, id вложений в состоянии агента и версия агента — только 0-9. "²" больше не роняет хаб с 500, в том числе в GET /agent и /agents.
  • 422 вместо 500 (#42 T2.1, #94 T8.1):
    • в POST /agent/state поле issues должно быть списком объектов со строковым key;
    • в POST /wiki/sync поле project должно быть строкой.
  • Файлы хранилища (#49 M-3):
    • нечитаемый файл или каталог на месте файла считается отсутствующим: при claim size = null, при скачивании 404, предупреждение в лог;
    • повторная загрузка перезаписывает нечитаемую копию.
  • Пустой SDLC_HUB_HOME (#102 T3.2) трактуется как незаданный: база больше не пишется в рабочий каталог.
  • Публикация агента (#39 T2.2, M-6):
    • перед публикацией прерванная прошлая восстанавливается по sha256 манифеста;
    • годный agent.py.prev не затирается.
  • Настройки (#64 «выкатка»): ключи v3 base_jql и board_id удаляются из setting при открытии хаба. Резервная копия базы делается штатно.

Проверки

  • make check: pytest 1187 passed, lint и tsc без замечаний, vitest 73 файла и 759 тестов. Перепроверено сессией на e20977e и 1cb56be.
  • По каждой задаче RED подтверждён до правки.
  • Ревью: каждая задача (sonnet), вся ветка (opus, три Important — исправлены) и повторное ревью волны исправлений.

Rulings I made

  • R-1 (proposal): O-3 из #4 (check_confirmed и симметричные типы связей) перенесён в пакет B. Как��е типы симметричны, знает только агент. Цена ошибки: при потерянном отчёте лишняя довыдача команды, агент ответит existed.
  • R-2 (design #1): обрезаются все строковые параметры, кроме summary, description, body, goal. Цена ошибки: тема с пробелами по краям уходит в Jira как есть.
  • R-3 (design #2): имя вложения приводится к NFC. Цена ошибки: имя в Jira будет в NFC, визуально то же.
  • R-4 (design #7): нечитаемый файл хранилища — «файла нет», причина только в логе хаба.
  • R-5 (design #9): _recover чинит только состояния, однозначные по sha256 манифеста. В редком сочетании сбоев нужно опубликовать повторно.
  • R-6 (design #10): base_jql/board_id удаляются через механизм устаревших ключей, без новой версии схемы.
  • R-7 (design #7): files.size для каталога на месте файла возвращает None (S_ISREG).
  • R-8 (design #1, I-1): необязательное поле ensure_fields из одних пробелов — 422 «из одних пробелов; для очистки — "" или null». Без этого обрезка давала бы "", а это «очистить поле в Jira». Для before/after/move_open_to пробелы по-прежнему значат «не задано». Цена ошибки: такой ввод отвергает хаб, а не Jira.
  • R-9 (design #7, I-3): files.size берёт размер через fstat открытого файла (Path.stat() не требует права на чтение); _intact ловит OSError, поэтому put перезаписывает нечитаемую копию. Цена ошибки: лишнее открытие файла при claim.
  • R-10 (design #10): на базе v7 с ключами v3 миграция удаляет и base_jql/board_id и поднимает settings_version. Значение остаётся в предупреждении migrate_v3 и в backups/schema-v3.

R-1…R-6 приняты на этапе спеки, R-7…R-10 — контроллером при исполнении. Все записаны в design.md, раздел «Decisions made without discussion». R-8 дописан и в спеку commands.

Отложено (deferred)

Маркер Суть
T1.1 hooks.py:36 — недостижимая проверка «filename пустой»
T1.2 нет теста retry с необрезанными params
T1.3 MCP batch_create: нет теста обрезки key
T1.4 дубль проверки обязательных параметров в validate
T2.2 ссылка #<id> вложения в MCP без теста
T2.3 разбор дат (history.py, mcp.py) без теста
T4.4 предупреждение о нечитаемом файле на каждый claim, без троттлинга
T5.1 нет теста на битый manifest.json в _recover
T5.3 часть регрессионных тестов публикации зелёная и без правки
T5.4 .tmp от оборванной _write_atomic не убираются
T5.5 предупреждение на каждую публикацию при неопределённом состоянии
M-1 нет теста подстановки настоящего ключа вместо временного с пробелами
M-4 тест SDLC_HUB_HOME="" не проверяет home_problem/home_warning
O-4 флейк tests/test_mcp.py::test_unbound_session (один раз в полном прогоне)
O-6 маршруты вики с \d принимают цифры Unicode ("١٢" → id 12)
O-7 нет проверки формы links и neighbor_keys в POST /agent/state
O-8 files.put поверх каталога на месте файла — 500
O-9 тема ensure_fields с пробелами по краям не подтверждается по зеркалу (цена R-2)

После merge: перезапуск хаба (kickstart) — за владельцем. При первом старте из setting уйдут base_jql и board_id.

🤖 Generated with Claude Code

Closes #129. Пакет C из сверки follow-ups 25.10: дефекты бэкенда хаба. Только Python-код хаба. Агенты, веб и схема базы не меняются. Спека — `openspec/changes/hub-backend-fixes/`, план — `docs/superpowers/plans/2026-10-26-hub-backend-fixes.md`. ## Что исправлено - **Параметры команд** (#111 T-F1, #49 M-4): - при постановке пакета идентификаторы и элементы списков обрезаются по краям, текст (`summary`, `description`, `body`, `goal`) хранится как задан; - `ensure_rank {before: " A-2 "}` больше не уходит агенту с пробелами; - временный ключ `" new:1 "` подставляется так же, как `new:1`; - ключ из одних пробелов даёт «нет key»; - поле `ensure_fields` из одних пробелов даёт 422 и не очищает поле в Jira; - `filename` приводится к NFC, поиск вложения по имени в MCP сравнивает имена в NFC. - **Цифровые id** (#86 F.1): `attachment_id`, id вложений в состоянии агента и версия агента — только `0-9`. `"²"` больше не роняет хаб с 500, в том числе в `GET /agent` и `/agents`. - **422 вместо 500** (#42 T2.1, #94 T8.1): - в `POST /agent/state` поле `issues` должно быть списком объектов со строковым `key`; - в `POST /wiki/sync` поле `project` должно быть строкой. - **Файлы хранилища** (#49 M-3): - нечитаемый файл или каталог на месте файла считается отсутствующим: при claim `size = null`, при скачивании 404, предупреждение в лог; - повторная загрузка перезаписывает нечитаемую копию. - **Пустой `SDLC_HUB_HOME`** (#102 T3.2) трактуется как незаданный: база больше не пишется в рабочий каталог. - **Публикация агента** (#39 T2.2, M-6): - перед публикацией прерванная прошлая восстанавливается по sha256 манифеста; - годный `agent.py.prev` не затирается. - **Настройки** (#64 «выкатка»): ключи v3 `base_jql` и `board_id` удаляются из `setting` при открытии хаба. Резервная копия базы делается штатно. ## Проверки - `make check`: pytest 1187 passed, lint и tsc без замечаний, vitest 73 файла и 759 тестов. Перепроверено сессией на e20977e и 1cb56be. - По каждой задаче RED подтверждён до правки. - Ревью: каждая задача (sonnet), вся ветка (opus, три Important — исправлены) и повторное ревью волны исправлений. ## Rulings I made - R-1 (proposal): O-3 из #4 (`check_confirmed` и симметричные типы связей) перенесён в пакет B. Как��е типы симметричны, знает только агент. Цена ошибки: при потерянном отчёте лишняя довыдача команды, агент ответит existed. - R-2 (design #1): обрезаются все строковые параметры, кроме `summary`, `description`, `body`, `goal`. Цена ошибки: тема с пробелами по краям уходит в Jira как есть. - R-3 (design #2): имя вложения приводится к NFC. Цена ошибки: имя в Jira будет в NFC, визуально то же. - R-4 (design #7): нечитаемый файл хранилища — «файла нет», причина только в логе хаба. - R-5 (design #9): `_recover` чинит только состояния, однозначные по sha256 манифеста. В редком сочетании сбоев нужно опубликовать повторно. - R-6 (design #10): `base_jql`/`board_id` удаляются через механизм устаревших ключей, без новой версии схемы. - R-7 (design #7): `files.size` для каталога на месте файла возвращает `None` (`S_ISREG`). - R-8 (design #1, I-1): необязательное поле `ensure_fields` из одних пробелов — 422 «из одних пробелов; для очистки — "" или null». Без этого обрезка давала бы `""`, а это «очистить поле в Jira». Для `before`/`after`/`move_open_to` пробелы по-прежнему значат «не задано». Цена ошибки: такой ввод отвергает хаб, а не Jira. - R-9 (design #7, I-3): `files.size` берёт размер через `fstat` открытого файла (`Path.stat()` не требует права на чтение); `_intact` ловит `OSError`, поэтому `put` перезаписывает нечитаемую копию. Цена ошибки: лишнее открытие файла при claim. - R-10 (design #10): на базе v7 с ключами v3 миграция удаляет и `base_jql`/`board_id` и поднимает `settings_version`. Значение остаётся в предупреждении `migrate_v3` и в `backups/schema-v3`. R-1…R-6 приняты на этапе спеки, R-7…R-10 — контроллером при исполнении. Все записаны в `design.md`, раздел «Decisions made without discussion». R-8 дописан и в спеку `commands`. ## Отложено (deferred) | Маркер | Суть | |---|---| | T1.1 | `hooks.py:36` — недостижимая проверка «filename пустой» | | T1.2 | нет теста retry с необрезанными params | | T1.3 | MCP `batch_create`: нет теста обрезки key | | T1.4 | дубль проверки обязательных параметров в `validate` | | T2.2 | ссылка `#<id>` вложения в MCP без теста | | T2.3 | разбор дат (`history.py`, `mcp.py`) без теста | | T4.4 | предупреждение о нечитаемом файле на каждый claim, без троттлинга | | T5.1 | нет теста на битый `manifest.json` в `_recover` | | T5.3 | часть регрессионных тестов публикации зелёная и без правки | | T5.4 | `.tmp` от оборванной `_write_atomic` не убираются | | T5.5 | предупреждение на каждую публикацию при неопределённом состоянии | | M-1 | нет теста подстановки настоящего ключа вместо временного с пробелами | | M-4 | тест `SDLC_HUB_HOME=""` не проверяет `home_problem`/`home_warning` | | O-4 | флейк `tests/test_mcp.py::test_unbound_session` (один раз в полном прогоне) | | O-6 | маршруты вики с `\d` принимают цифры Unicode (`"١٢"` → id 12) | | O-7 | нет проверки формы `links` и `neighbor_keys` в `POST /agent/state` | | O-8 | `files.put` поверх каталога на месте файла — 500 | | O-9 | тема `ensure_fields` с пробелами по краям не подтверждается по зеркалу (цена R-2) | После merge: перезапуск хаба (kickstart) — за владельцем. При первом старте из `setting` уйдут `base_jql` и `board_id`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 12 commits 2026-10-26 02:27:26 +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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mirror.check_issues проверяет форму issues до записи; /wiki/sync отвергает project не строку.

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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OBSOLETE расширен ключами base_jql и board_id: ensure_defaults убирает их при каждом открытии, миграция v7 -> v8 тоже.
settings_version не растёт. PUT /settings для этих ключей по-прежнему отвечает 422 с пояснением. Тест миграции v3
обновлён: после миграции ключей в настройках нет (проекты уже созданы из них).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
I-1 (R-8): ensure_fields с полем из одних пробелов даёт 422, очистка только "" или null.
I-2: agent_view не падает на нецифровой версии агента (GET /agent, /agents).
I-3 (R-9): files.size через fstat у открытого файла, _intact ловит OSError, put переписывает нечитаемый файл.
M-2: /agent/state отвергает пустые не-списки в issues.
T2.1, T3.1, T4.1-T4.3, T5.2, T6.1, T1.5: тесты и мелкие правки.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep merged commit 6f2cf1af03 into main 2026-10-26 09:19:52 +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#130
No description provided.