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