docs: review document for #434

Issue: #434
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-03 10:55:16 +00:00
parent a2cdd360bc
commit 4190db54ab
+182
View File
@@ -0,0 +1,182 @@
# SPEC-REVIEW-434-r2
- Issue: https://github.com/Matysh/houseplan-card/issues/434
- Этап: ревью ТЗ (PROCESS.md §2.4)
- ТЗ: `docs/specs/434-v171-polish-audit.md`
- Проверяемый SHA: `904a989af70481eb7f34c1e3cfc99de115ba869f` (= `git rev-parse HEAD`,
совпадает с SHA, названным автором в хендоффе r2)
- Заход: r2 · блокирующих циклов израсходовано 1 из 4 (полный трек, лимит 4)
- Вердикт: **зелёный**
## Скоуп раунда
Повторный раунд по §2.9/2.10: предмет разбора — дельта ТЗ между материалом r1
(`5566d6f9898e3c6d21f3e92a2ddf94536c86edc4`) и текущим `904a989a`, а не задача
целиком. Дельта проверена командой:
```
git diff 5566d6f9898e3c6d21f3e92a2ddf94536c86edc4..904a989af70481eb7f34c1e3cfc99de115ba869f -- docs/specs/434-v171-polish-audit.md
```
`git diff --stat` на этом же диапазоне подтверждает, что дельта не касается
ничего, кроме файла ТЗ, — второй изменённый файл в диапазоне,
`docs/reviews/SPEC-REVIEW-434-r1.md`, это публикация документа предыдущего
раунда конвейером (класс C, не предмет разбора).
Дельта локальна: она правит ровно одну тему — контракт job-level
`timeout-minutes` для job `smoke` (пункт 8 / AC9 / раздел 7.7 / «Производительность»
/ «Риски» / таблица «чем краснеет» / тест-план / «Принятые предположения»).
Условия «разбор остаётся полным» (ребейз на ушедший вперёд `dev`, смена
контракта поведения, новая подсистема, объём дельты сопоставим с исходной
задачей) не выполнены: ни ребейза, ни новой подсистемы, дельта — 40 строк
правки внутри уже существующего AC9, без изменения продуктового контракта
(изменение чисто CI/gate, не видимое пользователю). Сокращённый объём разбора
обоснован.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| [Medium, в скоупе] ТЗ утверждало неизменный `timeout-minutes: 20` у job `smoke`, хотя эти 20 минут принадлежат `performance_smoke`, а у `smoke` собственного лимита нет вовсе (дефолт GitHub 360 мин) | Текст ТЗ переписан по всем перечисленным в находке местам: п.8 «Подтверждённые причины», раздел 7.7 «Bounded smoke execution», «Производительность», «Риски», AC9, «чем краснеет» (AC9), тест-план (шаг 8), «Принятые технические предположения». Факт теперь сформулирован верно, и **в скоуп задачи добавлено решение**: job `smoke` получает собственный `timeout-minutes: 20` — новая независимая граница, отдельная от `performance_smoke` | `docs/specs/434-v171-polish-audit.md:73-76` («сама job `smoke` не имеет `timeout-minutes` и потому наследует 360-минутный default… Единственные 20 минут в workflow относятся к другой job — `performance_smoke`»); `:247-250` («Job `smoke` получает собственный `timeout-minutes: 20`: это отдельная граница, которой сейчас нет…»); `:384-389` (AC9 текст); `:420` (таблица «чем краснеет», мутация «удаляет `smoke.timeout-minutes`»); `:446-448` (тест-план п.8); `:470-472` (риски); `:517-519` (принятые предположения) |
Факт перепроверен независимо от заявления автора: `grep -n timeout
.github/workflows/validate.yml` на актуальном HEAD даёт ровно одно
совпадение — строка 715, job `performance_smoke` (заголовок job на строке 705,
`timeout-minutes: 20` на 715). Job `smoke` (заголовок строка 486, цикл
`for f in demo/smoke_*.mjs` строка 554) действительно не содержит
`timeout-minutes` нигде в своём теле. Формулировка ТЗ после правки точна.
## Проверка дельты по существу (не только «текст поменялся»)
Так как дельта меняет именно AC9 (появляется новое обязательство — job-level
timeout), доказательство которого дельта задевает, AC9 разобран заново, а не
унаследован:
- **Согласованность формулировок.** Все шесть затронутых мест (см. таблицу
выше) описывают одну и ту же границу одинаково: 20 минут, независимая от
`performance_smoke`, покрывает весь shard «при системном зависании либо
серии отдельных отказов» (`:470-472`), а не заменяет per-file timeout.
Противоречий между разделами не найдено.
- **Реалистичность значения 20 минут.** Комментарий в самом
`.github/workflows/validate.yml:492-494` фиксирует, что последовательный
прогон всех смоков занимал ~7.5 минут до шардирования на 3 части, то есть
штатный шард сейчас укладывается в ~2.5 минуты — у 20-минутного лимита
восьмикратный запас на холодный раннер, сопоставимый с обоснованием того же
значения у `performance_smoke` (`:711-713`, «15 минут не хватало... холодный
кэш»). Патологический сценарий, где job-level timeout прервёт shard до того,
как последовательно доработают все per-file timeout (215 файлов / 3 шарда
≈ 72 на шард × 190 c ≈ 228 минут в предельном случае «все зависли») —
ТЗ его не скрывает, а называет прямо как ожидаемое поведение защиты «при
серии отдельных отказов», а не как гарантию довести до конца каждый файл.
Технической ошибки или недосказанности здесь нет.
- **Проверяемость AC9 после правки.** «Чем краснеет» (`:420`) называет два
независимых свидетеля — мутацию per-file wrapper (`plain node "$f"`) и
отдельно мутацию `smoke.timeout-minutes` (удаление поля) — и требует, чтобы
workflow-контрактный тест реагировал на оба независимо
(«не находит одну из двух независимых границ»). Это не создаёт пустого
столбца и не путает test-time симуляцию (per-file probe) со статическим
YAML-контрактом (job-level timeout, аналог которому — `test/validate-workflow.test.mjs`,
подтверждённый как рабочий прецедент ещё в r1). Инженерно реализуемо тем же
способом, что и остальной AC9.
- **Затронутые модули учитывают файл workflow.** `docs/specs/434-v171-polish-audit.md:339-340`
прямо называет `.github/workflows/validate.yml` в списке затронутых модулей —
правка AC9 не «повисает» без соответствующего файла в скоупе.
Новых дефектов дельта не вносит.
## Унаследовано из r1
Всё, что не задето дельтой, принимается без повторной проверки — документ
`docs/reviews/SPEC-REVIEW-434-r1.md` (материал: ветка `issue/434-v171-polish-audit`,
коммит `5566d6f9898e`, дерево `1e1861969610794ffa6a9d8458b52ef3695496b8`,
блоб ТЗ `e0229c0cd6550a1c44b978335c52bd2b264b1c52`):
- Обязательные разделы §7.1 присутствуют полностью; продуктовые «Сценарий» и
«Что человек увидит до и после» отвечают на оба обязательных вопроса
(персона/поверхность/момент; видимое изменение без терминов реализации) —
и эти разделы дельтой r2 не тронуты (см. diff — правки только в §7.7,
«Производительность», «Риски», AC9, таблице и тест-плане).
- Восемь из девяти пунктов «Подтверждённые причины» (все, кроме п.8, который
и есть предмет дельты) построчно сверены с `origin/dev` двумя независимыми
агентами и подтверждены дословно, включая: `read_catalog`/`blob.is_file()`
(п.1), отсутствие capability-гарда в `space-card.ts:732` (п.2), кэш по
id-set без ревизии config (п.3), отсутствующий негативный тест «sidecar без
blob» (п.4), `reused:true` без catalog-записи (п.5), locale gate снимок
прошлого рендера (п.6, номера строк помечены как приблизительные), отсутствие
отдельного witness у `snapshotBindings.has(binding)` (п.7), throw до discard
в support preview (п.9).
- AC1–AC8, AC10–AC12 однозначны, способ доказательства назван для каждого;
таблица «чем краснеет» (#435) заполнена без пустых столбцов для всех, кроме
AC9, которую r2 перепроверил заново (см. выше).
- Маршрут (full) обоснован верно — критерий лёгкого трека не проходит из-за
нескольких независимых поверхностей и подсистем.
- Не-скоуп корректно исключает соседние более крупные рефакторинги
(LanguageRuntime/support pipeline/шардирование, изменения schema/API version,
видимого текста/UI).
- Изменение семантики `reused` не является изменением видимого контракта —
поле нигде не читается в `src/**`.
- AC3 (удаление orphan-блобов) не противоречит правилу SCOPE.md «никогда не
удалять файл по предположению»: причина удаления — явный вызов
`houseplan/assets/delete` с точным id, а не вывод из отсутствия ссылок.
- Раздел «Откат» корректно называет границы (миграции нет, восстановленные
sidecar остаются валидными записями); эти строки дельтой не менялись.
- Технические прецеденты (`scripts/mutation-gate.mjs` уже содержит Python-мутанты,
`test/validate-workflow.test.mjs` — рабочий образец YAML-контрактного теста)
подтверждены на `origin/dev` в r1 и остаются в силе — AC9 после правки
использует тот же прецедент.
## Что проверено в r2 (сверх наследования)
- Дельта ТЗ (`docs/specs/434-v171-polish-audit.md`, r1→r2) построчно прочитана
целиком.
- Факт из находки r1 перепроверен заново на актуальном HEAD:
`grep -n timeout .github/workflows/validate.yml` (1 совпадение, строка 715,
job `performance_smoke`); контекст job `smoke` (строки 486–586) и
`performance_smoke` (строки 705–720) прочитан целиком, `timeout-minutes` в
теле `smoke` действительно отсутствует.
- Реалистичность выбранного значения 20 минут сверена с комментарием в самом
workflow-файле (фактическое время шардированного прогона) и числом реальных
smoke-файлов (`ls demo/smoke_*.mjs | wc -l` → 215).
- Согласованность формулировки новой границы across всех шести затронутых
разделов ТЗ.
- `git diff --stat` между SHA r1 и r2 — подтверждено, что кроме ТЗ и
публикации документа r1 больше ничего не менялось (продуктовый код не
затронут, разбор кода в этом раунде не требуется).
## Чего не проверял
- Гейты (`typecheck`/`test`/`build`/backend pytest) не запускались: диапазон
правок — документация класса C (ТЗ + документ предыдущего ревью), кода нет.
- Не проверялся сам код реализации — его по-прежнему нет, это ревью ТЗ.
- Разделы, не затронутые дельтой (см. «Унаследовано из r1»), не разбирались
заново по существу — они наследуются от r1 согласно §2.10.
- Не оценивался точный сценарий частичного отказа (например, ровно 6–7
зависших файлов из 72 в шарде) на предмет того, какие именно файлы успеют
отработать до срабатывания job-level timeout — ТЗ прямо не гарантирует
«довести шард до конца при серии отказов», и это осознанно названо
принятым поведением защиты, а не заявленным AC.
## Материал раунда
- Ветка: `issue/434-v171-polish-audit`, SHA `904a989af70481eb7f34c1e3cfc99de115ba869f`.
- Дельта: `git diff 5566d6f9898e3c6d21f3e92a2ddf94536c86edc4..904a989af70481eb7f34c1e3cfc99de115ba869f -- docs/specs/434-v171-polish-audit.md`.
- Предыдущий раунд: `docs/reviews/SPEC-REVIEW-434-r1.md`, материал —
ветка `issue/434-v171-polish-audit`, коммит `5566d6f9898e`, дерево
`1e1861969610794ffa6a9d8458b52ef3695496b8`, блоб ТЗ
`e0229c0cd6550a1c44b978335c52bd2b264b1c52`.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/434-v171-polish-audit`, коммит `904a989af704` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `37d24151e91b534280e7ce0d4a437cd082afdc30`
```
git log --all --format='%H %T' | grep 37d24151e91b
```
- ТЗ `docs/specs/434-v171-polish-audit.md`, блоб `e235fcef2bcd817c39c0c5a9134fd2ec8f6eb2f1`
```
git log --all --find-object=e235fcef2bcd817c39c0c5a9134fd2ec8f6eb2f1 -- docs/specs/434-v171-polish-audit.md
```