учёт: заведён урожай ревью входа по конфигу
- пять задач партии review-2026-08-23: многозначность Remote-Name и Remote-Email, мелочи входа, механизация правила о зависимостях конфига, снятие мёртвого читателя .env, управляющие знаки в расширении - две существующие разведки дополнены находками того же прогона: адрес объекта в тексте отказа SpeechKit и выводимость имени копии из журнала
This commit is contained in:
@@ -44,7 +44,9 @@
|
|||||||
## Очередь
|
## Очередь
|
||||||
|
|
||||||
- [🧹 Поднимать сервис для локальной работы одной командой](items/dev-run-task.md) — Локальная проверка требует ручной чистки каталога данных и остановки процесса, а владельца панели заводят по одноразовой ссылке из журнала.
|
- [🧹 Поднимать сервис для локальной работы одной командой](items/dev-run-task.md) — Локальная проверка требует ручной чистки каталога данных и остановки процесса, а владельца панели заводят по одноразовой ссылке из журнала.
|
||||||
|
- [🧹 Снять мёртвый godotenv.Load() вместе с зависимостью](items/drop-dead-dotenv-loader.md) — сервис на каждом старте печатает предупреждение, приглашающее завести .env, читать который некому: это вторая точка, где может завестись секрет, и путь к нарушению критического инварианта сервис подсказывает сам
|
||||||
- [🧹 Вынести тело подкоманды resume из main-пакета и покрыть тестом](items/devtools-resume-testable.md) — go test ./cmd/... отвечает no test files: подкоманда возврата записи в работу не проверена ничем, а тест на неё пришлось бы писать, повторяя её тело руками.
|
- [🧹 Вынести тело подкоманды resume из main-пакета и покрыть тестом](items/devtools-resume-testable.md) — go test ./cmd/... отвечает no test files: подкоманда возврата записи в работу не проверена ничем, а тест на неё пришлось бы писать, повторяя её тело руками.
|
||||||
|
- [🧹 Механизировать правило «internal/config не знает транспорта»](items/archrule-config-knows-no-transport.md) — правило записано конвенцией, а сканера у него нет; проект его уже нарушает — go test ./internal/config линкует всю поверхность HTTP, и будущая надобность конфига в транспорте даст цикл импорта на уровне проверок
|
||||||
- [✨ Сделать экран загрузки записи и её состояния](items/upload-and-status-screen.md) — Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит.
|
- [✨ Сделать экран загрузки записи и её состояния](items/upload-and-status-screen.md) — Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит.
|
||||||
- [✨ Сделать экран списка своих записей и чтения текста](items/records-list-screen.md) — Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
|
- [✨ Сделать экран списка своих записей и чтения текста](items/records-list-screen.md) — Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
|
||||||
- [✨ Дать владельцу править запись, возвращать её в работу и видеть её путь](items/audiorecord-actions.md) — С записью нельзя сделать ничего: заголовок ставит одна языковая модель, остановленную возвращает в работу только владелец сервиса в панели, а журнал событий пишется и не читается никем, кроме него же
|
- [✨ Дать владельцу править запись, возвращать её в работу и видеть её путь](items/audiorecord-actions.md) — С записью нельзя сделать ничего: заголовок ставит одна языковая модель, остановленную возвращает в работу только владелец сервиса в панели, а журнал событий пишется и не читается никем, кроме него же
|
||||||
@@ -56,6 +58,7 @@
|
|||||||
- [🧹 Свести норму схемы со служебной таблицей goose](items/goose-table-in-schema-norm.md) — Таблица учёта goose_db_version несёт второй вид времени и своё умолчание, а норма схемы объявлена абсолютной; сторож перечисляет наши таблицы поимённо и служебной не видит вовсе.
|
- [🧹 Свести норму схемы со служебной таблицей goose](items/goose-table-in-schema-norm.md) — Таблица учёта goose_db_version несёт второй вид времени и своё умолчание, а норма схемы объявлена абсолютной; сторож перечисляет наши таблицы поимённо и служебной не видит вовсе.
|
||||||
- [🧹 Переименовать требование webapp про путь в журнале](items/rename-webapp-journal-requirement.md) — Заголовок требования называет половину нормы — путь чужого корня, — а само требование накрывает обе половины адресного пространства; заголовок это идентичность требования, и синк оставил бы вторую, устаревшую копию.
|
- [🧹 Переименовать требование webapp про путь в журнале](items/rename-webapp-journal-requirement.md) — Заголовок требования называет половину нормы — путь чужого корня, — а само требование накрывает обе половины адресного пространства; заголовок это идентичность требования, и синк оставил бы вторую, устаревшую копию.
|
||||||
- [✨ Сопоставить пользователя Telegram с учётной записью](items/telegram-account-link.md) — Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
|
- [✨ Сопоставить пользователя Telegram с учётной записью](items/telegram-account-link.md) — Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
|
||||||
|
- [🐞 Судить многозначность Remote-Name и Remote-Email тем же правилом, что и логин](items/identity-headers-reject-multivalue.md) — чужой адрес, занятый первым пришедшим, навсегда и молча оставляет без почты настоящего владельца: EnsureUser найденную запись не переписывает, ветвь отката заводит владельца без почты и без строки журнала
|
||||||
- [✨ Пускать скрипты в API по личным токенам](items/api-tokens.md) — Скрипту недоступны ни браузерная сессия, ни вход у Authelia: домен целиком стоит за прокси, и автоматизировать загрузку нечем.
|
- [✨ Пускать скрипты в API по личным токенам](items/api-tokens.md) — Скрипту недоступны ни браузерная сессия, ни вход у Authelia: домен целиком стоит за прокси, и автоматизировать загрузку нечем.
|
||||||
- [🧹 Покрыть тестами шаги конвейера и захват задачи](items/pipeline-step-tests.md) — Тестовых файлов в проекте два, и оба мимо конвейера: потеря ссылки на файл, двойной ответ пользователю и гонка при захвате не поймаются ничем.
|
- [🧹 Покрыть тестами шаги конвейера и захват задачи](items/pipeline-step-tests.md) — Тестовых файлов в проекте два, и оба мимо конвейера: потеря ссылки на файл, двойной ответ пользователю и гонка при захвате не поймаются ничем.
|
||||||
- [🧹 Проверять ответ обработчика на отказ репозитория](items/http-tests-repo-failure-injection.md) — Тесты обработчиков идут по настоящему sqlite без управляемых отказов: класс «база недоступна во время запроса» не проверен нигде.
|
- [🧹 Проверять ответ обработчика на отказ репозитория](items/http-tests-repo-failure-injection.md) — Тесты обработчиков идут по настоящему sqlite без управляемых отказов: класс «база недоступна во время запроса» не проверен нигде.
|
||||||
@@ -102,10 +105,12 @@
|
|||||||
- [✨ Считать объём, минуты и расход по каждому пользователю](items/usage-accounting.md) — Ни объём, ни длительность, ни обращения к платным сервисам никуда не записываются: восстановить расход задним числом не из чего.
|
- [✨ Считать объём, минуты и расход по каждому пользователю](items/usage-accounting.md) — Ни объём, ни длительность, ни обращения к платным сервисам никуда не записываются: восстановить расход задним числом не из чего.
|
||||||
- [✨ Сделать страницу статистики для владельца](items/admin-stats-screen.md) — Собранный учёт читается только запросом к базе руками: ни страницы, ни признака владельца в приложении нет.
|
- [✨ Сделать страницу статистики для владельца](items/admin-stats-screen.md) — Собранный учёт читается только запросом к базе руками: ни страницы, ни признака владельца в приложении нет.
|
||||||
- [🐞 Не давать отправителю задавать, что уедет в журнал](items/journal-fields-bounded-by-service.md) — Путь запроса уезжает в журнал целиком: аноним пишет туда до мегабайта своего текста одной строкой, а хвост имени файла приезжает внутри чужого текста ошибки вместо своего поля.
|
- [🐞 Не давать отправителю задавать, что уедет в журнал](items/journal-fields-bounded-by-service.md) — Путь запроса уезжает в журнал целиком: аноним пишет туда до мегабайта своего текста одной строкой, а хвост имени файла приезжает внутри чужого текста ошибки вместо своего поля.
|
||||||
|
- [🐞 Отвергать расширение с управляющими знаками на приёме, а не отдавать 500](items/reject-control-chars-in-extension.md) — комментарий кода объявляет этот исход недопустимым и ради него заведён предел длины расширения, но разрез сделан по длине и не сделан по составу знаков: отправитель получает 500, владелец — строку ERROR, неотличимую от аварии хранилища
|
||||||
- [🐞 Писать в журнал код ответа отвергнутого запроса](items/response-code-in-journal.md) — У всякого запроса, отвергнутого роутером, в журнале стоит нулевой код: всплеск отказов доступа неотличим от обычного трафика.
|
- [🐞 Писать в журнал код ответа отвергнутого запроса](items/response-code-in-journal.md) — У всякого запроса, отвергнутого роутером, в журнале стоит нулевой код: всплеск отказов доступа неотличим от обычного трафика.
|
||||||
- [🐞 Не затирать сохранённое вложение более бедным ответом](items/payload-not-overwritten-by-poorer-answer.md) — Пустой ответ распознавателя сохранённое больше не стирает, а более короткий — стирает: вложение заменяется, и пересчитать архив становится нечем.
|
- [🐞 Не затирать сохранённое вложение более бедным ответом](items/payload-not-overwritten-by-poorer-answer.md) — Пустой ответ распознавателя сохранённое больше не стирает, а более короткий — стирает: вложение заменяется, и пересчитать архив становится нечем.
|
||||||
- [🐞 Закрыть анониму адрес подтверждения почты](items/close-verification-endpoint.md) — Адрес подтверждения почты хранилища открыт без сессии: зная адрес вошедшего, аноним вызывает у сервиса отправку письма — сегодня безвредно, потому что почтового отправителя нет.
|
- [🐞 Закрыть анониму адрес подтверждения почты](items/close-verification-endpoint.md) — Адрес подтверждения почты хранилища открыт без сессии: зная адрес вошедшего, аноним вызывает у сервиса отправку письма — сегодня безвредно, потому что почтового отправителя нет.
|
||||||
- [🔬 Судьба рубежа расшифровки, у которого не осталось работы](items/transcribed-stage-without-work.md) — Шаг завершения после убранной доставки только двигает колонку, а рубеж при этом подпадает под сторож застревания: готовая расшифровка платит отдельный захват и час сторожа.
|
- [🔬 Судьба рубежа расшифровки, у которого не осталось работы](items/transcribed-stage-without-work.md) — Шаг завершения после убранной доставки только двигает колонку, а рубеж при этом подпадает под сторож застревания: готовая расшифровка платит отдельный захват и час сторожа.
|
||||||
- [🔬 Пределы длительности, приходящей из метаданных](items/duration-bounds-from-metadata.md) — Длительность приходит от ffprobe числом и не проверяется ничем: приведение к целому переполняется, а испорченная гистограмма чинится только перезапуском.
|
- [🔬 Пределы длительности, приходящей из метаданных](items/duration-bounds-from-metadata.md) — Длительность приходит от ffprobe числом и не проверяется ничем: приведение к целому переполняется, а испорченная гистограмма чинится только перезапуском.
|
||||||
- [🧹 Проверять уязвимости в зависимостях приложения](items/npm-deps-vulnerability-scan.md) — Зависимости приложения пришли задачей spa-skeleton, а сканера у них нет: govulncheck смотрит только модули Go. Ни проверки, ни объявленного исключения — непроверенная поверхность, о которой никто не решал
|
- [🧹 Проверять уязвимости в зависимостях приложения](items/npm-deps-vulnerability-scan.md) — Зависимости приложения пришли задачей spa-skeleton, а сканера у них нет: govulncheck смотрит только модули Go. Ни проверки, ни объявленного исключения — непроверенная поверхность, о которой никто не решал
|
||||||
|
- [🧹 Разобрать мелочи входа по доверенному заголовку](items/test-headers-login-nits.md) — три пункта ниже потолка триажа остались от одной задачи: функция строки старта не вызвана ни одним тестом, форма записи необязательного поля конфига стала неотличима от обязательного, два комментария описывают снятую подкоманду и не то число звеньев цепочки
|
||||||
- [🔬 Квота по общему размеру загруженного на пользователя](items/per-user-size-quota.md) — Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
|
- [🔬 Квота по общему размеру загруженного на пользователя](items/per-user-size-quota.md) — Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 🧹 Механизировать правило «internal/config не знает транспорта»
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь — Оба правят проверки, которым потом верят: правило направления зависимостей встаёт рядом с покрытием оснастки, до задач, что опираются на зелёный гейт
|
||||||
|
- **Зачем:** правило записано конвенцией, а сканера у него нет; проект его уже нарушает — go test ./internal/config линкует всю поверхность HTTP, и будущая надобность конфига в транспорте даст цикл импорта на уровне проверок
|
||||||
|
- **Теги:** review-2026-08-23
|
||||||
|
|
||||||
|
Направление зависимостей в проекте держат тесты-сканеры `internal/archrules`, а
|
||||||
|
не договорённость. У правила «`internal/config` не знает транспорта» сканера нет,
|
||||||
|
и проект его уже нарушает — файлом проверок, а не рабочим кодом.
|
||||||
|
|
||||||
|
Нашёл прогон ревью `config-test-headers-login` 2026-08-23: норма записана в
|
||||||
|
`docs/conventions/config.md`, а `grep -n "config" internal/archrules/arch_test.go`
|
||||||
|
не даёт ни одного совпадения. Нарушает её сегодня файл проверок —
|
||||||
|
`internal/config/test_headers_test.go` объявлен `package config` и импортирует
|
||||||
|
`internal/controller/http`. В рабочем бинарнике этого ребра нет, но
|
||||||
|
`go test ./internal/config` линкует всю поверхность HTTP.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/config/test_headers_test.go` — перевод во внешний пакет
|
||||||
|
`package config_test`;
|
||||||
|
- `internal/archrules/arch_test.go` — новое правило;
|
||||||
|
- `docs/conventions/config.md` — источник нормы, которую правило механизирует.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Правило существует и зелёное. **Оракул:** `internal/archrules/arch_test.go`
|
||||||
|
содержит правило «`internal/config` не знает транспорта», и
|
||||||
|
`go test ./internal/archrules` зелёный.
|
||||||
|
- Файл проверок объявлен `package config_test`. **Оракул:**
|
||||||
|
`head -1 internal/config/test_headers_test.go` даёт `package config_test`,
|
||||||
|
`go test ./internal/config` зелёный.
|
||||||
|
- Импорта `internal/controller/http` в пакете `config` нет. **Оракул:**
|
||||||
|
`go list -deps ./internal/config` не содержит `internal/controller/http`, и то
|
||||||
|
же говорит новое правило `internal/archrules`.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Внутренний порядок задан: сначала перенос файла во внешний пакет, потом правило,
|
||||||
|
— иначе правило по всем файлам уронит гейт. Норма не пересматривается: правится
|
||||||
|
то, чем она проверяется, а не она сама.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 🧹 Снять мёртвый godotenv.Load() вместе с зависимостью
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь — Чистка старта сервиса идёт вместе с задачей о локальном запуске: обе трогают одну точку входа
|
||||||
|
- **Зачем:** сервис на каждом старте печатает предупреждение, приглашающее завести .env, читать который некому: это вторая точка, где может завестись секрет, и путь к нарушению критического инварианта сервис подсказывает сам
|
||||||
|
- **Теги:** review-2026-08-23
|
||||||
|
|
||||||
|
Настройки сервис берёт из файла конфига целиком, а переменных окружения не
|
||||||
|
читает нигде. Вызов `godotenv.Load()` остался от прежней раскладки и на каждом
|
||||||
|
старте печатает предупреждение, приглашающее завести `.env`, — то есть завести
|
||||||
|
вторую точку, где может осесть секрет.
|
||||||
|
|
||||||
|
Нашёл прогон ревью `config-test-headers-login` 2026-08-23.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `cmd/transcriber/main.go` — вызов `godotenv.Load()` и его импорт;
|
||||||
|
- `go.mod` — зависимость `github.com/joho/godotenv`;
|
||||||
|
- `.dockerignore` — строка `.env`.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Переменных окружения не читает никто. **Оракул:**
|
||||||
|
`grep -rn "os.Getenv\|os.LookupEnv" --include=*.go internal/ cmd/ | grep -v _test`
|
||||||
|
→ пусто.
|
||||||
|
- Зависимости в `go.mod` нет. **Оракул:** `grep godotenv go.mod go.sum` → пусто,
|
||||||
|
`go build ./...` зелёный.
|
||||||
|
- Предупреждения на старте нет. **Оракул:** прогон бинарника сегодня печатает
|
||||||
|
`level=WARN msg="Warning: .env file not found, using system environment variables"`;
|
||||||
|
после правки такой строки в выводе старта нет.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Если переменные окружения кому-то понадобятся, у них заводится свой читатель, и
|
||||||
|
это другая задача. Строку `.env` в `.gitignore` уже добавило слияние прежней
|
||||||
|
задачи — она
|
||||||
|
остаётся.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 🐞 Судить многозначность Remote-Name и Remote-Email тем же правилом, что и логин
|
||||||
|
|
||||||
|
- **Тип:** fix
|
||||||
|
- **Категория:** Очередь — Правило узнавания правится там же, где личность связывается с учётной записью: обе задачи трогают один сбор contract.Identity
|
||||||
|
- **Зачем:** чужой адрес, занятый первым пришедшим, навсегда и молча оставляет без почты настоящего владельца: EnsureUser найденную запись не переписывает, ветвь отката заводит владельца без почты и без строки журнала
|
||||||
|
- **Теги:** review-2026-08-23
|
||||||
|
|
||||||
|
Многозначный `Remote-User` даёт `401` — многозначный `Remote-Email` даёт `200`,
|
||||||
|
и в базу ложится первое значение. Правило узнавания судит логин и не судит
|
||||||
|
остальные заголовки личности.
|
||||||
|
|
||||||
|
Один и тот же адрес почты, занятый чужим логином, достаётся первому пришедшему
|
||||||
|
навсегда: `EnsureUser` найденную запись не переписывает, а ветвь отката
|
||||||
|
уникальности заводит настоящего владельца **без почты** — и делает это молча,
|
||||||
|
без строки журнала.
|
||||||
|
|
||||||
|
Нашёл прогон ревью `config-test-headers-login` 2026-08-23, оракул воспроизведён.
|
||||||
|
|
||||||
|
## Воспроизведение
|
||||||
|
|
||||||
|
Свой падающий сценарий, прогнан на настоящей поверхности сервиса (`setupEnv` +
|
||||||
|
`AppChain`, база SQLite во временном каталоге, маршрут `/app/me`). Два шага:
|
||||||
|
|
||||||
|
1. Запрос с `Remote-User: squatter` и `Remote-Email: victim@corp.example` с
|
||||||
|
доверенного адреса → `200`, `SELECT email FROM users WHERE
|
||||||
|
provider_login='squatter'` → `victim@corp.example`.
|
||||||
|
2. Запрос настоящего владельца `Remote-User: victim`, `Remote-Email:
|
||||||
|
victim@corp.example` → `200`, `SELECT email … WHERE provider_login='victim'`
|
||||||
|
→ `""`, строк журнала о занятом адресе ноль.
|
||||||
|
|
||||||
|
Ожидалось: многозначный заголовок судится тем же правилом, что и логин, а откат
|
||||||
|
по занятому адресу виден владельцу сервиса строкой журнала.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/controller/http/identity.go:134-138` — сбор `contract.Identity` из
|
||||||
|
заголовков: `Login` берётся из проверенного перечня значений, `Name` и `Email`
|
||||||
|
— через `Header.Get`, то есть первым значением без суждения;
|
||||||
|
- `internal/adapter/repo/sqlite/identity.go:77-110` — ветвь отката уникальности
|
||||||
|
почты: заводит запись с пустым адресом и не пишет ни строки;
|
||||||
|
- требование спеки `access`: сегодня оно нормирует многозначность только для
|
||||||
|
`Remote-User`;
|
||||||
|
- `docs/security.md`, раздел «Периметр» — что именно сервис принимает от контура
|
||||||
|
на веру.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Многозначный `Remote-Email` даёт пустое значение, а не первое. **Оракул:**
|
||||||
|
тест на настоящей поверхности сервиса — запрос с двумя значениями `Remote-Email`
|
||||||
|
заводит учётную запись с пустым адресом; тот же тест на `Remote-Name`.
|
||||||
|
- Узнавание принимает однозначные `Remote-Name` и `Remote-Email` по-прежнему.
|
||||||
|
**Оракул:** тот же тест — одно значение доезжает до колонки без изменений.
|
||||||
|
- Ветвь отката уникальности почты оставляет строку журнала видимого уровня.
|
||||||
|
**Оракул:** тест хранилища — второй логин с занятым адресом заводится, и в
|
||||||
|
журнале стоит строка о занятом адресе; значения адреса в ней нет.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Правится боевой путь узнавания, а не отладочная подстановка заголовков.
|
||||||
|
Владение адресом почты между учётными записями не пересматривается: занятый
|
||||||
|
адрес по-прежнему достаётся тому, кто завёлся первым, — задача только о том,
|
||||||
|
чтобы это перестало происходить молча и по многозначному заголовку.
|
||||||
|
|
||||||
|
Отдельно: требование к контуру про `X-Forwarded-For` не записано ни в модели
|
||||||
|
угроз, ни в спеке и этой правкой не покрывается — правило «справа налево до
|
||||||
|
первого недоверенного» верно постольку, поскольку правое значение приписал
|
||||||
|
прокси, а шаблон лежит в `files/caddyproxy/Caddyfile.template` репозитория
|
||||||
|
`pet-project-server`. Отдельной записи не заводится — решение владельца.
|
||||||
@@ -9,8 +9,9 @@
|
|||||||
каталогу на диске не сказать ни чья запись, ни какого уровня копия: имя читается
|
каталогу на диске не сказать ни чья запись, ни какого уровня копия: имя читается
|
||||||
только вместе с базой.
|
только вместе с базой.
|
||||||
|
|
||||||
Пока записей мало, это не мешает. Цена растёт с каждой уложенной записью:
|
Пока на боевом сервере записей нет вовсе (стадия «стройка»), это не мешает.
|
||||||
раскладка каталога данных объявлена необратимой, и переименование задним числом
|
Цена растёт с каждой уложенной записью: раскладка каталога данных объявлена
|
||||||
|
необратимой, и переименование задним числом
|
||||||
стоит прохода по всему каталогу и по всем ссылкам в базе.
|
стоит прохода по всему каталогу и по всем ссылкам в базе.
|
||||||
|
|
||||||
Разведка отвечает не «переименовать ли», а «нужна ли читаемость без базы вообще»
|
Разведка отвечает не «переименовать ли», а «нужна ли читаемость без базы вообще»
|
||||||
@@ -20,8 +21,29 @@
|
|||||||
|
|
||||||
## Вопрос
|
## Вопрос
|
||||||
|
|
||||||
Нужно ли имени копии нести что-то, по чему запись узнаётся без обращения к базе,
|
Вопроса два, и оба об одном имени копии.
|
||||||
и какой ценой это берётся сейчас против той, что придётся заплатить потом?
|
|
||||||
|
1. Нужно ли имени копии нести что-то, по чему запись узнаётся без обращения к
|
||||||
|
базе, и какой ценой это берётся сейчас против той, что придётся заплатить
|
||||||
|
потом?
|
||||||
|
2. Допустимо ли, что имя копии выводится из строки журнала арифметикой, и если
|
||||||
|
нет — чем платить за невыводимость, не потеряв порядок имён?
|
||||||
|
|
||||||
|
Второй вопрос пришёл прогоном ревью `config-test-headers-login` 2026-08-23, и
|
||||||
|
выводимость там уже установлена: `TestProbeStorageNameDerivesFromRecordID` даёт
|
||||||
|
на `record_id 01m0pwf0r1cp9v0zt83w79ccbn` имя копии
|
||||||
|
`01m0pwf0r1cp9v0zt83w79ccbp.mp3`, ровно `инкремент(record_id)+ext`, а
|
||||||
|
`TestProbeDerivationRate` — 49 из 50 приёмов подряд. Журнал несёт `record_id` и
|
||||||
|
`file_ext`, то есть имя собирается без обращения к каталогу данных, и инвариант
|
||||||
|
«Имя файла на диске задаёт сервис, а в журнал не идёт» соблюдён по букве, но не
|
||||||
|
по назначению. Триаж тест не воспроизводил. Спрашивается поэтому не факт, а его цена.
|
||||||
|
|
||||||
|
Первый вопрос от этого дешевеет: часть читаемости без базы уже есть — просто не
|
||||||
|
по назначению и не решением.
|
||||||
|
|
||||||
|
Оба вопроса решает одна разведка, и отдельной записи второй не получает —
|
||||||
|
решение владельца. Довод: раскладка каталога данных объявлена необратимой, и два
|
||||||
|
ответа порознь дали бы две необратимые правки одного каталога.
|
||||||
|
|
||||||
## Куда ляжет ответ
|
## Куда ляжет ответ
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# 🐞 Отвергать расширение с управляющими знаками на приёме, а не отдавать 500
|
||||||
|
|
||||||
|
- **Тип:** fix
|
||||||
|
- **Категория:** Очередь — Вторая половина того же разреза: границы полей журнала задаются там же, где судится состав расширения
|
||||||
|
- **Зачем:** комментарий кода объявляет этот исход недопустимым и ради него заведён предел длины расширения, но разрез сделан по длине и не сделан по составу знаков: отправитель получает 500, владелец — строку ERROR, неотличимую от аварии хранилища
|
||||||
|
- **Теги:** review-2026-08-23
|
||||||
|
|
||||||
|
Расширение приходит из имени, данного отправителем, и приём судит у него только
|
||||||
|
длину. Управляющий знак внутри расширения проходит предел длины, доезжает до
|
||||||
|
заведения временной копии и роняет его: отправитель получает `500` за вход, за
|
||||||
|
который отвечает сам, а владелец — строку `ERROR`, неотличимую от аварии
|
||||||
|
хранилища. Ровно этот исход комментарий кода объявляет недопустимым и ради него
|
||||||
|
заводит предел длины.
|
||||||
|
|
||||||
|
Нашёл прогон ревью `config-test-headers-login` 2026-08-23. Триаж тест не
|
||||||
|
воспроизводил, поэтому находка понижена до гипотезы.
|
||||||
|
|
||||||
|
## Воспроизведение
|
||||||
|
|
||||||
|
Принять запись, чьё расширение несёт управляющий знак: ответ `500`, в журнале
|
||||||
|
строка `ERROR`. Ожидался отказ приёма своим кодом.
|
||||||
|
|
||||||
|
Падающий тест прохода `adversary`: имя доезжает формой RFC 2231
|
||||||
|
(`filename*=UTF-8''запись.mp%003`), ответ `500`, журнал
|
||||||
|
`level=ERROR msg="Failed to stage uploaded file" error="failed to create work file: open /tmp/transcriber-….mp\x003: invalid argument"`.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/service/transcribe.go:171-188` — разрез расширения по длине и сборка
|
||||||
|
имени копии;
|
||||||
|
- `internal/adapter/repo/sqlite/file_repo.go:56-67` — заведение временной копии,
|
||||||
|
которое и отказывает.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Приём судит состав знаков расширения так же, как судит длину. **Оракул:** тест
|
||||||
|
приёма — имя с управляющим знаком в расширении даёт отказ приёма своим кодом,
|
||||||
|
а не `500`; в журнале нет строки `ERROR` об отказе хранилища.
|
||||||
|
- Приём принимает годное расширение по-прежнему. **Оракул:** тот же тест — `.mp3`,
|
||||||
|
`.ogg` и имя без расширения дают прежний исход.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Половину про путь из `*os.PathError` в журнале держит
|
||||||
|
`journal-fields-bounded-by-service`, здесь она не переписывается.
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
- **Тип:** research
|
- **Тип:** research
|
||||||
- **Категория:** Очередь — Тот же род утечки в журнал, но вне темы входа — идёт следом за ней.
|
- **Категория:** Очередь — Тот же род утечки в журнал, но вне темы входа — идёт следом за ней.
|
||||||
- **Зачем:** Текст отказа операции приходит от Yandex и уезжает в журнал и в колонку error_text: если он несёт URI объекта, из журнала снова собирается ссылка на чужую запись.
|
- **Зачем:** Текст отказа операции приходит от Yandex и уезжает в журнал и в колонку error_text: если он несёт URI объекта, из журнала снова собирается ссылка на чужую запись.
|
||||||
|
- **Теги:** review-2026-08-23
|
||||||
|
|
||||||
Гипотеза ревью задачи `pocketbase-storage` 2026-08-12, оставшаяся без оракула:
|
Гипотеза ревью задачи `pocketbase-storage` 2026-08-12, оставшаяся без оракула:
|
||||||
поднять настоящий отказ Yandex нечем, а прогон на реальных ключах запрещён.
|
поднять настоящий отказ Yandex нечем, а прогон на реальных ключах запрещён.
|
||||||
@@ -10,6 +11,11 @@
|
|||||||
Object Storage больше не называют ключ, — но текст, пришедший **чужими руками**,
|
Object Storage больше не называют ключ, — но текст, пришедший **чужими руками**,
|
||||||
идёт в журнал и в задачу как есть.
|
идёт в журнал и в задачу как есть.
|
||||||
|
|
||||||
|
Подтверждено прогоном ревью `config-test-headers-login` 2026-08-23: та же
|
||||||
|
находка пришла второй раз. Оракула у неё нет и быть не может — нужен
|
||||||
|
настоящий SpeechKit, а прогон на реальных ключах запрещён («Yandex Cloud за
|
||||||
|
деньги»).
|
||||||
|
|
||||||
## Вопрос
|
## Вопрос
|
||||||
|
|
||||||
Несёт ли текст отказа операции распознавания адрес объекта, который мы отдали
|
Несёт ли текст отказа операции распознавания адрес объекта, который мы отдали
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# 🧹 Разобрать мелочи входа по доверенному заголовку
|
||||||
|
|
||||||
|
- **Тип:** chore
|
||||||
|
- **Категория:** Очередь — Место машинное, конец очереди: мелочи ниже потолка триажа никого не держат и ничего не требуют — берутся, когда дойдут
|
||||||
|
- **Зачем:** три пункта ниже потолка триажа остались от одной задачи: функция строки старта не вызвана ни одним тестом, форма записи необязательного поля конфига стала неотличима от обязательного, два комментария описывают снятую подкоманду и не то число звеньев цепочки
|
||||||
|
- **Теги:** review-2026-08-23
|
||||||
|
|
||||||
|
Три пункта ниже потолка триажа, оставшиеся от задачи `config-test-headers-login`.
|
||||||
|
Наблюдаемое поведение сервиса ни один из них не меняет: правится защита от
|
||||||
|
регрессии, форма записи умолчаний и два неверных комментария.
|
||||||
|
|
||||||
|
Нашёл прогон ревью `config-test-headers-login` 2026-08-23.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/controller/http/substitute.go` — функция имён подставляемых
|
||||||
|
заголовков и её тест;
|
||||||
|
- `internal/config/config.go`, `defaultConfig()` — форма записи умолчаний
|
||||||
|
необязательного поля;
|
||||||
|
- `Dockerfile:49-50` — комментарий об исключении `cmd/devtools` из сборки;
|
||||||
|
- `internal/controller/http/mounts.go:54-56` — комментарий, перечисляющий звенья
|
||||||
|
цепочки слоёв приложения.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Функцию имён подставляемых заголовков зовёт тест. **Оракул:**
|
||||||
|
`SubstitutedHeaderNames` не вызвана ни одним тестом (`go tool cover -func` →
|
||||||
|
0,0 %). Прогон настоящего бинарника печатает `headers=[Remote-User]` — имена,
|
||||||
|
не значения: дефекта сегодня нет, нет защиты от регрессии против требования
|
||||||
|
спеки «строка старта MUST не нести значений».
|
||||||
|
- Умолчания необязательных полей заданы в одном месте с прочими. **Оракул:**
|
||||||
|
умолчания `Debug` и `TestHeaders` заданы нулём типа, а не в `defaultConfig()`.
|
||||||
|
Нарушена `docs/conventions/config.md`, «Структура в коде»: «Умолчания задаются
|
||||||
|
в `defaultConfig()`… Новое поле требует правки обоих мест», и там же форма
|
||||||
|
обязательного поля — «умолчания нет ни в `defaultConfig()`, ни по нулевому
|
||||||
|
значению типа».
|
||||||
|
- Оба комментария описывают то, что есть. **Оракул:** комментарий
|
||||||
|
`Dockerfile:49-50` объясняет исключение `cmd/devtools` словами «оснастка
|
||||||
|
разработчика (подставной прокси)», а подкоманды больше нет; приёмочный шаг
|
||||||
|
искал следы по строке `devtools proxy` и эту не поймал.
|
||||||
|
`internal/controller/http/mounts.go:54-56` перечисляет три звена цепочки, тогда
|
||||||
|
как `AppChain` одевает четыре.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Поведение сервиса не меняется ни одним пунктом. Имя ключа конфига не трогается:
|
||||||
|
правится место, где задано его умолчание, а не сам ключ.
|
||||||
Reference in New Issue
Block a user