`[̀-ͯ]` bir aralık sınıfı; biome bunu "bir taban karakter +
birleştirici karakter çiftini de eşleyebilir" diye işaretliyordu.
NFD zaten her aksanı kendi işaretine ayırdığı için doğrudan `\p{M}`
(tüm birleştirici işaretler) hem doğru hem daha geniş.
Gerçek Volvo/PSA/Subaru yakalamalarıyla çıktı birebir aynı kaldı.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prod'da browse onarması Peugeot'yu taşıdı (64 bayat satır → 39 güncel
model) ama Volvo'da "upstream returned no models" ile korumalı dala
düştü. Sebep: `BACKEND_MODEL_PATH`'te p5volvo yok ve yedek keşif yedi
bilinen yolu deniyor — Volvo'nunki `/extern/vehicles/models`, yani
ÇOĞUL "vehicles", listede yok. Volvo/Polestar browse bu yüzden sıfır
model tohumluyor ve emekli P4 listesini sunmaya devam ediyordu.
Otoritatif kaynak her P5 backend'inin kendi `/extern/catmeta`
yanıtındaki `data.catalogEntryPoint.path`. Canlı doğrulandı:
p5volvo → /p5volvo/extern/vehicles/models (60 model: XC90, V40…)
p5psa → /p5psa/extern/vehicle/catalogs
İki değişiklik:
- p5psa ve p5volvo `BACKEND_MODEL_PATH`'e sabitlendi (sıfır ek istek).
p5psa zaten çalışıyordu ama her çağrıda beş boşa probe isteğiyle
yeniden keşfediliyordu.
- `resolveModelPathFromCatmeta()`: haritada olmayan backend artık yedi
kör probe yerine tek otoritatif catmeta çağrısı yapıyor. Sonuç
backend başına 30 gün cache'leniyor (olumsuz sonuç dahil), yani
backend ömrü boyunca tek istek; PL24 bir ucu taşırsa kendiliğinden
düzeliyor. Hata durumunda null → eski yedeğe düşer.
8 yeni test. api 638 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prod'daki 4.799 aracın 1.406'sının WMI'si haritada yoktu. Hangilerinin
PL24'te gerçekten karşılığı olduğu tahmin edilmedi — canlı
`/pl24-wmi/ext/api/2.0/decode` servisine gerçek prod VIN'leriyle tek tek
soruldu (2026-09-20).
**Renault yeniden açıldı (450 araç).** İki gerekçe de artık geçersiz,
ikisi de prod'a karşı doğrulandı:
- Askı kalkmış: `/p5renault` directAccess, gerçek müşteri aracı
VF14SRCL458170337 için `VEHICLE_IDENTIFIED` + "SYMBOL II/LOGAN II"
döndü; WMI servisi de `{service: renault_parts, error: false}` diyor.
- Devre kesici artık kesin olumsuz yanıtları saymıyor, yalnız geçici
taşıma hatalarını (`vehicles.service` `isTransient`).
**Yeni eşlemeler (toplam 730 araç):** NLH/TMA/NLJ/KMF → hyundai_parts,
KNE/KNC → kia_parts, MMC/XMC → mmc_parts, JSA → suzuki_parts,
NMB → **mercestrucks**_parts (servis binek Mercedes'e değil kamyon
kataloğuna çözüyor; bu 30 araç "Mercedes-Benz" etiketliydi ama binek
kataloğunda yok).
**Kasıtlı olarak EKLENMEYENLER** — servis HTTP 410 "no brands found"
dedi, tıpkı NM4 ve VR7 gibi: JHM/SHH/SHS/MAK/NLA (Honda), KL1
(Chevrolet). Eklemek yalnız boşuna istek üretir ve pcat/emex/vinpin
fallback'ini geciktirir. JMZ (Mazda) 410 değil ama fordp/fordt
döndürüyor (Ford-Mazda platform ortaklığı); marka tutarsız olduğu için
eklenmedi.
6 yeni test; "olmayanlar" da kilitlendi. Ayrıca her eşlemenin katalog
tanımı olduğunu doğrulayan bütünlük testi. api 623 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Volvo vinfoBasic aynı özniteliği iki kez veriyor: "Şanzıman kodu" = "B"
ile "Şanzıman" = "6-PSHIFT 2WD / MPS6", "Satis tipi" = "42" ile "Türü"
= "S80". VAG için doğru olan kod-önce sırası (VAG'ın "Şanzıman kodu"
zaten anlamlı: "MQ200") bu yüzden Volvo'da şanzımanı tek harf, seriyi
çıplak sayı yapıyordu.
`lookupDescriptive()` eklendi: listedeki ilk *açıklayıcı* değeri döner
(2 karakterden uzun ve salt rakam değil), hiçbiri açıklayıcı değilse
ilk mevcut değere düşer — yani yalnız kod üreten backend'ler aynen
eskisi gibi davranır. Şanzıman ve seri bu yardımcıya geçti.
Ayrıca eksik İngilizce etiketler eklendi: `transmission` (İngilizce
yanıtta şanzıman yine tek harfe düşüyordu) ve `body_style`.
Ölçüm (gerçek keşif yakalamaları, YV1AS84ABD1168166 S80):
- şanzıman "B" → "6-PSHIFT 2WD / MPS6"
- seri "42" → "S80"
- kaporta İngilizce yanıtta null → "Sedan"
- PSA İngilizce yanıtta şanzıman null → "BVM5"
PSA Türkçe, Subaru ve diğer markalarda çıktı değişmedi.
6 yeni test + iki dilde gerçek Volvo fixture'ı. api 617 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bir servis karışık olabilir: bazı satırları yeni mimariyle yeniden
listelenmiş, bazıları hâlâ eski. Silme servis adına göre yapıldığında
zaten taşınmış satırlar da gidiyordu — üstteki insert onları
`onConflictDoNothing` ile atladığı için geri gelmiyorlar, yani temelli
kayıp. Silme artık yalnız gerçekten bayat olan satırlara uygulanıyor.
Hangi satırların silindiğini doğrulayan test eklendi (drizzle
`inArray` parametrelerini okuyarak).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`catalog_vehicles` kalıcı bir cache: `getModels` markanın tek bir satırı
varsa erken dönüyor, dolayısıyla `fetchVehicleList` o marka için bir
daha hiç çağrılmıyor. PSA ve Volvo hâlâ P4 iken listelenmiş 188 satır
(prod 2026-09-20: 127 LEGACY_PSA + 61 LEGACY_VOLVO) bu yüzden kalıcı
olarak çakılı kalmıştı:
- Peugeot/Citroën browse donmuş 2024-02-13 anlık görüntüsünü sunuyor,
- Volvo/Polestar browse HTTP 503 dönen bir uca gidiyor.
Kod düzeltmesi bu satırlara hiçbir zaman ulaşmıyordu.
`isStaleBrowseArchitecture()` + `CatalogService.healStaleBrowseRows()`:
satırın kayıtlı mimarisi servis tablosundakiyle uyuşmuyorsa marka bir
kez yeniden listeleniyor, yeni satırlar yazılıp eskiler aynı
transaction'da siliniyor. VIN tarafındaki onarma ile aynı temkinli
kurallar: marka başına günde bir deneme (Redis kilidi), başarısız veya
boş listede eski satırlar korunuyor, silme ancak yenisi elde edilince
ve servis bazında yapılıyor.
8 yeni test; api paketi 610 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pencere kontrolü saf saat aritmetiği, hiç I/O yapmıyor ve pencere
dışındaki iş zaten hiçbir faydalı iş yapamıyor — dolayısıyla dakikalık
Redis sayacından da önce gelmeli. Yeni sıra: pencere → cooldown →
dakikalık tavan → günlük bütçe. Böylece pencere dışında uyanan iş
hiçbir sayacı kirletmiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prod'da PL24 prefetch tamamen durmuştu: günlük bütçe 600/600 dolu
görünürken gün boyunca SIFIR katalog isteği ve SIFIR yeni kategori
üretiliyordu (ölçüm 2026-09-20).
İki hata birlikte kapalı bir döngü kuruyordu:
1. Günlük bütçe `process()` içinde düşülüyor, iş-saati (ve cooldown)
kapısı ise her handler'ın başında duruyordu. Pencere dışında uyanan
bir iş önce bütçeden bir birim yiyor, sonra `time-window` fırlatıp
hiçbir iş yapmadan erteleniyordu.
2. Bütçesi biten kaynak "bir sonraki UTC gece yarısı"na erteleniyordu.
PREFETCH_PL24_START=9 ile bu an 03:00 Europe/Istanbul'a denk gelir —
pencere açılmadan altı saat önce. Uyanan iş yine pencereye takılıyor,
yine erteleniyor; taze günlük bütçe daha pencere açılmadan bu boş
uyanmalarla tükeniyordu.
Düzeltme:
- cooldown + iş-saati kapıları `process()` içinde, bütçe düşülmeden
ÖNCE çalışıyor; handler'lardaki kopyaları kaldırıldı.
- `alignToWindow()` eklendi: bütçe ertelemesi pencerenin içine
hizalanıyor. Pencere tanımlı değilse (varsayılan 0–24) no-op.
- `currentIstanbulHour` artık `istanbulHourAt`'e deleg ediyor ve h24
döngüsünün gece yarısı için ürettiği "24" değeri `% 24` ile
normalleniyor (aksi halde saat hiçbir pencereye düşmez).
8 yeni regresyon testi; api paketi 602 test geçiyor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 2 / açık soru kapatıldı (analiz: /home/s/ss/plv2.md, bulgu types_wmi-04).
Rapor "VR7 muhtemelen Citroën, peugeot_parts yanlış" diyordu ve doğrulama
istiyordu. Canlı portal WMI servisi (2026-09-19, /pl24-wmi/ext/api/2.0/decode,
pl24-wmidata kapsamlı token) kesin yanıtı verdi:
HTTP 410 — "error resolving VIN: no brands found for WMI = VR7"
Yani VR7 ne Peugeot ne Citroën: PL24'ün WMI veritabanında HİÇ YOK, tıpkı Tofaş
NM4 gibi. Raporun önerisi (VR7 → citroen_parts) uygulansaydı hata aynen devam
edecekti.
Prod'daki 17 VR7 aracının tamamı model çözülmeden ("Peugeot Peugeot") ve 0
kategoriyle kaydedilmişti. Haritada tutmak yalnız her sorguda boşuna bir PL24
isteği üretiyor ve gerçek kaynağa (pcat/emex/vinpin) geçişi geciktiriyor —
canlı gözlem: P4→P5 onarma denemesi de bu VIN'lerde "kayıt bulunamadı" ile
düşüyor.
Test: pl24.types.spec'e kapsam-dışı WMI kilidi (VR7 ve NM4 haritada olmamalı) +
canlı doğrulanmış yönlendirmeler (JF1→subaru, ZAR→alfa, VXK→psa_opel).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
POST /internal/admin/users/:id/subscriptions — panelin "Yeni abonelik başlat"
butonu için. manuallyActivate expired/cancelled'ı "yeni subscription
başlatın" diye reddediyordu ama bunu yapacak uç yoktu; manuel tanımlar
DB'den elle yapılıyordu.
- BillingService.startSubscription: active/trial varsa 409, plan aktif değilse
409, Full plan → tüm aktif markalar, marka-limitli plan → brandIds tam sayı +
aktif marka doğrulaması. days verilirse bitiş = şimdi + days (1..3650), yoksa
billingPeriod default'u (aylık/yıllık). Gelir eventi (PostHog/Meta) ve
referral kredisi tüketimi YOK — para hareketi olmayan founder tanımı.
- UserBillingController (internal/admin/users) + module kaydı.
- billing.service.spec.ts: 7 test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ww3tfpuxBcettz81dDvyYt
Faz 2 / veri göçü (analiz: /home/s/ss/plv2.md, bulgu psa-07 / consumers_jobs-05).
SORUN: PSA ve Volvo upstream'de P5'e taşındı, ama daha önce decode edilmiş
araçlar hâlâ eski yollara işaret ediyor: 324 PSA aracı (302'si bir kullanıcıya
bağlı, 33.535 kategori) donmuş 2024-02-13 anlık görüntüsünden, 36 Volvo aracı
(34'ü kullanıcılı, 10.195 kategori) ölü P4 ucundan besleniyor. `decodeVin`'in
db_hit kısa devresi yüzünden kod düzeltmesi bu satırlara ASLA ulaşmıyor; ayrıca
`categories` tablosundaki (vehicle_id, name, source) tekil indeksi yüzünden yeni
ağaç eskisinin üzerine yazılamıyor.
YAKLAŞIM: toplu yeniden decode fırtınası yerine **görüntülendikçe kendi kendini
onarma**. Tek hayatta kalan hesaba 360 aracı arka arkaya sormak yerine, her araç
ilk açılışında bir kez taşınır.
- `isStalePl24Architecture()` (pl24-tree.ts): saklı catalogPath `/psa`|`/volvo`
ama servis bugün P5 ise bayat. Hâlâ P4 olan markalar (Ford/Opel/Hyundai/Kia/
Nissan) ve zaten P5 olan kayıtlar dokunulmaz.
- `CategoriesService.healStalePl24Architecture()`: cache'i atlayarak yeniden
decode eder, eski ağacı SİLİP yenisini tek transaction'da yazar, raw_data'yı
yeni catalogInfo ile günceller, `fully_fetched`'i düşürür ve
`cat:tree` / `prefetch:complete` / `prefetch:noresult` anahtarlarını temizler.
Bilinçli olarak temkinli:
- Yeniden decode başarısız olur veya kategori dönmezse ESKİ ağaç korunur
(bayat veri, veri yokluğundan iyidir).
- Deneme `pl24:heal:<vehicleId>` ile günde bir kez (setNx) — çözülemeyen bir VIN
her sayfa görüntülemesinde PL24'e gidemez.
- Eski satırlar ancak yeni ağaç elde edildikten SONRA siliniyor (tekil indeks).
- PL24 bütçesi/hız sınırı zaten üstte: her onarma ~2 upstream isteği.
Test: `isStalePl24Architecture` 4 test (bayat tespiti, zaten-P5, hâlâ-P4 markalar,
eksik bilgi) + onarma akışı 4 test (ağaç değişir, P5 kayda dokunulmaz, boş decode
eski ağacı korur, günlük kilit). 221 test geçti; tsc + biome temiz.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 1 / adım 6 (analiz: /home/s/ss/plv2.md, bulgu p4legacy-03).
SORUN: Bu markalar ana gruplarını <a href> ile DEĞİL, satır attribute'uyla
veriyor:
Opel <tr class="tc-data-row" jsonurl="json-vin-main-group.action?catId=25
&mainGroupId=394…" caption="BODY SHELL AND PANELS" ident="394">
Hyundai <tr class="tc-data-row" url="vin-group.action?…&mainGroup=BO…">
<td>BO</td><td>BODY</td>
decodeVin yolu yalnız anchor tarayan parseP4NavigationCategories'i kullanıyordu
→ sayfa listeyi taşıdığı hâlde 0 kategori. Prod ölçümü (raw_data.categories):
Opel 177/181 (%97,8), Hyundai 12/12, Kia 6/6, Nissan 1/1 araç decode anında BOŞ.
(Kategori tablosu sonradan lazy-seed ile doluyor, ama decode kategorisiz
döndüğü için kullanıcı ilk açılışta boş görüyor ve prefetch self-seed'e zorlanıyor.)
Üç ayrı kusur vardı:
1. `parseFordGroupsFromHtml` bu satırları zaten anlıyor ama decode'dan hiç
çağrılmıyordu → kategori zincirine fallback olarak eklendi
(JSON ucu → tc-data-row tablosu → anchor taraması).
2. Volvo için konmuş `vin-group.action && !group1=` guard'ı Hyundai/Kia'nın
AYNI ucu `mainGroup=` anahtarıyla kullanan gerçek ana gruplarını da eliyordu
→ artık `group1=` veya `mainGroup=` taşıyan satırlar korunuyor.
3. Satır attribute'ları HTML-escaped (`&`), bu yüzden `[?&]mainGroup=`
kontrolü hiç tutmuyordu → URL artık ayrıştırma anında entity-decode ediliyor
(saklanan linkPath de böylece temiz).
Test: yeni `pl24-p4-groups.spec.ts` + 2026-09-16 canlı yakalamalarından kırpılmış
fixture'lar (`__fixtures__/p4_{opel,hyundai,ford,nissan}.html`). Opel 31 ana grup
("BODY SHELL AND PANELS"…), Hyundai 6 ("BODY"…), anchor-only parser'ın bu
sayfalarda hâlâ 0 döndüğü (fallback'in gerekçesi), Ford sharedCatCode scope
satırlarının bozulmadığı ve Nissan model-seçim satırlarının elendiği kilitlendi.
213 test geçti; tsc + biome temiz.
NOT: db_hit kısa devresi yüzünden mevcut araçlar kendiliğinden düzelmez; kazanç
yeni decode'larda. Toplu yeniden decode Faz 2'de.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 1 / adım 3-4 (analiz: /home/s/ss/plv2.md §3.3/§3.5, bulgular types_wmi-01/02/03/04/05,
p5core-02/03/04/05, psa-04/05/06, consumers_jobs-01/02).
CANLI GERÇEK (2026-09-16 keşfi): PL24 manufacturers API'si peugeot/citroen/
citroenDs/psa_opel/psa_vauxhall'ı `/p5psa`, volvo/polestar'ı `/p5volvo`,
subaru'yu `/p5subaru` olarak veriyor. Eski Volvo P4 ucu **503** döndürüyor
(ölü); eski PSA P4 ucu 200 döndürüyor ama verisi **2024-02-13'te donmuş**
(P5 upds 2026-09-07). Kodumuz her ikisini de P4'e yönlendiriyordu.
SERVİS TABLOSU:
- peugeot/citroen/citroenDs + YENİ psa_opel_parts, psa_vauxhall_parts → /p5psa
- volvo/polestar → /p5volvo · YENİ subaru_parts → /p5subaru
- YENİ abarth/alfa/jeep/lancia → /p5fiat
- WMI düzeltmeleri: ZAR→alfa, ZLA→lancia (fiatp'den), VXK→psa_opel (PSA-platform
Corsa F/Mokka B), YENİ JF1/JF2→subaru, 1C4/1J4→jeep. SERVICE_TO_BRAND genişledi.
- P4 kodu ve eski yollar DOKUNULMADI: mevcut 322 PSA + 36 Volvo aracı eski
ağaçlarıyla çalışmaya devam eder (veri göçü ayrı iş).
PARSER (canlı yanıt şekillerine göre):
- `normalizeLabel()`: JS toLowerCase Türkçe "İ"yi "i̇" (i + birleşen nokta)
yapıyordu → PSA'nın "AKTARMA SİSTEMLERİ"/"GÖVDE TİPİ" etiketleri HİÇBİR ZAMAN
eşleşmiyordu, şanzıman/gövde sessizce null kalıyordu. Artık tr-locale +
diakritik temizliği + ı→i.
- `parsePsaModelYear()`: "AM 2005" → 2005; "MAJÖR ENDEKS" bir yıl DEĞİL (bu
yüzden Citroën'ler 2001 görünüyordu). `damToModelYear()`: PSA DAM alanı
(1976-01-01'den gün) → üretim yılı, model yılı Temmuz'da döner.
PSA VIN'lerinde 10. karakter model yılı olmadığı için VIN fallback'i en sona.
- Alan listeleri: motor/engine (PSA "MOTOR", Volvo "Motor"), aktarma_sistemleri,
mission, govde_tipi, kaporta_stili, türü→series.
- Alt grup kodu: PSA'da `record.id` = illusPath ve birden çok illüstrasyon
paylaşıyor (canlı: 36 kayıt / 11 id) → benzersiz kod `values.illustration`.
- Parça filtresi: Volvo BOM'unun başlık satırı (id "null_null", link yok,
unavailable) hayalet parça olarak geçiyordu → elendi.
- Adet: qty (VAG/Subaru) · coef (PSA) · unit (Volvo). Uygulanabilirlik:
modelDescription · restriction.
AĞAÇ SINIFLANDIRMASI (yeni `pl24-tree.ts`, tek kaynak):
Dört ayrı sezgisel vardı ve birbiriyle çelişiyordu:
- `linkWid.includes("group")` → p5psa `illusTable`, p5volvo/p5subaru
`illustrationsTable` yakalanmıyordu → o seviye yaprak sanılıp parça olarak
çekiliyor, partno'suz kayıtlar 0 parçaya dönüşüyor, panel sessizce boş
kalıyordu (2026-06 isPsaParent olayının aynısı).
- case-sensitive `includes("/bomdetails")` → p5psa/p5volvo'nun camelCase
`/details/vin/bomDetails` yaprakları grup sanılıyor, parçaları hiç prefetch
edilmiyordu (bugün Mitsubishi %99,9 / Fiat %80 / Renault %70 bomDetails
yaprağı 0 parça — aynı kök neden).
Artık `isPl24LeafNode`/`isPl24GroupNode` tek kaynak; categories.service (2 yer),
catalog.service (2 yer) ve prefetch-worker aynı fonksiyonu kullanıyor.
Test: yeni `pl24-tree.spec.ts` (7 test, canlı VW/PSA/Volvo/Subaru wid+path
çiftleriyle regresyon kilidi). Etkilenen paketler: 208 test geçti. tsc + biome temiz.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
İki hesap banı da günler sonra, DB satır sayılarından fark edildi (tr 07-24 →
08-18'de, de 09-04 → 09-16'da). Üçüncüsünde bir saat içinde haber olsun.
- Yeni global `TelegramService` (aynı bot: TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID;
yapılandırılmamışsa sessizce devre dışı, istek yolunu asla etkilemez).
- `PL24AuthService` kesinti saatini Redis'te tutuyor (`pl24:auth:fail-since:<hesap>`),
böylece api+worker ve yeniden başlatmalar aynı başlangıcı görür — crash-loop
saati sıfırlayamaz. Başarılı login saati siler.
- Kesinti 60 dakikayı geçince tek sefer uyarı (`pl24:auth:alerted:<hesap>` NX,
6 saat) — hesap kodu, süre, son hata ve "banlanmış olabilir" notu ile.
Düzelince sessiz "🟢 düzeldi" mesajı.
- Devre kesici açıkken de saat işler: kesici login denemesini bastırdığı için
aksi halde hesap en ölü olduğu anda saat duruyordu.
- compose'a eksik env referansları: TELEGRAM_*, PL24_HTTP_DAILY_MAX,
PL24_HTTP_USER_RESERVE, PREFETCH_PL24_FAST_DEPTH, PREFETCH_PL24_DELAY_MS
(Coolify env'i ancak compose'da referanslıysa container'a ulaşıyor).
Test: 3 yeni test (1 saat dolmadan susar, dolunca tek sefer uyarır, düzelince
kurtarma mesajı + saat sıfırlanır). Redis stub'ı gerçek SET NX semantiğine
çekildi (alarm tekrarının bastırılması buna dayanıyor). 80 PL24 testi geçti.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 1 / adım 2b (analiz: /home/s/ss/plv2.md, bulgu consumers_jobs-03).
SORUN: Ban'ı süren hacim backfill değil, her yeni decode'da çalışan fast-lane
TAM AĞAÇ drill'iydi. 2026-08-18→09-04 arasında (PL24_TR_DISABLED main lane'i
park etmişken) yeni kategorilerin %100'ü aynı gün decode edilen araçlardan
geldi: günde 3-17 decode → 1.4k-6.8k kategori (bir Passat 1.251, bir L200
2.323). Araç başına drill derinliği sınırsızdı ve PL24 istekleri arasında hiç
bekleme yoktu.
DEĞİŞİKLİK:
- `PREFETCH_PL24_FAST_DEPTH` (varsayılan 1): PL24 reaktif/fast lane yalnız üst
gruplar + doğrudan çocuklarını gezer. Daha derini ya kullanıcı o düğümü
açtığında (lazy) ya da bütçeli backfill lane'inde çekilir. Diğer kaynaklar ve
PL24 main lane MAX_DEPTH kullanmaya devam eder. Fast lane'in derinlik tavanına
ulaşması normal durum olduğu için log seviyesi warn değil log.
- `PREFETCH_PL24_DELAY_MS` (varsayılan 8000) + ±%50 jitter: PL24 BACKFILL
işlerine per-job bekleme. Kullanıcının fast lane'i asla yavaşlatılmaz.
Metronom gibi düzenli akış otomasyon imzasıdır; jitter onu kırar.
- Telemetri düzeltmesi: `proxy_logs`'a ham hesap etiketi ("tr") yerine gerçekte
kullanılan hesap yazılıyor (PL24_TR_DISABLED altında "de"). Yeni
`PL24AuthService.activeAccount()`.
Test: `__testables` ile maxDepthFor/jitter dışa açıldı + derinlik tavanı testi.
Etkilenen paketler: 198 test geçti. tsc + biome temiz.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 1 / adım 2 (analiz: /home/s/ss/plv2.md, bulgular consumers_jobs-03/06/07,
deploy_ops-13).
SORUN: İki PL24 hesap banı da günde 5-7 bin kategori yazan (≈10 bin+ istek)
otomatik drill dalgalarının ardından geldi; gerçek kullanıcı decode'u ≤14/gün.
Mevcut korumaların hepsi iş-seviyesinde (iş/dk, iş/gün) — HTTP isteği sayan
hiçbir tavan yoktu. Üstelik PL24 çağrıları `proxy_logs`'a hiç yazılmıyordu
(yalnız pcat/emex), bu yüzden hacim ancak ban'dan SONRA, DB satır sayılarından
geriye dönük çıkarılabildi. Kill switch de yalnız decodeVin'i kapatıyordu; drill,
backfill ve katalog gezinmesi "kapalı" kaynağa istek atmaya devam ediyordu.
DEĞİŞİKLİK:
- Yeni `PL24BudgetService`: tüm PL24 upstream trafiği için tek geçit.
- Günlük sayaç `pl24:http:<gün>` (Redis, 8 gün TTL); tavan `PL24_HTTP_DAILY_MAX`
(varsayılan 1200).
- Kullanıcı rezervi `PL24_HTTP_USER_RESERVE` (varsayılan %40): backfill tavanın
%60'ında durur, kullanıcı decode'u sonuna kadar akar — kaçak bir backfill
müşteriyi asla aç bırakamaz. Lane ayrımı mevcut `isBackfillContext()` ile.
- Reddedilen çağrı ağa hiç çıkmaz ve telemetriye yazılmaz.
- Redis düşerse fail-open (auth/katalog telemetri yüzünden bloklanmaz).
- Telemetri: `ProxyServiceLeg` += `pl24_http`, `pl24_auth`. Login, authorize ve
tüm katalog fetch'leri `proxy_logs`'a yazıyor (provider dataimpulse/none,
sessionKey=hesap, status, süre, 403/429 → banned, taşıma hatası sınıflandırma).
Artık "hangi gün kaç istek attık, ilk 401/403 ne zaman başladı" sorusu
ban'dan ÖNCE yanıtlanabilir.
- Kill switch yayıldı: `kill-source-pl24` artık `budgetedFetch` içinde, yani
decode + drill + backfill + browse + görsel indirme dahil TÜM katalog trafiğini
kapatıyor (eskiden yalnız decodeVin).
- `fetchWithRetry` 401 çaresi netleşti: yalnız servis token'ı tazelenir; oturumun
kendisi gerekirse auth katmanında düşürülür.
Test: yeni `pl24-budget.service.spec.ts` (9 test: tavan, kullanıcı rezervi,
reddedilen çağrının ağa çıkmaması, Redis fail-open, telemetri biçimi/leg ayrımı,
ban sinyali, taşıma hatası sınıflandırma). Etkilenen paketler: 197 test geçti.
tsc + biome temiz.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Faz 1 / adım 1 (analiz: /home/s/ss/plv2.md §4.1, bulgular auth-02/03/05/06/09/11/15,
p4legacy-07, consumers-04).
SORUN: PL24'ün base JWT'si 600 saniye yaşıyor ve kod bunu "oturum" sayıyordu
(isTokenValid → login). Talep varsa her ~9 dakikada bir squeezeOut:true ile
YENİDEN LOGIN atılıyordu (günde ~144 login/hesap). PL24 hesap başına tek oturum
veriyor; bu desen hem kendi oturumumuzu sürekli düşürüyor hem de iki hesap
banından önceki otomasyon imzasını oluşturuyordu. Portalın kendisi bir kez login
olup PL24TOKEN çerezini saklıyor ve yalnız authorize'ı yeniliyor.
DEĞİŞİKLİK:
- Oturum = PL24TOKEN çerezi (süresiz). `ensureSession()` sahibi; bellekte tutulur,
Redis'te `pl24:auth:session:<hesap>` ile 24 saat paylaşılır (api/worker aynı
oturumu devralır). Süre kontrolü YOK — oturum yalnız sunucu reddederse düşer.
- Servis token'ı = authorize ile mint edilen 600 s'lik JWT; yalnız `Cookie:
PL24TOKEN` ile istenir (Bearer gerekmiyor — canlı doğrulandı).
- `session_status:"gone"` ve authorize 401/403 artık okunuyor: oturum düşürülür,
tek sefer yeniden login + retry.
- Single-flight: süreç içi in-flight promise + Redis kilidi
(`pl24:auth:login-lock:<hesap>`), kilidi alamayan paylaşılan oturumu bekler.
Eski prewarm iki paralel login atıyordu ve biri her boot'ta HTTP 500 alıyordu.
- Login sözleşmesi: portal ucu `/auth/ext/api/1.1/login`
({account,user,password,squeezeOut} → {loginStatus,sessionToken} + Set-Cookie);
RFC7807 problem gövdesi tiplendi. Önce squeezeOut:false denenir, yalnız
"session-limit-exceeded"/USER_ALREADY_LOGGED_IN'de squeezeOut:true ile tekrar.
`PL24_LOGIN_API=legacy` eski uca döner (bir sürüm geri çekilme bayrağı).
- Hata sınıflandırma: account-not-active / user-not-active / authentication /
2FA-required → ilk hatada 6 saatlik devre kesici (eskiden ölü hesap 5 dakikada
bir sonsuza dek deneniyordu: prod'da 376 ardışık 401). Ağ/timeout → 5 dakika.
- Saatlik login tavanı (hesap başına 6) — sağlıklı günde 0-1 login beklenir.
- P4 (.action) sayfaları için authorize kaldırıldı: yalnız çerez gönderiliyor
(canlı doğrulandı). Ford/PSA'daki 18 "ısıtma" authorize çağrısı ensureSession'a
indirildi; demo-sayfa/401 çaresi artık servis token temizleme değil oturum
yenileme.
- Gerçek Chrome User-Agent sabiti (eski değer hiçbir tarayıcıda yok).
- authorize'a `pl24-wmidata` eklendi (portal WMI decode'u için ön hazırlık).
- Ölü kod silindi: getAccessToken*/getSessionCookie* base-JWT yolu, hasService,
getAvailableServices, AUTH_TOKEN_TTL.
Test: pl24-auth.service.spec.ts yeniden yazıldı (19 test: tek login, single-flight,
Redis devralma, cookie-only authorize, gone/401 yeniden login, 412 squeezeOut,
P4 cookie-only header, kalıcı hata kesicisi, saatlik tavan). Tüm PL24 + tüketici
paketleri: 188 test geçti. tsc + biome temiz.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SOURCE_DAILY_MAX.pl24 zaten PREFETCH_DAILY_PL24'ü okuyor
(prefetch-worker.service.ts:156) ama compose'da hiç ${...} referansı
olmadığı için Coolify env'i container'a ulaşmıyordu; tavan 15.000'de
kilitliydi.
PL24 hesap banları (tr 07-24, de 09-04) günde 5-7 bin kategori üreten
otomatik drill dalgalarının ardından geldi. Yeni hesaba geçerken
(de-025446) backfill'in güvenli bir günlük tavanla çalışması gerekiyor.
Coolify env: PREFETCH_DAILY_PL24=600 (main lane payı %80 = 480 iş/gün),
PREFETCH_PL24_START=9 / PREFETCH_PL24_END=18 (Europe/Istanbul insan
saatleri), PREFETCH_RATE_PL24=6 (main 4/dk, fast 2/dk).
Analiz: /home/s/ss/plv2.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- tr-903645 PL24 tarafında pasif (07-24, "The account is not active"): flag
açıkken tüm tr auth/proxy/egress şeffaf biçimde de-708171'e maplenir
(PSA dahil — legacy authorizeService yolu tr'ye hardcode'du)
- PSA fetch'lerine dispatcher eklendi: de oturumu DE proxy'den çıkar
- resolveAccount flag altında herkese de döner (fetch/cache/etiket tutarlı)
- login devre kesici: 3 ardışık hata → 5 dk soğuma (haftalardır süren
~6 boş login/dk fırtınasını bitirir)
- prefetch: flag açıkken pl24 backfill üretimi durur + kuyruktaki pl24
main-lane işleri 15 dk defer'la park edilir (fast/user lane akmaya devam
eder) — hayatta kalan tek hesabı arka plan yükü yakmasın
- compose: PL24_TR_DISABLED env injection (api+worker)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dev testinde ana conversion ping'i (googleadservices/pagead/conversion) geçti
ama ad.doubleclick.net (ccm/collect enhanced) + google.com.tr (TR yerelleştirilmiş
1p-conversion) hâlâ bloklanıyordu. img-src/connect-src'e eklendi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gate fix (PR#254) prod'da doğrulandı: pressure=56 (eskiden backlog=11495),
bütçesi dolan kaynak eligible'dan düşüyor — runaway guard'ları canlı. Bütçenin
kalan tek riski bant genişliği maliyeti.
45k gerekçesi: guard'ı doğuran 2026-07-09 olayı ~74k çağrı/~10 GB idi; 45k onun
%61'i (~6 GB/gün). DAILY_FAST_RESERVE ile backfill main lane 36k alır = eski
efektif 30k'ya göre +%20, kullanıcıya 9k rezerv kalır. Burst hızı DEĞİŞMİYOR
(pcat 90/dk) → per-IP ban baskısı aynı, sadece tempo günün daha uzun bölümünde
sürüyor. Rollback: bir saatte >2 pcat HTTP 402 → 30_000'e dön.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sorun (2026-08-01): wait=0/active=0 iken scan backlog=11495 hesaplıyordu — hepsi
günlük-bütçe ile ertesi gün 00:00'a park edilmiş pcat job'ı. backlog>maxBacklog
olduğu için Phase-2 günün çoğunda askıdaydı; 2143 kısmi araç ilerlemiyordu.
Adversarial analiz kritik uyarı verdi: delayed'ı gate'ten çıkarmak TEK BAŞINA
470k runaway'i tekrarlar (üretim ~288k job/gün vs bütçe 65k/gün; bütçe-defer'leri
skipAttempt ile attempts tüketmediğinden asla düşmez). Bu yüzden üretim kaynağında
kesiliyor + ikinci tavan ekleniyor:
- Gate ikiye ayrıldı: pressure (waiting+active+10dk içinde vadesi gelen delayed,
HER İKİ kuyruk) vs maxBacklog; total (tüm bekleyen) vs HARD_MAX_TOTAL_JOBS=50k.
zcount BullMQ delayed ZSET score'u (dueMs*4096+seq) ile; hata halinde fail-CLOSED.
- ÜRETİMİ KES: scan, günlük main-lane bütçesi dolmuş kaynağı eligible'dan düşürür.
- Admission control JOB cinsinden (EST_JOBS_PER_VEHICLE=400), araç cinsinden değil.
- Günlük bütçe lane-aware (fast lane'e %20 rezerv) + READ-then-INCR (sayaç artık
defer'lerle şişmiyor; 48531 vs 30000 gözlemi) + 45dk jitter (thundering herd).
- KÖK NEDEN: addJob artık boolean, queueCategoryJob sayı döndürüyor; progress.total
yalnız GERÇEKTEN kuyruğa giren job'ı sayıyor. Şişmiş total zinciri asla
"finished"a ulaştırmıyordu → araç fullyFetched işaretlenmiyor → scan sonsuza dek
yeniden seçiyordu (~25/gün platosu). processInit queued===0 ise finalizeVehicle.
- prefetch:scheduled:<id> ÇAKIŞMASI: backfill artık :backfill: namespace'i yazıyor;
eski 6h TTL kullanıcının fresh-decode fast-lane init'ini sessizce atlatıyordu.
TTL 6h→36h (bütçe-park edilmiş zincir ~24h yaşıyor).
- Phase-2 artık kalıcı vehicles.fullyFetched'i hedefliyor (efemeral marker değil).
- CATEGORY_CAP DB'den ölçülüyor (her processInit'te sıfırlanan sayaçtan değil).
537 test geçti. Bütçe artışı (PREFETCH_DAILY_PCAT) BU PR'da DEĞİL — ayrı adım.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Conversion sadece register.tsx email path'inde ateşleniyordu; Google OAuth
signup'lar (register + login Google butonu) hiç ateşlemiyordu → ölçüm: paid-
kaynaklı kayıtların ~%79'u OAuth, Google Ads'e görünmüyordu, Max Conversions
sinyalsiz optimize ediyordu. Evrensel post-auth bileşeni (google-ads-signup-
conversion.tsx, __root'a mount) yeni kullanıcıda (createdAt < 30dk) conversion'ı
bir kez ateşler; transaction_id signup_<userId> Google tarafında dedup eder,
register.tsx guard'ı email path'in çift-ateşini önler. _gcl_aw cookie OAuth
roundtrip'inde korunduğu için atıf doğru. Playwright 6/6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix (herkes, flag'siz): evrensel post-auth segment kapısı (segment-gate.tsx).
Kayıtların ~%41'i segmentsizdi çünkü login.tsx Google OAuth segment adımını
atlıyordu. Yeni kullanıcıya zorunlu (açığı kapatır), mevcut segmentsize
7g-cooldown'lu yumuşak prompt (~538 backfill). Segment localStorage +
PostHog person prop (b2b_segment); backend persist faz 2.
Kova B (funnel-bucket flag, b2b_qualified): trial-value-upsell'e segmente-özel
Meta-kanıtlı kopya (iade / yanlış-parça / sınırsız-şase). Flag SADECE banner
görünürken okunur → deney maruziyeti = gerçekten gören aktif trial'lar.
vehicle_owner / bilinmeyen segment → nötr control kopya (ürün kararı).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-dakika rate cap'i (pcat 90/dk) burst'ü sınırlıyor ama günlük TOPLAM'ı değil —
bir kaynak saatlerce tavanda çalışıp proxy bütçesini boşaltabiliyor (2026-07-09:
backlog drain pcat'i ~74k çağrı / ~10 GB'a çıkardı). SOURCE_DAILY_MAX eklendi:
UTC-gün fixed-window Redis sayacı; kaynak günlük bütçeyi aşınca backfill job'ları
pencere dönene kadar ertelenir. Kullanıcı decode'ları etkilenmez (worker'dan
geçmiyor). Dakikalık gate'ten SONRA sayılır → rate-limitli retry'lar şişirmez.
Default (env-tunable): pcat 30k, emex 20k, pl24 15k/gün. Backlog drain'i
hızlandırmak için yükselt, bütçeyi daha çok korumak için düşür.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>