## 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** карточка не содержит кнопку выбора базы