## ADDED Requirements

### Requirement: Выбор кандидата метабазы из карточки подтверждения бота

Карточка подтверждения бота SHALL предлагать выбрать источник из сохранённых
кандидатов метабазы inline-кнопками, когда для распознавания загрузки есть хотя
бы один такой кандидат (типичный триггер ревью — несколько подходящих записей без
единичного сильного матча), а не молча оставлять загрузку с первым/лучшим или без
базы. Когда сохранённых кандидатов нет, карточка кнопку выбора базы показывать
SHALL NOT. Выбор SHALL быть двухшаговым: кнопка выбора базы разворачивает список
кандидатов (по кнопке на кандидата, выбранный помечен), а нажатие кандидата
пиннит его как источник и обновляет карточку. Отдельная кнопка SHALL возвращать
карточку без изменений.

Выбор кандидата из бота — **ручной матч** (пиннинг источника): он SHALL NOT
запускать авто-раскладку; раскладка выполняется отдельным подтверждением
(«Применить»). Бот SHALL оставаться быстрым выбором из готового короткого списка
кандидатов, тогда как точные правки (ручной ввод id/URL, «без базы») — на веб-
поверхности.

Id кандидата приходит в `callback_data` и является недоверенным входом: перед
доменным вызовом выбора система MUST валидировать его как ULID на границе
транспорта; невалидный id MUST отклоняться без изменения состояния загрузки.
Текст inline-кнопок Telegram не интерпретируется как разметка, поэтому название
кандидата в подписи кнопки инъекции разметки не создаёт.

#### Scenario: Карточка с кандидатами предлагает выбор базы

- **GIVEN** загрузка в `review`, для распознавания которой сохранены кандидаты метабазы
- **WHEN** бот рендерит карточку подтверждения
- **THEN** карточка содержит кнопку выбора базы, ведущую к списку кандидатов inline-кнопками

#### Scenario: Выбор кандидата пиннит источник без авто-раскладки

- **GIVEN** развёрнутый в карточке список кандидатов метабазы
- **WHEN** пользователь нажимает кнопку кандидата
- **THEN** этот кандидат закрепляется как источник (ручной матч), карточка
  обновляется на месте, а авто-раскладка не запускается — для раскладки нужно
  явное «Применить»

#### Scenario: Id кандидата из callback валидируется на границе

- **GIVEN** callback выбора кандидата, где значение id кандидата недоверенное/невалидное
- **WHEN** бот обрабатывает callback
- **THEN** id кандидата валидируется как ULID до доменного вызова, а невалидное
  значение отклоняется без изменения состояния загрузки

#### Scenario: Возврат из списка не меняет состояние

- **GIVEN** развёрнутый в карточке список кандидатов
- **WHEN** пользователь нажимает кнопку возврата
- **THEN** бот возвращает исходную карточку подтверждения, не меняя выбранный источник

#### Scenario: Без кандидатов кнопки выбора базы нет

- **GIVEN** загрузка в `review`, для распознавания которой кандидаты метабазы не сохранены
- **WHEN** бот рендерит карточку подтверждения
- **THEN** карточка не содержит кнопку выбора базы
