依存: #530(Cron + subgraph ポーリング + 通知チャンネル設定の基盤)。#530 完了後に着手する。
背景
クエストの完了報告(submitCompletion)が出されるとクエストは PendingReview になるが、現状これに気づく手段は以下しかない。
- Discord
/quest submit の結果は実行者本人にしか見えない ephemeral followup
- フロントエンドのクエスト詳細(
/{treeId}/quest/{questId})を能動的に見に行く
結果、申請が放置されエスクローされたロールシェアが宙に浮く。#530 で Thanks 送付の通知基盤(Cron + subgraph ポーリング + 通知チャンネル)ができるので、これに乗せて申請も通知したい。
承認できるのは誰か(重要)
IHatsQuestModule.approve の仕様上、承認できるのは 「クエスト作成者の単独承認」または「ワークスペースメンバー 2 名の承認」 で、どちらかが揃うと Completed になりエスクローが申請者へ渡る。専用の管理者ロールは存在しない。通知文面はこの前提に合わせる。
ゴール
#530 と同じ Cron の中で、前回チェック以降に新規発生したクエスト申請を検出し、#530 で設定済みの通知チャンネルへ投稿してレビューを促す。
ポーリング設計
SubmissionAttempt には blockNumber も workspaceId も無く(submittedAt = ブロックタイムスタンプのみ)、Quest.blockNumber は作成時のまま更新されない。よって #530 のブロックカーソルは流用できない。既存スキーマのまま Quest を引く方式にする(サブグラフ変更なし)。
quests(
where: { workspace: $treeId, status: PendingReview, submittedAt_gte: $cursor }
orderBy: submittedAt
orderDirection: asc
first: $n
) { questId submitter amount hatId wearer creator submittedAt attemptCount metadata { title } }
カーソル
自然に成り立つ性質(追加実装不要)
- ポーリング間隔内に申請 → 取り下げ / 却下された場合、
status は Open に戻り submittedAt は null になる(questMapping.ts)ため、クエリに引っかからず通知されない。レビュー不要なものは自動的に落ちる
実装メモ
メッセージ案
📝 クエストに完了報告が届きました
**{questTitle}** — {submitterLabel}
シェア {amount}
作成者の承認、またはメンバー2名の承認で完了します
{TOBAN_FRONTEND_URL}/{treeId}/quest/{questId}
変更対象(想定)
受け入れ条件
スコープ外
関連
ブランチ
feat/discord-bot-notify-quest-submission(main から)
背景
クエストの完了報告(
submitCompletion)が出されるとクエストはPendingReviewになるが、現状これに気づく手段は以下しかない。/quest submitの結果は実行者本人にしか見えない ephemeral followup/{treeId}/quest/{questId})を能動的に見に行く結果、申請が放置されエスクローされたロールシェアが宙に浮く。#530 で Thanks 送付の通知基盤(Cron + subgraph ポーリング + 通知チャンネル)ができるので、これに乗せて申請も通知したい。
承認できるのは誰か(重要)
IHatsQuestModule.approveの仕様上、承認できるのは 「クエスト作成者の単独承認」または「ワークスペースメンバー 2 名の承認」 で、どちらかが揃うとCompletedになりエスクローが申請者へ渡る。専用の管理者ロールは存在しない。通知文面はこの前提に合わせる。ゴール
#530 と同じ Cron の中で、前回チェック以降に新規発生したクエスト申請を検出し、#530 で設定済みの通知チャンネルへ投稿してレビューを促す。
discord_guild_config.notify_channel_idを共用(設定 UI の追加なし)resolveDisplayNameに従う(allowed_mentions: { parse: [] }により表示はメンション・ping は飛ばさない)ポーリング設計
SubmissionAttemptにはblockNumberもworkspaceIdも無く(submittedAt= ブロックタイムスタンプのみ)、Quest.blockNumberは作成時のまま更新されない。よって #530 のブロックカーソルは流用できない。既存スキーマのままQuestを引く方式にする(サブグラフ変更なし)。カーソル
last_submitted_at(タイムスタンプ)+ その境界タイムスタンプで通知済みのキー集合 を per-workspace で保持。submittedAt_gteで境界を再走査し、キー集合で de-dup する(feat(discord-bot): Cron で subgraph 監視し Thanks 送付を指定チャンネルへ通知(複数workspace対応) #530 のブロック境界と同じ考え方)questId単体ではなくquestId + attemptCount。 クエストは「申請 → 却下 → 再申請」がありえて、再申請は改めてレビューが必要なので別イベントとして通知する必要がある(SubmissionAttemptの attemptIndex に対応)自然に成り立つ性質(追加実装不要)
statusはOpenに戻りsubmittedAtはnullになる(questMapping.ts)ため、クエリに引っかからず通知されない。レビュー不要なものは自動的に落ちる実装メモ
scheduled()ハンドラの同一 tick 内で Thanks 通知と並走させる(Promise.allSettled。片方の失敗が他方を止めない)thanks_notify_cursor固定ではなくnotify_cursor(tree_id, kind, cursor, boundary_ids, updated_at)(kind=thanks/quest)の形にしておくと、この Issue でテーブル追加・API 追加が不要になる。feat(discord-bot): Cron で subgraph 監視し Thanks 送付を指定チャンネルへ通知(複数workspace対応) #530 の実装時に検討したいpostChannelMessage、resolveDisplayName、GET /api/guild-configs、カーソル read/write APIメッセージ案
変更対象(想定)
pkgs/extensions/discord-bot/src/notifier/(feat(discord-bot): Cron で subgraph 監視し Thanks 送付を指定チャンネルへ通知(複数workspace対応) #530 で新設される想定のディレクトリ): クエスト申請ポーリング + 整形pkgs/extensions/discord-bot/src/index.ts:scheduled()へ追加pkgs/extensions/discord-bot/src/chain.ts:PendingReviewクエスト取得クエリpkgs/extensions/discord-bot/src/identity.ts: カーソル(kind=quest)クライアントpkgs/extensions/identity: カーソルの kind 対応(feat(discord-bot): Cron で subgraph 監視し Thanks 送付を指定チャンネルへ通知(複数workspace対応) #530 で汎用化されていれば不要)pkgs/extensions/discord-bot/test/: 単体テスト受け入れ条件
PendingReviewになったクエストのみが通知される(取りこぼし・重複なし)questId + attemptCountで区別される)スコープ外
SubmissionAttemptへのblockNumber追加など)関連
ブランチ
feat/discord-bot-notify-quest-submission(mainから)