fix(mera): версия согласия ПДн отстала от новой редакции политики + смоук ждал не тот код
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 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 / backend-tests (pull_request) Successful in 5m3s
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 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 / backend-tests (pull_request) Successful in 5m3s
ДВА ПОСЛЕДСТВИЯ #3436, обнаруженные на прогоне против прода. 1. ДЕПЛОЙ МЕРЫ БЫЛ ЗАБЛОКИРОВАН. В #3436 политика конфиденциальности получила раздел про cookie, то есть новую редакцию, и `PRIVACY_APPROVAL` во фронте стал «№ 2 от 10 сентября 2026 г.». Бэкендовая `_CONSENT_POLICY_VERSION` осталась на «2026-08-13», а между ними стоит гейт `test_consent_text_frontend_sync.py` — он и упал. Job `test` в deploy-tradein.yml падает → `deploy` пропускается по своему `needs.test.result != 'failure'` → прод остался на старом образе фронта, при том что Caddy обновился отдельным пайплайном. Внешне это выглядело как «задеплоилось наполовину»: UTM на редиректе со слэшем починился, а noindex и robots.txt — нет. Гейт сработал ровно как задуман: версия согласия обязана указывать на ту редакцию документа, которую человек реально видел, иначе снимок согласия в trade_in_leads.consent_policy_version подписан не тем документом. Правим версию, а не тест. Согласия, собранные до 10.09, остаются с "2026-08-13" — в этом и смысл хранить версию per-row. 2. СМОУК ЖДАЛ 404 ТАМ, ГДЕ ПРОД ОТВЕЧАЕТ 401. Проверка «карта сайта МЕРЫ не просачивается через B2B-домен» ожидала 404 от allowlist'а site-блока, но корень gendsgn.ru закрыт пилотным basic_auth, и гейт отвечает 401 РАНЬШЕ, чем запрос доходит до allowlist'а. Проверка была написана без прогона против прода — это честно отмечено в её же комментарии — и упала на первом же запуске. Заведён `check_any`: PASS на любом из перечисленных кодов. Здесь допустимы 401 и 404 — оба означают проверяемое («наружу этого адреса нет»), а какой рубеж ответил первым, к предмету проверки отношения не имеет. Жёсткое ожидание к тому же сломалось бы при снятии пилотного гейта. Красная строка осталась там, где ей место: 200 означал бы реальную течь. Проверено: `pytest tests/test_consent_text_frontend_sync.py` — 6 passed; полный сьют бэкенда МЕРЫ локально 5737 passed; `bash -n` на смоуке чист; `check_any` прогнан против живого gendsgn.ru — PASS на фактическом 401. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ
This commit is contained in:
parent
aba07b581f
commit
2220df8741
2 changed files with 49 additions and 8 deletions
|
|
@ -51,6 +51,30 @@ check() {
|
|||
fi
|
||||
}
|
||||
|
||||
# check_any: PASS, если код ответа — ЛЮБОЙ из перечисленных.
|
||||
#
|
||||
# Нужен там, где проверяемое свойство — «этого адреса наружу нет», а каким
|
||||
# именно отказом это выражено, зависит от того, какой рубеж ответил первым.
|
||||
# На gendsgn.ru таких рубежа два: пилотный basic_auth (401) стоит выше
|
||||
# allowlist'а site-блока (404), и какой сработает — вопрос порядка директив, а
|
||||
# не безопасности. Фиксировать один конкретный код значило бы ронять смоук при
|
||||
# снятии пилотного гейта, то есть при изменении, которое к предмету проверки
|
||||
# отношения не имеет.
|
||||
check_any() {
|
||||
local desc="$1" url="$2"
|
||||
shift 2
|
||||
local code expected="$*"
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$url" 2>/dev/null)
|
||||
for want in "$@"; do
|
||||
if [ "$code" = "$want" ]; then
|
||||
echo "PASS: $desc ($url -> $code)"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo "FAIL: $desc ($url -> got '${code:-<no response>}', expected one of: $expected)"
|
||||
fail=1
|
||||
}
|
||||
|
||||
check_post() {
|
||||
local desc="$1" url="$2" body="$3" expected="$4" reject="${5:-}"
|
||||
local out code head_and_body
|
||||
|
|
@ -278,11 +302,21 @@ check "trade-in /api/v1/admin/users — 404 anonymous (несуществующ
|
|||
# закрытый B2B-контур gendsgn.ru — корень gendsgn.ru принадлежит Site
|
||||
# Finder, у него нет своего route на /sitemap.xml под МЕРУ, allowlist-by-
|
||||
# default этого site-блока (как и у meraocenka.ru) отдаёт 404 на всё
|
||||
# незаявленное. NB: не проверено вживую на проде (нет approval на prod-
|
||||
# доступ в рамках этой сессии) — если у Site Finder когда-нибудь появится
|
||||
# СВОЙ /sitemap.xml, ожидание 404 придётся пересмотреть на 200-с-другим-
|
||||
# содержимым.
|
||||
check "gendsgn.ru/sitemap.xml — must NOT serve MERA sitemap (закрытый контур)" "$BASE_MAIN/sitemap.xml" 404
|
||||
# незаявленное.
|
||||
#
|
||||
# ДВА ДОПУСТИМЫХ КОДА, А НЕ ОДИН. Первая редакция этой проверки ждала 404 и
|
||||
# упала на первом же прогоне против прода: корень gendsgn.ru закрыт
|
||||
# пилотным basic_auth, и гейт отвечает 401 РАНЬШЕ, чем запрос доходит до
|
||||
# allowlist'а. Оба ответа означают ровно проверяемое — карты сайта МЕРЫ на
|
||||
# B2B-домене нет; какой именно рубеж сработал первым, к предмету проверки
|
||||
# отношения не имеет, а вот снятие пилотного гейта (оно запланировано)
|
||||
# переключит 401 на 404 и уронило бы жёсткое ожидание на пустом месте.
|
||||
# Красная строка здесь — только 200: он означал бы, что sitemap реально
|
||||
# отдаётся из закрытого контура.
|
||||
#
|
||||
# Если у Site Finder когда-нибудь появится СВОЙ /sitemap.xml, ожидание
|
||||
# придётся пересмотреть на «200 с другим содержимым».
|
||||
check_any "gendsgn.ru/sitemap.xml — must NOT serve MERA sitemap (закрытый контур)" "$BASE_MAIN/sitemap.xml" 401 404
|
||||
|
||||
# 4. gendsgn.ru/api/v1/admin/* отдаёт 401 анониму (auth gate стоит ДО роутинга
|
||||
# в FastAPI — конкретный путь неважен, любой /api/v1/admin/* перехватывается
|
||||
|
|
|
|||
|
|
@ -53,12 +53,19 @@ _PHONE_MAX_DIGITS = 15
|
|||
# только в audit-лог (#2497 TODO, теперь закрыт).
|
||||
#
|
||||
# Значение = дата утверждения политики (PRIVACY_APPROVAL в frontend/src/app/
|
||||
# mera-public/content.ts: «приказом директора № 1 от 13 августа 2026 г.» →
|
||||
# "2026-08-13"), а не дата этого коммита — версия обязана указывать на редакцию
|
||||
# mera-public/content.ts: «приказом директора № 2 от 10 сентября 2026 г.» →
|
||||
# "2026-09-10"), а не дата этого коммита — версия обязана указывать на редакцию
|
||||
# ДОКУМЕНТА, на который согласие фактически ссылается (чекбокс теперь линкует
|
||||
# именно на /mera-public/privacy). test_consent_text_frontend_sync.py проверяет
|
||||
# это соответствие автоматически, так что рассинхронизация здесь падает в CI.
|
||||
_CONSENT_POLICY_VERSION = "2026-08-13"
|
||||
#
|
||||
# Редакция № 2 от 10.09.2026: в политику добавлен раздел 9 «Файлы cookie и
|
||||
# веб-аналитика» (на публичный сайт поставлены Яндекс.Метрика и GA4), прежний
|
||||
# раздел «Контакты» стал десятым. Согласия, собранные до этой даты, ссылаются
|
||||
# на редакцию № 1 и остаются с версией "2026-08-13" — в этом и смысл хранить
|
||||
# версию per-row: снимок согласия должен указывать на тот документ, который
|
||||
# человек видел, а не на текущий.
|
||||
_CONSENT_POLICY_VERSION = "2026-09-10"
|
||||
|
||||
# Снимок точного текста согласия, который видит пользователь при отправке лида
|
||||
# (ПЛОСКИЙ текст — без разметки ссылки на политику, которая в LeadForm.tsx рядом
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue