удалён вход Telegram, владелец записи стал обязателен в схеме
- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
This commit is contained in:
+91
-38
@@ -51,14 +51,14 @@
|
||||
- повтор шага на той же задаче не создаёт лишних файлов и записей;
|
||||
- отвечает пользователю ровно один раз.
|
||||
|
||||
**Транспорт** (`internal/controller/tg`, `internal/controller/http`):
|
||||
**Транспорт** (`internal/controller/http`):
|
||||
|
||||
- проверяет право отправителя до всякой работы;
|
||||
- не логирует ошибку, которую уже залогировал доменный слой;
|
||||
- переводит доменную ошибку в свой ответ, а не отдаёт сырой текст;
|
||||
- закрывает то, что открыл, на всех ветках выхода.
|
||||
|
||||
**Клиент внешнего сервиса** (`adapter/recognizer/yandex`, `adapter/telegram`):
|
||||
**Клиент внешнего сервиса** (`adapter/recognizer/yandex`):
|
||||
|
||||
- имеет таймаут и не виснет, когда внешний сервис не отвечает;
|
||||
- не кладёт секрет в URL и не даёт ему утечь через ошибку транспорта;
|
||||
@@ -121,17 +121,19 @@
|
||||
- **«Файлы и объекты не удаляются, диск растёт».** Факт верный и записан в
|
||||
[database.md](database.md); срок хранения не задан сознательно, задачи на него нет.
|
||||
Новой находкой это не считается, пока не измерен рост.
|
||||
- **«Запись без владельца не достаётся никому».** Не дефект: владельца не имеют
|
||||
записи, принятые ботом, — связи чата Telegram с учётной записью приложения
|
||||
сервис не ведёт, её заводит `telegram-account-link`. Ответ такой записи по API
|
||||
совпадает с ответом на несуществующую, и это норма — расшифровку отправитель
|
||||
получает в чат.
|
||||
- **«Запись без владельца не достаётся никому».** Строка отменена **дважды**, и
|
||||
обе отмены оставлены намеренно: прогон, помнящий любую из прежних редакций,
|
||||
иначе выбросил бы настоящую находку как известную.
|
||||
|
||||
**Прежняя редакция этой строки отменена 2026-08-14.** До задачи
|
||||
`record-ownership` здесь стояло «вошедший видит чужие записи — не дефект и не
|
||||
новость»: разграничения не было сознательно. Теперь оно есть, и такая находка
|
||||
настоящая. Строка оставлена вместо удаления намеренно: прогон, помнящий её
|
||||
прежний вид, выбросил бы регрессию не глядя.
|
||||
До задачи `record-ownership` здесь стояло «вошедший видит чужие записи — не
|
||||
дефект и не новость»: разграничения не было сознательно. Первая отмена
|
||||
2026-08-14 завела разграничение и объявила не дефектом уже другое — запись без
|
||||
владельца, принятую ботом.
|
||||
|
||||
Вторая отмена того же дня, задачей `remove-telegram-intake`, сняла и это:
|
||||
колонка владельца пустого значения больше не принимает, ничьих записей у
|
||||
сервиса не бывает вовсе. **Запись без владельца сегодня — настоящая находка**,
|
||||
а не известное исключение.
|
||||
|
||||
### Вопросы по темам
|
||||
|
||||
@@ -145,10 +147,10 @@
|
||||
делает с задачей, деньгами и ответом отправителю» (чтение `worker.go` и
|
||||
`transcribe.go`, 2026-08-13; прежняя запись от 2026-08-10 устарела вместе с
|
||||
дефектом «остановка хоронила запись»).
|
||||
- `operations`: появился ли таймаут у обращения к Telegram, S3 и SpeechKit — ни у
|
||||
одного из них таймаута нет, и проброс контекста на этот вопрос **не отвечает**:
|
||||
контекст здесь несёт жизнь процесса, а не дедлайн вызова (чтение `tg.go`,
|
||||
`s3.go`, `speechkit.go`, 2026-08-13).
|
||||
- `operations`: появился ли таймаут у обращения к S3 и SpeechKit — ни у одного
|
||||
из них таймаута нет, и проброс контекста на этот вопрос **не отвечает**:
|
||||
контекст здесь несёт жизнь процесса, а не дедлайн вызова (чтение `s3.go`,
|
||||
`speechkit.go`, 2026-08-13).
|
||||
- `operations`: не удвоилась ли запись об одном сбое — шаг логирует ошибку и
|
||||
возвращает её воркеру, который логирует снова (чтение `transcribe.go`,
|
||||
2026-08-10).
|
||||
@@ -161,14 +163,12 @@
|
||||
- `security`: не уходит ли значение, пришедшее снаружи, меткой метрики — страница
|
||||
метрик отдаётся без проверки отправителя, и метка это поверхность пошире
|
||||
журнала (журнал, запись 2026-08-11 про хвост имени).
|
||||
- `architecture`: не появился ли второй путь приёма мимо
|
||||
`createTranscribeJob` — сегодня через него идут оба входа
|
||||
- `architecture`: не появился ли второй путь приёма мимо `createRecord` — сегодня
|
||||
он единственный, которым запись попадает в хранилище
|
||||
([architecture.md](architecture.md), «Единые точки проекта»).
|
||||
- `architecture`: не поехало ли поведение в `architecture.md` вместо спеки —
|
||||
заведены четыре capability (`intake`, `pipeline`, `storage`, `access`), и
|
||||
первые две описаны частично. Поведение прочих узлов, включая
|
||||
приём из Telegram, живёт в обзоре под маркерами долга, а соблазн дописать туда
|
||||
ещё — самый большой.
|
||||
заведённые capability описывают поведение не целиком, и остаток живёт в обзоре
|
||||
под маркерами долга, а соблазн дописать туда ещё — самый большой.
|
||||
- `conventions`: новая колонка правится в обоих местах репозитория, а новый
|
||||
рубеж — одним дескриптором
|
||||
(CLAUDE.md, «Инварианты»).
|
||||
@@ -205,12 +205,12 @@
|
||||
- замена хранилища или переход на PocketBase — любой её кусок;
|
||||
- смена модели очереди: захват, повторы и воркеры разом;
|
||||
- каркас приложения: сборка фронтенда, раздача статики и шаг гейта разом;
|
||||
- изменение, трогающее оба входа сразу — Telegram и HTTP.
|
||||
- изменение, убирающее или возвращающее вход приёма целиком.
|
||||
|
||||
**Незнакомое здесь** (поднимает до `large`, ось формы решения):
|
||||
|
||||
- вход через OIDC и разграничение доступа: как связаны пользователь Telegram и
|
||||
пользователь приложения, до начала работы назвать нельзя;
|
||||
- вход через OIDC и разграничение доступа: как связать чат Telegram с учётной
|
||||
записью, до начала работы назвать нельзя;
|
||||
- всё, что делается на выбранном фреймворке впервые: правила
|
||||
[conventions/web-ui.md](conventions/web-ui.md) выведены из выбора и из замера
|
||||
на пробном экране, а не из написанного кода, и первая же задача проверяет их
|
||||
@@ -224,7 +224,6 @@
|
||||
|
||||
**Мелкое здесь** (опускает до `small`):
|
||||
|
||||
- правка текста, который видит пользователь Telegram;
|
||||
- новая метрика в `internal/metrics`;
|
||||
- правка `config.example.toml` и умолчаний `defaultConfig()` без нового поля;
|
||||
- правка документов канона.
|
||||
@@ -259,19 +258,19 @@ API и имя не откатываются обратной правкой по
|
||||
`adapter/metaviewer/ffmpeg` нет; решение и его цена — в
|
||||
[adr/ADR-2026-08-11-stub-adapters-in-tests.md](adr/ADR-2026-08-11-stub-adapters-in-tests.md);
|
||||
- **работа сервиса с настоящими внешними собеседниками.** Сам сервис поднять
|
||||
теперь можно: с `telegram.enabled = false` он встаёт и работает одним входом
|
||||
(`openspec/specs/intake`, «Признак включения решает, поднимается ли вход
|
||||
Telegram»). Живой прогон — осмотр HTTP, панели, журнала и остановки — доступен
|
||||
теперь любой задаче. Прежняя формулировка «всё, что требует поднять сервис целиком»
|
||||
снята задачей `local-run-without-telegram-token` 2026-08-13; рецепт прогона
|
||||
сменился с пустого ключа доступа на выключенный вход задачей
|
||||
`telegram-enabled-flag` того же дня.
|
||||
можно: он встаёт своим единственным входом на выдуманных непустых ключах
|
||||
секций `[auth]` и `[yandex]` — наружу они на старте не ходят. Живой прогон —
|
||||
осмотр HTTP, панели, журнала, метрик и остановки — доступен любой задаче.
|
||||
Прежняя формулировка «всё, что требует поднять сервис целиком» снята задачей
|
||||
`local-run-without-telegram-token` 2026-08-13; рецепт прогона менялся дважды —
|
||||
с пустого ключа доступа на выключенный вход (`telegram-enabled-flag` того же
|
||||
дня), а 2026-08-14 признак включения ушёл вместе с самим входом.
|
||||
|
||||
**Остаток**: за настоящий Telegram, SpeechKit и Object Storage живой прогон
|
||||
по-прежнему не отвечает — боевым токеном запускаться запрещено, ключи Yandex в
|
||||
прогоне выдуманные, а распознавание подменяют в коде. Проверить живьём можно
|
||||
подъём, отказ старта, маршруты и остановку; нельзя — приём из Telegram,
|
||||
расшифровку и заливку.
|
||||
**Остаток**: за настоящие SpeechKit и Object Storage живой прогон по-прежнему
|
||||
не отвечает — ключи Yandex в прогоне выдуманные, а распознавание подменяют в
|
||||
коде. Проверить живьём можно подъём, отказ старта, маршруты, метрики и
|
||||
остановку; нельзя — расшифровку и заливку. Вход через живого провайдера OIDC
|
||||
тоже недоступен: сессию в прогоне выдать нечем.
|
||||
|
||||
## Журнал дефектов
|
||||
|
||||
@@ -281,6 +280,60 @@ API и имя не откатываются обратной правкой по
|
||||
истории git 2026-08-10: поле «Чем воспроизведён» называет у них коммит, а не
|
||||
оракул, и выдумывать оракул задним числом нельзя.
|
||||
|
||||
## 2026-08-15 — пустой второй ответ распознавателя стирал сохранённую расшифровку [пойман ревью]
|
||||
|
||||
- **Где:** `internal/adapter/repo/pocketbase/text_repo.go`, `TextRepository.Put`
|
||||
и `StructureRepository.Put`; путь до них — `poll` → `storeOutcome` в
|
||||
`internal/service/transcribe.go`. Кода задачи `remove-telegram-intake` дефект не
|
||||
касался: она этот путь не трогала
|
||||
- **Симптом:** поток от SpeechKit, закрывшийся на первом же ответе, отказом не
|
||||
считается — наружу уходит пустой результат без отказа. Замена содержимого шла
|
||||
безусловно, и повторный опрос той же операции клал пустое поверх сохранённой
|
||||
расшифровки. Шаг при этом объявлял запись готовой: рубеж двигался, опрос
|
||||
готовности отдавал `done` без текста
|
||||
- **Причина:** соседний хранитель того же результата — сырой ответ провайдера —
|
||||
от пустого значения защищён условием `len(raw) > 0` с самого заведения, а текст
|
||||
и структура реплик такого условия не имели. Разное правило у двух хранителей
|
||||
одного результата
|
||||
- **Чем воспроизведён:** падающий тест враждебного прохода, переснятый триажем, —
|
||||
`expected: "Личный разговор." actual: ""`. Оракул закреплён в дереве:
|
||||
`internal/service/recognition_test.go`, `TestEmptySecondAnswerKeepsArchivedText`;
|
||||
он же проверяет, что до второго ответа дело действительно дошло
|
||||
- **Почему не поймали раньше:** повторный опрос одной операции — не редкость, но
|
||||
и не штатный путь: он наступает, когда держатель захвата умер, сохранение рубежа
|
||||
отказало либо человек снял остановку в панели. Ни один прогон до этого не строил
|
||||
такого входа, а от чтения кода защита у соседа выглядела общей
|
||||
- **Что меняем:** правило «пустое не кладётся поверх сохранённого» записано
|
||||
нормой в спеку `storage` и держится **хранилищем**, а не шагом: шагов, кладущих
|
||||
текст, больше одного, и правило у одного из них у остальных читалось бы как
|
||||
снятое. Дефект пред-существующий, чинился решением владельца от 2026-08-14 в
|
||||
задаче, которая его нашла
|
||||
|
||||
## 2026-08-15 — пустая расшифровка перестала быть заметной вместе с убранным входом [пойман ревью]
|
||||
|
||||
- **Где:** `internal/service/transcribe.go`, шаг завершения; документы
|
||||
`docs/conventions/logging.md` и `docs/architecture.md`
|
||||
- **Симптом:** запись с пустым распознаванием доходила до конечного рубежа и от
|
||||
успешной не отличалась ничем — ни строкой журнала, ни ответом опроса
|
||||
- **Причина:** единственным следом этого случая был текст, уходивший отправителю
|
||||
в чат («на записи нет текста»). Задача убрала доставку целиком, и след исчез
|
||||
вместе с ней — при том, что конвенция журнала называет пустой текст
|
||||
распознавания поимённым примером уровня «может стать проблемой», а обзор
|
||||
архитектуры обещал заглушку
|
||||
- **Чем воспроизведён:** `internal/service/recognition_test.go`,
|
||||
`TestEmptyRecognitionIsNamedInJournal` — подставной распознаватель отдаёт
|
||||
готовую операцию с пустым результатом, проверка судит уровень строки и
|
||||
идентификатор записи
|
||||
- **Почему не поймали раньше:** удаление сняло **последнего потребителя** видимого
|
||||
признака, а не сам признак; такое не видно ни компилятору, ни грепу по
|
||||
удаляемому имени. Нашёл проход конвенций, сверив таблицу уровней журнала с тем,
|
||||
что осталось в коде
|
||||
- **Что меняем:** шаг опроса пишет строку уровня «может стать проблемой» с
|
||||
идентификатором записи; строка обзора архитектуры переписана на фактическое
|
||||
поведение. Класс общий: **удаляя канал, проверь, не был ли он единственным
|
||||
потребителем сигнала** — сигнал переживает канал только там, где его переносят
|
||||
руками
|
||||
|
||||
## 2026-08-13 — сторож инварианта про секрет искал подстроку, которой не бывает [пойман ревью]
|
||||
|
||||
- **Где:** `internal/config/config_test.go`, проверка «значение ключа доступа не
|
||||
|
||||
Reference in New Issue
Block a user