mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 06:08:59 +00:00
Issue: #426 Issue: #427 Issue: #428 Issue: #431 Issue: #432 Issue: #434 User-Visible: no
195 lines
17 KiB
Markdown
195 lines
17 KiB
Markdown
# 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
|
||
```
|