Issue: #426 Issue: #427 Issue: #428 Issue: #431 Issue: #432 Issue: #434 User-Visible: no
17 KiB
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, разбор полный
(первый заход, раздел «Унаследовано» не применяется).
Как проверялось
docs/SCOPE.md— сценарий и персоны сверены с J1 (живой обзор), J4 (онбординг/каталог) и J6 (устойчивость интеграции); особо — «View mode is the product for two of the three personas», что прямо мотивирует контракт non-admin в ТЗ.AGENTS.md,PROCESS.md§1, §2.3–2.4, §5, §7.1, §7.2 — формат ТЗ, класс изменений (класс C, документ,Issue:#432/User-Visible: noв коммите17a1c10b— свереноgit show --stat), обязательные разделы, лимит циклов, формат вердикта.- Тело issue #432 и все 4 комментария (аналитика, вопрос Q1, решение владельца по Q1, хендофф ТЗ на ревью) прочитаны целиком.
- Код на этом 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 на каждый GETassets,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 технически реализуем без изобретения нового примитива.
- Сверены смежные документы:
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 добавлена и указывает на верный файл ТЗ. - Проверено использование
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-тестом). scripts/mutation-gate.mjs— подтверждено, что реестр уже содержит мутанты дляcustom_components/houseplan/websocket_api.pyс backend pytest guard'ами (например, строки 146–179), то есть план ТЗ §14/AC11 зарегистрировать постоянных свидетелей для backend-защит — не изобретение нового механизма, а использование существующего.- Проверены обязательные разделы §7.1 PROCESS.md построчно (см. таблицу ниже) и однозначность/доказуемость каждого AC1–AC11.
- Дешёвые гейты не перегонялись: коммит
17a1c10b— чистый docs-diff (docs/specs/ 432-asset-resolve-authorization-cache.md+ одна строка вdocs/specs/README.md), подтвержденоgit show --stat; Validate на этом SHA зелёный (см. ссылку в задании). Для документа спецификации без изменений вsrc/**/custom_components/**/*.pytypecheck/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 (не блокирует, зафиксировано без правки).
- Раздел «модель данных и миграция» из обязательного списка §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в этом коммите нет. Дешёвые гейты на SHA17a1c10bподтверждены зелёным 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 → в задаче
Материал раунда
- Ветка:
issue/432-asset-resolve-authorization-cache, коммит17a1c10bef67— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
efc2aa18b50775a262a444d5ea1544c588646ebagit log --all --format='%H %T' | grep efc2aa18b507 - ТЗ
docs/specs/432-asset-resolve-authorization-cache.md, блоб8593bd54ad7d7e8a4d6459949fbb960c34ccddc9git log --all --find-object=8593bd54ad7d7e8a4d6459949fbb960c34ccddc9 -- docs/specs/432-asset-resolve-authorization-cache.md