يبقى النص الكامل لمنشور في قائمة الانتظار ضمن قائمة المراجعة حتى بعد حذفه من قِبل كاتبه، دون وجود طريقة لحذفه نهائيًا

ملخص

عندما يمر منشور عبر قائمة الانتظار للمراجعة، يحتفظ ReviewableQueuedPost.payload["raw"] بنسخة كاملة من النص المُقدَّم. بعد اعتماد المنشور ثم حذفه من قِبل كاتبه، يُستبدل المنشور العام بعبارة «(تم حذف المنشور من قِبل كاتبه)»، لكن عنصر المراجعة يحتفظ بالنص الأصلي طالما أن صف المنشور موجود — وهو ما يستمر إلى ما لا نهاية بعد الحذف من قِبل الكاتب — ويواصل تقديمه للموظفين في /review.

لا توجد آلية لتنظيفه: فقيمة Reviewable.scrubbable_types هي [ReviewableUser]، لذا فإن إجراء التنظيف (Scrub) الذي أُضيف للموظفين في #36556 («يمكن للمشرفين الآن استخدام إجراء ‘Scrub’ لإزالة البيانات الشخصية للمستخدم») لا يشمل المنشورات في قائمة الانتظار. يُزال الحمولة فقط إذا قام أحدهم بتدمير سجل المنشور بشكل دائم، وهو ما لا يفعله حذف الكاتب، ولا تسمح به الواجهة لرسالة الموضوع الأولى.

تم التكرار على v2026.7.1. الكود ذي الصلة لم يتغير على فرع main حتى تاريخ 2026-09-25.

الإصدار

Discourse v2026.7.1 (مستضاف ذاتيًا، نسخة اختبار محلية معتمدة). النواة فقط — لا توجد إضافات (Plugins) متورطة.

خطوات التكرار

  1. بصفتك مشرفًا، اجعل منشورات المستخدم العادي تتطلب موافقة، على سبيل المثال اضبط approve post count على 5 وأزل trust_level_0 من approve unless allowed groups.

  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/ على مهام clean_up_* / purge_* للمسودات، ورموز البريد الإلكتروني، والتصدير، والمرفوعات، ومفاتيح API وغيرها، لكن لا شيء لعناصر المراجعة.

الأثر

هذا ليس إفشاءً عامًا — قائمة المراجعة مخصصة للموظفين فقط، ولا أدّعي خلاف ذلك. إنه فجوة في الاحتفاظ بالبيانات: النص الذي أزاله المستخدم من المنتدى يبقى في مخزن المراجعة إلى ما لا نهاية، ويُعرض في واجهة قائمة المراجعة، ولا يمكن لأي إجراء من المشرف إزالته إلا بتدمير سجل المنشور بشكل دائم.

يهم الأمر بنفس السبب الذي أضاف من أجله #36556 التنظيف للمستخدمين المرفوضين: حمولات عناصر المراجعة يمكن أن تحمل بيانات شخصية، ولا توجد طريقة مدعومة لموقع يستقبل طلب حذف (Erasure Request) لمسح حمولة منشور في قائمة الانتظار.

الإصلاح المقترح

أي من الخيارات التالية يغلق المشكلة؛ وفعل كلاهما أفضل:

  1. توسيع resolve_reviewables_for_author_deletion (ومسار سلة المهملات) لتنظيف payload['raw'] على عناصر المراجعة التي تم حذف منشورها المستهدف من قِبل الكاتب أو إرساله إلى سلة المهملات.

  2. إضافة ReviewableQueuedPost إلى Reviewable.scrubbable_types والسماح لإجراء التنظيف الإداري الحالي بالتطبيق عندما لم يعد المنشور المستهدف يحمل المحتوى، بحيث يمتلك المشغّلون إجراءً مدعومًا للتعامل مع الحمولات التي أُنشئت قبل الإصلاح.

ستؤكد مواصفة الانحدار (Regression spec) أنه بعد أن يحذف الكاتب منشورًا معتمدًا في قائمة الانتظار، لم تعد حمولة عنصر المراجعة تحتوي على النص المُقدَّم.

إعجابَين (2)