8 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| e86f0782da |
ci: смоук периметра МЕРЫ запускается сразу после деплоя (#2917) (#2922)
All checks were successful
Deploy / changes (push) Successful in 10s
perimeter-smoke-mera / smoke (push) Successful in 12s
Deploy Trade-In / changes (push) Successful in 14s
Deploy / build-frontend (push) Successful in 51s
Deploy / build-backend (push) Successful in 53s
Deploy / build-worker (push) Successful in 53s
Deploy Trade-In / build-browser (push) Successful in 40s
Deploy / deploy (push) Successful in 1m33s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Successful in 2m50s
Deploy Trade-In / test (push) Successful in 4m8s
Deploy Trade-In / build-backend (push) Successful in 35s
Deploy Trade-In / deploy (push) Successful in 1m39s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
|
|
b1fb7bb055 |
Merge remote-tracking branch 'forgejo/main' into feat/mera-public-api
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Successful in 46s
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 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 4m45s
# Conflicts: # tradein-mvp/backend/app/core/rbac.py |
||
|
|
7424c283d5 |
feat(mera/b2c): отдельный экран оценки на meraocenka.ru и короткие адреса
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 8s
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 / browser-tests (pull_request) Successful in 44s
CI Trade-In / frontend-checks (pull_request) Successful in 1m2s
CI Trade-In / backend-tests (pull_request) Successful in 4m41s
## Экран проверки — /estimate Проверка квартиры вынесена на собственный адрес: там у автокомплита есть место под список подсказок, а у результата — место рядом с полями. Форма в герое лэндинга осталась входной точкой и уводит сюда, донося набранное через sessionStorage (НЕ через query — адрес в URL попал бы в access-лог Caddy рядом с IP посетителя, а мы на той же странице обещаем ничего не хранить). Показывает живую пробу покрытия: сколько похожих квартир продаётся рядом и сколько в среднем висят их объявления. Ни одной рублёвой цифры — цену продаёт платный шаг. Тексты вердикта вынесены чистой функцией (coverage-copy.ts) и покрыты тестами: подпись под возрастом обязана говорить «объявление», а не «продаётся» (выборка цензурирована), при неизвестном возрасте плитки нет вообще, а пустая когорта объясняется как факт о рынке с подсказкой, что поменять, — директива «никогда не блокировать вывод». ## Короткие адреса Человек больше не видит /trade-in/mera-public/... — только /, /estimate, /oferta, /refund, /privacy. Длинные адреса отдают 301 на короткие: у страницы один канонический адрес, старые ссылки живы. Цена решения: ссылки работают только на meraocenka.ru (короткие пути раздаёт этот хост). Открывать лэндинг для проверки нужно там же, а не с gendsgn.ru. Ссылки эмитятся обычным <a> (PublicLink) — next/link подставляет basePath, и href="/estimate" уехал бы на несуществующий /trade-in/estimate. ## Три дефекта, найденных на живом сайте 1. Палитра v3 никуда не доезжала. b2c-tokens.ts не импортировал НИКТО, ни одна --b2c-* переменная не объявлялась, каскад молча пропускал такие декларации — лэндинг отдавал 200 бесцветным. Добавлен мост b2cVars, гейтом стал тест: каждая использованная в CSS переменная обязана быть объявлена. 2. Голый /trade-in/mera-public падал в 404 — матчер был со слэшем и звёздочкой. Ровно туда вела «Главная» в подвале. 3. «Для бизнеса» вела на «/» — то есть на сам лэндинг. Теперь абсолютный адрес B2B-контура. Пункты «Проверьте себя» и «Продажа под ключ» вели на якоря, которых нет нигде: приведены к виду «Статьи» — видны, но не кликабельны. ## Периметр и приватность Подсказки переведены на POST: access-лог публичного домена пишет URI целиком, то есть GET с ?q= сохранял бы адрес квартиры в файл. Тело в лог не попадает. Метод запинен тестом — это часть обещания, а не стиль. П. 5.4 политики ПДн переписан ВМЕСТЕ с кодом: прежний текст утверждал, что адрес не покидает браузер, и это перестало быть правдой. Новый говорит точно — передаётся, используется однократно, в базах не сохраняется. Последнее проверено по коду: suggest() работает без кэша, проба — один SELECT, аудит пишет строку только при наличии username. PUBLIC_ESTIMATE_ENABLED сузился до платного шага (бесплатная проба не хранит ничего, платный расчёт хранит). ## Проверено vitest 47 passed (9 файлов), tsc, next lint, next build, isolation guard 40 файлов, backend 75 passed, caddy validate = Valid configuration. Мутации структурных гейтов: убрать --b2c-accent-text из b2cVars → падает тест палитры убрать /estimate из @meraPages → падает тест маршрутов добавить импорт next/link → падает тест basePath откат → 14 passed Caddyfile добавлен в paths-filter фронтового лэйна: его читает тест маршрутов, и без этой строки правка одного лишь Caddyfile не запускала бы ни один гейт. Refs #2894, #2895 |
||
|
|
d046eaada2 |
feat(mera/b2c): публичный API МЕРЫ — /api/public/mera/{suggest,coverage}
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
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
CI Trade-In / backend-tests (pull_request) Successful in 4m49s
Первый шаг к отдельному B2C-интерфейсу оценки на meraocenka.ru: домен получает собственную поверхность бэкенда вместо того, чтобы тянуть куски закрытого контура. ## Отдельный префикс, а не проброс кусков /api/v1/* На meraocenka.ru действует allowlist-by-default. Открыть там API можно было двумя способами: перечислить нужные v1-пути поимённо — или завести префикс, под которым по определению не лежит ничего закрытого. Выбран второй: при первом одна опечатка в матчере (`/trade-in/api/*` вместо точного пути) открывает наружу весь v1 — ~20 ручек, включая PDF расчётов, фотографии и админку. Цена ошибки, а не удобство. Добавить сюда приватную ручку теперь нужно СПЕЦИАЛЬНО — положив файл в app/api/public/. Случайно нельзя. ## Ноль записей в БД Обе ручки только читают: /coverage — один SELECT, /suggest — прокси автокомплита. Это условие, при котором публичная форма работает ДО контура согласия 152-ФЗ (#2895: сегодня адрес физлица попадает в trade_in_estimates раньше согласия, а пути удаления в бэкенде нет). Платный расчёт, который писать будет, открывается только вместе с ним. ## Делегирование, а не копии Обе ручки вызывают те же функции, что обслуживают закрытый контур (v1.geocode.suggest_addresses, v1.trade_in.coverage_probe). Разбор #2894 показал, чем кончается вторая копия когорты: проба отвечает «данные есть» там, где платный расчёт видит ноль. Публичный ответ переиспользует CoverageProbeResponse — на нём уже стоит гейт «ни одного price-подобного поля». ## Бюджеты Общего 300/60с мало: /suggest через DaData-тир — платный внешний вызов, абуз стоит денег. Свои per-IP окна: 40/мин на подсказки (человек с debounce'ом тратит единицы на адрес), 15/мин на пробу. ## Проверено 11 тестов, из них структурные: набор ручек под /api/public проверяется на РАВЕНСТВО (третья, добавленная без правки теста, роняет сборку) и сверяется с rbac._PUBLIC_PATHS в обе стороны — чтобы не осталось открытого пути-призрака. Рядом висит закрытый маршрут-двойник: без него «аноним получает 200» одинаково зелёный и когда исключение точечное, и когда auth-гейт снят целиком. Мутации: убрать пути из rbac._PUBLIC_PATHS → 7 failed / 4 passed снять бюджет с /coverage → 2 failed / 9 passed откат → 11 passed Плюс 72 passed на связке rbac + coverage + version, `caddy validate` = Valid configuration, ruff чист. Смоук периметра дополнен парой, которую нельзя разделять: публичные ручки отвечают 200 анонимно И /trade-in/api/v1/* на этом домене по-прежнему 404. Зелёная только первая проверка = API открыт целиком, а тест этого не заметил. Refs #2894, #2895 |
||
|
|
a8fa7364ae |
merge(tradein/payments): влить main в feat/tradein-payments-perimeter-hardening
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
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 4m56s
Слияние main принесло собственные Sentry-скрубберы (redact_telegram_bot_token + stabilize_retry_error_fingerprint) в app/main.py и app/scheduler_main.py — конфликт разрешён композицией, а не выбором стороны: обработчик перед отправкой в GlitchTip теперь прогоняет событие через всю цепочку в указанном порядке: scrub_payment_request_body → scrub_pii_event → redact_telegram_bot_token → stabilize_retry_error_fingerprint (main.py), и без redact_telegram_bot_token в scheduler_main.py (тот процесс не держит TelegramClient) — оба канала, before_send и before_send_transaction, используют один и тот же обработчик. tests/test_sentry_scrub.py: тесты обеих сторон объединены без потерь — PR-D2 платёжный composed-тест (body-wipe + PII-scrub + token-redaction) и весь блок RetryError fingerprint-стабилизации из main сосуществуют в одном файле. |
||
|
|
c6243b166f |
feat(mera): оферта, политика возврата и утверждённая политика ПДн + реквизиты
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / 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 / 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 56s
Юридический блокер публичного B2C-запуска (G6 в mera-b2c-paid-flow-decision.md: `LEGAL_ENTITY == null` → нет реквизитов, нет продажи) и входное требование модерации эквайера: оферты не было вообще, а privacy-страница в собственной шапке писала, что она НЕ утверждённая политика по ст. 18.1 152-ФЗ. Что сделано: - content.ts: `LEGAL_ENTITY` заполнен (ООО «ПРОЕКТ ФЛЭТ», ИНН/КПП/ОГРН, адреса, директор, банковские реквизиты), добавлены `SUPPORT_EMAIL`, `SERVICE_PRICE_RUB`, пути и короткие публичные URL документов. Единый источник — подвал, оферта, возврат и ПДн рендерят эти поля, а не повторяют строки. ОГРН сверен с открытыми данными ЕГРЮЛ (в исходном сообщении владельца был с опечаткой …028028, верный …028281). - Новые страницы /mera-public/oferta и /mera-public/refund — редакции владельца от 13.08.2026 дословно, плейсхолдер `[адрес электронной почты]` заменён на support@meraocenka.ru. - privacy/page.tsx переписана на утверждённую редакцию с оператором и реквизитами приказа. Сохранены оба инварианта честности: раздел про страницу ввода адреса условен по `PUBLIC_ESTIMATE_ENABLED` (п. 5.4 — пока публичный расчёт выключен, адрес не покидает браузер), срок хранения оплаченного отчёта рендерится из `PAID_REPORT_RETENTION_MONTHS`, а не числом в тексте (test_paid_retention_text_consistency.py). - Caddyfile: короткие адреса /oferta, /refund, /privacy → rewrite на поддерево лэндинга. Именно они напечатаны внутри документов и уйдут в заявку эквайеру. Пути перечислены поимённо — allowlist-by-default периметра не ослаблен. - smoke-mera-perimeter.sh: три новые проверки на короткие адреса. Публикация оферты НЕ включает приём оплаты: платёжного контура в коде нет, `PUBLIC_ESTIMATE_ENABLED` по-прежнему false. Оферта публикуется раньше кнопки намеренно — без неё эквайер не примет заявку. Проверено локально: tsc, next lint, vitest (29), isolation guard, next build (три страницы пререндерены), pytest test_paid_retention_text_consistency (3), caddy validate + adapt (rewrite на месте), рендер всех трёх страниц через next start — реквизиты, почта и цена на месте. |
||
|
|
1f85ef7d4e |
fix(tradein/payments): тело нотификации не течёт в мониторинг и аудит, повторы банка не отбиваются лимитом
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Successful in 3m50s
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 / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
PR-D2 платёжного контура МЕРЫ — закрывает утечки до открытия публичных путей (PR-D3/D4), сам ничего не открывает: _PUBLIC_PATHS (rbac.py), Caddyfile, roles.yaml, auth_session.py не тронуты. - sentry_scrub.py: новая scrub_payment_request_body — вырезает event.request.data целиком для /api/v1/trade-in/payments/* (sentry_sdk 2.64 кладёт полное тело запроса в request.data, send_default_pii=False это НЕ гейтит — тот флаг управляет только куками). Плюс расширен _PII_KEYS: customer_email/customer_phone/pan/expdate/cardid/rebillid/token/terminalkey. - main.py, scheduler_main.py, tgbot_main.py (все 3 точки инициализации sentry_sdk.init в проекте) — тот же обработчик проведён в ОБА канала, before_send и before_send_transaction. Мотивирующий инцидент: на соседнем продукте вчера закрыли только error-канал, transaction остался без обработчика вообще. - ratelimit.py: точный путь notify — свой щедрый SlidingWindowLimiter (3000/60с per-IP, идиома support.py) вместо общего лимитера, но НЕ полное отключение — backstop против шторма запросов остаётся, подпись проверяется уже после разбора тела (PR-D3). Только notify, не checkout (тот с сессией). - request_audit.py: notify — в audit skip-набор (defense-in-depth: middleware внешний относительно rbac_guard и читает сырой X-Authenticated-User — спуфнутый заголовок иначе писал бы фальшивые события с атрибуцией admin). - smoke-mera-perimeter.sh: негативные проверки-канарейки — notify/checkout сейчас закрыты 404 (meraocenka.ru, Caddy не проксирует) и 401 (gendsgn.ru, rbac ещё не открыл) с обеих сторон периметра. Тесты: scrub на произвольной глубине + payment-path body-wipe, AST-разбор (не substring — комментарии в этих же файлах сами упоминают before_send_transaction) на проводку обоих каналов во всех точках инициализации, 400 запросов notify без единого 429 + контроль что общий лимитер по-прежнему активен на других путях, notify вне user_events даже со спуфнутым X-Authenticated-User: admin. |
||
| 55254b43e4 |
feat(mera/b2c): публичный периметр на отдельном домене meraocenka.ru — этап 1 из 8 (#2545)
Открывает публичный site-блок meraocenka.ru с allowlist-by-default: разрешены только корень лэндинга, его поддерево и _next/static, всё остальное 404. B2B-контур gendsgn.ru не затронут. DNS A-записи для всех трёх доменов на 46.173.16.127 подтверждены до мержа. |