feat(mera/b2c): публичный периметр на отдельном домене meraocenka.ru — этап 1 из 8 #2545
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#2545
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/mera-b2c-perimeter"
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?
⛔ НЕ МЕРЖИТЬ до появления DNS-записей
Требуются A-записи → IP VPS
46.173.16.127для трёх доменов:meraocenka.ru(канонический),merahome.ruиmeraotsenka.ru(301 на канонический).Что будет без них: конфиг синтаксически валиден, перезагрузка Caddy не падает, остальные домены не страдают (автоматический HTTPS изолирован по хостам). Но выпуск сертификата провалится и будет ретраиться с задержкой, а DNS-зависимые смоук-проверки закономерно покраснеют.
Обновление 2026-07-31 — смена домена и починка протухшего инварианта
1. Домен изменён с
mera.gendsgn.ruнаmeraocenka.ru. Поддомен отвергнут: Яндекс считает поддомен отдельным сайтом (отдельная верификация в Вебмастере, отдельное назначение региона) — SEO-работы столько же, сколько у нового домена, но в имени остаётся чужой брендgendsgn. Домены зарегистрированы 31.07.2026 (Beget,paid-till 2027-07-31).2. Добавлены два site-блока с 301 на канонический —
merahome.ruиmeraotsenka.ru. Второй ловит альтернативный транслит «оценка» (ocenka/otsenkaна слух неразличимы), первый — прежний рабочий вариант имени.{uri}сохраняет путь и query.3. Починен протухший инвариант смоука — это важнее смены домена.
Проверка «
gendsgn.ru/trade-in/v2отдаёт 401 анониму» больше не верна: после #2555/#2558 trade-in ушёл с Caddy basic_auth на собственный логин. Проверено живьём:Страница рендерится анониму, а
RouteGuardуводит на/loginуже на клиенте. Гейт данных переехал на API. Проверка заменена на API-уровень, тоже проверено живьём:Если бы смоук поехал в прежнем виде, он был бы красным на ровном месте — а после «починки» под 200 стал бы зелёным при полностью открытом наружу бэкенде.
4. Влит
main(ветка отставала на 3 дня, PR былmergeable: false). Ребейз не использовался — он потребовал бы force-push. Конфликт был один, вRouteGuard.tsx:mainдобавилif (isLoginPage), ветка — bypass публичных путей. Разрешён сохранением обоих, отдельными условиями.Прогон смоука сейчас (4 инварианта безопасности зелёные, DNS-зависимые красные ожидаемо):
tsc --noEmit— чисто. СкобкиCaddyfileсбалансированы (52/52).bash -nсмоука — чисто.Зачем этот этап
Первый из восьми в плане запуска B2C. Открывает периметр без функционала — публичной страницы-заглушки достаточно, чтобы доказать главное: B2B не приоткрылся. API не трогается вообще.
Порядок в плане жёсткий: периметр открывается последним по функционалу, но первым по инфраструктуре — чтобы всё остальное строилось за уже проверенной границей.
Почему отдельный домен, а не путь в текущем
Вся защита сегодня — это один блок
route {}, где импорт basic_auth закрывает всё, что ниже по файлу, а публичность делается исключениями выше. Модель «запрещено всё, кроме дырок», порядко-зависимая — сам файл предупреждает об этом в шапке. Каждая новая дырка это шанс ошибиться матчером и открыть B2B-дашборд вместе с историей чужих оценок.На отдельном site-блоке B2B-маршрутов физически нет:
basic_authне импортирован, разрешены ровно два маршрута, всё остальное —404через финальный catch-all. Открывать нечего.Честное ограничение изоляции
Изоляция получилась на уровне маршрутизации, а не бандла. Next.js App Router не даёт независимый корневой layout без переноса всех существующих маршрутов в отдельную группу — это слишком крупный рефактор для первого этапа. Поэтому:
RouteGuard(тот же приём, что уже используется для превью, но безусловный — страница публична по продуктовому решению);"use client", без единого хука (useMe/useQuota/useHistoryне импортируются), провайдер чата не подключён;Полная изоляция бандла — отдельная задача, если решим, что она нужна.
Смоук периметра — главный артефакт этапа
Остаётся навсегда как регресс-тест. Запускается вручную и ежедневно по расписанию, а не как пре-мерж гейт: проверки требуют живого DNS и выпущенного сертификата, чего в контексте PR быть не может.
Побочный эффект мержа
PR трогает и корневой
Caddyfile, и фронтенд trade-in, поэтому сработают оба деплой-пайплайна. Оба идемпотентны, чекаут общий, порядок не важен.Test plan
tsc --noEmit— чистоbash -nсмоук-скрипта — чистоmain, конфликт разрешёнcaddy fmtлокально не прогнать (Docker не запущен)feat(mera/b2c): публичный периметр на отдельном поддомене — этап 1 из 8to feat(mera/b2c): публичный периметр на отдельном домене meraocenka.ru — этап 1 из 8Блокер снят — DNS есть, мержим
Шапка PR («⛔ НЕ МЕРЖИТЬ до появления DNS-записей») устарела. Проверено сейчас, резолв через 8.8.8.8:
Текущее состояние доменов до мержа — они уже смотрят в прод, но site-блока нет, поэтому:
https://meraocenka.ru/→ TLS handshake fails (сертификат не выпущен, блока нет). Контроль:https://gendsgn.ru/с той же машины в тот же момент → 200/401, т.е. это не локальная проблема.http://meraocenka.ru/→ 401 — запрос проваливается в дефолтную обработку и получает basic_auth от B2B-контура.То есть посетитель публичного домена сейчас видит либо ошибку сертификата, либо окно логина. Это и чинится мержем.
Что изменено в этом заходе
1. Влит актуальный
main(ветка отставала на 37 коммитов). Конфликты — три файла:app/mera-public/layout.tsx,page.tsx(add/add): разрешены в пользуmain. #2615 заменил заглушку этой ветки на полноценный лэндинг с компонентами;RouteGuard.tsx(content): в пользуmain— там уже есть выносGuardedRouteв отдельный чанк и хелперisPublicPath(), константыBASE_PATH/ENABLE_PREVIEWиз старой стороны больше не нужны (логика уехала вGuardedRoute). Проверено грепом, что висячих ссылок на них в файле не осталось.Дифф ветки к
mainпосле мержа — ровно вклад периметра: 3 файла, +226/-0.2. Закрыт разрыв на стыке #2615 и этого PR. Периметр писался под заглушку без подстраниц, а у лэндинга есть
privacy/. Футер ссылается на неё черезnext/link(PRIVACY_PATH = "/mera-public/privacy"), Next сbasePath=/trade-inэмитит/trade-in/mera-public/privacy— такогоhandleв блоке не было, ссылка уходила в catch-all404. То есть обязательный по 152-ФЗ документ был бы недоступен с публичной страницы с первой же секунды после выпуска сертификата.Добавлен
handle /trade-in/mera-public/*— ровно поддерево лэндинга, не/trade-in/*. Плюс два регресс-теста в смоук именно на ширину этого матчера:meraocenka.ru/trade-in/v2и/trade-in/api/v1/meобязаны отдавать 404 (старая проверка/v2без префикса такое расширение бы не заметила).3. Ужесточения по итогам ревью (вердикт ⚠️ MINOR, критичных нет, утечки B2B не найдено):
/trade-in/_next/*→/trade-in/_next/static/*— тот же, что у@uipreview(:78), проверенный в проде. Широкий вариант открывал анонимам/_next/image(оптимизация картинок = CPU по запросу), который лэндингу не нужен: проверено,next/image,<img>иurl()вmera-public/не встречаются вообще, картинок нет, шрифты —next/font/googleself-hosted в_next/static/media;reverse_proxyдобавленheader_up -X-Authenticated-User— единообразие с остальным периметром (:87, :129, :169). Блок не подbasic_auth, значит аноним тем более может прислать свой заголовок. Сейчас инертно (лэндинг статичен), но станет дырой на этапе 5, когда откроется публичный/estimate;/trade-in/_next/image→ 404.Проверено
tsc --noEmitвtradein-mvp/frontend— exit 0bash -nсмоука — чистоCaddyfile— 68/68c8ff2013— combined state success (тестовые джобыskippedпо paths-filter: в диффе нет backend/frontend-кода)handle /— точное совпадение, не префикс; Caddy нормализует путь до матчинга (..,%2e%2e, двойные слэши allowlist не обходят);handleсортируются по специфичности, catch-all не перехватывает разрешённые пути;rewriteбез интерполяции клиентского пути; сателлитные 301 без open-redirect (хост захардкожен, из клиента только{uri})Не проверено
caddy fmtлокально не гонял (Docker не поднят).