fix(kredi): kullanıcı başına üretim kilidi — çifte harcama kapanır
All checks were successful
Deploy / deploy (push) Successful in 10m52s
All checks were successful
Deploy / deploy (push) Successful in 10m52s
credit_ledger.ref_id benzersizliği yalnız AYNI denemenin tekrarını koruyordu (ağda kaybolan cevap → aynı requestId ile retry). requestId istemcide her mount'ta yeniden üretildiği için "aynı kullanıcı üretimi iki kez başlattı" durumu hiç yakalanmıyordu: üretim sürerken (20-60 sn) ikinci sekmede açılan /listem?uret=1 ya da geri→ileri ikinci bir ListeUretici monte edip 3 krediyi ikinci kez yakıyordu. Karşılığında tek rapor çıkıyordu, çünkü reports.user_id benzersiz ve ikinci üretim birincinin üstüne yazıyor. Bakiyesi yetmeyen deneme kullanıcısı ise akışın ortasında "kredi yetersiz" ekranı görüyordu. - report_locks (user_id birincil anahtar): ikinci INSERT benzersizlik ihlaliyle düşer, bu da "üretim sürüyor" demektir. Kilit kredi harcamasından ÖNCE alınır, finally'de bırakılır. - Bayat kilit (süreç OOM'la öldü, deploy) 180 sn sonra devralınabilir; devir koşullu UPDATE olduğu için iki eşzamanlı devralmadan yalnız biri kazanır. Salım requestId koşullu: geç uyanan eski sahip devralanın kilidini silemez. - Yeni SURUYOR kodu: istemci bunu "hata" sayıp taze requestId ile yeniden denerse çifte harcama kapısı yeniden açılırdı. Ekranda "Tekrar dene" bilerek yok; kullanıcı listeye yönlendiriliyor, kredi harcanmadı. - DUPLICATE dalının "İstek tekrarlandı" HATA'sı da SURUYOR'a çevrildi (BACKLOG #1'deki ikinci çifte harcama yolu). Revizyon dalı değişmedi. BACKLOG #1 · güvenlik denetimi G5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,24 @@ export const reports = sqliteTable("reports", {
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
||||
});
|
||||
|
||||
// Kullanıcı başına üretim kilidi. `credit_ledger.ref_id` benzersizliği yalnız
|
||||
// AYNI denemenin tekrarını (ağda kaybolan cevap) koruyor; requestId istemcide
|
||||
// her mount'ta yeniden üretildiği için "aynı kullanıcı üretimi iki kez
|
||||
// başlattı" durumunu hiç yakalamıyordu: ikinci sekme ya da geri→ileri, ikinci
|
||||
// bir ListeUretici monte edip 3 krediyi ikinci kez yakıyordu (tek rapor,
|
||||
// çünkü reports.user_id benzersiz ve üstüne yazılıyor).
|
||||
// userId birincil anahtar → ikinci INSERT benzersizlik ihlaliyle düşer, bu da
|
||||
// "üretim sürüyor" demektir. startedAt bayat kilitleri (süreç öldü, konteyner
|
||||
// yeniden başladı) devralmak için var.
|
||||
export const reportLocks = sqliteTable("report_locks", {
|
||||
userId: text("user_id")
|
||||
.primaryKey()
|
||||
.references(() => user.id),
|
||||
/** Kilidi tutan deneme; salım yalnız sahibi tarafından yapılabilsin diye. */
|
||||
requestId: text("request_id").notNull(),
|
||||
startedAt: integer("started_at", { mode: "timestamp" }).notNull(),
|
||||
});
|
||||
|
||||
// Anonim /sonuc tadımlığı: batch üretilmiş tek tercih satırları.
|
||||
// Anahtar: sıra kovası × puan türü × kategori × üniversite tipi
|
||||
// ("genel" = kategorisiz/tipsiz fallback). Anonim istek LLM'e asla gitmez;
|
||||
|
||||
Reference in New Issue
Block a user