# ذخیره در ویرایشگر راه‌دور و پایش تأخیر ## علت‌های ریشه‌ای ویرایشگر پیشین فایل کاری خود را در پوشهٔ والد مقصد بارگذاری می‌کرد؛ پوشه‌ای که حساب 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 یک فایل کاری پنهان `.tmp` با نام تصادفی 128 بیتی را از طریق SFTP با ایجاد انحصاری (`wx`)، حالت `0600` و مهلت کانال/بارگذاری شصت ثانیه‌ای بارگذاری می‌کند. دفترچه شامل مقصد، وضعیت پایهٔ اصلی و هش مورد انتظار فایل کاری است؛ اطلاعات احراز هویت در آن نیست. 4. **یک اجرای 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 packages کاربر را نادیده می‌گیرد و bytecode نمی‌نویسد. مسیرها و آرگومان‌های JSON در Core برای پوسته نقل‌قول می‌شوند. افزونه‌ها هرگز اجرای دلخواه SSH یا دستگیره‌های SFTP دریافت نمی‌کنند. sandbox، CSP، مجوزهای manifest، راستی‌آزمایی کلید میزبان و راستی‌آزمایی به‌روزرسانی موجود همچنان به کار می‌روند. ## 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()` یک درخواست ثابت اجرای `true` از طریق کلاینت SSH احراز هویت‌شدهٔ موجود است. این مقدار شامل راه‌اندازی کانال، پردازش سرور و تحویل پاسخ است؛ RTT خالص شبکه یا مدت ورود SSH نیست. راهنمای ابزار (tooltip) ترجمه‌شدهٔ نشانگر این تفاوت را توضیح می‌دهد. پایش به‌محض اتصال ترمینال آغاز می‌شود. کاوش‌ها هم‌زمان و ناهم‌زمان با مهلت درخواست/فرایند پنج ثانیه‌ای اجرا می‌شوند. نمونهٔ بعدی دوازده ثانیه پس از کامل شدن نمونهٔ قبلی زمان‌بندی می‌شود تا از هم‌پوشانی جلوگیری شود. قطع اتصال 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 تکرار می‌کند. این گردش کار هنوز از این working tree اجرا نشده است. - اعتبارسنجی محلی روی 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 بالا). این تغییر وابستگی‌ها یا مدل اعتماد به‌روزرسانی برنامه را تغییر نمی‌دهد. کاربر درخواست کرد تغییرات در working tree موجود، اکنون `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` (جدید)