# रिमोट एडिटर में सहेजना और लेटेंसी मॉनिटरिंग ## मूल कारण पिछला एडिटर अपनी कार्य-फ़ाइल को लक्ष्य की पैरेंट डायरेक्टरी में अपलोड करता था, जिसमें `/etc` या `/var` के अंदर SSH खाता अक्सर लिख नहीं सकता। SFTP rename में न तो अधिकृत sudo पथ था, न बेसलाइन टकराव जाँच, न सुरक्षा मेटाडेटा सत्यापन और न ही भरोसेमंद ओवरराइट व्यवहार। इसका त्रुटि हैंडलर अस्थायी प्रति हटा देता था, जिससे कार्य पूरा होना अनिश्चित होने पर भी रिकवरी सामग्री मिट जाती थी। सेशन स्थिति पहले केवल कनेक्शन की स्थिति दिखाती थी। टर्मिनल संकेतक को डेटा देने वाला कोई ICMP या प्रमाणित SSH प्रतिक्रिया माप नहीं था। ## सहेजने का प्रोटोकॉल 1. एक तय SSH हेल्पर डिस्क्रिप्टर-आधारित सापेक्ष कार्यों और हर पथ घटक के लिए `O_NOFOLLOW` का उपयोग करके एक सामान्य, एकल-लिंक वाली फ़ाइल खोलता है। वह सामग्री, SHA-256 हैश, डिवाइस/inode, UID, GID, सभी अनुमति बिट, नैनोसेकंड स्तर के संशोधन/परिवर्तन समय और सभी पढ़ने योग्य विस्तारित एट्रिब्यूट दर्ज करता है। Core हैश सत्यापित करता है और BOM बनाए रखते हुए सख़्त UTF-8 डिकोड करता है। हर दस्तावेज़ को एक अपारदर्शी `editId` मिलती है, जो उसके प्लगइन, टर्मिनल सेशन और सर्वर पहचान तक सीमित होती है। 2. एक बिना विशेषाधिकार वाला तैयारी चरण `pwd.getpwuid(os.getuid())` से प्रमाणित खाते की होम डायरेक्टरी पता करता है, `0700` मोड में `$HOME/ch-j-sm/` बनाता है और एक निजी, अनन्य रिकवरी जर्नल बनाता है। असुरक्षित होम, सिमलिंक वर्कस्पेस या किसी दूसरे खाते के स्वामित्व वाला वर्कस्पेस अस्वीकार कर दिया जाता है। 3. Core अनन्य निर्माण (`wx`), मोड `0600` और साठ सेकंड के चैनल/अपलोड टाइमआउट के साथ SFTP से एक छिपी, यादृच्छिक 128-बिट नाम वाली `.tmp` कार्य-फ़ाइल अपलोड करता है। जर्नल में लक्ष्य, मूल बेसलाइन और अपेक्षित कार्य-फ़ाइल हैश होता है; इसमें कोई क्रेडेंशियल नहीं होता। 4. **एक ही SSH exec पूरा अंतिम चरण पूरा करता है**, वैकल्पिक रूप से मौजूदा sudo एक्ज़िक्यूटर के ज़रिए। हेल्पर निजी कार्य-प्रति को सुरक्षित रूप से खोलकर उसका हैश निकालता है और उसे डिस्क पर फ़्लश करता है। वह मूल inode को लॉक करता है, उसकी बेसलाइन जाँचता है, लक्ष्य के बगल में एक अनन्य गंतव्य फ़ाइल बनाता है, पूरी सामग्री लिखता है, मेटाडेटा लागू और सत्यापित करता है, मूल फ़ाइल को फिर से जाँचता है और बदली जाने वाली फ़ाइल को फ़्लश करता है। 5. उसी डायरेक्टरी में `os.replace` लक्ष्य को परमाणु रूप से बदल देता है। पैरेंट डायरेक्टरी फ़्लश की जाती है और अंतिम पथ, हैश, inode तथा मेटाडेटा सत्यापित किए जाते हैं। बदलाव होने तक मूल पथ उपलब्ध रहता है। कार्य-फ़ाइल कभी भी फ़ाइल सिस्टम के पार नहीं ले जाई जाती और अंतिम चरण में कभी हटाई नहीं जाती। 6. Core को सत्यापित सफलता मिलने के बाद, एक अलग सफ़ाई चरण कार्य-फ़ाइल और जर्नल हटाने से पहले अंतिम सामग्री और सुरक्षा मेटाडेटा को फिर से जाँचता है। सफ़ाई विफल होने पर भी `recoveryAvailable: true` के साथ पुष्ट सहेजने की रिपोर्ट दी जाती है। अपलोड विफल होने, sudo विफल होने, टकराव, मेटाडेटा त्रुटि या बदलाव विफल होने पर मूल फ़ाइल और सारी तैयार सामग्री सुरक्षित रहती है। अधूरे अपलोड को अधूरा ही चिह्नित किया जाता है और उन्हें पूरे संपादित दस्तावेज़ के रूप में बहाल नहीं किया जा सकता। SSH संचार टूटने, एग्ज़िट स्थिति न मिलने, टाइमआउट और अप्रत्याशित अंतिम परिणाम को **अज्ञात** बताया जाता है, कभी "सहेजा गया" नहीं। अनिश्चित दस्तावेज़ पर Core अगला सहेजना रोक देता है; बदलाव का कोई स्वचालित पुनःप्रयास नहीं होता। ## मेटाडेटा, विशेषाधिकार और सुरक्षा सीमाएँ हेल्पर UID, GID और सभी POSIX अनुमति बिट सुरक्षित रखता और सत्यापित करता है, जिनमें executable, setuid, setgid और sticky बिट शामिल हैं। वह Linux विस्तारित एट्रिब्यूट को बाइट-दर-बाइट दोबारा बनाता है, जिनमें POSIX ACL, capabilities और SELinux लेबल शामिल हैं। स्वामित्व और फ़ाइल सामग्री सुरक्षा एट्रिब्यूट से पहले सेट की जाती है, क्योंकि लिखने/chown से capabilities या विशेष अनुमति बिट साफ़ हो सकते हैं। गंतव्य पर विरासत में मिले ऐसे एट्रिब्यूट जो मूल में नहीं हैं, हटा दिए जाते हैं। संशोधन समय नए लेखन को दर्शाता है और मूल से कॉपी नहीं किया जाता। न पढ़ा जा सकने या न दर्शाया जा सकने वाला एट्रिब्यूट कार्य को रोक देता है; मेटाडेटा चुपचाप नहीं छोड़ा जाता। IMA/EVM हस्ताक्षर स्पष्ट रूप से अस्वीकार किए जाते हैं, क्योंकि सामग्री/inode से बंधे अखंडता हस्ताक्षर कॉपी करने से वे अमान्य हो जाएँगे। लक्ष्य या होम पथ में सिमलिंक, हार्ड-लिंक वाली फ़ाइलें और असामान्य फ़ाइलें अस्वीकार की जाती हैं। कोई इन-प्लेस या कमज़ोर SFTP ओवरराइट विकल्प उपयोग नहीं होता। सामान्य लिखने योग्य सहेजना SSH खाते के रूप में चलता है। सुरक्षित सहेजने के लिए स्पष्ट `{ sudo: true }` ज़रूरी है; बिना पासवर्ड वाले sudo के लिए sudo पासवर्ड वैकल्पिक है। पासवर्ड सत्यापित करके केवल चैनल stdin पर भेजे जाते हैं, कभी कमांड, जर्नल, कार्य-फ़ाइल, लॉगर या रिमोट त्रुटि में नहीं। root SSH सेशन मौजूदा सीधे root एक्ज़िक्यूटर का उपयोग करते हैं। प्राधिकरण विफल होने पर मूल और कार्य-प्रति दोनों अप्रभावित रहती हैं। root के रूप में चलने वाले सभी सहेजने (सीधा root SSH या sudo) उन लक्ष्य डायरेक्टरी/पूर्वज डायरेक्टरी को भी अस्वीकार करते हैं जो दूसरे खातों के स्वामित्व में हों, उनके द्वारा लिखी जा सकती हों या जिन पर access ACL हो, बजाय किसी दूसरे खाते द्वारा नियंत्रित विशेषाधिकार वाले पथ नाम पर भरोसा करने के। तय हेल्पर `python3 -I -B` के साथ चलता है: यह उपयोगकर्ता के Python पथ, कार्य डायरेक्टरी और उपयोगकर्ता site-packages को अनदेखा करता है और कोई बाइटकोड नहीं लिखता। पथ और JSON आर्ग्युमेंट Core में शेल-कोट किए जाते हैं। प्लगइन को कभी भी मनमाना SSH exec या SFTP हैंडल नहीं मिलता। मौजूदा सैंडबॉक्स, CSP, मैनिफ़ेस्ट अनुमतियाँ, होस्ट-कुंजी सत्यापन और अपडेट सत्यापन लागू रहते हैं। ## Plugin API और बचा हुआ Monaco एकीकरण **अलग से वितरित Monaco/File Manager UI इस चेकआउट में मौजूद नहीं है। इसका UI एकीकरण, sudo संकेत, बिना सहेजे बफ़र का प्रबंधन और अनुवाद यहाँ लागू या परीक्षित नहीं किए जा सके।** बंडल प्लगइन सोर्स Hash & Checksum प्लगइन है। कोई वैकल्पिक File Manager सोर्स गढ़ा नहीं गया। मौजूदा मेथड उपलब्ध रहते हैं। अतिरिक्त मेथड/विकल्प सैंडबॉक्स वाले preload और अनुमति-जाँच वाले रनटाइम से उपलब्ध कराए जाते हैं: ```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 reject किए गए Error ऑब्जेक्ट से कस्टम प्रॉपर्टी हटा देता है। पुराना तीन-आर्ग्युमेंट वाला `writeText` एक ही खुले दस्तावेज़ की बेसलाइन का उपयोग कर सकता है। एक ही पथ के कई सक्रिय दस्तावेज़ों के लिए `editId` ज़रूरी है; अस्पष्ट पुरानी कॉल किसी दूसरे दस्तावेज़ की बेसलाइन चुनने के बजाय सुरक्षित रूप से विफल होती हैं। प्लगइन को अनुपयोगी हैंडल छोड़ देने चाहिए। बाहरी UI को सहेजना तुरंत दिखाना चाहिए, विफलता पर बफ़र को बिना सहेजा रखना चाहिए, केवल अनुमति/प्रमाणीकरण विफलता के बाद 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** वह लेटेंसी है जो SSH द्वारा उपयोग किए गए रिज़ॉल्व्ड IP को भेजे गए ICMP echo के क्लाइंट-साइड पर वास्तव में मिले जवाब से रिपोर्ट होती है। Core प्लेटफ़ॉर्म के ping एक्ज़िक्यूटेबल को एक तय आर्ग्युमेंट ऐरे के साथ असिंक्रोनस रूप से चलाता है, बिना शेल के और बिना व्यवस्थापक के रूप में शुरू करने की आवश्यकता के। Linux, macOS (`ping6` सहित) और Windows के आर्ग्युमेंट अलग-अलग चुने जाते हैं। ब्लॉक किया गया ICMP, न मिला एक्ज़िक्यूटेबल, टाइमआउट, अमान्य आउटपुट या `<1ms` जैसा केवल ऊपरी सीमा वाला Windows जवाब, ये सब "उपलब्ध नहीं" मान देते हैं, कोई गढ़ी हुई संख्या नहीं। **SSH RTT** मौजूदा प्रमाणित SSH क्लाइंट पर एक तय `true` exec अनुरोध की मोनोटोनिक `performance.now()` अवधि है। इसमें चैनल सेटअप, सर्वर प्रसंस्करण और जवाब की डिलीवरी शामिल है; यह न तो शुद्ध नेटवर्क RTT है और न ही SSH लॉगिन की अवधि। अनूदित संकेतक टूलटिप यह अंतर समझाता है। टर्मिनल कनेक्ट होते ही मॉनिटरिंग शुरू हो जाती है। प्रोब एक साथ और असिंक्रोनस रूप से चलते हैं, पाँच सेकंड के अनुरोध/प्रोसेस टाइमआउट के साथ। ओवरलैप रोकने के लिए अगला नमूना पिछले नमूने के पूरा होने के बारह सेकंड बाद निर्धारित होता है। डिस्कनेक्ट ICMP को रोकता है, टाइमर साफ़ करता है, लंबित SSH कार्य रद्द करता है और मान साफ़ करता है। रिकॉर्ड पहचान जाँच पिछले कनेक्शन को पुनःकनेक्शन के बाद मान प्रकाशित करने से रोकती है। ICMP की विफलता SSH कनेक्शन की स्थिति कभी नहीं बदलती। लेटेंसी मौजूदा `state` इवेंट / `ssh:state` IPC पेलोड के अंदर भेजी जाती है: ```js latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" } ``` अनुपलब्ध संख्याएँ `null` होती हैं। कॉम्पैक्ट टर्मिनल संकेतक मिलीसेकंड, स्थानीयकृत "उपलब्ध नहीं" पाठ और चेक/जर्मन/अंग्रेज़ी लेबल का उपयोग करता है। डिस्कनेक्ट होने पर यह छिप जाता है। किसी अतिरिक्त रेंडरर निष्पादन क्षमता या IPC अनुरोध की ज़रूरत नहीं पड़ी। ## सत्यापन और परिचालन सीमाएँ - `app/` से चलाएँ: `npm ci`, फिर `npm test`। - अंतिम स्थानीय परिणाम: **179 परीक्षण; 163 सफल, 0 विफल, 16 छोड़े गए**। सभी छोड़े गए मामले इस macOS होस्ट पर Linux फ़ाइल सिस्टम वाले हैं। `npm ci`, Python सिंटैक्स कंपाइलेशन और `git diff --check` सफलतापूर्वक पूरे हुए। - परीक्षण Core सहेजने/रिकवरी, sudo प्रोटोकॉल और पासवर्ड छिपाने, हानिकारक पथों की वास्तविक POSIX शेल कोटिंग, टकराव, अपलोड, विफल सफ़ाई, अनिश्चित परिणाम, समवर्ती दस्तावेज़, IPC अनुमतियाँ और लेटेंसी जीवनचक्र को कवर करते हैं। - Linux फ़ाइल सिस्टम परीक्षण निजी टेस्ट खाते के होम लुकअप के साथ जारी किए गए Python एंट्री पॉइंट को चलाते हैं: स्वामित्व/मोड/xattr, POSIX ACL, सिमलिंक/हार्डलिंक, FIFO, अपलोड और मेटाडेटा विफलता, बदलाव विफलता, लक्ष्य पर रेस, बदलाव के बाद अज्ञात परिणाम और रिकवरी। क्रॉस-फ़ाइल सिस्टम स्टेजिंग उपलब्ध होने पर `/dev/shm` और किसी अन्य फ़ाइल सिस्टम का उपयोग करती है। ये परीक्षण गैर-Linux होस्ट पर छोड़ दिए जाते हैं; ACL/अलग फ़ाइल सिस्टम सुविधाएँ न होने पर उन्हें स्पष्ट रूप से छोड़ा जाता है। - जोड़ा गया Linux GitHub Actions जॉब पूरा सूट चलाता है और फ़ाइल सिस्टम मामलों को root के रूप में दोहराता है। यह वर्कफ़्लो इस वर्किंग ट्री से अभी नहीं चलाया गया है। - स्थानीय सत्यापन macOS पर हुआ। Python सोर्स सिंटैक्स सीधे Command Line Tools के Python एक्ज़िक्यूटेबल से जाँचा गया। एक वास्तविक macOS लूपबैक ICMP स्मोक जाँच ने `0.071 ms` लौटाया। कोई लाइव रिमोट SSH सर्वर, Windows रनटाइम, Linux रनटाइम, SELinux परिनियोजन या बाहरी Monaco UI स्थानीय रूप से परीक्षित नहीं किया गया। - रिमोट टेक्स्ट संपादन के लिए अब Linux और डिस्क्रिप्टर-आधारित सापेक्ष फ़ाइल सिस्टम/xattr समर्थन वाला Python 3 ज़रूरी है। इंटरप्रेटर न होने पर यह स्पष्ट रूप से विफल होता है। यह Core असुरक्षित मेटाडेटा, लिंक और डायरेक्टरी कॉन्फ़िगरेशन को जानबूझकर अस्वीकार करता है। - लॉकिंग केवल सलाहकारी है: अंतिम बेसलाइन जाँच और परमाणु rename के बीच के छोटे अंतराल में असहयोगी प्रोसेस अब भी रेस कर सकते हैं। 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` (नई)