Files
houseplan-card/docs/reviews/SPEC-REVIEW-434-r2.md
T
Codex d4dd027b0a build: prepare v1.71.0-beta.2 candidate
Issue: #426
Issue: #427
Issue: #428
Issue: #431
Issue: #432
Issue: #434
User-Visible: no
2026-09-03 15:23:40 +03:00

183 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```