# Penyimpanan editor jarak jauh dan pemantauan latensi ## Akar masalah Editor sebelumnya mengunggah file kerjanya ke direktori induk target, yang sering kali tidak dapat ditulis oleh akun SSH di bawah `/etc` atau `/var`. Penggantian nama melalui SFTP tidak menyediakan jalur sudo yang sah, pemeriksaan konflik terhadap kondisi awal, verifikasi metadata keamanan, atau semantik penimpaan yang andal. Penangan kesalahannya menghapus salinan sementara, sehingga bahan pemulihan ikut terhapus bahkan ketika penyelesaian tidak pasti. Status sesi sebelumnya hanya menampilkan status koneksi. Tidak ada pengukuran ICMP atau respons SSH terautentikasi yang memasok indikator terminal. ## Protokol penyimpanan 1. Helper SSH tetap membuka file reguler dengan satu tautan menggunakan operasi relatif terhadap deskriptor dan `O_NOFOLLOW` untuk setiap komponen jalur. Helper mengambil isi, hash SHA-256, perangkat/inode, UID, GID, seluruh bit izin, waktu modifikasi/perubahan dalam nanodetik, dan semua atribut tambahan yang dapat dibaca. Core memverifikasi hash dan mendekode UTF-8 secara ketat sambil mempertahankan BOM. Setiap dokumen mendapat `editId` buram yang dibatasi pada plugin, sesi terminal, dan identitas servernya. 2. Operasi persiapan tanpa hak istimewa me-resolve direktori home akun terautentikasi melalui `pwd.getpwuid(os.getuid())`, membuat `$HOME/ch-j-sm/` dengan mode `0700`, dan membuat jurnal pemulihan privat yang eksklusif. Home yang tidak aman, ruang kerja berupa tautan simbolis, atau ruang kerja milik akun lain akan ditolak. 3. Core mengunggah file kerja `.tmp` tersembunyi dengan nama acak 128-bit melalui SFTP dengan pembuatan eksklusif (`wx`), mode `0600`, dan batas waktu kanal/unggahan enam puluh detik. Jurnal berisi target, kondisi awal asli, dan hash file kerja yang diharapkan; jurnal tidak berisi kredensial. 4. **Satu eksekusi SSH melakukan seluruh tahap finalisasi**, secara opsional melalui eksekutor sudo yang sudah ada. Helper membuka dan menghitung hash salinan kerja privat dengan aman, lalu mem-flush-nya. Helper mengunci inode asli, memeriksa kondisi awalnya, membuat file tujuan eksklusif di samping target, menulis seluruh isi, menerapkan dan memverifikasi metadata, memeriksa file asli sekali lagi, dan mem-flush file pengganti. 5. `os.replace` di direktori yang sama mengganti target secara atomik. Direktori induk di-flush, lalu jalur akhir, hash, inode, dan metadata diverifikasi. Jalur asli tetap tersedia hingga penggantian. File kerja tidak pernah dipindahkan antarsistem file dan tidak pernah dihapus oleh tahap finalisasi. 6. Setelah Core menerima keberhasilan yang terverifikasi, pembersihan terpisah memeriksa ulang isi akhir dan metadata keamanan sebelum menghapus file kerja dan jurnal. Kegagalan pembersihan tetap melaporkan penyimpanan yang terkonfirmasi dengan `recoveryAvailable: true`. Unggahan yang gagal, sudo yang gagal, konflik, kesalahan metadata, atau penggantian yang gagal mempertahankan file asli dan semua bahan yang sudah disiapkan. Unggahan yang tidak lengkap ditandai demikian dan tidak dapat dipulihkan sebagai dokumen suntingan yang lengkap. Komunikasi SSH yang terputus, status keluar yang hilang, batas waktu habis, dan hasil finalisasi yang tidak terduga dilaporkan sebagai **tidak diketahui**, tidak pernah sebagai tersimpan. Core memblokir penyimpanan berikutnya pada dokumen yang hasilnya tidak pasti; tidak ada percobaan ulang penggantian secara otomatis. ## Metadata, hak istimewa, dan batas keamanan Helper mempertahankan dan memverifikasi UID, GID, dan semua bit izin POSIX, termasuk bit executable, setuid, setgid, dan sticky. Helper mereproduksi atribut tambahan Linux byte demi byte, termasuk ACL POSIX, capabilities, dan label SELinux. Kepemilikan dan isi file ditetapkan sebelum atribut keamanan, karena penulisan/chown dapat menghapus capabilities atau bit izin khusus. Atribut tujuan yang diwarisi tetapi tidak ada pada file asli akan dihapus. Waktu modifikasi mencerminkan penulisan baru dan tidak disalin dari file asli. Atribut yang tidak dapat dibaca atau tidak dapat direpresentasikan akan membatalkan operasi; metadata tidak dibuang secara diam-diam. Tanda tangan IMA/EVM ditolak secara eksplisit karena menyalin tanda tangan integritas yang terikat pada isi/inode akan membuatnya tidak valid. Tautan simbolis di jalur target atau home, file dengan tautan keras, dan file nonreguler ditolak. Tidak ada fallback penimpaan di tempat atau penimpaan SFTP yang lebih lemah. Penyimpanan biasa yang dapat ditulis dijalankan sebagai akun SSH. Penyimpanan terlindungi memerlukan `{ sudo: true }` secara eksplisit; kata sandi sudo bersifat opsional untuk sudo tanpa kata sandi. Kata sandi divalidasi dan hanya diteruskan melalui stdin kanal, tidak pernah di dalam perintah, jurnal, file kerja, logger, atau kesalahan jarak jauh. Sesi SSH root menggunakan eksekutor root langsung yang sudah ada. Otorisasi yang gagal membiarkan file asli dan salinan kerja tetap utuh. Semua penyimpanan yang dijalankan sebagai root (SSH root langsung atau sudo) juga menolak direktori target/leluhurnya yang dimiliki atau dapat ditulis oleh akun lain, atau yang memiliki ACL akses, alih-alih memercayai nama jalur berhak istimewa yang dikendalikan oleh akun lain. Helper tetap berjalan dengan `python3 -I -B`: helper mengabaikan jalur Python pengguna, direktori kerja, dan paket situs pengguna, serta tidak menulis bytecode. Jalur dan argumen JSON dikutip untuk shell di Core. Plugin tidak pernah menerima eksekusi SSH sembarang atau handle SFTP. Sandbox, CSP, izin manifes, verifikasi kunci host, dan verifikasi pembaruan yang ada tetap digunakan. ## Plugin API dan integrasi Monaco yang tersisa **UI Monaco/File Manager yang didistribusikan secara terpisah tidak ada di checkout ini. Integrasi UI-nya, permintaan sudo, penanganan buffer yang belum disimpan, dan terjemahannya tidak dapat diimplementasikan atau diuji di sini.** Sumber plugin yang disertakan adalah plugin Hash & Checksum. Tidak ada sumber File Manager pengganti yang dibuat-buat. Metode yang ada tetap tersedia. Metode/opsi tambahan diekspos oleh preload dalam sandbox dan runtime yang memeriksa izin: ```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. ``` Metode pemulihan menerima argumen terakhir opsional `{ sudo: true, sudoPassword }` saat pemeriksaan target memerlukan peningkatan hak. Metode ini tetap mempertahankan ruang kerja pengguna terautentikasi bahkan saat dijalankan melalui sudo. Jurnal tetap ada setelah aplikasi dimulai ulang dan SSH tersambung kembali; pemulihan tidak pernah mengulangi finalisasi secara otomatis. Tinjau buffer yang dipulihkan terhadap target yang baru dibuka sebelum menyimpannya secara eksplisit. `files.read` melindungi pembacaan, pemeriksaan, dan pelepasan handle. `files.write` melindungi penyimpanan dan pembersihan pemulihan. `writeText` tetap menolak saat gagal untuk plugin yang sudah ada; `saveText` mengembalikan amplop terstruktur `{ ok, value/error }` karena Electron membuang properti kustom pada objek Error yang ditolak. `writeText` lama dengan tiga argumen dapat menggunakan kondisi awal dari satu dokumen yang terbuka. Beberapa dokumen aktif untuk jalur yang sama memerlukan `editId`; panggilan lama yang ambigu gagal dengan aman alih-alih memilih kondisi awal dokumen lain. Plugin sebaiknya melepaskan handle yang tidak digunakan. UI eksternal harus langsung menampilkan status penyimpanan, mempertahankan buffer sebagai belum disimpan saat gagal, menawarkan sudo hanya setelah kegagalan izin/autentikasi, menampilkan konflik tanpa menimpa, dan menawarkan pemulihan setelah koneksi terputus atau hasil yang tidak diketahui. UI harus menerjemahkan status ini ke dalam bahasa Ceko, Jerman, dan Inggris. Teks status yang disarankan: | Status | Ceko | Jerman | Inggris | | --- | --- | --- | --- | | Menyimpan | Ukládání… | Wird gespeichert… | Saving… | | Terkonfirmasi | Uloženo | Gespeichert | Saved | | Gagal | Uložení selhalo | Speichern fehlgeschlagen | Save failed | | Sudo diperlukan | Vyžadováno oprávnění sudo | sudo-Autorisierung erforderlich | Sudo authorization required | | Konflik | Původní soubor byl změněn | Originaldatei wurde geändert | Original file changed | | Terputus | Spojení bylo přerušeno | Verbindung unterbrochen | Connection lost | | Tidak diketahui | Výsledek uložení není znám | Speicherergebnis unbekannt | Save result unknown | | Pemulihan | Obnova je dostupná | Wiederherstellung verfügbar | Recovery available | ## Definisi latensi dan siklus hidup **Ping** adalah latensi yang dilaporkan oleh balasan ICMP echo sebenarnya di sisi klien ke IP hasil resolve yang digunakan SSH. Core memanggil executable ping platform secara asinkron dengan array argumen tetap, tanpa shell dan tanpa persyaratan menjalankan sebagai administrator. Argumen untuk Linux, macOS (termasuk `ping6`), dan Windows dipilih secara terpisah. ICMP yang diblokir, executable yang tidak ada, batas waktu habis, keluaran tidak valid, atau balasan Windows yang hanya berupa batas atas seperti `<1ms` menghasilkan nilai tidak tersedia, bukan angka yang dikarang. **SSH RTT** adalah durasi monotonik `performance.now()` dari permintaan exec `true` tetap melalui klien SSH terautentikasi yang sudah ada. Nilai ini mencakup penyiapan kanal, pemrosesan server, dan pengiriman respons; nilai ini bukan RTT jaringan murni atau durasi login SSH. Tooltip indikator yang diterjemahkan menjelaskan perbedaan ini. Pemantauan dimulai segera setelah terminal tersambung. Probe berjalan bersamaan dan asinkron, dengan batas waktu permintaan/proses lima detik. Sampel berikutnya dijadwalkan dua belas detik setelah sampel sebelumnya selesai, sehingga tidak tumpang tindih. Pemutusan koneksi membatalkan ICMP, menghapus timer, membatalkan pekerjaan SSH yang tertunda, dan mengosongkan nilai. Pemeriksaan identitas rekaman mencegah koneksi sebelumnya menerbitkan nilai setelah tersambung kembali. Kegagalan ICMP tidak pernah mengubah status koneksi SSH. Latensi dikirim di dalam payload IPC event `state` / `ssh:state` yang sudah ada: ```js latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" } ``` Angka yang tidak tersedia bernilai `null`. Indikator terminal yang ringkas menggunakan milidetik, teks tidak tersedia yang dilokalkan, dan label Ceko/Jerman/Inggris. Indikator disembunyikan saat koneksi terputus. Tidak diperlukan kemampuan eksekusi renderer atau permintaan IPC tambahan. ## Validasi dan batasan operasional - Jalankan dari `app/`: `npm ci`, lalu `npm test`. - Hasil lokal akhir: **179 pengujian; 163 lulus, 0 gagal, 16 dilewati**. Semua yang dilewati adalah kasus sistem file Linux pada host macOS ini. `npm ci`, kompilasi sintaksis Python, dan `git diff --check` berhasil diselesaikan. - Pengujian mencakup penyimpanan/pemulihan Core, protokol sudo dan penyamaran kata sandi, pengutipan shell POSIX sungguhan untuk jalur berbahaya, konflik, unggahan, pembersihan yang gagal, hasil yang tidak pasti, dokumen bersamaan, izin IPC, dan siklus hidup latensi. - Pengujian sistem file Linux menjalankan titik masuk Python yang dikirim, menggunakan pencarian home akun uji privat: kepemilikan/mode/xattrs, ACL POSIX, tautan simbolis/tautan keras, FIFO, kegagalan unggahan dan metadata, kegagalan penggantian, race pada target, hasil yang tidak diketahui setelah penggantian, dan pemulihan. Penyiapan lintas sistem file menggunakan `/dev/shm` dan sistem file lain jika tersedia. Pengujian ini dilewati pada host non-Linux; fasilitas ACL/sistem file terpisah yang tidak ada dilewati secara eksplisit. - Job GitHub Actions Linux yang ditambahkan menjalankan seluruh rangkaian pengujian dan mengulangi kasus sistem file sebagai root. Workflow ini belum dijalankan dari working tree ini. - Validasi lokal dilakukan di macOS. Sintaksis sumber Python diperiksa dengan executable Python dari Command Line Tools secara langsung. Pemeriksaan asap ICMP loopback macOS sungguhan menghasilkan `0.071 ms`. Tidak ada server SSH jarak jauh aktif, runtime Windows, runtime Linux, penerapan SELinux, atau UI Monaco eksternal yang diuji secara lokal. - Penyuntingan teks jarak jauh kini memerlukan Linux dan Python 3 dengan dukungan sistem file/xattr relatif terhadap deskriptor. Interpreter yang tidak ada akan gagal secara eksplisit. Core ini sengaja menolak metadata, tautan, dan konfigurasi direktori yang tidak aman. - Penguncian bersifat anjuran: proses yang tidak bekerja sama masih dapat berlomba dalam interval sempit antara pemeriksaan kondisi awal terakhir dan penggantian nama atomik. Linux tidak menyediakan operasi umum bandingkan-hash-isi-dan-ganti yang atomik. Pemeriksaan mendeteksi perubahan sebelum interval tersebut; salinan kerja yang dipertahankan mendukung pemulihan sesudahnya. - Data pemulihan tetap berada di akun jarak jauh hingga pembersihan yang terkonfirmasi. Jurnal yang tidak lengkap, berkonflik, dan tidak dapat diperiksa tidak pernah dibuang secara diam-diam. Tidak ada kedaluwarsa otomatis untuk file pemulihan dan tidak ada opsi penimpaan paksa. - `npm ci` melaporkan 17 temuan audit dependensi (9 sedang, 8 tinggi). Perubahan ini tidak mengubah dependensi atau model kepercayaan pembaruan aplikasi. Pengguna meminta agar perubahan dipertahankan di working tree yang ada, saat ini `feature/hash-checksum-plugin`; tidak ada peralihan branch atau pembersihan yang tidak terkait. ## File yang diubah untuk tugas ini - `src/main/files/remoteFileService.js` - `src/main/files/remoteEditor.py` (baru) - `src/main/sessions/sessionManager.js` - `src/main/sessions/latencyMonitor.js` (baru) - `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` (baru) - `test/remoteEditorProtocol.test.js` (baru) - `test/remoteEditorLinux.test.js` (baru) - `test/fixtures/remoteEditorLinux.py` (baru) - `test/latencyMonitor.test.js` (baru) - `README.md` - `docs/remote-editor-latency.md` (file ini) - `../.github/workflows/remote-editor-tests.yml` (baru)