Files
jellybit/openspec/changes/archive/2026-07-18-telegram-vybor-nahodok/specs/notifications/spec.md
T
avandClaude Opus 4.8 f75d6b1f91 tgbot: выбор кандидата метабазы inline-кнопками в карточке ревью
Когда для распознавания сохранены кандидаты метабазы, карточка подтверждения
бота показывает кнопку «🗂 База (N)». По ней двухшагово (как delete/dismiss)
разворачивается список кандидатов inline-кнопками; выбор пиннит источник через
worker.ChooseCandidate (ручной матч, без авто-раскладки) и обновляет карточку.
Веб остаётся точкой точных правок (ручной ввод id/URL, «без базы»).

Безопасность границы: id кандидата из callback_data валидируется как ULID
(ident.Parse) до доменного вызова, как в вебе. Текст inline-кнопок Telegram не
парсится как HTML — название кандидата в подписи не экранируется.

SDD: change telegram-vybor-nahodok — дельта notifications (ADDED «Выбор
кандидата метабазы из карточки подтверждения бота») + review (MODIFIED
«Разделение труда транспортов»: быстрый выбор кандидата — Telegram-действие).
Влито в specs, change заархивирован. Миграций БД нет (кандидаты уже в БД).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 16:02:47 +03:00

4.9 KiB

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