docs: раскладка переехала в .av-dev.toml, а расхождения документов сведены
- Перевод на канон 1 доделан: адреса служебного файла и имена скиллов переставлены в девяти местах прозы и кода, гейт зовёт три скрипта по новым путям, прежние docs/.docs.json и tasks/.tasks.json удалены. - Сверка двумя агентами нашла четырнадцать расхождений, тринадцать сведены строками: число прогонов ревью и преамбула журнала дефектов, счёт capability, маршруты README, дубли инварианта захвата и кодов прогона, протухшие указатели записок разведки, маркер долга на переехавшем абзаце. Срок жизни сессии нормирует спека access, database.md на неё ссылается. - Purpose спеки pipeline объявляет неописанным то, что в ней же и стоит; правка идёт изменением openspec, поэтому заведена задача pipeline-spec-purpose-drift.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Раскладка av-dev в этом проекте: версия и настройки проверок.
|
||||
# Файл ведут скиллы плагина, править руками можно — комментарии свои.
|
||||
|
||||
version = 1 # версия раскладки; обратной совместимости нет, есть «приведён» и «нет»
|
||||
|
||||
[docs]
|
||||
# каталог миграций: по нему docs.py сверяет схему с database.md
|
||||
migrations = "internal/adapter/repo/pocketbase/migrations"
|
||||
|
||||
[tasks]
|
||||
# каталог задач от корня репозитория; имена частей — умолчания скрипта
|
||||
dir = "tasks"
|
||||
@@ -162,7 +162,7 @@ task gate # весь набор проверок разом
|
||||
- `gitleaks` — висит на pre-commit в `lefthook.yml` и смотрит только индекс
|
||||
коммита. Полную историю никто не проверяет;
|
||||
- согласованность документов между собой и с кодом — её судят агенты, зовёт
|
||||
их скилл `av-dev-docs:healthcheck`, и звать его надо руками;
|
||||
их скилл `av-dev:doc-healthcheck`, и звать его надо руками;
|
||||
- покрытие изменённых строк не считается ничем.
|
||||
|
||||
**Гейт на `master` сегодня зелёный целиком, и объявленных долгов у него нет.**
|
||||
@@ -206,9 +206,9 @@ task gate # весь набор проверок разом
|
||||
ключа конфига, любое действие с боевыми данными и с Yandex Cloud, ротация
|
||||
секрета.
|
||||
- **Что считается сломанным** — новый красный шаг гейта, которого не было до
|
||||
твоей правки. Такое чинится прежде любой другой работы. Два объявленных долга
|
||||
из раздела «Гейт» сломанным состоянием **не** считаются, пока их не закрыли
|
||||
задачами.
|
||||
твоей правки. Такое чинится прежде любой другой работы. Исключений из этого
|
||||
правила нет: раздел «Гейт» называет оба прежних долга закрытыми, и списывать
|
||||
красный шаг больше не на что.
|
||||
- **Ориентир по размеру порции:** не замерялся.
|
||||
- **Что такое «сделана»:** `task gate` зелёный и критерии приёмки проверены
|
||||
поимённо.
|
||||
|
||||
@@ -62,9 +62,13 @@ inv pl -- transcriber
|
||||
|
||||
## HTTP API
|
||||
|
||||
Четыре маршрута: `POST /api/audio` — приём записи, `GET /api/status/:id` —
|
||||
готовность задачи, `GET /metrics` — метрики Prometheus с префиксом
|
||||
`transcriber_`, `GET /health` — проверка живости.
|
||||
Семь адресов приложения: `POST /api/audio` — приём записи, `GET /api/status/:id`
|
||||
— готовность задачи, `GET /auth/login`, `GET /auth/callback` и
|
||||
`POST /auth/logout` — вход через провайдера
|
||||
([access](openspec/specs/access/spec.md)), `GET /metrics` — метрики Prometheus с
|
||||
префиксом `transcriber_`, `GET /health` — проверка живости. Сверх них тем же
|
||||
портом отдаётся собственная поверхность встроенного хранилища и панель `/_/` —
|
||||
[docs/security.md](docs/security.md), «Из чего строятся пути и ключи».
|
||||
|
||||
Контракт приёма и опроса нормативен и живёт в
|
||||
[openspec/specs/intake/spec.md](openspec/specs/intake/spec.md): поля запроса и
|
||||
@@ -74,7 +78,7 @@ inv pl -- transcriber
|
||||
## Состояния задач
|
||||
|
||||
Перечень состояний, переходы между ними и число воркеров —
|
||||
[docs/database.md](docs/database.md), разделы «Таблицы» и «Представление
|
||||
[docs/database.md](docs/database.md), разделы «Коллекции» и «Представление
|
||||
данных»; как сложен конвейер целиком — [docs/architecture.md](docs/architecture.md).
|
||||
|
||||
## Структура проекта
|
||||
|
||||
+13
-12
@@ -15,9 +15,9 @@ vars:
|
||||
# отправлял читателя искать разъехавшееся там, где просто неполно дерево. Сам
|
||||
# `task` отдаёт наружу свой 201 на любой отказ шага, поэтому словарь читается
|
||||
# по коду скрипта, а не по коду `task`.
|
||||
DOCS_PY: '{{.DOCS_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev-docs/skills/canon/scripts/docs.py"}}'
|
||||
TASKS_PY: '{{.TASKS_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev-tasks/skills/tasks/scripts/tasks.py"}}'
|
||||
OPENSPEC_PY: '{{.OPENSPEC_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev-code/skills/openspec/scripts/openspec.py"}}'
|
||||
DOCS_PY: '{{.DOCS_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev/skills/doc-canon/scripts/docs.py"}}'
|
||||
TASKS_PY: '{{.TASKS_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev/skills/task-track/scripts/tasks.py"}}'
|
||||
OPENSPEC_PY: '{{.OPENSPEC_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev/skills/code-openspec/scripts/openspec.py"}}'
|
||||
|
||||
tasks:
|
||||
|
||||
@@ -89,19 +89,20 @@ tasks:
|
||||
echo "задай свою: task migrations BASE=<rev>"
|
||||
exit 3
|
||||
fi
|
||||
# Каталог шагов берётся из docs/.docs.json — там он уже записан ключом
|
||||
# `migrations` для сверки документов. Свой литерал завёл бы факту второй
|
||||
# дом: каталог переехал бы, а один из двух стражей молча позеленел.
|
||||
dir=$(python3 -c 'import json,sys; print(json.load(open("docs/.docs.json"))["migrations"])' 2>/dev/null) || dir=""
|
||||
# Каталог шагов берётся из .av-dev.toml — там он уже записан ключом
|
||||
# `migrations` секции `[docs]` для сверки документов. Свой литерал завёл
|
||||
# бы факту второй дом: каталог переехал бы, а один из двух стражей молча
|
||||
# позеленел. До слияния плагинов файл звался docs/.docs.json.
|
||||
dir=$(python3 -c 'import tomllib; print(tomllib.load(open(".av-dev.toml","rb"))["docs"]["migrations"])' 2>/dev/null) || dir=""
|
||||
if [ -z "$dir" ] || [ ! -d "$dir" ]; then
|
||||
echo "каталог шагов схемы не найден: ключ migrations в docs/.docs.json → '$dir'"
|
||||
echo "каталог шагов схемы не найден: ключ [docs] migrations в .av-dev.toml → '$dir'"
|
||||
exit 3
|
||||
fi
|
||||
# Страж предмета: правило, потерявшее файлы, стало бы вечно зелёным от
|
||||
# одного переименования — тот же приём, что у правил `internal/archrules`.
|
||||
if [ -z "$(ls "$dir" | grep -E '^[0-9]{12}_.*\.go$')" ]; then
|
||||
echo "в $dir нет ни одного файла шага: правило потеряло предмет"
|
||||
echo "поправь шаблон имени в этом шаге либо ключ migrations в docs/.docs.json"
|
||||
echo "поправь шаблон имени в этом шаге либо ключ [docs] migrations в .av-dev.toml"
|
||||
exit 3
|
||||
fi
|
||||
# Баз две, и вторая обязательна. `{{.BASE}}` отвечает на «шаг уже уехал»
|
||||
@@ -175,7 +176,7 @@ tasks:
|
||||
py=$(eval echo {{.DOCS_PY}})
|
||||
if [ ! -f "$py" ]; then
|
||||
echo "docs.py не найден: $py"
|
||||
echo "поставь плагин av-dev-docs либо задай путь: task docs DOCS_PY=<путь>"
|
||||
echo "поставь плагин av-dev либо задай путь: task docs DOCS_PY=<путь>"
|
||||
exit 3
|
||||
fi
|
||||
python3 "$py" check --base {{.BASE}}
|
||||
@@ -187,7 +188,7 @@ tasks:
|
||||
py=$(eval echo {{.TASKS_PY}})
|
||||
if [ ! -f "$py" ]; then
|
||||
echo "tasks.py не найден: $py"
|
||||
echo "поставь плагин av-dev-tasks либо задай путь: task tasks TASKS_PY=<путь>"
|
||||
echo "поставь плагин av-dev либо задай путь: task tasks TASKS_PY=<путь>"
|
||||
exit 3
|
||||
fi
|
||||
python3 "$py" check --dir tasks
|
||||
@@ -199,7 +200,7 @@ tasks:
|
||||
py=$(eval echo {{.OPENSPEC_PY}})
|
||||
if [ ! -f "$py" ]; then
|
||||
echo "openspec.py не найден: $py"
|
||||
echo "поставь плагин av-dev-code либо задай путь: task openspec OPENSPEC_PY=<путь>"
|
||||
echo "поставь плагин av-dev либо задай путь: task openspec OPENSPEC_PY=<путь>"
|
||||
exit 3
|
||||
fi
|
||||
python3 "$py" check --dir .
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"canon": 14,
|
||||
"migrations": "internal/adapter/repo/pocketbase/migrations"
|
||||
}
|
||||
+4
-1
@@ -21,7 +21,10 @@
|
||||
- Имя файла — `ADR-ГГГГ-ММ-ДД-slug.md`, дата — когда решение реально принято.
|
||||
Слаг **английский по сути, а не транслитом**: `queue-as-table`, не
|
||||
`ochered-tablicej`. Форму имени и слаг проверяет `docs.py check`.
|
||||
- Записи неизменяемы: передумали — новая запись, старой ставится статус.
|
||||
- Записи неизменяемы **в решении**: передумали — новая запись, старой ставится
|
||||
статус. Уточнить прежнюю запись можно только строкой «*Уточнено ГГГГ-ММ-ДД:*» в
|
||||
разделе «Последствия» и только фактом, который решения не меняет, — например
|
||||
действующим адресом того, что решение завело.
|
||||
- Активная запись статуса не имеет. Значений два: `заменено на ADR-…` и
|
||||
`устарело`; ставятся полем меты записи — `- **Статус:** …` рядом с датой и
|
||||
источником, а не абзацем в теле.
|
||||
|
||||
@@ -83,12 +83,13 @@
|
||||
| Шаги схемы | `internal/adapter/repo/pocketbase/migrations` | Файл на шаг, имя файла — имя шага; там же имена коллекций |
|
||||
| Панель владельца | `internal/adapter/repo/pocketbase`, `panel.go` | Правка задачи в панели проходит те же правила перехода, что и правка из кода |
|
||||
|
||||
<!-- канон: поведение → openspec/specs/pipeline; ещё НЕ переехало: спека заведена, но это в ней не описано -->
|
||||
<!-- канон: поведение → openspec/specs/pipeline; ещё НЕ переехало: цепочка переходов состояний -->
|
||||
|
||||
Конвейер: `created` → `converted` → `transcribe` → `done` либо `failed`. Три
|
||||
воркера двигают по одному переходу, каждый опрашивает базу раз в секунду. Задача,
|
||||
исчерпавшая попытки, уходит в `dead` мимо этой цепочки: её переводит туда не шаг,
|
||||
а тот, кто её захватил.
|
||||
воркера двигают по одному переходу, каждый опрашивает базу раз в секунду. Что
|
||||
делает задача, исчерпавшая попытки, нормирует
|
||||
[pipeline](../openspec/specs/pipeline/spec.md), «Число попыток и состояние
|
||||
«мертва»».
|
||||
|
||||
## Внешние границы и форматы
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ Go-проект как есть. Своё здесь — перечень пра
|
||||
документов. Дороже всех — у шага появляется своя норма и свои тесты.
|
||||
|
||||
Ступень, выбранная неверно, видна сразу. Запрет по имени, обходимый одной
|
||||
лишней строкой, — это ступень 4, наряженная третьей: так было с правилом о
|
||||
лишней строкой, — на деле правило четвёртой ступени, оформленное как правило
|
||||
третьей: так было с правилом о
|
||||
заголовках ответа, которое сначала запретило текст `\.Header\(\)\.Get`, а
|
||||
обходилось присваиванием в переменную. Правило переписано на суждение **по типу
|
||||
приёмника** (`analyze-types`), и это уже настоящая третья ступень.
|
||||
@@ -146,7 +147,7 @@ Go-проект как есть. Своё здесь — перечень пра
|
||||
| Проверка судит ответ по готовому ответу (`Result()`), а не по живой карте заголовков обработчика | `.golangci.yml` → `forbidigo` с `analyze-types`, находки только в `*_test.go`. Судит по типу приёмника (`httptest.ResponseRecorder`), поэтому ловит любую форму: цепочкой, через переменную, по индексу карты, обходом, полем `HeaderMap`. Остаётся ревью проверка, идущая мимо recorder — через свой `http.ResponseWriter` |
|
||||
| Каждый сценарий нормы шага сверки версий проверен мутацией, а не памятью | `scripts/check_go_version_test.go` — 20 сценариев спеки `toolchain` плюс два свойства самого шага: исход не зависит от установленного `go`, и шаг не зовёт ни `go`, ни `docker`, ни сеть |
|
||||
| Форма утверждения в проверках: «ожидалось» и «получено» не перепутаны местами, отказ судится `NoError`, а не `Nil`, `require` не зовут из горутины | `.golangci.yml` → `testifylint` |
|
||||
| Одновременный доступ проверен детектором, а не чтением кода | `Taskfile.yml` → шаг `tests` (`go test -race ./...`). Общее у воркеров — счётчики метрик, логгер и клиент бота; захват задачи в гонку не входит, он по построению её не даёт (одно состояние на воркер) — см. «Типовые ложноположительные» в [../review.md](../review.md). Без компилятора C шаг гоняет тесты без детектора и краснеет кодом 3: гонки — не повод отнимать у гейта сами тесты |
|
||||
| Одновременный доступ проверен детектором, а не чтением кода | `Taskfile.yml` → шаг `tests` (`go test -race ./...`). Общее у воркеров — счётчики метрик, логгер и клиент бота; захват задачи в гонку не входит, он по построению её не даёт (одно состояние на воркер) — см. «Типовые ложноположительные» в [../review.md](../review.md). Что делает шаг без компилятора C и каким кодом краснеет — [CLAUDE.md](../../CLAUDE.md), «Гейт» |
|
||||
| Строчное подавление называет линтер и причину, а протухшее краснеет | `.golangci.yml` → `nolintlint` (`require-explanation`, `require-specific`, `allow-unused: false`) |
|
||||
|
||||
### Форма кода и файлов вне Go
|
||||
@@ -164,8 +165,8 @@ Go-проект как есть. Своё здесь — перечень пра
|
||||
|
||||
| Правило | Где механизировано |
|
||||
| --- | --- |
|
||||
| Применённый шаг схемы не переписывается: у файла шага допустим один статус — `A` | `Taskfile.yml` → шаг `migrations`. Закрывает инвариант CLAUDE.md (critical), которого не держит ни компилятор, ни хранилище: применённое считается по имени файла. Баз диффа две — `BASE` и `HEAD`: первая отвечает на «шаг уже уехал» ровно настолько, насколько свежа `origin/master`, вторая ловит правку закоммиченного шага независимо от неё. Каталог берётся из ключа `migrations` в `docs/.docs.json`, чтобы у факта не было второго дома; пустой каталог роняет шаг — правило, потерявшее предмет, молчать не должно. `migrations.go` под правило не подпадает: строка `Register` нового шага прибавляется именно там |
|
||||
| Раскладка документов, битые ссылки, изменённый шаг схемы без правки `database.md` | `docs.py check`; каталог шагов задаёт ключ `migrations` в `docs/.docs.json` |
|
||||
| Применённый шаг схемы не переписывается: у файла шага допустим один статус — `A` | `Taskfile.yml` → шаг `migrations`. Закрывает инвариант CLAUDE.md (critical), которого не держит ни компилятор, ни хранилище: применённое считается по имени файла. Баз диффа две — `BASE` и `HEAD`: первая отвечает на «шаг уже уехал» ровно настолько, насколько свежа `origin/master`, вторая ловит правку закоммиченного шага независимо от неё. Каталог берётся из ключа `migrations` секции `[docs]` в `.av-dev.toml`, чтобы у факта не было второго дома. Исходы шага и их коды — [CLAUDE.md](../../CLAUDE.md), «Гейт». `migrations.go` под правило не подпадает: строка `Register` нового шага прибавляется именно там |
|
||||
| Раскладка документов, битые ссылки, изменённый шаг схемы без правки `database.md` | `docs.py check`; каталог шагов задаёт ключ `migrations` секции `[docs]` в `.av-dev.toml` |
|
||||
| Согласованность каталога задач, форма `openspec/config.yaml` | `tasks.py check`, `openspec.py check` |
|
||||
| Секреты в коммите | `lefthook.yml` → `gitleaks git --staged` |
|
||||
| Достижимая из кода уязвимость в зависимостях | `Taskfile.yml` → шаг `vulns` (`govulncheck ./...`) |
|
||||
|
||||
@@ -163,8 +163,7 @@ log := log.With("job_id", job.Id, "capability", "conversion")
|
||||
|
||||
*Расхождение, и оно системное:* сегодня шаг конвейера логирует ошибку `Error` и
|
||||
тут же возвращает её воркеру, который логирует её второй раз. Один сбой даёт две
|
||||
записи. Плюс `internal/controller/http/transcribe.go` пишет через `log.Printf`
|
||||
мимо `slog` целиком.
|
||||
записи.
|
||||
|
||||
## Внешние сервисы: логируем все вызовы
|
||||
|
||||
@@ -241,7 +240,7 @@ Object Storage, скачивание файла из Telegram и опрос оп
|
||||
проверка `errors.Is` на причину сохраняется. Общее правило: **секрет не кладём
|
||||
в URL, если у сервиса есть заголовок** — тогда его нет и в ошибке транспорта.
|
||||
|
||||
Разговор с Telegram этому правилу следует, и точка чистки одна на все вызовы —
|
||||
Обращения к Telegram этому правилу следуют, и точка чистки одна на все вызовы —
|
||||
`internal/adapter/telegram`, `NewBot`. Токен стоит в пути **каждого** обращения к
|
||||
Bot API, поэтому чистка на месте употребления закрывала бы один вызов из пяти:
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
- **Обёртка — единственное место, где читается код ответа.** Она же превращает
|
||||
ошибку контракта в доменную ошибку приложения; экран получает готовый текст, а
|
||||
не `Response`.
|
||||
- **Сессия живёт кукой `transcriber_session`**, и приложение её не читает: кука
|
||||
`HttpOnly`, браузер шлёт её сам, а вошедшего экран узнаёт по ответу API. Норма
|
||||
— [access](../../openspec/specs/access/spec.md).
|
||||
|
||||
## Показ ошибок и состояний
|
||||
|
||||
@@ -100,5 +103,5 @@
|
||||
узнала»).
|
||||
- **Устройство service worker и версионирование статики** — задача
|
||||
[installable-pwa](../../tasks/items/installable-pwa.md).
|
||||
- **Где живёт сессия и как приложение узнаёт вошедшего** — открытый вопрос
|
||||
- **Как связываются пользователь Telegram и пользователь веба** — открытый вопрос
|
||||
«Учётные записи» в [../architecture.md](../architecture.md).
|
||||
|
||||
+10
-8
@@ -16,7 +16,7 @@ CGO сборке не нужен.
|
||||
|
||||
Каталог у шагов свой, а не файл внутри пакета репозитория, и причина внешняя:
|
||||
шаг гейта сверяет изменённые шаги схемы с правкой этого документа по **префиксу
|
||||
пути** (`docs/.docs.json`, ключ `migrations`), а префикс наводится только на
|
||||
пути** (`.av-dev.toml`, ключ `migrations` секции `[docs]`), а префикс наводится только на
|
||||
каталог. Имена коллекций живут там же, рядом с шагом, который их заводит; пакет
|
||||
репозитория берёт их оттуда.
|
||||
|
||||
@@ -78,10 +78,10 @@ capability, и третий смысл развёл бы одно слово п
|
||||
Прежней колонки `is_error` нет: задача выбывает из выборки состоянием, и способ
|
||||
этот один.
|
||||
|
||||
**Состояния `failed` и `dead` — разные приговоры.** В `failed` задачу переводит
|
||||
шаг, рассудивший об этой записи окончательно; в `dead` она уходит без такого
|
||||
суждения — мы повторяли и перестали. Ни один шаг конвейера в `dead` не переводит
|
||||
сам: это делает тот, кто захватил задачу с превышенным счётчиком.
|
||||
**Состояния `failed` и `dead` — разные приговоры**, и чей это приговор, нормирует
|
||||
[pipeline](../openspec/specs/pipeline/spec.md), «Число попыток и состояние
|
||||
«мертва»». Схеме принадлежит только закрытость перечня: шестое состояние
|
||||
потребует нового шага.
|
||||
|
||||
**Правила доступа обеих коллекций пусты**, то есть перечислять и читать записи
|
||||
может только владелец панели. Проверено прогоном: анонимный запрос к
|
||||
@@ -118,8 +118,10 @@ capability, и третий смысл развёл бы одно слово п
|
||||
поэтому захваты выстраиваются в очередь. Порядок выборки — по времени
|
||||
заведения **и по ключу**: время неуникально, и без ключа порядок обработки
|
||||
невоспроизводим.
|
||||
- **Запись результата условна по признаку захвата.** Шаг, чей захват за время
|
||||
работы достался другому, завершается без записи и без ответа отправителю.
|
||||
- **Запись результата условна по признаку захвата** — инвариант «Результат пишет
|
||||
только держатель захвата» в [CLAUDE.md](../CLAUDE.md), «Инварианты» (major);
|
||||
норма — [pipeline](../openspec/specs/pipeline/spec.md). Здесь названо потому,
|
||||
что условие проверяется тем же запросом, что и сам захват.
|
||||
- **Список колонок задан четырьмя местами** — `applyToRecord`, `recordToJob`,
|
||||
константой `acquireColumns` и структурой `acquiredRow`, — плюс шагом схемы.
|
||||
Все четыре лежат в одном пакете, но компилятор видит два: правило правки и его
|
||||
@@ -148,7 +150,7 @@ capability, и третий смысл развёл бы одно слово п
|
||||
| Качество кодирования vorbis | `-q:a 4` | `adapter/converter/ffmpeg/ffmpeg.go` | — |
|
||||
| Жизнь приглашения завести владельца панели | 30 минут | умолчание PocketBase | — |
|
||||
| Потолок размера одной записи | 8 ГиБ | `entity.MaxRecordSize` | расчётный потолок в шесть часов с запасом на видео |
|
||||
| Срок жизни сессии | 7 суток | `pbrepo.SessionDuration`, ставится при подъёме | решение владельца 2026-08-12; умолчание библиотеки в 5 суток никем не выбрано |
|
||||
| Срок жизни сессии | нормирует [access](../openspec/specs/access/spec.md) | `pbrepo.SessionDuration`, ставится при подъёме | решение владельца 2026-08-12; умолчание библиотеки никем не выбрано, и спека прямо запрещает его применять |
|
||||
| Потолок времени на вход у провайдера | 10 минут | `controller/http/auth.go` | дольше носитель состояния не нужен |
|
||||
| Таймаут обмена кода у провайдера | 15 секунд | там же | молчащий провайдер иначе держит обработчик возврата открытым |
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
| Владелец сервиса | Загрузить диктофонную запись или видео из семейного архива с телефона и получить текст. Видеть, кто сколько загрузил и во что это обошлось |
|
||||
| Приглашённый пользователь | Войти в приложение через свою учётную запись, загрузить запись, забрать текст, вернуться к ней через месяц. Приложение ставится на телефон; каждый видит только свои записи |
|
||||
| Пользователь Telegram | Отправить боту голосовое сообщение и получить текст ответом. Работает сегодня |
|
||||
| Внешняя программа | Отдать файл по HTTP, представившись своим токеном, и опросить готовность. Сегодня почти не работает: приём и опрос закрыты сессией OIDC, а своего токена у программы нет — годится только кука, снятая из браузера. Токен приносит `api-tokens` |
|
||||
| Внешняя программа | Отдать файл по HTTP, представившись своим токеном, и опросить готовность. Сегодня почти не работает: приём и опрос закрыты сессией OIDC, а своего токена у программы нет — годится только чужая сессия, снятая из браузера и предъявленная кукой либо заголовком `Authorization`. Токен приносит `api-tokens` |
|
||||
|
||||
**Основной вход — приложение**, бот и HTTP API дополняют его. До 2026-08-11
|
||||
основным был бот, и порядок здесь перевёрнут сознательно: диктофонная запись на
|
||||
|
||||
@@ -49,8 +49,9 @@
|
||||
|
||||
## Захват чинится одним запросом
|
||||
|
||||
Сегодняшний захват — два запроса подряд без транзакции
|
||||
([../database.md](../database.md), «Представление данных»). Замер показал, что
|
||||
Захват **на момент замера** — два запроса подряд без транзакции; после перехода
|
||||
на PocketBase он свернулся в один с `RETURNING` —
|
||||
[../database.md](../database.md), «Представление данных». Замер показал, что
|
||||
после перехода на PocketBase он сворачивается в один: движок за
|
||||
`modernc.org/sqlite` v1.55.0 — версии 3.53.3, `RETURNING` в нём есть, и на трёх
|
||||
горутинах разом запись получила **ровно одна**.
|
||||
|
||||
@@ -90,6 +90,10 @@ pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайн
|
||||
`modernc.org/sqlite`, а не через `mattn/go-sqlite3`. Требование CGO записано
|
||||
сегодня свойством стека в `../../CLAUDE.md`, и перевод его снимает.
|
||||
|
||||
*Уточнено 2026-08-12:* перевод состоялся, и требования CGO в стеке больше нет —
|
||||
[../../CLAUDE.md](../../CLAUDE.md), «Стек»: компилятор C нужен только детектору
|
||||
гонок в гейте.
|
||||
|
||||
Бинарник пробника — 33 954 634 байта против 43 498 904 у сегодняшнего приложения
|
||||
(`go build` без флагов). **Числа не сравнимы напрямую:** в пробнике нет ни бота,
|
||||
ни клиента SpeechKit, ни клиента Object Storage. Что даст сборка после перевода,
|
||||
|
||||
+19
-16
@@ -2,11 +2,12 @@
|
||||
|
||||
## Как настроен конвейер
|
||||
|
||||
Конвейер ревью прогонялся один раз — 2026-08-11, на изменении
|
||||
`fix-http-handler-tests`; его триаж лежит в
|
||||
`openspec/changes/archive/2026-08-11-fix-http-handler-tests/review/triage.md`.
|
||||
Разделы ниже заполнены наперёд по коду и правятся по итогам прогонов: «Типовые
|
||||
ложноположительные» первым прогоном уже пользовались.
|
||||
Артефакты шести прогонов лежат в `openspec/changes/archive/<id>/review/`: у трёх
|
||||
ранних, начиная с `fix-http-handler-tests` 2026-08-11, это `triage.md`, у трёх
|
||||
поздних — `report.md`. Сверх них конвейер прогонялся 2026-08-13 на работе, шедшей
|
||||
без своего изменения openspec; артефакта в архиве у тех прогонов нет, и урожай их
|
||||
виден только записями журнала ниже. Разделы ниже заведены наперёд по коду
|
||||
2026-08-11 и с тех пор правятся урожаем прогонов.
|
||||
|
||||
Что уже проверяет машина и о чём поэтому спрашивать не нужно — конвенция
|
||||
[conventions/go-linters.md](conventions/go-linters.md). Вопросы ниже — то, чего
|
||||
@@ -144,9 +145,10 @@
|
||||
`createTranscribeJob` — сегодня через него идут оба входа
|
||||
([architecture.md](architecture.md), «Единые точки проекта»).
|
||||
- `architecture`: не поехало ли поведение в `architecture.md` вместо спеки —
|
||||
заведены две capability (`openspec/specs/intake` и `openspec/specs/pipeline`),
|
||||
и каждая описана частично. Поведение прочих узлов живёт в обзоре под маркерами
|
||||
долга, а соблазн дописать туда ещё — самый большой.
|
||||
заведены пять capability (`intake`, `pipeline`, `storage`, `access`,
|
||||
`toolchain`), и первые две описаны частично. Поведение прочих узлов, включая
|
||||
приём из Telegram, живёт в обзоре под маркерами долга, а соблазн дописать туда
|
||||
ещё — самый большой.
|
||||
- `conventions`: новая колонка правится во всех четырёх местах репозитория
|
||||
(CLAUDE.md, «Инварианты»).
|
||||
- `autotests`: покрыт ли изменённый шаг конвейера хоть одним тестом — сегодня
|
||||
@@ -240,11 +242,11 @@ API и имя не откатываются обратной правкой по
|
||||
|
||||
## Журнал дефектов
|
||||
|
||||
Верхняя запись найдена конвейером ревью на первом же его прогоне, вторая —
|
||||
прогоном гейта при заведении канона 2026-08-10, две нижние восстановлены по
|
||||
истории git тогда же. Три нижние помечены `проскочил`: ревью тогда не было, и
|
||||
поймать их было некому. У восстановленных нет поля «Чем воспроизведён», и
|
||||
выдумывать его задним числом нельзя.
|
||||
Записи новые сверху. `[пойман ревью]` — дефект нашёл прогон конвейера,
|
||||
`[пойман сканером]` — тест-сканер `internal/archrules`, `[проскочил]` — дефект
|
||||
уехал в код, и поймать его тогда было некому. Две нижние записи восстановлены по
|
||||
истории git 2026-08-10: поле «Чем воспроизведён» называет у них коммит, а не
|
||||
оракул, и выдумывать оракул задним числом нельзя.
|
||||
|
||||
## 2026-08-13 — остановка сервиса хоронила конвертируемую запись [пойман ревью]
|
||||
|
||||
@@ -297,7 +299,7 @@ API и имя не откатываются обратной правкой по
|
||||
оценкой «сегодня она не логируется — то есть утечки нет», и оценка была
|
||||
неверной. Строка лога существовала всё это время, но проза о ней не знала, а
|
||||
машина прозу не проверяет
|
||||
- **Что меняем:** чистка перенесена с места употребления на **границу клиента** —
|
||||
- **Что меняем:** чистку перенесли с места употребления на **границу клиента** —
|
||||
`internal/adapter/telegram`, `NewBot`: свой `Do` разворачивает отказ в
|
||||
первопричину, а подменённый логгер библиотеки вычищает токен из строк длинного
|
||||
опроса, которые она печатает сама, мимо нашего `slog`. Транспорт бота токена
|
||||
@@ -352,8 +354,9 @@ API и имя не откатываются обратной правкой по
|
||||
- **Что меняем:** правило судит по типу приёмника (`analyze-types`,
|
||||
`httptest.ResponseRecorder.Header` и `.HeaderMap`) и ловит все шесть форм;
|
||||
проверено мутацией по каждой. Отсюда же строка в
|
||||
docs/conventions/go-linters.md, «Лестница механизации»: запрет по имени, обходимый лишней строкой, — это ступень
|
||||
тест-сканера, наряженная запретом
|
||||
docs/conventions/go-linters.md, «Лестница механизации»: запрет по имени,
|
||||
обходимый лишней строкой, требует ступени тест-сканера, хотя выглядит запретом
|
||||
по имени
|
||||
|
||||
## 2026-08-12 — закрыли поверхность так, что войти не мог никто [пойман ревью]
|
||||
|
||||
|
||||
+9
-6
@@ -122,8 +122,10 @@ Telegram отправителю.
|
||||
- **Поверхность самого хранилища.** Вместе с переводом наружу выходят
|
||||
`/api/collections/...`, `/api/logs`, `/api/backups`, `/api/settings`,
|
||||
`/api/crons` и панель `/_/`. Правила доступа коллекций оставлены пустыми, то
|
||||
есть доступны они только владельцу панели; проверено прогоном — записи отдают
|
||||
`403`, служебные разделы `401`.
|
||||
есть доступны они только владельцу панели; коды, снятые прогоном, —
|
||||
[database.md](database.md), «Коллекции», норма —
|
||||
[storage](../openspec/specs/storage/spec.md), «Наружу хранилище отдаёт только
|
||||
то, что заказано».
|
||||
|
||||
Целевой периметр добавляет сюда три вещи, и все три — от новых задач:
|
||||
|
||||
@@ -143,9 +145,10 @@ Telegram отправителю.
|
||||
с фамилией), а не с числовым идентификатором. Имя пользователя Telegram
|
||||
меняется владельцем в любой момент: список привязан к изменяемому значению.
|
||||
- **HTTP API** — сессия, заведённая входом через OIDC у Authelia. Предъявляется
|
||||
кукой `transcriber_session`, живёт семь суток, обесценивается выходом.
|
||||
кукой `transcriber_session`, обесценивается выходом, срок жизни назначен числом
|
||||
([database.md](database.md), «Настройки с числовым значением»).
|
||||
Продление сессии закрыто: с ним предъявитель менял бы своё значение на новое
|
||||
бессрочно, и семисуточный срок — единственное, чем отзыв доступа у провайдера
|
||||
бессрочно, и назначенный срок — единственное, чем отзыв доступа у провайдера
|
||||
доходит до сервиса, — не значил бы ничего.
|
||||
Предъявленный заголовок `Authorization` принимается тоже — это та же сессия и
|
||||
та же проверка, но она названа здесь отдельно, потому что это второй способ
|
||||
@@ -271,8 +274,8 @@ Telegram отправителю.
|
||||
`…/sendMessage`, `…/getMe`, `…/getUpdates`) и в ссылке на скачивание
|
||||
(`file.Link(token)`). Сами адреса нигде не логируются, но до 2026-08-13 их
|
||||
уносил **отказ транспорта**: `*url.Error` встраивает адрес целиком, а отказы
|
||||
скачивания и отправки пишутся в журнал. Теперь адрес снимается на границе
|
||||
клиента — `internal/adapter/telegram`, `NewBot`: свой `Do` чистит отказ, а
|
||||
скачивания и отправки пишутся в журнал. Теперь адрес на границе клиента снимает
|
||||
свой `Do` — `internal/adapter/telegram`, `NewBot`: он чистит отказ, а
|
||||
подменённый логгер библиотеки вычищает токен из строк длинного опроса, которые
|
||||
она печатает сама. Транспорт бота токена больше не получает вовсе: клиента ему
|
||||
отдают готовым. Правило — [conventions/logging.md](conventions/logging.md),
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
//
|
||||
// Шаги лежат своим каталогом, а не файлом внутри пакета репозитория, и причина
|
||||
// внешняя: сверка документов ловит изменённый шаг схемы при нетронутом
|
||||
// `docs/database.md` по префиксу пути (`docs/.docs.json`, ключ `migrations`), а
|
||||
// префикс наводится только на каталог. Пока шаги лежали файлом, наводить его
|
||||
// `docs/database.md` по префиксу пути (`.av-dev.toml`, ключ `migrations` секции
|
||||
// `[docs]`), а префикс наводится только на каталог. Пока шаги лежали файлом,
|
||||
// наводить его
|
||||
// было не на что, и проверка молчала на всякой правке схемы.
|
||||
package migrations
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ context: |
|
||||
первым молча, и заметно это становится в предложении, которое уже написано.
|
||||
|
||||
Ревью: правило выбора метки и состав проходов здесь не пересказываем — их дом
|
||||
скилл av-dev-code:review, проектная настройка — docs/review.md.
|
||||
скилл av-dev:code-review, проектная настройка — docs/review.md.
|
||||
|
||||
Конвенции кода: механизированное проверяет гейт, прозой остаётся
|
||||
docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"tasks": 1
|
||||
}
|
||||
+2
-1
@@ -6,7 +6,7 @@
|
||||
это очередь, и первая строка — то, что делают следующим. Порядок
|
||||
назначает человек на груминге, машина его не выводит. Одно исключение
|
||||
производно от типа — сырьё (`research` без раздела «Вопрос»)
|
||||
стоит в конце: его не берут. Ведётся скиллом `tasks`.
|
||||
стоит в конце: его не берут. Ведётся скиллом `av-dev:task-track`.
|
||||
|
||||
Секция одна — полок домена у проекта нет, и делить очередь на две
|
||||
значило бы держать два порядка вместо одного.
|
||||
@@ -98,4 +98,5 @@
|
||||
- [🧹 Запретить обращаться к 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, и по ним читатель строит решения о выкладке и о периметре.
|
||||
- [🧹 Свести Purpose спеки pipeline с её же требованиями](items/pipeline-spec-purpose-drift.md) — Преамбула спеки объявляет сознательно неописанными пять требований, которые в ней же и стоят с 2026-08-12: читатель узнаёт границу нормы из раздела, который ей противоречит.
|
||||
- [🔬 Квота по общему размеру загруженного на пользователя](items/per-user-size-quota.md) — Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
## Критерии приёмки
|
||||
|
||||
- Ни одно из шести мест не отвечает на свой вопрос двумя способами. Оракул —
|
||||
повторный прогон `av-dev-docs:healthcheck`: перечисленные шесть находок не
|
||||
повторный прогон `av-dev:doc-healthcheck`: перечисленные шесть находок не
|
||||
возвращаются.
|
||||
- Провайдер OIDC стоит в таблице внешних зависимостей со своими четырьмя
|
||||
столбцами отказа, и счёт зависимостей в «Открытых вопросах» сходится с
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
кодом 3.
|
||||
|
||||
Чего не знаем: ведут ли себя так же `docs.py check`, `tasks.py check` и
|
||||
`openspec.py check`. Скрипты чужие — они живут в плагинах `av-dev-docs`,
|
||||
`av-dev-tasks` и `av-dev-code`, и править их в этом репозитории нельзя. Отсюда и
|
||||
тип записи: способ починки зависит от ответа. Найдётся страж внутри — делать
|
||||
`openspec.py check`. Скрипты чужие — они живут в плагине `av-dev`, и править их
|
||||
в этом репозитории нельзя. Отсюда и тип записи: способ починки зависит от
|
||||
ответа. Найдётся страж внутри — делать
|
||||
нечего; не найдётся — либо обёртка в `Taskfile.yml` со своей проверкой предмета,
|
||||
либо разговор с владельцем плагина.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Шаг заведён 2026-08-13 и проверен мутацией на восьми исходах вручную — правка
|
||||
уехавшего шага в дереве и в коммите, удаление, переименование, новый шаг, правка
|
||||
`migrations.go`, отсутствующий ключ в `docs/.docs.json`, каталог без шагов,
|
||||
`migrations.go`, отсутствующий ключ в `.av-dev.toml`, каталог без шагов,
|
||||
неразрешимая база диффа. Прогон был разовым: в дереве от него не осталось ничего.
|
||||
|
||||
Прецедент рядом. У шага сверки версий Go есть спека
|
||||
@@ -24,7 +24,7 @@
|
||||
## Затрагивает
|
||||
|
||||
- шаг `migrations` в `Taskfile.yml` — его логика разбора `git diff`;
|
||||
- ключ `migrations` в `docs/.docs.json` — из него шаг берёт каталог;
|
||||
- ключ `migrations` секции `[docs]` в `.av-dev.toml` — из него шаг берёт каталог;
|
||||
- каталог шагов схемы `internal/adapter/repo/pocketbase/migrations/` как предмет
|
||||
правила;
|
||||
- возможно — новая capability в `openspec/specs/` и файл проверок рядом с
|
||||
@@ -36,7 +36,7 @@
|
||||
временном клоне репозитория: правка файла шага даёт код 1 и называет файл.
|
||||
- Новый файл шага проверку не роняет, и правка `migrations.go` тоже: строка
|
||||
`Register` нового шага прибавляется именно там. Оракул — те же два сценария.
|
||||
- Каталог без единого файла шага и отсутствующий ключ в `docs/.docs.json` дают
|
||||
- Каталог без единого файла шага и отсутствующий ключ в `.av-dev.toml` дают
|
||||
код 3, а не тихий ноль. Оракул — два сценария на временном каталоге.
|
||||
- Проверка сценариев идёт в гейте, а не руками. Оракул — `task gate` красный при
|
||||
внесённом нарушении шаблона имени файла шага.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# 🧹 Свести Purpose спеки pipeline с её же требованиями
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь — Правка одного раздела спеки, но откладывать её значит держать нормативный документ противоречащим себе.
|
||||
- **Зачем:** Преамбула спеки объявляет сознательно неописанными пять требований, которые в ней же и стоят с 2026-08-12: читатель узнаёт границу нормы из раздела, который ей противоречит.
|
||||
|
||||
Нашла сверка документов 2026-08-13. `Purpose` спеки `pipeline` перечисляет как
|
||||
сознательно неописанные захват задачи и срок его протухания, число попыток,
|
||||
состояние «мертва» и паузу перед повтором. Ниже в той же спеке эти требования
|
||||
стоят: их дописало изменение `pocketbase-storage` 2026-08-12, а преамбулу не
|
||||
поправило.
|
||||
|
||||
Не переехала в спеку одна вещь — цепочка переходов
|
||||
`created` → `converted` → `transcribe` → `done` либо `failed`. Маркер долга в
|
||||
[architecture.md](../../docs/architecture.md) уже уточнён под это и называет
|
||||
неперехавшей именно цепочку, так что после правки `Purpose` два документа
|
||||
сойдутся.
|
||||
|
||||
Материал для замены — формулировка из отчёта сверки:
|
||||
|
||||
> Описаны: пустой прогон воркера, неделимость захвата и срок его протухания,
|
||||
> число попыток и состояние «мертва», условность записи результата по признаку
|
||||
> захвата, нарастающая пауза перед повтором. Сознательно не описаны: цепочка
|
||||
> переходов `created` → `converted` → `transcribe` → `done` либо `failed`,
|
||||
> отмена контекста посреди шага, освобождение ресурсов внешних клиентов. Это не
|
||||
> значит, что такого поведения нет: оно живёт в коде, а требования на него не
|
||||
> написаны, потому что требование без проверки — предположение, а не норма.
|
||||
> Первая задача, которая трогает любое из перечисленного, дописывает его сюда.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- раздел `Purpose` в `openspec/specs/pipeline/spec.md` — требований спеки правка
|
||||
не касается, они уже написаны;
|
||||
- маркер долга о поведении в `docs/architecture.md` — как парная сторона
|
||||
утверждения о том, что ещё не переехало.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- `Purpose` не называет неописанным ни одно требование, которое в спеке стоит.
|
||||
Оракул — построчная сверка перечня из `Purpose` с заголовками `Requirement`
|
||||
той же спеки: пересечения нет.
|
||||
- Спека остаётся годной для инструмента. Оракул — `openspec validate --strict`
|
||||
отрабатывает без отказа.
|
||||
- Маркер долга в `docs/architecture.md` и `Purpose` называют неперехавшим одно и
|
||||
то же. Оракул — чтение обоих мест подряд: перечни совпадают.
|
||||
|
||||
## Рамки
|
||||
|
||||
Правится преамбула, а не требования: поведение сервиса задача не меняет и кода
|
||||
не трогает. Спека правится изменением openspec своим порядком, а не прямой
|
||||
правкой файла. Соседняя задача
|
||||
[context-cancel-in-pipeline](context-cancel-in-pipeline.md) уберёт из перечня
|
||||
неописанного отмену контекста, когда доедет, — здесь эта строка остаётся на
|
||||
месте.
|
||||
@@ -42,7 +42,7 @@
|
||||
## Рамки
|
||||
|
||||
Правится только настройка конвейера в `docs/review.md`. Устав самого конвейера
|
||||
живёт в плагине `av-dev-code` и этой задачей не трогается: проект вправе
|
||||
живёт в скилле `av-dev:code-review` и этой задачей не трогается: проект вправе
|
||||
настраивать свои темы и триггеры, но не переписывать чужой скилл.
|
||||
|
||||
Журнал дефектов в том же файле не трогается — записи неизменяемы.
|
||||
|
||||
Reference in New Issue
Block a user