docs: точного числа накопленного в документах больше нет

- CLAUDE.md, «Язык»: ссылаться можно на конкретную запись или на весь корпус
  разом, но не на их количество — число протухает молча, машина его не считает.
  Изъятие названо: неизменное число и историческое в записи о прошлом остаются.
- Сняты счёты capability, прогонов ревью, типизированных ошибок, воркеров,
  сверок документов и правил линтера в docs/, спеке pipeline и CLAUDE.md.
- Заодно исправлено то, что этот же счёт и скрывал: типизированных ошибок три,
  а не две — LostAcquisitionError был потерян из перечня.
This commit is contained in:
av
2026-08-13 19:23:00 +03:00
parent 4c87220d90
commit 903941f587
9 changed files with 51 additions and 32 deletions
+8 -7
View File
@@ -8,8 +8,8 @@
[passport.md](passport.md) и в [tasks/BACKLOG.md](../tasks/BACKLOG.md); что из
этого ещё не решено — в разделе «Открытые вопросы».
Заведены четыре capability, и все нормируют **поведение сервиса** для его
потребителей. Инструмент, которым сервис собирают, спеками не нормируется вовсе:
Заведённые capability нормируют **поведение сервиса** для его потребителей —
все до одной. Инструмент, которым сервис собирают, спеками не нормируется вовсе:
у набора проверок и сборки другой потребитель — тот, кто собирает, — и решением
от 2026-08-13 его нормы живут в самих шагах, их проверках и
[conventions/go-linters.md](conventions/go-linters.md).
@@ -87,8 +87,8 @@
<!-- канон: поведение → openspec/specs/pipeline; ещё НЕ переехало: цепочка переходов состояний -->
Конвейер: `created``converted``transcribe``done` либо `failed`. Три
воркера двигают по одному переходу, каждый опрашивает базу раз в секунду. Что
Конвейер: `created``converted``transcribe``done` либо `failed`. Каждый
переход двигает свой воркер, и каждый опрашивает базу раз в секунду. Что
делает задача, исчерпавшая попытки, нормирует
[pipeline](../openspec/specs/pipeline/spec.md), «Число попыток и состояние
«мертва»».
@@ -141,7 +141,7 @@
`transcriber_worker_job_count` с меткой `error="true"` и по логам контейнера.
Отдельного оповещения нет.
- **Характер потока:** непрерывный, но разреженный. Бот держит длинный опрос,
три воркера опрашивают базу вхолостую с паузой из
воркеры опрашивают базу вхолостую с паузой из
[database.md](database.md), «Настройки с числовым значением».
## Единые точки проекта
@@ -228,8 +228,9 @@
конвертер этот случай не проверялся.
- **Очередь.** Модель очереди сделана задачей `pocketbase-storage` 2026-08-12
([ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md)) и нормирована
спекой `pipeline`. Не решено, отказываться ли от холостого опроса: три воркера
дают 259 200 запросов к базе в сутки — расчёт из паузы воркера, а не замер
спекой `pipeline`. Не решено, отказываться ли от холостого опроса: он
даёт сотни тысяч запросов к базе в сутки — расчёт из числа воркеров и их
паузы, а не замер
([research/job-queue.md](research/job-queue.md), «Как снималось»), — при
нагрузке в единицы записей в день, и во что это обходится, никто не мерил.
- **Наблюдаемость.** `/metrics` остаётся и развивается. Чем — дописывать
+1 -1
View File
@@ -21,7 +21,7 @@ severity — в [CLAUDE.md](../../CLAUDE.md).
UUID вместо ULID, лог пишется на каждом шаге и дублируется воркером, `msg`
предложение с заглавной буквы вместо константной категории.
Из этого перечня закрыты два. Доменные ошибки проверялись приведением типа до
Часть перечня закрыта. Доменные ошибки проверялись приведением типа до
2026-08-11, задача `errors-as-instead-of-typecast`. Время брали `time.Now()` по
месту до 2026-08-13 — теперь его читает единая точка `internal/clock`, и правило
держит линтер. Оба места больше не долг, а регрессия.
+9 -8
View File
@@ -65,14 +65,15 @@ transcriber — **приложение, а не библиотека**: внеш
вызывающему нужны **данные** ошибки. Достаём `errors.As`. Не плодим типы там,
где хватает sentinel.
Сегодня в проекте две типизированные ошибки, и обе несут данные:
`contract.JobNotFoundError` (состояние и сообщение) и `contract.NoopJobError`
(состояние). Третья, `tg.EmptyBotTokenError`, была ровно тем случаем, против
которого написано правило — тип без полей, — и снята задачей
`local-run-without-telegram-token` 2026-08-13; её место занял sentinel
`telegram.ErrEmptyToken`. Рядом с ним живёт `contract.ErrDeliveryChannelDown`
тоже sentinel и по той же причине: заглушка отправителя не знает ни задачи, ни
чата, и нести ей нечего.
Типизированные ошибки проекта несут данные все до одной:
`contract.JobNotFoundError` (состояние и сообщение), `contract.NoopJobError`
(состояние), `contract.LostAcquisitionError` (идентификатор задачи).
`tg.EmptyBotTokenError` был ровно тем случаем, против которого написано правило —
тип без полей, — и снят задачей `local-run-without-telegram-token` 2026-08-13;
его место занял sentinel `telegram.ErrEmptyToken`. Рядом живёт
`contract.ErrDeliveryChannelDown` — тоже sentinel и по той же причине: заглушка
отправителя не знает ни задачи, ни чата, и нести ей нечего.
## Граница и трансляция: приватный и публичный канал
+1 -1
View File
@@ -91,7 +91,7 @@
| Ядро (`internal/service`) не знает ни адаптеров, ни транспортов | `internal/archrules``TestЯдроНеЗнаетОбАдаптерах`, `TestЯдроНеЗнаетОТранспортах` |
| Транспорты (`controller/http`, `controller/tg`, `controller/worker`) не знают друг о друге | `internal/archrules``TestТранспортыНеЗнаютДругОДруге` |
| Адаптер не знает ни ядра, ни транспортов | `internal/archrules``TestАдаптерыНеЗнаютНиЯдра_НиТранспортов` |
| Колонки очереди согласованы: перечень захвата ↔ структура захвата ↔ шаг схемы ↔ запись коллекции ↔ перенос поля в задачу | `internal/archrules` четыре правила о захвате. Закрывает инвариант «колонки правятся в четырёх местах» (CLAUDE.md, major), которого компилятор не держит. Литерал колонки ищется в телах нужных функций: по файлу целиком условие выполнялось бы тегами `db:"…"` самой структуры, и правило было бы зелёным всегда |
| Колонки очереди согласованы: перечень захвата ↔ структура захвата ↔ шаг схемы ↔ запись коллекции ↔ перенос поля в задачу | `internal/archrules` → правила о захвате. Закрывает инвариант «колонки правятся в четырёх местах» (CLAUDE.md, major), которого компилятор не держит. Литерал колонки ищется в телах нужных функций: по файлу целиком условие выполнялось бы тегами `db:"…"` самой структуры, и правило было бы зелёным всегда |
### Отмена и внешний собеседник
+1 -1
View File
@@ -242,7 +242,7 @@ Object Storage, скачивание файла из Telegram и опрос оп
Обращения к Telegram этому правилу следуют, и точка чистки одна на все вызовы —
`internal/adapter/telegram`, `NewBot`. Токен стоит в пути **каждого** обращения к
Bot API, поэтому чистка на месте употребления закрывала бы один вызов из пяти:
Bot API, поэтому чистка на месте употребления закрывала бы один вызов из всех:
- отказ транспорта разворачивает в первопричину клиент бота (`safeClient`), а
библиотека отдаёт наш отказ вызывающему нетронутым — этим закрыты `getFile`,
+1 -1
View File
@@ -39,7 +39,7 @@ CGO сборке не нужен.
### `files`
Один файл на одну физическую копию: исходник, результат конвертации и копия в
Object Storage — три разные записи.
Object Storage — каждая своей записью.
| Поле | Тип | Что |
| --- | --- | --- |
+9 -7
View File
@@ -2,13 +2,15 @@
## Как настроен конвейер
Артефакты семи прогонов лежат в `openspec/changes/archive/<id>/review/`: у трёх
ранних, начиная с `fix-http-handler-tests` 2026-08-11, это `triage.md`, у трёх
поздних`report.md`, у седьмого (`start-without-telegram-token` 2026-08-13)
снова `triage.md`. Сверх них конвейер прогонялся 2026-08-13 на работе, шедшей
без своего изменения openspec; артефакта в архиве у тех прогонов нет, и урожай их
виден только записями журнала ниже. Разделы ниже заведены наперёд по коду
2026-08-11 и с тех пор правятся урожаем прогонов.
Артефакты прогонов лежат в `openspec/changes/archive/<id>/review/` — под именем
`triage.md` либо `report.md`: имя менялось по ходу, и оба встречаются. Самый
ранний`fix-http-handler-tests` 2026-08-11, самый поздний
`start-without-telegram-token` 2026-08-13.
Конвейер прогонялся и на работе, шедшей без своего изменения openspec; артефакта
в архиве у таких прогонов нет, и урожай их виден только записями журнала ниже.
Разделы ниже заведены наперёд по коду 2026-08-11 и с тех пор правятся урожаем
прогонов.
**Проход, поднявший сервис, обязан его остановить.** Живой прогон стал доступен
2026-08-13 (см. «Недоступно проверке»), и первый же им воспользовался: враждебный