docs: канон приведён к сегодняшнему состоянию после сверки
- периметр: passport.md и security.md больше не утверждают, что HTTP API открыт без аутентификации, а review.md не числит эту находку типовой ложноположительной — приём, опрос и файл закрыты сессией с 2026-08-12; - logging.md писал, что расширение попадает в журнал полем пути: описано изъятие инварианта приватности — собственное поле, имени и пути нет; - узел ревью переименован в repo/pocketbase, поведение конвейера из обзора уехало ссылкой в спеку pipeline, Purpose спеки storage написан вместо заглушки, в ADR о переезде дописано уточнение о действующей раскладке.
This commit is contained in:
@@ -67,6 +67,10 @@ PocketBase заменяет SQLite с goqu и goose и берёт на себя
|
|||||||
`pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайных символов>` рядом с
|
`pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайных символов>` рядом с
|
||||||
файлом атрибутов. Момент перехода назначает человек; данные прежней базы не
|
файлом атрибутов. Момент перехода назначает человек; данные прежней базы не
|
||||||
переносятся по прежнему решению задачи `pocketbase-storage`.
|
переносятся по прежнему решению задачи `pocketbase-storage`.
|
||||||
|
*Уточнено 2026-08-12:* каталог задаётся ключом `[storage] data_dir` со
|
||||||
|
значением `data`. Суффикс из десяти знаков дописывает конструктор имени,
|
||||||
|
которого сервис не зовёт, — имя задаёт он сам. Действующая раскладка —
|
||||||
|
[../database.md](../database.md), «Представление данных».
|
||||||
- `−` вход перестаёт быть нашим: задача `oidc-login` переписывается с
|
- `−` вход перестаёт быть нашим: задача `oidc-login` переписывается с
|
||||||
собственной обработки ответа провайдера на настройку провайдера в PocketBase.
|
собственной обработки ответа провайдера на настройку провайдера в PocketBase.
|
||||||
Что делать с сессией и где она живёт, решает уже не наш код.
|
Что делать с сессией и где она живёт, решает уже не наш код.
|
||||||
|
|||||||
+24
-14
@@ -12,9 +12,12 @@
|
|||||||
потребителей; пятая — исключение из первого абзаца: она нормирует не сервис, а
|
потребителей; пятая — исключение из первого абзаца: она нормирует не сервис, а
|
||||||
инструмент, которым его собирают, и потребитель у неё другой — тот, кто собирает.
|
инструмент, которым его собирают, и потребитель у неё другой — тот, кто собирает.
|
||||||
|
|
||||||
- [intake](../openspec/specs/intake/spec.md) — **только приём по HTTP**: его
|
- [intake](../openspec/specs/intake/spec.md) — **только приём по HTTP**: приём и
|
||||||
нормируют проверки, написанные задачей `http-handler-tests-never-green`
|
опрос за сессией, имя отправителя не доходит ни до хранилища, ни до журнала,
|
||||||
2026-08-11;
|
метка метрики несёт только известное расширение. Задачи
|
||||||
|
`http-handler-tests-never-green` и `no-user-filename-in-log` 2026-08-11,
|
||||||
|
`pocketbase-storage` и `oidc-login` 2026-08-12. Приём из Telegram здесь не
|
||||||
|
описан;
|
||||||
- [pipeline](../openspec/specs/pipeline/spec.md) — пустой прогон воркера, захват
|
- [pipeline](../openspec/specs/pipeline/spec.md) — пустой прогон воркера, захват
|
||||||
задачи и срок его протухания, число попыток, состояние «мертва» и пауза перед
|
задачи и срок его протухания, число попыток, состояние «мертва» и пауза перед
|
||||||
повтором: задачи `errors-as-instead-of-typecast` 2026-08-11 и
|
повтором: задачи `errors-as-instead-of-typecast` 2026-08-11 и
|
||||||
@@ -40,14 +43,17 @@
|
|||||||
|
|
||||||
- **Один процесс.** Бот, HTTP-сервер и фоновые воркеры живут в одном бинарнике и
|
- **Один процесс.** Бот, HTTP-сервер и фоновые воркеры живут в одном бинарнике и
|
||||||
делят одну базу. Отдельного воркер-процесса нет намеренно.
|
делят одну базу. Отдельного воркер-процесса нет намеренно.
|
||||||
- **Очередь таблицей.** Состояние задачи лежит коллекцией хранилища, воркер
|
- **Очередь таблицей.** Состояние задачи лежит коллекцией хранилища; неделимость
|
||||||
забирает работу одним запросом с захватом. Внешний брокер не заводим: нагрузка
|
захвата и порядок выборки нормирует
|
||||||
— единицы записей в день (оценка владельца, не замер). Готовую библиотеку
|
[pipeline](../openspec/specs/pipeline/spec.md), «Захват задачи неделим».
|
||||||
очереди тоже не заводим — решено 2026-08-11,
|
Внешний брокер не заводим: нагрузка — единицы записей в день (оценка владельца,
|
||||||
|
не замер). Готовую библиотеку очереди тоже не заводим — решено 2026-08-11,
|
||||||
[ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md), сравнение
|
[ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md), сравнение
|
||||||
кандидатов в [research/job-queue.md](research/job-queue.md).
|
кандидатов в [research/job-queue.md](research/job-queue.md).
|
||||||
- **Шаг конвейера идемпотентен по повтору.** Задача, брошенная на середине,
|
- **Шаг конвейера идемпотентен по повтору.** Что делает срок захвата и когда
|
||||||
достаётся снова по истечении срока захвата и проходит шаг заново.
|
задача возвращается в работу, нормирует
|
||||||
|
[pipeline](../openspec/specs/pipeline/spec.md), «Брошенная задача возвращается
|
||||||
|
в работу»; здесь это принцип письма шага, а не описание поведения.
|
||||||
- **Ядро зависит от интерфейсов.** `internal/service` знает только
|
- **Ядро зависит от интерфейсов.** `internal/service` знает только
|
||||||
`internal/contract`; ffmpeg, Yandex, Telegram и хранилище подставляются в
|
`internal/contract`; ffmpeg, Yandex, Telegram и хранилище подставляются в
|
||||||
`main.go`.
|
`main.go`.
|
||||||
@@ -149,11 +155,15 @@
|
|||||||
|
|
||||||
## Открытые вопросы
|
## Открытые вопросы
|
||||||
|
|
||||||
- **Учётные записи.** Вход через OIDC, провайдер — Authelia, а ответ провайдера
|
- **Учётные записи.** Вход через OIDC решён и развёрнут 2026-08-12: провайдер —
|
||||||
обрабатывает PocketBase, а не наш код
|
Authelia, ответ провайдера обрабатывает PocketBase, а не наш код
|
||||||
([ADR](adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)). Не решено, где живёт сессия
|
([ADR](adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)), сессия
|
||||||
и как связываются пользователь Telegram и пользователь веба. Панель
|
живёт кукой `transcriber_session` и сама себя не продлевает. Норма —
|
||||||
администратора при этом Authelia не закрывает: у неё свой пароль
|
[access](../openspec/specs/access/spec.md), решения —
|
||||||
|
[ADR-2026-08-12-session-without-refresh](adr/ADR-2026-08-12-session-without-refresh.md)
|
||||||
|
и [ADR-2026-08-12-oidc-exchange-via-own-route](adr/ADR-2026-08-12-oidc-exchange-via-own-route.md).
|
||||||
|
**Не решено одно:** как связываются пользователь Telegram и пользователь веба.
|
||||||
|
Панель администратора при этом Authelia не закрывает: у неё свой пароль
|
||||||
суперпользователя.
|
суперпользователя.
|
||||||
- **Приложение.** Экранов нет вовсе, есть только API. Решено делать SPA,
|
- **Приложение.** Экранов нет вовсе, есть только API. Решено делать SPA,
|
||||||
устанавливаемое на телефон, а фреймворком взят Vue 3 с роутером пятой версии и
|
устанавливаемое на телефон, а фреймворком взят Vue 3 с роутером пятой версии и
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ TOML. Пустой токен бота ловится в `NewTelegramController`
|
|||||||
## Структура в коде
|
## Структура в коде
|
||||||
|
|
||||||
- Весь разбор и проверка — в `internal/config`; наружу отдаётся готовая `Config`.
|
- Весь разбор и проверка — в `internal/config`; наружу отдаётся готовая `Config`.
|
||||||
- Одна корневая структура `Config` с под-структурами по секциям (`Server`,
|
- Одна корневая структура `Config` с под-структурами по секциям. Перечень секций
|
||||||
`Database`, `Storage`, `Yandex`, `Telegram`).
|
и полей здесь не повторяем: источник истины по составу — `config.dist.toml`,
|
||||||
|
действующие числа — [../database.md](../database.md), «Настройки с числовым
|
||||||
|
значением». Каталог данных задаётся одним ключом `[storage] data_dir`
|
||||||
|
([ADR](../adr/ADR-2026-08-12-single-data-dir-config-key.md)).
|
||||||
- Умолчания задаются в `defaultConfig()`, файл их перекрывает. Новое поле
|
- Умолчания задаются в `defaultConfig()`, файл их перекрывает. Новое поле
|
||||||
требует правки обоих мест.
|
требует правки обоих мест.
|
||||||
|
|||||||
@@ -135,8 +135,11 @@ transcriber — **приложение, а не библиотека**: внеш
|
|||||||
- Не для управления потоком и не для ожидаемых ошибок (нет сети, плохой ввод) —
|
- Не для управления потоком и не для ожидаемых ошибок (нет сети, плохой ввод) —
|
||||||
это значения `error`.
|
это значения `error`.
|
||||||
- `recover` — на верхней границе обработчика, чтобы один паникующий запрос не
|
- `recover` — на верхней границе обработчика, чтобы один паникующий запрос не
|
||||||
ронял процесс. В transcriber это `gin.Recovery()`; у воркеров и у бота такой
|
ронял процесс. В transcriber его вешает роутер хранилища сам
|
||||||
границы **нет**: паника в шаге конвейера роняет процесс целиком.
|
(`apis.panicRecover`, слой с идентификатором `DefaultPanicRecoverMiddlewareId`
|
||||||
|
на каждом роутере PocketBase): паникующий обработчик отдаёт `500`, процесс
|
||||||
|
живёт. Своего слоя мы не пишем. У воркеров и у бота такой границы **нет**:
|
||||||
|
паника в шаге конвейера роняет процесс целиком.
|
||||||
|
|
||||||
## Несколько ошибок
|
## Несколько ошибок
|
||||||
|
|
||||||
|
|||||||
@@ -241,11 +241,14 @@ Object Storage, скачивание файла из Telegram и опрос оп
|
|||||||
она не логируется — то есть утечки нет, но защищает от неё только отсутствие
|
она не логируется — то есть утечки нет, но защищает от неё только отсутствие
|
||||||
строки лога.
|
строки лога.
|
||||||
|
|
||||||
*Расхождение:* расширение берётся из имени отправителя дословно
|
*Изъятие, а не расхождение:* расширение берётся из имени отправителя дословно
|
||||||
(`filepath.Ext`), поэтому имя `запись.тайное-слово` отдаёт приватный хвост
|
(`filepath.Ext`), поэтому имя `запись.тайное-слово` отдаёт приватный хвост
|
||||||
расширением, и в журнал оно попадает полем пути. Наружу — в метку метрики — этот
|
расширением. В журнал оно идёт **собственным полем** строки приёма — это
|
||||||
хвост не выходит: там расширение приводится к перечню известных форматов. Остаток
|
объявленное изъятие инварианта приватности ([CLAUDE.md](../../CLAUDE.md),
|
||||||
описан в [../security.md](../security.md).
|
«Инварианты»); ни имени файла в хранилище, ни пути к нему в журнале нет вовсе
|
||||||
|
(норма — `openspec/specs/intake`). Наружу — в метку метрики — хвост не выходит:
|
||||||
|
там расширение приводится к перечню известных форматов. Остаток описан в
|
||||||
|
[../security.md](../security.md).
|
||||||
|
|
||||||
## Куда пишем и уровень
|
## Куда пишем и уровень
|
||||||
|
|
||||||
|
|||||||
+17
-9
@@ -22,7 +22,7 @@
|
|||||||
| Владелец сервиса | Загрузить диктофонную запись или видео из семейного архива с телефона и получить текст. Видеть, кто сколько загрузил и во что это обошлось |
|
| Владелец сервиса | Загрузить диктофонную запись или видео из семейного архива с телефона и получить текст. Видеть, кто сколько загрузил и во что это обошлось |
|
||||||
| Приглашённый пользователь | Войти в приложение через свою учётную запись, загрузить запись, забрать текст, вернуться к ней через месяц. Приложение ставится на телефон; каждый видит только свои записи |
|
| Приглашённый пользователь | Войти в приложение через свою учётную запись, загрузить запись, забрать текст, вернуться к ней через месяц. Приложение ставится на телефон; каждый видит только свои записи |
|
||||||
| Пользователь Telegram | Отправить боту голосовое сообщение и получить текст ответом. Работает сегодня |
|
| Пользователь Telegram | Отправить боту голосовое сообщение и получить текст ответом. Работает сегодня |
|
||||||
| Внешняя программа | Отдать файл по HTTP, представившись своим токеном, и опросить готовность. Работает сегодня, но токенов нет и доступ не разграничен |
|
| Внешняя программа | Отдать файл по HTTP, представившись своим токеном, и опросить готовность. Сегодня почти не работает: приём и опрос закрыты сессией OIDC, а своего токена у программы нет — годится только кука, снятая из браузера. Токен приносит `api-tokens` |
|
||||||
|
|
||||||
**Основной вход — приложение**, бот и HTTP API дополняют его. До 2026-08-11
|
**Основной вход — приложение**, бот и HTTP API дополняют его. До 2026-08-11
|
||||||
основным был бот, и порядок здесь перевёрнут сознательно: диктофонная запись на
|
основным был бот, и порядок здесь перевёрнут сознательно: диктофонная запись на
|
||||||
@@ -68,8 +68,11 @@
|
|||||||
файл. Своей записи и работы без сети не делаем — граница цели
|
файл. Своей записи и работы без сети не делаем — граница цели
|
||||||
[web-access](../tasks/items/web-access.md).
|
[web-access](../tasks/items/web-access.md).
|
||||||
- **Файловое хранилище общего назначения.** Храним аудио и видео, отданные ради
|
- **Файловое хранилище общего назначения.** Храним аудио и видео, отданные ради
|
||||||
речи в них. Складом произвольных файлов, папками и общим доступом к чужим
|
речи в них. Складом произвольных файлов и папками сервис не становится. Общего
|
||||||
записям сервис не становится.
|
доступа к чужим записям целью тоже нет — но **сегодня он есть**: владельца у
|
||||||
|
записи в модели данных не существует, и всякий вошедший видит все записи
|
||||||
|
([security.md](security.md), «Периметр»). Это состояние, а не решение;
|
||||||
|
закрывает его `record-ownership`.
|
||||||
- **Учёт денег.** Считаем объём, минуты и токены по каждому пользователю и
|
- **Учёт денег.** Считаем объём, минуты и токены по каждому пользователю и
|
||||||
показываем их владельцу. Цен, счетов и отказов по исчерпании квоты не делаем:
|
показываем их владельцу. Цен, счетов и отказов по исчерпании квоты не делаем:
|
||||||
пользователя, потратившего слишком много, останавливает разговор или отзыв
|
пользователя, потратившего слишком много, останавливает разговор или отзыв
|
||||||
@@ -95,8 +98,10 @@
|
|||||||
отличает их по MIME-типу и расширению. Работает сегодня.
|
отличает их по MIME-типу и расширению. Работает сегодня.
|
||||||
5. **Загрузка по HTTP.** Программа шлёт `POST /api/audio` со своим токеном,
|
5. **Загрузка по HTTP.** Программа шлёт `POST /api/audio` со своим токеном,
|
||||||
получает идентификатор задачи и опрашивает `GET /api/status/:id`, пока не
|
получает идентификатор задачи и опрашивает `GET /api/status/:id`, пока не
|
||||||
увидит `done` и текст. Работает сегодня, но без токена и без разграничения
|
увидит `done` и текст. Сегодня доступно только предъявившему сессию OIDC:
|
||||||
доступа.
|
анонимный запрос обоими адресами отклоняется. Своего входа у программы нет —
|
||||||
|
его заводит `api-tokens`, — как нет и разграничения записей между
|
||||||
|
пользователями.
|
||||||
6. **Отказ на середине.** Конвертация или распознавание не удались — задача
|
6. **Отказ на середине.** Конвертация или распознавание не удались — задача
|
||||||
переходит в `failed`, а пользователь получает сообщение о том, что именно не
|
переходит в `failed`, а пользователь получает сообщение о том, что именно не
|
||||||
вышло, и предложение повторить.
|
вышло, и предложение повторить.
|
||||||
@@ -109,7 +114,10 @@
|
|||||||
которой пользуемся: она и задаёт потолок по длине записи и формату.
|
которой пользуемся: она и задаёт потолок по длине записи и формату.
|
||||||
- **Whisper и его серверные обёртки** — запасной путь, если внешний сервис
|
- **Whisper и его серверные обёртки** — запасной путь, если внешний сервис
|
||||||
перестанет устраивать по цене или по качеству русской речи.
|
перестанет устраивать по цене или по качеству русской речи.
|
||||||
- **PocketBase** — хранилище взамен сегодняшнего SQLite, решено 2026-08-11
|
**PocketBase** из референсов ушла: она больше не кандидат — в стек её перевела
|
||||||
([adr](adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)). Учётные
|
задача `pocketbase-storage` 2026-08-12
|
||||||
записи оно хранит и получает от Authelia своим провайдером OIDC, но источником
|
([adr](adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)); там она
|
||||||
их не становится: заводит и проверяет людей по-прежнему Authelia.
|
держит хранилище, файлы и панель владельца. Схема и
|
||||||
|
раскладка — [database.md](database.md). Учётные записи она хранит и получает от
|
||||||
|
Authelia своим провайдером OIDC, но источником их не становится: заводит и
|
||||||
|
проверяет людей по-прежнему Authelia.
|
||||||
|
|||||||
+15
-8
@@ -38,12 +38,16 @@
|
|||||||
- вырожденный ответ (пустой, усечённый, без ожидаемого поля) не превращает в
|
- вырожденный ответ (пустой, усечённый, без ожидаемого поля) не превращает в
|
||||||
успех молча.
|
успех молча.
|
||||||
|
|
||||||
**Репозиторий SQLite** (`adapter/repo/sqlite`):
|
**Репозиторий хранилища** (`internal/adapter/repo/pocketbase`):
|
||||||
|
|
||||||
- список колонок совпадает во всех четырёх запросах файла;
|
- список колонок совпадает во всех четырёх местах — `applyToRecord`,
|
||||||
- `NULL` в колонке разбирается в указатель, а не роняет `Scan`;
|
`recordToJob`, `acquireColumns`, `acquiredRow` — и в шаге схемы (инвариант
|
||||||
- захват задачи не выдаёт одну строку двум вызывающим;
|
[CLAUDE.md](../CLAUDE.md), «Инварианты»);
|
||||||
- ошибка драйвера транслируется в доменную у источника.
|
- захват задачи не выдаёт одну строку двум вызывающим, а результат пишет только
|
||||||
|
держатель захвата;
|
||||||
|
- репозиторий кладёт время в сыром запросе тем же видом, каким хранилище пишет
|
||||||
|
свои `created`/`updated` ([database.md](database.md), «Представление данных»);
|
||||||
|
- отказ хранилища не выходит наружу дословно: он несёт ключ файла целиком.
|
||||||
|
|
||||||
**Обёртка над внешним процессом** (`adapter/converter/ffmpeg`,
|
**Обёртка над внешним процессом** (`adapter/converter/ffmpeg`,
|
||||||
`adapter/metaviewer/ffmpeg`):
|
`adapter/metaviewer/ffmpeg`):
|
||||||
@@ -95,9 +99,12 @@
|
|||||||
- **«Файлы и объекты не удаляются, диск растёт».** Факт верный и записан в
|
- **«Файлы и объекты не удаляются, диск растёт».** Факт верный и записан в
|
||||||
[database.md](database.md); срок хранения не задан сознательно, задачи на него нет.
|
[database.md](database.md); срок хранения не задан сознательно, задачи на него нет.
|
||||||
Новой находкой это не считается, пока не измерен рост.
|
Новой находкой это не считается, пока не измерен рост.
|
||||||
- **«HTTP API открыт без аутентификации».** Известно и записано первой строкой
|
- **«У записи нет владельца: вошедший видит чужие записи».** Не дефект и не
|
||||||
[security.md](security.md). Находкой считается только новая поверхность,
|
новость: приём, опрос и файл закрыты сессией OIDC с 2026-08-12, а
|
||||||
выставленная наружу, а не повторение этого факта.
|
разграничения по владельцу нет сознательно — [security.md](security.md),
|
||||||
|
«Периметр», и `openspec/specs/access`, «Purpose». Находкой считается новая
|
||||||
|
поверхность, выставленная наружу, либо путь к содержимому записи **без**
|
||||||
|
сессии, а не повторение этого факта.
|
||||||
|
|
||||||
### Вопросы по темам
|
### Вопросы по темам
|
||||||
|
|
||||||
|
|||||||
+8
-6
@@ -53,8 +53,8 @@
|
|||||||
|
|
||||||
| Вход | Канал | Кто может слать |
|
| Вход | Канал | Кто может слать |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Аудиофайл и его имя | `POST /api/audio`, multipart-поле `audio` | Любой из интернета |
|
| Аудиофайл и его имя | `POST /api/audio`, multipart-поле `audio` | Любой вошедший через OIDC; без сессии — `401` до чтения тела |
|
||||||
| Идентификатор задачи | `GET /api/status/:id` | Любой из интернета |
|
| Идентификатор задачи | `GET /api/status/:id` | Любой вошедший через OIDC; без сессии — `401`, одинаковый для заведённой и незаведённой задачи |
|
||||||
| Голосовое, аудио, документ | Telegram, длинный опрос | Любой пользователь Telegram; обрабатывается только из белого списка |
|
| Голосовое, аудио, документ | Telegram, длинный опрос | Любой пользователь Telegram; обрабатывается только из белого списка |
|
||||||
| Имя файла в Telegram | Поле `file_path` ответа Bot API | Telegram, а через него — отправитель |
|
| Имя файла в Telegram | Поле `file_path` ответа Bot API | Telegram, а через него — отправитель |
|
||||||
| Содержимое аудио | Файл, скармливаемый `ffmpeg` и `ffprobe` | Отправитель по любому из каналов |
|
| Содержимое аудио | Файл, скармливаемый `ffmpeg` и `ffprobe` | Отправитель по любому из каналов |
|
||||||
@@ -283,10 +283,12 @@ Telegram отправителю.
|
|||||||
- **Стойкость к целенаправленной нагрузке.** Ограничения по числу запросов и по
|
- **Стойкость к целенаправленной нагрузке.** Ограничения по числу запросов и по
|
||||||
размеру файла нет, и защищаться от исчерпания диска мы сейчас не пытаемся.
|
размеру файла нет, и защищаться от исчерпания диска мы сейчас не пытаемся.
|
||||||
- **Исчерпание диска приглашёнными.** Записи и тексты хранятся бессрочно
|
- **Исчерпание диска приглашёнными.** Записи и тексты хранятся бессрочно
|
||||||
(паспорт, 2026-08-11), шестичасовая запись весит единицы гигабайт — оценка, а не
|
(паспорт, 2026-08-11). Шестичасовая запись весит единицы гигабайт — оценка, а
|
||||||
замер: `research/` пуст, потолок длины стоит открытым вопросом
|
не замер: распределения длин у сервиса нет, а самая длинная проверенная запись
|
||||||
`architecture.md`, «Долгие записи», — а квот нет и не будет: решено считать расход и показывать его владельцу, а не отказывать
|
— 9,6 МБ ([research/pocketbase-defaults.md](research/pocketbase-defaults.md)).
|
||||||
(цель `usage-stats`). Перебравшего останавливает разговор или отзыв доступа в
|
Потолок длины стоит открытым вопросом `architecture.md`, «Долгие записи». Квот
|
||||||
|
нет и не будет: решено считать расход и показывать его владельцу, а не
|
||||||
|
отказывать (цель `usage-stats`). Перебравшего останавливает разговор или отзыв доступа в
|
||||||
Authelia. Рост каталога данных при этом ничем не наблюдается —
|
Authelia. Рост каталога данных при этом ничем не наблюдается —
|
||||||
открытый вопрос `architecture.md`.
|
открытый вопрос `architecture.md`.
|
||||||
- **Перерасход денег на внешних сервисах.** Распознавание и языковая модель
|
- **Перерасход денег на внешних сервисах.** Распознавание и языковая модель
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ Telegram делит с ним общий шаг заведения задачи,
|
|||||||
|
|
||||||
- **GIVEN** источник метаданных читает запись и отдаёт её длительность
|
- **GIVEN** источник метаданных читает запись и отдаёт её длительность
|
||||||
- **WHEN** программа шлёт запись с именем, чей хвост после последней точки не
|
- **WHEN** программа шлёт запись с именем, чей хвост после последней точки не
|
||||||
принадлежит перечню known-форматов
|
принадлежит перечню известных форматов
|
||||||
- **THEN** метка метрики принимает значение `other`
|
- **THEN** метка метрики принимает значение `other`
|
||||||
- **AND** имя файла в хранилище сохраняет пришедшее расширение
|
- **AND** имя файла в хранилище сохраняет пришедшее расширение
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
# storage Specification
|
# storage Specification
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
TBD - created by archiving change pocketbase-storage. Update Purpose after archive.
|
|
||||||
|
Где живут запись, её метаданные и её файл: раскладка каталога данных, приведение
|
||||||
|
схемы при подъёме, отдача файла ссылкой по токену, собственная поверхность
|
||||||
|
хранилища и панель владельца.
|
||||||
|
|
||||||
|
Приём и опрос готовности нормирует `intake`, вход и сессию — `access`.
|
||||||
|
Сознательно не описаны: перенос прежних данных — его нет по решению задачи
|
||||||
|
`pocketbase-storage`; удаление записей и файлов — сервис объявлен архивом
|
||||||
|
2026-08-11, а удаление приносит задача `delete-record`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
### Requirement: Сервис поднимается на чистом каталоге данных
|
### Requirement: Сервис поднимается на чистом каталоге данных
|
||||||
|
|
||||||
|
|||||||
@@ -162,10 +162,10 @@ Capability нормирует **не поведение сервиса** для
|
|||||||
и MUST не зависеть от рабочего каталога, из которого запущен. Шаг MUST только
|
и MUST не зависеть от рабочего каталога, из которого запущен. Шаг MUST только
|
||||||
читать: файлов он не правит и разошедшихся мест не чинит.
|
читать: файлов он не правит и разошедшихся мест не чинит.
|
||||||
|
|
||||||
Коды выхода MUST следовать словарю прочих проверочных шагов проекта: 0 сошлось,
|
Коды выхода MUST следовать общему словарю проверочных шагов проекта; словарь
|
||||||
1 расхождение, 2 ошибка употребления, 3 окружение. Своего словаря шаг MUST не
|
объявляет раздел «Гейт» в `CLAUDE.md`, и здесь он не повторяется. Своего словаря шаг
|
||||||
заводить: раздел «Гейт» в `CLAUDE.md` объявляет словарь общим, и четвёртый шаг с
|
MUST не заводить: четвёртый шаг с собственной семантикой сделал бы это
|
||||||
собственной семантикой сделал бы это утверждение неверным.
|
утверждение неверным.
|
||||||
|
|
||||||
Место, где числа не нашлось вовсе, MUST считаться отказом с именем этого места.
|
Место, где числа не нашлось вовсе, MUST считаться отказом с именем этого места.
|
||||||
«Нечего сравнивать» исходом MUST не быть: пропавшая строка иначе выглядела бы
|
«Нечего сравнивать» исходом MUST не быть: пропавшая строка иначе выглядела бы
|
||||||
|
|||||||
Reference in New Issue
Block a user