# الحفظ في المحرر البعيد ومراقبة زمن الاستجابة ## الأسباب الجذرية كان المحرر السابق يرفع ملف العمل إلى الدليل الأب للهدف، وهو دليل لا يستطيع حساب SSH الكتابة فيه غالبًا ضمن `/etc` أو `/var`. ولم تكن إعادة التسمية عبر SFTP توفر مسار sudo مُصرّحًا به، ولا فحصًا للتعارض مقابل الحالة الأساسية، ولا تحققًا من البيانات الوصفية الأمنية، ولا دلالات استبدال موثوقة. وكان معالج الأخطاء يحذف النسخة المؤقتة، فيزيل مادة الاسترداد حتى عندما يكون اكتمال العملية غير مؤكد. كانت حالة الجلسة سابقًا تعرض حالة الاتصال فقط. ولم يكن هناك قياس ICMP أو قياس لاستجابة SSH موثّقة يغذّي مؤشر الطرفية. ## بروتوكول الحفظ 1. يفتح مساعد SSH ثابت ملفًا عاديًا ذا رابط واحد باستخدام عمليات نسبية إلى الواصف و`O_NOFOLLOW` لكل مكوّن من مكوّنات المسار. ويلتقط المحتوى وتجزئة SHA-256 والجهاز/inode وUID وGID وبتات الأذونات الكاملة وأوقات التعديل/التغيير بدقة النانوثانية وجميع السمات الممتدة القابلة للقراءة. يتحقق Core من التجزئة ويفك ترميز UTF-8 بصرامة مع الحفاظ على BOM. ويحصل كل مستند على `editId` معتم مقيّد بالإضافة وجلسة الطرفية وهوية الخادم. 2. تحلّ عملية تحضير دون امتيازات الدليل المنزلي للحساب الموثّق عبر `pwd.getpwuid(os.getuid())`، وتنشئ `$HOME/ch-j-sm/` بالوضع `0700`، وتنشئ سجل استرداد (journal) خاصًا وحصريًا. ويُرفض الدليل المنزلي غير الآمن، أو مساحة العمل التي هي رابط رمزي، أو مساحة العمل المملوكة لحساب آخر. 3. يرفع Core عبر SFTP ملف عمل مخفيًا بامتداد `.tmp` واسم عشوائي بطول 128 بت، مع إنشاء حصري (`wx`) والوضع `0600` ومهلة قناة/رفع مدتها ستون ثانية. يحتوي السجل على الهدف والحالة الأساسية الأصلية والتجزئة المتوقعة لملف العمل؛ ولا يحتوي على بيانات اعتماد. 4. **تنفّذ عملية exec واحدة عبر SSH مرحلة الإنهاء بأكملها**، اختياريًا عبر منفّذ sudo القائم. يفتح المساعد نسخة العمل الخاصة ويحسب تجزئتها بأمان ثم يدفعها إلى القرص (flush). ويقفل inode الأصلي، ويفحص حالته الأساسية، وينشئ ملف وجهة حصريًا بجوار الهدف، ويكتب المحتوى كاملًا، ويطبّق البيانات الوصفية ويتحقق منها، ويفحص الأصل مرة أخرى، ويدفع البديل إلى القرص. 5. يستبدل `os.replace` في الدليل نفسه الهدف بشكل ذري. ثم يُدفع الدليل الأب إلى القرص ويُتحقق من المسار النهائي والتجزئة وinode والبيانات الوصفية. ويبقى المسار الأصلي متاحًا حتى الاستبدال. ولا يُنقل ملف العمل أبدًا عبر أنظمة الملفات، ولا تزيله مرحلة الإنهاء أبدًا. 6. بعد أن يتلقى Core نجاحًا متحققًا منه، تعيد عملية تنظيف منفصلة فحص المحتوى النهائي والبيانات الوصفية الأمنية قبل إزالة ملف العمل والسجل. وحتى إذا فشل التنظيف يُبلَّغ عن حفظ مؤكد مع `recoveryAvailable: true`. يحافظ فشل الرفع أو فشل sudo أو التعارض أو خطأ البيانات الوصفية أو فشل الاستبدال على الملف الأصلي وعلى أي مادة مُجهّزة. وتُميَّز عمليات الرفع غير المكتملة بذلك ولا يمكن استردادها كمستند معدّل كامل. ويُبلَّغ عن فقدان اتصال SSH وغياب حالة الخروج وانتهاء المهلات ونتائج الإنهاء غير المتوقعة بأنها **غير معروفة**، ولا يُبلَّغ عنها أبدًا بأنها محفوظة. يحظر Core أي حفظ آخر لمستند نتيجته غير مؤكدة؛ ولا توجد إعادة محاولة تلقائية للاستبدال. ## البيانات الوصفية والامتيازات وحدود السلامة يحافظ المساعد على UID وGID وجميع بتات أذونات POSIX ويتحقق منها، بما في ذلك بتات التنفيذ وsetuid وsetgid وsticky. ويعيد إنتاج السمات الممتدة في Linux بايتًا بايتًا، بما في ذلك POSIX ACL وcapabilities وتسميات SELinux. تُضبط الملكية ومحتوى الملف قبل السمات الأمنية، لأن الكتابة/chown قد تمحو capabilities أو بتات الأذونات الخاصة. وتُزال السمات الموروثة في الوجهة غير الموجودة في الأصل. ويعكس وقت التعديل الكتابة الجديدة ولا يُنسخ من الأصل. تؤدي السمة غير القابلة للقراءة أو غير القابلة للتمثيل إلى إلغاء العملية؛ ولا تُسقط البيانات الوصفية بصمت. وتُرفض توقيعات IMA/EVM صراحةً لأن نسخ توقيعات السلامة المرتبطة بالمحتوى/inode سيبطلها. وتُرفض الروابط الرمزية في مسار الهدف أو الدليل المنزلي، والملفات ذات الروابط الصلبة، والملفات غير العادية. ولا يُستخدم أي بديل للاستبدال في المكان أو استبدال SFTP أضعف. تعمل عمليات الحفظ العادية القابلة للكتابة باسم حساب SSH. وتتطلب عمليات الحفظ المحمية `{ sudo: true }` صراحةً؛ وكلمة مرور sudo اختيارية عند استخدام sudo دون كلمة مرور. تُتحقق كلمات المرور وتُمرَّر فقط عبر stdin الخاص بالقناة، ولا تكون أبدًا داخل الأمر أو السجل أو ملف العمل أو أداة التسجيل أو الخطأ البعيد. وتستخدم جلسات SSH بحساب root منفّذ root المباشر القائم. ويترك فشل التفويض الأصل ونسخة العمل سليمين. كما ترفض جميع عمليات الحفظ المنفّذة بصلاحيات root (عبر SSH مباشر بحساب root أو عبر sudo) الأدلة المستهدفة/الأسلاف المملوكة لحسابات أخرى أو القابلة للكتابة منها أو التي تحمل ACL للوصول، بدلًا من الثقة بمسار ذي امتيازات يتحكم فيه حساب آخر. يعمل المساعد الثابت بالخيارين `python3 -I -B`: فيتجاهل مسارات Python الخاصة بالمستخدم ودليل العمل وحزم site الخاصة بالمستخدم، ولا يكتب bytecode. تُقتبس المسارات ووسائط JSON للصدفة داخل Core. ولا تتلقى الإضافات أبدًا تنفيذ SSH عشوائيًا أو مقابض SFTP. وتظل sandbox وCSP وأذونات البيان والتحقق من مفتاح المضيف والتحقق من التحديثات قيد الاستخدام. ## Plugin API والتكامل المتبقي مع Monaco **واجهة Monaco/File Manager الموزعة بشكل منفصل غير موجودة في هذه النسخة (checkout). ولم يكن ممكنًا هنا تنفيذ تكامل واجهتها ومطالبة sudo ومعالجة المخازن غير المحفوظة وترجماتها أو اختبارها.** مصدر الإضافة المضمّن هو إضافة Hash & Checksum. ولم يُخترع أي مصدر بديل لـ File Manager. تبقى الطرق القائمة متاحة. وتُتاح طرق/خيارات إضافية عبر preload داخل sandbox وبيئة تشغيل تتحقق من الأذونات: ```js const opened = await chjPlugin.files.readText(sessionId, path); // For a protected read, explicitly pass { sudo: true, sudoPassword }. const result = await chjPlugin.files.saveText(sessionId, path, editor.getValue(), { editId: opened.editId, sudo: true, // Include only after the user authorizes elevation. sudoPassword // Omit for passwordless sudo; never persist this value. }); if (result.ok && result.value.status === "saved") { // Mark this exact submitted editor version as saved. Later edits stay dirty. } else { // Keep the Monaco model dirty. Translate result.error.code/status. // result.error.recoveryId/recoveryPath identify retained working material. } await chjPlugin.files.closeText(opened.editId); const recovery = await chjPlugin.files.listRecovery(sessionId); const copy = await chjPlugin.files.readRecovery(sessionId, recoveryId); // Review copy.status and copy.text; open a fresh document before another save. await chjPlugin.files.cleanupRecovery(sessionId, recoveryId); // Cleanup rejects anything whose target is not currently confirmed. ``` تقبل طرق الاسترداد وسيطًا أخيرًا اختياريًا `{ sudo: true, sudoPassword }` عندما يتطلب فحص الهدف رفع الصلاحيات. وتحتفظ بمساحة عمل المستخدم الموثّق حتى عند التنفيذ عبر sudo. تبقى السجلات بعد إعادة تشغيل التطبيق وإعادة اتصال SSH؛ ولا يكرر الاسترداد مرحلة الإنهاء تلقائيًا أبدًا. راجع المخزن المسترد مقابل هدف مفتوح حديثًا قبل حفظه صراحةً. يحمي `files.read` عمليات القراءة والفحص وتحرير المقابض. ويحمي `files.write` عمليات الحفظ وتنظيف الاسترداد. ولا يزال `writeText` يرفض (reject) عند الفشل للإضافات القائمة؛ أما `saveText` فيعيد مغلّفًا منظّمًا `{ ok, value/error }` لأن Electron يُسقط الخصائص المخصصة من كائنات Error المرفوضة. ويمكن لـ `writeText` القديم بثلاث وسائط استخدام الحالة الأساسية لمستند واحد مفتوح. وتتطلب المستندات الحية المتعددة للمسار نفسه `editId`؛ وتفشل الاستدعاءات القديمة الملتبسة بأمان بدلًا من اختيار الحالة الأساسية لمستند آخر. وينبغي للإضافات تحرير المقابض غير المستخدمة. يجب على الواجهة الخارجية عرض حالة الحفظ فورًا، وإبقاء المخازن غير محفوظة عند الفشل، وعرض sudo فقط بعد أعطال الأذونات/المصادقة، وإظهار التعارضات دون استبدال، وعرض الاسترداد بعد فقدان الاتصال أو عند نتيجة غير معروفة. ويجب عليها ترجمة هذه الحالات إلى التشيكية والألمانية والإنجليزية. نصوص الحالات المقترحة: | الحالة | التشيكية | الألمانية | الإنجليزية | | --- | --- | --- | --- | | جارٍ الحفظ | Ukládání… | Wird gespeichert… | Saving… | | مؤكَّد | Uloženo | Gespeichert | Saved | | فشل | Uložení selhalo | Speichern fehlgeschlagen | Save failed | | يلزم sudo | Vyžadováno oprávnění sudo | sudo-Autorisierung erforderlich | Sudo authorization required | | تعارض | Původní soubor byl změněn | Originaldatei wurde geändert | Original file changed | | انقطع الاتصال | Spojení bylo přerušeno | Verbindung unterbrochen | Connection lost | | غير معروف | Výsledek uložení není znám | Speicherergebnis unbekannt | Save result unknown | | الاسترداد | Obnova je dostupná | Wiederherstellung verfügbar | Recovery available | ## تعريفات زمن الاستجابة ودورة الحياة **Ping** هو زمن الاستجابة الذي يُبلغ عنه رد ICMP echo فعلي من جهة العميل إلى عنوان IP المحلول الذي يستخدمه SSH. يستدعي Core الملف التنفيذي ping الخاص بالمنصة بشكل غير متزامن بمصفوفة وسائط ثابتة، دون صدفة ودون اشتراط البدء بصلاحيات المسؤول. وتُختار وسائط Linux وmacOS (بما في ذلك `ping6`) وWindows كلٌّ على حدة. وينتج عن حظر ICMP أو غياب الملف التنفيذي أو انتهاء المهلة أو المخرجات غير الصالحة أو رد Windows الذي يقتصر على حد أعلى مثل `<1ms` قيمة غير متاحة، لا رقم مختلق. **SSH RTT** هو المدة الرتيبة المقاسة عبر `performance.now()` لطلب exec ثابت `true` عبر عميل SSH الموثّق القائم. ويشمل إعداد القناة ومعالجة الخادم وتسليم الاستجابة؛ وليس RTT شبكيًا خالصًا ولا مدة تسجيل الدخول عبر SSH. ويشرح التلميح المترجم للمؤشر هذا الفرق. تبدأ المراقبة فور اتصال الطرفية. تعمل المجسّات بشكل متزامن وغير متزامن، بمهلة طلب/عملية مدتها خمس ثوانٍ. وتُجدول العيّنة التالية بعد اثنتي عشرة ثانية من اكتمال العيّنة السابقة، تجنبًا للتداخل. يؤدي قطع الاتصال إلى إيقاف ICMP ومسح المؤقتات وإلغاء أعمال SSH المعلّقة ومسح القيم. وتمنع فحوص هوية السجل اتصالًا سابقًا من النشر بعد إعادة الاتصال. ولا يغيّر فشل ICMP حالة اتصال SSH أبدًا. ينتقل زمن الاستجابة داخل حمولة IPC القائمة لحدث `state` / `ssh:state`: ```js latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" } ``` القيم غير المتاحة هي `null`. ويستخدم مؤشر الطرفية المدمج وحدة المللي ثانية ونصًا مترجمًا لعدم التوفر وتسميات بالتشيكية/الألمانية/الإنجليزية. ويختفي عند قطع الاتصال. ولم تكن هناك حاجة إلى قدرة تنفيذ إضافية للـ renderer ولا إلى طلب IPC إضافي. ## التحقق والقيود التشغيلية - شغّل من `app/`: ‏`npm ci` ثم `npm test`. - النتيجة المحلية النهائية: **179 اختبارًا؛ نجح 163، وفشل 0، وتُخطّي 16**. جميع الاختبارات المتخطاة هي حالات نظام ملفات Linux على مضيف macOS هذا. واكتملت `npm ci` وترجمة صياغة Python و`git diff --check` بنجاح. - تغطي الاختبارات حفظ Core واسترداده، وبروتوكول sudo وإخفاء كلمات المرور، والاقتباس الفعلي لصدفة POSIX للمسارات العدائية، والتعارضات، وعمليات الرفع، وفشل التنظيف، والنتائج غير المؤكدة، والمستندات المتزامنة، وأذونات IPC، ودورة حياة قياس زمن الاستجابة. - تشغّل اختبارات نظام ملفات Linux نقطة دخول Python المشحونة، باستخدام بحث خاص عن الدليل المنزلي لحساب الاختبار: الملكية/الوضع/xattrs، وPOSIX ACL، والروابط الرمزية/الصلبة، وFIFO، وأعطال الرفع والبيانات الوصفية، وأعطال الاستبدال، والتسابق على الهدف، والنتائج غير المعروفة بعد الاستبدال، والاسترداد. ويستخدم التحضير عبر أنظمة الملفات `/dev/shm` ونظام ملفات آخر عند توفره. وتُتخطى هذه الاختبارات على المضيفين غير العاملين بـ Linux؛ وتُتخطى صراحةً عند غياب مرافق ACL/أنظمة الملفات المنفصلة. - تشغّل مهمة GitHub Actions المضافة لـ Linux المجموعة الكاملة وتكرر حالات نظام الملفات بصلاحيات root. ولم يُطلق سير العمل هذا من شجرة العمل هذه. - جرى التحقق المحلي على macOS. وفُحصت صياغة مصدر Python مباشرةً بالملف التنفيذي لـ Python من Command Line Tools. وأعاد فحص دخان فعلي لـ ICMP عبر loopback على macOS القيمة `0.071 ms`. ولم يُختبر محليًا أي خادم SSH بعيد حي أو بيئة تشغيل Windows أو بيئة تشغيل Linux أو نشر SELinux أو واجهة Monaco خارجية. - يتطلب تحرير النصوص البعيدة الآن Linux وPython 3 مع دعم نظام الملفات/xattr النسبي إلى الواصف. ويفشل صراحةً عند غياب المفسّر. ويرفض Core هذا عن قصد البيانات الوصفية والروابط وإعدادات الأدلة غير الآمنة. - القفل استشاري: لا تزال العمليات غير المتعاونة قادرة على التسابق في الفاصل الضيق بين آخر فحص للحالة الأساسية وإعادة التسمية الذرية. ولا يوفر Linux عملية عامة ذرية لمقارنة تجزئة المحتوى ثم الاستبدال. تكتشف الفحوص التغييرات قبل ذلك الفاصل؛ وتدعم نسخ العمل المحتفظ بها الاسترداد بعده. - تبقى بيانات الاسترداد على الحساب البعيد حتى تنظيف مؤكد. ولا تُتجاهل السجلات غير المكتملة أو المتعارضة أو غير القابلة للفحص بصمت أبدًا. ولا يوجد انتهاء صلاحية تلقائي لملفات الاسترداد ولا خيار استبدال قسري. - أبلغت `npm ci` عن 17 نتيجة في تدقيق التبعيات (9 متوسطة و8 عالية). ولا يغيّر هذا التعديل التبعيات ولا نموذج الثقة في تحديثات التطبيق. طلب المستخدم الاحتفاظ بالتغييرات في شجرة العمل القائمة، وهي حاليًا `feature/hash-checksum-plugin`؛ ولم يُجرَ أي تبديل للفرع أو تنظيف غير ذي صلة. ## الملفات التي تغيّرت لهذه المهمة - `src/main/files/remoteFileService.js` - `src/main/files/remoteEditor.py` (جديد) - `src/main/sessions/sessionManager.js` - `src/main/sessions/latencyMonitor.js` (جديد) - `src/main/plugins/pluginRuntime.js` - `src/preload/pluginPreload.js` - `src/renderer/app.js` - `src/renderer/index.html` - `src/renderer/styles.css` - `src/renderer/i18n.js` - `test/remoteFileService.test.js` - `test/sessionManager.test.js` - `test/pluginRuntime.test.js` - `test/remoteEditor.test.js` (جديد) - `test/remoteEditorProtocol.test.js` (جديد) - `test/remoteEditorLinux.test.js` (جديد) - `test/fixtures/remoteEditorLinux.py` (جديد) - `test/latencyMonitor.test.js` (جديد) - `README.md` - `docs/remote-editor-latency.md` (هذا الملف) - `../.github/workflows/remote-editor-tests.yml` (جديد)