mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
@@ -0,0 +1,194 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user