записано решение уйти с PocketBase на SQLite со своим каталогом файлов
- разведка storage-without-pocketbase: шесть ролей библиотеки в этом коде, отпавший довод перевода, шесть модулей достижимы только через неё - ADR-2026-08-22-storage-without-pocketbase заменяет три записи: перевод в PocketBase, очередь коллекцией, файл за защищённым полем - задача storage-without-pocketbase встала первой строкой плана стройки
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
- **Дата:** 2026-08-11
|
||||
- **Источник:** [../research/pocketbase.md](../research/pocketbase.md) — записка
|
||||
разведки `pocketbase-admin-fit`
|
||||
- **Статус:** заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md)
|
||||
|
||||
## Решение
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
- **Дата:** 2026-08-11
|
||||
- **Источник:** [../research/job-queue.md](../research/job-queue.md) — записка
|
||||
разведки `job-queue-choice`
|
||||
- **Статус:** заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md)
|
||||
|
||||
## Решение
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
раздел «Что изменило ревью кода», плюс отчёт триажа
|
||||
[../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
пункт 3
|
||||
- **Статус:** заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md)
|
||||
|
||||
## Решение
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Хранилищем становится SQLite с каталогом файлов, а PocketBase уходит целиком
|
||||
|
||||
- **Дата:** 2026-08-22
|
||||
- **Источник:** [../research/storage-without-pocketbase.md](../research/storage-without-pocketbase.md) —
|
||||
записка разведки о выборе хранилища
|
||||
|
||||
## Решение
|
||||
|
||||
PocketBase уходит из проекта целиком: состояние записей и метаданные переезжают
|
||||
в SQLite, с которым сервис работает напрямую через `modernc.org/sqlite`, файлы
|
||||
записей — в свой каталог со своей раскладкой, маршруты и слои — на `net/http`,
|
||||
шаги схемы — на свой раннер. Панель администратора теряется и **не заменяется
|
||||
ничем**: пока идёт стройка, остановленную запись возвращает в работу запрос к
|
||||
базе.
|
||||
|
||||
Запись заменяет три:
|
||||
[ADR-2026-08-11-pocketbase-storage-with-admin-panel](ADR-2026-08-11-pocketbase-storage-with-admin-panel.md),
|
||||
[ADR-2026-08-11-queue-as-pocketbase-collection](ADR-2026-08-11-queue-as-pocketbase-collection.md) и
|
||||
[ADR-2026-08-12-protected-file-behind-session](ADR-2026-08-12-protected-file-behind-session.md).
|
||||
|
||||
**Что из заменённых решений подтверждается, а не отменяется.** Очередь остаётся
|
||||
своей таблицей, захват — одним запросом с `RETURNING`, готовую библиотеку
|
||||
очереди по-прежнему не берём: замер, которым это решено, снят на
|
||||
`modernc.org/sqlite` — том самом драйвере, который остаётся и после ухода.
|
||||
Отменяется у той записи одно слово: таблица перестаёт быть коллекцией.
|
||||
Приложение остаётся в своём корне `/app/`
|
||||
([ADR-2026-08-15-app-namespace](ADR-2026-08-15-app-namespace.md)). Файл записи
|
||||
остаётся закрытым — но проверкой владельца в своём обработчике, а не защищённым
|
||||
полем коллекции и коротким токеном.
|
||||
|
||||
## Почему
|
||||
|
||||
Разведка мерила не «хранилище против хранилища», а то, что библиотека держит в
|
||||
этом коде. Цитата из источника:
|
||||
|
||||
> Разрез «хранилище против хранилища» вопроса не покрывает: библиотека держит
|
||||
> шесть ролей сразу, и только две из них про хранение.
|
||||
|
||||
Довод, на котором стоял перевод, отпал сам. Источник, раздел о трёх доводах:
|
||||
|
||||
> Вход делает само приложение с 2026-08-22 […]: пришедшего называет заголовок
|
||||
> прокси, а учётную запись заводит наш `EnsureUser`. Пользователи в коллекции
|
||||
> есть **потому, что их пишет наш код**, а не провайдер библиотеки. Довод,
|
||||
> которым отвергнут отвергнутый вариант, перестал быть верным.
|
||||
|
||||
Отвергнут вариант «уйти в два шага», оставив панель жить в промежутке, и
|
||||
отвергнут решением владельца: панель на стройке заменяется запросом к базе, а
|
||||
вторая порция работы стоит дороже, чем то, что она сберегает.
|
||||
|
||||
Обстоятельство, которое назначило момент:
|
||||
|
||||
> на сервере данных нет и сервис остановлен, поэтому смена стоит только кода.
|
||||
> Дешевле она не станет никогда — каталог `internal/controller/http` прирастает
|
||||
> кодом на чужих типах с каждой задачей.
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` периметр сервиса становится только нашим. Панель `/_/` исчезает вместе с
|
||||
дефектом `/%5f/` из [../security.md](../security.md), а пространство
|
||||
хранилища `/api/` — вместе с необходимостью держать его открытым ради файлов.
|
||||
- `+` пропадает секрет, которого не было до перевода, — пароль суперпользователя
|
||||
панели.
|
||||
- `+` файлы ложатся своей раскладкой, и загрузка частями, узнавание по хеш-сумме,
|
||||
удаление записи и вторая копия рядом становятся обычной работой с файлами.
|
||||
- `+` из сборки уходят шесть модулей, достижимых только через библиотеку:
|
||||
`imaging`, `mailyak`, `jwt`, `fexpr`, `cobra`, драйвер MySQL.
|
||||
`modernc.org/sqlite` остаётся, и сборка по-прежнему обходится без CGO.
|
||||
- `−` владелец сервиса остаётся без панели. Правку записи, возврат остановленной
|
||||
в работу и просмотр очереди до появления экранов делает запрос к базе руками.
|
||||
Задачи `audiorecord-actions` и `play-recording-in-app` этим становятся не
|
||||
улучшением, а заменой утраченного инструмента.
|
||||
- `−` шаги схемы, отдачу файла, ограничитель частоты и настройку базы пишем и
|
||||
сопровождаем сами. Единственный писатель у `modernc.org/sqlite` — наша забота
|
||||
с этого дня.
|
||||
- `−` раскладка каталога данных меняется необратимо. Цена сегодня нулевая:
|
||||
стройка, на сервере пусто; после первой боевой записи она перестаёт быть
|
||||
нулевой.
|
||||
- `−` 1497 строк контроллера и 3075 строк его проверок написаны на
|
||||
`*core.RequestEvent` и переписываются целиком.
|
||||
+4
-3
@@ -35,6 +35,7 @@
|
||||
|
||||
| Дата | Запись | Статус |
|
||||
| --- | --- | --- |
|
||||
| 2026-08-22 | [Хранилищем становится SQLite с каталогом файлов, а PocketBase уходит целиком](ADR-2026-08-22-storage-without-pocketbase.md) | |
|
||||
| 2026-08-22 | [Пришедшего называет заголовок доверенного прокси, а не собственный вход OIDC](ADR-2026-08-22-login-by-trusted-header.md) | |
|
||||
| 2026-08-15 | [Node зовётся контейнером, а не ставится на машину разработчика](ADR-2026-08-15-node-in-container-not-on-machine.md) | |
|
||||
| 2026-08-15 | [Приложение живёт своим пространством адресов, а не общим с хранилищем](ADR-2026-08-15-app-namespace.md) | |
|
||||
@@ -49,7 +50,7 @@
|
||||
| 2026-08-14 | [Учётная запись с записями не удаляется, и это осознанный тупик](ADR-2026-08-14-account-with-records-is-not-deleted.md) | |
|
||||
| 2026-08-13 | [Намерение объявляется признаком, а не выводится из ключа доступа](ADR-2026-08-13-telegram-intent-declared-not-inferred.md) | устарело: вход убран [ADR-2026-08-15-telegram-intake-removed-temporarily](ADR-2026-08-15-telegram-intake-removed-temporarily.md) |
|
||||
| 2026-08-13 | [Недоступность Telegram подъёму сервиса не мешает](ADR-2026-08-13-telegram-outage-does-not-block-startup.md) | устарело: вход убран [ADR-2026-08-15-telegram-intake-removed-temporarily](ADR-2026-08-15-telegram-intake-removed-temporarily.md) |
|
||||
| 2026-08-12 | [Файл записи закрыт защищённым полем и отдаётся вошедшему по токену файла](ADR-2026-08-12-protected-file-behind-session.md) | |
|
||||
| 2026-08-12 | [Файл записи закрыт защищённым полем и отдаётся вошедшему по токену файла](ADR-2026-08-12-protected-file-behind-session.md) | заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md) |
|
||||
| 2026-08-12 | [Сессия живёт семь суток и не продлевает саму себя](ADR-2026-08-12-session-without-refresh.md) | заменено на [ADR-2026-08-22-login-by-trusted-header](ADR-2026-08-22-login-by-trusted-header.md) |
|
||||
| 2026-08-12 | [Кого пускать в сервис, решает правило провайдера, а не сервис](ADR-2026-08-12-access-delegated-to-provider.md) | |
|
||||
| 2026-08-12 | [Код провайдера меняется на сессию вызовом собственного адреса хранилища внутри процесса](ADR-2026-08-12-oidc-exchange-via-own-route.md) | заменено на [ADR-2026-08-22-login-by-trusted-header](ADR-2026-08-22-login-by-trusted-header.md) |
|
||||
@@ -61,8 +62,8 @@
|
||||
| 2026-08-11 | [Отказ, который решено не проверять, объявляется поимённо](ADR-2026-08-11-errcheck-check-blank.md) | |
|
||||
| 2026-08-11 | [Наружу расширение выходит только приведённым к перечню](ADR-2026-08-11-known-format-label.md) | |
|
||||
| 2026-08-11 | [Приложение пишем на Vue, а Node входит в гейт и в образ](ADR-2026-08-11-spa-on-vue.md) | |
|
||||
| 2026-08-11 | [Очередь остаётся своей таблицей, но коллекцией PocketBase](ADR-2026-08-11-queue-as-pocketbase-collection.md) | |
|
||||
| 2026-08-11 | [Хранилище, файлы и вход переезжают в PocketBase](ADR-2026-08-11-pocketbase-storage-with-admin-panel.md) | |
|
||||
| 2026-08-11 | [Очередь остаётся своей таблицей, но коллекцией PocketBase](ADR-2026-08-11-queue-as-pocketbase-collection.md) | заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md) |
|
||||
| 2026-08-11 | [Хранилище, файлы и вход переезжают в PocketBase](ADR-2026-08-11-pocketbase-storage-with-admin-panel.md) | заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md) |
|
||||
| 2026-08-11 | [Проверки не зовут внешних программ](ADR-2026-08-11-stub-adapters-in-tests.md) | |
|
||||
|
||||
Решения, принятые до заведения канона 2026-08-10, источника в архиве изменений
|
||||
|
||||
@@ -22,6 +22,7 @@ SpeechKit, Yandex Object Storage и `ffmpeg`. Мерить нужно то, чт
|
||||
|
||||
| Дата | Запись | О чём |
|
||||
| --- | --- | --- |
|
||||
| 2026-08-22 | [Хранилище: PocketBase против голого SQLite с каталогом файлов](storage-without-pocketbase.md) | Шесть ролей библиотеки в этом коде, отпавший довод перевода, объём кода на её типах, шесть модулей только через неё |
|
||||
| 2026-08-15 | [Раздача приложения: что делают за нас библиотека и сборщик](webapp-serving.md) | Раскодированный путь у маршрутизатора, второй журнал у PocketBase, нулевое время у вшитого файла, зависание установщика без сети |
|
||||
| 2026-08-13 | [Разбор TOML: какое семейство отказов несёт значения из файла](toml-decode-errors.md) | Значения только в `ParseError.Message`, врущее поле `Line`, отказ значением в BurntSushi/toml v1.5.0 |
|
||||
| 2026-08-12 | [PocketBase: умолчания, которые ломают штатный сценарий](pocketbase-defaults.md) | Потолок файла 5 МиБ, тело 32 МиБ, таймаут чтения, суффикс имени, хук правки |
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# Хранилище: PocketBase против голого SQLite с каталогом файлов
|
||||
|
||||
Отвечает на вопрос владельца от 2026-08-22: не окажется ли голый SQLite с
|
||||
каталогом аудиозаписей гибче встроенной PocketBase. Записи в каталоге задач у
|
||||
вопроса нет — он поднят по ходу работы, и разведка идёт от него, а не от
|
||||
постановки.
|
||||
|
||||
Мерилось **дерево репозитория**, а не внешний сервис: вопрос о том, что
|
||||
библиотека уже держит в этом коде и чем за это плачено. Замеров на живом потоке
|
||||
нет.
|
||||
|
||||
## Как снималось
|
||||
|
||||
Дата — 2026-08-22, коммит `a8fb479`, дерево чисто. Всё считано в самом
|
||||
репозитории, боевые данные не участвовали.
|
||||
|
||||
- **Пакеты в сборке:** `go list -deps ./... | wc -l` → **584**; из них с
|
||||
`pocketbase` в пути — `go list -deps ./... | grep -c pocketbase` → **40**.
|
||||
- **Кто тянет тяжёлый модуль:** `go mod why -m <модуль>` по семи модулям.
|
||||
- **Объём кода:** `find <каталог> -name '*.go' [! -name '*_test.go'] | xargs wc -l`.
|
||||
- **Протечка библиотеки за адаптер:** `grep -l "pocketbase/" internal/controller/http/*.go`.
|
||||
- **Узлы для варианта с монтированием** — чтением кэша модулей:
|
||||
`router.Router.BuildMux()` отдаёт `http.Handler`
|
||||
(`tools/router/router.go:61`), а `apis.Serve` присваивает `e.Server.Handler`
|
||||
(`apis/serve.go:223`). Прототипа на этих двух узлах не собирал — проверено
|
||||
только их существование.
|
||||
|
||||
## PocketBase здесь — фреймворк приложения, а не хранилище
|
||||
|
||||
Разрез «хранилище против хранилища» вопроса не покрывает: библиотека держит
|
||||
шесть ролей сразу, и только две из них про хранение.
|
||||
|
||||
| Роль | Где | Чем заменяется |
|
||||
| --- | --- | --- |
|
||||
| SQLite без CGO, пул записи одним соединением | `pbrepo.New` | `modernc.org/sqlite` напрямую, свои WAL, `busy_timeout` и единственный писатель |
|
||||
| Схема и шаги миграций | `internal/adapter/repo/pocketbase/migrations`, 1039 строк | свой раннер либо `goose`, который тут уже был |
|
||||
| Файлы записей на диске и отдача `/api/files/…` по токену | `file_repo.go`, `FileTokenPath` | каталог и свой обработчик отдачи |
|
||||
| Маршрутизатор, цепочка слоёв с приоритетами, ограничитель частоты | `internal/controller/http` целиком | `net/http` и счётчик по ключу |
|
||||
| Учётная запись значением `e.Auth` | `identity.go` | строка своей таблицы |
|
||||
| Панель `/_/` | покупалась ради неё | ничем |
|
||||
|
||||
**Библиотека вышла за адаптер.** Из пяти не-тестовых файлов
|
||||
`internal/controller/http` её импортируют **все пять**, из шести тестовых —
|
||||
**все шесть**: 1497 строк кода контроллера и 3075 строк его проверок написаны
|
||||
на `*core.RequestEvent`. Адаптер хранилища — ещё 1787 строк без шагов схемы.
|
||||
|
||||
## Три довода прежнего решения: один умер 2026-08-22
|
||||
|
||||
[ADR-2026-08-11-pocketbase-storage-with-admin-panel](../adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)
|
||||
покупал библиотеку за три вещи разом, и отвергал половинчатые пути тем, что
|
||||
порознь ни одна перевода не оправдывает.
|
||||
|
||||
1. **Правка записей в панели** — работает. Единственная роль, которой сегодня
|
||||
нет замены.
|
||||
2. **Файлы видны в панели, и копия накрывает их вместе с базой** — работает; тот
|
||||
же ADR оговаривает, что копии сервер делает своими средствами.
|
||||
3. **Вход через её провайдер OIDC** — **отпал**. Цитата, которой ADR отверг
|
||||
вариант «взять библиотеку только хранилищем»:
|
||||
|
||||
> Панель показывает свою коллекцию пользователей и ничего больше. Отсюда
|
||||
> следствие для целевого входа: **пользователи Authelia в панели не появятся,
|
||||
> если вход делает само приложение**.
|
||||
|
||||
Вход делает само приложение с 2026-08-22
|
||||
([ADR-2026-08-22-login-by-trusted-header](../adr/ADR-2026-08-22-login-by-trusted-header.md)):
|
||||
пришедшего называет заголовок прокси, а учётную запись заводит наш
|
||||
`EnsureUser`. Пользователи в коллекции есть **потому, что их пишет наш код**,
|
||||
а не провайдер библиотеки. Довод, которым отвергнут отвергнутый вариант,
|
||||
перестал быть верным — и вместе с ним отпало основание держать вход в
|
||||
библиотеке.
|
||||
|
||||
## Что оплачено и не работает
|
||||
|
||||
- **Захват записи идёт сырым запросом мимо записей коллекции**
|
||||
(`record_repo.go`, `FindAndAcquire`): `app.DB().NewQuery` с `UPDATE … RETURNING`.
|
||||
На самом горячем месте абстракция не помогает, а её ограничения действуют —
|
||||
хуки коллекции не срабатывают, время изменения проставляет наш запрос, времена
|
||||
сравниваются строками побайтово.
|
||||
- **Налог соседства двух периметров в одном процессе и на одном порту.**
|
||||
Появился секрет, которого не было, — пароль суперпользователя. Панель
|
||||
опубликована в интернет, и её барьер обходится подменой знака: `/%5f/`
|
||||
попадает в ту же группу, что `/_/`, а правило прокси написано на литерал
|
||||
([../security.md](../security.md)); дефект не закрыт. Узнавание нельзя
|
||||
повесить на всю поверхность хранилища, иначе узнанный перепишет себе
|
||||
`provider_login` и заберёт чужой архив — область слоя сужена, и причина стоит
|
||||
абзацем в `identity.go`.
|
||||
- **Пространство `/api/` нельзя закрыть на прокси**, потому что за файлами
|
||||
ходит туда браузер пользователя. Своя отдача файла снимает это ограничение:
|
||||
закрыть можно всё пространство хранилища разом.
|
||||
|
||||
## Чем платит уход
|
||||
|
||||
- **Панель теряется целиком.** Правка записи, возврат остановленной в работу,
|
||||
просмотр очереди фильтром, прослушивание файла — всё это сегодня живёт только
|
||||
там.
|
||||
- **Пишем сами** шаги схемы, отдачу файла, ограничитель частоты и настройку
|
||||
базы. Последняя — не формальность: у `modernc.org/sqlite` запись идёт
|
||||
единственным соединением, и библиотека держит это за нас двумя пулами.
|
||||
- **Раскладка каталога данных** названа необратимой в `../../CLAUDE.md`.
|
||||
На стройке цена нулевая: на сервере пусто, переносить нечего.
|
||||
|
||||
## Что уходит из сборки, а что остаётся
|
||||
|
||||
`go mod why -m` показал, что шесть модулей достижимы **только** через
|
||||
PocketBase: `disintegration/imaging` (через `tools/filesystem`),
|
||||
`domodwyer/mailyak/v3` и `golang-jwt/jwt/v5` (через `core`),
|
||||
`ganigeorgiev/fexpr` (через `apis`), `spf13/cobra` (набор команд),
|
||||
`go-sql-driver/mysql` (через `pocketbase/dbx`). `modernc.org/sqlite` остаётся:
|
||||
он нужен и без библиотеки, и требование обходиться без CGO с ним сохраняется.
|
||||
|
||||
## Варианты и что выбрано
|
||||
|
||||
| Вариант | Что делает | Чем платит |
|
||||
| --- | --- | --- |
|
||||
| Оставить как есть | ноль работы | долг растёт с каждой задачей на `RequestEvent` |
|
||||
| **Уйти целиком** | SQLite напрямую, каталог файлов, свои шаги схемы и маршруты | панель теряется сразу, работа одной порцией |
|
||||
| Уйти в два шага | сначала снять с периметра HTTP, панель оставить; потом с хранилища | панель жива в промежутке, работа та же, но двумя порциями |
|
||||
| Локализовать протечку | библиотека не выходит за `internal/adapter/repo` | решение откладывается, панель остаётся |
|
||||
|
||||
**Решением владельца от 2026-08-22 взят уход целиком, и панель не заменяется
|
||||
ничем**: пока стройка не кончилась, остановленную запись возвращают в работу
|
||||
запросом к базе. Решение записано в
|
||||
[ADR-2026-08-22-storage-without-pocketbase](../adr/ADR-2026-08-22-storage-without-pocketbase.md).
|
||||
|
||||
Обстоятельство, которое решило дело: на сервере данных нет и сервис остановлен,
|
||||
поэтому смена стоит только кода. Дешевле она не станет никогда — каталог
|
||||
`internal/controller/http` прирастает кодом на чужих типах с каждой задачей.
|
||||
@@ -43,6 +43,7 @@
|
||||
|
||||
## Очередь
|
||||
|
||||
- [✨ Уйти с PocketBase на SQLite со своим каталогом файлов](items/storage-without-pocketbase.md) — Библиотека держит шесть ролей и вышла за адаптер: её типы стоят во всех пяти файлах контроллера и во всех шести его проверках, панель опубликована в интернет вместе с необойдённым /%5f/, а пространство /api/ нельзя закрыть на прокси, потому что за файлами туда ходит браузер пользователя.
|
||||
- [🧹 Поднимать сервис для локальной работы одной командой](items/dev-run-task.md) — Локальная проверка требует ручной чистки каталога данных и остановки процесса, а владельца панели заводят по одноразовой ссылке из журнала.
|
||||
- [✨ Сделать экран загрузки записи и её состояния](items/upload-and-status-screen.md) — Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит.
|
||||
- [✨ Сделать экран списка своих записей и чтения текста](items/records-list-screen.md) — Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# ✨ Уйти с PocketBase на SQLite со своим каталогом файлов
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Очередь — Всё остальное строится поверх хранилища: экраны пишутся на типах контроллера, а dev-run-task заводит владельца панели, которой не станет
|
||||
- **Зачем:** Библиотека держит шесть ролей и вышла за адаптер: её типы стоят во всех пяти файлах контроллера и во всех шести его проверках, панель опубликована в интернет вместе с необойдённым /%5f/, а пространство /api/ нельзя закрыть на прокси, потому что за файлами туда ходит браузер пользователя.
|
||||
|
||||
Сервис работает с SQLite напрямую, файлы записей лежат в своём каталоге и
|
||||
отдаются своим обработчиком, маршруты и слои живут на `net/http`, схему двигают
|
||||
свои шаги. Панель администратора исчезает вместе с адресами `/_/` и `/api/`.
|
||||
|
||||
Решение — [ADR-2026-08-22-storage-without-pocketbase](../../docs/adr/ADR-2026-08-22-storage-without-pocketbase.md),
|
||||
разведка — [storage-without-pocketbase](../../docs/research/storage-without-pocketbase.md).
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- каталог `internal/adapter/repo/pocketbase` целиком и его шаги схемы;
|
||||
- каталог `internal/controller/http` целиком вместе с проверками;
|
||||
- подъём сервера и цепочку слоёв в `cmd/transcriber/main.go`;
|
||||
- раскладку каталога данных: файл базы и каталог файлов записей — **необратимое**;
|
||||
- публичный контракт HTTP: пространство `/api/` и адрес `/_/` исчезают, корень
|
||||
`/app/` остаётся;
|
||||
- отдачу файла записи браузеру: короткий токен файла и защищённое поле коллекции;
|
||||
- панель администратора как инструмент владельца сервиса;
|
||||
- ключ конфига `[storage] data_dir` и перечень зависимостей `go.mod`;
|
||||
- модель угроз: адрес `/_/`, дефект `/%5f/` и правило прокси на пространство
|
||||
хранилища.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Библиотеки нет в сборке. **Оракул:** `go list -deps ./... | grep -c pocketbase`
|
||||
даёт `0`, а `go mod tidy` не возвращает её модулей в `go.mod`.
|
||||
- Чужой файл недостижим по прямой ссылке. **Оракул:** тест контроллера — запрос
|
||||
к файлу чужой записи и к несуществующей отвечает одинаково.
|
||||
- Захват записи остаётся неделимым. **Оракул:** тест с параллельными воркерами
|
||||
под `-race` — запись достаётся ровно одному, второй получает отказ по значению
|
||||
признака захвата.
|
||||
- Пространства хранилища не существует. **Оракул:** тест маршрутов — `/api/…`,
|
||||
`/_/` и `/%5f/` отвечают тем же, чем всякий неизвестный путь.
|
||||
- Схема накатывается на пустом каталоге до старта воркеров. **Оракул:** запуск на
|
||||
чистом каталоге данных — ни одного отказа в журнале до первой строки о готовности.
|
||||
|
||||
## Рамки
|
||||
|
||||
Данные не переносятся: стройка, на сервере пусто. Панель не заменяется ничем —
|
||||
остановленную запись возвращает в работу запрос к базе, пока этого не сделают
|
||||
экраны владельца. Очередь остаётся своей таблицей с захватом одним запросом
|
||||
`RETURNING`. Боевой каталог данных не трогать.
|
||||
Reference in New Issue
Block a user