# خطة إصلاح أمن API ومحدد الطلبات

تاريخ الخطة: 16 أكتوبر 2026. المنفّذ المستهدف: Sol.

**فصل الماستر عن الإرسال بطلب المستخدم — 19 أكتوبر 2026:** أصبح `OTP_DEFAULT_CODE` مخصصًا للتحقق من تسجيل دخول العملاء فقط؛ التوليد والإعادة يرسلان كودًا عشوائيًا لا يساوي الماستر المفعّل. لا يظهر الماستر في API بأي بيئة، بما فيها local/testing. يظل التفعيل الصريح خارج local/testing وقيود التحدي ونطاق المتجر والتينانت كما هي، ولا يُقبل الماستر لحذف الحساب. نتائج 18 أكتوبر أدناه تاريخية وتسبق هذا الفصل.

**تغيير لاحق بطلب المستخدم — 18 أكتوبر 2026:** شرط المرحلة 6 أدناه أصبح هو الوضع الافتراضي، مع استثناء صريح لتشغيل كود تسجيل الدخول الثابت خارج local/testing عبر `OTP_ALLOW_DEFAULT_IN_PRODUCTION=true` مع `OTP_DEFAULT_CODE`. عند هذا التفعيل يعرض فحص البيئة WARN دون طباعة الكود؛ يظل الكود مخفيًا من API الإنتاج، وتستمر حدود المحاولات والانتهاء ومنع إعادة الاستخدام وربط التحدي بالمتجر والتينانت. أكواد حذف الحساب تظل عشوائية. معرفة الكود الثابت تسمح بالدخول لحساب عميل.

**تحقق الاستثناء محليًا:** نجحت مجموعة `tests/Feature/Api/V1/Customer` مع `tests/Feature/Platform/StagingReadinessTest.php` و`tests/Feature/Platform/RateLimitSecurityTest.php`: **148 اختبارًا، 952 assertion، صفر فشل أو أخطاء أو حالات risky، exit code 0**. تشمل اختبار رفض الافتراضي عند غياب التفعيل، واختبار التوليد والتحقق عبر email/WhatsApp تحت محاكاة production مع التفعيل، وإخفاء الكود والتحذير، ورفض التحدي المنتهي والمقفل والمستهلك والملغي أو التابع لمتجر آخر. نجح فحص Pint للملفات الخمسة المتأثرة و`git diff --check`. الاختبارات على SQLite في الذاكرة مع عزل الإرسال وتبديل التينانت؛ لم تُعدّل `.env` الفعلية أو بيانات التشغيل ولم يُنشر التغيير. نتائج المراجعة التاريخية أدناه محفوظة كما كانت.

نفّذ المراحل التالية بالترتيب، وحدّث سجل التقدم في هذا الملف بعد كل مرحلة. المطلوب إصلاح السلوك الأمني واختباره، وليس مجرد إضافة middleware أو تغيير أرقام الحدود. هذه خطة تنفيذ؛ جميع مراحل الإصلاح ما زالت غير منفذة عند إنشائها.

## قواعد التنفيذ

- اقرأ `AGENTS.md` في جذر المشروع قبل البدء. جميع المسارات أدناه نسبية إلى جذر المشروع، إلا مسارات الاختبارات في أوامر PHPUnit فهي نسبية إلى `api/`.
- افحص `git status` و`git diff` أولًا. توجد تغييرات كثيرة للمستخدم؛ لا تستبدلها أو تعيد تنسيقها أو تنسبها لهذا العمل. لا commit أو push.
- النطاق الأساسي `api/`. عدّل `admin/` فقط إذا احتاج عقد API أو عرض الصلاحيات تعديلًا مباشرًا. `Main/` و`front/` خارج نطاق التعديل؛ افحص استهلاكهما للعقود قراءة فقط عند الحاجة، وسجّل أي تعديل واجهة مطلوب قبل النشر.
- لا تعدّل `.env` أو كلمات مرور الديمو أو بيانات المتاجر. إعدادات جديدة توثّق في `api/.env.example` بقيم غير سرية.
- لا تشغّل migrations أو seeders على قواعد التشغيل. أي migration ضرورية تُراجع كملف فقط؛ تنفيذها على تينانت قائم يحتاج الإذن المحدد في `AGENTS.md`. لا تفترض وجود `roles.is_system`.
- لا تفعّل بوابة دفع على متجر الكويت المحلي `24`، ولا تتصل ببوابة حقيقية أو ترسل بريدًا أو WhatsApp أثناء الاختبارات. استخدم fixtures وfakes معزولة.
- لا تبدأ بتنفيذ اختبارات ضغط على التشغيل المحلي أو الإنتاج. إثبات التجاوزات داخل PHPUnit، ثم تحقق محدود من التدفقات العادية.
- لكل عيب: أضف اختبارًا يتوقع السلوك الآمن، تأكد أنه يفشل بسبب العيب الحالي، ثم أصلح الكود حتى ينجح. اختبارات المراجعة القديمة كانت تثبت السلوك غير الآمن؛ لا تنسخ توقعاتها كاختبارات نجاح للإصلاح.
- لا تتوقف لطلب موافقة بين المراحل الروتينية. إذا احتجت وصولًا غير متاح أو تغييرًا خارج النطاق، سجّل العائق وأكمل العمل المستقل. لا تعتبر مرحلة مكتملة مع اختبار قبول ناقص.

## الأدلة وحدودها

المراجعة السابقة شغّلت 40 اختبارًا قائمًا و10 اختبارات إثبات معزولة. الاختبارات العشرة أثبتت تجاوزات rate limiting، وثلاث مشاكل صلاحيات موظفين، وعودة الصلاحيات عند حفظ قائمة فارغة، وبقاء التوكنات بعد تغيير كلمة المرور، وعمل مولّد OTP ثابت تحت بيئة production.

اختبارات الإثبات كانت في `/tmp/AuthTenantSecurityAuditTest.php` و`/tmp/RateLimitSecurityAuditTest.php`. قد لا تبقى هذه الملفات؛ السيناريوهات في هذه الخطة كافية لإعادة كتابتها داخل المشروع. اختبار OTP السابق اختبر المولّد، وليس تدفق التحقق الكامل. فحص رفع الملفات أثبت قبول HTML غير ضار وإعادته كـ `text/html`، ولم يثبت تنفيذ PHP على السيرفر. مشكلة ربط webhook بعملية دفع أخرى مستنتجة من الكود وتحتاج اختبارًا معزولًا في المرحلة 3 قبل تعديلها.

نتائج الصلاحيات بين المتاجر تخص متاجر داخل التينانت نفسه؛ لا تُعرض باعتبارها اختراقًا مثبتًا بين قواعد تينانتات مستقلة. إعدادات الإنتاج والبروكسي وHTTPS لم تُفحص.

## تشغيل الاختبارات

من داخل `api/` استخدم الدالة التالية في جلسة الشِل، ثم نفّذ مسارات كل مرحلة بها:

```bash
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:$PATH"
security_test() {
  env -u APP_ENV -u APP_DEBUG -u DB_CONNECTION -u DB_DATABASE -u DB_URL \
    -u DB_CENTRAL_DATABASE -u DB_CENTRAL_CONNECTION -u CACHE_STORE \
    -u QUEUE_CONNECTION -u SESSION_DRIVER \
    ./vendor/bin/phpunit --do-not-cache-result "$@"
}
```

استخدم `phpunit.xml` الحالي، ولا تستخدم `--no-configuration`. اختبارات الأمن الجديدة يجب أن تؤكد أن قاعدة البيانات SQLite `:memory:` قبل إنشاء fixtures. أبقِ التطبيق في بيئة testing أثناء تجهيز قاعدة الاختبار؛ لا تغيّر البيئة لمحاكاة production إلا بعد تجهيز العزل، ثم استعدها في `finally`.

## المرحلة 0 تثبيت نقطة البداية

**الملفات:** `AGENTS.md`، `api/phpunit.xml`، `api/tests/TestCase.php`، `api/tests/Concerns/`، `api/bootstrap/app.php`، ملفات المسارات الحالية.

1. سجّل التغييرات الموجودة والاختبارات التي ستتأثر، وأعد قراءة الدوال المعنية بدل الاعتماد على أرقام أسطر المراجعة.
2. احصر المسارات الحديثة والقديمة للمستخدمين، تسجيل الدخول، البحث، التواصل، المحفظة، وwebhooks مع ترتيب middleware الفعلي.
3. شغّل الاختبارات الأساسية التالية وسجّل أي فشل سابق للإصلاح، دون تعديله لإخفائه:

```bash
security_test tests/Feature/Platform/ProductionHardeningTest.php
security_test tests/Feature/Api/V1/UnauthenticatedAccessTest.php
security_test tests/Feature/Api/V1/Integration/IntegrationSecurityTest.php
security_test tests/Feature/Api/V1/Admin/StoreUsers/CustomRolePermissionTest.php
```

**معيار الإكمال:** تأكد العزل، وحددت نقطة البداية والتغييرات السابقة للمستخدم، ولا توجد نتيجة اختبار منسوبة إلى قاعدة تشغيل فعلية.

## المرحلة 1 حماية هويات الموظفين ومنع ترقية النفس

**الملفات الأساسية:**

- `api/app/Services/Admin/StoreUsers/AdminStoreUserService.php`
- `api/app/Services/Admin/StoreUsers/AdminStoreUserAccess.php`
- `api/app/Services/Admin/Authorization/StorePermissionGate.php`
- `api/app/Http/Requests/Api/V1/Admin/StoreUsers/StoreStoreUserRequest.php`
- `api/app/Http/Requests/Api/V1/Admin/StoreUsers/UpdateStoreUserRequest.php`
- `api/app/Policies/StoreUserPolicy.php`

**التعديل:**

1. افصل إنشاء حساب جديد عن دعوة حساب موجود. دعوة الموجود تضيف تعيين متجر فقط؛ لا تغيّر كلمة المرور أو حالة الحساب العامة أو دوره العالمي أو بيانات هويته ضمنيًا. ارفض حقول تعديل بيانات الدخول في دعوة المستخدم الموجود برسالة واضحة بدل تجاهلها بصمت.
2. راجع مسار update أيضًا: صلاحية إدارة موظفي متجر واحد لا تكفي لتغيير بيانات دخول هوية لها وصول إلى متاجر لا يديرها الفاعل. طبّق التحقق في الخدمة على كل مسار تعديل مماثل، مع استثناء موثق لصلاحية المنصة المناسبة والتغيير الذاتي الموثق.
3. طبّق حدود الصلاحيات التي يستطيع الفاعل منحها على الأدوار النظامية والمخصصة. لا يجوز منح دور تتجاوز صلاحياته الفعلية صلاحيات الفاعل في المتجر المستهدف.
4. تحقّق قبل أي كتابة أو مزامنة أدوار؛ الطلب المرفوض لا يترك تعديلًا جزئيًا. حافظ على منع تعيين Super Admin، وحماية آخر مدير، ومنع نزع الوصول الذاتي وفق قواعد المشروع.

**اختبارات القبول:**

- مدير A لا يقدر تغيير كلمة مرور مدير B عند دعوته بالـ ID أو البريد، ولا عبر update. تحقق من ثبات hash كلمة المرور وحالة الحساب والأدوار والتعيينات عند الرفض.
- دعوة المستخدم الموجود بدون تغيير هويته تنجح إذا كانت مأذونة، وإنشاء موظف جديد يظل يعمل.
- صاحب دور مخصص فيه `store_users.manage` فقط لا يستطيع ترقية نفسه أو مستخدم آخر إلى دور أوسع، سواء من create أو update أو مسار قديم.
- المدير المأذون يستطيع منح دور ضمن حدوده. Super Admin لا يُعيّن كموظف، وآخر مدير لا يفقد الإدارة.

**الاختبارات:** وسّع `api/tests/Feature/Api/V1/Admin/StoreUsers/AdminStoreUserApiTest.php` و`CustomRolePermissionTest.php`. يمكن إنشاء `StoreUserSecurityTest.php` بجوارهما إذا أصبحت السيناريوهات كثيرة.

**الواجهة عند الحاجة:** `admin/src/modules/store-users/pages/StoreUserFormPage.jsx` واختباره؛ أخفِ/امنع إدخال كلمة مرور عند دعوة مستخدم موجود، مع رسالة مترجمة في `ar.js` و`en.js`. رفض API هو الحماية الأساسية.

**معيار الإكمال:** جميع اختبارات الرفض والنجاح أعلاه تمر، ولا يستطيع مدير متجر تغيير هوية خارج نطاقه أو منح صلاحيات لا يملكها.

## المرحلة 2 عزل صلاحيات الأدوار حسب المتجر

**الملفات الأساسية:**

- `api/app/Services/Admin/Authorization/AuthorizesStorePermission.php`
- `api/app/Services/Admin/Authorization/StorePermissionGate.php`
- `api/app/Services/Admin/Authorization/SystemRolePermissionSync.php`
- `api/app/Services/Admin/AdminPermissionCatalog.php`
- `api/app/Services/Admin/StoreUsers/AdminRoleService.php`
- `api/app/Models/User.php` و`api/app/Models/StoreUser.php`

**التعديل:**

1. اجعل تعيين المتجر الحالي هو مصدر الدور الفعّال للأدوار النظامية والمخصصة. لا تسمح لدور عالمي مأخوذ من متجر A بتوسيع صلاحيات المستخدم في B.
2. حافظ على قاعدة المشروع: الدور النظامي غير المعدّل يُفحص باسم الدور الفعّال في المتجر، والمعدّل يستخدم صلاحياته المحفوظة. لا تحوّل الدور إلى مخصص ولا تغيّر `is_system` إلى false.
3. حفظ `permissions=[]` يعني نزع جميع الصلاحيات، وليس استعادة الكتالوج. وحّد النتيجة بين التنفيذ، bootstrap، عرض الصلاحيات، القائمة، وصلاحيات الشاشات.
4. راجع `SystemRolePermissionSync` واستدعاءاته حتى لا تعيد المزامنة صلاحيات نزعها المستخدم. لا تستخدم غياب صفوف الصلاحيات وحده للتمييز بين دور قديم غير مهيأ ودور أُفرغ عمدًا. اشرح قرار التوافق مع التينانتات القديمة واختبره؛ عند غموض الحالة لا تمنح صلاحيات ضمنيًا.
5. يجب أن يعمل الإصلاح بدون افتراض عمود `roles.is_system`. إذا احتاج التصميم معلومة دائمة إضافية، وثّقها وخطة توافقها؛ لا تنفّذ migration على قواعد قائمة تلقائيًا.

**اختبارات القبول:**

- مستخدم مدير في A وstaff في B يُرفض عند إدارة موظفي B، بينما يبقى وصوله المأذون في A صحيحًا. كرر مع دور مخصص ومع تعيين غير نشط.
- نزع كل صلاحيات Store Staff يحفظ القائمة الفارغة ويمنع الطلبات المحمية ولا يعيدها في bootstrap أو بعد إعادة تحميل التطبيق/تفريغ cache.
- المزامنة لا تمحو تخصيص الدور. الأدوار غير المعدلة تعرض صلاحيات الشاشات التابعة للكتالوج كما ينص `AGENTS.md`.
- صلاحية وحدة عامة وحدها لا تمنح شاشة لم تُمنح صراحة في دور معدل/مخصص.
- سلوك قديم بلا `roles.is_system` لا ينتج خطأ SQL، ولا يفتح وصولًا إضافيًا.

**الاختبارات:** `CustomRolePermissionTest.php`، `AdminStoreUserApiTest.php`، `api/tests/Unit/Services/Admin/AdminPermissionCatalogTest.php`، `api/tests/Feature/Api/V1/Admin/Orders/AdminOrderAuthorizationTest.php`، واختبارات navigation القائمة عند تغييره.

**معيار الإكمال:** صلاحيات API والقائمة والمسار متطابقة وتخص المتجر الحالي، والتخصيص الفارغ محفوظ فعليًا.

## المرحلة 3 ربط نتيجة الدفع بالعملية الصحيحة

**الملفات الأساسية:**

- `api/app/Services/Payment/PaymentWebhookService.php`
- `api/app/DTOs/Payment/WebhookResult.php`
- `api/app/Payments/Gateways/HesabeGateway.php` و`KpayGateway.php`
- `api/app/Http/Controllers/Api/V1/Webhooks/PaymentWebhookController.php`
- بقية `api/app/Payments/Gateways/` ومسارات callbacks عند اشتراكها في منطق تطبيق النتيجة.

**التعديل:**

1. ابدأ باختبار معزول لوجود اختلاف بين العملية المختارة من الطلب والعملية التي تحققت منها البوابة. راجع أثر ذلك في المسارات الحديثة والقديمة؛ لا تغيّر السلوك المالي بناء على افتراض غير مثبت.
2. اجعل مرجع العملية الموثق من البوابة هو مصدر الحقيقة. إذا تعارضت هوية العملية أو المتجر أو إعداد البوابة مع بيانات الطلب، ارفض المعالجة دون تحديث أي دفعة أو طلب أو محفظة.
3. تحقق من المبلغ بثلاث خانات عشرية ومن العملة عند تحويل الحالة إلى paid وفق عقد البوابة. لا تثق بقيمة مالية غير موثقة من الطلب، ولا تعتبر غياب حقول غير مطلوبة في عقد بوابة سليمة سببًا لكسر callbacks الشرعية.
4. حافظ على التحقق من التوقيع/التشفير، وعدم تكرار الحدث، وقفل العملية عند الحاجة لمنع السباق. يجب ألا يستطيع حدث مرفوض حجز مفتاح idempotency لحدث صحيح لاحق.
5. لا تكشف أسرار البوابة أو payload حساس في response أو log جديد. أبقِ شكل استجابة webhook المتوافق مع إعادة المحاولة الموثقة للبوابة.

**اختبارات القبول:** اختلاف عملية/متجر/بوابة/مبلغ/عملة لا يغيّر الحالة أو المخزون أو الرصيد؛ حدث صحيح ينجح؛ تكرار الحدث لا يكرر transaction أو cashback؛ توقيع/تشفير غير صحيح يرفض؛ مسار tenant لا يغيّر دفعات تينانت آخر. استخدم تشفيرًا بمفاتيح اختبار وHTTP fakes فقط.

**الاختبارات:** `api/tests/Feature/Api/V1/Payment/PaymentWebhookTest.php`، `PaymentGatewayHardeningTest.php`، `HostedPaymentGatewayTest.php`، `PaymentStoreIsolationTest.php`. أضف `PaymentWebhookBindingTest.php` بجوارها عند الحاجة.

**معيار الإكمال:** العملية التي جرى التحقق منها هي وحدها التي يمكن تحديثها، مع بقاء callbacks الشرعية وidempotency صحيحين.

## المرحلة 4 إصلاح محدد الطلبات وتغطية المداخل البديلة

**الملفات الأساسية:**

- `api/app/Providers/RouteServiceProvider.php`
- `api/config/ecommerce.php` و`api/config/cache.php`
- `api/bootstrap/app.php`
- `api/routes/api/v1/storefront.php` و`admin.php` و`admin-legacy.php` و`platform.php`
- `api/app/Http/Middleware/ResolveClient.php` وmiddleware حل المتجر، للمراجعة وضبط الترتيب عند الضرورة فقط.
- طلبات تسجيل الدخول وOTP، و`ProductController.php` داخل `Storefront/Catalog/`.
- `api/app/Services/Wallet/WalletGiftCodeService.php` و`api/app/Http/Controllers/Api/V1/Admin/Wallet/WalletGiftCodeController.php`.

**التعديل:**

1. ابنِ مفتاح الدخول من هوية العملية الفعلية: tenant المعتمد + نوع العملية + هوية الدخول بعد نفس التطبيع المستخدم في المصادقة. لا تستخدم `email` إذا كان المسار يصادق بـ `login`، ولا تستخدم `X-Store-Code` الخام إذا لم يكن مصدر المتجر في هذا المسار.
2. استخدم أكثر من حد عند الحاجة: حد مستقل لكل IP ضد توزيع المحاولات على حسابات مختلفة، وحد لمحاولات الحساب مع مراعاة عدم توفير قفل دائم يسهل إساءة استخدامه. لا تجعل دمج IP والحساب في مفتاح واحد بديلًا عن الحماية المستقلة.
3. التواصل يستخدم tenant والمتجر المحلولين فعليًا. الهيدر الزائد أو تبديل spelling لا يمنح عدادًا جديدًا لنفس الوجهة.
4. احمِ البحث من كل مداخله، بما فيها `products?q=` و`products?search=` والمسارات التي تحمل المتجر في الرابط أو الهيدر. لا تطبّق المحدد نفسه مرتين على طلب واحد.
5. أضف حدًا لاسترداد أكواد المحفظة حسب المستخدم وIP مع عزل tenant، وعدّ المحاولات الفاشلة. حافظ على صلاحية الأكواد القائمة؛ قوِّ إنشاء الأكواد الجديدة وتحقق من عدم تسريب وجود الكود من رسائل مختلفة غير لازمة.
6. أضف حدًا عامًا قابلًا للضبط للمسارات التي لا تغطيها محددات متخصصة، بعد حصر طلبات الصفحة العادية. افصل callbacks/webhooks ومراقبة الصحة بما يلائم تشغيلها؛ لا تجعل سقف التصفح يمنع إشعارات الدفع الصحيحة.
7. كل مفتاح به user ID أو store ID يحتاج namespace للتينانت إذا كان backend مشتركًا. راجع database/file/Redis بحسب الإعداد؛ لا تفرض Redis أو تبدّل backend الإنتاج دون معرفة طوبولوجيا التشغيل. لا تستخدم cache `array` في التشغيل الحقيقي.
8. حافظ على تشغيل `ResolveClient` قبل Sanctum وأي cache يعتمد قاعدة التينانت. المحدد داخل التطبيق بعد حل التينانت لا يحمي تكلفة حل التينانت نفسها؛ سجّل حماية هذه الطبقة ضمن إعدادات السيرفر في المرحلة 9.
9. أعد `429` مع `Retry-After` وترويسات limit المفيدة، وتأكد أن معالج exceptions وCORS لا يمحوانها. لا تسجل كلمات المرور أو OTP ضمن مفاتيح مكشوفة أو logs.

**القيم:** ابدأ بالمحددات المتخصصة الحالية: auth=10، search=60، contact=8، checkout/payment/reviews=20، webhook=120 في الدقيقة؛ أصلح الهوية والتغطية قبل تغييرها. حد استرداد مبدئي مقترح 5 محاولات/دقيقة لكل مستخدم، مع حد IP مستقل قابل للضبط. قيم IP والحد العام تُختار بعد عدّ طلبات التدفقات العادية وتوثيق سببها، خصوصًا مستخدمي NAT. هذه قيم بداية للتقييم وليست ادعاءً بملاءمتها للإنتاج.

**اختبارات القبول:**

- على endpoint حقيقي داخل PHPUnit، يصل العميل للحد ثم يحصل على 429 رغم تغيير هيدر غير مستخدم أو إضافة حقل email غير معتمد. غيّر IP مع تثبيت الحساب والعكس لاختبار الحدود المستقلة.
- التواصل إلى نفس متجر المسار يظل محظورًا رغم تغيير هيدر المتجر الزائد.
- البحث عبر المسار المخصص ورابط المنتجات لا يمنح حدًا جديدًا لنفس العملية، والتصفح العادي لا يتعطل بالخطأ.
- استرداد المحفظة يصل إلى 429؛ لا تحدث كتابة رصيد للطلبات المرفوضة؛ الكود الصحيح يظل يعمل بعد انتهاء النافذة.
- تينانتان لهما user/store IDs متطابقة لا يتشاركان حد المستخدم بالخطأ. اختبر backend مشتركًا معزولًا إلى جانب array لاكتشاف أخطاء namespace.
- اختبر انتهاء النافذة بـ time travel، ووجود Retry-After، والطلبات المتزامنة المحدودة على backend مماثل للإنتاج داخل بيئة اختبار منفصلة إذا كان متاحًا.
- إثبات مصادقة/بحث/checkout طبيعي دون 429 مبكر؛ webhook صحيح لا يُحظر بسبب استخدام التصفح العادي.

**الاختبارات:** ملف جديد مقترح `api/tests/Feature/Platform/RateLimitSecurityTest.php`؛ و`ProductionHardeningTest.php`، `api/tests/Feature/Api/V1/Storefront/StorefrontApiContractTest.php`، `api/tests/Feature/Api/V1/Contact/ContactModuleApiTest.php`، واختبارات المحفظة ذات الصلة في `api/tests/Feature/Api/V1/Phase18/Phase18VariantWalletCashbackTest.php`.

**معيار الإكمال:** التجاوزات الأربعة لا تعمل، والاختبارات تمر عبر مسارات فعلية لا عبر callback معزول فقط، والحدود لا تخلط التينانتات أو تكسر الاستخدام الطبيعي.

## المرحلة 5 تحديث الاعتماديات المتأثرة

**الملفات:** `api/composer.json` و`api/composer.lock`؛ كود التطبيق فقط إذا لزم توافق موثق.

1. أعد `composer audit --locked --format=json` وراجع الحزم المثبتة. نتيجة المراجعة السابقة كانت 21 تنبيهًا موزعة على Guzzle وLaravel وCommonMark وFlysystem وSymfony HTTP Foundation؛ العدد يتغير مع تحديث قاعدة التنبيهات.
2. حدّد إصدارات مصححة متوافقة مع PHP والاستضافة من advisories الرسمية الحالية. لا تكتفِ بأول إصدار يغلق تنبيهًا قديمًا مع بقاء تنبيه أحدث.
3. نفّذ تحديثًا مستهدفًا للحزم المتأثرة واعتمادياتها الضرورية؛ لا ترقِّ major أو كل الحزم بلا داعٍ. راجع Composer scripts قبل التنفيذ، ولا تشغّل `setup` أو scripts قد تعمل migrate/seed.
4. لا تعالج النتيجة بإضافة ignore أو تعطيل audit. وثّق أي تنبيه متبقٍ وشروط وصوله؛ لا تصف المرحلة بأنها خالية من الثغرات مع تنبيه بلا معالجة.

**اختبارات التأكد:** `composer validate --no-check-publish`، و`composer check-platform-reqs` محليًا، ثم audit جديد. شغّل اختبارات HTTP/auth/webhook/media/Excel/exports المتأثرة؛ وفي الإقفال شغّل suite API وفق إعداد الاختبار. توافق PHP على cPanel بند مستقل إذا لم يتوفر وصول إليه.

**معيار الإكمال:** التنبيهات المستهدفة عولجت بإصدارات متوافقة، واختبارات التوافق نجحت، وأي قيد نشر موثق صراحة.

## المرحلة 6 منع OTP ثابت خارج الاختبار والتطوير

**الملفات:** `api/app/Services/Auth/Otp/OtpCodeGenerator.php`، `OtpVerificationService.php`، `OtpChallengeService.php`، `api/config/ecommerce.php`، `api/app/Services/Platform/ReadinessChecker.php`، `api/.env.example`.

**التعديل:** احصر OTP ثابتًا في `local/testing` بحراسة تنفيذية تشمل التوليد والتحقق. في production/staging يجب ألا يُستخدم كبديل عن hash التحدي حتى لو وُجد المتغير بالخطأ. أضف فحص إعداد واضح لا يطبع القيمة. احتفظ بالتوليد العشوائي، انتهاء الصلاحية، حد المحاولات، منع إعادة الاستخدام، وربط الوجهة والمتجر والتينانت.

**اختبارات القبول:** تحت محاكاة production مع قيمة افتراضية اختبارية، الكود الثابت المخالف للتحدي يُرفض، والكود العشوائي الصحيح يُقبل عبر endpoint الكامل؛ كود منتهي/مستهلك/مقفل أو تابع لسياق آخر يُرفض؛ التطوير يعمل حسب الإعداد المقصود. اعزل مقدمي إرسال OTP.

**الاختبارات:** `api/tests/Feature/Api/V1/Customer/CustomerOtpAuthTest.php`، مع اختبار وحدة للمولّد عند الحاجة، واختبارات readiness ذات الصلة.

**معيار الإكمال:** وجود `OTP_DEFAULT_CODE` وحده لا يسمح بتجاوز تحقق production، ولا تتسرّب قيمته في الاستجابات أو السجلات.

## المرحلة 7 تقييد رفع وعرض الملفات العامة

**الملفات:**

- `api/app/Http/Requests/Api/V1/Admin/Media/StoreMediaAssetRequest.php`
- `api/app/Services/Admin/Media/AdminMediaLibraryService.php`
- `api/app/Http/Controllers/PublicMediaController.php`
- `api/app/Services/Store/TenantFilesystemManager.php`
- `api/config/filesystems.php` و`api/app/Http/Middleware/SecurityHeaders.php`
- طلبات وخدمات رفع صور الكتالوج والشعار عند اشتراكها في تخزين عام مماثل.

**التعديل:**

1. احصر أنواع الملفات المطلوبة فعليًا للوسائط، وطبّق allowlist بحسب المحتوى والامتداد معًا. لا تعتمد `getClientOriginalExtension()` وحده. امنع ملفات التنفيذ وHTML والمحتوى النشط غير المعالج.
2. SVG يُرفض افتراضيًا إلا إذا كان مطلوبًا وله مسار تعقيم موثوق واختبارات؛ لا تستخدم regex لتعقيمه. احتفظ بدعم الصور والوثائق المشروعة التي يحتاجها المشروع.
3. قدّم الأنواع غير الآمنة للعرض inline كـ attachment أو من origin مستقل مع سياسة مناسبة. لا تكتفِ بـ nosniff، فهو لا يعزل ملف HTML صحيح النوع.
4. غطِّ الملفات القديمة الموجودة عند التقديم بدون حذفها أو تعديل صفوفها جماعيًا. وثّق حماية مسارات static storage/CDN التي قد تتجاوز الكنترولر.
5. حافظ على منع path traversal وعزل جذور public/private لكل tenant.

**اختبارات القبول:** رفض محتوى HTML/PHP باسم صورة، ورفض SVG نشط، وقبول صورة صحيحة وملف مسموح؛ ملف قديم نشط لا يُقدّم inline بلا عزل؛ traversal لا يقرأ private أو `.env`؛ مستخدم متجر لا يدير أصول متجر آخر؛ لا حذف تلقائي لأي ملف قديم.

**الاختبارات:** `api/tests/Feature/Api/V1/Admin/Media/AdminMediaLibraryApiTest.php`، `api/tests/Feature/PublicMediaControllerTest.php`، `api/tests/Feature/Api/V1/Admin/Catalog/AdminCatalogMediaApiTest.php`. عدّل رسائل `admin` واختبارات MediaLibraryPage فقط إذا احتاج رفض الأنواع الجديدة توضيحًا.

**معيار الإكمال:** رفع الملفات وتقديم الموجود منها يغطيان الخطر، وصور المتجر المعتادة ما زالت تعمل. لا تدّعِ منع تنفيذ على مستوى السيرفر اعتمادًا على اختبار الكنترولر وحده.

## المرحلة 8 حماية تغيير كلمة المرور ودورة حياة التوكنات

**الملفات:**

- `api/app/Services/Customer/CustomerAuthService.php`
- `api/app/Http/Requests/Api/V1/Storefront/Customer/UpdateCustomerProfileRequest.php`
- `api/app/Http/Controllers/Api/V1/Storefront/Auth/CustomerPasswordResetController.php`
- `api/app/Services/Admin/AdminAuthService.php` وطلبات تغيير كلمة مرور الإدارة.
- `api/config/sanctum.php`، وإعداد auth المخصص عند الحاجة، وكل موضع `createToken` متعلق بهذه التدفقات.

**التعديل:**

1. تغيير كلمة مرور العميل من profile يحتاج كلمة المرور الحالية أو إثبات إعادة مصادقة مكافئ، مع إبقاء تدفق reset الموثق مستقلًا. لا تحوّل امتلاك bearer token وحده إلى قدرة تغيير بيانات دخول دائمة بلا إعادة تحقق.
2. بعد تغيير كلمة المرور أبطل التوكنات الأخرى للمستخدم فقط. حدّد بوضوح هل يحتفظ الطلب الحالي بتوكنه أم يعاد تسجيل الدخول؛ الاختيار المبدئي المحافظة على الجلسة الحالية في التغيير الموثق، وإبطال الجميع بعد reset.
3. أضف مدة صلاحية محدودة وقابلة للضبط للتوكنات الجديدة، مع سياسة واضحة للإدارة والعملاء وOTP login. لا تحذف كل توكنات الإنتاج أو تفرض تسجيل خروج جماعي ضمنيًا. وثّق انتقال التوكنات القديمة عديمة الانتهاء وكيف يُعتمد قبل النشر.
4. افحص المستهلكين قراءة فقط قبل تغيير عقد profile. إذا كانت واجهة متجر لا ترسل إعادة المصادقة المطلوبة، سجّل تعديلها كمتطلب إطلاق منفصل؛ لا تغيّر `front/` تلقائيًا ولا تعتبر التدفق جاهزًا للمستخدم قبل توافقه.

**اختبارات القبول:** تغيير بلا إثبات/بكلمة مرور حالية خطأ يُرفض بلا تغيير؛ الصحيح ينجح؛ توكن ثانٍ يصبح 401؛ توكنات مستخدم آخر وتينانت آخر تبقى سليمة؛ انتهاء التوكن يرفض الطلب باستخدام time travel؛ reset وlogout وOTP login لا يتجاوزون السياسة.

**الاختبارات:** `api/tests/Feature/Api/V1/Customer/CustomerAuthTest.php`، `CustomerOtpAuthTest.php`، `api/tests/Feature/Api/V1/Admin/AdminAuthTest.php`، واختبارات reset القائمة أو ملف جديد عند عدم وجودها.

**معيار الإكمال:** تغيير بيانات الدخول يتطلب إثباتًا مناسبًا ويقطع الجلسات المطلوبة، وسياسة التوكنات وانتقال العقود موثقان وقابلان للاختبار.

## المرحلة 9 التحقق الشامل وتجهيز النشر

**الملفات:** الاختبارات المضافة والمعدلة، `api/.env.example`، وهذه الخطة. إعدادات البروكسي/السيرفر تُوثّق، ولا تُغيّر على إنتاج غير مفحوص.

1. شغّل اختبارات المراحل المتأثرة، ثم suite API مرة واحدة بعد استقرار التعديلات، مع نفس عزل البيئة. لا تخفِ فشلًا سابقًا ولا تعتبره ناتجًا عن الإصلاح بلا مقارنة.
2. إذا تغيّر admin، شغّل اختبارات ملفاته بـ `npm test -- <مسار الاختبار>` من `admin/` و`npm run typecheck`. جرّب المسار المتأثر على `http://localhost:5177/admin`: نفّذ إجراءً مأذونًا وآخر مرفوضًا، وتأكد من الرسالة والحفظ الفعلي. API الصحيح `http://127.0.0.1:8010/api/v1`.
3. راجع نشر `APP_DEBUG=false`، وحذف إعداد OTP الثابت في الإنتاج، وTLS، وPHP المتوافق، وتعطيل تنفيذ السكربتات من مجلدات uploads، وعزل المحتوى العام إذا لزم.
4. افحص البروكسي الفعلي قبل ضبط TrustProxies: وثّق العناوين الموثوقة وكيف يصل IP العميل، وارفض الثقة العمياء في `X-Forwarded-For`. اختبر أن العميل المباشر لا يغيّر هويته بمجرد إرسال هيدر، وأن المرور عبر البروكسي الموثوق يحتسب IP الصحيح.
5. وثّق backend عدادات مشتركًا عند تعدد instances، وحدود ingress قبل PHP لحماية ما يسبق middleware. افحص CORS بحسب origins المطلوبة ومصادقة bearer؛ لا تعتبر CORS بديلًا للصلاحيات أو تجعل wildcard وحده دليل اختراق.
6. سجّل متغيرات الإعداد الجديدة، تغييرات العقود، الملفات المعدلة، ونتائج الاختبارات. لا تشغّل migrations جماعية ولا deploy تلقائيًا. متطلب نشر غير متاح يبقى بندًا مفتوحًا واضحًا.
7. تجهيز التراجع يكون بإصدار سابق معروف وخطة تغييرات إعداد مدروسة، لا `git reset` لملفات المستخدم ولا rollback لبيانات المتاجر. اذكر الأثر الأمني لإعادة إصدار قديم قبل اعتماده.

**معيار الإكمال:** اختبارات القبول والتوافق ناجحة؛ الإصلاحات المحلية منفصلة بوضوح عن بنود تحقق الإنتاج؛ لا يوجد ادعاء بأن الإنتاج آمن أو أن النشر تم دون فحصه فعليًا.

## سجل التقدم

حوّل الحالة إلى «مكتملة» فقط بعد تسجيل أسماء الاختبارات ونتائجها. استخدم «متوقفة على …» لمتطلب حقيقي، مع الاستمرار في المراحل المستقلة.

| المرحلة | الحالة | الملفات التي عُدلت فعليًا | الاختبارات والنتيجة | المتبقي |
| --- | --- | --- | --- | --- |
| 0 نقطة البداية | مكتملة | ملف الخطة فقط | 20 اختبارًا، 99 assertion، ناجحة | لا يوجد فشل أساس مسجل |
| 1 هويات الموظفين ومنح الأدوار | مكتملة محليًا | AdminStoreUserService، AdminRoleService، StorePermissionGate، PlatformStoreService، اختبارات StoreUserSecurity وPlatformStoreManagement | StoreUsers: 29 اختبارًا و174 assertion؛ PlatformStoreManagement: 14 اختبارًا و91 assertion، ناجحة | تحسين واجهة دعوة الموجود اختياري؛ API يرفض تغيير هويته |
| 2 صلاحيات المتجر والأدوار الفارغة | مكتملة | StorePermissionGate، AuthorizesStorePermission، AdminPermissionCatalog، SystemRolePermissionSync، AdminCatalogAccess، AdminRoleService، User، RoleSeeder، مساعدات fixtures واختبارات الصلاحيات | 29 اختبارًا، 184 assertion، ناجحة | تهيئة أدوار قديمة فارغة تحتاج مراجعة صريحة قبل النشر |
| 3 ربط نتائج الدفع | مكتملة محليًا | ملفات المرحلة السابقة، وPaymobGateway وPaymobTransactionFixture واختبارات HMAC/callback والتحقق لدى المزود | 42 اختبارًا أوليًا؛ مجموعة إقفال Paymob/Binding/Provider/AdminVerify: 48 اختبارًا و279 assertion، ناجحة | اختبار تزامن على محرك الإنتاج عند توفر بيئة منفصلة |
| 4 محدد الطلبات | مكتملة محليًا | RouteServiceProvider، routes/api.php وstorefront.php، bootstrap/app.php، ecommerce/cache/cors، WalletGiftCodeController، .env.example، RateLimitSecurityTest | 10 اختبارات مركزة و59 assertion؛ اختبارات RateLimitSecurity الأحد عشر كلها نجحت في المجموعة الشاملة | ضبط الحدود حسب NAT وحجم التشغيل؛ تزامن backend الإنتاج منفصل |
| 5 الاعتماديات | مكتملة محليًا | api/composer.lock وvendor المحلي | audit بلا تنبيهات؛ validate وplatform-reqs ناجحة؛ 59 اختبارًا و418 assertion | توافق PHP والاستضافة الفعلية قبل النشر |
| 6 OTP | مكتملة محليًا؛ عائق التوثيق أُغلق في 17 أكتوبر | OtpCodeGenerator، ReadinessChecker، .env.example، CustomerOtpAuthTest، والتوثيق المطلوب | اختبارات OTP نجحت سابقًا؛ StagingReadiness وBackendQa الآن 20 اختبارًا و129 assertion ناجحة | ضبط بيئة النشر وفحص مزود OTP منفصلان |
| 7 رفع الملفات | مكتملة محليًا | PublicMediaFile الجديد، StoreMediaAssetRequest، AdminMediaLibraryService، PublicMediaController، اختبارات MediaLibrary وPublicMedia | 19 اختبارًا، 103 assertion، ناجحة | حماية static/CDN التي تتجاوز الكنترولر ضمن تحقق الإنتاج |
| 8 كلمة المرور والتوكنات | مكتملة API؛ توافق الواجهات وانتقال القديم قبل النشر | AccountProfileService وApiTokenService الجديدان، CustomerAuth/AdminAuth/OtpVerification/GuestCheckout، طلَبا profile، AdminStoreUserService، ecommerce/.env.example، اختبارات الجلسات والمصادقة | 62 اختبارًا و274 assertion؛ اختبار إضافي للموظف 1 و5 assertions | front لا يمرر current_password حاليًا؛ PAT قديم بلا expiry يحتاج انتقالًا معتمدًا |
| 9 التحقق وتجهيز النشر | مكتملة محليًا في 17 أكتوبر 2026 | الملفات السابقة، BackendQaTest وCustomerOtpAuthTest، و14 ملف توثيق للتشغيل والعقود وQA | الشامل النهائي: 1639 حالة، 1637 ناجحة و8203 assertion، بلا فشل أو أخطاء، وحالتان MySQL متخطاتان؛ exit 0 | MySQL منفصل، وتحقق الإنتاج/الواجهات وانتقال PAT قبل النشر |

## تسليم كل مرحلة

### تقدم المرحلة 9

#### استكمال 17 أكتوبر 2026

**النتيجة النهائية وإقفال التحقق المحلي:** اكتمل `security_test --log-events-text /tmp/ecommerce-phase9-20261017-final-events.log` على النسخة النهائية: **1639 حالة، 1637 نجاحًا، 8203 assertion، صفر فشل وصفر أخطاء، وحالتان متخطاتان، exit code 0** خلال 1237 ثانية تقريبًا. مرّت اختبارات BackendQa الاثنا عشر وStagingReadiness الثمانية، بما فيها جميع فحوص وجود التوثيق، كما مرّ اختبار production OTP المشدد داخل المجموعة الشاملة. بذلك أُغلقت عوائق QA والتوثيق المحلية ولم يُكتفَ بإعادة اختبارات منتقاة.

الحالتان المتخطاتان كما هما في المصدر: `DeliverySlotConcurrencyTest::test_capacity_one_accepts_only_one_concurrent_mysql_reservation` يحتاج قواعد MySQL release معزولة، و`ReleaseCandidateMysqlSmokeTest::test_mysql_table_uses_utf8mb4_charset_and_collation` يحتاج driver MySQL. لا يُعد skip إثباتًا للتزامن أو الترميز. ما زالت تحققات الاستضافة وTLS/proxy/ingress وstatic/CDN وتوافق الواجهات مع current_password وانتقال PAT القديم متطلبات قبل النشر في SECURITY_DEPLOYMENT_CHECKLIST، ولا يغيّر نجاح SQLite حالتها.

ملفات هذه المتابعة الفعلية:

- الاختبارات: `api/tests/Feature/Platform/BackendQaTest.php` و`api/tests/Feature/Api/V1/Customer/CustomerOtpAuthTest.php`.
- التشغيل: `docs/STAGING_DEPLOYMENT_GUIDE.md` و`docs/ENVIRONMENT_REFERENCE.md` و`docs/DEPLOYMENT_RUNBOOK.md` و`docs/QA_BUG_REPORT_TEMPLATE.md`.
- API QA: `api/docs/BACKEND_QA_GUIDE.md` و`api/docs/API_MANUAL_QA_CHECKLIST.md` و`api/docs/POSTMAN_QA_GUIDE.md` و`api/docs/MONITORING_GUIDE.md` و`api/docs/API_ERROR_CODES.md`.
- الإدارة: `admin/docs/ADMIN_PANEL_QA_CHECKLIST.md` و`admin/docs/ADMIN_ROLE_QA_MATRIX.md`.
- توثيق المتجر: `front/3lcost/docs/FRONTEND_INTEGRATION_QA_CHECKLIST.md` و`front/3lcost/docs/STOREFRONT_API_CONTRACT.md` و`front/3lcost/docs/STOREFRONT_FRONTEND_FLOW.md`.
- السجل وقائمة النشر: هذا الملف و`api/docs/SECURITY_DEPLOYMENT_CHECKLIST.md`.

مرّت مراجعة روابط الوثائق الأربع عشرة، وPHP syntax وPint لملفي الاختبار، و`git diff --check` للمسارات المتأثرة. لم يُعدّل كود API التشغيلي أو seeders أو كود الواجهات في هذه المتابعة، ولم تُعدّل .env أو قاعدة تشغيل، ولم يحدث commit أو push أو deploy.

تفاصيل التشخيص والإعادات قبل الوصول للنتيجة النهائية محفوظة أدناه:

أُعيد أول اختبار BackendQa قبل التعديل، فتوقف في التجهيز بسبب غياب tenant. تغيّر تجهيز هذه المجموعة فقط: تأكيد أن الاتصال SQLite `:memory:`، منع HTTP غير المزيف، وربط CurrentClientResolver بسجل client الذي أنشأه UsesTenantApiFixtures قبل DatabaseSeeder. لم يتغير guard الخاص بالتينانت أو أي seeder تشغيلي، ولم تتغير قاعدة تشغيل أو بيانات ديمو قائمة.

بعد إصلاح fixture نجح 11 اختبارًا وبقي اختبار وجود التوثيق. أُضيفت الملفات الأربعة عشر المطلوبة دون حذف توقعات الاختبارات: أدلة staging والإعدادات والنشر وقالب البلاغ تحت `docs/`؛ أدلة Backend QA وAPI manual QA وPostman والمراقبة والأخطاء تحت `api/docs/`؛ قائمة لوحة التحكم ومصفوفة الأدوار تحت `admin/docs/`؛ وعقد storefront وتدفقه وقائمة تكامله تحت `front/3lcost/docs/`. المصفوفة مشتقة من كتالوج الصلاحيات الحالي، والوثائق تميز خطوات التحقق من النتائج المنفذة. لم يتغير كود admin أو front أو Main.

أمر القبول `security_test tests/Feature/Platform/BackendQaTest.php tests/Feature/Platform/StagingReadinessTest.php` نجح: **20 اختبارًا و129 assertion**، بلا فشل أو أخطاء تجهيز. مرّت مراجعة روابط ملفات التوثيق الجديدة وPint لملف BackendQaTest و`git diff --check`. بدأت إعادة المجموعة الشاملة بعد هذه التعديلات؛ النتيجة النهائية تُسجل هنا عند انتهائها. تحققات MySQL والنشر الفعلية تبقى مستقلة ولا يثبتها SQLite.

إعادة الشامل الأولى في 17 أكتوبر نفذت 1639 حالة و8196 assertion: 1636 نجاحًا، فشل واحد، وحالتان MySQL متخطاتان، بلا أخطاء تجهيز. مرّت BackendQa والتوثيق؛ الفشل الوحيد في سيناريو production الخاص بـ OTP عند قبول كود التحدي بعد محاولة الكود الافتراضي. نجح السيناريو منفردًا (1 اختبار و6 assertions)، ثم مجموعة OTP كاملة (12 اختبارًا و49 assertion)، لذلك لم يُنسب الفشل إلى منطق التشغيل دون دليل.

سيناريو المقارنة كان يستخدم وقتًا حقيقيًا ويقبل أي 422 في محاولة الرفض؛ أصبح زمنه مثبتًا بـ freezeTime، وأصبح يشترط رفض `code` برسالة الكود غير الصحيح، وبقاء التحدي Pending واحتساب محاولة واحدة، قبل قبول كوده الحقيقي. اختبار expiry المستقل بقي كما هو؛ لم يتغير أي كود OTP تشغيلي أو مدة صلاحيته أو mock عزل قاعدة البيانات. نجح `security_test tests/Feature/Api/V1/Customer/CustomerOtpAuthTest.php tests/Feature/Platform/BackendQaTest.php tests/Feature/Platform/StagingReadinessTest.php`: **32 اختبارًا و183 assertion**. شُغّل Pint على ملفي الاختبار المحددين فقط، وبدأت بعدها إعادة الشامل على النسخة النهائية. فشل التشغيل الأول لا يُمحى أو يُستبدل بمجرد نجاح الإعادة المركزة.

#### نتائج التشغيل السابق في 16 أكتوبر 2026

**نتيجة التشغيل الشامل:** `security_test` نفّذ 1638 حالة و8055 assertion خلال 1145 ثانية تقريبًا: 1620 نجاحًا، 4 حالات فشل، 12 خطأ تجهيز، وحالتان متخطاتان. هذه النتيجة قبل إصلاح الحالات الثلاث أدناه، ولا تُقدّم باعتبارها suite خضراء على النسخة النهائية.

**إعادة التحقق بعد آخر تعديل:** شُغلت `security_test tests/Feature/Api/V1/Platform/Stores/PlatformStoreManagementTest.php tests/Feature/Api/V1/Payment/PaymentGatewayHardeningTest.php tests/Feature/Api/V1/Admin/Reports/AdminOperationalReportTest.php` على الملفات النهائية: **31 اختبارًا و195 assertion، كلها ناجحة**. تغطي مجموعات التوافق الثلاث كل حالات الفشل التي أُصلحت، وتضيف الحالة الأمنية الجديدة للمنصة. لم يُعد تشغيل suite كاملة بعد هذه التعديلات؛ المتبقي المسجل هو تجهيز BackendQa والتوثيق، مع تحققات MySQL والنشر المنفصلة.

- `AdminOperationalReportTest`: fixture كتب وقت المتجر المحلي في أعمدة تُقارن بـ UTC، ففشل عند هذا الوقت من اليوم. نجحت نسخة تشخيص معزولة بتوقيت UTC؛ ثم عُدّلت حقول placed_at وused_at في الاختبار فقط إلى UTC دون تغيير أي توقع أو كود تقارير.
- `PaymentGatewayHardeningTest::test_pending_recovery_command_processes_stale_pending_payment`: عينة المزود القديمة لا تحمل order/amount/currency. عُدّلت إلى PaymobTransactionFixture مع order ID مستقل عن transaction ID؛ لم تُضعف فحوص الدفع. نجحت الحالتان السابقتان بعد الإصلاح: اختباران و20 assertion.
- `PlatformStoreManagementTest::test_assign_existing_user_as_admin_without_duplicate_user`: PlatformStoreService كان يضيف name افتراضيًا حتى لو لم يرسله العميل، فيصطدم بعقد الدعوة الآمن. أصبح يمرر الاسم عند طلبه صراحة فقط، في المسار المباشر وadmin المتداخل. نجحت المجموعة كاملة: 14 اختبارًا و91 assertion، بينها اختبار جديد يرفض تغيير بيانات الدخول أثناء دعوة حساب موجود. التعديل في API فقط، وليس واجهة Main.
- `BackendQaTest`: الحالات الاثنتا عشرة تفشل في setUp لأن `$this->seed()` لا يحدد tenant، فيرفض TenantBootstrapSeeder قبل تنفيذ الأعمال. ملفات DatabaseSeeder وBackendQaTest لم تتغير ضمن هذا الإصلاح؛ لم يُضعف guard ولم يُشغّل seeder على قاعدة تشغيل. تحتاج هذه المجموعة تحديث fixtures مع tenant صريح ومعزول كعمل QA مستقل.
- `StagingReadinessTest::test_required_phase17_docs_exist`: فشل التوثيق القديم المسجل في المرحلة 6 ما زال قائمًا؛ تفاصيل المسارات أدناه. لا تؤكد هذه النتيجة سلامة الحالات التي لم تتجاوز تجهيز BackendQa.
- الحالتان المتخطاتان تخصان تزامن سعة التوصيل وcharset/collation على MySQL؛ SQLite المعزولة لا تثبتهما، ويلزم تشغيلهما على قواعد release منفصلة.

مرّت داخل المجموعة الشاملة جميع حالات StoreUserSecurity (16)، وRateLimitSecurity (11)، وPaymobCallbackSecurity (21)، وPaymobWebhookContract (1)، وPaymentWebhookBinding (6)، وCustomerSessionSecurity (4)، وCustomerOtpAuth (12)، وPublicMediaController (4). آخر `composer audit --locked` بلا advisories أو abandoned packages، و`git diff --check -- api` ناجح. فحص PHP syntax شمل 122 ملفًا بلا أخطاء. Pint لملفات الأمن المحددة ناجح؛ PlatformStoreService له ملاحظات تنسيق قديمة مطابقة لفحص نسخة HEAD، ولم يُعد تنسيق الملف كله.

توقف تشغيل شامل أول بعد 476 نجاحًا بسبب حد PHP: مسار تصدير Excel يضبط `set_time_limit(120)`، فيبقى المؤقت على عملية PHPUnit المشتركة ويفشل اختبار لاحق غير متعلق بالتصدير. نجحت حالة POS التي توقف عندها منفردة (1 اختبار، 1 assertion). أصبح `tests/TestCase.php` يصفر المؤقت عند بداية كل اختبار فقط، دون تعديل إعدادات تطبيق التشغيل أو مسار التصدير. أُعيدت حالتا التصدير وPOS قبل بدء التشغيل الشامل الجديد؛ لا تُحسب النتيجة المقطوعة نجاحًا للمجموعة.

إعادة تشغيل الإقفال من `api/` بعد تعريف `security_test` أعلاه: `security_test` للمجموعة الشاملة؛ ومجموعة الدفع المركزة: `security_test tests/Unit/Payments/PaymobWebhookContractTest.php tests/Feature/Api/V1/Payment/PaymobCallbackSecurityTest.php tests/Feature/Api/V1/Payment/PaymentProviderIntegrationTest.php tests/Feature/Api/V1/Admin/Payments/AdminPaymentVerificationTest.php tests/Feature/Api/V1/Payment/PaymentWebhookBindingTest.php`. مجموعة الموظفين: `security_test tests/Feature/Api/V1/Admin/StoreUsers`.

فحص ملفات اختبار `StagingReadinessTest::test_required_phase17_docs_exist` كشف أن المسارات الثمانية المطلوبة مفقودة: `docs/STAGING_DEPLOYMENT_GUIDE.md` و`docs/ENVIRONMENT_REFERENCE.md` و`api/docs/MONITORING_GUIDE.md` و`front/3lcost/docs/FRONTEND_INTEGRATION_QA_CHECKLIST.md` و`docs/DEPLOYMENT_RUNBOOK.md` و`front/3lcost/docs/STOREFRONT_API_CONTRACT.md` و`front/3lcost/docs/STOREFRONT_FRONTEND_FLOW.md` و`api/docs/API_ERROR_CODES.md`. يفشل الاختبار عند أولها فقط؛ لم تُضف ملفات شكلية لإخفاء الفشل، ولم تتغير `front/` في هذا التنفيذ.

مراجعة الحماية النهائية أثبتت فشل حالتين إضافيتين قبل إصلاحهما: نزع صلاحية الإدارة من دور نظام مشترك لكل مديري المتجر، وحذف تعيين موظف في متجر متاح للفاعل دون أن يديره أثناء syncStoreAccess. أصبحت المزامنة محصورة بمتاجر الإدارة، وتعديل الدور المشترك يفحص جميع المتاجر المتأثرة، وآخر مدير يُحسب بالصلاحيات المحفوظة مع استبعاد كل تعيينات الدور الجاري تغييره. أضيفت transactions وأقفال المتاجر والحسابات في مسارات الموظفين وتعديل الأدوار لتسلسل التغييرات؛ تحقق التزامن على محرك الإنتاج يبقى منفصلًا. نجحت مجموعة StoreUsers كاملة بعد أربع حالات الإقفال الجديدة: 29 اختبارًا و174 assertion.

أُضيفت SECURITY_DEPLOYMENT_CHECKLIST لتغييرات العقود والحدود وانتقال PAT والأدوار القديمة وإعدادات الاستضافة. نجح اختبارا IP المباشر والبروكسي الموثوق. ثبتت حالة إضافية في دعوة مستخدم client_id آخر في قاعدة مشتركة، فأُضيف guard صريح لإنشاء/تحديث الموظف، ونجحت مجموعة StoreUsers وRateLimitSecurity: 36 اختبارًا و222 assertion. شملت مراجعة syntax 111 ملف PHP متغيرًا وجديدًا بلا أخطاء. شُغّل Pint على ملفات الأمن المحددة فقط، ولم يُستخدم --dirty على المستودع. بدأت المجموعة الشاملة ووصلت لأكثر من 300 نجاح دون فشل قبل إيقافها عمدًا لاستكمال مراجعة Paymob على النسخة النهائية؛ هذه ليست نتيجة suite مكتملة.

### نتيجة المرحلة 8

فشلت أولًا اختبارات غياب إعادة المصادقة، بقاء التوكن الثاني، غياب expiry، وتجاوز تغيير البريد أو مسار الموظفين. أصبح AccountProfileService يتحقق من كلمة المرور الحالية عند تغيير password/email/phone مع transaction وقفل حساب، ويحافظ على PAT الحالي ويبطل الباقي. تغيير اسم فقط أو إرسال بريد/هاتف لم يتغير لا يحتاج إعادة تحقق. مسار تعديل الموظف يرفض تغيير كلمة مرور الفاعل نفسه ويوجّهه لمسار password الموثق؛ تغيير كلمة مرور موظف آخر مأذون يبطل جلسات ذلك الموظف وحده. ApiTokenService يصدر توكنات جديدة محدودة للإدارة 720 دقيقة وللعميل 43200 دقيقة، قابلة للضبط، في التسجيل والدخول وOTP وguest checkout. global Sanctum expiration بقي null لمنع تسجيل خروج جماعي غير معتمد؛ لا تغيير للتوكنات القائمة. نجحت CustomerSessionSecurity وCustomerAuth وCustomerOtpAuth وAdminAuth وStoreUserSecurity وCustomerPasswordReset وAdminPasswordReset: 62 اختبارًا، 274 assertion؛ ثم اختبار عزل إبطال جلسات الموظف: 1 و5 assertions. تغيّر عقد profile: يلزم current_password الصحيح عند تغيير بيانات الدخول. فحص القراءة لـ front/3lcost وkalemat وyaboow كشف أنها لا تمرره، ويلزم تعديلها باعتماد منفصل قبل إطلاق هذا التدفق؛ لم تُعدّل front أو Main. للمستخدمين الذين دخلوا بـ OTP ولا يعرفون كلمة مرور، استخدم تدفق reset الموثق بدل تجاوز شرط التحقق. انتقال التوكنات القديمة مفصل في قائمة تجهيز النشر.

### نتيجة المرحلة 7

ثبت أولًا قبول HTML وتقديمه inline. أصبح الرفع محصورًا في JPEG/PNG/WebP/GIF/PDF مع فحص المحتوى والامتداد في الطلب والخدمة، وامتداد التخزين مشتق من MIME. ملفات HTML وSVG والتنفيذ تُرفض حتى مع اسم مضلل. اختُبر ملف HTML حقيقي أُرسل باسم jpg وMIME عميل مزيف؛ أُصلح fixture كان يزوّر MIME من الاسم بدل قياس المحتوى. الملفات القديمة خارج الصور الآمنة تُقدّم attachment مع CSP sandbox وnosniff دون حذفها. اجتازت AdminMediaLibrary وPublicMediaController وAdminCatalogMedia: 19 اختبارًا و103 assertion، مع قبول الصور وPDF وعزل المتاجر ومنع traversal. لم تُعدّل واجهة admin؛ عرض SVG كخيار في منتقيها لا يتجاوز رفض API، وتحسين هذا الخيار واجهي فقط. المسارات static/CDN تبقى بحاجة لحماية إعداد الاستضافة.

### نتيجة المرحلة 6

فشل اختبار API الخاص بمحاكاة production قبل الإصلاح لأنه قبل الكود الافتراضي بدل كود التحدي. أصبح configuredDefault يرفض خارج local/testing، فيغطي التوليد والتحقق معًا، وفحص البيئة يعلن FAIL عند بقاء المتغير دون إظهار قيمته. نجح رفض الافتراضي وقبول كود التحدي الصحيح على endpoint كامل مع mock يمنع أي تبديل فعلي لقاعدة بيانات أو ملفات أثناء المحاكاة. شُغلت CustomerOtpAuth وStagingReadiness: 19 ناجحًا وفشل واحد في test_required_phase17_docs_exist بسبب docs/STAGING_DEPLOYMENT_GUIDE.md المفقود أصلًا؛ لم يُحذف هذا الملف ولم يُغيّر اختبار وجوده. الاختباران معًا نفذا 77 assertion. فشل التوثيق لا يعتمد عليه إصلاح رفع الملفات التالي، ويبقى ظاهرًا للإقفال.

### نتيجة المرحلة 5

انخفضت التنبيهات من 21 إلى صفر بعد تحديث مستهدف بدون Composer scripts أو ترقيات major: Laravel 13.37.0، Guzzle 7.15.5، CommonMark 2.10.3، Flysystem 3.36.0، Symfony HTTP Foundation 8.1.9، مع prompts/promises/psr7 الضرورية. لم يُغيّر composer.json. أمر التحديث: `composer update laravel/framework 'guzzlehttp/guzzle:^7.15.2' league/commonmark league/flysystem symfony/http-foundation --with-all-dependencies --minimal-changes --no-scripts --no-interaction`. اجتازت validate وcheck-platform-reqs محليًا على PHP 8.4.15. اجتازت ProductionHardening وAdminAuth وPaymentWebhookBinding وHostedPaymentGateway وAdminMediaLibrary وCatalogExcelImport وCatalogProductExport: 59 اختبارًا و418 assertion. لا يُنقل vendor المحلي إلى cPanel؛ يلزم composer install من lock والتحقق من متطلبات منصة الاستضافة هناك.

### نتيجة المرحلة 4

فشلت اختبارات HTTP الستة للتجاوزات قبل الإصلاح، ثم أُغلقت بتطابق هوية المصادقة الفعلية وحدود IP مستقلة ومفاتيح tenant. البحث بكل المداخل يشترك في عداد واحد والتصفح بلا بحث لا يستهلكه. أُضيف حد عام 300/دقيقة قابل للضبط، وحد استرداد 5/دقيقة للمستخدم و30 للـ IP؛ callbacks/probes منفصلة عن التصفح. التواصل القديم يحل المتجر قبل throttle صراحة، ولا يثق بهيدر زائد. ترويسات Retry-After محفوظة ومكشوفة عبر CORS. اختبرت persistence على file cache جديد وفصل tenant بنفس هوية الدخول، والتقدم الزمني، وسلامة probes. إنشاء قسيمة بلا code يولّد 24 حرفًا؛ الكود اليدوي الجديد لا يقل عن 16، والقديم يظل قابلًا للتحديث. المجموعة الواسعة مرّ منها 59 اختبارًا؛ أصلحت ترتيب التواصل وتجهيز file limiter الاختباري ثم أعدت الحالات المتأثرة ضمن 10 اختبارات ناجحة و59 assertion. الإقفال الشامل سيعيد التوافق كاملًا. لا تغيير لقيم إعدادات التشغيل؛ يلزم قياس NAT والضغط الفعلي قبل اعتماد حدود الإنتاج.

### نتيجة المرحلة 3

استكمال الإقفال: استُبدلت صيغة Paymob التجريبية بحقول Transaction HMAC العشرين الموثقة، مع اختبار vector مستقل وقراءة obj دون الاعتماد على flags في الغلاف غير الموقّع. اختيار العملية من order.id الموقّع فقط، مع منع تصادم order ID بآخر transaction ID، ثم فحص المبلغ والعملة وحالات pending/auth/refund/void. أثبتت تسع حالات إضافية أن polling المزود كان يقبل نتيجة فاشلة أو تخص طلبًا/مبلغًا/عملة/transaction آخر؛ أصبح يتحقق من هوية المزود والبيانات المالية والحالة ويحفظ order_id للتكرار اللاحق. اختبارات polling تستخدم transaction ID محفوظًا من callback معلّق؛ storefront لا يثق بـ ID يرسله العميل. عُدلت عينتا ProviderIntegration وAdminPaymentVerification الناجحتان لتحتويا حقول المزود المطلوبة بدل response مبسط لا يكفي للتحقق. اجتازت مجموعة PaymobWebhookContract وPaymobCallbackSecurity وPaymentProviderIntegration وAdminPaymentVerification وPaymentWebhookBinding: 48 اختبارًا و279 assertion.

فشل اختبار معزول قبل الإصلاح لأن نتيجة لعملية مختلفة حولت العملية المختارة إلى paid. أصبح التحديث داخل قفل transaction مع مقارنة هوية العملية والمتجر وإعداد البوابة والمرجع والمبلغ والعملة الموثقين عند توفرها. Hesabe وKPay يمرران بيانات الربط الموثقة، وMyFatoorah يستخدم ID العملية التي تحقق منها بدل ID الغلاف. نقل فحص التكرار بعد التحقق وربطه بالمتجر يمنع الحدث غير الصحيح من حجز المفتاح. اختبار الربط يغطي ستة اختلافات ويتأكد من ثبات الطلب والمخزون وسجل المحفظة والمعاملات. شُغلت PaymentWebhookBinding وPaymentWebhook وPaymentGatewayHardening وHostedPaymentGateway وPaymentStoreIsolation بدون اتصال خارجي: 42 اختبارًا و231 assertion ناجحة. لم تُفعّل أي بوابة على بيانات التشغيل.

### نتيجة المرحلة 2

ثبت فشل اختباري عزل الدور وقائمة الصلاحيات الفارغة قبل الإصلاح. أصبح الدور الفعّال من تعيين المتجر الحالي، وتوقفت القائمة الفارغة عن استعادة الكتالوج. المزامنة تحفظ الأدوار الموجودة وتُهيّئ الجديدة فقط، أو IDs جديدة يمررها RoleSeeder صراحة؛ لا يُستنتج إذن من غياب الصفوف. تم اختبار مخطط قديم بعد إزالة is_system داخل SQLite الاختبارية. عدّلت مساعد fixtures الذي كان يعيّن كل الأدوار كـ admin إلى الدور الصحيح حتى تختبر الاختبارات الصلاحيات المقصودة. اجتازت StoreUsers وAdminPermissionCatalog وAdminOrderAuthorization: 29 اختبارًا، 184 assertion. لم تُنفّذ migration أو seed على التشغيل. متطلب نشر: راجع أي أدوار قديمة فارغة بدل منحها صلاحيات تلقائيًا.

### نتيجة المرحلة 0

تأكدت إعدادات SQLite `:memory:` وcache array من `phpunit.xml`. التغييرات السابقة تشمل 249 ملفًا متتبعًا، بخلاف الملفات الجديدة؛ بقيت محفوظة. اجتازت ProductionHardening وUnauthenticatedAccess وIntegrationSecurity وCustomRolePermission جميعها: 20 اختبارًا و99 assertion. مسارات إدارة الموظفين حديثة تحت `/admin/store-users`؛ مسارات الإدارة القديمة لا تعرّف بديلًا لإدارتهم. البحث والتواصل لهما مدخل بالهيدر وآخر بالمتجر في المسار؛ استرداد المحفظة تحت `/auth/customer/wallet/redeem`. ترتيب حل التينانت قبل Sanctum/cache محفوظ، ولا يوجد حد عام حاليًا.

بعد كل مرحلة أضف ملخصًا قصيرًا هنا: المشكلة التي أُغلقت، السلوك قبل/بعد، الملفات الفعلية، أمر الاختبار ونتيجته، وأي أثر على عقد API أو النشر. انتقل للمرحلة التالية بعد اجتياز معاييرها دون طلب موافقة روتينية. عند نهاية العمل قدّم ملخص الإصلاحات وبنود الإنتاج المفتوحة للمستخدم بالعربية.

### نتيجة المرحلة 1

فشلت اختبارات الأمن الأربعة أولًا على السلوك القديم، ثم نجحت بعد فصل الدعوة عن تعديل الهوية، وفحص حدود منح الأدوار النظامية والمخصصة، وحفظ الدور العالمي للمستخدم الموجود، ومنع تغيير بيانات حساب مرتبط بمتاجر خارج إدارة الفاعل. العمليات أصبحت داخل transaction لمنع الكتابة الجزئية، وتعيين عدة متاجر يفحص صلاحية الإدارة والمنح لكل متجر. شُغلت StoreUserSecurity وAdminStoreUserApi وCustomRolePermission: 17 اختبارًا و124 assertion. تغير العقد: دعوة حساب موجود مع name/password غير فارغين ترجع 422؛ دعوته بدون تعديل الهوية تنجح. لم يتغير admin في هذه المرحلة.
