точки входа переехали в cmd/, заведена заглушка OIDC для локального входа
- main.go и journal_route_test.go переехали в cmd/transcriber без правок содержимого; образ собирает ./cmd/transcriber поимённо - cmd/oidcstub отвечает на /authorize, /token и /userinfo, проверок не делает и слушает петлевой адрес: войти без Authelia стало чем - ступень сборки приложения переехала на node:24 с alpine — musl ждёт ответа на AAAA, которого нет, и npm ci висел вместо отказа
This commit is contained in:
+13
-2
@@ -77,8 +77,8 @@
|
||||
[pipeline](../openspec/specs/pipeline/spec.md), «Брошенная задача возвращается
|
||||
в работу»; здесь это принцип письма шага, а не описание поведения.
|
||||
- **Ядро зависит от интерфейсов.** `internal/service` знает только
|
||||
`internal/contract`; ffmpeg, Yandex и хранилище подставляются в
|
||||
`main.go`. Правило механизировано тестами-сканерами `internal/archrules`, и
|
||||
`internal/contract`; ffmpeg, Yandex и хранилище подставляются в точке входа
|
||||
`cmd/transcriber`. Правило механизировано тестами-сканерами `internal/archrules`, и
|
||||
они же держат обратные направления: транспорты не знают друг о друге, адаптер
|
||||
не знает ни ядра, ни транспортов.
|
||||
*Изъятие:* транспорт **вправе** знать адаптер хранилища — `controller/http`
|
||||
@@ -219,6 +219,17 @@
|
||||
не попадает. Финальный слой — alpine с `ca-certificates` и `ffmpeg`, процесс
|
||||
работает под непривилегированным пользователем `transcriber`.
|
||||
|
||||
Ступень бинарника собирает **одну** точку входа — `./cmd/transcriber`, а не весь
|
||||
пакет: рядом в `cmd/` живёт `oidcstub`, подставной провайдер OIDC для локального
|
||||
входа, и в образе ему делать нечего.
|
||||
|
||||
Ступень приложения стоит на образе с glibc, а не на alpine, и решает это не вес:
|
||||
у musl запрос имени идёт `A` и `AAAA` разом и ждёт **оба** ответа, поэтому
|
||||
DNS-сервер, молчащий на `AAAA`, оставляет установщика пакетов без адреса при
|
||||
живом `A`. Установщик уходит в повторы с нарастающей паузой на каждом пакете, и
|
||||
сборка не краснеет, а **висит** — исход хуже красного. Слои этой ступени в
|
||||
рабочий слой не едут, поэтому её вес остаётся ценой одной сборки.
|
||||
|
||||
**По весу финальный образ от ступени приложения не растёт вовсе:** она отдаёт
|
||||
следующей только собранное, а сама в рабочий слой не копируется. Вшитое
|
||||
приложение прибавляет к бинарнику 86 072 байта. Время сборки образа не
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
**Взято из проекта jellybit.** Расхождения с сегодняшним кодом названы по месту.
|
||||
Главные: комментариями снабжена половина полей; единого места проверки на старте
|
||||
нет: у секций `[auth]` и `[pipeline]` свой `Validate()` в `main.go`, а пустые
|
||||
нет: у секций `[auth]` и `[pipeline]` свой `Validate()` в точке входа, а пустые
|
||||
ключи `[yandex]` ловит конструктор распознавателя.
|
||||
|
||||
**Механизировано:** запрет `os.Getenv` — `forbidigo` в `.golangci.yml`
|
||||
([go-linters.md](go-linters.md), «Механизировано»). Он держит правило «настройки
|
||||
приезжают из TOML»; `godotenv` в `main.go` по-прежнему загружает `.env`, но кладёт
|
||||
приезжают из TOML»; `godotenv` в `cmd/transcriber` по-прежнему загружает `.env`, но кладёт
|
||||
его в окружение процесса, а не в настройки приложения.
|
||||
|
||||
## Принципы
|
||||
@@ -18,7 +18,7 @@
|
||||
- **Конфигурация — только TOML.** Переменные окружения для конфигурации **не
|
||||
используем**: окружение наследуется дочерними процессами и видно через
|
||||
`/proc/<pid>/environ` — для секретов это слабее файла под `0600`.
|
||||
*Расхождение:* `main.go` зовёт `godotenv.Load()` и молча продолжает без файла.
|
||||
*Расхождение:* `cmd/transcriber` зовёт `godotenv.Load()` и молча продолжает без файла.
|
||||
- Грузим **один раз при старте** в одну типизированную структуру `Config`
|
||||
(под-структуры по секциям). Дальше по коду читаем только её — чтения файла в
|
||||
прикладном коде нет, только загрузчик `internal/config`.
|
||||
@@ -62,6 +62,12 @@ force_shutdown_timeout = <N> # ждать остановки ворке
|
||||
говорит, какой формы значение здесь ждут. Пустым оставлен только
|
||||
`client_secret` — он и есть секрет.
|
||||
|
||||
Там же, комментарием под секцией, стоит **второй набор значений `[auth]` — под
|
||||
подставной провайдер `cmd/oidcstub`**. Они — не живая форма, и это намеренно:
|
||||
образец описывает боевую выкладку, а локальный вход — способ до неё дойти, и два
|
||||
рабочих набора в одном файле читались бы как выбор без указания, какой из них
|
||||
чей.
|
||||
|
||||
## Поля по дискриминатору `type`
|
||||
|
||||
Когда набор полей секции зависит от поля-дискриминатора `type` (выбор одного из
|
||||
@@ -130,7 +136,7 @@ TOML. Пустые ключи Yandex ловятся в конструкторе
|
||||
входом 2026-08-14: секции больше нет, и своей проверки у неё тоже.
|
||||
|
||||
Секция `[auth]` — первая, у которой проверка своя и стоит на старте:
|
||||
`AuthConfig.Validate()` зовётся из `main.go` сразу после загрузки и роняет
|
||||
`AuthConfig.Validate()` зовётся из `cmd/transcriber` сразу после загрузки и роняет
|
||||
процесс с перечнем незаполненных ключей. Причина в цене умолчания: поднявшись с
|
||||
молча выключенным входом, сервис остался бы открытым наружу, а узнать об этом
|
||||
было бы неоткуда. Сообщение называет **имена ключей**, а не значения — значение
|
||||
|
||||
@@ -31,7 +31,10 @@ OpenSpec.
|
||||
{"time":"2026-08-10T11:23:45.123456Z","level":"INFO","msg":"record accepted","capability":"intake","record_id":"…","source":"api","duration_seconds":137}
|
||||
```
|
||||
|
||||
*Расхождение:* `main.go` ставит `slog.NewTextHandler(os.Stdout, …)`.
|
||||
*Расхождение:* текстовый обработчик ставят **оба** пакета `cmd/` —
|
||||
`slog.NewTextHandler(os.Stdout, …)` и в `transcriber`, и в `oidcstub`. У второго
|
||||
это выбор, а не долг: его вывод читает человек в терминале, и разбирать его
|
||||
отбором никто не станет.
|
||||
|
||||
## Сообщение
|
||||
|
||||
@@ -78,7 +81,7 @@ OpenSpec.
|
||||
- `slog` не разделяет CRITICAL и FATAL — сбой на старте логируем `ERROR` и
|
||||
завершаем процесс с ненулевым кодом.
|
||||
|
||||
*Расхождение:* уровень зашит константой в `main.go`, `DEBUG` включить нечем.
|
||||
*Расхождение:* уровень зашит константой в `cmd/transcriber`, `DEBUG` включить нечем.
|
||||
Пустой прогон воркера не логируется вовсе — и это правилу не противоречит.
|
||||
|
||||
## Время
|
||||
@@ -208,7 +211,7 @@ Object Storage и опрос операции не логируются ника
|
||||
`INFO` они не пишутся.
|
||||
|
||||
Расхождения здесь больше нет: слой журналирования запросов свой,
|
||||
`main.go`, хук `OnServe` — вместе с gin ушёл и `sloggin`. `/health` и `/metrics`
|
||||
`cmd/transcriber`, хук `OnServe` — вместе с gin ушёл и `sloggin`. `/health` и `/metrics`
|
||||
идут на `DEBUG`, то есть при боевом `INFO` не пишутся вовсе.
|
||||
|
||||
Хранилище ведёт **свой** журнал запросов в собственной таблице, и он виден
|
||||
|
||||
@@ -169,6 +169,20 @@ pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайн
|
||||
**Куки библиотека не читает вовсе** — сессию берёт только заголовком
|
||||
`Authorization` (`apis/middlewares.go`, `getAuthTokenFromRequest`).
|
||||
|
||||
**Учётную запись обмен ищет двумя способами подряд, а связь с провайдером
|
||||
уникальна.** Сперва — по неизменяемому признаку провайдера, а не найдя — по
|
||||
адресу почты (`apis/record_auth_with_oauth2.go`, ветка `case authUser.Email !=
|
||||
""` → `FindAuthRecordByEmail`). Найденной записи он пытается добавить связь, а на
|
||||
ней стоит уникальный индекс
|
||||
`idx_externalAuths_record_provider (collectionRef, recordRef, provider)`. Отсюда
|
||||
исход, обратный ожидаемому: два разных признака провайдера с **одной** почтой не
|
||||
сливаются в одного владельца молча — второй вход отвергается, обмен отдаёт `400`,
|
||||
сервис — `401` со строкой `Failed to exchange provider code`, а настоящая причина
|
||||
остаётся в журнале хранилища строкой `failed to save linked rel: … Value must be
|
||||
unique`. Дописано 2026-08-15 задачей про заглушку OIDC; получено прогоном против
|
||||
временного каталога — чтение исходников давало ту же цепочку, но противоположную
|
||||
развязку.
|
||||
|
||||
**Журнал запросов пишет строку запроса целиком.** `activityLogger` на корневом
|
||||
роутере кладёт `RequestURI` полем `url` в таблицу `_logs`, ретеншен по умолчанию
|
||||
`MaxDays: 5`. Значит всё, что пришло параметром адреса, оседает там на пять
|
||||
|
||||
@@ -119,6 +119,18 @@
|
||||
|
||||
### Типовые ложноположительные
|
||||
|
||||
- **«Хранилище молча сливает две учётные записи с одной почтой в одного
|
||||
владельца».** Для версии v0.39.10 неверно, и неверна именно развязка. Первая
|
||||
половина цепочки настоящая: обмен ищет запись по признаку провайдера, а не
|
||||
найдя — по адресу почты, и приходит к чужой записи. Но повесить на неё второй
|
||||
признак он не может — уникальный индекс
|
||||
`idx_externalAuths_record_provider (collectionRef, recordRef, provider)` связь
|
||||
отвергает, обмен отдаёт `400`, а сервис — `401` со строкой
|
||||
`Failed to exchange provider code`. Отказ **громкий**, тихого слияния владельцев
|
||||
не происходит, и ложно-зелёной проверки разграничения такой дефект не даёт.
|
||||
Проверено прогоном 2026-08-15 (задача про заглушку OIDC); найдено чтением
|
||||
исходников библиотеки, опровергнуто запуском — то есть цена гипотезы, добытой
|
||||
без прогона, здесь и измерена.
|
||||
- **«Воркер глотает ошибку `NoopJobError`».** Не дефект: этот тип означает «задач
|
||||
в этом состоянии нет», и `internal/controller/worker/worker.go` намеренно не
|
||||
логирует его и не считает в метрику. Норма записана требованием
|
||||
@@ -159,6 +171,14 @@
|
||||
|
||||
Форма: `<тема>: <вопрос> (<откуда>)`.
|
||||
|
||||
- `operations`: не завёл ли инструмент разработчика второй дом тому, что уже есть
|
||||
в проверках. Подставных провайдера OIDC в репозитории теперь два — `cmd/oidcstub`
|
||||
и `fakeProvider` в `internal/controller/http/login_test.go`, — с теми же
|
||||
адресами и той же посылкой про `email_verified`, и они уже разошлись в мелочи
|
||||
(`token_type` «bearer» против «Bearer»). Тем же вопросом судится подставной
|
||||
распознаватель. Записанной конвенции о единственном доме подставных внешних
|
||||
собеседников у проекта нет, поэтому спрашивать надо, а не считать нарушением
|
||||
(ревью задачи про заглушку OIDC, 2026-08-15).
|
||||
- `operations`: как шаг отвечает на отмену посреди работы — контекст доходит до
|
||||
внешнего собеседника и это держат правила `noctx` и `contextcheck`
|
||||
([conventions/go-linters.md](conventions/go-linters.md), «Отмена и внешний
|
||||
@@ -312,6 +332,27 @@ API и имя не откатываются обратной правкой по
|
||||
истории git 2026-08-10: поле «Чем воспроизведён» называет у них коммит, а не
|
||||
оракул, и выдумывать оракул задним числом нельзя.
|
||||
|
||||
## 2026-08-15 — короткая форма рецепта входа не работала, а проверяли длинную [пойман ревью]
|
||||
|
||||
- **Где:** `cmd/oidcstub` — подставной провайдер OIDC для локального входа;
|
||||
доккоммент пакета, подсказка флага `-sub` и проза `config.example.toml`
|
||||
- **Симптом:** рецепт «второй вошедший получается сменой `-sub`» записан в трёх
|
||||
местах и в короткой форме не работал вовсе. Заглушка отдавала обоим `sub` одну
|
||||
и ту же почту умолчанием, вход отвечал `401`, а причина оставалась строкой в
|
||||
журнале хранилища
|
||||
- **Причина:** обмен ищет учётную запись сперва по признаку провайдера, а не
|
||||
найдя — по адресу почты. Второй `sub` при общей почте приходил к первой записи,
|
||||
а признак провайдера на записи уникален — `idx_externalAuths_record_provider` —
|
||||
и связь отвергалась. Умолчание почты стояло своим значением вместо выведенного
|
||||
из `sub`
|
||||
- **Почему не поймали раньше:** рецепт проверяли **длинной** формой, где почта
|
||||
задана флагом явно. Короткую не гонял никто, хотя записана она первой и берут
|
||||
читатели именно её
|
||||
- **Что меняем:** проверять ту форму рецепта, которая записана **короче всех**.
|
||||
Оракул — прогон именно её: два входа подряд разными `-sub` без прочих флагов,
|
||||
затем счёт записей в коллекции пользователей. Само умолчание почты теперь
|
||||
выводится из `-sub`
|
||||
|
||||
## 2026-08-15 — своя раздача статики потеряла отказ от записи успеха [пойман ревью]
|
||||
|
||||
- **Где:** `internal/controller/http/webapp.go`, регистрация корневого маршрута;
|
||||
|
||||
@@ -335,6 +335,13 @@ Storage, оттуда его читает SpeechKit. Третий путь —
|
||||
|
||||
- **Атака на сам сервер и на контур.** Компрометация хоста, прокси, Docker и
|
||||
Ansible — не наша граница.
|
||||
- **Машина разработчика и то, что он на ней поднимает.** С 2026-08-15 в
|
||||
репозитории лежит `cmd/oidcstub` — подставной провайдер OIDC, который выдаёт
|
||||
сессию всякому спросившему и не сверяет ни секрета клиента, ни проверочного
|
||||
кода PKCE. Двух вещей это не отменяет, и обе проверяемы: в образ он не едет
|
||||
(ступень собирает `./cmd/transcriber` поимённо), а слушает петлевой адрес.
|
||||
Периметра выкладки заглушка поэтому не касается; кто поднял её у себя в чужой
|
||||
сети, отвечает за это сам.
|
||||
- **Злоупотребление со стороны пользователя из белого списка.** Приглашённому
|
||||
доверяем полностью.
|
||||
- **Достоверность расшифровки.** Подмена или искажение текста на стороне
|
||||
|
||||
Reference in New Issue
Block a user