投稿者が投稿を削除しても、キューに登録された投稿の本文がレビューキューに残り、削除する方法がない

概要

投稿が承認キューを通過すると、ReviewableQueuedPost.payload["raw"] には送信された本文の完全なコピーが保持されます。投稿が承認された後、著者によって削除されると、公開されている投稿は「(著者により削除されました)」というスタブに置き換えられますが、レビューアブル(reviewable)は投稿レコードが存在する限り(著者削除後には無期限に)元の本文を保持し続け、/review でスタッフに対して提供し続けます。

これに対するスクラブ(データ消去)パスは存在しません。Reviewable.scrubbable_types は [ReviewableUser] であるため、#36556(「モデレーターはユーザーの個人データを削除するために ‘Scrub’ アクションを使用できるようになりました」)で追加された管理者スクラブアクションは、キューに登録された投稿をカバーしていません。ペイロードが削除されるのは、誰かが投稿レコードを完全に破棄した場合のみです。これは著者の削除とは異なり、UI ではトピックの最初の投稿に対しては許可されていません。

v2026.7.1 で再現しました。関連するコードは、2026年9月25日時点の main において変更されていません。

バージョン

Discourse v2026.7.1(セルフホスト、ローカル認可済みテストインスタンス)。コアのみ — プラグインは関与していません。

再現手順

  1. 管理者として、一般ユーザーの投稿に承認を必要とさせる設定を行います。例えば、approve post count を 5 に設定し、approve unless allowed groups から trust_level_0 を削除します。

  2. その一般ユーザーとして、本文に一意のマーカーを含むトピックを作成します。レスポンスは {"action": "enqueued"} として返されます。

  3. スタッフとして承認します:PUT /review/<reviewable_id>/perform/approve_post?version=0。投稿が作成されます。

  4. 著者として、通常のユーザー向けアクションを通じて削除します — この投稿が最初の投稿であるトピックの場合、DELETE /t/<topic_id>.json を使用します。

  5. スタッフとして GET /review.json?status=all をリクエストします。

期待される動作

著者がコンテンツを削除した時点で、同じテキストのモデレーション用コピーもコンテンツのライフサイクルに従うべきです。削除されるべきか、スクラブされるべきか、少なくとも拒否された ReviewableUser のように管理者によってスクラブ可能であるべきです。

実際の動作

ステップ 5 で依然として元の完全な本文が返されます。再現実行で測定した結果は以下の通りです:

観察項目 結果
著者削除後の posts レコード user_deleted = t, raw = '(topic deleted by author)'
著者削除後の reviewables.payload {"raw":"Queued body DRLGQUEUEDfb08ab5b648f. padding …"} — 変更なし
スタッフによる GET /review.json?status=all 元の本文を含む
Reviewable.scrubbable_types ["ReviewableUser"]
管理者による PUT /review/<id>/scrub.json HTTP 404(スクラブ可能なタイプではない)
投稿レコードが完全に破棄された後 レビューアブルレコードは消去される(dependent: :destroy)

最後の行がこれをクリアする唯一のパスであり、著者の削除からは到達できません:著者の削除は投稿を user_deleted にマークし、delete_removed_posts_after 時間後にゴミ箱に移動します。ゴミ箱に移動した投稿レコードは依然として存在するため、ペイロードも依然として存在します。

原因

  • app/models/reviewable_queued_post.rb が payload['raw'] を構築・読み取りますが、保持目的や保持期間に関する記述はありません。

  • lib/post_destroyer.rb:562 の resolve_reviewables_for_author_deletion は Reviewable.where(target: @post, status: pending) のみに関与します。承認済みのキュー投稿レビューアブルは、ステート遷移もスクラブもされません。

  • app/models/post.rb:71 の has_many :reviewables, as: :target, dependent: :destroy — これはレコードの破棄時に発火しますが、著者削除が生成するソフトデリート時には発火しません。

  • app/models/reviewable.rb:81 の scrubbable_types は [ReviewableUser] を返します。app/controllers/reviewables_controller.rb:191 はさらに status: rejected を要求します。

  • レビューアブル用のスケジュールされたクリーンアップはありません。app/jobs/scheduled/ にはドラフト、メールトークン、エクスポート、アップロード、API キーなどに対する clean_up_* / purge_* ジョブが含まれていますが、レビューアブル用はありません。

影響

これは公開漏洩ではありません — レビューキューはスタッフ専用であり、そう主張するつもりもありません。これはデータ保持のギャップです:ユーザーがフォーラムから削除したテキストがモデレーションストアに無期限に留まり、レビューキュー UI で表示され、投稿レコードを完全に破棄する以外の管理者アクションでは削除できません。

これは、#36556 が拒否されたユーザーのスクラブを追加したのと同じ理由で重要です:レビューアブルのペイロードには個人データが含まれる可能性があり、削除リクエストを受けたサイトには、キュー投稿のペイロードをクリアするサポートされた方法がありません。

修正提案

以下のいずれかを実施すれば解決しますが、両方実施する方が望ましいです:

  1. resolve_reviewables_for_author_deletion(およびゴミ箱パス)を拡張し、ターゲット投稿が著者削除またはゴミ箱に移動されたレビューアブルの payload['raw'] をスクラブするようにします。

  2. ReviewableQueuedPost を Reviewable.scrubbable_types に追加し、ターゲット投稿がもはやコンテンツを保持していない場合に既存の管理者スクラブアクションが適用されるようにします。これにより、修正前に作成されたペイロードに対するサポートされた救済手段をオペレーターに提供できます。

リグレッション仕様では、著者が承認済みのキュー投稿を削除した後、レビューアブルのペイロードに送信された本文が含まれていないことをアサートする必要があります。

「いいね!」 2