Log.print used developer.log with no kDebugMode guard, so release
builds emitted wallet JWTs, the HMAC secret, phone numbers and full
API responses to os_log/logcat on the user's device. Both the rider
and driver apps were affected.
Also removes a hardcoded test-account condition in the rider login
flow that skipped the entire FCM-token/fingerprint comparison — and
therefore the device-change OTP — for one email address.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
السبب الجذري: BASE_URL واحد يشير إلى marketing_engine/index.php، بينما
`process_organic_post` و`log` موجودان في social_worker.php وحده. فكانا
يقعان على `default` فيعود {"status":"error"} بكود HTTP 200 — بلا استثناء
ولا سجل. أثره: processOrganicPost يعود null فيفشل شرط generated_comment في
FacebookBotService:137 → لا تعليق عضوي يُنشر أبداً، وكل logMessage يُبتلع
صامتاً فلم يظهر العطل في أي سجل. (تعليق العميل سطر 10 كان يذكر
social_worker.php أصلاً — الرابط هو ما خالف النية.)
- توجيه لكل أمر حسب موضعه: index.php لـ get_task/complete_task/fail_task/
evaluate_posts · social_worker.php لـ process_organic_post/log
- evaluate_posts: index.php يرد بلا حقل `data`، والعميل كان يشترطه فيعود
null في كل نجاح. النجاح صار يُقاس بـ status
- ترميز URL لكل المعاملات: رسالة خطأ فيها & أو # كانت تشقّ جسم
x-www-form-urlencoded وتفسد صفّ المهمة
- كل رد غير 200 أو status != success يُسجَّل في Logcat بدل الصمت
- المضيف إلى BuildConfig.BACKEND_HOST في البوتين (gradle) — لا نطاق مشفّر
في الكود، فتبديله لأي علامة أخرى سطر واحد
- val لا const val لقيم BuildConfig (ليست ثوابت وقت ترجمة في كوتلن)
لم يُبنَ بعد. لم يُمسّ أي PHP: الباك إند حيّ.
ما زال مفتوحاً بقرار المالك: (1) فحص X-Bot-Token معطَّل بتعليق في
social_worker.php:14 و index.php:17 والتوكن placeholder → النقطتان
مفتوحتان؛ (2) طابور social_tasks الذي يغذّيه schedule_manager بلا مستهلك.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
send_fcm.php هي نقطة كل الرسائل والمكالمات بين الراكب والسائق (التطبيقان
يستدعيانها مباشرة)، ولم تكن تحتوي أي error_log إطلاقاً — بخلاف FcmService
التي تطبع [FCM_DEBUG] وتخدم دورة حياة الرحلة فقط.
النتيجة أن فشل الرسائل كان غير قابل للتشخيص: لا سبب، ولا رد Google، ولا
حتى معرفة إن كان الطلب وصل. وقد وعدت المستخدم بقراءة [FCM_DEBUG] لهذا
المسار وهو وعد خاطئ — لا وجود له هنا.
أُضيفت ثلاث نقاط:
- نتيجة الإرسال: category + طرف من التوكن + http + طول التوكن + رد Google
عند الفشل. طول التوكن مقصود: توكن FCM الحقيقي ~163 محرفاً، والمشفّر في
جدول tokens 216 — فيُكشف أي blob مشفّر من سطر واحد.
- رفض 403: التطبيقان لا يرسلان x-api-key، فلحظة ضبط FCM_INTERNAL_API_KEY
تموت كل الرسائل بينهما بصمت. الآن يُسجَّل السبب صريحاً.
- رفض 400 على target فارغ: يعني أن المُرسِل لا يملك توكن الطرف الآخر
(tokenPassenger أو driverToken لم يصله في حمولة القبول).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docker/nginx/default.conf:13 كان فيه:
access_log off; # أثناء اختبار الضغط؛ فعّله عند الحاجة
مُطفأً من اختبار ضغط قديم ونُسي.
أثره أن أي بحث في سجلّ الوصول يرجع صفراً دائماً:
docker compose logs nginx | grep -c "send_fcm" → 0
فيبدو الاستنتاج أن التطبيق لا يستدعي المسار إطلاقاً، والحقيقة أن السجلّ
فارغ لا أن الطلبات غائبة. استنتاج خاطئ تماماً بُني على قياس معطّل.
مع توجيه error_log في www-pool.conf (الالتزام e106d2df) تكتمل الرؤية:
nginx يقول هل وصل الطلب، وPHP يقول ماذا ردّ Google.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
www-pool.conf كان يوجّه slowlog إلى /proc/self/fd/2 لكن لا شيء لـ error_log،
فكل رسائل error_log() في الباك إند تُبتلع ولا يظهر أي سطر في:
docker compose logs php
والباك إند مليء بها ويعتمد عليها في التشخيص: [accept_ride] و [FCM_DEBUG]
(يطبع حمولة FCM ورد Google كاملاً) و 📲 [FCM_RESULT] و [SOCKET_DEBUG] و
[SSRF_BLOCKED] و [SOCKET_BLOCKED].
النتيجة العملية أن سبب فشل الإشعارات كان يمكن قراءته في سطر واحد من رد
Google (UNREGISTERED / INVALID_ARGUMENT / 403) بدل الاستنتاج من الكود.
catch_workers_output=yes ينقل stdout/stderr للعمّال، و decorate_workers_output=no
يمنع بادئات الضجيج، وphp_admin_value[error_log] يثبّت المسار على fd/2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
في لوج الإنتاج ثلاثة اتصالات متزامنة للسائق نفسه (التطبيق يفتح سوكيتاً من
location_controller وآخر من background_service ومحاولات لم تُنظَّف):
✅ Driver Connected: #243ea… (×3)
ومعالج disconnect كان ينفّذ unset($connectedDrivers[$driverId]) بلا تحقّق من
أيّ سوكيت أُغلق. فإغلاق نسخة واحدة كان يُطفئ السائق فعلياً وهو متصل:
1) dispatch_order يراه offline فيضع الطلب في طابور الانتظار بدل إرساله،
ويظهر في اللوج: 🚫 Ride #2569 cancelled — notified 0/0 driver(s).
2) update_location من سوكيت ما زال حياً يجد $driverState[$driverId] محذوفاً،
و &$driverState[...] يُنشئ مدخلاً null فتُقرأ منه كل الحسابات:
Warning: Trying to access array offset on value of type null (860-872)
Warning: Undefined array key "status" (907)
الإصلاحان:
- نحفظ sid (معرّف السوكيت) مع السجلّ، ولا ننظّف في disconnect إلا إذا كان
المُغلَق هو المسجَّل حالياً؛ وإلا نتركه ونسجّل «stale duplicate».
- حرس على $driverState في update_location: يُعاد بناؤه إن غاب لأي سبب
(إغلاق نسخة أخرى، إعادة تشغيل) بدل القراءة من null.
سيرفر فقط — لا يحتاج بناء التطبيقات. يبقى فتح التطبيق لعدة سوكيتات هدراً
في الحركة يستحق إصلاحاً في الطرف الآخر لاحقاً.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
driver_socket.php عند توزيع الطلب (dispatch_order) يرسل إشعار FCM احتياطياً
**فقط** إذا كانت المنصّة ios، لأن iOS يخنق أحداث السوكيت في الخلفية:
$platform = $connectedDrivers[$driverId]['platform'] ?? 'android';
if ($platform === 'ios' && !empty($token)) { sendFCM_Async(...) }
وتطبيق السائق لم يكن يرسل platform في الـ query إطلاقاً، فالافتراضي
'android' — أي أن كابتن الآيفون لا يستلم الإشعار الاحتياطي ويفوته الطلب وهو
في الخلفية، ويعتمد على السوكيت وحده وهو مخنوق أصلاً.
ظهر بالعين في لوج الإنتاج على جهاز آيفون:
✅ Driver Connected: #243ea897954387c9 (android)
أُضيف الحقل في موضعي الاتصال: location_controller و background_service.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
الموضعان كانا يقرآن مفتاحاً لا يُكتب في أي مكان في المشروع:
$redisLocation->hGetAll("driver:location:$driverId")
الكاتب الفعلي هو معالج الدفعات في loction_server/driver_socket.php، وهو
يكتب hmset على driver:profile:{id} و driver:public:{id} معاً بنفس الحقول
(lat/lng/heading/speed/status/updated_at). و driver:public له TTL 86400
بينما driver:profile له 900 فقط، فالعام هو الأنسب للقراءة.
الأثر: كانت حمولة القبول تصل الراكب بلا إحداثيات أولية للسائق، فلا يظهر
الماركر إلا بعد أول تحديث موقع من السوكيت أو الـ polling.
أحد الموضعين ملف getRideOrderID.php الذي أضفته في f66db7db — نسخت النمط
من acceptRide.php فنقلت الخطأ معه.
مثبَّت على الإنتاج: redis-cli --scan --pattern 'driver:*' أرجع
driver:public:<id> فقط، ولا شيء باسم driver:location.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nginx على السيرفر يرفض التحميل:
nginx: [emerg] unknown directive "http2" in siro-sockets-tls.conf:30
التوجيه http2 on; لم يُضَف إلا في nginx 1.25.1. والحذف هو الصحيح لا مجرد
حلٍّ سريع: ترقية WebSocket تعتمد على ترويسة Upgrade في HTTP/1.1 ولا معنى
لها في HTTP/2 أصلاً (وproxy_http_version 1.1 مضبوط في الـ location).
تحذير تشغيلي: بعد up --force-recreate انتقلت الحاويتان إلى 127.0.0.1:12020
و13030، فإن فشل nginx -t يبقى المنفذان 2020/3030 بلا أي مستمع (Connection
refused) حتى ينجح reload. رتّب النشر: صحّح الإعداد ⇒ nginx -t ⇒ reload.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
السبب (مثبَّت على الإنتاج): التزام c35b350b بتاريخ 2026-07-23 — وهو التزام
تلقائي برسالة "Update: <time>" — أضاف المنفذ للروابط:
- 'https://jordan-siro.intaleqapp.com'
+ 'https://jordan-siro.intaleqapp.com:2020'
وكانت القيمة العاملة قبل ذلك بلا منفذ (443) فيُنهي البروكسي TLS ويمرّر.
بإضافة :2020 صار التطبيق يضرب حاوية Workerman مباشرة، وهي تفتح المنفذ نصاً
صريحاً (new SocketIO(2020) بلا سياق SSL)، فمصافحة TLS تتجمّد حتى المهلة:
❌ Socket Connect Error: timeout
❌ Socket Connect Timeout: 20000
ونتيجته أن السائق لا يبعث update_location إطلاقاً ⇒ لا GPS في Redis ⇒ لا
موقع للراكب لا عبر السوكيت ولا عبر الـ polling (كلاهما يقرأ من نفس المصدر).
مثبَّت بالأدلة على السيرفر:
- ss: المنفذان 2020/3030 يملكهما docker-proxy (نص صريح)، و443 nginx.
- curl على 127.0.0.1:2020 يرد {"sid":...,"upgrades":["websocket"]} — أي أن
الحاوية سليمة تماماً والناقص هو طبقة TLS وحدها.
الحل بلا أي بناء للتطبيقات (وبلا تنزيل مستوى الأمان — الـ JWT يُرسَل في
query string فلا يجوز إطلاقاً تحويله إلى http):
- الحاويتان تُنشران على 127.0.0.1:12020 و 127.0.0.1:13030 فقط.
- nginx على المضيف يستمع على 2020/3030 بشهادة الدومين ويمرّر إليهما مع
ترقية WebSocket (nginx/siro-sockets-tls.conf).
- proxy_read/send_timeout 3600s: اتصال السائق يعيش ساعات، وبلا ذلك يقطعه
nginx كل 60ث فتدخل الحاوية حلقة إعادة اتصال دائمة.
- proxy_buffering off و access_log off (نبضات GPS تُغرق القرص).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
استرجاع شبكة أمان موقع السائق (انحدارة من الالتزام ab3be7e2):
- كان شرط "Smart Mode" يتخطّى _startDriverLocationPollingWithTimer كلياً إذا
جاء القبول من السوكيت. قبل إصلاح ترتيب الاتصال كان السوكيت يخسر دائماً
فالـ polling يعمل ويحمل الموقع؛ وبعد أن صار يفوز تعطّلت شبكة الأمان.
- وصول حدث القبول لا يعني أن تدفّق المواقع يعمل: التدفّق له شرط مستقل —
تطبيق السائق يحقن passenger_id في update_location فقط إذا كان rideStatus
نشطاً و BoxName.passengerID مكتوباً، وهو لا يُكتب إلا بعد فتح صفحة خريطة
السائق (map_driver_controller.dart:2444).
- الآن الـ polling يعمل دائماً، و handleDriverLocationUpdate يوقفه تلقائياً
بعد 3 تحديثات ناجحة من السوكيت — وهذا المنطق كان موجوداً أصلاً وهو التصميم
المقصود. السوكيت يبقى المسار السريع بلا فقدان شبكة الأمان.
عدم تعطيل إنشاء الرحلة بانتظار سوكيت غير قابل للوصول:
- ensureConnectedBeforeRide كانت تنتظر 4 ثوانٍ قبل كل طلب. سوكيتات
Workerman تستمع نصّاً صريحاً على 2020/3030 بينما التطبيقان يستخدمان
https:// عليها (nginx لا يمرّرها — انظر docker/nginx/default.conf)، فمصافحة
TLS ضد منفذ غير TLS تتجمّد حتى المهلة ⇒ 4 ثوانٍ مهدورة في كل رحلة.
- المهلة صارت 2ث، وأُضيف cooldown دقيقتين: بعد فشل قريب لا ننتظر إطلاقاً بل
نحاول الاتصال في الخلفية ونكمل الطلب فوراً.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
سوكيت الراكب:
- setReconnectionAttempts: من 20 إلى لا نهائي، توحيداً مع تطبيق السائق. بعد
20 محاولة كان يستسلم **نهائياً** فيصمت للأبد بعد دقائق من شبكة سيئة ويبقى
الراكب على الـ polling بلا أن يدري — وخطره أكبر الآن لأن الاتصال صار يعيش
من فتح الخريطة لا من إنشاء الرحلة.
- الـ heartbeat من 15ث إلى 30ث: Socket.IO يعمل ping/pong على مستوى
البروتوكول ومستمع 'heartbeat' في passenger_socket.php فارغ أصلاً.
البدء البارد على iOS (سبب "التطبيق يغلق ونرجع من أول"):
- didChangeAppLifecycleState لا يُنفَّذ عند إطلاق التطبيق (يبدأ في resumed
بلا انتقال حالة)، وهو المُنادي الوحيد لـ _checkPendingTrip ⇒ الضغط على
الإشعار والتطبيق مقتول لا يوجّه لصفحة الطلب إطلاقاً.
- وحتى لو نُفِّذ، كان يستسلم على '/' ويحوّل المسؤولية لـ
HomeCaptainController الذي لا يملك أي منطق كهذا ⇒ الطلب يبقى في التخزين
للأبد. صار addPostFrameCallback + مُجدوِل يعيد المحاولة حتى خروج التطبيق
من شاشة البداية (سقف ~30ث)، ولا يحذف الطلب قبل التوجيه.
- حرس إضافي: لا اقتحام لرحلة نشطة بطلب قديم، لا توجيه مكرر إذا كنا على
OrderRequestPage، وتنظيف الحمولة التالفة.
TripOverlayPlugin أندرويد فقط بلا أي حرس منصّة:
- كل دالة كانت تنادي invokeMethod مباشرة ⇒ MissingPluginException على iOS
(لا يوجد ios/ ولا مدخل في pubspec.plugin.platforms).
- الأخطر: backgroundMessageHandler كان ينادي showOverlay **قبل** حفظ
pending_driver_list، فيسقط الاستثناء ويُلغي الحفظ كلياً على iOS. عُكس
الترتيب — الحفظ أولاً دائماً، وهو الحدّ الأدنى الذي لا يتوقف على منصّة أو
صلاحية.
- أُضيف isSupported وحُصِّنت كل الدوال (ترجع false/no-op ولا ترمي أبداً)،
فلا يُقطع أيضاً _initApp قبل _listenToOverlayEvents.
أخطاء index في حمولة add_ride.php (أربع نقاط قبول):
- 'Duration' كان index 4 وهو end_lng لا المدة ⇒ صار 15 (duration_text).
أحد المواضع كان فيه تعليق "انتبه: تأكد من الإندكس الصحيح للوقت".
- 'passengerWalletBurc' كان 26 وهو price_for_driver (أرباح السائق) لا رصيد
الراكب ⇒ صار 27 (passenger_wallet).
- الموضعان مكرران في main.dart و local_notification.dart و order_over_lay.dart
و order_request_controller.dart — وُحِّدت كلها.
- حرس الطول في backgroundMessageHandler: كان length > 29 مع قراءة index 30
⇒ RangeError على قائمة طولها 30 بالضبط.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
كان السوكيت مضموناً أن يخسر السباق الأول بنيوياً. ترتيب
startSearchingForDriver كان:
① await postRideDetailsToServer() → add_ride.php يوزّع الطلب على الكباتن فوراً
② _addRideToWaitingTable() (بلا await)
③ initConnectionWithSocket() ← السوكيت يبدأ الاتصال الآن فقط
بين ① و③ نافذة = ذهاب/عودة HTTP + مصافحة WebSocket + قراءة الـ JWT، ثانية
إلى ثلاث (أسوأ على 3G). وغرف Socket.IO لا تخزّن الأحداث: لو قبل كابتن داخل
تلك النافذة فإن $io->to('passenger_X')->emit() يُرمى نهائياً لأن الغرفة
فارغة — لا طابور ولا إعادة إرسال. وكابتن ينظر لقائمة السوق يقبل في أقل من
ثانية، فالنافذة مُرجَّحة لا نادرة.
هذا يفسّر لماذا كان الـ 404 على getRideOrderID.php قاتلاً: السوكيت يخسر،
والـ FCM يصل بعد أن ضبط الـ polling _isAcceptanceProcessed=true فيُرفض،
والشبكة الاحتياطية الوحيدة مكسورة.
ترتيب السيرفر كان صحيحاً أصلاً (acceptRide.php ينادي
notifyPassengerOnRideServer قبل sendFCM_Internal) — المشكلة أن المستقبِل
لم يكن موجوداً بعد.
التغييرات:
- ensureConnectedBeforeRide(): يوصل وينتظر الانضمام للغرفة بسقف 4 ثوانٍ،
ولا يُفشل إنشاء الرحلة عند انتهاء المهلة (نكمل ونتّكل على FCM/polling).
- startSearchingForDriver ينتظرها قبل postRideDetailsToServer.
- وصل السوكيت في initializeDataAfterLogin (عند فتح الخريطة) فيصير الانتظار
صفراً عملياً في الحالة الطبيعية.
- _isConnecting يغطّي نافذة الـ await على الـ JWT حتى لا يفتح النداءان
المتزامنان سوكيتين، وحرس على passengerID الفارغ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
تطبيق الراكب يطلب /ride/rides/getRideOrderID.php منذ البداية، والموجود على
السيرفر getRideOrderIDNew.php فقط ⇒ 404 صامت في getUpdatedRideForDriverApply.
هذا يفسّر عرضين ظنّاهما منفصلين:
- الـ polling يسبق الـ FCM فيضبط _isAcceptanceProcessed=true ثم يأخذ 404،
فيُرفض بعدها payload الـ FCM الذي يحمل driver_info كاملاً
("Already processed") ⇒ الرحلة مقبولة بلا معلومات سائق.
- ولأن driverToken يبقى فارغاً، ترد send_fcm.php بـ 400 Missing: target
⇒ رسائل الراكب للسائق لا تُرسَل إطلاقاً. (الاتجاه المعاكس كان يعمل بعد
إصلاح getRideWaiting.php — نفس خطأ التشفير معكوساً.)
getRideOrderIDNew.php لا يصلح بديلاً: داخلي عبر get_connect.php →
validateInternalKey فلا يستطيع التطبيق مناداته، ولا يرجع
ratingCount/completedRides/driverTier.
النقطة الجديدة:
- connect.php (JWT)، والراكب من الـ JWT فقط لا من الطلب (حماية IDOR).
- نفس استعلام acceptRide.php وشكل رده حرفياً حتى يقرأه
_fillDriverDataLocally بنفس المفاتيح.
- فك تشفير driverToken.token — بلا ذلك يصل التطبيق blob يستخدمه كـ FCM
target فيرفضه FCM بـ 400.
- تفادي تصادم المفاتيح: getUpdatedRideForDriverApply يقرأ
passengerName + last_name كاسم الراكب، فلقب السائق نُقل إلى
driver_last_name واسم الراكب الحقيقي يُرجَّع في مكانه.
- ride DB هو المرجع مع fallback على primary، ورحلة بلا سائق ترد success
لا failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول):
- تطبيق الراكب كان يرسل id فقط بلا jwt، و passenger_socket.php يرفض أي
اتصال بلا jwt ⇒ الراكب لا ينضم لغرفته أبداً ولا يستلم ride_status_change
ولا driver_location_update. تظهر حالة القبول عبر الـ polling فقط بينما
driver_info يصل بالسوكيت وحده. (سوكيت السائق يعتبر الـ jwt اختيارياً،
ومن هنا جاء التباين بين التطبيقين.)
- cancelled_by_driver كان يسقط من switch حالات الراكب فيبقى معلّقاً بعد
إلغاء السائق.
- حماية socket (late) من القراءة قبل التهيئة عند الانسحاب بلا jwt.
الإشعارات والرسائل (سبب "مرات توصل ومرات لا"):
- جدول tokens يخزّن توكن الراكب مشفّراً، و getRideWaiting.php كان يرجعه
بلا فك تشفير ⇒ من يقبل من قائمة السوق يحمل blob مشفّراً يستخدمه كـ FCM
target فيرفضه FCM بـ 400: لا إشعار قبول ولا رسائل. ومن يقبل من الـ
dispatch/FCM يحمل نصاً صريحاً فتعمل. الفرق كان في طريقة القبول.
- acceptRide.php يحلّ التوكن من القاعدة دائماً ولا يثق بالعميل (أصحّ أمنياً).
- market_new_ride كان لا يحمل passengerId ولا الإحداثيات فتصل "null"؛
أُضيفت بلا أي PII لأن الحمولة تُبَثّ لكل سائق قريب لا للفائز فقط.
- send_fcm.php: مهلة على OAuth (كان يعلّق حتى مهلة PHP فتُسقط الرسالة
بصمت)، توحيد ding→default لأندرويد، وحقن title/body/tone في data
مطابقةً لـ FcmService.
- تطبيق السائق يقرأ title/body من data أولاً مثل الراكب، ولا يعرض فقاعة
فارغة للرسائل الصامتة.
السوكيت والإعدادات:
- forwardLocationToPassengerSocket كان يقرأ lat/lng والحمولة فيها
latitude/longitude ⇒ المسافة تخرج ضخمة والـ throttle معطّل تماماً
فيُعاد التوجيه مع كل نبضة GPS.
- notifyPassengerOnRideServer كان يرجع null بصمت مطلق عند حجب العنوان.
- العنوان الافتراضي لسيرفر الموقع كان nginx/loction_server/driver_socket.php
وهو ديمون Workerman لا يُخدَم عبر nginx ⇒ صار socket_driver:2021.
(LOCATION_API_URL بقي على nginx لأن api_get_nearby.php سكربت عادي.)
- ride_server/passenger_socket.php (النسخة التي يشغّلها Docker) كانت ناقصة
كل كود مواصلاتي الموجود في passenger_server/ ⇒ نُقل مع REDIS_HOST.
- .env.example: ALLOWED_SOCKET_URLS يغطّي أسماء حاويات Docker، وإضافة
PASSENGER_SOCKET_INTERNAL_URL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
vendor/ و composer.lock كانا مستثنيين في .gitignore، فلم يصل أيٌّ منهما
إلى السيرفر. النتيجة أن realpath() في payment_server/v2/main/connect.php:3
ترجع false وينهار كل مسار /v2/main/ بـ Fatal error.
- إزالة قاعدتَي composer.lock من .gitignore (vendor/ يبقى مستثنى)
- الالتزام بملفات القفل لـ payment_server/v2 و ride_server و loction_server
- ترقية firebase/php-jwt في payment_server/v2 من ^6.11 إلى ^7.0 (v7.1.0)
لأن 6.11.x محجوب بتحذير أمني PKSA-y2cr-5h3j-g3ys — لا تغيير في الشيفرة،
الاستخدام محصور في JWT::encode/decode و Key وأصناف الاستثناءات
- تثبيت platform.php=8.2 ليطابق القفل صورة الحاوية لا PHP المحلي
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>