mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
@@ -0,0 +1,315 @@
|
||||
# CODE-REVIEW-434-r1
|
||||
|
||||
- Issue: #434 — «Полиш аудита v1.71.0-beta.1»
|
||||
- Этап: код-ревью (PROCESS.md §2.7), полный трек
|
||||
- Заход: r1 · блокирующих циклов израсходовано 0/4
|
||||
- SHA материала: `dbda64e89d6b76cabfe57c21f281cb6d5132c257` (HEAD в момент разбора; сверено `git rev-parse HEAD` непосредственно перед выводом)
|
||||
- Диапазон: `origin/dev...HEAD` (5 коммитов: `8a75c53e` спека, `f383d8fd`/`4190db54` документы спек-ревью r1/r2, `cba13621` реализация, `dbda64e8` довесок-мутант)
|
||||
- Ветка ребейзнута конвейером на 1 коммит dev (`a71531ba → dbda64e8`) — разбор проведён полностью, не по дельте, как и требуется для r1.
|
||||
|
||||
## Скоуп
|
||||
|
||||
ТЗ закрывает девять подтверждённых разрывов аудита v1.71.0-beta.1: физический
|
||||
учёт decor-blobs независимо от sidecar (AC1), recovery orphan-загрузки с честным
|
||||
`reused` (AC2), точное явное удаление (AC3), недостающий негативный тест
|
||||
sidecar-без-blob (AC4), capability guard в `houseplan-space-card` + revision-scoped
|
||||
resolve cache (AC5, AC6), честный «текущий» locale gate для danger confirmation
|
||||
(AC7), негативный witness Area snapshot cleanup (AC8), bounded smoke timeouts
|
||||
(AC9), отзыв support-токена из непринятого превью (AC10). Все относятся к Core
|
||||
user jobs `docs/SCOPE.md` (надёжность View/редактора, честность данных, CI-гигиена
|
||||
для Contributor); продуктовой рамки, требующей отдельного вопроса владельцу, не
|
||||
нашёл.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
### Дешёвые гейты (прогнаны лично, SHA выше)
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Typecheck | `npx tsc --noEmit` | 0 ошибок |
|
||||
| Unit (JS) | `npm test` | 1813 тестов, 1812 pass, 1 skipped, 0 fail |
|
||||
| no-new-any | `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | «Новых any нет» (60 добавленных строк в 5 файлах) |
|
||||
| Build + 3 копии бандла | `npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && npm run bundle:sync` | совпадают байт-в-байт; `git status` после ребилда чист |
|
||||
| check-docs (diff трогает `src/**`) | `node scripts/check-docs.mjs` | «Documentation checks passed (7 files, 10 external links)» |
|
||||
| backend pure | `python3 -m pytest tests_backend/test_decor_assets.py -q` | 52 passed |
|
||||
| model-invariants | не запускал | diff не трогает геометрию (rooms/walls/`layout`/`marker.space`/`open_spans`) — не применимо |
|
||||
|
||||
### Отбор смоков (`node scripts/smoke-select.mjs --base origin/dev --head HEAD`)
|
||||
|
||||
Матрица 216 смоков, «широкий» символ — порог >43 смоков. Вывод инструмента:
|
||||
21 «прямое совпадение», 35 «слабая связь», 1 «зарегистрированная связь»
|
||||
(`smoke_french_locale` ← `languageRenderGate`, не запускал — French locale
|
||||
сценарий не задет этой задачей, связь регистрируется как «эта функция широко
|
||||
переиспользуется», не как прямое следствие правки).
|
||||
|
||||
Прогнал целенаправленно:
|
||||
|
||||
- `demo/smoke_danger_confirm_branches.mjs` — прямое совпадение, единственный
|
||||
файл, названный в AC7 и изменённый в диффе. **OK**, все 38 полей `true`.
|
||||
- `demo/smoke_space_card_decor_capability.mjs` (новый файл, AC5) — **OK**,
|
||||
4/4 поля `true`.
|
||||
- `demo/smoke_support_feedback.mjs` (новый файл, AC10) — **OK**, 29/29 полей `true`.
|
||||
|
||||
Остальные 20 «прямых» и 35 «слабых» совпадений — реакция на широко
|
||||
переиспользуемые символы (`_cfgRev`, `_snap`, `_config`, `_confirmDanger`),
|
||||
не специфичные для этой задачи логике; полный прогон всей матрицы — предрелизный
|
||||
гейт (PROCESS §8), не гейт ревью. Golden не запускал — спека прямо говорит
|
||||
«Golden не требуется» (визуальный кадр не меняется), проверено чтением: diff не
|
||||
трогает ни один шаблон рендера кроме `_renderRoot`/`_renderDangerConfirm`,
|
||||
геометрию которых golden не покрывает по замыслу задачи.
|
||||
|
||||
### Backend HA harness — НЕ прогонял (честно, гейт пропущен намеренно)
|
||||
|
||||
`tests_backend/test_ha_websocket.py` содержит новые/изменённые тесты для AC2 и
|
||||
AC3 (`test_decor_asset_upload_deduplicates_and_rejects_mime_spoofing` расширен,
|
||||
`test_decor_asset_delete_removes_exact_orphans_only` новый). Окружение этого
|
||||
ревью — Python 3.12, `pytest-homeassistant-custom-component==0.13.357` требует
|
||||
Python ≥3.14 (проверил: `pip install` дал `ResolutionImpossible`/no matching
|
||||
distribution), `.venv-backend` отсутствует. Это ограничение среды ревью, не
|
||||
находка по коду (AGENTS.md: «только облачные агенты держат харнесс»).
|
||||
**AC2 и AC3 доказаны чтением, не исполнением** — см. ниже.
|
||||
|
||||
### Мутационные свидетели — прогнаны выборочно (`--changed` затронул 109/380, полный прогон непропорционален; проверил вручную 8 новых/изменённых записей, относящихся к этой задаче)
|
||||
|
||||
`node scripts/mutation-gate.mjs --changed --check` — все 109 применимых
|
||||
патчей легли на текущий код (`ok` по каждому, 0 `FAIL`). Живой прогон (патч →
|
||||
пересборка → гард) сделал для мутантов этой задачи:
|
||||
|
||||
| Мутант / AC | Гард | Результат живого прогона |
|
||||
|---|---|---|
|
||||
| `decor-physical-inventory-follows-sidecars` (AC1) | `pytest -k physical_inventory` | применил патч руками → **тест покраснел** (`assert [] == [...]`) |
|
||||
| `decor-catalog-accepts-sidecar-without-blob` (AC4) | `pytest -k valid_shaped_sidecar_without_blob` | применил патч руками → **тест покраснел** (`row is not None`) |
|
||||
| `danger-confirm-uses-last-rendered-language-gate` (AC7) | `smoke_danger_confirm_branches` | применил патч руками → **смок покраснел** на `warmToReadyAllowsBeforeRender` |
|
||||
| `danger-confirm-warm-transition-cancel-removed` (AC7) | `smoke_danger_confirm_branches` | применил патч руками → **смок покраснел** на 3 полях (`readyToWarmCancelsOpenConfirmation`, `warmLanguageGateKeepsControllerEmpty`, `warmLanguageGateRemovesDecisionSurface`) |
|
||||
| `area-cleanup-keeps-candidate-outside-current-snapshot` (AC8) | `node --test -k "absent from the current snapshot"` | применил патч руками → **тест покраснел** (`true !== false`) |
|
||||
| `support-invalid-response-leaks-issued-token` (AC10) | `smoke_support_feedback` | применил патч руками → **смок покраснел** на `invalidPreviewTokenIsDiscardedExactlyOnce` |
|
||||
|
||||
После каждого прогона дерево восстановлено (`git status` чист, `cp` бэкапа
|
||||
исходника обратно + ребилд). Не прогонял живьём: `decor-orphan-repair-runs-after-quota`,
|
||||
`decor-orphan-repair-claims-reuse`, `decor-delete-skips-orphan-blobs` — их гард
|
||||
использует `tests_backend/test_ha_websocket.py`, недоступный в этой среде
|
||||
(см. выше); патчи прошли `--check` (якорь находится ровно один раз в текущем
|
||||
коде), сама логика разобрана чтением ниже.
|
||||
|
||||
## Разбор по AC
|
||||
|
||||
**AC1 — physical inventory.** `physical_asset_blobs()`/`physical_asset_usage()`
|
||||
(`decor_assets.py`) считают файлы через `stat(follow_symlinks=False)` +
|
||||
`S_ISREG` + `suffix in ASSET_EXTENSIONS` + `ASSET_ID_RE.fullmatch(stem)` —
|
||||
директории, symlink, `.json`, неизвестные расширения и prefix-совпадения не
|
||||
входят; новый тест `test_physical_inventory_counts_exact_promoted_blobs_not_sidecars`
|
||||
проверяет все эти случаи одним фикстуром. **Доказано**: pytest прогнан, мутация
|
||||
красит.
|
||||
|
||||
**AC2 — upload recovery.** `http_api.py`: recovery-ветка (`if blob.exists(): ...`)
|
||||
идёт строго ДО вызова `physical_asset_usage()`/проверки квоты — repair не требует
|
||||
свободного слота, как того требует контракт. `existing = read_asset(...)` (valid
|
||||
sidecar + verified blob) возвращает `reused:true` без изменений; orphan (blob без
|
||||
принимаемого catalog row) хешируется, атомарно создаёт sidecar через
|
||||
`tempfile.mkstemp` + `os.replace`, возвращает `reused:false`; digest mismatch
|
||||
кидает `invalid_image` до любой записи. Тест `test_decor_asset_upload_deduplicates_and_rejects_mime_spoofing`
|
||||
покрывает repair-при-полной-квоте (`monkeypatch MAX_DECOR_ASSETS_COUNT=1`) и
|
||||
digest-mismatch (проверяет, что повреждённый blob и отсутствие sidecar
|
||||
остаются нетронутыми, `rejected.status==507`). **Проверено чтением, не
|
||||
исполнением** (харнесс недоступен); тест и код логически согласованы построчно.
|
||||
|
||||
**AC3 — explicit delete.** `websocket_api.py::ws_assets_delete._delete()`
|
||||
перебирает `ASSET_EXTENSIONS` по точному `<aid><ext>`, плюс sidecar; `in_use`
|
||||
проверяется ДО вызова `_delete()` под `write_lock+upload_lock`, так что ничего
|
||||
не удаляется при отказе. Новый тест `test_decor_asset_delete_removes_exact_orphans_only`
|
||||
кладёт orphan `.png`+`.svg`+битый `.json`, соседний `.gif` (неизвестное
|
||||
расширение), `aid+"0".png` (prefix) и директорию `aid.webp` — все три «не
|
||||
трогать» кейса проверяются явно. **Проверено чтением, не исполнением** (харнесс
|
||||
недоступен); код и тест согласованы построчно, включая заявленный `removed`.
|
||||
|
||||
**AC4 — regression sidecar-без-blob.** Продуктовый код не менялся (защита
|
||||
`blob.is_file()` уже была в `_read_catalog_row` на `dev`) — задача добавляла
|
||||
только пропущенный отрицательный тест, ровно как описано в причине #4. **Доказано**:
|
||||
pytest прогнан, мутация (удаление `or not blob.is_file()`) красит новый тест.
|
||||
|
||||
**AC5 — capability guard.** `config-store.ts`: `decorAssetsApi` — только точное
|
||||
`=== 1`, localStorage-seed всегда `null`; unit-тесты `config-store.test.mjs`
|
||||
проверяют апгрейд/даунгрейд/`localStorage`. `space-card.ts`: resolve вызывается
|
||||
только при `snap.decorAssetsApi === DECOR_ASSETS_API_VERSION`; при отсутствии —
|
||||
`_decorAssets` безусловно очищается, resolve не вызывается — подтверждено живым
|
||||
смоком (`oldBackendSkippedResolve`, `downgradeRevokesWithoutResolve`). **Доказано**
|
||||
смоком + unit.
|
||||
|
||||
Отдельно проверил утверждение «принимает capability-only изменение snapshot
|
||||
даже при неизменных config/layout/vLights fingerprints»
|
||||
(`decorAssetsCapabilityChanged` в OR-условии замены `this._snap`, space-card.ts:732)
|
||||
— см. находку Low-1 ниже: код корректен, но заявленный сценарий не
|
||||
верифицирован тем инструментом, который на него ссылается.
|
||||
|
||||
**AC6 — resolve cache.** `resolveDecorAssets()`: `key = JSON.stringify([configEpoch, unique])`,
|
||||
кэш пишется только после успешного цикла (до `resolveCache.set` включая throw —
|
||||
исключение прерывает функцию раньше). Три новых/изменённых unit-теста
|
||||
(`decor-assets.test.mjs`) прогнаны в `npm test`, логика проверена чтением —
|
||||
мутация «убрать epoch из key» ловится тестом «resolve cache is scoped by
|
||||
authoritative config epoch» (прочитан, не мутировал вручную — тривиально
|
||||
следует из кода). **Доказано** unit-тестами.
|
||||
|
||||
**AC7 — danger confirmation.** `_dangerConfirmLocaleGate` стал геттером
|
||||
(никогда не кэшируется), `_confirmDanger` проверяет его на каждый вызов;
|
||||
`willUpdate` отменяет открытый confirm при переходе в `warm` СИНХРОННО до
|
||||
`render()`; `render()`/`_renderRoot()` используют один и тот же call site
|
||||
шаблона, чтобы вложенный `noChange` для тела уживался с независимым удалением
|
||||
`hp-confirm`. Смок прогнан живьём (зелёный, 38/38), и я вручную применил и
|
||||
откатил обе новые мутации реестра — оба раза целевые поля красятся именно так,
|
||||
как заявлено в `because`. **Доказано** смоком + двумя живыми мутациями.
|
||||
|
||||
**AC8 — Area snapshot negative witness.** Продуктовый код не менялся (защита
|
||||
`snapshotBindings.has(binding)` уже была на `dev`) — только новый unit-тест.
|
||||
**Доказано**: тест прогнан, мутация красит именно этот тест.
|
||||
|
||||
**AC9 — bounded smoke execution.** `validate.yml`: job `smoke` получил
|
||||
`timeout-minutes: 20`; каждый файл — `timeout --kill-after=10s 180s node "$f"`,
|
||||
статус 124 печатает отдельную диагностическую строку, `continue` внутри цикла
|
||||
файлов не встречается — падение одного файла не останавливает шард (существующий
|
||||
код, не тронут). `smoke_danger_confirm_branches.mjs` обернул оба
|
||||
German-ожидания (`germanStarted`, `germanCompleted`) в `Promise.race` с 1000 мс.
|
||||
Новый `test/smoke-exception-guard.test.mjs` проверяет все три границы текстовым
|
||||
соответствием YAML — прогнан, зелёный. Живой смок подтверждает, что German-путь
|
||||
укладывается в границы (никакого зависания на реальном прогоне). **Доказано**
|
||||
текстовым контрактным тестом + живым прогоном смока; сам workflow на GitHub
|
||||
Actions не выполнялся (не гейт ревью, а CI-инфраструктура).
|
||||
|
||||
**AC10 — support token cleanup.** `_buildSupportPreview()`: `issuedToken`
|
||||
фиксируется сразу после парсинга ответа (валидация формы `^[0-9a-f]{48}$`), ДО
|
||||
общей проверки hash/format/version/size/spaces/expires/text, которая кидает
|
||||
`support_rejected`; `catch`-блок discard'ит `issuedToken`, если он не был
|
||||
обнулён успешным путём. Malformed/отсутствующий токен никогда не попадает в
|
||||
`issuedToken`. Живой смок зелёный (29/29), живая мутация (снятие discard в
|
||||
catch) красит ровно `invalidPreviewTokenIsDiscardedExactlyOnce`. **Доказано**
|
||||
смоком + живой мутацией.
|
||||
|
||||
**AC11 — совместимость/документация.** Grep диффа не находит изменений схемы,
|
||||
версии API (`DECOR_ASSETS_API_VERSION` остался `1`), i18n-ключей или визуальных
|
||||
шаблонов вне `_renderRoot`/`_renderDangerConfirm` (сам рендер тех же узлов, что и
|
||||
раньше, просто перегруппированный). `docs/ARCHITECTURE.md`, `CONFIG-COMPATIBILITY.md`,
|
||||
`SUPPORT-PRIVACY.md`, `TESTING.md`, оба `CHANGELOG*` изменены в одном коммите
|
||||
`cba13621` с трейлерами `Issue: #434` / `User-Visible: yes`. **Проверено
|
||||
чтением**: diff review + `check-docs` зелёный.
|
||||
|
||||
**AC12 — гейты.** Typecheck/unit/build/bundle-sync/no-new-any/check-docs —
|
||||
зелёные (таблица выше). Backend pure — зелёный. Backend HA — не прогонял
|
||||
(ограничение среды, см. выше, отражено честно). Отобранные смоки — зелёные.
|
||||
Мутационные свидетели — 6/9 новых прогнаны живьём и красят корректно, 3/9
|
||||
(HA-зависимые) проверены `--check` + чтением. Golden/full performance —
|
||||
предрелизные, не гейт ревью (спека сама это утверждает).
|
||||
|
||||
## Находки
|
||||
|
||||
### Low-1 — `decorAssetsCapabilityChanged` в `space-card.ts:711-732` не влияет на
|
||||
наблюдаемое поведение и не верифицируется смоком, на который ссылается спека
|
||||
|
||||
`this._snap.decorAssetsApi` пишется в двух местах (`space-card.ts:733` и `:736-737`
|
||||
через частичное обновление) и читается **только** в вычислении самого
|
||||
`decorAssetsCapabilityChanged` (`:711-712`) — больше нигде в проекте
|
||||
(`grep -n "decorAssetsApi" src/*.ts` даёт ровно 6 строк, все либо запись, либо
|
||||
сравнение с только что полученным `snap`, а не с `this._snap`). Реальная
|
||||
семантика AC5 — вызов/невызов `resolve` и очистка `_decorAssets` — читает
|
||||
**свежий** `snap.decorAssetsApi` напрямую (`:713`, `:740`), а не `this._snap`.
|
||||
Поэтому замена `this._snap` целиком против частичного обновления `rev`/`layoutRev`
|
||||
не производит никакой разницы, которую можно было бы пронаблюдать.
|
||||
|
||||
**Как проверил.** Убрал `|| decorAssetsCapabilityChanged` из условия на
|
||||
`:732`, пересобрал бандл, прогнал `smoke_space_card_decor_capability.mjs`
|
||||
(остался зелёным, 4/4) и отдельным зондом (`page.evaluate`, три `_load()`
|
||||
подряд: null→1→null) убедился, что `_snap.decorAssetsApi` меняется
|
||||
`null → 1 → null` **даже без строки** — потому что `virtualLightsChanged`
|
||||
в этой фикстуре (и, по прослеживанию кода, в любом реальном сценарии, где
|
||||
`fetchFresh()` вообще выполняется — `cache` в `config-store.ts` всегда `null`
|
||||
на входе в `fetchFresh`, значит `virtualLightSnapshot()` каждый раз строит
|
||||
новый объект) уже безусловно `true` ровно тогда, когда снимок вообще меняется.
|
||||
|
||||
`test/space-card-audit-lows.test.mjs` проверяет присутствие строки
|
||||
`decorAssetsCapabilityChanged` в исходнике текстовым `assert.match` — это ловит
|
||||
буквальное удаление термина, но не поведенческую регрессию, и не то же самое,
|
||||
что «тест умеет падать» в смысле PROCESS §2.7 (сам мутационный реестр не
|
||||
содержит записи для этой строки).
|
||||
|
||||
**Не блокирует.** Строка не создаёт неверного поведения (она безвредно
|
||||
избыточна), реальный контракт AC5 (запрет resolve на старом backend, очистка
|
||||
map при даунгрейде, кэш по epoch) доказан отдельно и надёжно. Снимаю как Low с
|
||||
запиской: если авторы захотят вычистить, это чистое упрощение (убрать термин
|
||||
из OR и из текстового теста, ничего не потеряв); можно и оставить как
|
||||
задел на случай, если `_snap.decorAssetsApi` найдёт читателя в будущем.
|
||||
|
||||
### Low-2 — `_dangerConfirmLocaleGate` геттер может вызвать `languageRenderGate()`
|
||||
дважды за один цикл обновления, пока открыт danger confirm
|
||||
|
||||
`languageRenderGate()` (`src/i18n/language-runtime.ts:105`) не чистая функция:
|
||||
мутирует `pendingHosts`/`committedHosts`, дёргает `host.setAttribute`, и — что
|
||||
существеннее — при `state === 'pending'` безусловно подписывает новый
|
||||
`.then()` на `runtime.ensure(code)` при **каждом** вызове, не только при первом
|
||||
вхождении в pending. `willUpdate()` (`houseplan-card.ts:4192-4194`) читает
|
||||
геттер, когда `this._dangerConfirm` истинен, и `_renderBody()` (`:11239`) читает
|
||||
его снова безусловно — при открытом danger confirm это два вызова за один
|
||||
рендер-цикл вместо одного до этой правки (когда значение кэшировалось полем).
|
||||
|
||||
**Не блокирует.** Оба вызова происходят синхронно в одном тике (между
|
||||
`willUpdate` и `render()` нет await), поэтому `runtime.state(code)` не может
|
||||
измениться между ними — итоговое значение геттера согласовано на всём проходе.
|
||||
Лишняя подписка `.then()` на уже существующий (мемоизированный) промис
|
||||
`runtime.ensure()` добавляет не более одного лишнего `requestUpdate()` при
|
||||
разрешении — Lit схлопывает такие вызовы. Открытый danger confirm — не самое
|
||||
частое состояние карточки. Снимаю как Low без правки.
|
||||
|
||||
## Унаследовано / повторная проверка
|
||||
|
||||
Не применимо — это первый заход код-ревью (r1) по этому issue; разделы
|
||||
«Закрытие r(N-1)» и «Унаследовано из r(N-1)» не ведутся согласно PROCESS §2.10.
|
||||
(Два документа `SPEC-REVIEW-434-r1.md`/`-r2.md` в диффе относятся к
|
||||
предыдущему этапу — ревью ТЗ, а не коду; их вердикт не переоткрывал.)
|
||||
|
||||
## Чего не проверял и почему
|
||||
|
||||
- **`tests_backend/test_ha_websocket.py` (полный HA-харнесс)** — среда ревью
|
||||
Python 3.12, пин `pytest-homeassistant-custom-component==0.13.357` требует
|
||||
≥3.14, `.venv-backend` недоступен. AC2/AC3 доказаны чтением построчного
|
||||
соответствия теста и кода (см. разбор выше), не исполнением.
|
||||
- **Полный `npm run golden:verify`** — спека прямо утверждает «Golden не
|
||||
требуется» (нулевая визуальная дельта в устойчивых состояниях); чтением
|
||||
подтвердил, что изменённые рендер-точки (`_renderRoot`) рисуют тот же набор
|
||||
DOM-узлов, что и раньше, в другой обёртке.
|
||||
- **`npm run golden:capture`/`performance_smoke`/полный набор `demo/smoke_*.mjs` (216 файлов)**
|
||||
— предрелизный гейт (PROCESS §8), непропорционален объёму этой задачи; выбрал
|
||||
3 смока, прямо названных в AC и физически изменённых в диффе.
|
||||
- **Полный `node scripts/mutation-gate.mjs`(без `--changed`) — 380 мутантов** —
|
||||
предрелизный гейт по определению самого скрипта («прогон дорогой... его
|
||||
место — перед стабильным релизом»); ограничился `--changed --check` (все 109
|
||||
затронутых патчей применимы) плюс живым прогоном 6 из 9 новых/изменённых
|
||||
записей, относящихся именно к этой задаче.
|
||||
- **`node scripts/model-invariants.mjs`** — diff не касается геометрии
|
||||
(rooms/walls/layout/marker.space/open_spans), гейт неприменим.
|
||||
- **French locale smoke (`smoke_french_locale.mjs`)** — «зарегистрированная
|
||||
связь» через `languageRenderGate`, но сценарий (network/lazy German chunk)
|
||||
не задет этой задачей; решил не гонять.
|
||||
- **35 «слабых» совпадений smoke-select** — общие символы (`_cfgRev`, `_config`
|
||||
и т.п.), не специфичные для этой правки; не гонял, полагаясь на unit/typecheck
|
||||
как более узкое доказательство отсутствия регрессии в этих поверхностях.
|
||||
|
||||
## Итог
|
||||
|
||||
High: 0. Medium: 0. Low: 2, обе сняты с запиской (см. выше), правки не требуют.
|
||||
Все 12 AC доказаны — либо зелёным тестом/смоком с подтверждённой (в 8 из 10
|
||||
защитных случаев — лично прогнанной) способностью падать, либо честной пометкой
|
||||
«проверено чтением, не исполнением» там, где харнесс недоступен в среде ревью.
|
||||
Документация, changelog и трейлеры в порядке. Вердикт: **зелёный**.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/434-v171-polish-audit`, коммит `a71531ba30d2` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `840317519dfa1ae59a2ecf7317ce53bd819c4504`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 840317519dfa
|
||||
```
|
||||
- ТЗ `docs/specs/434-v171-polish-audit.md`, блоб `e235fcef2bcd817c39c0c5a9134fd2ec8f6eb2f1`
|
||||
```
|
||||
git log --all --find-object=e235fcef2bcd817c39c0c5a9134fd2ec8f6eb2f1 -- docs/specs/434-v171-polish-audit.md
|
||||
```
|
||||
Reference in New Issue
Block a user