# Kullanıcı verisi yedekleme ve geri yükleme Araç `users.sqlite` ve veritabanında referansı bulunan `belgeler/` dosyalarını kapsar. İhale/kalem arşivi, kaynak önbellekleri, ortam değişkenleri ve dağıtım yapılandırması bu yedeğin parçası değildir. Tam üretim geri dönüşü için bunların ayrıca platform volume/config yedeği gerekir. ## Önkoşul Uygulamayı ve bu dizine yazan bütün zamanlanmış işleri platform üzerinden durdurun. Etkin sunucu ortamından gerçek `IHALEIST_USER_DATA_DIR`/volume yolunu doğrulayın. `--offline` işlemlerin durdurulduğuna ilişkin operatör beyanıdır; araç çalışan servisleri otomatik bulup kapatmaz. Canlı dosya silme/yükleme sürerken tutarlı yedek garantisi verilmez. Node'un `node:sqlite` modülü gerekli. Aşağıdaki yollar örnektir; doğrulanan yollarla değiştirin. Hedef dizin henüz bulunmamalı. ```sh node server/backup-user-data.mjs backup /volume/data /backups/release-001 --offline node server/backup-user-data.mjs verify /backups/release-001 node server/backup-user-data.mjs restore /backups/release-001 /recovery/release-001 ``` Yedek aracı SQLite snapshot oluşturur, bütünlük kontrolü yapar, referanslı dosyaları kopyalar ve SHA-256/byte manifesti üretir. Kayıp dosya başarısızlıktır; eksik yedek başarılı gösterilmez. Dizin/dosya izinleri 0700/0600'dır. Doğrulama şifreleme veya imza değildir; yedeğin erişimini ve dış saklamasını platformda ayrıca yönetin. ## Geri dönüş provası 1. Önce yeni bir test dizinine geri yükleyin. Üretim dizininin üzerine yazmayın. 2. Geri yüklenen dizine bağlı izole uygulamada iki test hesabının kayıtlarını, beş hazırlık kaydını, belgelerin indirilmesini, fiyat/toplam ve sürüm çakışmalarını doğrulayın. 3. Yayın adayının migration'ını bu kopyada çalıştırın. Eski ve yeni uygulama sürümlerinin hangi şemayla uyumlu olduğunu kaydedin; otomatik ters migration varsaymayın. 4. Uygulama ve veri geri dönüş noktalarını birlikte seçin. Platformun volume değiştirme adımı doğrulanmadan üretim dizinini silmeyin/yeniden adlandırmayın. 5. Yayın sonrası kayıt/indirme hatası varsa ilgili özelliği kapatın; doğrulanmış uygulama/volume çiftine dönüşü uygulayın. Başarılı yeni kullanıcı kayıtları varsa eski yedeğe kör dönüşle bunları kaybetmeyin; ileri düzeltme veya veri birleştirme değerlendirin. ## Mevcut kanıt `tests/backup-user-data.test.mjs` izole SQLite ve gerçek Türkçe dosya baytlarıyla yedek/restore, hazırlık fiyatı, hedef üzerine yazmama ve değiştirilmiş yedeği reddetme senaryolarını doğrular. Üretim servis durdurma, volume yedeği ve platform geri dönüşü henüz denenmedi. Bu dosya yayın onayı veya tüm D11 tamamlandı beyanı değildir. ## Bekleyen dosya temizliği `20260906_document_file_cleanup` migration'ı, mantıksal olarak silinmiş belgelerin fiziksel dosyalarını kalıcı kuyrukta tutar. Erişim kaydı ve temizleme görevi aynı transaction'da değişir; sonraki veritabanı açılışları temizliği yeniden dener. Dosya sistemi sorunu sürüyorsa kuyrukta kalır. Bu dosyalar aktif belge yedeğine dahil değildir; yedekte kuyruk kaydı bulunup dosya bulunmazsa yeniden açılışta ENOENT başarılı temizleme olarak ele alınır. Fiziksel silmenin anında tamamlandığı varsayılmamalı; işletim kontrolünde kuyruk büyüklüğü ve volume izinleri izlenmelidir. Kaynak gözlem geçmişi `IHALEIST_SHARED_DATA_DIR/tender-history.sqlite` içinde saklanır (değişken yoksa `server/data`). Kullanıcı verisi yedek aracı bu depoyu kapsamaz; tam yayın/geri dönüş provasında ortak veri volume'uyla birlikte ayrıca yedeklenmelidir. ## Ortak veri volume'u için yedekleme `server/backup-data-volume.mjs`, seçilen ortak veri dizinindeki normal dosyaları alt dizinleriyle kapsar. Dosya başlığından SQLite olarak tanınan depolar `VACUUM INTO` ile alınır; bağlı WAL/SHM dosyaları ayrı kopyalanmaz. Diğer dosyalar kopyalanır, boyut ve SHA-256 manifesti yazılır. Büyük dosyaların hash'i akışla hesaplanır. Sembolik bağlantılar, iç içe kaynak/hedef ve mevcut hedef dizinleri reddedilir. Bütün kaynak yazıcılarını durdurduktan sonra, doğrulanmış gerçek volume yollarıyla: ```sh node server/backup-data-volume.mjs backup /volume/shared /backups/shared-release-001 --offline node server/backup-data-volume.mjs verify /backups/shared-release-001 node server/backup-data-volume.mjs restore /backups/shared-release-001 /recovery/shared-release-001 ``` Bu araç seçilen dizin dışındaki worker depolarını, ortam değişkenlerini veya dağıtım yapılandırmasını kendiliğinden bulmaz. Kullanıcı belgelerinde veritabanı-dosya referans kontrolü için kullanıcı yedek aracını da kullanın. Tüm veri dizinleri ve kullanıcı yedeği aynı durdurulmuş sürümün seti olarak alınmalı; manifest zamanları tek başına servislerin durduğunu kanıtlamaz. Şifreleme/imzalama yapılmaz; backup erişimi ve dış saklama platformda yönetilir. Fiziksel boş dizinler ayrı manifest girdileri değildir. `tests/data-volume-backup.test.mjs`, iki SQLite deposu, committed WAL verisi, alt dizin dosyası, restore, hedef üzerine yazmama, değişmiş/ek dosya ve sembolik bağlantı reddini doğrular. Gerçek üretim volume'larına bu komutlar henüz uygulanmadı; platform geri dönüş provası açık. Kalıcı teklif asistanı işleri `IHALEIST_USER_DATA_DIR/ai-jobs.sqlite` içinde özel kaynak ve sonuç metni içerir. `backup-user-data.mjs` yalnız users.sqlite ve referanslı belgeleri aldığı için bu dosya ayrıca volume aracıyla yedeklenmelidir. Bütün yazıcılar durdurulmadan job/user yedek seti tutarlı kabul edilmez. Kalıcı genel analiz ve teklif işleri için süreç kesintisi davranışı: - İşçi kaynak metnini sabitledikten sonra aniden kapanırsa kayıt hemen başarısız sayılmaz. Son heartbeat üzerinden 60 saniye dolunca iş tarayıcısı kaydı `interrupted` durumuna geçirir. - Kesilen işi otomatik tekrar çağırmaz; kullanıcı Kayıtlı AI işleri üzerinden yeniden başlatır. Kaynak sabitlenmişse aynı girdi kullanılır. Sağlayıcıya daha önce ulaşmış çağrının ücretini veya sonucunu geri alma garantisi yoktur. - İnsan incelemeli genel analizde dosya/inceleme sürümü değişmişse eski işi yeniden başlatmak yerine güncel incelemeyle yeni iş gerekir. - Kaynağın sabitlenmesinden önce kesilen işin, geçmişte hangi uzak metni edinmiş olabileceği kanıtlanamaz. Eski sürümde tamamlanan kayıtların kaynakları geriye dönük üretilemez. - `node --test tests/ai-job-crash.test.mjs`: iki gerçek alt süreç SIGKILL testi; yeni süreçte kaynak/sonuç ve SQLite bütünlüğü kontrol edilir. Lease süresi testte saat parametresiyle ilerletilir. Gerçek AI sağlayıcısı ve üretim platformunun volume yeniden bağlama davranışı bu testin kapsamı dışındadır. Bildirim ve takip taraması deposu artık `IHALEIST_USER_DATA_DIR/takip.sqlite` içindedir; değişken yoksa mevcut `server/data/takip.sqlite` yolu korunur. Özel kullanıcı veri diziniyle çalışan eski kurulum yükseltilirken eski sabit yoldaki takip.sqlite ayrıca envantere alınmalıdır. Tüm yazıcılar durdurularak SQLite tutarlı yedeği doğrulanmalı ve hedefte mevcut bir takip.sqlite üzerine yazmadan yeni dizine aktarılmalıdır. Bu yerel değişiklik üretim verisini taşımadı. Tam volume yedeği bu dosyayı kapsar; yalnız `backup-user-data.mjs` kullanmak bildirimleri yedeklemez. E-posta aboneliği deposu `notify.sqlite` da `IHALEIST_USER_DATA_DIR` ayarını izler; değişken yoksa eski `server/data` yolu kullanılır. Özel dizinli eski kurulumlarda yükseltmeden önce sabit yoldaki notify.sqlite tutarlı biçimde yedeklenip yeni hedefe taşınmalıdır; mevcut hedef üzerine yazılmaz. Yerel değişiklik üretim deposunu taşımadı. Bu dosya yalnız kullanıcı/belge yedeğinin kapsamına girmez, tam volume envanterinde ayrıca bulunmalıdır. notify.sqlite abonelik kimliği geçişi: `criteria_key` sütunu eklendi; konu ve kurum kümeleri dahil bütün filtreler aboneliğin kimliğine katılır. Eski tablo işlem içinde yeniden kurulur; id, oluşturulma, aktiflik, son gönderim, görülen ihaleler ve AUTOINCREMENT üst sınırı korunur. Eski eşdeğer yinelenen kayıtlar silinmez; ayrı legacy kimliğiyle korunur. Bozuk eski konu/kurum JSON'u geçişi başarısız yapar ve transaction geri alınır. Yükseltmeden önce notify.sqlite yedeği alınmalı; eski uygulama koduna dönülecekse eski unique kuralını kullanan INSERT nedeniyle yalnız kodu geri almak yeterli değildir: eşleşen eski veritabanı yedeğiyle birlikte geri dönüş gerekir. Yeni sürümde oluşan kayıtların geri dönüşte nasıl korunacağı ayrıca değerlendirilmelidir. Kalıcı e-posta kuyruğu aynı notify.sqlite içinde `notification_outbox` tablosundadır. Tam içerik/gönderen/alıcı ve ihale listesi ile sağlayıcı istek kimliği burada korunur; yedeğe dahil edilmelidir. Bir bekleyen grup abonelik başına tek aktif kayıtla tutulur. 120 saniyelik işçi sahipliği, 30 saniyelik sağlayıcı isteğinden uzundur. İlk denemeden 23 saat sonra belirsiz kayıt otomatik tekrar gönderilmez; Resend'in 24 saatlik tekrar önleme süresine güvenli pay bırakılır. Yönetici sağlayıcı kaydını doğrulamalıdır; Mobil PWA sayfasındaki yöneticiye özel E-posta gönderim incelemesi bölümünden kayıt karara bağlanır. Sağlayıcı kabulü doğrulandıysa gönderim kimliği/onay/açıklama ile kapatılır; aksi halde yeniden göndermeden kapatma seçilebilir. Her iki işlem de yeni e-posta göndermez ve açıklamalı karar geçmişini korur. Bekleyen iş varken sağlayıcı hesabı/anahtarı değiştirilmemelidir: tekrar önleme anahtarı farklı sağlayıcı hesapları arasında garanti vermez. Eski uygulamaya dönmek kuyruğu yok sayabileceğinden kod ve DB geri dönüşü birlikte değerlendirilmelidir. `node --test tests/outbox-crash.test.mjs`, sağlayıcı kabulünden sonra commit öncesi/sonrası gerçek SIGKILL kurtarmasını izole volume'da sınar. Ayrı süreçte aynı payload/istek kimliği ve SQLite bütünlüğü doğrulanır. Sağlayıcı yerel bir simülatördür; test gerçek Resend hesabının idempotency davranışını veya üretim platformundaki disk kurtarmasını kanıtlamaz. ### Güncellik nedeniyle tutulan e-postalar Kuyruktaki ilanın artık kaynakta bulunmaması, teklif zamanının değişmesi, iptal/sonuç veya süre aşımı halinde otomatik tekrar durur ve yönetici inceleme listesinde gerekçe görünür. Liste sınırlaması veya geçici kaynak eksikliği de bu durumu oluşturabilir. Önce sağlayıcı kaydını ve resmi ilanı karşılaştırın; mevcut karar akışından gönderildiğini kanıtlayarak veya yeniden göndermeden kapatarak çözün. Payload ya da idempotency anahtarını elle değiştirmeyin. Yeniden göndermeden kapatmak bu gruptaki ihale kimliklerini görülmüş sayar; değişen ilan için otomatik yeni e-posta taahhüt edilmez. ### Firma profili / kayıtlı filtre sürüm sözleşmesi `GET /api/me/data` artık `veri` ile aynı okumaya ait `revisions` döndürür. `firma_profil` ve `saved_filters` PUT/POST istekleri `revision` göndermelidir (yeni kayıt için null). Eksik sürüm 428, değişmiş sürüm 409 üretir; otomatik üzerine yazma veya otomatik tekrar yoktur. Frontend ve backend birlikte sürümlenmelidir. Eski açık sekme 428 alırsa kaydedilmemiş girdiler korunup sayfa yenilenmelidir. Veritabanı şeması değişmez; eski sunucuya dönmek bu iki anahtardaki çakışma korumasını kaldırır. Yayın kabulünde iki sekmenin aynı kaydı değiştirmesi ve reddedilen sekmenin girdilerinin korunması sınanmalıdır. Ekip kaydı (`ekip`) da aynı içerik sürümü sözleşmesine dahil edildi. Yeni ekip arayüzü sunucu onayını bekler. Eski istemciden sürümsüz ekip yazımı 428 ile reddedilir; frontend/backend birlikte güncellenmelidir. Ekip alanlarının geçersiz veya kimliklerinin yinelenmiş olduğu eski kayıtlar önce düzeltilmelidir; sessiz veri temizliği yapılmaz. Favori kurumlar (`favori_kurumlar`) da sürümlü kullanıcı verisi yazımı ister. Eski istemcinin sürümsüz yazımı 428 döner; frontend/backend birlikte sürümlenmelidir. Bekleyen değişiklikler sunucudan onay gelmeden favori listesinde gösterilmez. Rakip izleme (`rakip_izleme`) da içerik sürümü zorunlu anahtarlara dahil edildi. Eski istemcilerde sürümsüz yazım 428 alır; güncel frontend ile aynı sürüm backend kullanılmalıdır. Eski rakip kayıtlarındaki ölçüler yeni arayüzde güncel havuz eşleşmesi yerine kullanılmaz; kayıtlı firma adı/sektör korunur. Bildirim tercihleri (`reminder`) sürümlü yazım sözleşmesine dahil edildi. Saat alanları HH:mm, pushEnabled/quietEnabled alanları boolean olmalıdır. Frontend/backend birlikte güncellenmelidir. Eski e-postaEnabled kaydı e-posta aboneliği iptali anlamına gelmez; gönderimler abonelik yönetiminden denetlenir. Haftalık özet `brief` anahtarı user-store tarafından artık kabul edilir. `/api/me/brief` GET tercih ve revision döndürür; metin/brief önizlemesi için `?preview=1` gerekir. POST boolean kapali ve okunan revision ister. Frontend/backend birlikte güncellenmelidir; eski sürüme dönmek brief kaydını tekrar reddeder ve opt-out tercihini okuyamayabilir. Haftalık özet teslimat deposu `IHALEIST_USER_DATA_DIR/brief.sqlite` içindedir (ayar yoksa `server/data`). Alıcı, haftalık mesaj planı, mesaj içeriği, parça konumu, işçi sahipliği ve kabul/belirsizlik kayıtlarını içerir; özel veridir. Tam volume yedeği bu SQLite dosyasını kapsar, yalnız kullanıcı/belge yedeği kapsamaz. Dosya 0600, yeni dizin 0700 oluşturulur. Kod geri dönüşü bu kayıtları yok sayıp aynı haftanın özetini tekrar gönderebilir; eski kodun otomatik gönderimini açmadan geçiş haftası sağlayıcı kayıtları değerlendirilmelidir. Eski `server/data/brief-son.json` silinmez: eski sürümün aynı hafta için işaretlediği otomatik gönderim geçişte tekrar yapılmaz. Eski sürüm bu işareti göndermeden önce yazdığı için bu kayıt teslimatı kanıtlamaz. Geçiş haftası manuel kontrol gerektirir. Yeni zamanlayıcı bu dosyaya tamamlandı kaydı yazmaz; kaynak yüklenemezse Pazartesi sonraki saatlik kontrolde tekrar yükleyebilir. Sağlayıcı çağrısına girilmiş fakat kabulü doğrulanamamış mesajlar otomatik yeniden gönderilmez. 120 saniyelik sahipliği sona eren yarım kayıt da belirsiz olur. `brief-outbox.mjs` içindeki salt-okuma `inspect()` sonuçları kayıt incelemesine yardım eder; yönetici karar ekranı/karar geçmişi henüz tamamlanmadı. Kayıtları silmek veya doğrudan pending yapmak yeniden mesaj gönderme riski taşır.