Ревью кодовой базы: 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>
9.1 KiB
9.1 KiB
План: 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 — пользователь: меню «Канал» при выбранных устройствах создаёт задачи в панели «Задачи»; удаление одного файла на «Бэкапах» показывает итог.