docs: точного числа накопленного в документах больше нет
- CLAUDE.md, «Язык»: ссылаться можно на конкретную запись или на весь корпус разом, но не на их количество — число протухает молча, машина его не считает. Изъятие названо: неизменное число и историческое в записи о прошлом остаются. - Сняты счёты capability, прогонов ревью, типизированных ошибок, воркеров, сверок документов и правил линтера в docs/, спеке pipeline и CLAUDE.md. - Заодно исправлено то, что этот же счёт и скрывал: типизированных ошибок три, а не две — LostAcquisitionError был потерян из перечня.
This commit is contained in:
@@ -141,7 +141,7 @@ task gate # весь набор проверок разом
|
|||||||
**затронутых файлах** дешёвую часть: `gofmt` (правит на месте и добавляет в
|
**затронутых файлах** дешёвую часть: `gofmt` (правит на месте и добавляет в
|
||||||
коммит), `golangci-lint` по пакетам тронутых файлов, `shellcheck`, `hadolint`,
|
коммит), `golangci-lint` по пакетам тронутых файлов, `shellcheck`, `hadolint`,
|
||||||
`gitleaks` по индексу. Только гейту остаются сборка, `go vet`, тесты целиком,
|
`gitleaks` по индексу. Только гейту остаются сборка, `go vet`, тесты целиком,
|
||||||
сверка версий Go, три сверки документов и `govulncheck`: они смотрят всё
|
сверка версий Go, сверки документов и `govulncheck`: они смотрят всё
|
||||||
дерево либо требуют сети, а pre-commit обязан быть быстрым.
|
дерево либо требуют сети, а pre-commit обязан быть быстрым.
|
||||||
- **Шагу `vulns` нужна сеть**, и он один такой: база уязвимостей живёт на
|
- **Шагу `vulns` нужна сеть**, и он один такой: база уязвимостей живёт на
|
||||||
vuln.go.dev. Без сети шаг краснеет, а не пропускается молча; сам инструмент
|
vuln.go.dev. Без сети шаг краснеет, а не пропускается молча; сам инструмент
|
||||||
@@ -155,8 +155,8 @@ task gate # весь набор проверок разом
|
|||||||
которого образ перестаёт собираться, но собираемости не проверяет. Собрать
|
которого образ перестаёт собираться, но собираемости не проверяет. Собрать
|
||||||
образ по-прежнему может только человек — `task image`, и на подъёме версии
|
образ по-прежнему может только человек — `task image`, и на подъёме версии
|
||||||
это обязательно;
|
это обязательно;
|
||||||
- собираемость `Dockerfile`: `hadolint` судит форму, а не сборку, и два его
|
- собираемость `Dockerfile`: `hadolint` судит форму, а не сборку, и часть его
|
||||||
правила подавлены поимённо — `DL3007` до задачи `pin-runtime-image-base` и
|
правил подавлена поимённо — `DL3007` до задачи `pin-runtime-image-base` и
|
||||||
`DL3018` по существу (alpine не держит старые версии пакетов, закрепление
|
`DL3018` по существу (alpine не держит старые версии пакетов, закрепление
|
||||||
ломает сборку через недели). Причины стоят строками в `Taskfile.yml`;
|
ломает сборку через недели). Причины стоят строками в `Taskfile.yml`;
|
||||||
- `gitleaks` — висит на pre-commit в `lefthook.yml` и смотрит только индекс
|
- `gitleaks` — висит на pre-commit в `lefthook.yml` и смотрит только индекс
|
||||||
@@ -231,3 +231,18 @@ task gate # весь набор проверок разом
|
|||||||
- Документация, комментарии, сообщения коммитов — русский.
|
- Документация, комментарии, сообщения коммитов — русский.
|
||||||
- Код и идентификаторы — английский.
|
- Код и идентификаторы — английский.
|
||||||
- Текст, который видит пользователь Telegram, — русский.
|
- Текст, который видит пользователь Telegram, — русский.
|
||||||
|
- **Точного числа накопленного в документах нет.** «Три capability», «пять
|
||||||
|
прогонов ревью», «две типизированные ошибки» расходятся с действительностью на
|
||||||
|
первой же задаче, которая прибавит четвёртую, — и расходятся молча: машина
|
||||||
|
такое не считает, а читатель верит написанному. Ссылаться можно только на
|
||||||
|
**конкретную запись** (по имени, со ссылкой) либо на **весь корпус разом**
|
||||||
|
(«заведённые capability», «записи журнала ниже»). Само перечисление при этом
|
||||||
|
законно: перечень обновляют вместе с предметом, а число живёт отдельно от него
|
||||||
|
и потому протухает в одиночку.
|
||||||
|
|
||||||
|
*Изъятие:* число, которое не растёт с работой, остаётся числом — количество
|
||||||
|
уровней журнала в библиотеке, ступеней сборки образа, состояний списка на
|
||||||
|
экране. Так же законно **историческое** число в записи о прошлом: «решением от
|
||||||
|
2026-08-13 закрыты четыре задачи» описывает событие, а не сегодняшний счёт.
|
||||||
|
Настройки с числовым значением — свой случай, их дом
|
||||||
|
[docs/database.md](docs/database.md).
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
[passport.md](passport.md) и в [tasks/BACKLOG.md](../tasks/BACKLOG.md); что из
|
[passport.md](passport.md) и в [tasks/BACKLOG.md](../tasks/BACKLOG.md); что из
|
||||||
этого ещё не решено — в разделе «Открытые вопросы».
|
этого ещё не решено — в разделе «Открытые вопросы».
|
||||||
|
|
||||||
Заведены четыре capability, и все нормируют **поведение сервиса** для его
|
Заведённые capability нормируют **поведение сервиса** для его потребителей —
|
||||||
потребителей. Инструмент, которым сервис собирают, спеками не нормируется вовсе:
|
все до одной. Инструмент, которым сервис собирают, спеками не нормируется вовсе:
|
||||||
у набора проверок и сборки другой потребитель — тот, кто собирает, — и решением
|
у набора проверок и сборки другой потребитель — тот, кто собирает, — и решением
|
||||||
от 2026-08-13 его нормы живут в самих шагах, их проверках и
|
от 2026-08-13 его нормы живут в самих шагах, их проверках и
|
||||||
[conventions/go-linters.md](conventions/go-linters.md).
|
[conventions/go-linters.md](conventions/go-linters.md).
|
||||||
@@ -87,8 +87,8 @@
|
|||||||
|
|
||||||
<!-- канон: поведение → openspec/specs/pipeline; ещё НЕ переехало: цепочка переходов состояний -->
|
<!-- канон: поведение → openspec/specs/pipeline; ещё НЕ переехало: цепочка переходов состояний -->
|
||||||
|
|
||||||
Конвейер: `created` → `converted` → `transcribe` → `done` либо `failed`. Три
|
Конвейер: `created` → `converted` → `transcribe` → `done` либо `failed`. Каждый
|
||||||
воркера двигают по одному переходу, каждый опрашивает базу раз в секунду. Что
|
переход двигает свой воркер, и каждый опрашивает базу раз в секунду. Что
|
||||||
делает задача, исчерпавшая попытки, нормирует
|
делает задача, исчерпавшая попытки, нормирует
|
||||||
[pipeline](../openspec/specs/pipeline/spec.md), «Число попыток и состояние
|
[pipeline](../openspec/specs/pipeline/spec.md), «Число попыток и состояние
|
||||||
«мертва»».
|
«мертва»».
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
`transcriber_worker_job_count` с меткой `error="true"` и по логам контейнера.
|
`transcriber_worker_job_count` с меткой `error="true"` и по логам контейнера.
|
||||||
Отдельного оповещения нет.
|
Отдельного оповещения нет.
|
||||||
- **Характер потока:** непрерывный, но разреженный. Бот держит длинный опрос,
|
- **Характер потока:** непрерывный, но разреженный. Бот держит длинный опрос,
|
||||||
три воркера опрашивают базу вхолостую с паузой из
|
воркеры опрашивают базу вхолостую с паузой из
|
||||||
[database.md](database.md), «Настройки с числовым значением».
|
[database.md](database.md), «Настройки с числовым значением».
|
||||||
|
|
||||||
## Единые точки проекта
|
## Единые точки проекта
|
||||||
@@ -228,8 +228,9 @@
|
|||||||
конвертер этот случай не проверялся.
|
конвертер этот случай не проверялся.
|
||||||
- **Очередь.** Модель очереди сделана задачей `pocketbase-storage` 2026-08-12
|
- **Очередь.** Модель очереди сделана задачей `pocketbase-storage` 2026-08-12
|
||||||
([ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md)) и нормирована
|
([ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md)) и нормирована
|
||||||
спекой `pipeline`. Не решено, отказываться ли от холостого опроса: три воркера
|
спекой `pipeline`. Не решено, отказываться ли от холостого опроса: он
|
||||||
дают 259 200 запросов к базе в сутки — расчёт из паузы воркера, а не замер
|
даёт сотни тысяч запросов к базе в сутки — расчёт из числа воркеров и их
|
||||||
|
паузы, а не замер
|
||||||
([research/job-queue.md](research/job-queue.md), «Как снималось»), — при
|
([research/job-queue.md](research/job-queue.md), «Как снималось»), — при
|
||||||
нагрузке в единицы записей в день, и во что это обходится, никто не мерил.
|
нагрузке в единицы записей в день, и во что это обходится, никто не мерил.
|
||||||
- **Наблюдаемость.** `/metrics` остаётся и развивается. Чем — дописывать
|
- **Наблюдаемость.** `/metrics` остаётся и развивается. Чем — дописывать
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ severity — в [CLAUDE.md](../../CLAUDE.md).
|
|||||||
UUID вместо ULID, лог пишется на каждом шаге и дублируется воркером, `msg` —
|
UUID вместо ULID, лог пишется на каждом шаге и дублируется воркером, `msg` —
|
||||||
предложение с заглавной буквы вместо константной категории.
|
предложение с заглавной буквы вместо константной категории.
|
||||||
|
|
||||||
Из этого перечня закрыты два. Доменные ошибки проверялись приведением типа до
|
Часть перечня закрыта. Доменные ошибки проверялись приведением типа до
|
||||||
2026-08-11, задача `errors-as-instead-of-typecast`. Время брали `time.Now()` по
|
2026-08-11, задача `errors-as-instead-of-typecast`. Время брали `time.Now()` по
|
||||||
месту до 2026-08-13 — теперь его читает единая точка `internal/clock`, и правило
|
месту до 2026-08-13 — теперь его читает единая точка `internal/clock`, и правило
|
||||||
держит линтер. Оба места больше не долг, а регрессия.
|
держит линтер. Оба места больше не долг, а регрессия.
|
||||||
|
|||||||
@@ -65,14 +65,15 @@ transcriber — **приложение, а не библиотека**: внеш
|
|||||||
вызывающему нужны **данные** ошибки. Достаём `errors.As`. Не плодим типы там,
|
вызывающему нужны **данные** ошибки. Достаём `errors.As`. Не плодим типы там,
|
||||||
где хватает sentinel.
|
где хватает sentinel.
|
||||||
|
|
||||||
Сегодня в проекте две типизированные ошибки, и обе несут данные:
|
Типизированные ошибки проекта несут данные все до одной:
|
||||||
`contract.JobNotFoundError` (состояние и сообщение) и `contract.NoopJobError`
|
`contract.JobNotFoundError` (состояние и сообщение), `contract.NoopJobError`
|
||||||
(состояние). Третья, `tg.EmptyBotTokenError`, была ровно тем случаем, против
|
(состояние), `contract.LostAcquisitionError` (идентификатор задачи).
|
||||||
которого написано правило — тип без полей, — и снята задачей
|
|
||||||
`local-run-without-telegram-token` 2026-08-13; её место занял sentinel
|
`tg.EmptyBotTokenError` был ровно тем случаем, против которого написано правило —
|
||||||
`telegram.ErrEmptyToken`. Рядом с ним живёт `contract.ErrDeliveryChannelDown` —
|
тип без полей, — и снят задачей `local-run-without-telegram-token` 2026-08-13;
|
||||||
тоже sentinel и по той же причине: заглушка отправителя не знает ни задачи, ни
|
его место занял sentinel `telegram.ErrEmptyToken`. Рядом живёт
|
||||||
чата, и нести ей нечего.
|
`contract.ErrDeliveryChannelDown` — тоже sentinel и по той же причине: заглушка
|
||||||
|
отправителя не знает ни задачи, ни чата, и нести ей нечего.
|
||||||
|
|
||||||
## Граница и трансляция: приватный и публичный канал
|
## Граница и трансляция: приватный и публичный канал
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
| Ядро (`internal/service`) не знает ни адаптеров, ни транспортов | `internal/archrules` → `TestЯдроНеЗнаетОбАдаптерах`, `TestЯдроНеЗнаетОТранспортах` |
|
| Ядро (`internal/service`) не знает ни адаптеров, ни транспортов | `internal/archrules` → `TestЯдроНеЗнаетОбАдаптерах`, `TestЯдроНеЗнаетОТранспортах` |
|
||||||
| Транспорты (`controller/http`, `controller/tg`, `controller/worker`) не знают друг о друге | `internal/archrules` → `TestТранспортыНеЗнаютДругОДруге` |
|
| Транспорты (`controller/http`, `controller/tg`, `controller/worker`) не знают друг о друге | `internal/archrules` → `TestТранспортыНеЗнаютДругОДруге` |
|
||||||
| Адаптер не знает ни ядра, ни транспортов | `internal/archrules` → `TestАдаптерыНеЗнаютНиЯдра_НиТранспортов` |
|
| Адаптер не знает ни ядра, ни транспортов | `internal/archrules` → `TestАдаптерыНеЗнаютНиЯдра_НиТранспортов` |
|
||||||
| Колонки очереди согласованы: перечень захвата ↔ структура захвата ↔ шаг схемы ↔ запись коллекции ↔ перенос поля в задачу | `internal/archrules` → четыре правила о захвате. Закрывает инвариант «колонки правятся в четырёх местах» (CLAUDE.md, major), которого компилятор не держит. Литерал колонки ищется в телах нужных функций: по файлу целиком условие выполнялось бы тегами `db:"…"` самой структуры, и правило было бы зелёным всегда |
|
| Колонки очереди согласованы: перечень захвата ↔ структура захвата ↔ шаг схемы ↔ запись коллекции ↔ перенос поля в задачу | `internal/archrules` → правила о захвате. Закрывает инвариант «колонки правятся в четырёх местах» (CLAUDE.md, major), которого компилятор не держит. Литерал колонки ищется в телах нужных функций: по файлу целиком условие выполнялось бы тегами `db:"…"` самой структуры, и правило было бы зелёным всегда |
|
||||||
|
|
||||||
### Отмена и внешний собеседник
|
### Отмена и внешний собеседник
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ Object Storage, скачивание файла из Telegram и опрос оп
|
|||||||
|
|
||||||
Обращения к Telegram этому правилу следуют, и точка чистки одна на все вызовы —
|
Обращения к Telegram этому правилу следуют, и точка чистки одна на все вызовы —
|
||||||
`internal/adapter/telegram`, `NewBot`. Токен стоит в пути **каждого** обращения к
|
`internal/adapter/telegram`, `NewBot`. Токен стоит в пути **каждого** обращения к
|
||||||
Bot API, поэтому чистка на месте употребления закрывала бы один вызов из пяти:
|
Bot API, поэтому чистка на месте употребления закрывала бы один вызов из всех:
|
||||||
|
|
||||||
- отказ транспорта разворачивает в первопричину клиент бота (`safeClient`), а
|
- отказ транспорта разворачивает в первопричину клиент бота (`safeClient`), а
|
||||||
библиотека отдаёт наш отказ вызывающему нетронутым — этим закрыты `getFile`,
|
библиотека отдаёт наш отказ вызывающему нетронутым — этим закрыты `getFile`,
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ CGO сборке не нужен.
|
|||||||
### `files`
|
### `files`
|
||||||
|
|
||||||
Один файл на одну физическую копию: исходник, результат конвертации и копия в
|
Один файл на одну физическую копию: исходник, результат конвертации и копия в
|
||||||
Object Storage — три разные записи.
|
Object Storage — каждая своей записью.
|
||||||
|
|
||||||
| Поле | Тип | Что |
|
| Поле | Тип | Что |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|||||||
+9
-7
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
## Как настроен конвейер
|
## Как настроен конвейер
|
||||||
|
|
||||||
Артефакты семи прогонов лежат в `openspec/changes/archive/<id>/review/`: у трёх
|
Артефакты прогонов лежат в `openspec/changes/archive/<id>/review/` — под именем
|
||||||
ранних, начиная с `fix-http-handler-tests` 2026-08-11, это `triage.md`, у трёх
|
`triage.md` либо `report.md`: имя менялось по ходу, и оба встречаются. Самый
|
||||||
поздних — `report.md`, у седьмого (`start-without-telegram-token` 2026-08-13) —
|
ранний — `fix-http-handler-tests` 2026-08-11, самый поздний —
|
||||||
снова `triage.md`. Сверх них конвейер прогонялся 2026-08-13 на работе, шедшей
|
`start-without-telegram-token` 2026-08-13.
|
||||||
без своего изменения openspec; артефакта в архиве у тех прогонов нет, и урожай их
|
|
||||||
виден только записями журнала ниже. Разделы ниже заведены наперёд по коду
|
Конвейер прогонялся и на работе, шедшей без своего изменения openspec; артефакта
|
||||||
2026-08-11 и с тех пор правятся урожаем прогонов.
|
в архиве у таких прогонов нет, и урожай их виден только записями журнала ниже.
|
||||||
|
Разделы ниже заведены наперёд по коду 2026-08-11 и с тех пор правятся урожаем
|
||||||
|
прогонов.
|
||||||
|
|
||||||
**Проход, поднявший сервис, обязан его остановить.** Живой прогон стал доступен
|
**Проход, поднявший сервис, обязан его остановить.** Живой прогон стал доступен
|
||||||
2026-08-13 (см. «Недоступно проверке»), и первый же им воспользовался: враждебный
|
2026-08-13 (см. «Недоступно проверке»), и первый же им воспользовался: враждебный
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ done | failed`, отмена контекста посреди шага и ос
|
|||||||
узнаваться по смыслу значения, а не по его точной форме, и MUST переживать
|
узнаваться по смыслу значения, а не по его точной форме, и MUST переживать
|
||||||
пояснения, добавленные к этому значению на любом промежуточном шаге пути.
|
пояснения, добавленные к этому значению на любом промежуточном шаге пути.
|
||||||
|
|
||||||
Требование стоит на инварианте проекта «`NoopJobError` — не ошибка»: три воркера
|
Требование стоит на инварианте проекта «`NoopJobError` — не ошибка»: воркеры
|
||||||
опрашивают базу раз в секунду, и пустой прогон, принятый за отказ, даёт три
|
опрашивают базу раз в секунду, и пустой прогон, принятый за отказ, даёт от
|
||||||
записи отказа в секунду и столько же засчитанных сбоев, которых не было.
|
каждого запись отказа в секунду и столько же засчитанных сбоев, которых не было.
|
||||||
|
|
||||||
Признак пустого прогона MUST рождаться только ответом хранилища на опрос этим же
|
Признак пустого прогона MUST рождаться только ответом хранилища на опрос этим же
|
||||||
шагом. Слой, придающий отказу собственный смысл, MUST не сохранять чужой признак
|
шагом. Слой, придающий отказу собственный смысл, MUST не сохранять чужой признак
|
||||||
|
|||||||
Reference in New Issue
Block a user