Files
houseplan-card/docs/reviews/SPEC-REVIEW-43-r5.md
T
2026-09-02 00:05:36 +00:00

198 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-43-r5
- Issue: #43 — «Диалог помощи и обратной связи с обезличенным support report»
- Этап: ТЗ на ревью (PROCESS.md §2.4), полный трек (issue не помечен `small`)
- Заход: r5 · блокирующих циклов израсходовано 3 из 4 до этого раунда
- Материал: `docs/specs/043-private-support-report.md`, `scripts/support-relay/README.md`,
`scripts/support-relay/tests/test_relay.py` на ветке `issue/43-help-feedback`
- SHA материала r4 (названо в самом вердикте r4): `cd8d01ac`
- SHA материала этого раунда (`git rev-parse HEAD` непосредственно перед выводом): `3ce5bc0ea2bab55c7c586d63b78a76459fd5c4d2`
- Предыдущий документ: `docs/reviews/SPEC-REVIEW-43-r4.md`
## Скоуп разбора
Раунд r4 закончился жёлтым вердиктом с единственной находкой уровня Medium (в
скоупе, техническая): §9.2/AC12a/§14.4 описывали чтение `X-Forwarded-For` как
безусловный факт, хотя в коде это работает только под флагом
`HP_RELAY_TRUSTED_PROXY` (default on), а ветка `trusted_proxy=False` не имела
покрытия тестами.
Дельта `cd8d01ac..3ce5bc0e` строго докс- и тест-only:
```
docs/reviews/SPEC-REVIEW-43-r4.md | 210 +++++++++++++
docs/specs/043-private-support-report.md | 21 +-
scripts/support-relay/README.md | 16 +-
scripts/support-relay/tests/test_relay.py | 51 ++++
```
(первый файл — публикация артефакта r4 предыдущим прогоном конвейера, не
предмет этого разбора). Кода продукта (`src/**`, `custom_components/**`) дельта
не касается, новая подсистема не задета, объём несравним с задачей — это
классический случай «предмет раунда — дельта», разбор по ней, а не заново.
## Как проверялось
1. Прочитан полный текст `docs/specs/043-private-support-report.md` заново
(не только изменённые абзацы) — чтобы дельта на 21 строку не спрятала
контекстную рассинхронизацию с остальным документом.
2. Сверено текстовое утверждение §9.2 с фактическим кодом:
`scripts/support-relay/hp_relay/app.py:147-158` (`_source()`) и
`scripts/support-relay/hp_relay/config.py:82` (`trusted_proxy` default) —
поведение обеих позиций переключателя дословно совпадает с новым текстом
ТЗ.
3. Прогнан полный набор relay: `cd scripts/support-relay && python3 -m
unittest discover -s tests -q` → **36 проверок, все зелёные**.
4. Дисциплина «тест умеет падать» применена к новому тесту
`test_direct_node_ignores_the_forwarded_header` лично, а не со слов автора:
в `hp_relay/app.py::_source()` заменено `if service.cfg.trusted_proxy:` на
`if True:` (эмуляция мутации «переключатель проигнорирован») —
`python3 -m unittest tests.test_relay.DirectNodeTestCase -v` упал:
`AssertionError: 3 != 1` (три разных ключа источника вместо одного). Файл
восстановлен из копии, `git diff --stat hp_relay/app.py` после отката —
пусто, повторный полный прогон — снова 36/36.
5. Проверено, что существующий тест `test_client_cannot_pick_its_own_rate_bucket`
(позиция переключателя по умолчанию, `trusted_proxy=True`) не тронут и
продолжает покрывать вторую половину AC12a.
6. `node scripts/process-gate.mjs` — офлайн-прогон на HEAD, диапазон
`origin/dev..HEAD`, 12 коммитов: «гейт пройден, предупреждений 0».
7. Проверены трейлеры обоих коммитов дельты (`git log --format='%B'
cd8d01ac..HEAD`): у обоих `Issue: #43` и `User-Visible: no` — верно, дельта
не меняет пользовательское поведение.
8. Прочитана вся история issue #43 (18 комментариев) для контекста: подтверждено,
что все пять внешних DoR-зависимостей §17 закрыты предыдущими комментариями
владельца (deployment target, канал доставки/credentials, 30-дневное
удаление, staging для CI, ответственный), последний — «`blocked` можно
снимать: внешних зависимостей у реализации больше нет» (2026-09-01 22:04).
Это не переоценивается заново в этом раунде — оно не относится к предмету
дельты (флаг доверенного прокси) и не изменилось между r4 и r5.
## Закрытие раунда r4
| Находка r4 | Чем закрыта | Где видно |
|---|---|---|
| **Medium.** §9.2 описывает чтение `X-Forwarded-For` как безусловный факт, хотя в коде это работает только под флагом `HP_RELAY_TRUSTED_PROXY` (default on); AC12a и §14.4 не называют флаг; нет теста на позицию `trusted_proxy=False`. | §9.2 переписан: флаг назван по имени, обе позиции описаны явно («включён» / «выключен»), добавлено требование «за прокси выключать запрещено» с обоснованием цены (весь публичный эндпоинт делил бы одну корзину). AC12a переписан под обе позиции с указанием двух тестов. §14.4 получил строку про мутацию «читать заголовок независимо от переключателя». Написан и подтверждён тест `test_direct_node_ignores_the_forwarded_header` (не только обещан в тексте — прогнан и лично провален мутацией). | `docs/specs/043-private-support-report.md` §9.2 (абзац «Behind a reverse proxy…»), §13 AC12a, §14.4; `scripts/support-relay/tests/test_relay.py:312-361` (`DirectNodeTestCase`); код без изменений — `hp_relay/app.py:147-158` уже соответствовал описанию. |
Закрыто полностью, без остатка: расхождение между текстом ТЗ и кодом, из-за
которого требование «не пережило бы рефакторинг `app.py`» (как выразился автор
в комментарии к r4), теперь закреплено и текстом, и работающим тестом,
способным упасть.
## Унаследовано из r4 (и через r4 из более ранних раундов)
Со ссылкой на `docs/reviews/SPEC-REVIEW-43-r4.md`, SHA `cd8d01ac`, без повторной
проверки в этом раунде — дельта их не касается:
- §1–8 (сценарий, персона, скоуп, решения владельца, UX-контракт кнопки/диалога/
формы/preview/submit) — полностью проверены в r1 построчно против кода
(`src/houseplan-card.ts`, `src/houseplan-editor-runtime.ts`), включая
соответствие пяти утверждений §3 фактическому состоянию;
- §7 (envelope, allowlist, псевдонимизация, privacy invariant, лимиты) —
проверен в r1 на внутреннюю непротиворечивость allowlist/exclusion списков;
- §8 (backend API preview/submit) — контракт «просмотренные байты = отправленные
байты» (кешированный TTL-токен + mutation-gate) проверен в r1 как спроектированный
в контракт, а не оставленный реализации;
- §9.1, §9.3 (архитектура relay, каналы доставки Telegram/`ha_webhook`, ретеншн) —
полностью разобраны заново в r3 после смены продакшн-канала на боевом стенде
(это была не локальная правка, а смена контракта поведения, разбор шёл целиком,
не по дельте); r4 унаследовал их без изменений, этот раунд — тоже, дельта их
не трогает;
- §10–13 (кроме AC12a), §15–20 (state/compat, touch/a11y §11 с канонической
фразой «Touch editor: supported», i18n, AC1–AC17 кроме AC12a, test plan §14.1–14.3,
performance-бюджеты, риски, §17 DoR-зависимости, §18 release-артефакты, §19
rollback, §20 assumed-блок) — проверены в r1–r3, последняя правка (r4) их не
касалась, эта (r5) — тоже;
- классификация `scripts/support-relay/**` как класс B (`AGENTS.md`/`PROCESS.md`,
`scripts/process-gate.mjs::classify()` regex `/^scripts\//`) — закрыта в r2
чтением regex и живым прогоном гейта, не пересматривалась;
- фактическое закрытие всех пяти пунктов §17 DoR (deployment target, канал
доставки, 30-дневный ретеншн, staging, ответственный) — установлено серией
комментариев владельца в issue между r2 и r3 с воспроизведёнными командами
(`curl .../health`, systemd-таймеры, README) и последним подтверждением
«внешних зависимостей больше нет»; это не предмет спек-ревью ТЗ как текста
(ТЗ корректно описывает эти зависимости как внешние в §17), а операционный
факт вне дельты — принят как есть.
## Находки
**Low, снимается решением ревьюера с записью, цикл не расходует.**
`scripts/support-relay/README.md` (раздел «Тесты»): текст утверждает
«четырнадцать мутаций рабочего кода» и затем перечисляет их в скобках —
пересчёт даёт **тринадцать** пунктов (снять сверку хеша · разрешить лишнюю
часть · не чистить управляющие символы · снять лимит · писать адрес в журнал ·
игнорировать идемпотентность · отключить рубильник · отключить ретеншн · не
проверять секции пакета · брать первый элемент `X-Forwarded-For` · подменить
`source` в вебхуке · приложить пакет к вебхуку · читать заголовок независимо от
переключателя — 13). Расхождение уже было в r4-версии текста (там «тринадцать»
против фактических двенадцати пунктов списка) и в этом раунде перенесено на
единицу дальше вместе с добавлением тринадцатого пункта. Не блокирует: это
описательное число в README для человека, читающего перед раскруткой relay, оно
не служит доказательством ни одного AC (доказательство — сам прогон
`unittest discover`, который лично прогнан и даёт 36/36) и не является тем
«одно число — один источник» пользовательским значением, о котором
предупреждает §8 PROCESS.md — это внутренний devops-README, а не
пользовательский интерфейс. Снимаю без возврата автору; при следующей правке
этого README посчитать пункты заново.
Других находок нет: High — 0, Medium — 0.
## Что проверено и корректно
- Текст §9.2/AC12a/§14.4 после правки дословно соответствует поведению кода
(`_source()` в `app.py`, default в `config.py`) для обеих позиций
переключателя;
- Новый тест `DirectNodeTestCase.test_direct_node_ignores_the_forwarded_header`
реально существует, реально прогоняется, реально падает на релевантной
мутации (проверено лично, не со слов автора) и не задевает остальные 35
проверок;
- Существующий тест на позицию `trusted_proxy=True`
(`test_client_cannot_pick_its_own_rate_bucket`) не тронут дельтой и
по-прежнему проходит;
- Деплой-артефакты (`scripts/support-relay/deploy/env.example`,
`Caddyfile.fragment`) уже фиксируют `HP_RELAY_TRUSTED_PROXY=1` и перезапись
заголовка на обоих продовых хостах — согласуется с новым текстом §9.2 «Both
production hosts run behind Caddy, so both keep the switch on»;
- `node scripts/process-gate.mjs` зелёный (12 коммитов в диапазоне, 0
предупреждений); трейлеры обоих коммитов дельты корректны
(`Issue: #43`, `User-Visible: no`);
- Документ ТЗ остаётся внутренне непротиворечивым за пределами дельты — не
найдено новых противоречий между §9.2 и остальными разделами при повторном
чтении документа целиком.
## Чего не проверял и почему
- `npx tsc --noEmit`, `npm test`, `npm run build` со сверкой копий бандла —
дельта не трогает `src/**`/`custom_components/**`; докс- и Python-тест-онли
изменение не имеет для них предмета. (Ранее на цепочке этого issue Validate
на предыдущих relay-коммитах уже проходил зелёным на CI — здесь новых
фронтенд/бэкенд-в-смысле-Python-интеграции изменений нет вовсе.)
- `node scripts/check-docs.mjs` — требуется только при изменении `src/**`;
здесь его нет.
- `node scripts/model-invariants.mjs` — геометрия/`layout`/ссылки на неё не
затронуты; речь только о rate-limit relay.
- `python -m pytest tests_backend -q` — эта команда покрывает
`custom_components/houseplan/**`, не relay-поддерево; relay использует
отдельный `unittest discover -s scripts/support-relay/tests`, который
прогнан лично (см. выше).
- Браузерные смоки, `golden:verify`, performance-профили — нет UI/визуальных
изменений в этой дельте; продуктовый код (`src/**`) для #43 ещё не начат
(подтверждено комментарием автора «Продуктовый код (`src/**`) не начат — §17
соблюдён» и отсутствием изменений `src/**` во всём диапазоне `origin/dev..HEAD`).
- Повторная проверка §9.1/§9.3 (архитектура каналов доставки, ретеншн) и
внешних DoR-фактов §17 «с нуля» (например, повторный `curl` на
`support.houseplan.tech`/`support-staging.houseplan.tech`) — не предмет этой
дельты; принято по унаследованной цепочке r2→r3 с воспроизведёнными в issue
командами, см. раздел «Унаследовано из r4» выше.
## Вердикт
Единственная Medium-находка r4 закрыта предметно (текст ТЗ + код, который уже
был правильным, + новый работающий и лично провалившийся на мутации тест).
Единственная новая находка этого раунда — Low, косметическая, в devops-README,
не влияющая ни на один AC, снята с записью без возврата автору.
**Зелёный.** ТЗ готово к `S5-ready`. С учётом комментариев в issue все пять
внешних DoR-зависимостей §17 отмечены автором как закрытые — если это
подтверждается конвейером/владельцем, `blocked` может сниматься вместе с
переходом статуса.