2c9016a383
The 3.9.1 hotfix only repaired WizardDraftRepository, the most common
Dapper call in the wizard. The same AOT-unsafe CommandDefinition pattern
remained in 4 other places that the user hit immediately after the
deploy: the 'Choose visibility' wizard step triggers GetOwnerClubsAsync
when the user picks 'Публичная в витрине клуба' or 'Только для членов
клуба'. The wizard swallowed PlatformNotSupportedException, the
callback ack replied with '⚠️ Ошибка', and the next step never rendered.
Privacy 'didn't stick' from the user's perspective.
Two changes to fix the Discord side as well:
1. Switched GetOwnerClubsAsync / LoadClubsAsync / LoadManagerUserIdsAsync
to the direct (sql, params) overload across TelegramWizardMessenger,
DiscordWizardMessenger, DiscordWizardInteractionModule, and
DiscordPermissionLookup — same pattern as the 3.9.1 fix.
2. Added Dapper.AOT module attribute ([module: Dapper.DapperAot]) and
InterceptorsPreviewNamespaces to the DiscordBot project. The
DiscordBot assembly was previously skipped by the AOT source
generator, so even the direct-overload fix wouldn't have produced
interceptors for the Discord-specific Dapper call sites. With this
addition, the generator emits 3 DiscordBot-specific interceptors
(DiscordWizardMessenger, DiscordWizardInteractionModule,
DiscordPermissionLookup) and the AssemblyLoad ships with the right
GmRelay.DiscordBot.generated.cs.
Also expanded the AOT shape regression tests to cover all 4
CommandDefinition sites + added a 'containingClass' parameter to
ExtractMethodBody to disambiguate the duplicated LoadClubsAsync names
in DiscordWizardInteractionModule.
Bumps: 3.9.1 -> 3.9.2.
23 lines
12 KiB
JSON
23 lines
12 KiB
JSON
{
|
||
"last_cycle": [
|
||
{
|
||
"task_id": "discord-wizard-impl",
|
||
"verdict": "manual_retry",
|
||
"reason": "Verifier FAIL — критически отсутствует `DiscordWizardInteractionModule`. Без него wizard может отрисовать только первый экран, кнопки/select/modal не обрабатываются, submitter зарегистрирован в DI но не вызывается. Стейт-машина Shared + IWizardMessenger + Discord-рендерер всё на месте, но без interaction module весь wizard декоративный. Также: deliverable.md в корне репо описывает Task 1, не Discord-адаптер; нет smoke-тестов на новый код. Новая попытка: добавить ТОЛЬКО interaction module, подключить submitter, обновить deliverable.md."
|
||
}
|
||
],
|
||
"next_cycle": [
|
||
{
|
||
"task_id": "discord-wizard-impl",
|
||
"title": "Discord-адаптер: добавить interaction module (handlers) + подключить submitter",
|
||
"prompt": "**Единственная цель** этой попытки: добавить недостающий `DiscordWizardInteractionModule` с обработчиками кнопок, StringSelectMenu и модалов. Без него wizard декоративен — verifier уже сделал FAIL.\n\n## Контекст: что УЖЕ есть (не трогай, не переписывай)\n\nПроверь, что у тебя на диске в `src/GmRelay.DiscordBot/Features/Sessions/Wizard/` (commit b81d865 на `feat/issue-112-wizard-refactor`):\n- `DiscordWizardContextStore.cs` — кэш контекста (guildId/channelId/messageId)\n- `DiscordWizardMessenger.cs` — реализация `IWizardMessenger` (edit/send/answer/getClubs)\n- `DiscordWizardStep.cs` — рендер кнопок/select/modal customId\n- `DiscordWizardSubmitter.cs` — финализация (BuildCommand → Shared.CreateSessionHandler → edit «✅ Создано»)\n- `DiscordWizardCommand.cs` — slash `/newsession-wizard`\n- `DiscordPermissionLookup.cs` — helper для owner/co-GM проверки\n\n## Что нужно добавить\n\n### 1. `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs`\n\nСоздай `DiscordWizardInteractionModule : ComponentInteractionModule<...>` (или split на 2 модуля, если NetCord требует). Реализуй handlers для:\n\n**Кнопки** (`[ComponentInteraction(\"wizard:btn:...\")]`):\n- `wizard:btn:choice:<step>:<value>` — choice-кнопки (Type, System, Duration, Capacity waitlist, Visibility, PickClub, Publish, PoolAddSlots, PoolSlotCapacity)\n- `wizard:btn:cancel` — отмена (delete draft + edit msg на «❌ Мастер отменён» + remove context)\n- `wizard:btn:back` — назад (draft.Step = previous, persist, re-render)\n- `wizard:btn:create` — финализация (вызвать `DiscordWizardSubmitter.SubmitAsync`)\n- `wizard:btn:resume:continue` — ре-рендер текущего шага draft'а\n- `wizard:btn:resume:restart` — удалить draft и запустить новый\n\n**StringSelectMenu** (`[ComponentInteraction(\"wizard:select:...\")]`):\n- `wizard:select:<step>` — Visibility / PickClub / PoolSystemDuration\n\n**Modal** (`[ModalInteraction(\"wizard:modal:...\")]`):\n- `wizard:modal:<step>` — Title / Description / Cover / System free-text / Duration free-text / DateTime / Capacity / PoolSlotDateTime / PoolSlotCapacity / PoolSystemDuration free-text\n\nКаждый handler:\n1. Достаёт `draft_id` из customId (формат: `wizard:btn:choice:step:value:<draftId>` — посмотри `DiscordWizardStep.ButtonCustomId` для фактического формата и подгони).\n2. Загружает draft из `IWizardDraftRepository.GetByIdAsync(draftId, ct)` — если нет, ответь ephemeral «Мастер не найден».\n3. Проверяет `OwnerId == Context.User.Id.ToString()` (иначе чужая кнопка → ignore + ephemeral).\n4. Формирует `WizardInteraction` (OwnerId, Text/CallbackPayload, InteractionId).\n5. Вызывает `GameCreationWizard.HandleInteractionAsync(draft, interaction, ct)`.\n6. Если рендер вернул `openModal != null` — ответь `InteractionCallback.Modal(modalProperties)` где `modalProperties = DiscordWizardStep.BuildModal(openModal, draft)`.\n7. Иначе — пере-рендери edit и ответь `InteractionCallback.DeferredModifyMessage` (или сразу edit через messenger).\n\n**Modal handler** отличается: после `HandleInteractionAsync` нужно отредактировать сообщение на новый шаг (через `DiscordWizardMessenger.EditDraftMessageAsync`).\n\n### 2. Подключи `DiscordWizardSubmitter`\n\nВ `DiscordWizardInteractionModule`:\n- Инжектируй `DiscordWizardSubmitter` через конструктор.\n- На нажатие `wizard:btn:create`: получи draft, вызови `_submitter.SubmitAsync(draft, ct)`, обработай результат (edit msg на «✅ Создано: N сессий» или retry/cancel buttons).\n- Удали из контекст-стора после успеха.\n\n### 3. Smoke-тест (минимально, 2-3 кейса)\n\n`tests/GmRelay.Bot.Tests/Discord/DiscordWizardInteractionModuleSourceTests.cs`:\n- Проверь, что класс существует и наследует `ComponentInteractionModule`.\n- Проверь, что есть методы с атрибутами `[ComponentInteraction(\"wizard:btn:choice:...\")]` и `[ModalInteraction(\"wizard:modal:...\")]` через reflection.\n- Проверь, что customId форматы совпадают с теми, что генерит `DiscordWizardStep`.\n\nЭто не настоящие runtime-тесты, а source-level проверки. Достаточно чтобы `dotnet test` показал зелёный smoke test на эту категорию.\n\n### 4. Обнови `deliverable.md` в корне репо\n\nЗамени содержимое на summary по Discord-адаптеру:\n- Список файлов (6 старых + новый `DiscordWizardInteractionModule.cs`)\n- Какие customId форматы, какие handlers\n- «Открытые вопросы»: deferred features (например, если не успел pool-flow или не все шаги)\n- Ссылка на коммит\n\n### 5. Build + test + commit + push\n\n```\ndotnet build GM-Relay.slnx\ndotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --no-build\ndotnet format --verify-no-changes\ngit add -A\ngit commit -m \"feat(discord): wire interaction handlers for wizard (issue #112)\"\ngit push origin feat/issue-112-wizard-refactor\n```\n\n## Acceptance (минимум)\n- `DiscordWizardInteractionModule.cs` существует, реализует handlers для buttons + select + modal\n- `DiscordWizardSubmitter` вызывается из `wizard:btn:create` handler'а\n- Build зелёный, все wizard-тесты + 2-3 новых smoke-теста зелёные\n- `dotnet format` без замечаний\n- deliverable.md обновлён\n- Коммит запушен\n\n## Не делай\n- Не переписывай существующие 6 файлов (если нет compile error).\n- Не пиши unit-тесты handlers с моками NetCord (используй source-level reflection как указано выше).\n- Не открывай PR (это owner work).\n- Не добавляй новые пакеты.\n- Не улучшай UX — только рабочий handler chain.\n\n## Если не хватает времени\nПриоритет (по убыванию, 30 мин cap):\n1. Только `DiscordWizardInteractionModule.cs` с handlers (≥ buttons + create + cancel) — это MUST.\n2. Wire submitter.\n3. Smoke test.\n4. Update deliverable.\n\nЕсли не успеваешь 1+2+3 — коммить то, что есть, и помечай в deliverable «открытые вопросы». Build green лучше, чем идеально завершённый код.\n\nЧитай `D:\\Projects\\Game\\CLAUDE.md` для команд.",
|
||
"assigned_to": "coder",
|
||
"verified_by": "verifier",
|
||
"verify_prompt": "Adversarial verification Discord-визарда (issue #112), retry attempt.\nНЕ перечитывай описание исполнителя — перезапускай команды и атакуй поведение.\n\n## Шаги\n\n1. `dotnet build D:\\Projects\\Game\\GM-Relay.slnx` — успех.\n2. `dotnet test D:\\Projects\\Game\\tests\\GmRelay.Bot.Tests\\GmRelay.Bot.Tests.csproj --verbosity normal` — все wizard-тесты + новые smoke-тесты на interaction module зелёные.\n3. `dotnet format --verify-no-changes --verbosity diagnostic` — без замечаний.\n4. `dotnet list package --vulnerable --include-transitive` в src/GmRelay.Bot и src/GmRelay.DiscordBot — нет HIGH/CRITICAL.\n5. `git grep \"Telegram.Bot\" D:\\Projects\\Game\\src\\GmRelay.DiscordBot/` — пусто.\n6. `git grep \"NetCord\" D:\\Projects\\Game\\src\\GmRelay.Shared/` — пусто (только doc-комменты допустимы).\n7. `git grep \"Reflection\" D:\\Projects\\Game\\src\\GmRelay.DiscordBot\\Features\\Sessions\\Wizard\\` — пусто.\n8. `DiscordWizardInteractionModule` существует в `src/GmRelay.DiscordBot/Features/Sessions/Wizard/`. Содержит handlers для `wizard:btn:*`, `wizard:select:*`, `wizard:modal:*` (через `[ComponentInteraction]` / `[ModalInteraction]`).\n9. `DiscordWizardSubmitter` вызывается из `DiscordWizardInteractionModule` (не dead code — есть явный `_submitter.SubmitAsync(...)` вызов в create-button handler).\n10. `deliverable.md` в корне репо обновлён — описывает Discord-адаптер, не Task 1.\n11. `DiscordWizardCommand` (slash `/newsession-wizard`) и `DiscordNewSessionCommand` (text `/newsession`) оба компилируются.\n12. `dotnet grep` — `GameCreationWizard` существует только в Shared.\n\n## Adversarial\n- Прочти `DiscordWizardInteractionModule.cs` целиком. Ищи: NRE на `Context.User as GuildInteractionUser`, отсутствие null-проверок, customId overflow, потеря draft_id при парсинге customId.\n- Проверь, что `wizard:btn:create` handler действительно вызывает `DiscordWizardSubmitter` (а не просто `AnswerCallback`).\n- Проверь, что `wizard:btn:cancel` handler удаляет draft И чистит `DiscordWizardContextStore`.\n- Проверь, что modal handler корректно парсит `ModalInteractionData.Components` и достаёт `TextInput` value.\n\n## Критерии FAIL\n- `DiscordWizardInteractionModule` всё ещё отсутствует или содержит только stub/empty handlers.\n- `DiscordWizardSubmitter` остаётся dead code (не вызывается нигде).\n- Любой тест красный.\n- `dotnet build` падает.\n- `dotnet format` ругается.\n- Есть vulnerable HIGH/CRITICAL.\n- Telegram.Bot в DiscordBot или NetCord в Shared (кроме doc).\n- Reflection в AOT-коде.\n- `GameCreationWizard` дублируется.\n- `/newsession` text-only сломан.\n- deliverable.md в репо не обновлён (по-прежнему описывает Task 1).\n- Коммит не запушен.\n- Серьёзные баги: NRE без null-проверки, modal handler теряет значение TextInput, create button не вызывает submitter.\n\nНа FAIL укажи конкретный файл/строку/команду и обоснование.",
|
||
"timeout_ms": 1800000
|
||
}
|
||
],
|
||
"plan_complete": false,
|
||
"message_to_user": "Возвращаю Discord-адаптер на доработку: главная проблема — нет interaction module (handlers кнопок/select/modal), без них wizard декоративный. Подключить submitter, добавить smoke-тесты, обновить deliverable."
|
||
}
|