From eacaf76d5f0fa5ba5e94dd2b06552e60d20298d9 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Thu, 13 Aug 2026 10:55:38 +0300 Subject: [PATCH] =?UTF-8?q?tasks:=20=D0=B7=D0=B0=D0=B2=D0=B5=D0=B4=D1=91?= =?UTF-8?q?=D0=BD=20=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BE=D0=BA=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D0=BE=20=D0=B3=D0=B5=D0=B9?= =?UTF-8?q?=D1=82=D0=B5,=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=82=D0=B5=20=D0=B8=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - четыре новые записи: проверить шаг migrations так же, как шаг сверки версий Go; свести шесть расхождений между документами канона; запретить обращаться к Bot API мимо клиента бота; разведка о шагах гейта, теряющих предмет - context-cancel-in-pipeline приведена к правде: дописан перечень сделанного попутно, критерий с оракулом «тест на трёх прерываниях подряд» разбит надвое — проверена была только его узкая половина --- tasks/BACKLOG.md | 6 +- .../items/bot-api-only-through-bot-client.md | 50 +++++++++++++++ tasks/items/context-cancel-in-pipeline.md | 37 +++++++++-- tasks/items/docs-consistency-2026-08-13.md | 62 +++++++++++++++++++ tasks/items/gate-steps-subject-guard.md | 34 ++++++++++ tasks/items/migrations-step-norm-and-tests.md | 48 ++++++++++++++ 6 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 tasks/items/bot-api-only-through-bot-client.md create mode 100644 tasks/items/docs-consistency-2026-08-13.md create mode 100644 tasks/items/gate-steps-subject-guard.md create mode 100644 tasks/items/migrations-step-norm-and-tests.md diff --git a/tasks/BACKLOG.md b/tasks/BACKLOG.md index 4f1aef5..6001530 100644 --- a/tasks/BACKLOG.md +++ b/tasks/BACKLOG.md @@ -56,7 +56,7 @@ - [🧹 Покрыть тестами шаги конвейера и захват задачи](items/pipeline-step-tests.md) — Тестовых файлов в проекте два, и оба мимо конвейера: потеря ссылки на файл, двойной ответ пользователю и гонка при захвате не поймаются ничем. - [🧹 Покрыть тестами разбор вывода ffprobe](items/metaviewer-adapter-tests.md) — Проверки приёма перестали звать настоящий ffprobe 2026-08-11, а своего теста у адаптера метаданных нет: разбор JSON и отличие «программы нет в PATH» от «обработка отказала» не проверяет ничто. - [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата. -- [🧹 Прервать шаг конвейера отменой контекста](items/context-cancel-in-pipeline.md) — Воркер читает ctx только между итерациями: остановка контейнера ждёт конца шага, а на занятом писателе один запрос к хранилищу держится до 9,5 секунды при мягком таймауте в 5. +- [🧹 Прервать шаг конвейера отменой контекста](items/context-cancel-in-pipeline.md) — Половина сделана 2026-08-13 — контекст доходит до внешних вызовов, а прерванный шаг оставляет задачу на повтор и не тратит попытку, — но осталось то, ради чего задача заводилась: хранилище контекста не принимает ни одним методом, и бюджет мягкой остановки не замерен. - [🐞 Убирать записанный файл, когда приём отказал на середине](items/orphan-file-on-failed-intake.md) — Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками. - [🧹 Разобрать мелочи слоя хранилища](items/storage-layer-nits.md) — Три мелочи ниже потолка триажа: цикл воркера пишет потерю захвата уровнем ERROR и считает её отказом, тип ошибки заведён там, где конвенция просит sentinel, а FileName несёт два разных смысла. - [✨ Собрать каркас приложения и раздать его из бинарника](items/spa-skeleton.md) — Экранов нет и собирать их нечем: ни сборки фронтенда, ни раздачи статики в проекте не существует. @@ -94,4 +94,8 @@ - [🧹 Закрепить версию рантайм-базы образа](items/pin-runtime-image-base.md) — Финальный слой Dockerfile собирается на alpine:latest, а task image идёт с --pull, поэтому два образа из одного коммита с разницей в неделю несут разный ffmpeg — регрессия конвертации после такой пересборки выглядит как задачи в failed при пустом диффе репозитория, и откат на прежний коммит её не чинит. - [🧹 Настроить конвейер ревью по итогам прогона go-1-26-upgrade](items/review-config-from-go-upgrade.md) — Прогон вскрыл две прорехи настройки: «Типовые узлы» знают только рантайм и не знают рода «проверочный шаг набора проверок», а «Триггеры метки» не видят оси «изменение трогает канон» — и именно она дала обе блокирующие находки. - [🐞 Починить срок сессии, который ставит откат шага входа](items/rollback-restores-wrong-session-duration.md) — Константа defaultAuthTokenDuration в шаге 202608120001 названа умолчанием библиотеки, но 1209600 — это 14 суток, а умолчание PocketBase 432000, пять суток: откат объявляет возврат к умолчанию и ставит срок вдвое больше выбранных владельцем семи. +- [🧹 Проверить шаг гейта migrations так же, как шаг сверки версий Go](items/migrations-step-norm-and-tests.md) — Шаг охраняет critical-инвариант «применённый шаг схемы не переписывается», но своих проверок не имеет: дрейф шаблона имени, переезд каталога или потеря grep в конвейере оставят его вечно зелёным, и это не заметит ничто. +- [🧹 Запретить обращаться к Bot API мимо клиента бота](items/bot-api-only-through-bot-client.md) — Чистка отказа от адреса с токеном живёт в клиенте; свой http.Client в транспорте вернёт утечку молча — правило noctx такую подмену не ловит, а класс уже стоил одного дефекта. +- [🔬 Шаги гейта, у которых правило может потерять предмет](items/gate-steps-subject-guard.md) — У шага migrations страж предмета есть, у шагов docs, tasks и openspec неизвестно: они зовут чужие скрипты из плагинов, и правило, потерявшее файлы, зеленело бы молча. +- [🧹 Свести шесть расхождений между документами канона](items/docs-consistency-2026-08-13.md) — Сверка 2026-08-13 нашла шесть мест, где два документа отвечают на один вопрос по-разному; четыре из них в architecture.md, и по ним читатель строит решения о выкладке и о периметре. - [🔬 Квота по общему размеру загруженного на пользователя](items/per-user-size-quota.md) — Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас. diff --git a/tasks/items/bot-api-only-through-bot-client.md b/tasks/items/bot-api-only-through-bot-client.md new file mode 100644 index 0000000..9495c4d --- /dev/null +++ b/tasks/items/bot-api-only-through-bot-client.md @@ -0,0 +1,50 @@ +# 🧹 Запретить обращаться к Bot API мимо клиента бота + +- **Тип:** chore +- **Категория:** Очередь — Класс уже дал утечку токена; сегодня его держат две проверки на сегодняшних местах, а не правило. +- **Зачем:** Чистка отказа от адреса с токеном живёт в клиенте; свой http.Client в транспорте вернёт утечку молча — правило noctx такую подмену не ловит, а класс уже стоил одного дефекта. + +Токен бота стоит в пути каждого обращения к Bot API, а `http.Client` кладёт +адрес запроса в `*url.Error` целиком. Поэтому 2026-08-13 чистка перенесена на +границу клиента: `internal/adapter/telegram`, `NewBot` — свой `Do` разворачивает +отказ в первопричину, а подменённый логгер вычищает токен из строк самой +библиотеки. Случай записан в журнал дефектов +[review.md](../../docs/review.md), 2026-08-13. + +Держится это на двух стыках, и оба обходятся одной строкой: скачивание записи +берёт клиента у бота (`c.bot.Client.Do`), а не общий, и бот заводится единой +точкой, а не `tgbotapi.NewBotAPI`. Проверки на оба стыка есть +(`internal/controller/tg/download_test.go`, +`internal/adapter/telegram/bot_test.go`), но они судят **сегодняшние** места: +новый файл в транспорте, заведший свой клиент, они не увидят. + +Ступень по лестнице механизации — четвёртая: свойство о структуре, а не о +вызове, и выражается тест-сканером в `internal/archrules`. + +## Затрагивает + +- `internal/archrules` — новое правило-сканер и его перечень предметов; +- `internal/controller/tg` — как поднадзорный пакет; +- `internal/adapter/telegram` — как единственное законное место, где клиент + заводится; +- [go-linters.md](../../docs/conventions/go-linters.md) — строка в перечне + механизированного и снятие соответствующей прозы. + +## Критерии приёмки + +- Свой `http.Client`, `http.DefaultClient` или `tgbotapi.NewBotAPI` в + `internal/controller/tg` роняет проверку и называет файл. Оракул — мутация: + внести каждую из трёх форм по очереди, все три краснеют. +- Законное употребление в `internal/adapter/telegram` проверку не роняет. + Оракул — тот же прогон на нетронутом дереве, зелёный. +- Правило не молчит, потеряв предмет: переименованный или исчезнувший пакет + роняет проверку, а не проходит обходом по пустому перечню. Оракул — мутация с + переименованием каталога. +- Правило записано строкой в `go-linters.md`, и проза, которую оно заменило, + оттуда снята. Оракул — чтение записи. + +## Рамки + +Боевым токеном не запускаться: сканер читает исходники, живого бота ему не +нужно. Внутренний логгер библиотеки правилом не выражается — его сторожит +проверка `TestLibraryLoggerIsActuallyInstalled`, и это остаётся так. diff --git a/tasks/items/context-cancel-in-pipeline.md b/tasks/items/context-cancel-in-pipeline.md index de53c66..30af1b5 100644 --- a/tasks/items/context-cancel-in-pipeline.md +++ b/tasks/items/context-cancel-in-pipeline.md @@ -2,7 +2,7 @@ - **Тип:** chore - **Категория:** Очередь — Отмена контекста трогает те же шаги, что и таймауты: одна окрестность кода, два захода дороже одного. -- **Зачем:** Воркер читает ctx только между итерациями: остановка контейнера ждёт конца шага, а на занятом писателе один запрос к хранилищу держится до 9,5 секунды при мягком таймауте в 5. +- **Зачем:** Половина сделана 2026-08-13 — контекст доходит до внешних вызовов, а прерванный шаг оставляет задачу на повтор и не тратит попытку, — но осталось то, ради чего задача заводилась: хранилище контекста не принимает ни одним методом, и бюджет мягкой остановки не замерен. Долг назван в `docs/architecture.md` — раздел про capability `pipeline`, где отмена контекста посреди шага перечислена среди неописанного. Ревью задачи @@ -16,6 +16,28 @@ задачи держится 9,53 секунды (таймаут занятости хранилища — 10 секунд) и возвращает отличимый отказ, а не «работы нет». +**Сделано попутно 2026-08-13** (коммит `f494dcb`, работа о линтерах и контексте +— задачей не бралась, поэтому карточка и осталась открытой): + +- контекст проложен от воркера и обоих входов до внешних вызовов: `ffmpeg` и + `ffprobe` заводятся через `exec.CommandContext`, SpeechKit и Object Storage + принимают его доводом, скачивание записи идёт запросом с контекстом; +- прерванный шаг приговора не выносит: задача остаётся пригодной к повтору, + попытку не тратит и отправителю о несуществующем сбое не сообщает. Отмена + различается по `ctx.Err()`, а не по ошибке: убитый процесс отдаёт + «signal: killed», и `errors.Is` его с отменой не свяжет; +- воркер не считает остановку отказом и не пишет о ней владельцу; задача не + забирается вовсе, если нас уже остановили; +- принятие операции SpeechKit от отмены защищено своим пределом: потерянный + идентификатор заставил бы повтор оплатить ту же запись второй раз; +- проверки: `internal/service/shutdown_test.go`, + `TestShutdownIsNotAFailure` в `internal/controller/worker/worker_test.go`. + +Из пяти критериев приёмки закрыты два — «оборванный шаг оставляет задачу +пригодной к повтору» и «прерванный шаг не тратит попытку». Открыты три: бюджет +остановки не замерен (хранилище контекста не принимает, и обрывать в нём нечего), +три отмены подряд ни один тест не гоняет, спека `pipeline` отмену не описывает. + ## Затрагивает - цикл воркера `internal/controller/worker`; @@ -32,11 +54,14 @@ - Шаг, идущий дольше мягкого таймаута, обрывается по сигналу остановки. Оракул — тест с шагом, спящим дольше таймаута: остановка укладывается в бюджет, а не доживает до жёсткой. -- Оборванный шаг оставляет задачу пригодной к повтору. Оракул — тот же тест: - после остановки задача не захвачена и выдаётся следующему. -- Отмена не путается с отказом: прерванный шаг не тратит попытку и не уходит в - «мертва». Оракул — тест на трёх прерываниях подряд, счётчик попыток не растёт - до предела. +- Оборванный шаг оставляет задачу пригодной к повтору. Оракул — тест + `TestShutdownDuringConversionKeepsJobRetryable`: после остановки задача не + захвачена, стоит в прежнем состоянии и выдаётся следующему прогону. +- Прерванный шаг не тратит попытку. Оракул — тот же тест: счётчик попыток задачи + после отмены равен тому, что был до захвата. +- Три отмены подряд не уводят задачу в «мертва». Оракул — тест на трёх + прерываниях подряд: счётчик попыток не растёт до предела, состояние задачи не + меняется. - Спека `pipeline` описывает отмену, и раздел `Purpose` её больше не числит неописанной. Оракул — `openspec validate --strict`. diff --git a/tasks/items/docs-consistency-2026-08-13.md b/tasks/items/docs-consistency-2026-08-13.md new file mode 100644 index 0000000..42d3395 --- /dev/null +++ b/tasks/items/docs-consistency-2026-08-13.md @@ -0,0 +1,62 @@ +# 🧹 Свести шесть расхождений между документами канона + +- **Тип:** chore +- **Категория:** Очередь — Находки одной сверки: чинится одним заходом, пока помнится, чем каждое место было найдено. +- **Зачем:** Сверка 2026-08-13 нашла шесть мест, где два документа отвечают на один вопрос по-разному; четыре из них в architecture.md, и по ним читатель строит решения о выкладке и о периметре. + +Находки сверки документов агентами `doc-consistency` и `doc-code-drift`, +прогнанной 2026-08-13 вместе с работой о контексте и токене. К той работе +расхождения отношения не имеют — они старше, и потому не чинились тем же +коммитом. + +Каждое место названо с домом факта, то есть с тем документом, который прав: + +1. **Панель администратора против Authelia.** `architecture.md`, «Открытые + вопросы»: «Панель администратора Authelia не закрывает». `security.md`, + «Периметр»: адрес `/_/` закрывает Authelia на обратном прокси решением от + 2026-08-11. Дом — `security.md`. +2. **Провайдер OIDC не назван внешней зависимостью.** Работает с 2026-08-12, у + него два таймаута в `database.md` и нормативная спека `access`, но в таблице + зависимостей `architecture.md` его нет, а языковая модель числится там + «пятой зависимостью». Дом — `architecture.md`, править его. +3. **«Таймаута нет ни у одного обращения наружу»** (`architecture.md`, + «Эксплуатация») против таймаутов обмена кода у провайдера в `database.md` и + требования спеки `access`. Верно узкое утверждение: таймаутов нет у Telegram, + Object Storage и SpeechKit. Дом — `database.md`. +4. **gin в `README.md`.** Веб-фреймворка нет: HTTP-поверхность — роутер + встроенной PocketBase, и `logging.md` прямо говорит, что вместе с gin ушёл и + `sloggin`. Дом стека — `CLAUDE.md`. +5. **Вид временной метки.** `conventions/database.md`: RFC 3339 с `T`, секундная + точность. `docs/database.md`: `2006-01-02 15:04:05.000Z`, и вид обязателен + побайтово — сравнение в SQLite строковое. Дом — `docs/database.md`; + конвенции нужна строка «*Расхождение:*». +6. **Дубли текста в `CLAUDE.md`** — подавления `hadolint` и настройка + `errcheck` пересказаны там дословно, хотя обе преамбулы договорились, что + дом перечня подавлений — `go-linters.md`. + +## Затрагивает + +- `docs/architecture.md` — «Открытые вопросы», таблица внешних зависимостей, + раздел «Эксплуатация»; +- `docs/conventions/database.md` — вид временной метки; +- `docs/security.md` и `docs/database.md` — как дома фактов, если правка + потребует уточнить формулировку; +- `README.md` — перечень технологий; +- `CLAUDE.md` — раздел «Гейт», два места с пересказом. + +## Критерии приёмки + +- Ни одно из шести мест не отвечает на свой вопрос двумя способами. Оракул — + повторный прогон `av-dev-docs:healthcheck`: перечисленные шесть находок не + возвращаются. +- Провайдер OIDC стоит в таблице внешних зависимостей со своими четырьмя + столбцами отказа, и счёт зависимостей в «Открытых вопросах» сходится с + таблицей. Оракул — чтение таблицы: число строк против числа в тексте. +- Дрейф раскладки и битые ссылки не заведены. Оракул — `task gate` зелёный + (шаг `docs`). + +## Рамки + +Правится текст документов, а не поведение сервиса: если расхождение окажется +дефектом кода, оно уезжает своей задачей, а не чинится здесь. Спеки +`openspec/specs/` не трогаем — у них свой порядок изменения. diff --git a/tasks/items/gate-steps-subject-guard.md b/tasks/items/gate-steps-subject-guard.md new file mode 100644 index 0000000..4bdd8ed --- /dev/null +++ b/tasks/items/gate-steps-subject-guard.md @@ -0,0 +1,34 @@ +# 🔬 Шаги гейта, у которых правило может потерять предмет + +- **Тип:** research +- **Категория:** Очередь — Разведка о чужих скриптах: пока ответа нет, неизвестно даже, есть ли работа. +- **Зачем:** У шага migrations страж предмета есть, у шагов docs, tasks и openspec неизвестно: они зовут чужие скрипты из плагинов, и правило, потерявшее файлы, зеленело бы молча. + +Класс известен и записан: правило, чей предмет исчез, обходит пустой перечень +ноль раз и проходит зелёным. В `internal/archrules` от этого стоит +`TestПакетыПравилСуществуют` — он падает, когда пакет из правила переименован. У +шага `migrations` страж завёлся 2026-08-13: пустой каталог шагов роняет шаг с +кодом 3. + +Чего не знаем: ведут ли себя так же `docs.py check`, `tasks.py check` и +`openspec.py check`. Скрипты чужие — они живут в плагинах `av-dev-docs`, +`av-dev-tasks` и `av-dev-code`, и править их в этом репозитории нельзя. Отсюда и +тип записи: способ починки зависит от ответа. Найдётся страж внутри — делать +нечего; не найдётся — либо обёртка в `Taskfile.yml` со своей проверкой предмета, +либо разговор с владельцем плагина. + +## Вопрос + +Какие шаги гейта проходят зелёными, когда предмет их правила исчез, — и чем это +чинится, если сам скрипт править нельзя? + +## Куда ляжет ответ + +`docs/research/gate-steps-subject-guard.md` — записка с перечнем шагов, снятыми +исходами (по каждому: что сделали с предметом, каким кодом ответил шаг) и +рекомендацией. Исход разведки — задачи на те шаги, где страж нужен и возможен. + +## Рамки + +Скрипты плагинов не правим: они не в этом репозитории. Прогоны идут на временном +клоне репозитория, каталоги `docs/` и `tasks/` рабочего дерева не трогаем. diff --git a/tasks/items/migrations-step-norm-and-tests.md b/tasks/items/migrations-step-norm-and-tests.md new file mode 100644 index 0000000..7cc5afd --- /dev/null +++ b/tasks/items/migrations-step-norm-and-tests.md @@ -0,0 +1,48 @@ +# 🧹 Проверить шаг гейта migrations так же, как шаг сверки версий Go + +- **Тип:** chore +- **Категория:** Очередь — Шаг уже стоит в гейте и уже назван стражем critical-инварианта в двух документах — необеспеченное обещание дороже отсутствующего. +- **Зачем:** Шаг охраняет critical-инвариант «применённый шаг схемы не переписывается», но своих проверок не имеет: дрейф шаблона имени, переезд каталога или потеря grep в конвейере оставят его вечно зелёным, и это не заметит ничто. + +Шаг заведён 2026-08-13 и проверен мутацией на восьми исходах вручную — правка +уехавшего шага в дереве и в коммите, удаление, переименование, новый шаг, правка +`migrations.go`, отсутствующий ключ в `docs/.docs.json`, каталог без шагов, +неразрешимая база диффа. Прогон был разовым: в дереве от него не осталось ничего. + +Прецедент рядом. У шага сверки версий Go есть спека +[toolchain](../../openspec/specs/toolchain/spec.md) и 20 мутационно проверенных +сценариев в `scripts/check_go_version_test.go`; заведены они после дефекта +2026-08-12, когда зелёный шаг не проверял ничего и образ перестал собираться. +Долг назван строкой в +[go-linters.md](../../docs/conventions/go-linters.md), «Границы: где что живёт». + +**Развилка, решаемая внутри задачи:** нормировать шаг спекой (второй capability +о проверке, как `toolchain`) либо ограничиться проверками без нормы. Первое +дороже и даёт построчную сверку сценариев; второе закрывает регрессию, но +оставляет норму в комментарии `Taskfile.yml`. + +## Затрагивает + +- шаг `migrations` в `Taskfile.yml` — его логика разбора `git diff`; +- ключ `migrations` в `docs/.docs.json` — из него шаг берёт каталог; +- каталог шагов схемы `internal/adapter/repo/pocketbase/migrations/` как предмет + правила; +- возможно — новая capability в `openspec/specs/` и файл проверок рядом с + `scripts/check_go_version_test.go`. + +## Критерии приёмки + +- Переписанный уехавший шаг схемы роняет проверку. Оракул — прогон сценария на + временном клоне репозитория: правка файла шага даёт код 1 и называет файл. +- Новый файл шага проверку не роняет, и правка `migrations.go` тоже: строка + `Register` нового шага прибавляется именно там. Оракул — те же два сценария. +- Каталог без единого файла шага и отсутствующий ключ в `docs/.docs.json` дают + код 3, а не тихий ноль. Оракул — два сценария на временном каталоге. +- Проверка сценариев идёт в гейте, а не руками. Оракул — `task gate` красный при + внесённом нарушении шаблона имени файла шага. + +## Рамки + +Боевой каталог данных и файлы шагов схемы не трогаем: сценарии гоняются на +временном клоне репозитория. Чужие скрипты проверок (`docs.py`, `tasks.py`, +`openspec.py`) — не наши, они в задаче `gate-steps-subject-guard`.