Files
houseplan-card/docs/reviews/SPEC-REVIEW-432-r1.md
T
Codex d4dd027b0a build: prepare v1.71.0-beta.2 candidate
Issue: #426
Issue: #427
Issue: #428
Issue: #431
Issue: #432
Issue: #434
User-Visible: no
2026-09-03 15:23:40 +03:00

195 lines
17 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-432-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/432
- ТЗ: `docs/specs/432-asset-resolve-authorization-cache.md`
- Материал ревью: SHA `17a1c10bef67ecd6235d36e324416e58142f3e11` (HEAD ветки на момент ревью, коммит `docs(spec): define bounded asset resolution`, дерево ветки `issue/432-asset-resolve-authorization-cache`)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4 (лимит для полного трека — 4; лёгкий/короткий трек не применяется, трек полный)
- Вердикт: **зелёный**
## Скоуп ревью
Первый заход ревью ТЗ для issue #432 (security/performance баг: `houseplan/assets/resolve`
без проверки прав и без ограничения стоимости хеширования; тот же дефект стоимости у
`HouseplanContentView.get()`). Аналитика зафиксировала полный трек (два endpoint/модуля,
публичный контракт доступа и стоимость файловых операций меняются — критерии `small`
не выполняются, это явно названо в комментарии аналитики). Владелец ответил на
единственный продуктовый вопрос (Q1: что видит non-admin при `admin_only`) до написания
ТЗ; ТЗ фиксирует принятый Default. Ревью — по `PROCESS.md` §2.4 и §7.1, разбор полный
(первый заход, раздел «Унаследовано» не применяется).
## Как проверялось
1. `docs/SCOPE.md` — сценарий и персоны сверены с J1 (живой обзор), J4 (онбординг/каталог)
и J6 (устойчивость интеграции); особо — «View mode is the product for two of the three
personas», что прямо мотивирует контракт non-admin в ТЗ.
2. `AGENTS.md`, `PROCESS.md` §1, §2.3–2.4, §5, §7.1, §7.2 — формат ТЗ, класс изменений
(класс C, документ, `Issue:#432`/`User-Visible: no` в коммите `17a1c10b` — сверено
`git show --stat`), обязательные разделы, лимит циклов, формат вердикта.
3. Тело issue #432 и все 4 комментария (аналитика, вопрос Q1, решение владельца по Q1,
хендофф ТЗ на ревью) прочитаны целиком.
4. Код на этом SHA прочитан против каждого фактического утверждения ТЗ, не поверх:
- `custom_components/houseplan/websocket_api.py:1127–1161` — `ws_assets_resolve`
подтверждён: нет `_check_write`, нет `_runtime()`, полный `read_catalog(root)` +
`path.read_bytes()` + SHA-256 на совпавшую строку каталога;
- `custom_components/houseplan/http_api.py:157–216` — `HouseplanContentView.get()`
подтверждён: полный `read_bytes()` + SHA-256 на каждый GET `assets`, `immutable`
заголовок не ограничивает повторные запросы;
- `custom_components/houseplan/auth.py:16–31` — `may_write()` подтверждает точную
семантику writer/read-only, которую ТЗ использует в AC1–AC3;
- `custom_components/houseplan/decor_assets.py:353–408` — `asset_refs()`,
`read_catalog()`, `public_asset()` существуют и имеют заявленную сигнатуру;
`asset_refs()` действительно покрывает единственное место использования
`asset_id` в конфиге (перепроверено по `import_export.py`, `validation.py` —
других держателей `asset_id` в config нет);
- `custom_components/houseplan/const.py` — квоты 200 файлов / 256 МиБ / 2 МиБ и
`DECOR_ASSETS_API_VERSION = 1` подтверждены, совпадают с заявленным в ТЗ §6/§10;
- `custom_components/houseplan/store.py:78–92` — `write_lock`/`upload_lock`
существуют на `HouseplanData`, паттерн `async with rt.write_lock` уже используется
для похожего authoritative snapshot в `ws_assets_list` — контракт §7.3 технически
реализуем без изобретения нового примитива.
5. Сверены смежные документы: `docs/specs/051-custom-decor-images.md:323` — оригинальный
контракт `houseplan/assets/resolve` действительно зафиксирован как `authenticated
read` (не writer-only); `docs/specs/131-readonly-cold-start.md` — подтверждает, что
read-only View обязан быть визуально полным, что обосновывает Default-решение по Q1.
`docs/CONFIG-COMPATIBILITY.md:170` — запись про #432 добавлена и указывает на верный
файл ТЗ.
6. Проверено использование `resolveDecorAssets()` (`src/decor-assets.ts`) обеими
поверхностями — `src/houseplan-card.ts` и `src/space-card.ts` — что подтверждает
заявление ТЗ §11 о parity full/space card и наличие существующего frontend unit
теста `test/decor-assets.test.mjs`, на который ТЗ ссылается как на доказательство
для read-only View (AC1 покрывается backend-контрактом + этим тестом, а не новым
frontend-тестом).
7. `scripts/mutation-gate.mjs` — подтверждено, что реестр уже содержит мутанты для
`custom_components/houseplan/websocket_api.py` с backend pytest guard'ами (например,
строки 146–179), то есть план ТЗ §14/AC11 зарегистрировать постоянных свидетелей для
backend-защит — не изобретение нового механизма, а использование существующего.
8. Проверены обязательные разделы §7.1 PROCESS.md построчно (см. таблицу ниже) и
однозначность/доказуемость каждого AC1–AC11.
9. Дешёвые гейты не перегонялись: коммит `17a1c10b` — чистый docs-diff (`docs/specs/
432-asset-resolve-authorization-cache.md` + одна строка в `docs/specs/README.md`),
подтверждено `git show --stat`; Validate на этом SHA зелёный (см. ссылку в задании).
Для документа спецификации без изменений в `src/**`/`custom_components/**/*.py`
`typecheck`/`test`/`build`/`check-docs`/инварианты модели не относятся к предмету
ревью этого этапа — само содержимое ещё не код, а его читаемость и доказуемость.
## Проверка §7.1 (обязательные разделы) и однозначность AC
| Раздел §7.1 | Есть в ТЗ | Где |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1 |
| Что человек увидит до/после | ✅ | §2 |
| Проблема | ✅ | §3, подтверждена кодом (см. выше) |
| Скоуп / не-скоуп | ✅ | §5 / §6 |
| Контракт поведения | ✅ | §7–§10 (доступ WS, GET, cache, ошибки/совместимость) |
| UX | ✅ | §11 — явно «новых контролов, текстов… нет» |
| Модель данных и миграция | ✅ (кратко, по существу — миграции нет) | §10 «Ошибки и совместимость», §20 (cache не persisted) |
| i18n | ✅ | §11 |
| AC1…ACn с доказательством | ✅ | §13, каждый AC помечен способом доказательства (`backend/HA`, `backend/unit`, `review/docs`, `mutation gate`) |
| План автотестов | ✅ | §15, 8 пунктов, включая явный список implementation-гейтов |
| Риски | ✅ | §17, 6 рисков со смягчением |
| Откат | ✅ | §18 |
| Release-артефакты | ✅ | §19 |
Раздел «Модель данных и миграция» не вынесен отдельным заголовком, а распределён между
§10 и §20 — содержательно раздел закрыт (нет schema/capability migration, cache
memory-only и не persisted), структурно это Low, не блокирует (см. «Находки»).
Обязательная по правилу #435 таблица защитных доказательств присутствует (§14),
третий столбец «чем краснеет» заполнен для каждой строки конкретной мутацией и
наблюдаемым эффектом — не общей фразой.
## Проверка отсутствия непомеченных догадок
Каждое фактическое утверждение о текущем поведении кода в ТЗ (§3, §7.1, §9.3, ссылки на
`may_write`, `asset_refs`, `read_catalog`, `write_lock`, квоты, capability-версию,
контракт #51 «authenticated read», обязательность read-only View по #131) сверено с
реальным кодом/документами выше и подтвердилось. Технические решения, для которых
однозначного prior art нет (например, точный состав cache signature `size + mtime_ns +
ctime_ns`, выбор между fail-dark и одной повторной попыткой, место хранения cache —
`hass.data` либо runtime-сервис), явно вынесены в §20 «Принятые технические
предположения» с пометкой «ревьюер вправе оспорить» — ни одно не выдано за факт.
Продуктовый вопрос (Q1) задан владельцу отдельно и заранее, до написания ТЗ, что и
требует правило «не бывает сложной задачи без единого открытого вопроса» — вопрос был,
он закрыт до этапа ревью, что для ревью ТЗ корректно (открытых продуктовых вопросов
к моменту сдачи ТЗ быть не должно).
## Находки
Нет находок уровня High или Medium.
**Low (не блокирует, зафиксировано без правки).**
1. Раздел «модель данных и миграция» из обязательного списка §7.1 PROCESS.md не выделен
отдельным заголовком, а распределён по §10/§20. Содержание присутствует и
исчерпывающее (нет миграции, cache не persisted), поэтому это вопрос структуры
документа, а не пропущенное решение. Снимается без правки: следующий автор того же
ТЗ увидит прецедент, что содержание важнее буквального оглавления, когда факт «нет
миграции» явно закрыт в другом месте того же документа.
## Что проверено и корректно
- Полная grounding-проверка технических утверждений ТЗ против фактического кода
(`websocket_api.py`, `http_api.py`, `auth.py`, `decor_assets.py`, `const.py`,
`store.py`) — расхождений не найдено.
- Product-рамка: сценарий и «что человек увидит» отвечают на оба обязательных
продуктовых вопроса, персона и поверхность названы, соответствие J1/J4/J6 по
`docs/SCOPE.md` подтверждено, включая явную ссылку на инвариант read-only View (#131).
- Решение владельца по Q1 корректно перенесено в контракт (§4, §7.3) без искажения:
read-only видит только referenced-assets, writer — полный каталог, GET не меняется.
- AC1–AC11 однозначны, у каждого назван способ доказательства; для защитных AC (AC2,
AC3, AC5, AC6, AC7, AC9) заполнена обязательная по #435 таблица «чем доказан / чем
краснеет» с конкретной мутацией, а не общей фразой.
- Не-скоуп (§6) корректно отделяет эту задачу от смежных: quota/upload-валидация,
writer-only GET, config schema migration, frontend/i18n, общий cache для других
типов файлов — явно исключены и не проросли в контракт.
- Откат (§18) явно запрещает «тихо» отключать security/performance защиту через флаг —
соответствует духу standing rule о необратимых действиях.
- Release-артефакты (§19) требуют оба changelog, обновление ARCHITECTURE.md и
CONFIG-COMPATIBILITY.md, что уже подтверждено записью в README ТЗ на этом SHA.
- Трейлеры коммита `17a1c10b` (`Issue: #432`, `User-Visible: no`) корректны для
docs-only спецификации; `docs/specs/README.md` содержит обратную ссылку на issue.
## Чего не проверял
- Реализацию — код ещё не написан, это этап ревью ТЗ, не код-ревью.
- Полный набор гейтов (`typecheck`/`test`/`build`/`golden`/backend pytest/browser
smoke/`model-invariants`) — не относится к предмету этого этапа: диапазон материала
этого раунда — только `docs/specs/432-*.md` и тело issue, изменений в `src/**` или
`custom_components/**/*.py` в этом коммите нет. Дешёвые гейты на SHA `17a1c10b`
подтверждены зелёным Validate (ссылка в задании), поэтому не перегонялись повторно.
- Осуществимость точной реализации bounded single-flight (потокобезопасность между
executor-потоками HA) — это техническое решение, оставленное автору по правилу §7.1
PROCESS.md («всё, чего пользователь не наблюдает, агенты решают сами»); будет
предметом код-ревью через AC6 и его mutation witness.
- Полноту `scripts/mutation-gate.mjs` записей для AC2/AC5/AC6/AC7 — их ещё нет (ТЗ
только планирует их появление в §14/AC11), поэтому проверять на этом этапе нечего;
это станет предметом код-ревью.
## Материал раунда
- SHA: `17a1c10bef67ecd6235d36e324416e58142f3e11`
- Дерево: `docs/specs/432-asset-resolve-authorization-cache.md`,
`docs/specs/README.md` (запись про #432)
- Ветка: `issue/432-asset-resolve-authorization-cache`
- Первый заход — раздел «Унаследовано из r0» не применяется.
## Вердикт
Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0 → в задаче
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/432-asset-resolve-authorization-cache`, коммит `17a1c10bef67` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `efc2aa18b50775a262a444d5ea1544c588646eba`
```
git log --all --format='%H %T' | grep efc2aa18b507
```
- ТЗ `docs/specs/432-asset-resolve-authorization-cache.md`, блоб `8593bd54ad7d7e8a4d6459949fbb960c34ccddc9`
```
git log --all --find-object=8593bd54ad7d7e8a4d6459949fbb960c34ccddc9 -- docs/specs/432-asset-resolve-authorization-cache.md
```