fix(health): HEAD на /health в обоих бэкендах — аптайм проверял то, что всегда отвечает 405 #2893
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#2893
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-uptime-honest-green"
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?
Проблема
Из аудита 15.08. За 72 часа аптайм показал 4292 успешные проверки из 4292 — при том, что снаружи
HEAD https://gendsgn.ru/healthотдавал 405, аHEAD /— 401.То есть зелёный был получен не потому, что сервис жив, а потому, что проверка ничего не проверяла.
Что сделано
Добавлен обработчик
HEADна health-эндпоинт. Ответ 200 без тела, с тем жеContent-Type: application/json, что и уGET— RFC 9110 требует у HEAD те же заголовки, что у GET.Что поймало ревью
Фикс изначально лёг не в тот бэкенд. Наблюдаемый симптом —
gendsgn.ru/health— обслуживает Site Finder:Caddyfile:60роутит/healthнаbackend:8000, а бэкенд «Меры» получает трафик только через/trade-in/api/*, и голый/healthтуда не попадает никогда. Правка же была сделана в «Мере».Во втором круге маршрутизация проверена по
Caddyfileвmain, и обработчик добавлен в оба бэкенда: в Site Finder — чтобы починить наблюдаемые 405, в «Мере» — чтобы будущий монитор её собственного health не наступил на те же грабли.Заодно эмпирически подтверждено расхождение заголовков:
GETотдавалcontent-type: application/json,HEAD— вообще ничего. Исправлено.Вердикт второго круга — MINOR.
Чего этот PR НЕ чинит
Корень ложного зелёного лежит внутри GlitchTip, а это чужой образ: для типа монитора PING код выставляет
is_up=Trueдо чтения ответа, а у всех пяти мониторовexpected_status=None, то есть сравнивать не с чем. Патчить чужой сервис здесь не будем.Лечится настройкой: перевести мониторы на GET и обязательно проставить
expected_status=200. SQL подготовлен, но до выполнения требует правки — ревьюер справедливо указал, что целевые URL в нём заданы неверно: публичная страница «Меры» живёт наmeraocenka.ru, а не на пути внутриgendsgn.ru. Плюс схему таблицы мониторов подтвердить не удалось (доступа к базе GlitchTip из рабочей сессии нет).Поэтому SQL идёт отдельным шагом, вместе с настройкой получателей алертов — там же будут заведены мониторы «Меры», которых сейчас нет ни одного.
Test plan
HEAD /healthвозвращает 200 в обоих бэкендах, заголовки совпадают сGETcurl -I https://gendsgn.ru/health→ 200 вместо 405@app.get("/health") в FastAPI/Starlette не добавляет HEAD-обработчик автоматически (в отличие от низкоуровневого Route(methods=["GET"])) — внешний uptime-monитор (GlitchTip PING-тип шлёт HEAD) получал 405 и не мог отличить "жив" от "мёртв" по статусу. Добавлен явный @app.head("/health") — 200 без тела (RFC 9110 §9.3.2), GET не тронут. Тест test_health_endpoint.py фиксирует оба метода; RED до фикса (HEAD → 405), GREEN после (проверено git stash + повторный прогон).Review-разбор ветки fix/tradein-uptime-honest-green: 1. [HIGH] Прод-симптом `HEAD gendsgn.ru/health -> 405` обслуживает Site Finder (Caddyfile:60 `handle /health { reverse_proxy backend:8000 }`), а предыдущий коммит правил только tradein-mvp/backend, чей /health наружу не проксируется вообще. Добавлен @app.head("/health") в backend/app/main.py рядом с существующим @app.get — эмпирически подтверждено (uv run pytest): HEAD было 405, стало 200. tradein-mvp фикс не откачен (безвреден, годится для будущего internal-caller), но обвязан комментарием, что реальный прод-путь чинится не там. 2. [LOW] Response(status_code=200) без media_type отдавал HEAD без Content-Type, тогда как GET отдаёт application/json — расходится с заявленным в комментарии RFC 9110 §9.3.2. Добавлен media_type в обоих бэкендах; Content-Length сознательно не подгоняем под байты GET-ответа (payload header field, RFC разрешает опускать для HEAD) — не дублируем сборку payload ради байт-в-байт соответствия. Тесты: test_health_head_ok_no_body добавлен в backend/tests/test_health.py (Site Finder) — RED-check (git stash app/main.py) воспроизводит прод-баг 1:1: assert 405 == 200. tradein-mvp/backend/tests/test_health_endpoint.py дополнен проверкой Content-Type. uv run pytest — все зелёные.