Files
ros_control/docs/changes/018-correctness-consistency/plan.md
T
ayurishchevandClaude Opus 5.5 123b5abdfc Ревью кодовой базы и исправления корректности по его итогам
Ревью кодовой базы: docs/reviews/2026-09-27-codebase-review.md.

Корректность и согласованность, пункты 5–7 ревью (docs/changes/018):
- одиночное удаление бэкапа в UI идёт через общий delete_many: пометка
  deleted_at и событие backup.deleted, как у группового удаления и API;
- единая система миграций: ручные ALTER из db._migrate перенесены в
  migrations.run (при user_version < 1, до замены ID);
- групповая смена канала выполняется фоновыми задачами set_channel;
  PUT /api/v1/batch/channel → 202 {"job_ids": [...]} (ломающее изменение
  API), меню «Канал» в UI выводит задачи в панель «Задачи».

Тесты: 22 из 22. Стенд проверен на порту 8001 (8000 занят посторонним
процессом), боевые данные не изменены. Ручная проверка UI пользователем
на момент коммита не подтверждена.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:23:34 +03:00

9.1 KiB
Raw Blame History

План: 018 — корректность и согласованность (по ревью 2026-09-27)

Context

Ревью docs/reviews/2026-09-27-codebase-review.md, раздел «Корректность и согласованность», пункты 5–7:

  • п. 5 — одиночное удаление бэкапа в UI (POST /backups/delete, app/ui/routes.py::backup_delete) вызывает s3.delete_object напрямую. Метаданные (backups.deleted_at) не обновляются, событие backup.deleted не пишется — в отличие от группового удаления и API, которые идут через backups.delete_many.
  • п. 6 — две системы миграций: app/db.py::_migrate (ручные ALTER TABLE devices ADD COLUMN … без версии) и app/migrations.py (PRAGMA user_version). Нужна одна точка с версией схемы.
  • п. 7 — групповая смена канала (PUT /api/v1/batch/channel, POST /ui/batch/channel) выполняется последовательно внутри HTTP-запроса: на большом парке упирается в таймаут, сбой одного устройства прерывает остальные.

Решения пользователя:

  • п. 7 — фоновые задачи: новый тип задачи set_channel; PUT /api/v1/batch/channel меняет контракт на 202 {"job_ids": [...]} (как /batch/backup).
  • Стенд — существующий контейнер ros_control-ros_control-1 (compose-проект в корне репозитория). В томе боевые данные: удалять существующие устройства, группы, бэкапы, задачи и записи журнала нельзя. Для проверок можно создавать новые объекты; созданное проверкой убирается только штатными средствами, существующее не трогается.

Изменения

п. 5 — одиночное удаление через общий сервис (app/ui/routes.py)

  • backup_delete: вместо s3.key_allowed + s3.delete_object вызвать backups.delete_many([key]) (проверка ключа, удаление, sync_rows с событием уже внутри). Недопустимый ключ — прежний ValueError → 400.
  • Ответ — редирект на /backups?deleted=N&failed=M, как у backups_delete_many (страница уже показывает итог по этим параметрам).
  • API DELETE /api/v1/backups уже использует delete_many — не меняется.

п. 6 — единая система миграций (app/db.py, app/migrations.py)

  • Удалить db._migrate и его вызов из init_db.
  • В migrations.py добавить _add_legacy_columns(con): те же три ALTER (use_tls BOOLEAN NOT NULL DEFAULT 1, group_id VARCHAR(40), note TEXT), идемпотентно по PRAGMA table_info(devices).
  • Вызов в migrations.run при version < 1 до _is_legacy/_to_v1 — _to_v1 читает use_tls, group_id, note из старой таблицы, поэтому колонки должны появиться раньше. SCHEMA_VERSION остаётся 2 (схема не меняется).
  • Ветка :memory: (тесты) не меняется: create_all создаёт полную схему.
  • Боевая БД уже на user_version = 2 — для неё изменение не выполняет никаких действий.

п. 7 — групповая смена канала через задачи (app/services/jobs.py, app/services/ops.py, app/api/v1.py, app/ui/routes.py, app/ui/templates/dashboard.html)

  • ops.run_set_channel(device_id, channel) -> str: переиспользовать ops.set_channel (установка канала + refresh_status); возвращает сообщение «Канал установлен».
  • jobs.JOB_TYPES["set_channel"] = ops.run_set_channel. jobs.start_jobs(job_type, device_ids, params: dict | None = None): params передаются раннеру как именованные аргументы (await JOB_TYPES[t](device_id, **params)) и пишутся в data события job.created. Колонку в jobs не добавляем: незавершённые задачи после перезапуска всё равно помечаются проваленными (fail_stale_jobs), повторно параметры не нужны.
  • Проверка канала — до создания задач: channel not in CHANNELS → ValueError (400); в API и так Literal[CHANNELS].
  • API: PUT /api/v1/batch/channel → status_code=202, ответ {"job_ids": jobs.start_jobs("set_channel", body.resolve(), {"channel": body.channel})}. PUT /api/v1/devices/{id}/update/channel (одно устройство) — без изменений, синхронный.
  • UI: POST /ui/batch/channel — создаёт задачи и возвращает _jobs.html, как остальные групповые действия; в dashboard.html у кнопок меню «Канал» hx-target="#jobs", hx-include="[name=device_ids]:checked" (как у «Обновление»). Смена канала у одного устройства из меню строки (/ui/devices/{id}/channel) — без изменений.
  • Таблица устройств обновится автоопросом (/ui/devices?poll=1) после завершения задач — refresh_status уже записывает статус.
  • Отображение типа задачи set_channel в панели «Задачи» и журнале — проверить подписи типов (если есть словарь подписей — добавить «Смена канала»).

Тесты (минимально)

  • В существующий тест группы/устройств или новый короткий: PUT /api/v1/batch/channel → 202 и job_ids по числу устройств; раннер подменён (monkeypatch на ops.set_channel), задача завершается done, в job.created есть {"channel": …}.
  • test_migration_replaces_numeric_ids: убедиться, что тест по-прежнему покрывает старую схему без колонок use_tls/group_id/note (если его фикстура создаёт их сама — добавить вариант без них).
  • test_bulk_delete_backups: одна проверка POST /backups/delete → редирект с deleted=1, вызов идёт через delete_many.

Документация

  • README: таблица API (PUT /api/v1/batch/channel → 202 {"job_ids"}), типы задач, строка 018 в «История изменений»; число тестов в разделе «Разработка».
  • summary.md — оркестратор после проверки.

Исполнение

  • Код, тесты, пересборка стенда — исполнитель (Sonnet). Тесты не запускает, не коммитит, summary.md не создаёт.
  • Оркестратор: ревью диффа, полный прогон тестов, проверки на стенде на новых тестовых объектах без удаления существующих данных.
  • Пользователь: ручная проверка UI.

Проверка

  • ./venv/bin/python -m pytest -q — все зелёные.
  • Стенд: docker compose up -d --build --force-recreate, контейнер ros_control-ros_control-1 запущен, docker exec … grep -c set_channel /srv/app/services/jobs.py ≥ 1, PRAGMA user_version = 2, число строк в таблицах до и после совпадает.
  • Сценарии на стенде (новое тестовое устройство с недоступным адресом, например 192.0.2.1 из TEST-NET): PUT /api/v1/batch/channel → 202, задача set_channel завершается failed с ошибкой соединения, остальные задачи не затронуты; неверный канал → 422; тестовое устройство затем удаляется через API (только оно).
  • Миграция старой схемы — тестом на временной БД (боевую не трогаем).
  • Ручная проверка UI — пользователь: меню «Канал» при выбранных устройствах создаёт задачи в панели «Задачи»; удаление одного файла на «Бэкапах» показывает итог.