fix(ptica): resume_geo_job больше не возобновляет что попало (#2464) #2946
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2946
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2464-resume-geo-job-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Соседняя ручка фильтрует, эта — нет
cancel_geo_jobстрокой выше:resume_geo_job— без фильтра вовсе:При том что докстринг обещает «Re-enqueue paused/failed job».
Следствия:
queuedи прогнать заново, затирая результат;job_id, лишние запросы к НСПД, у которого WAF.Замер
Все задачи в терминальных статусах. То есть
resumeна любую существующую делал ровно то, чего не должен.Второе: ответ всегда был
resumed: trueНезависимо от того, изменилось ли что-нибудь. Теперь ответ отражает факт:
Задача при этом не ставится в очередь. Без этой части правка была бы половинчатой: guard бы стоял, а вызывающий всё равно думал бы, что возобновил.
cancelledоставлен возобновляемым намеренно —cancelэто ручное действие оператора, и без такой возможности отменённая по ошибке задача не восстанавливалась бы никак. Докстринг приведён в соответствие.Проверка
origin/mainAssertionError: UPDATE без фильтра статуса — возобновляется что угодноdone→ не возобновляем и говорим почемуstatuspausedвозобновляетсяКонтроль пришлось переделать. Первая версия проверяла и
resumed, и новый ключstatus— и падала наorigin/mainсKeyError: 'status', то есть по причине «в ответе нет поля», а не «законный путь сломан». Контроль обязан быть зелёным по обе стороны, иначе он не контроль. Разделил.pytest tests/api/v1: 359 passed, 1 skipped, rc=0Refs #2464
UPDATE шёл БЕЗ фильтра статуса — в отличие от соседнего cancel_geo_job, который фильтрует явно (`AND status IN ('queued','running','paused')`): UPDATE nspd_geo_jobs SET status='queued', error=NULL WHERE job_id=:id Следствия: завершённую задачу можно было перевести обратно в 'queued' и прогнать заново, затирая результат; уже бегущую — поставить в очередь второй раз, получив двух воркеров на один job_id и лишние запросы к НСПД, у которого WAF. Замер на проде 19.08: все 66 задач в терминальных статусах (61 done, 5 cancelled). То есть resume на ЛЮБУЮ существующую делал ровно то, чего не должен. Второе: ручка возвращала resumed=True всегда, независимо от того, изменилось ли что-нибудь. Теперь ответ отражает факт — не подошёл статус, значит resumed=False, текущий статус и причина в ответе, задача НЕ ставится в очередь. 'cancelled' оставлен возобновляемым намеренно: cancel — ручное действие оператора, и без этого отменённая по ошибке задача не восстанавливалась бы никак. Тесты: 4 красных на origin/main, главный — «AssertionError: UPDATE без фильтра статуса — возобновляется что угодно». Контроль пришлось переделать: первая версия проверяла и новый ключ `status`, из-за чего падала на origin/main с KeyError, то есть по причине «в ответе нет поля», а не «законный путь сломан». Разделено: контроль смотрит только resumed и зелёный по обе стороны, новый ключ проверяется отдельным тестом. pytest tests/api/v1: 359 passed, 1 skipped, rc=0Красный
openapi-codegen-checkбыл прав — и указал на настоящий дефектЧек упал не из-за формы ответа (она
dict[str, Any], в схеме это объект без свойств), а из-за моих длинных докстрингов.FastAPI кладёт докстринг в
descriptionсхемы OpenAPI, оттуда он попадает в опубликованный контракт и в сгенерированные типы фронта (frontend/src/lib/api-types.ts). То есть в публичную схему API утекало внутреннее обоснование: «замер на проде 19.08: все 66 задач в терминальных статусах», ссылки на номер эпика.Это плохо независимо от гейта. Перенёс обоснование в комментарии над телом функции; в докстринге осталась одна строка — та, что действительно описывает поведение ручки.
После переноса в схеме осталось одно изменение — однострочное описание
resume_geo_job. Оно верное: старое обещало «paused/failed», новое описывает фактическое поведение.api-types.tsперегенерирован тем же способом, что в CI, и регенерация повторена после правок pre-commit — схема не сдвинулась.Второй коммит: близнец у
cancel_geo_jobРазбирая resume, я приводил
cancel_geo_jobв пример — у него фильтр статуса был всегда. И не заметил, что у него та же вторая половина дефекта: ответ возвращалcancelled=Trueнезависимо от того, задел ли UPDATE хоть одну строку. Несуществующий job_id и уже завершённая задача давали тот же ответ, что настоящая отмена.Правка в этой же ветке, а не отдельным PR: файл один и тот же, параллельные PR на общие файлы в репозитории запрещены.
Тесты: 2 красных на
origin/mainсassert True is False, контроль зелёный по обе стороны.pytest tests/api/v1: 362 passed, 1 skipped, rc=0 — на итоговом дереве.prune -afубиваетcompose pullдеплоя ПТИЦЫ (разные группы concurrency, один докер-демон) #2950