## 1. Интерфейс транспорта

- [x] 1.1 `internal/tgbot/bot.go`: добавить в интерфейс `Reviewer` метод
  `ChooseCandidate(ctx context.Context, id, candidateID string) error`.
- [x] 1.2 `internal/tgbot/bot_test.go`: дополнить `fakeReviewer` методом
  `ChooseCandidate` (запоминает выбор для проверки).

## 2. Клавиатуры (render.go)

- [x] 2.1 `reviewKeyboard`: когда `len(rd.Candidates) > 0`, добавить кнопку
  «🗂 База (N)» с callback `sources:<id>`.
- [x] 2.2 Новая `candidatesKeyboard(rd)`: по кнопке на кандидата (дедуп по
  `provider:provider_id`, потолок), текст `provider · Title (Year)`, у `Chosen` —
  префикс «✓ », callback `pick:<id>:<candidateID>`; нижняя кнопка «← Назад» с
  callback `srcback:<id>`.

## 3. Обработка callback (bot.go)

- [x] 3.1 `handleCallback`: ветки `sources` (answer + `editMarkup`
  списком кандидатов, перечитав `ReviewData`; домен не трогаем) и `srcback`
  (answer «Отменено» + `refreshCard`).
- [x] 3.2 Ветка `pick`: валидировать `val` (id кандидата) через `ident.Parse`
  (невалидный → answer «Кнопка устарела…», состояние не меняем), затем
  `reviewer.ChooseCandidate(ctx, id, candidateID)`; успех → answer «Источник
  выбран» + `refreshCard`; ошибка — общая ветка ошибки.

## 4. Тесты

- [x] 4.1 `internal/tgbot`: карточка ревью с кандидатами содержит кнопку
  `sources:<id>`; без кандидатов — не содержит.
- [x] 4.2 `sources` разворачивает клавиатуру с кнопками `pick:<id>:<candID>` и
  «← Назад»; выбранный кандидат помечен «✓ ».
- [x] 4.3 `pick` с валидным id зовёт `ChooseCandidate` с этим id и обновляет
  карточку; `pick` с невалидным id — не зовёт домен.

## 5. Спека и проверки

- [x] 5.1 `openspec validate --strict telegram-vybor-nahodok`.
- [x] 5.2 `task test` и `task lint` — зелёные.
