# Ревью кода: go-1-26-upgrade — триаж

База диффа: `HEAD` (коммит `aa20b22`), изменение целиком в рабочем дереве. Дата
прогона: 2026-08-12.

## Сводка

**Размер, сложность, метка.** Размер — среднее: `tasks.md` 20 шагов в 4 разделах,
8 файлов; `internal/` не тронут ни строкой. Сложность — знакомое: все узлы
названы поимённо до работы, шагов формы «разобраться/выяснить» нет. **Метка
`medium`** (максимум по осям), **режим — по графу**. Триггеры `docs/review.md`
проверены все три группы, ни один пункт не совпал.

**Состояние гейта: ЗЕЛЁНЫЙ.** Подтверждено собственным прогоном триажа, а не
только отчётом прохода: `task gate BASE=HEAD` → exit 0. Все девять шагов зелёные,
включая новый `go-version`. Одно унаследованное замечание `tasks.py`
(`any-audio-source`: цель без задач и без тега `decomposed`) шаг не роняет и к
диффу отношения не имеет.

### План разметки задачи с исходом по каждой теме

| тема | дом | глубина | кто закрывает | исход |
|---|---|---|---|---|
| requirements | `openspec/specs/` + дельта `specs/toolchain/spec.md` | разбор | specs | **закрыта**, 4 находки (S1–S4) + 2 блока наблюдений |
| autotests | `CLAUDE.md`, раздел «Гейт» | — | autotests | **закрыта**, 3 находки (A1–A3) + отчёт гейта |
| conventions | `docs/conventions/` | разбор | code | **закрыта**, 3 находки (C1–C3) + 3 наблюдения ниже порога |
| architecture | `docs/architecture.md` + источник `docs/passport.md` | разбор | basics | **закрыта**, 1 находка (B1) + 4 пункта «дешевле переделать» |
| security | `docs/security.md` | разбор | basics | **закрыта**, находок нет: тема неприменима к диффу целиком |
| operations | `docs/architecture.md` §Эксплуатация + источник `docs/database.md` | разбор | basics | **закрыта**, 1 находка (B3); 4 вопроса из 6 неприменимы |

**Тем без отчёта нет.** Все шесть тем ядра вернули отчёты; своих тем сверх ядра у
проекта нет. Ни одна тема не осталась непроверенной по причине «проход не
запускался».

**Находок на входе:** 13 нумерованных (A1–A3, S1–S4, C1–C3, B1–B3) плюс 15
ненумерованных содержательных пунктов (3 «поведение вне спеки», 5 «границы
спеки», 3 наблюдения ниже порога, 4 «дешевле переделать до мерджа») = **28
позиций**. **Осталось в основных секциях: 6** — 2 блокирующие и 4 «стоит
исправить сейчас». Слито по причине 4 пары, понижено до гипотез 3, уехало в
promote 5, выброшено 6 (все названы поимённо).

### Сигнал о заниженной метке

**Сигнал подан двумя проходами независимо — `code` и `basics`.** Оба назвали одно
и то же: изменение вводит новое понятие (capability `toolchain` — первая, что
описывает не поведение сервиса, а инструмент разработки), новый каталог верхнего
уровня `scripts/` и новый шаг гейта; `design.md` сам записывает нерешённое
натяжение в размещении capability. Оба сказали, что метка `large` дала бы
отдельный проход `review-architecture`, и оба отказались решать за конвейер.

**Провенанс один, приоритет два.** Согласие двух проходов — это одна модель,
высказавшаяся дважды: `confidence` оно не повышает, приоритет повышает. Одна
строка с двумя провенансами, а не два пункта.

**Суждение триажа — факт для человека, не команда конвейеру:**

1. **По записанному правилу разметка верна.** `docs/review.md`, «Триггеры метки»:
   в группе «Крупное здесь» ближайший пункт — «каркас приложения: сборка
   фронтенда, раздача статики и шаг гейта разом» — требует трёх вещей сразу,
   здесь только шаг гейта. В «Незнакомое здесь» не совпал ни один из шести: форма
   решения (сравнение строк `sed`/`awk`, без `go` и без docker) была названа до
   работы. Отрицательный тест пройден: миграции, формата файла, контракта API и
   имени ключа конфига изменение не трогает. `review-scope` не ошибся против
   правила, которое у него было.
2. **Ось, на которую указали проходы, в правиле отсутствует.** Ни один триггер не
   говорит о заведении новой capability и о новом каталоге верхнего уровня. А
   именно эта ось дала **обе блокирующие находки прогона** — обе про канон, а не
   про код. Сигнал верен по существу: правило разметки не видит того, что в этом
   изменении оказалось самым дорогим.
3. **Перезапуска это не требует, метку задним числом не пересматривают.** Тема
   `architecture` дома не лишилась и без отчёта не осталась — её закрыл `basics`
   на глубине «разбор». Честный остаток: тему смотрел проход широкого профиля, а
   не специализированный, и вопрос «правильно ли выбрано имя и дом capability»
   остался без независимого разбора (H-1).
4. **Что с этим делать — не здесь.** Кандидат в правило вынесен в promote (P-5).

---

## 1. Блокирует мердж (2 из 3)

### B-1. Требование «ровно одно вхождение» станет нормой в форме, которая про `Dockerfile` уже неверна, а на `CLAUDE.md` уронит гейт на правдивой строке

- Файл: `openspec/changes/go-1-26-upgrade/specs/toolchain/spec.md:16-18` против
  `scripts/check-go-version.sh:127-131` и `:147-153`; правильные слова уже лежат в
  `openspec/changes/go-1-26-upgrade/design.md:158-163`
- Severity: **major** | Confidence: **high**
- Действие: **развилка**
- Найдено проходами: `specs` (S1 и пункт «Границы спеки»), `basics` (пункт «Рамка
  правила „ровно одно вхождение“»). **Слито триажем по причине:** причина одна —
  требование написано пофайлово единым правилом, а четыре места устроены
  по-разному. Правится одним абзацем.
- **Оракул — три прогона на копии дерева** (копии в scratchpad; рабочее дерево не
  тронуто, `git status --porcelain` до и после совпадает):
  1. второй сборочный слой `FROM docker.io/library/golang:1.26-alpine AS second` →
     **exit 0**. Норма гласит: «Каждое место MUST называть версию ровно один раз.
     Второе вхождение числа в том же месте MUST считаться отказом», и перечень
     мест включает `Dockerfile`. Код нормы не исполняет и исполнять не должен:
     `collect Dockerfile "$(read_dockerfile)" 0` передаёт `strict=0` намеренно.
     Контроль: тот же второй слой с `golang:1.25-alpine` → exit 1, «Dockerfile
     называет несколько разных версий» — то есть совпадение слоёв проверяется,
     единственность нет;
  2. в `CLAUDE.md` дописана правдивая строка `- прежде собирались на Go 1.25; долг
     закрыт задачей go-1-26-upgrade` → **exit 1**, «CLAUDE.md называет несколько
     разных версий»;
  3. в `README.md` дописан блок кода с `# нужен Go 1.26` → **exit 1**, «README.md
     называет версию больше одного раза».
- Последствие. **Со стороны `Dockerfile`** — молчаливое расхождение нормы и кода:
  после архивации нормой станет спека, а не `design.md`. Многослойная сборка с
  двумя `FROM golang:` — законная форма. Ревьюер следующего изменения увидит
  `strict=0`, прочтёт MUST и «починит» скрипт, уронив гейт на рабочем
  `Dockerfile`; обратный исход не лучше — норма останется ложью, на которую
  сошлются. **Со стороны документов** — гарантированный ложный красный:
  `CLAUDE.md` по устройству ведёт историю (раздел «Гейт» прямо говорит «Два
  прежних долга закрыты и здесь названы»), и первая же правдивая запись о прошлой
  версии роняет шаг. По правилу проекта «Что считается сломанным — новый красный
  шаг гейта… чинится прежде любой другой работы» это остановит работу, а
  сообщение «называет несколько разных версий» толкает чинить не скрипт, а
  исторический документ, то есть подделывать запись. `README.md` ловится тем же на
  любом блоке кода с командой установки.
- Почему до мерджа: спека замерзает архивацией, после неё правка MUST — отдельное
  изменение. Сегодня это один абзац.
- **Вопрос человеку:**
  - **Вариант А (дешёвый, ожидаемый).** Развести правило по местам прямо в
    требовании, дословно как уже написано в `design.md:158-163`: единственности
    требовать от `go.mod`, `CLAUDE.md` и `README.md`, а от `Dockerfile` —
    совпадения всех вхождений `FROM golang:`. Плюс сузить рамку для документов:
    правило считает не файл целиком, а помеченную строку стека (или раздел
    «Стек»/«Технологии»). Цена: абзац спеки + `read_doc` начинает читать раздел, а
    не файл — несколько строк скрипта. Сценарий «Одно место называет два разных
    числа» остаётся верным и правки не требует.
  - **Вариант Б (дешевле сейчас, дороже потом).** Развести только `Dockerfile`
    (правка чисто текстовая, кода не трогает), а цену «файл целиком» для
    документов принять осознанно и записать остатком в спеке: «`CLAUDE.md` не
    ведёт истории версий Go; запись о прошлой версии живёт в `docs/review.md`».
    Тогда красный на истории — не сюрприз, а объявленный запрет.

### B-2. Канонический перечень capability назовёт три из четырёх ровно в момент архивации, и промолчит именно о новой

- Файл: `docs/architecture.md:11-26`; `openspec/changes/go-1-26-upgrade/tasks.md`,
  раздел «3. Документы» (в нём `docs/architecture.md` нет)
- Severity: **minor** | Confidence: **high**
- Действие: **инлайн**
- Найдено проходами: `specs` (S3), `basics` (B1). **Дубль по причине, слит;**
  предложение взято более широкое, от `basics`.
- Оракул — поимённые положения, все перепроверены триажем:
  - `openspec/config.yaml:25-26` дословно: «Состояние спек и правило „первая
    задача, трогающая поведение, заводит спеку своей capability“ —
    docs/architecture.md, преамбула». Дом назначен, и он один;
  - `docs/architecture.md:11` дословно: «Заведены три capability:»; `proposal.md`
    заводит четвёртую;
  - прецедент: прошлое изменение правило этот список **тем же коммитом**, что и
    реализацию — `git show 01cc31d -- docs/architecture.md` даёт `-Заведены две
    capability, и каждая описана частично:` / `+Заведены три capability:`;
  - `tasks.md`, раздел 3, содержит ровно два пункта — `CLAUDE.md` и
    `docs/review.md`; обзора архитектуры в нём нет;
  - машинного оракула нет и быть не может: `CLAUDE.md`, «Гейт» — «согласованность
    документов между собой и с кодом — её судят агенты, зовёт их скилл
    `av-dev-docs:healthcheck`, и звать его надо руками».
- Последствие. После архивации в `openspec/specs/` появится четвёртая capability,
  о которой единственный назначенный обзор молчит. Следующий, кто возьмётся за
  версию инструмента сборки, пойдёт по указанному дому, четвёртой строки не найдёт
  и либо заведёт вторую спеку на ту же тему, либо припишет требование в `pipeline`
  — ровно то, от чего `design.md:79-82` отказался. Отказ молчаливый: гейт этого
  класса не ловит по устройству.
- Предложение (инлайн, три правки):
  1. четвёртая строка перечня в `docs/architecture.md` — про `toolchain`, со
     ссылкой на спеку и задачу-источник;
  2. оговорка к преамбуле: сегодня она читается «поведение системы здесь не
     описывается — нормативно оно живёт в `openspec/specs/`», а `toolchain`
     описывает **не** поведение сервиса; без оговорки преамбула становится
     неверной в момент архивации;
  3. пункт в `tasks.md`, раздел «3. Документы», чтобы правка не потерялась.

**Третий слот блокирующих не занят** — кандидатов нет: всё прочее либо не
замерзает мерджем, либо не имеет оракула.

---

## 2. Стоит исправить сейчас (4 из 4)

### N-1. Единственный новый страж проекта не покрыт ничем: следующая правка его регулярных выражений перестанет ловить случай молча

- Файл: `scripts/check-go-version.sh` (весь); `tasks.md:96-111`
- Severity: **major** | Confidence: **high**
- Действие: **развилка** (новая работа, за границей объявленного scope)
- Найдено проходами: `autotests` (A1), `specs` (S4). **Дубль по причине, слит.**
- Оракул (перепроверено триажем):
  - `find . -iname '*check-go-version*'` → ровно один файл, сам скрипт; тестов нет;
  - `grep -rln 'check-go-version' --include='*_test.go' --include='*.bats'
    --include='*test*.sh' .` → пусто;
  - `task gate` прогоняет скрипт ровно на согласованном дереве, то есть проверяет
    один сценарий дельты из четырнадцати — «Версии совпадают»;
  - раздел «4. Проверка» в `tasks.md` перечисляет 11 сценариев, все `[x]`, но ни
    один не зафиксирован ничем, кроме прозы: это разовый ручной прогон, а не
    оракул;
  - положение проекта, которое здесь нарушено, записано: `docs/review.md»,
    «Типовые узлы» → «Любой узел»: «изменённое место покрыто хоть одним
    **проходящим** тестом».
- Последствие. POSIX-sh с разбором четырёх разных форм через `sed`/`awk` — класс
  кода, где правка одного образца ломает смежный случай беззвучно. `go vet`,
  `golangci-lint` и `gofmt` shell не видят; `shellcheck` в гейт сознательно не
  введён. Правка третьего аргумента `collect` или образца `read_doc` снимает
  проверку молча, и заметят это на следующем подъёме версии — примерно через год,
  и ровно тем способом, каким был найден дефект 2026-08-12: образ перестал
  собираться, и этого не увидел никто. Класс — «молчание»: страж перестаёт
  стеречь, не сообщая об этом.
- **Вопрос человеку:**
  - **А. Сейчас, в этом изменении.** Тест-скрипт рядом
    (`scripts/check-go-version.test.sh`) и отдельный шаг гейта: десяток
    мутационных прогонов на временной копии дерева — по одному на сценарий дельты.
    Цена ~100 строк shell плюс шаг Taskfile. Плюс: страж проверен ровно тем
    способом, каким `docs/review.md` велит проверять оракулы.
  - **Б. Задачей урожая, вместе с `shellcheck` (P-1).** Один шаг гейта, гоняющий и
    линтер оболочки, и мутационные прогоны. Плюс: не раздувает изменение, scope
    остаётся заявленным. Минус: между мерджем и задачей страж не проверен ничем, и
    правки в этот промежуток пройдут вслепую.
  - **В. Принять остаток осознанно** и записать строкой в `docs/review.md`,
    «Недоступно проверке» → «Перестали проверять сознательно», с ценой. Плюс:
    честно и бесплатно. Минус: следующий промах этого класса будет уже вторым.

### N-2. Новое машинное правило не попало в единственный индекс механизированного, и следующий проход конвенций будет сверять версии руками

- Файл: `docs/conventions/README.md:50-65`
- Severity: **minor** | Confidence: **medium**
- Действие: **инлайн**
- Найдено проходом: `code` (C3)
- Оракул — поимённое положение конвенций: `docs/conventions/README.md:52-53`
  («Проверяется командами из CLAUDE.md; прозой не дублируется») и `:64-65` («Не
  названное здесь место механизации означает, что проход по конвенциям будет
  добросовестно проверять уже проверенное»). Строка про `docs.py check` в той же
  таблице — прямой прецедент внесения шагов гейта.
- Последствие. Дифф заводит новое машинное правило и не вносит его в индекс,
  объявленный исчерпывающим. Следующий проход по конвенциям и следующий человек
  будут считать согласованность версий непроверенной и сверять её руками. Заодно
  это единственное место в `docs/conventions/`, откуда новый скрипт вообще был бы
  виден: сегодня из дома конвенций он не виден никак.
- Предложение: строка в таблицу — `| Одно число версии Go в go.mod, Dockerfile,
  CLAUDE.md и README.md | Taskfile.yml → шаг go-version
  (scripts/check-go-version.sh) |`.

### N-3. Сломанное окружение шаг объявит расхождением версий и назовёт невиновный файл

- Файл: `scripts/check-go-version.sh:105-119`, `:147-153`
- Severity: **minor** | Confidence: **high** (проход давал `medium`; поднято
  прогоном)
- Действие: **инлайн**
- Найдено проходом: `code` (C1)
- **Оракул — два прогона на копии дерева:**
  1. `chmod a-r README.md && sh scripts/check-go-version.sh` →
     ```
     sed: can't read .../README.md: Permission denied
     check-go-version: README.md не называет версию Go
     Объявленная версия Go по местам:
       go.mod      1.26
       Dockerfile  1.26
       CLAUDE.md   1.26
       README.md   версия не названа
     exit=1
     ```
  2. прогон с `PATH`, где нет `awk` → **exit 127**, код вне словаря вовсе.

  Для сравнения, штатные пути проверены и корректны: нет файла места → 3, лишний
  аргумент → 2, директива `toolchain` → 1, согласованное дерево → 0.
- Последствие. Значения добываются подстановкой команд в **аргументе** (`collect
  README.md "$(read_doc "$readmemd")" 1`), а POSIX теряет код возврата подстановки,
  стоящей в аргументе простой команды. Любой отказ чтения — файл есть, но
  нечитаем; урезанный `PATH`; сломанный апплет busybox — даёт пустой вход,
  `collect` видит `total -eq 0` и печатает утверждение **о содержимом файла** там,
  где сломалось окружение. По словарю, который этот же дифф и расширил, 1 значит
  «дрейф», 3 — «окружение», 4 — «внутренний сбой»; кода 4 скрипт не возвращает ни
  на одном пути.
- **Честная оценка веса.** Ущерб невелик: `sed` печатает свою причину строкой
  выше, так что человек у терминала подсказку видит, а `task gate` различает
  только ноль и не-ноль. Вероятность низкая — CI у проекта нет, гейт гоняет
  владелец на своей машине. Находка остаётся потому, что шаг гейта — источник, на
  который смотрят как на истину, и ложное утверждение о конкретном файле из такого
  источника дороже своей вероятности.
- Предложение: добывать значения через промежуточную переменную с проверкой кода,
  а не в аргументе — четыре места, по одному на источник.

### N-4. Цена отказа от `shellcheck` названа в памятке вчетверо, и вопрос от этого отложится снова

- Файл: `CLAUDE.md:122-123`; то же в `design.md:37-38`
- Severity: **minor** | Confidence: **high**
- Действие: **инлайн**
- Найдено проходом: `code` (C2); сюда же ушла находка `autotests` A3 как замер
  цены.
- Оракул — замер, снятый триажем на этом прогоне:
  - `find . -path ./.git -prune -o -name '*.sh' -print` → ровно два файла:
    `./scripts/check-go-version.sh` и `./docker/entrypoint.sh`; в гейте из них
    один. Остальные три шага гейта — `docs.py`, `tasks.py`, `openspec.py` — Python
    и лежат в плагинах вне репозитория;
  - `shellcheck scripts/check-go-version.sh` → **одно** замечание, SC1007 на
    `root=$(CDPATH= cd -- ... )`, и оно ложное: `CDPATH= ` — идиома очистки
    переменной перед `cd`. Значит заведение линтера стоит одной строки шага плюс
    одной директивы подавления.
- Последствие. Строка «`shellcheck` для скриптов гейта — их четыре, и заводить им
  линтер надо разом» смешивает «скриптов в гейте четыре» с «`shellcheck` применим
  к четырём». Применим он к одному. Следующий прочтёт в памятке цену «надо разом,
  четыре штуки» и отложит вопрос снова — при том что нелинтуемым остаётся ровно
  тот файл, который проход `code` был вынужден разбирать глазами построчно, а
  триаж — прогонять руками.
- Предложение: переписать пункт по факту — «shell-скрипт в гейте один; три
  соседних шага — Python в плагинах. Линтер не заведён; цена ему одна строка шага
  и одно подавление SC1007». Ту же правку в `design.md`.

---

## 3. Гипотезы без доказательства

### H-1. Имя и дом capability `toolchain` замерзают мерджем (понижено: оракула нет)

Из прохода `basics`, «Дешевле переделать до мерджа». После архивации спека уезжает
в `openspec/specs/` насовсем; `design.md` сам признаёт, что при одном требовании
переименовать дешевле, чем расщепить. Вопрос дома шире: четыре однородных шага
набора проверок живут в двух разных домах — у трёх плагинных дома нет вовсе, у
четвёртого есть нормативная спека, и эта асимметрия становится постоянной.

**Почему понижено.** Оракула нет и построить его нечем: это суждение о
правильности имени, а не о поведении. Плюс два смягчающих факта: стадия ревью
дизайна (`specs` + rubric) прошла до кода и её замечания отработаны — вопрос уже
был на столе; `CLAUDE.md`, «Необратимое», имени capability не перечисляет.
Severity снята.

**Остаток честный:** независимого архитектурного разбора у этого вопроса не было —
на метке `medium` отдельный проход не запускается, тему закрывал `basics` широким
профилем. Это и есть содержание сигнала о заниженной метке.

### H-2. Пересборка того же коммита через месяц даст другой `ffmpeg` (понижено: замера нет, строка не из этого диффа)

Из прохода `basics` (B3). `Dockerfile:26` — рантайм-слой `alpine:latest`, а
`Taskfile.yml:93` собирает с `--pull`: два образа из одного коммита с разницей в
неделю несут разные `ffmpeg`. Регрессия конвертации после такой пересборки
выглядит как задачи в `failed` при пустом диффе репозитория, и откат на прежний
коммит её не чинит. Класс тот же, ради которого написан весь новый шаг:
объявленное и собранное расходятся, и сверять некому.

**Почему понижено.** Замера нет — ни одного числа о том, как часто и насколько
меняется `ffmpeg` в `alpine:latest`; снять на этом прогоне нечем. Строка внесена
не этим изменением, только активирована им. `Confidence: medium`.

### H-3. «Два дома у семантики шага» — проверено и не подтвердилось

Из прохода `basics`. Утверждение: семантика шага описана и в `CLAUDE.md` «Гейт», и
в спеке `toolchain`, а `openspec/config.yaml` предупреждает, что «второй дом факта
расходится с первым молча».

**Проверено триажем:** спека `spec.md:93-96` не пересказывает словарь, а
**ссылается** на него — «раздел „Гейт“ в `CLAUDE.md` объявляет словарь общим». Это
уже правильная форма: один дом факта, вторая точка — ссылка. Последствия не
построено, находкой не выводится.

---

## 4. Promote candidates

- **P-1. `shellcheck` шагом набора проверок.** Цена замерена на этом прогоне:
  файлов `.sh` два, в гейте один, единственное сегодняшнее замечание — ложный
  SC1007 на идиому `CDPATH= cd`. Шаг стоит одной строки плюс одной директивы
  подавления.
- **P-2. Род узла «скрипт набора проверок» в `docs/review.md`, «Типовые узлы».**
  Сегодня перечень родов покрывает только рантайм. Скрипт гейта — новый род с
  собственными проверяемыми свойствами: отличает «расхождение» от «сломанного
  окружения», исход есть функция коммита, покрыт мутационным прогоном. Без этого
  рода свойство «изменённое место покрыто хоть одним проходящим тестом» к shell не
  приложено ничем, и находка N-1 в следующий раз опять будет добываться с нуля.
- **P-3. Обёртки `Taskfile` отдают 1, когда скрипта нет, а словарь велит 3.** Из
  S2/B2, слитых по причине. **Но так делают все четыре обёртки** — `docs`,
  `tasks`, `openspec` и новая `go-version`: новый шаг лишь повторил существующий
  рисунок, и дефектом **этого** диффа это не является. Сам скрипт при отсутствующем
  месте выходит корректно — 3 (проверено). Правило, а не правка: привести все
  четыре обёртки к 3 разом либо убрать «или файл не найден» из описания кода 3 и
  объявить `exit 1` нормой для «скрипта шага нет».
- **P-4. Норму «версия внешнего инструмента объявлена числом и сверяется»
  распространить на рантайм-базу образа.** Из H-2. Non-Goal этого изменения
  записан; кандидат в отдельную задачу.
- **P-5. Триггер метки: изменение, заводящее новую capability или новый каталог
  верхнего уровня.** Из сигнала о заниженной метке. Сегодня «Триггеры метки» видят
  только объём и незнакомость формы решения; ось «изменение трогает канон» в них
  отсутствует, а на этом прогоне именно она дала обе блокирующие находки.

---

## 5. Границы покрытия

Секция не сокращается. Без неё формулировка «критичных проблем не обнаружено»
запрещена — и здесь она не употребляется.

### План: темы, дома, глубины

Все шесть тем ядра размечены, у всех есть дом, все закрыты — таблица в сводке.
**Тем без дома нет. Тем без отчёта нет. Своих тем сверх ядра проект не
объявляет.** Глубина «разбор» у пяти тем, у `autotests` глубина не назначалась.

### Какие проходы запускались

На метке `medium`, в режиме «по графу», запускались четыре: `autotests`, `specs`,
`code`, `basics`. Стадия ревью дизайна (`specs` + `rubric`) прошла раньше, до
кода, и её замечания отработаны — на этом прогоне она не повторялась.

### Какие проходы не запускались и почему

- **`review-architecture`** — не запускается на метке `medium` по устройству
  графа; тема `architecture` отдана `basics`. Именно об этом сигнал двух проходов.
- **Проход независимой реализации** — снят из конвейера по стоимости.
- **Проход про идиоматичность языка** — упразднён.

### Что каждый запущенный проход не мог проверить в принципе

Оговорка о происхождении: **сырые выводы, поданные триажу, несут границы прозой, а
не блоком `Coverage of this pass` из контракта.** Перечень ниже восстановлен по
тому, что проходы написали, а не по их charter'ам, — и может быть неполон. Это
отдельная строка деградации.

- `autotests` — не судит содержание кода; видит зелёное/красное и наличие тестов.
  Прогон гейта не собирает образ (намеренно) и не считает покрытие изменённых строк.
- `specs` — судит соответствие кода дельта-спеке и обратно; не судит качество кода
  вне нормы и не проверяет, нужна ли норма вообще.
- `code` — темы `conventions` плюс технический разбор; Go-кода дифф не содержит,
  поэтому `logging.md`, `errors.md`, `config.md` неприменимы поимённо, и разбор
  свёлся к shell, который ни один линтер проекта не видит.
- `basics` — темы `security`, `operations`, `architecture` широким профилем; на
  этой метке заменяет специализированные проходы, а не дополняет их.
- **Триаж не находит ничего нового по определению**: работает с чужими выводами и
  своими прогонами-оракулами. Пропуск любого прохода — его пропуск тоже.

### Неприменимые темы и вопросы — названы, а не пропущены

- **`security`: тема неприменима к диффу целиком.** `internal/` не тронут ни
  строкой. Три вопроса темы из `docs/review.md` адресованы
  `internal/service/transcribe.go` и `internal/metrics` — они не менялись, вопросы
  остаются открытыми и после этого прогона. Периметр сборки дом объявляет вне
  модели: `docs/security.md:233-235`.
- **`operations`: применимы 2 вопроса из 6.** Отказ соседа, повтор и
  одновременность, остановка на середине, наблюдаемость, рост объёма —
  неприменимы: рантайм не меняется. Три вопроса темы из `docs/review.md` к диффу
  неприменимы и остаются открытыми.
- **`autotests`: вопрос темы из `docs/review.md:133-134`** адресован
  job-конвейеру в `internal/`, которого дифф не трогает.
- **`conventions`: вопрос темы** («новая колонка правится во всех четырёх местах»)
  — **колонок изменение не трогает вовсе**; `internal/adapter/repo/pocketbase` не
  изменён ни строкой.

### Что осталось целиком на человеке

**Не проверит ни один проход:**
- `operations`: поведение внешних сервисов под нагрузкой и на границах;
- `operations`: реальный профиль нагрузки;
- `security`: стойкость `ffmpeg` к вредоносному входу.

**Перестали проверять сознательно:**
- `autotests`: разбор вывода настоящего `ffprobe` — решение и цена в
  `docs/adr/ADR-2026-08-11-stub-adapters-in-tests.md`.

**Своё, для этого изменения:** **собираемость образа на объявленной версии** —
требование «Объявленное число — то, на котором проект собирается» прямо оставляет
проверку человеку и запрещает вводить её в набор проверок. Косвенные свидетельства
положительные (`go version` → `go1.26.5`; гейт зелёный; образ `transcriber:dev` в
наличии), но **самой сборки триаж не запускал** — она объявлена сделанной пунктом
`tasks.md` 4.2.

### Каких документов проекта не хватило

- **`docs/review.md`, «Типовые ложноположительные» — есть и непуст (4 пункта), но
  все четыре про рантайм `internal/`.** Дифф его не трогает, поэтому **проектных
  ложноположительных для него не существует вовсе**: отсев вкусовщины шёл по общим
  критериям устава, без проектного входа.
- **`CLAUDE.md`, «Инварианты» — раздел есть, 9 пунктов, и ни один не применим к
  диффу.** Следствие названо прямо: **ни одна находка этого прогона не поднята до
  `critical` по основанию «нарушен инвариант проекта» — сослаться не на что.**
  Ранжирование велось по обратимости, выведенной из механики openspec, и это
  **предположение триажа**, а не записанное правило проекта.
- **`CLAUDE.md`, «Необратимое» — имени capability, состава `openspec/specs/` и
  раскладки `scripts/` в нём нет.** Поэтому «спека замерзает мерджем» — вывод из
  механики openspec, а не проектная норма.
- **`CLAUDE.md`, «Ориентир по размеру порции: не замерялся» — дословно.** Значит
  суждение «объём right-size» опирается на оценку триажа, а не на проектное число.

### Сработавшие потолки

- `basics` — **сообщил сам**: 3 находки при потолке 4, за срезом ничего.
- `code` — **сообщил частично**: «Потолок конвенций 1/4 — срез не сработал». Про
  потолок технической половины не сказал ничего.
- `specs` — **не сообщил потолок вовсе.** 4 находки. **Это находка о прогоне:**
  проход обязан сообщать потолок сам.
- `autotests` — **не сообщил потолок вовсе.** 3 находки. То же.
- **Триаж:** 2 из 3 блокирующих, 4 из 4 «стоит исправить сейчас». Секция «сейчас»
  заполнена под завязку; ничего не выброшено молча.

### Выброшено — поимённо

1. **A2**: вопрос темы про шаг конвейера — к диффу не относится.
2. **A3**: `shellcheck` SC1007 — ложное срабатывание, действующего последствия нет;
   содержание уехало замером в N-4 и P-1.
3. **Порядок шагов в `gate`**: самый дешёвый шаг стоит пятым. Вкусовщина по всем
   трём условиям: поведения не меняет, стоимости следующего изменения не меняет,
   записанной конвенции о порядке шагов в проекте нет.
4. **`proposal.md`: «Внешних зависимостей изменение не трогает»** против переезда
   `smithy-go` из `// indirect` в прямые требования. Версия `v1.27.7` не менялась,
   пакет импортируется в `internal/adapter/recognizer/yandex/s3.go:15` — штатный
   результат заказанного `go mod tidy`. Прозаическая неточность без последствий.
5. **`CLAUDE.md:114-115`** «краснеет с именем недостающего плагина» — новый шаг
   плагином не является. Последствия не построено; чинится вместе с P-3.
6. **`docs/review.md:194-198`**: преамбула журнала не сходится с верхней записью.
   Расхождение приехало предыдущим коммитом, вне диффа.

### Четыре строки, которые не принесёт ни один проход

1. **Решения проекта не сверялись.** `docs/adr/` — процессный документ, прогон его
   не открывает. Для этого изменения это особенно весомо: `design.md` ссылается на
   решения, но ни один проход не открывал `docs/adr/` и не проверял, не
   противоречит ли новая норма уже принятому.
2. **Записанные наблюдения проекта не использовались.** `docs/research/` — тоже
   процессный. Всякое число в этом отчёте снято на этом прогоне и сопровождается
   командой замера.
3. **Поимённая сверка с руководствами по стилю языка не задавалась ни одним
   проходом.** Для этого изменения дыра шире обычного: основной артефакт —
   POSIX-shell, у которого в проекте нет ни конвенции, ни линтера, ни руководства.
4. **Альтернативной реализации, с которой можно сдиффить решения, у конвейера
   нет.** Вопрос «а можно ли было решить это принципиально иначе — например, одним
   `go.mod` как источником истины и генерацией остальных трёх мест» никто не
   задавал.

### Среда прогона

- **Рабочее дерево не тронуто.** Все оракулы добыты на копиях в scratchpad. `git
  status --porcelain` до и после прогона совпадает символ в символ.
- **Запреты `CLAUDE.md` соблюдены:** боевой каталог данных не трогался, боевой
  токен не запускался, в Yandex Cloud не ходили, выкладка не запускалась,
  `testdata` не заводилась, временное — только в scratchpad.
- **Отказов доступа не было.** `deny: Read(./build)` на путях этого изменения не
  сработал (capability названа `toolchain` именно поэтому).
- **Ограничение среды:** подагентам запрещено писать файлы отчётов. Отчёт возвращён
  триажем текстом и записан сюда оркестратором.

---

# Дополнение: перепроверка после отработки B-1

Записано оркестратором после того, как находки триажа были отработаны. Отчёт без
этого раздела сообщал бы о составе прогона неверно.

## Что было сделано по находкам

| Находка | Действие | Исход |
|---|---|---|
| B-1 | развилка → человеку | Выбран вариант А: правило множественности разведено по местам, рамка в документах сужена до раздела стека. Изменило требование и код |
| B-2 | инлайн | Четвёртая capability и оговорка о её природе — в преамбуле `docs/architecture.md`. Ссылка на спеку поставлена шагом синка: до архивации файла нет и `docs.py check` краснеет битой ссылкой |
| N-1 | развилка → человеку | Выбран вариант Б: задачей урожая, вместе с `shellcheck`. Между мерджем и той задачей страж не проверен ничем — названо остатком |
| N-2 | инлайн | Строка в таблице «Механизировано» `docs/conventions/README.md` |
| N-3 | инлайн | Проверка читаемости места: код 3 и сообщение о нечитаемости вместо «версия не названа» с кодом 1 |
| N-4 | инлайн | Цена отказа от `shellcheck` названа по замеру в `CLAUDE.md` и `design.md`; ложный `SC1007` подавлен в скрипте |

## Второй прогон: только проход `specs`

**Полный прогон ревью кода не повторялся.** Правка по B-1 изменила дельта-спеку и
код, поэтому перепрогнан **целенаправленно один проход** — `specs`, владеющий
темой `requirements`, чей дом и изменился. `autotests` заменён собственным
прогоном гейта оркестратором; `code` и `basics` не перезапускались.

**Чем это ограничено, прямо:** технический разбор новых функций `section`,
`has_section` и `collect_doc` независимым проходом **не выполнялся** — их читал
только `specs` в своей оптике (соответствие норме) и оркестратор. Проход `code`
видел прежнюю редакцию скрипта. Триаж второй раз не запускался, поэтому находки
ниже не проходили дедупликации и добычи оракула независимым агентом — оракулы у
них свои, прогонами.

**Разметка не повторялась,** хотя дельта-спека менялась. Причина названа: правка
сузила формулировку одного требования внутри уже размеченной capability, не
меняя ни набора capability, ни периметра узлов, ни списка тем — план тем остался
бы тем же. Это осознанное отступление от правила «дельта-спеки изменились —
повтори разметку», а не пропуск.

## Находки перепроверки — три, все minor, все отработаны

- **S1 закрыта по существу**, а не переформулировкой. Проверено обеими сторонами:
  норма больше не требует единственности от сборочного образа, и код ровно это и
  делает; правдивая историческая строка о прошлой версии в памятке даёт зелёное,
  а второе число внутри раздела стека — красное.
- **F-1.** Таблица `design.md` продолжала велеть «ровно одно вхождение на файл» —
  правило, обратное принятой норме, — и переживала бы мердж как единственное
  описание того, как машина ищет число. Абзац-мотивировка вдобавок стал
  фактически неверен. Переписаны оба.
- **F-2, дороже прочих.** Норма называла начало раздела и молчала о конце.
  Раздел закрывался только заголовком того же уровня, а строка, похожая на
  заголовок, внутри блока кода читалась как настоящий заголовок. Худший исход —
  **ложное зелёное**: если строку версии из раздела убрать, а ниже по файлу
  появится заголовок первого уровня и любое «Go 1.26», шаг добрал бы число из
  чужого места и промолчал. Граница определена требованием и исполнена кодом;
  контрольный прогон подтверждает, что ложное зелёное исчезло — шаг теперь
  честно говорит «версия не названа».
- **F-3.** Отличие «сломанного окружения» от «пропавшей строки» держалось только
  на коде: ни один сценарий его не требовал, автотеста нет, гейт гоняет скрипт
  ровно на согласованном дереве. Записано требованием и сценарием.

## Оракулы перепроверки

Регрессионная батарея — **21 прогон**, все совпали с ожиданием: 4 мутации по
минору и 4 удаления строки версии (по одной на место), директива `toolchain`,
патч в теге, смена базы образа, два слоя одной версии, два слоя разных версий,
дубль внутри раздела, история вне раздела, пропавший раздел, `PATH` без `go`,
запуск из подкаталога, коды выхода 0/1/2/3. Отдельно проверено, что при
нечитаемом файле строка «не называет версию» не печатается ни разу.

Сверх того: `openspec validate --strict` — valid; `task gate` — exit 0;
`task image` пересобран на `golang:1.26-alpine` — exit 0; `shellcheck` на скрипте
чист.

## Что осталось открытым после отработки

- **N-1: страж не покрыт ничем.** Решением человека уехало задачей урожая. Из
  девятнадцати сценариев дельты машина гоняет один — тот, где всё сошлось;
  остальные восемнадцать подтверждены разовыми прогонами. До закрытия той задачи
  правки скрипта идут вслепую.
- **H-2: рантайм-база образа берётся «последней доступной».** Два образа из
  одного коммита с разницей в неделю несут разные `ffmpeg`. Вне границ задачи,
  уезжает урожаем.
- **Сигнал о заниженной метке** остаётся фактом для человека: ось «изменение
  трогает канон» в правиле выбора метки отсутствует, а на этом прогоне именно она
  дала обе блокирующие находки. Кандидат в правило — P-5.
