3651 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| d74121d74c | Merge pull request 'Отказ в alert-ack перестаёт съедать следующий алерт' (#3490) from fix/3471-alert-ack-drain-body into main | |||
| 694bf13d3d | Merge pull request 'Очередь задач и Redis становятся видимыми' (#3489) from feat/3471-celery-redis-metrics into main | |||
|
|
24c2052057 |
style(tg): перенос длинной строки заголовков ретранслятора
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m58s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY6iWDnGDthdvsMWgK1BMG |
||
| 2edaae148f |
Merge pull request 'Ответ оператора не исчезает при сбое БД, и реплай на собственный ответ снова маршрутизируется' (#3479) from fix/3471-bridge-db-failure-reply-loss into main
Some checks failed
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 14s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 6m48s
Deploy Trade-In / build-backend (push) Successful in 1m13s
Deploy Trade-In / deploy (push) Has been cancelled
|
|||
|
|
4d3e273405 |
fix(ops): alert-ack вычитывает тело запроса до любой ветки отказа
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Соединение переиспользуется (protocol_version = HTTP/1.1), и Caddy перед
сервисом держит пул к апстриму. Ответ 401/404/503 без чтения тела оставлял
его в сокете, и следующий запрос по тому же соединению начинался с чужих
байт.
Поймано на проде: зонд без секрета получил 401, а следующий запрос — уже с
верным секретом — вернул 501 Unsupported method ('{"text":"probe"}POST').
То есть один отказ съедал следующий НАСТОЯЩИЙ алерт, ровно в том канале,
который заводился как резервный.
Тело теперь читается один раз в начале do_POST и передаётся вниз. Четыре
теста поднимают настоящий сокет и шлют пару запросов по одному соединению —
на прежнем коде три из них падают с той же строкой 501.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY6iWDnGDthdvsMWgK1BMG
|
||
| 382f266801 |
Merge pull request 'Алерт GlitchTip переживает недоступность Telegram: отправка уходит в фон, 502 остаётся' (#3485) from feat/3471-glitchtip-alert-queue into main
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 19s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
|
|||
|
|
690f1ef5d2 |
feat(metrics): продуктовые счётчики Prometheus для Меры и Птицы + дашборд
All checks were successful
CI / backend-tests (pull_request) Successful in 17m59s
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m18s
CI Trade-In / backend-tests (pull_request) Successful in 5m59s
Владелец попросил вывести продукт в Графану — до этого там были только
технические панели (запросы/латентность/память). Список счётчиков взят из
реально пишущихся событий, а не выдуман:
Мера (tradein-mvp/backend/app/observability/metrics.py):
- mera_estimates_total{outcome=ok|insufficient_data} — POST /estimate,
зеркалит user_events.event_type=estimate_request (294 строки в БД),
insufficient_data — не ошибка, а исход без аналогов.
- mera_address_suggestions_total{found=yes|no} — GET /geocode/suggest,
своего user_events-события у ручки не было.
- mera_reports_exported_total (без лейблов) — GET /estimate/{id}/pdf.
- mera_leads_total (без лейблов) — POST /trade-in/lead.
- mera_support_messages_total{channel=web|anon} — POST /support/messages
и /support/anon/messages, счётчик после успешной доставки в Telegram.
- mera_logins_total{result=success|failed} — рядом с user_events
login_success/login_failed в auth.py (97/453 строк в БД).
Птица (backend/app/observability/metrics.py):
- sitefinder_reports_exported_total{format} — GET .../forecast/export
(md/json/tg/docx/pptx/pdf) и POST .../best-layouts/pdf.
Метки везде — фиксированный литерал из места вызова (outcome/found/channel/
result/format), никогда username/адрес/estimate_id/кадастровый номер —
это ровно то, что взрывает кардинальность ряда у Prometheus.
Дашборд ops/metrics/grafana/dashboards/product.json ("Продуктовые метрики",
uid gendesign-product) — воронка Меры (оценки/подсказки/лиды/отчёты/входы/
поддержка) + экспорт форматов Птицы, часовые increase()-панели без
стекирования (на соседней панели оно уже давало ложную тревогу, PR #3474).
Provisioning тот же, что у apps.json — сканирует директорию, отдельного
конфига не нужно.
ops/metrics/alloy/alloy-apps.alloy проверен: у job "apps" нет relabel-
фильтра по __name__ (в отличие от cadvisor) — новые счётчики уходят в
remote_write как есть, правки не потребовалось.
Refs #3471
|
||
| 05959464ae |
ci.yml: запускать backend-tests на правках метрик (#3467/#3475)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 14s
CI / changes (pull_request) Successful in 19s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m36s
CI / openapi-codegen-check (pull_request) Successful in 2m48s
CI / backend-tests (pull_request) Successful in 18m27s
Гейт backend/tests/ops/test_3467_prometheus_reload.py (едет в PR #3475) читает .forgejo/workflows/deploy-metrics.yml и docker-compose.metrics.yml. Пока этих путей нет в фильтре `backend`, правка, трогающая ТОЛЬКО deploy-metrics.yml — например дописывающая `|| true` к шагу перезагрузки Prometheus, — даёт backend=false: джоба backend-tests пропускается, гейт не исполняется, регрессия уезжает в main зелёной. Это ровно тот класс, который осуждает комментарий двумя абзацами выше в этом же файле: гейт, который не запускается на той самой правке, от которой стережёт, — украшение. Список правится одной веткой намеренно: параллельный PR #3475 его не трогает, иначе две ветки подрались бы за один фильтр. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
34c15ecf08 |
feat(ops): маршрут ретранслятора Telegram на metrics.gendsgn.ru
Some checks failed
CI Trade-In / changes (pull_request) Successful in 15s
CI / changes (pull_request) Successful in 20s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 1m39s
Без маршрута сервис tg-relay недостижим снаружи и настройка TELEGRAM_RELAY_BASE_URL на продуктовом хосте ни к чему не приводит. Путь несёт токен бота, поэтому помечен log_skip: иначе он осядет в файловом логе сайта и в stdout-копии, которую читает Alloy (#3154). Таймаут ответа поднят до 80с — getUpdates висит long-poll'ом до ~40с, дефолтного не хватает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY6iWDnGDthdvsMWgK1BMG |
||
|
|
9a93e575cc | Merge remote-tracking branch 'forgejo/main' into feat/3471-telegram-relay-beget | ||
| c826793a2a |
Merge pull request 'Меньше шума: выключенные платежи не засоряют ленту ошибок, провал прокси-пробы пишется строкой вместо трейса' (#3483) from fix/3471-observability-noise into main
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 15s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
|
|||
| 127c9a5c2a |
Merge pull request 'Деплой метрик перечитывает конфиг Prometheus, а не только кладёт его на диск' (#3476) from fix/3467-metrics-deploy-reload into main
All checks were successful
Deploy / changes (push) Successful in 11s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Metrics / server (push) Successful in 32s
Deploy / build-backend (push) Successful in 51s
Deploy / build-worker (push) Successful in 58s
Deploy Metrics / agent-infra (push) Successful in 29s
Deploy Metrics / agent-apps (push) Successful in 31s
Deploy / deploy (push) Successful in 1m18s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 1m46s
|
|||
|
|
62a560387c |
feat(ops): измеряем очередь Celery и Redis, до сих пор слепая зона
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 15s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Prometheus не видел ни одной серии celery_*/redis_* — переполнение очереди Site Finder и залипший воркер снаружи выглядели одинаково, тишиной (issue #3471). Добавлено (только на продуктовом хосте, профиль apps): - redis-exporter (oliver006/redis_exporter) — здоровье общего Redis (db0 celery-брокер Site Finder, db1 SearchCache trade-in, db2 glitchtip), адрес через alias gendesign-redis на сети shared, без нового сетевого доступа. - celery-exporter (danihodovic/celery-exporter) — глубина очереди, число живых воркеров, счётчик неуспешных задач. Выбран вместо redis-exporter --check-keys, потому что дефолтная очередь "celery" дала бы только глубину, но не воркеров и не failures. - Скрейп обоих в alloy-apps.alloy. - Алерты в infra.yml: RedisDown, NoActiveCeleryWorkers, CeleryQueueGrowing (порог 150 предварительный — реальных данных по глубине очереди ещё нет, пересмотр через неделю наблюдений). Имена метрик celery-exporter (celery_queue_length, celery_worker_up, celery_task_failed_total) — по документации проекта, без прогона на реальном брокере; сверить после первого деплоя, см. комментарий у сервиса. promtool check rules — 23 правила, SUCCESS. Refs #3471 |
||
|
|
e0564d12fe |
feat(tg): продуктовый Bot API трафик уходит через ретранслятор на Beget
Замер 12.09.2026, оба хоста в одни и те же минуты: getMe из tradein-tgbot на Selectel — 9 успешных из 12, три ConnectTimeout; TCP-443 до адреса, резолвящегося на Selectel (149.154.167.220) — 5 из 6; TCP-443 до адреса, резолвящегося на Beget (149.154.166.110) — 8 из 8. За сутки в логе бота 508 строк network error, за 30 дней 92 обрыва итерации poll loop. Значит: путь до Telegram с Selectel лоссовый, с Beget чистый — Alertmanager (живёт на Beget) шлёт в тот же чат без проблем, а бот поддержки на Selectel часть отправок теряет. Добавлен ops/metrics/tg-relay — stdlib-only HTTP-сервис (тот же принцип, что у alert-ack: без зависимостей, поднимается даже когда всё остальное сломано), проксирует Bot API целиком (метод, путь, тело — sendMessage, copyMessage, getUpdates) на api.telegram.org. Токен из пути не логируется: log_request переопределён полностью, путь редактируется до записи в лог. Аутентификация — общий секрет в X-Relay-Secret, по образцу X-Internal-Auth-Secret из этого же стека. Клиент (tgbot/client.py) при транспортном отказе похода на ретранслятор делает одну попытку напрямую к api.telegram.org — хуже прямого пути быть не должно ни при каких условиях. Пустой TELEGRAM_RELAY_BASE_URL — прежнее поведение без изменений, это и есть механизм отката. Refs #3471 |
||
|
|
ac5b044f7e |
fix(scrapers): ожидаемые исходы сбора (бан, пустой пул, капча) больше не error
Some checks failed
CI Trade-In / changes (pull_request) Successful in 14s
CI / changes (pull_request) Successful in 18s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 6m17s
Скрапер один давал 3006 error-строк в сутки из ~3700 по всему Trade-In — ленту перестали читать, и настоящая поломка терялась в ней. Принцип: ожидаемый исход сбора (площадка забанила, пул прокси пуст, капча/недогруз, серия блоков перевалила порог circuit breaker) — это состояние работы против недружелюбного источника, а не инцидент. В error остаётся только неожиданное: изменившаяся вёрстка/схема (Cian markup change), просроченный токен ротации прокси (ASocks 401), неразобранное исключение. Переведено error -> warning в 9 файлах, 12 мест: "СТОП — пул прокси пуст" (avito/domclick/cian_history/yandex_newbuilding_sweep x2), "пул прокси исчерпан" (cian_session, cian_price_history, yandex_address_backfill), FAIL-CLOSED без здорового узла для source (proxy_egress), ABORT по счётчику подтверждённых блоков площадки (avito, domclick, yandex_detail_backfill). Оставлено error намеренно: cookie-алерты Cian/DomClick (#2658, #2674) — они рассчитаны именно на LoggingIntegration(event_level=ERROR) в scheduler_main.py и без него молчат по 37 дней; ABORT по смешанным/soft причинам без единого подтверждённого блока площадки (#3272, #2674/#3196) — это может быть наш баг, а не бан, сигнал сознательно не приглушали. GlitchTip: сентри-интеграция скрапера уже настроена как LoggingIntegration(level=INFO, event_level=ERROR) в scheduler_main.py — отдельной правки sentry_scrub.py не требуется, понижение уровня logger само убирает эти записи из GlitchTip. Итоговая FINISHED-строка со счётчиками (attempted/enriched/blocked/failed) уже существует в каждом detail_backfill — новую не добавлял. Refs #3471 |
||
|
|
5e80b56bdc |
fix(tg): out-строки писались с support_chat_id=NULL — вечный wildcard-матч
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 16s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 6m17s
Deep review PR #3479 нашёл дефект в предыдущем фиксе (#3471 пункт 3): новые direction='out' строки стали видимы резолверам (find_chat_by_topic_message, find_thread_by_topic_message), но писались без support_chat_id. Резолверы матчат support_chat_id IS NULL как лениентный wildcard "любой текущий чат" (легаси-строки до 187/188) — то есть КАЖДАЯ out-строка становилась таким wildcard. При ротации support-группы новый message_id мог бы случайно совпасть со старой out-строкой: TG-путь увёл бы ответ ЧУЖОМУ клиенту через copyMessage, веб-путь записал бы ответ в чужой тред. Ровно от этого защищали миграции 187/188 (review M1). - bridge.py: TG- и веб-ветка `_handle_group_reply` теперь передают support_chat_id=settings.telegram_support_chat_id в record_message / record_web_out_message (симметрично уже существующей in-ветке). - web_support_storage.record_outbound: добавлен параметр support_chat_id, пишется в INSERT (колонка уже существовала, DDL не нужен). - Тест test_group_reply_to_own_previous_tg_reply_resolves_target_chat сидел предыдущую out-строку с уже заполненным support_chat_id вручную, хотя код писал NULL — маскировал дефект. Добавлены прямые проверки на записанное support_chat_id (TG и веб), обе падают на прежней реализации (проверено локальным откатом изменения — 2 failed, restore — 41 passed). - Комментарий про "апдейт частично применён в Telegram" в except-ветке веб-ответа был неверен для этого случая (на веб-пути ничего не уходит в Telegram до сбоя БД) — переписан на настоящую причину: сбой БД не переигрывается по общей политике process_update, а не из-за частичной доставки. Refs #3471 |
||
| c53eaf7079 |
Merge pull request 'Маршрут и секрет для резервного приёмника алертов GlitchTip' (#3484) from chore/3471-glitchtip-fallback-wiring into main
Some checks failed
Deploy / deploy-status (push) Successful in 1s
Deploy Infra Host / sync-infra-host (push) Successful in 10s
Deploy Metrics / server (push) Successful in 26s
Deploy / changes (push) Successful in 11s
Deploy Metrics / agent-apps (push) Successful in 37s
Deploy Metrics / agent-infra (push) Successful in 30s
Deploy / build-backend (push) Successful in 52s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 57s
Deploy / build-frontend (push) Successful in 56s
Deploy / deploy (push) Successful in 1m17s
Deploy / perimeter-smoke (push) Has been cancelled
|
|||
|
|
9eb42607b9 |
feat(glitchtip): фоновая ретрай-доставка алерта в Telegram при отказе синхронной попытки
All checks were successful
CI Trade-In / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 27s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 8m54s
GlitchTip не ретраит вебхуки (#3157) — is_sent проставляется безусловно сразу после HTTP-ответа приёмника. При отказе Telegram синхронная попытка отвечала 502 и текст алерта пропадал безвозвратно (TRADE-IN-3F7, 28.08.2026; сеть до Telegram с хоста теряет ~каждый четвёртый запрос — замер 12.09). 502 при отказе Telegram ОСТАВЛЕН как есть — он задуман осознанно (#3456) как честный сигнал отправителю. Меняется судьба самого текста: перед возвратом 502 доставка ставится в фон через starlette.background.BackgroundTask на самом JSONResponse (app.tasks.glitchtip_alert_retry.retry_forward_alert), а не через FastAPI BackgroundTasks-зависимость — та привязывает задачи только к ответу, который вернул сам хендлер, а `raise HTTPException` строит отдельный ответ в exception-мидлваре, и такая задача не выполнилась бы вовсе (воспроизведено тестом при первой попытке реализации). Celery в проекте нет: ни app/celery_app.py, ни зависимости celery в backend/pyproject.toml не существует — бутстрап полноценной очереди с воркером вне границ этой задачи (новый контейнер/брокер). Фон использует штатную "воркерную" ретрай-политику TelegramClient.send_message (5 попыток, backoff до 30s) плюс свой внешний потолок в 3 попытки, чтобы недоставляемый алерт не крутился вечно — при исчерпании сдаётся с ERROR-логом текста. Переиспользует существующее форматирование (_build_message) и общий клиент приложения, без дублирования и новых переменных окружения. Refs #3471, #3157 |
||
|
|
e7f127bc6e |
chore(ops): маршрут и секрет для резервного приёмника алертов GlitchTip
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 18s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Обвязка к #3482, который добавил сам эндпоинт в alert-ack, но не мог тронуть caddy и workflow: файлы Caddy в тот момент правил параллельный PR #3478. Три вещи: маршрут /glitchtip* на metrics.gendsgn.ru, проброс нового секрета ALERT_ACK_GLITCHTIP_SECRET в окружение деплоя, и предупреждение шага, если секрет пуст. Последнее не косметика: при пустом секрете эндпоинт отвечает 503 на всё, и без предупреждения резервный канал молча не поднялся бы. Секрет намеренно отдельный от продуктового TRADEIN_INTERNAL_AUTH_SECRET — это другой хост и другой домен безопасности. Refs #3471, #3482 |
||
| 01960b03be | Merge pull request 'Резервный канал для алертов GlitchTip: приём на alert-ack, другой хост и другой провайдер' (#3482) from feat/3471-glitchtip-fallback-alert-ack into main | |||
| 1254294ac3 | Merge pull request 'Alertmanager объявлен единственным путём доставки: встроенный алертинг Grafana выключен явно' (#3481) from chore/3158-grafana-alerting-off into main | |||
| 2d87f70711 |
Merge pull request 'Access-логи Caddy доезжают в Loki: статусы и латентность прокси наконец видны' (#3478) from feat/3471-caddy-access-logs into main
Some checks failed
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy Infra Host / sync-infra-host (push) Successful in 7s
Deploy / changes (push) Successful in 12s
Deploy / build-backend (push) Successful in 51s
Deploy / build-worker (push) Successful in 47s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-frontend (push) Successful in 51s
Deploy / deploy (push) Has been cancelled
|
|||
| 5d4d17a5e1 | Merge pull request 'Тревоги уровня приложения, cAdvisor и пропавшие фоновые контейнеры; critical переживает подавление' (#3477) from feat/3471-app-alerts-coverage into main | |||
| 9298db0803 | Merge pull request 'Панель классов ответов больше не стекируется — красная линия и есть число 5xx' (#3474) from fix/3471-dashboard-5xx-stacking into main | |||
| c3840019c5 |
Merge pull request 'Москва в реестре городов лендинга и кабинета' (#3472) from feat/msk-public-city-registry into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 15s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 3m3s
Deploy Trade-In / test (push) Successful in 5m21s
Deploy Trade-In / build-backend (push) Successful in 53s
Deploy Trade-In / deploy (push) Successful in 1m52s
Deploy Trade-In / deploy-status (push) Successful in 3s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m50s
|
|||
|
|
165c4a5edd |
fix(obs): убрать шум выключенных платежей и трейсы health-проб proxy_pool
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 17s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 6m59s
Два источника шума в error-ленте и логах: 1. GlitchTip группа TRADE-IN-3GG: 167 событий за 29.08-12.09 — 503 "payments are disabled" из payments.py._require_enabled, которые бьёт внутренний IP смоук-проверки (кнопки оплаты во фронте нет). sentry_sdk StarletteIntegration репортит любой HTTPException с кодом из 5xx как error-событие, даже когда FastAPI штатно обработал исключение и вернул корректный ответ. Добавлен before_send-фильтр drop_payments_disabled_event (app/observability/sentry_scrub.py), матчащий по (status_code=503, detail="payments are disabled") через hint["exc_info"] — не по коду 503 в целом, чтобы не проглотить другие 503. Подключён во всех трёх точках инициализации sentry_sdk.init (app/main.py — единственный реальный источник события, scheduler_main.py и tgbot_main.py — belt-and-suspenders для единообразия, по образцу scrub_payment_request_body). Само поведение ручки не меняется — 503 остаётся, фильтруется только репортинг в трекер. 2. proxy_pool._probe_proxy: httpx.ProxyError (407 от прокси-провайдера) не попадал ни под TimeoutException, ни под ConnectError и падал в generic except Exception с exc_info=True — 184 строки полного traceback в сутки на штатный провал health-пробы, хотя итоговая сводка checked/ok/failed и так его учитывает. Добавлена отдельная ветка except httpx.ProxyError с логом в одну строку (узел + причина текстом исключения, без трейса). Логика самой пробы, аренды узлов и правил пула не изменена. Тесты: tests/test_sentry_scrub.py (drop_payments_disabled_event — дропает целевой 503, пропускает прочие ошибки и прочие 503/detail-комбинации), tests/services/test_proxy_pool.py (ProxyError логируется одной строкой без exc_info, счётчики healthcheck не ломаются). Refs #3471 |
||
|
|
423842ae36 |
feat(ops): резервный получатель GlitchTip-алертов в alert-ack
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Все три alert-правила GlitchTip (backend, frontend, Trade-In) сейчас шлют единственный вебхук в продуктовый бэкенд на Selectel — тот самый хост, за которым они следят. Если там упал backend или Caddy, ошибки приложения задерживаются или пропадают именно тогда, когда нужнее всего. Добавлен POST /glitchtip в alert-ack (живёт на инфраструктурном хосте Beget, не зависит от здоровья продукта): второй получатель того же Slack-совместимого payload, аутентификация секретом в заголовке X-GlitchTip-Secret или query ?secret= (тот же подход, что у tradein-mvp/backend/app/api/v1/glitchtip.py). Сообщение уходит в существующую тему клиентских инцидентов с явной пометкой «резервный канал». Секрет свой (ALERT_ACK_GLITCHTIP_SECRET), не переиспользует продуктовый TRADEIN_INTERNAL_AUTH_SECRET. Refs #3471 |
||
|
|
93451fae08 |
chore(ops): выключить встроенный Grafana Alerting, единственный путь — Alertmanager
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 14s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Проверка живого API Grafana 12.09.2026: правил алертинга ноль, контакт-поинт единственный и стоковый — grafana-default-email на example@email.com, GF_SMTP_* не заданы. Интерфейс выглядит настроенным, кнопка "New alert rule" работает, а результат молча уходит в никуда — ровно тот случай, из-за которого никто не проверяет второй, настоящий путь доставки. Дублирующий движок на том же датасорсе Prometheus надёжности не добавляет (общая точка отказа), а поддержку удваивает. Решение: Alertmanager — единственный путь доставки тревог, Grafana только рисует. GF_UNIFIED_ALERTING_ENABLED: "false" в docker-compose.metrics.yml (секция grafana) — единственная официальная секция [unified_alerting] в Grafana 11.x, легаси-[alerting] удалён из Grafana ещё в 9.0 (сверено с grafana.com/docs/ grafana/v11.5/setup-grafana/configure-grafana/#unified_alerting). Разом убирает Alerting из UI и глушит движок правил, так что искать и вычищать стоковый контакт-поинт отдельно не требуется. ops/metrics/grafana/provisioning/alerting/README.md — явная отметка для следующего человека: провижинить contact points/rules в эту папку не нужно, Grafana её при выключенном unified alerting не читает. Closes #3158, refs #3471 |
||
|
|
99f123e646 |
fix(tg): ответ оператора на веб-чат не теряется молча при сбое БД
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 14s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m56s
Для веб-треда запись в web_support_messages(direction='out') И ЕСТЬ доставка клиенту (веб-фронт читает её polling'ом). process_update на SQLAlchemyError безусловно делал rollback() и всё равно сдвигал offset — Telegram апдейт больше не отдавал, ответ оператора пропадал навсегда, а сам оператор был уверен, что ответил. Воспроизведено на проде 31.08.2026 (клиент kopylov). - `_handle_group_reply`: сбой БД на `record_web_out_message` теперь ловится локально — rollback → уведомление оператору реплаем в топик, что ответ НЕ доставлен и его нужно повторить; offset всё равно сдвигается (апдейт уже частично применён в Telegram, переигрывать нельзя). - `_notify_topic` возвращает bool: если само уведомление тоже упало (Telegram недоступен), пишем `logger.error` с thread_id/message_id (без текста переписки — ПДн в лог не идёт), чтобы это не осталось полностью немым. - Второй дефект того же узла: `direction='out'`-строки никогда не сохраняли topic_message_id, из-за чего реплай оператора на СВОЙ предыдущий ответ не резолвился (маршрут держался только на зеркале клиента). Теперь TG- и веб-путь сохраняют id ответа оператора в топике, `find_chat_by_topic_message` / `find_thread_by_topic_message` больше не фильтруют по direction. Колонка и partial unique индекс уже существовали (186/187) — миграция не потребовалась. Refs #3471 |
||
|
|
25c938a833 |
feat(ops): access-логи Caddy на stdout для боевых доменов — Alloy теперь видит их в Loki
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
За 3 часа в gendesign-caddy-1 не было ни одной строки http.log.access — только
ACME/TLS/warn от reverse_proxy. Статусы, латентность и RPS фронтового прокси
были не видны в Loki, 5xx приходилось искать в логе uvicorn.
Боевой конфиг собирается из caddy/sites/apps.caddy (импортируется корневым
Caddyfile через `import caddy/sites/{$CADDY_SITES:*}.caddy`, CADDY_SITES=apps
на Selectel/Poincare) — правка внесена туда, не в плоский Caddyfile в корне
(93 строки в git — это только заготовка, реальный конфиг на проде собирается
из caddy/sites/* + сниппетов, ~20КБ через admin API). caddy/sites/infra.caddy
(Beget: obsidian/errors/git.gendsgn.ru) не трогал — не боевой трафик, вне
скоупа issue.
Для gendsgn.ru и meraocenka.ru добавлен второй логгер (access_stdout, JSON)
рядом с существующим файловым — Alloy на этом хосте уже собирает stdout
контейнеров через journald (loki.source.journal в
ops/metrics/alloy/alloy-apps.alloy), второй bind-монт не нужен.
Объём: по измерению на проде 12.09.2026 (docker exec, wc -l + первый/последний
ts в текущих файловых логах) — gendsgn.ru ~4.5k запросов/сутки, meraocenka.ru
~4.2k запросов/сутки. После исключения шумных путей (см. ниже) новая копия
на stdout — это дополнительно ~6-7 МБ/сутки в Loki, то есть около +15-20% к
текущим ~39 МБ/сутки при ретенции 30 дней (после сжатия Loki фактический
прирост диска меньше).
Шумные пути исключены через log_skip: /health на gendsgn.ru — 32% строк
файлового лога в измеренном сегменте (аптайм-монитор раз в минуту, без
диагностической ценности), статика Next (_next/static, trade-in/_next/static)
на обоих доменах — 5.1% строк на meraocenka.ru. Важный нюанс: log_skip в
Caddy — общий флаг на запрос для ВСЕХ логгеров сайта, скипать выборочно
только stdout-копию нельзя, поэтому эти пути пропадают и из существующих
файловых логов тоже (gendsgn.ru.log, meraocenka.ru.log) — осознанный побочный
эффект, а не только экономия трафика в Loki.
Секреты в query-параметрах (?secret=, ?token= и т.п. — инцидент #3154) второй
раз не чистим: уже работающий loki.process.scrub_credentials в
ops/metrics/alloy/alloy-apps.alloy (#3354, тот же список имён, что в
app/core/log_scrub.py) стоит на пути ЛЮБОГО journal-лога и вырежет их до
записи в Loki. Alloy-конфиг не менял — существующий пайплайн уже покрывает
новый источник.
Осталось за скобками (не входит в этот PR): caddy/sites/infra.caddy на Beget
логирует access тем же способом (файл, не stdout) — если нужна наблюдаемость
git.gendsgn.ru/errors.gendsgn.ru/metrics.gendsgn.ru, это отдельная задача с
тем же паттерном.
Refs #3471
|
||
|
|
655652ae1c |
feat(ops): алерты уровня приложения и три слепые зоны мониторинга
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 14s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
- AppHighErrorRate / AppHighLatencyP95 (job="app", severity=critical,
host="apps") — доля 5xx и p95 задержки по http_requests_total /
http_request_duration_seconds_bucket теперь ловятся Prometheus'ом, а не
только постфактум в GlitchTip. Пара critical+apps обязательна для
маршрута telegram-clients в alertmanager.yml.tmpl.
- Inhibit по HostAgentDown больше не гасит critical того же хоста —
target_matchers сужен до severity="warning" (падение node-exporter
раньше молча забирало с собой PostgresLongTransactionCritical и
critical-алерты cAdvisor).
- cAdvisor: keep-фильтр в alloy-infra.alloy резал у него `up` наравне с
container_*-мусором — job "cadvisor" не публиковал свою же серию `up`.
Пропущены up/scrape_samples_scraped, добавлен CadvisorDown.
- TradeInBackgroundContainerMissing по absent(container_last_seen) на
tradein-tgbot/tradein-scraper — эти контейнеры не HTTP-сервисы и в
up{} не участвуют вовсе; крэш без рестарта раньше не алертился.
Не закрыто: живой, но зависший процесс tgbot/scraper (container_last_seen
не про внутренний прогресс, а про то, что Docker видит контейнер running).
Refs #3471
|
||
| 2e928c715b |
Гейт #3448: закрыть зелёные мутации, добавить признак непустоты, запускать на ci-tradein.yml
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 16s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m42s
CI / openapi-codegen-check (pull_request) Successful in 2m37s
CI / backend-tests (pull_request) Successful in 19m21s
Мутационный прогон нашёл пять зелёных мутаций — то есть мест, где логику можно сломать, а гейт этого не заметит. Закрыты фикстурами, каждая краснеет ровно на своей мутации: * CADDY_RE → `^(Caddyfile|caddy)`: тогда `caddy-extra/**` и `Caddyfile.bak` дают caddy_only=true — тихий пропуск полного деплоя, против которого весь PR; * снятие проверки «файлов больше нуля»: пустой дифф формально удовлетворяет «ни один файл не лежит вне caddy» и отключает сборку; * выпадение `data/sql/**` из backend: миграции едут в backend-образе; * подмена базы на `HEAD^..HEAD`: на ОДНОМ мерж-коммите даёт верный ответ и выглядит рабочей, а на push'е из нескольких коммитов теряет первый — фикстура «бэкенд-коммит + caddy-коммит» это ловит; * потеря `core.quotePath=false`: кириллический путь под backend/ выпадает из классификации. Плюс прод-сторож из deploy-caddy: его кусок (от PROD_HEAD до `git reset --hard`) извлекается из ssh-скрипта и ИСПОЛНЯЕТСЯ на временном репозитории, где прод-дерево отстаёт от origin/main — отдельно законный случай (отстал только конфиг прокси) и отказной (отстал бэкенд). Проверяется и порядок: сторож обязан стоять ДО `git reset`. Команда ищется регуляркой по началу строки, а не подстрокой: `git reset --hard` упоминается выше в комментариях, и поиск по тексту находил объяснение вместо кода. Признак непустоты у проверки исключающих `!`-шаблонов: раньше она бы прошла при нулевом охвате (переименуют действие, заведут .yaml) — теперь отдельно утверждается, что хотя бы один шаг paths-filter найден, как это сделано в ci.yml для shell-гейта. Маска расширена до *.y*ml, параметризация — по найденным шагам. ci.yml: в фильтр `backend` добавлен `.forgejo/workflows/ci-tradein.yml` — там тоже живёт paths-filter, и без этой строки правка с `!`-шаблоном не запустила бы backend-tests, то есть гейт не побежал бы ровно на той правке, от которой стережёт. Докстринг фикстуры с мержем переписан: он утверждал, что «дифф последнего коммита» на мерж-коммите даёт пустой список (это верно для `git show`, а не для `git diff HEAD^ HEAD`) — то есть обещал защиту, которой у этой фикстуры нет. Теперь там сказано, что подмену базы стережёт отдельная проверка. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 9a8aaa2d2d |
deploy-caddy: сторож отставания прода, общий лок и квотирование путей (#3448)
Оживший быстрый путь снимает гард свежести :latest. Пока caddy_only был мёртв, любой push шёл полным деплоем, и гард #2950 прикрывал прод по умолчанию. Теперь при caddy_only=true джоба `deploy` пропускается целиком — вместе с гардом. Сценарий отказа. Push A правит бэкенд, билды ~6 мин, `deploy` в очереди. Через 2 мин push B правит только caddy/. На Forgejo 10.0.3 ещё не стартовавшая `deploy` предыдущего прогона отменяется ДАЖЕ при cancel-in-progress: false (наблюдение 21.08.2026 10:35:13, шапка scripts/check-latest-image-revision.sh; workflow-level concurrency там не исполняется, см. deploy.yml). Дифф A..B — один caddy-файл, быстрый путь включается, `compose pull` + `up -d` не делает никто: прод крутит старый образ, голова main зелёная, сигнала нет. Сторож в ssh-скрипте deploy-caddy: если прод отстаёт от origin/main не только по Caddyfile/caddy/**, быстрый путь запрещён, шаг падает и называет файлы. Стоит ДО `git reset --hard` намеренно — при отказе прод-HEAD остаётся честным для следующего прогона. У Trade-In для того же заведён отдельный маркер (/opt/gendesign/.tradein-deployed-sha, deploy-tradein.yml), у ПТИЦЫ маркера нет, и `git reset` делает прод-HEAD его эквивалентом. ЧЕГО СТОРОЖ НЕ ЛОВИТ: `deploy`, упавшую ПОСЛЕ `git reset --hard` (например на миграции). Тогда прод-HEAD уже равен новому коммиту, а контейнеры старые — это остаётся за настоящим маркером «что задеплоено». Тот же лок, что и у полного деплоя. deploy-caddy делает `git reset --hard` в /opt/gendesign, то есть правит прод-дерево — ровно то, что job `deploy` сериализует через flock /var/lock/gendesign-docker-deploy.lock. Пока путь был мёртв, сталкиваться было нечему; теперь это первая джоба, трогающая прод-дерево в обход сериализации. Квотирование путей. `git diff --name-only` и `git ls-files` при core.quotePath (умолчание true) отдают не-ASCII пути закавыченными с \NNN-экранированием — `^backend/` такую строку не матчит, и файл backend/<кириллица>.py дал бы backend=false. Старый paths-filter брал `--name-status -z`, где квотирования нет: это единственное место, где переход на свой diff менял поведение. В дереве такие пути уже живут (docs/Бизнес-план…). Добавлен `-c core.quotePath=false` в обе команды и в сторож выше. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
6febb36afd |
fix(ops): деплой метрик перечитывает конфиг Prometheus, а не только кладёт его на диск
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 3m14s
CI / backend-tests (pull_request) Successful in 19m15s
lastConfigTime у gendesign-prometheus совпадал со startTime контейнера 16 суток: docker compose up -d не пересоздаёт контейнер из-за изменения содержимого бинд-маунта (сравнивается только описание сервиса), а --web.enable-lifecycle был включён, но /-/reload никто не вызывал. Любая правка ops/metrics/prometheus/** доезжала до диска и молча не вступала в силу до случайного рестарта, при зелёном деплое. Добавлен шаг по образцу уже работающей проверки Caddyfile в этом же workflow: promtool check config + promtool check rules внутри контейнера, reload только при успешной проверке, приёмка через сравнение lastConfigTime до/после (обновляется на каждый успешный reload, поэтому надёжно ловит и несостоявшийся вызов). Провал promtool теперь роняет шаг и не трогает работающий Prometheus. Alertmanager уже чинился отдельно (--force-recreate, #3078/#3136) — reload для него намеренно не помогает из-за переиспользуемого инода, это не regressed. Loki (/etc/loki/loki-config.yml), Grafana (provisioning) и Alloy (config.alloy) в том же деплое лежат на дисковых бинд-маунтах без reload — чинить их этим PR не стал, см. summary задачи. Refs #3467, #3471 |
||
| 1eee4b955d |
Merge pull request 'ДКП-коридор по Москве не строился: имя улицы не извлекалось из московского формата адреса' (#3473) from fix/msk-street-name-suffix into main
Some checks failed
Deploy Trade-In / changes (push) Successful in 22s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m27s
Deploy Trade-In / test (push) Successful in 5m8s
Deploy Trade-In / build-backend (push) Successful in 1m30s
Deploy Trade-In / deploy (push) Successful in 1m47s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Has been cancelled
|
|||
| 42daf8404f |
Merge pull request 'feat(mera/лендинг): витрина показывает полосу расхождения −5…+20 %; плитку «уверенность низкая» сменил замер 12.09' (#3468) from feat/landing-showcase-band into main
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
Deploy Trade-In / build-frontend (push) Has been cancelled
|
|||
|
|
412d78f357 |
fix(ops): панель классов ответов больше не стекируется — красная линия и есть число 5xx
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 16s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
11.09 панель «Запросы по классам ответов» дала ложную тревогу: при stacking=normal верхняя, красная линия рисуется на высоте суммы всех классов, и её положение читается как объём пятисоток. Фактически за то окно их было шесть. Стек здесь ничего не даёт: суммарный трафик уже показан отдельной панелью «Запросов в минуту», а от этой нужна форма каждого класса по отдельности. Заливка снижена, линия утолщена — без стека 25% заливки перекрывают друг друга. Описание панели теперь прямо говорит, что линии независимы. Refs #3471 |
||
| 00e0bddfb4 |
Merge pull request 'Московский city_hint больше не уходит молча в регион 66' (#3470) from feat/msk-suggest-region-inference into main
Some checks failed
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m47s
Deploy Trade-In / build-backend (push) Successful in 1m14s
Deploy Trade-In / deploy (push) Has been cancelled
|
|||
| db47fa0ecd |
fix(mera/лендинг): утверждение про полосу проверяет само себя по показанным строкам
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m48s
CI Trade-In / backend-tests (pull_request) Successful in 6m0s
Дыра в выкате, найденная ревьюером до мержа. Подпись про полосу собиралась из констант `BAND_MIN_PCT`/`BAND_MAX_PCT` в коде фронта, а строки витрины и `rejection_rule` приезжают из БД, от ПОСЛЕДНЕГО прогона задачи `landing_showcase_deals`. Задачи нет в расписании — её запускают руками. Значит в окне «фронт выкачен, витрина не пересчитана» страница утверждала бы «показаны сделки с расхождением от −5 % до +20 %», а под утверждением лежали бы прежние двадцать строк: по замеру на проде 12 из 20 вне полосы, худшая +75,7 %. Утверждение и его опровержение в одном экране — хуже, чем было до правки. Чинится конструкцией, а не запуском задачи: `allWithinBand(deals)` в `deal-view.ts` спрашивает САМИ показанные строки теми же границами, что стоят в тексте. * Все показанные строки в полосе — печатаем прежнюю формулировку. * Хоть одна вне — про полосу НЕ утверждаем. В таблице: «Полосу расхождения от -5 % до +20 % эта подпись не обещает: среди показанных строк есть расхождения вне неё, то есть витрину собрал прогон с другим правилом — тем, что напечатано выше». Правило того прогона и так приезжает в `rejection_rule` из ТОГО ЖЕ прогона, что и строки, поэтому подпись с ними согласована по построению. В ленте остаётся только то, что посчитано по строкам: медиана и худшая. * Медиана по ВСЕЙ сверке (15,3 %, 325 сделок) печатается в обеих ветках — она и удерживает страницу честной независимо от того, пересчитана витрина. Тесты по значению в обе стороны: набор с одной строкой вне полосы (+75,71 % — реальная строка прода) → утверждения про полосу нет; все в полосе → есть. Фальсификация: `allWithinBand` обезврежен руками (всегда true) — краснеют оба новых теста, и красный текст показывает ровно тот дефект: «Это отобранная полоса расхождения от -5 % до +20 % … худшая 75,7 %». Проверка возвращена. Проверка заодно поймала мои же фикстуры ленты: −11,5 % ниже нижней границы полосы (−5 %), то есть «маленькое отклонение» ещё не значит «в полосе». Значения заменены на внутриполосные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
d78b1f8881 |
fix(tradein): ДКП-коридор по Москве не строился — имя улицы не извлекалось
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 6m5s
`extract_street_name` возвращал None для любого московского адреса, потому что парсер ждёт тип улицы ПЕРЕД названием («ул. Малышева»), а в Москве он стоит после: «Тверская улица, 6». Keyword-регекс требует пробел сразу за типом, там запятая — совпадения нет вовсе; дальше fallback брал первый токен с большой буквы, получал «Москва» из стоп-списка и отдавал None. Следствие на проде (замер 12.09): оценка по московскому адресу отвечает 200 с 25 аналогами, но `dkp_corridor` в ответе — null, при 212 937 московских ДКП в базе. Коридор сделок по Москве не строился ни разу. Добавлен второй проход: ищем тип улицы без требования пробела и берём 1-3 слова ДО него в пределах той же запятой-секции. Прежний путь не тронут — «ул. X» и реверс-формат Nominatim разбираются как раньше; непустые результаты не меняются, новый проход даёт значение только там, где раньше был None. Списки типов улиц вынесены в общую константу, чтобы два регекса не разъехались при добавлении нового типа. Нумерованные проезды («Проектируемый проезд № 4062») намеренно остаются None: имя «Проектируемый» собрало бы коридор по сотням разных проездов. ## Регион-скоуп двух ручек Непустое имя улицы включает `/street-deals` и `/sales-vs-listings`, где раньше для Москвы был ранний выход. Обе скоупятся только по `_resolve_target_city` — словарю городов Свердловской области, — поэтому для Москвы фильтр города пуст, и остаётся один ILIKE по улице. Замер на проде: улица «Ясная» — 168 сделок в регионе 66 и 80 в 77, «Советская» — 1202 и 17. Без фильтра региона московский запрос смешал бы екатеринбургские сделки в медиану, то есть фикс парсера сам по себе открыл бы дыру. Поэтому в обе ручки добавлен обязательный фильтр по `region_code`; регион выводится из адреса через реестр регионов точным сравнением сегмента, а не подстрокой — иначе екатеринбургская «Московская улица» уехала бы в регион 77. В `deals` регион заполнен у всех строк (66 → 108 623, 77 → 212 937, NULL нет), так что фильтр ничего не отрезает у существующих запросов. У `/sales-vs-listings` табличная функция параметра региона не знает, её миграция в этот фикс не входит. Фильтр применён снаружи, соединением с `deals` по идентификатору сделки: сторона объявлений остаётся без регион-скоупа. Это осознанный компромисс, он описан в коде; полный фикс — отдельная миграция с параметром региона внутри функции. Тесты: 535 passed во всех файлах, затрагивающих коридор и уличную статистику (+13 новых), ruff чистый. |
||
|
|
c6711d05c4 |
feat(mera-public): Москва в реестре городов лендинга и кабинета
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m20s
CI Trade-In / backend-tests (pull_request) Successful in 6m2s
Реестр городов один на публичную форму и кабинет, и до сих пор он знал только Свердловскую область. Московский адрес нельзя было выбрать ни там, ни там, хотя бэкенд Москву поддерживает: реестр регионов знает 77, проба покрытия знает московские центроиды, оценка отрабатывает. Москва подана НЕ как ещё один «частично покрытый город области», а отдельной строкой: сбор по ней есть, а замера полноты покрытия нет, и приписывать ей формулировки области было бы неправдой. Для этого у `OblastCity` появилось поле `region`, а `SECONDARY_CITIES` теперь строится из `OBLAST_66_CITIES` — иначе Москва попала бы в перечисление городов области. Бэкенду поле не отправляется: это различение нужно только фронту. В `landing-facts.ts` строки Москвы сознательно нет — там лежат замеры покрытия по городам, а по Москве замера не делали. Придумывать цифру нельзя, поэтому паритет-тест копи сверяет замеры с `OBLAST_66_CITIES`. Тексты про географию переписаны в четырёх местах: плашка покрытия на главной, карточка бесплатной пробы, ответ FAQ про регионы и сообщение «адрес вне покрытия». Везде одна и та же честная формулировка: по области — полное и частичное покрытие, по Москве — считаем, но полноту не мерили. Юридический адрес в подвале не трогали, там «Свердловская область» — это адрес компании, а не география сервиса. Паритет-тест дропдауна и порогов покрытия на бэкенде дополнен Москвой: город, предлагаемый к выбору, обязан быть отвечаемым пробой. Фронт: 218 passed, tsc и eslint чистые. Бэкенд: 34 passed в затронутом файле. |
||
|
|
ef82a707fc |
fix(mera): московский city_hint больше не уходит молча в регион 66
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m31s
Публичный `/suggest` и кабинетный `/api/v1/geocode/suggest` всегда звали геокодер с `region_code=66`: публичная ручка регион не передавала вовсе, а у кабинетной он был обязательным параметром со значением по умолчанию. `city_hint="Москва"` на это не влиял — DaData и Nominatim получали свердловский hard-констрейнт и молча возвращали ПУСТО. С сайта и из кабинета московский адрес просто нельзя было ввести, хотя оценка, проба покрытия и реестр регионов Москву уже поддерживают. Добавлен `effective_region_code()`: явный `region_code` важнее вывода из `city_hint`, вывод идёт через существующий реестр `app.services.regions` (`REGIONS[77].cities` содержит «москва»), последний рубеж — прежний `DEFAULT_REGION_CODE`. Отдельного списка городов не заводим: разъехаться двум спискам — вопрос времени. Поведение сегодняшних клиентов не меняется байт-в-байт: без `city_hint` и с любым свердловским городом регион по-прежнему 66. Публичная схема принимает `region_code` на будущее — если фронт когда-нибудь начнёт его слать, он будет приоритетнее хинта; неизвестный регион как и раньше отдаёт 422 из геокодера, а не 500. Тесты: четыре инварианта на сам хелпер (нет хинта → 66; свердловский город → 66; Москва → 77; явный 66 поверх Москвы → 66) и по одному на каждую ручку — что вниз по потоку уезжает ожидаемый регион. Прежние тесты region-скоупа геокодера не тронуты. 189 passed в связанных файлах, ruff чистый. |
||
| 2467943200 |
feat(mera/лендинг): витрина показывает полосу расхождения −5…+20 %, плитку уверенности сменил замер 12.09
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m13s
CI Trade-In / backend-tests (pull_request) Successful in 5m29s
Владелец просит на витрине только сделки, где прогноз разошёлся с ценой ДКП в пределах от −5 % до +20 %. Фильтр живёт в продюсере (`select_rows`), поэтому таблица сверок и бегущая строка берут ОДИН набор, а не два. Чтобы страница от этого не начала врать: * `REJECTION_RULE` переписан. Прежняя формулировка («величина отклонения на отбор и отбраковку не влияет — иначе витрина показывала бы лучший хвост») после фильтра стала ложью ровно про то, чего опасалась, поэтому снята, а не смягчена. Новая называет полосу и говорит, что это отбор показательных строк, а не вся сверка. Границы в текст ПОДСТАВЛЯЮТСЯ из констант `BAND_MIN_ERR_PCT`/`BAND_MAX_ERR_PCT` — подпись не может разъехаться с фильтром, и это проверяется тестом. * Фильтр стоит в `select_rows`, а не в `build_row`: строка вне полосы остаётся кандидатом и попадает в `eligible`. Отбраковав её раньше, мы получили бы «показано 20 из 20 годных» — счётчик, из которого отбор не виден вообще. * Счётчики разъехались с подписью, и подпись поправлена: `eligible − written` больше не значит «столько не поместилось», в разницу входят отсеянные полосой. Под таблицей теперь «показано N строк из M собранных прогоном». * «В пределах 20 % — N из N» из подписи снято: при потолке полосы +20 счёт всегда выходил бы N из N и читался бы как замер попадания. Неработающая проверка читается как работающая. * Медиана по ВСЕЙ сверке (15,3 %, 325 сделок) в подписи осталась и теперь сторожится тестом: без неё разброс отобранной двадцатки читается как точность расчёта. * Полоса названа и в подписи ленты — она висит над первым экраном, её числа читают раньше любых оговорок блока «Точность». * Меньше лимита в полосе — показываем сколько есть, добора нет. Плитка «400 из 400 расчётов с пометкой „уверенность низкая“» заменена на свежий замер 12.09.2026 (engine=full, 290 сделок, медиана трёх пересборок с солями 11/22/33): «52,7 % сделок — расхождение в пределах ±20 %». Запись `confidenceLow` не удалена, а помечена снятой (прогон 29.08 на кластеризованной выборке) — до решения владельца. Оговорки новой величины называют три вещи, без которых она льстит: замер не point-in-time, разброс пересборок 46,2–56,6 %, и что медианное расхождение того же прогона (19,1 %) ВЫШЕ прежних 15,3 % от 31.08 — на странице два числа разных дат, и молчать о том, что свежий прогон вышел хуже, нельзя. `priceError` и `coverage` не тронуты. Сторож свежести теперь следит за ОБЕИМИ датами замеров, а не только за 31.08. Проверено: на проде из 20 сегодняшних строк витрины в полосу попадают 8 (40 %), что сходится с 35,5 % «доли в полосе» из бэктеста 12.09. Фальсификация: снятие фильтра руками красит 3 теста, ключевой — по значению ([44, 43, 41] вместо [44] на реальных строках прода +75,7 / −27,9 / +9,9 %). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| cfcdb9393c |
Merge pull request 'Тревоги печатали не ту величину, которую называли: «2.684e+11% от mem_limit» и «доля HOT 75.21%» при пороге 20%' (#3464) from fix/alert-value-is-not-the-ratio into main
All checks were successful
Deploy / changes (push) Successful in 12s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Metrics / server (push) Successful in 22s
Deploy Metrics / agent-infra (push) Successful in 27s
Deploy Metrics / agent-apps (push) Successful in 29s
Deploy / build-worker (push) Successful in 45s
Deploy / build-backend (push) Successful in 47s
Deploy / deploy (push) Successful in 1m12s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 1m42s
|
|||
| 03d9745b4f |
Отмена по бюджету больше не оставляет поток в сессии запроса — оценка не теряется на 500 (#3449)
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m28s
Deploy Trade-In / test (push) Successful in 4m38s
Deploy Trade-In / build-backend (push) Successful in 1m6s
Deploy Trade-In / deploy (push) Successful in 1m46s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m41s
|
|||
| dfc39355f9 |
Merge pull request 'Коридор сделок с малой выборкой честно помечен справочным' (#3462) from fix/3452-corridor-advisory-zone into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Has been cancelled
|
|||
| 668ac40631 |
fix(tradein): подпись коридора говорит про выборку, а не про алгоритм
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m9s
CI Trade-In / backend-tests (pull_request) Successful in 5m21s
Ревью #3462 поймало ложь в микрокопии: «оценку по ним не корректировали» утверждает про АЛГОРИТМ то, чего код не гарантирует. Порог estimate_corridor_clamp_min_n гейтит только две страховки — кламп headline и radius-floor. Третий ценовой путь, гейт Tier C (#1795 шаг 3, estimator.py), сравнивает якорь с потолком коридора БЕЗ порога вообще: коридор из пяти сделок там способен уронить headline на треть (воспроизведено ревьюером: якорь Tier C 300 000 ₽/м², с коридором 200 000 против 300 500 без него). Плюс deals-headline-fallback берёт медиану коридора начиная с трёх сделок. Формулировка переписана на утверждение о ДАННЫХ — оно истинно во всех достижимых состояниях: «справочно: сделок мало (N) — коридор ориентировочный». Ветка «объявлений рядом нет» (n_analogs = 0) больше не молчит: раньше там возвращался null, и клиент не узнавал, что вся его цена стоит на трёх сделках. Теперь — «оценка построена на этих сделках — их всего N». Докстринги advisory_only в схеме и комментарий у лога тоже перестали обещать «коридор в цену не пошёл»: поле значит ровно «страховки выключены». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 33714e464e |
fix(alerts): в тексте тревоги печаталась не та величина, которую текст называет
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m30s
CI / backend-tests (pull_request) Successful in 17m50s
Боевые сообщения в Telegram 12.09:
«apps / tradein-browser: 2.684e+11% от mem_limit. Дальше OOM-kill.»
«apps / listings: доля HOT 75.21%.» — при пороге срабатывания «доля < 20%»
Причина общая и она про ФОРМУ выражения, а не про условие: в PromQL `A and B`
возвращает ЗНАЧЕНИЯ ЛЕВОЙ части, отфильтрованные правой. В `$value` попадало A:
- ContainerNearMemoryLimit: слева стоял `container_spec_memory_limit_bytes` —
в сообщение уходил лимит в байтах (2 684 354 560), отрендеренный как
процент. Замер 12.09: настоящее потребление того контейнера — 1.2 % лимита.
- PostgresLowHotUpdateRatio: слева стоял `rate(tup_upd[6h])` — апдейтов в
секунду. Это опаснее: 0.7521 превращалось в «75.21%», попадало в
правдоподобный диапазон и противоречило собственному порогу, но выглядело
настоящим числом. Замер 12.09 по listings: rate(tup_upd[6h]) = 0.0411 →
сообщение сказало бы «4.11%», настоящая доля HOT = 0.00%.
Условия срабатывания в обоих случаях были ВЕРНЫ — врал только текст, поэтому
дефект и прожил незамеченным.
Правка: отношение вынесено влево, а побочное условие — внутрь знаменателя
(`X / (Y > 0)`), где оно и фильтрует серии, и защищает от деления на ноль.
Проверено на живом Prometheus (только чтение): новое выражение памяти отдаёт
доли 0.35–0.71 (топ — gendesign-infra-postgres 70.8 %), новое выражение HOT —
доли 0.00–1.00. `promtool check rules` — SUCCESS, 16 rules.
Третье правило того же семейства (PostgresDeadTuplesHigh) верно, но верно
случайно: печатаемая величина совпала с левым операндом. Помечено комментарием,
чтобы его не «причесали» по образцу двух других.
Гейт: backend/tests/ops/test_alert_value_is_the_described_quantity.py — если
описание рендерит `$value` как долю (`humanizePercentage`), левая часть
выражения обязана содержать деление. Фальсификация: вернул файл правил с
origin/main → красные test_percentage_annotations_come_from_a_ratio и
test_known_two_rules_are_fixed; с правкой — 3 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|||
| 091137a0c5 |
Быстрый путь caddy_only: считаем изменённые файлы сами, без исключающих шаблонов (#3448)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m34s
CI / backend-tests (pull_request) Successful in 17m41s
Быстрый путь «правка ТОЛЬКО прокси» (#2916) не отработал ни разу: мерж |
|||
| 5f2810b8c6 |
Витрина «сделки против объявлений» перестаёт пустовать: снят предикат по синтетической комнатности сделок (#3451)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Successful in 38s
Deploy Trade-In / build-frontend (push) Successful in 2m15s
Deploy Trade-In / test (push) Successful in 4m35s
Deploy Trade-In / build-backend (push) Successful in 1m5s
Deploy Trade-In / deploy (push) Successful in 1m31s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m41s
|