учёт: закрыт переезд хранилища, заведён урожай его ревью

- storage-without-pocketbase закрыта как реализованная: приёмка сошлась по всем
  пяти критериям записи и по двенадцати приёмочным свойствам рубрики ревью
  дизайна, работа лежит коммитом c9b7765
- урожай триажа ревью развёрнут в одиннадцать записей с тегом партии
  review-2026-08-23 и расставлен по зависимости, а не в конец списка
- находка про признак живости воркера слита в stalled-pipeline-metric: у неё та
  же причина — вставший конвейер неотличим от простоя
This commit is contained in:
av
2026-08-23 08:41:49 +03:00
parent c9b7765646
commit 75c6f0168a
14 changed files with 540 additions and 50 deletions
@@ -0,0 +1,48 @@
# 🧹 Отдавать файл записи через http.ServeContent
- **Тип:** chore
- **Категория:** Очередь — Стандартная семантика диапазонов нужна проигрыванию записи на экране: браузер тянет звук именно диапазонами
- **Зачем:** Отдача файла считает диапазоны байт своей рукой — около 110 строк семантики HTTP, которую стандартная библиотека делает сама, и на этих строках стоит проигрывание записи на экране.
- **Теги:** review-2026-08-23
Отдача файла записи разбирает заголовок `Range`, считает границы, ставит
`Content-Range` и код `206` своей рукой — около 110 строк семантики HTTP.
`http.ServeContent` делает то же самое: диапазоны, `If-Range`, `Last-Modified`,
`ETag`, неверный диапазон с кодом `416`.
Своя реализация стоит не тем, что длиннее, а тем, что расходится: браузер
проигрывает звук именно диапазонами, и задача `play-recording-in-app` встанет
поверх этих строк.
Нашёл проход `review-architecture` (вторая половина R2) ревью change
`2026-08-23-storage-without-pocketbase`.
## Затрагивает
- `internal/controller/http/file.go` — разбор `Range`, ответ `206` и заголовки
диапазона;
- тип содержимого ответа: он выводится из закрытого перечня форматов и остаётся
за нами, `ServeContent` его не выбирает;
- открытие копии в `internal/adapter/repo/sqlite/store.go``ServeContent`
требует `io.ReadSeeker` и время последнего изменения;
- публичный контракт HTTP: коды `200`, `206`, `416` и заголовки ответа на
запрос файла.
## Критерии приёмки
- Диапазоны считает стандартная библиотека. **Оракул:** `grep` по
`internal/controller/http` — ни одного разбора заголовка `Range` руками,
вызов `http.ServeContent` один.
- Ответы на диапазон не изменились. **Оракул:** тест контроллера — запрос без
`Range` отдаёт `200` и всю длину, запрос `bytes=0-99` отдаёт `206` с
`Content-Range`, запрос за пределом длины отдаёт `416`.
- Тип содержимого по-прежнему выбирает сервис. **Оракул:** тест на записи с
расширением `.html` — ответ несёт `application/octet-stream` и
`attachment`, а не тип из имени.
- Чужой файл остаётся недостижимым. **Оракул:** прежний тест владения —
чужая и несуществующая запись отвечают байт в байт одинаково.
## Рамки
Раскладку каталога данных и имена копий не трогаем: она объявлена необратимой.
Ответ на запрос чужого файла не меняется ни кодом, ни телом.