Commit Graph

1071 Commits

Author SHA1 Message Date
semih
ddc223f745 feat(pl24): bayat P4 mimarisindeki araçları görüntülendikçe P5'e taşı
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
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>
2026-09-17 16:53:15 +03:00
semih
8d284f6442 fix(pl24): Opel/Hyundai/Kia decode'unda 0 kategori — tablo satırları okunmuyordu
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
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 (`&amp;`), 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>
2026-09-17 14:47:53 +03:00
semih
dde6f6a15c feat(pl24): PSA + Volvo'yu P5'e taşı, eksik katalogları ekle, ağaç sınıflandırmasını birleştir
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
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>
2026-09-17 14:00:30 +03:00
semih
3cfa3d588a feat(pl24): PL24 girişi 1 saat başarısızsa Telegram uyarısı
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
İ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>
2026-09-17 13:53:15 +03:00
semih
02ec3856d5 perf(pl24): reaktif drill derinliğini sınırla, backfill'i jitter'lı aralıkla yavaşlat
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
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>
2026-09-17 13:44:59 +03:00
semih
56a8ea09be feat(pl24): günlük HTTP bütçesi, kullanıcı rezervi ve proxy_logs telemetrisi
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
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>
2026-09-17 13:13:05 +03:00
semih
e50a907931 refactor(pl24): oturum modeli — tek login, çerez tabanlı authorize yenileme
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
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>
2026-09-17 00:59:36 +03:00
semih
330015fbb6 chore(prefetch): PL24 günlük backfill tavanını env'den ayarlanabilir yap
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>
2026-09-16 20:55:56 +03:00
5d9da95830 fix(pl24): tr hesabı kapalıyken de köprüsü (PL24_TR_DISABLED) + login devre kesici
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
- 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>
2026-08-18 14:47:53 +03:00
181bdbe971 fix(billing): dunning kurtarma zinciri (migration 0035 + webhook + banner)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Stripe gerçeği: 3 abonelik past_due (3×₺999 = brüt MRR'ın ~%27'si), 1 abonelik
dün dunning'den iptal (tugem, ₺999 kayıp); kurtarma tarihsel %0. Kök nedenler:
(1) dunning e-postasının CTA'sı ödeme imkânı olmayan /dashboard/subscription'a
gidiyordu, (2) kart güncelleme yüzeyi hiç yok + yeniden checkout 'Zaten aktif
aboneliğiniz var' ile bloklu, (3) app past_due'yu hiç bilmiyordu (DB state yok,
banner yok), (4) subscription.deleted no-op'tu (churn görünmez + status active
kaldığı için re-subscribe kalıcı bloklu).

- migration 0035: user_subscriptions.dunning_since + dunning_invoice_url
- handleInvoiceFailed: dunning state persist + e-posta CTA'sı Stripe hosted
  invoice sayfasına (öde/yeni kart/3DS) + milestone e-posta (1./3./final —
  deneme başına aynı mail spam'i bitti) + attempt_count PostHog'a
- handleInvoicePaid: her iki dalda dunning temizliği
- handleSubscriptionDeleted: status→cancelled (re-subscribe deblke),
  subscription_churned PostHog event'i (reason: payment_failure/cancelled)
- web: DunningBanner (kapatılamaz, kırmızı; CTA hosted invoice) dashboard'da
- spec: yeni davranışa güncellendi + milestone/final testleri (14/14)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 11:10:44 +03:00
57d16b9aea fix(analytics): CSP'ye kalan enhanced-conversion endpoint'lerini ekle
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-08-04 16:53:48 +03:00
eb277cd55a fix(analytics): CSP'ye Google Ads tag + conversion domain'lerini ekle
CSP script-src googletagmanager.com İÇERMİYORDU → gtag.js hiç yüklenmiyordu →
gtag config/conversion event'i çalışmıyor, _gcl_aw linker cookie set olmuyor,
conversion ping'i Google'a hiç gitmiyor → haftalardır panelde 0 dönüşüm (doğru
AW id + label + kanonik gtag'e rağmen). ASIL bloker buydu — gtag shim (6c936d5)
+ OAuth (685c6af) fix'leri gerekliydi ama tek başına yetmezdi.
- script-src += www.googletagmanager.com
- img-src/connect-src += google.com, googleadservices.com, googleads.g.doubleclick.net
Meta Pixel worker-src ve Sentry ingest ile aynı sınıf CSP-blocking bug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 16:47:22 +03:00
2df73e6a48 perf(prefetch): pcat günlük bütçe 30k→45k (main lane 36k, fast lane 9k rezerv)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-08-01 14:32:42 +03:00
c21472d930 Merge pull request 'fix(prefetch): Phase-2 kilidini ac (pressure/total gate) + tamamlanma muhasebesi' (#254) from dev into main 2026-08-01 14:22:23 +03:00
3597b03c4f fix(prefetch): Phase-2 kilidini aç (pressure vs total gate) + tamamlanma muhasebesi
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-08-01 14:21:44 +03:00
2cda50f9af Merge pull request 'fix(analytics): gtag shim kanonik arguments push' (#253) from dev into main 2026-08-01 10:09:43 +03:00
6c936d546b fix(analytics): gtag shim kanonik arguments push — tüm conversion takibi kırıktı
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
initGoogleAds gtag shim'i array push ediyordu ((...args) => dataLayer.push(args));
gtag.js komut olarak sadece `arguments` nesnesini işler, array'i inert dataLayer
verisi sayar → config/event HİÇ çalışmıyordu → _gcl_aw linker cookie'si set
olmuyor, conversion ping'i Google'a gitmiyor, panelde 14 günde 0 dönüşüm (email
kayıtları dahil — sadece OAuth değil). Google'ın kanonik snippet formuna
(function gtag(){dataLayer.push(arguments)}) döndürüldü. OAuth post-auth fix'iyle
(685c6af) birlikte email+OAuth tüm kayıtlar artık ateşler + doğru atfeder.
Doğrulama: headless test gtag'i teyit edemez (Google bot-baskılaması); kesin
kanıt gerçek tarayıcıda kaydın panele düşmesi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 10:09:28 +03:00
bf0cc4e96c Merge pull request 'fix(analytics): OAuth Google Ads sign-up conversion' (#252) from dev into main 2026-08-01 09:16:41 +03:00
685c6af3ed fix(analytics): OAuth kayıtlarında Google Ads sign-up conversion'ı ateşle
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-08-01 09:16:20 +03:00
95818b2501 Merge pull request 'feat(growth): B2B segment kapısı + funnel-bucket A/B (Kova B)' (#251) from dev into main 2026-07-29 11:03:48 +03:00
55b4cd38e9 feat(growth): B2B segment kapısı + funnel-bucket A/B deneyi (Kova B)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-07-27 17:26:19 +03:00
c0045fa372 Merge pull request 'feat(prefetch): kalici vehicles.fully_fetched kolonu (migration 0034)' (#250) from dev into main 2026-07-25 10:44:19 +03:00
6610bdf720 feat(prefetch): kalıcı vehicles.fully_fetched kolonu (güvenilir tamlık ölçümü)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Redis prefetch:complete:* 21g TTL'li → "% tam çekilmiş"i güvenilir ölçemiyorduk.
Migration 0034: vehicles'a fully_fetched (bool, default false) + fully_fetched_at
(ts) + index. incrementCompleted chain parça ile bitince kalıcı flag'i set eder
(Redis marker'a EK — operasyonel skip mantığı değişmedi). fully_fetched_at her
re-drill tamamlanışında güncellenir (son-doğrulanma). Idempotent SQL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 10:40:40 +03:00
8c1ee15fac Merge pull request 'perf(prefetch): backfill günlük bütçe (storm guard)' (#249) from dev into main 2026-07-16 13:32:47 +03:00
ed8a6ce6e9 perf(prefetch): backfill'e per-source günlük bütçe (storm guard)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
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>
2026-07-16 13:32:34 +03:00
8a4dd6e10e Merge pull request 'chore(proxy): DataImpulse birincil, Floxy default kaldır' (#248) from dev into main 2026-07-16 13:04:30 +03:00
bf834b8f41 chore(proxy): DataImpulse'i birincil yap, Floxy default'unu kaldır
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Floxy kalıcı olarak devre dışı (bakiye/tünel ölü — pcat+emex loglarında sürekli
"Floxy unhealthy → DataImpulse failover"). Floxy-primary her çağrıda önce bir
başarısız deneme yakıp sonra failover ediyordu. DataImpulse zaten kanıtlanmış
çalışıyor (call-leg %97,6).

PCAT_PROXY_PROVIDER ve EMEX_PROXY_PROVIDER default'ları floxy→dataimpulse
(pcat auth + emex browser + emex HTTP ayağı). Floxy hâlâ env ile seçilebilir
(PCAT_PROXY_PROVIDER=floxy / EMEX_PROXY_PROVIDER=floxy); failover makinesi
dormant kalıyor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:04:15 +03:00
bd134bba06 Merge pull request 'perf(pcat): capture widget asset cache — ~%90 capture bant genişliği' (#247) from dev into main 2026-07-16 12:52:59 +03:00
7fd486855f perf(pcat): capture'da widget asset'lerini cache'le — ~%90 capture bant genişliği
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Her JWT capture yeni context (boş cache) açıp aynı widget JS/CSS'ini residential
proxy'den yeniden indiriyordu — ölçüldü ~2,2 MB/capture, %93'ü static (en büyüğü
tüm sitelerde ORTAK `.../v3/bundle_<hash>.js`, 603 KB). ~3-4k capture/gün ile bu
en büyük kaçınılabilir proxy maliyeti (~20 GB/15g), sıfır ban riski.

URL-anahtarlı, disk-destekli (hot in-memory + best-effort disk) cache eklendi.
Sadece versiyonlu static JS/CSS cache'lenir (hash/`?_=` → invalidation otomatik);
token XHR (/v3/api/proxy/*) ve HTML document HER ZAMAN canlı geçer. HIT'te sıfır
proxy byte'ı; MISS'te bir kez route.fetch (context proxy'sinden) + sakla + servis.
PCAT_ASSET_CACHE=false ile redeploysuz kapatılabilir (kill switch).

Doğrulama (autotrade.md + e-trak.ru, gerçek yükleme, 2 ardışık capture):
warm capture canlı trafiği %89-91 düştü VE token XHR ikisinde de atıldı — yani
cache'ten JS servisi widget'ı bozmuyor (geçen capture-blocking olayının tersi).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:47:10 +03:00
8d07d9a31a Merge pull request 'fix(vinpin): blank Dialogys header retries instead of definitive not_found' (#246) from dev into main 2026-07-16 07:59:39 +03:00
fdbaaf375f fix(vinpin): blank Dialogys header → ambiguous (retry), not definitive not_found
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
A blank header region means the Dialogys vehicle result never rendered (transient/
slow), NOT a genuine miss — a decodable Renault (Megane) read "" here and was wrongly
returned not_found (made definitive by the Rpartstore-down gate). Return ambiguous so
the caller's cheap in-session reset retries; a real empty-form miss just exhausts the
bounded retries. A NON-empty unparseable header (old R19's garbled parts screen) stays
a fast definitive not_found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 07:59:32 +03:00
ebbdb22e33 Merge pull request 'chore(compose): VINPIN_RPARTSTORE_ENABLED env ref' (#245) from dev into main 2026-07-16 07:41:23 +03:00
8e4581f1b5 chore(compose): add VINPIN_RPARTSTORE_ENABLED env ref (api+worker)
So the flag reaches the container (Coolify only injects compose-referenced ${VAR}).
Default true = unchanged; set false during a Rpartstore outage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 07:41:16 +03:00
679664d7b0 Merge pull request 'feat(vinpin): VINPIN_RPARTSTORE_ENABLED flag — skip Rpartstore during outage (+pcat capture fix)' (#244) from dev into main 2026-07-16 07:39:43 +03:00
c3ea03db03 feat(vinpin): VINPIN_RPARTSTORE_ENABLED flag skips Rpartstore during known outage
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Rpartstore is DOWN upstream. Every Renault decode still tried to OPEN it
first → launch-error, stuck "Loading application..." app (dirty-resume for
the next decode), ~30-50s burned, and the in-memory down-cooldown resets on
every worker restart so the first decode after each restart repaid the full
cost. That overhead pushed decodes over VINPIN_DECODE_BUDGET_MS → budget
abort → sessionPoisoned → cascade.

Add a persistent kill-switch: VINPIN_RPARTSTORE_ENABLED=false makes BOTH
Renault paths (warm warmRenaultDecode + cold runRenaultFlow) skip opening
Rpartstore entirely and route straight to Dialogys — mirroring the existing
rpartstoreInCooldown() skip but surviving worker restarts. Flag-disabled is
treated as "Rpartstore unavailable" exactly like cooldown, so primaryRan
stays false and a clean Dialogys not_found is definitive (no retry thrash).
Also gated the _warmUp Rpartstore-open so a future warm session with the
flag off pays no launch cost / leaves no stray app.

DEFAULT true (only the exact string "false" disables) → behaviour with the
flag unset is completely unchanged. tsc clean; vinpin unit tests green
(+3 flag tests: warm/cold skip + Dialogys-definitive, and default-true still
attempts Rpartstore).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 07:36:20 +03:00
7ec4fb9297 Merge pull request 'fix(pcat): capture route'unu bilinen-iyi'ye döndür — prod token capture kurtar' (#243) from dev into main
Reviewed-on: #243
2026-07-16 07:00:24 +03:00
6df9c18efb fix(pcat): capture route'unu bilinen-iyi'ye döndür — prod token capture kurtar
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
0f2126d'deki agresif capture blocking (stylesheet + yastatic/google-font/ad
domain'leri) prod'da token capture'ı öldürdü: dataimpulse ile 425→0 capture/
saat, tam deploy anında (03:40 UTC), hepsi capture_timeout. RU katalog widget'ı
init olup /v3/api/proxy XHR'ını atmak için CSS'ine ve Yandex-hosted runtime'ına
ihtiyaç duyuyor. Aynı deploy'da emex (HTML-scrape, widget yok) aynı dataimpulse
üzerinden sağlıklı kaldı → sorun pcat-capture'a özgü.

pcat capture blocking'i orijinaline döndürüldü (image/font/media + tracker'lar)
— resim engellemesi zaten çalışıyordu, korunur. Emex blocking (kanıtlanmış
güvenli) ve ipify gate (PCAT_EXIT_IP_PROBE, kapalı) korunur.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 06:56:09 +03:00
f42f1888ae Merge pull request 'perf(proxy): capture/scrape browser'larında bant genişliği israfını kes' (#242) from dev into main
Reviewed-on: #242
2026-07-16 06:39:49 +03:00
0f2126d694 perf(proxy): capture/scrape browser'larında bant genişliği israfını kes
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
DataImpulse residential kotası hızlı tükeniyordu (15 günde 58.5 GB). CSV
analizi capture/scrape tarayıcılarının tam sayfa (resim/font/CSS/reklam/
CDN) yüklediğini gösterdi.

- pcat + emex capture: resource-blocking'e stylesheet + üçüncü-parti çöp
  (yastatic, google fonts/autofill, adsco.re, displayvertising, tidio,
  ipify) eklendi. Widget runtime CDN'leri (jsdelivr/unpkg) bilinçli hariç.
- emex: engelleme context seviyesine alındı → tüm tab'ları kapsıyor.
- pcat: ipify exit-IP probe artık PCAT_EXIT_IP_PROBE ile default-off
  (15 günde ~54k faturalı istek + 352 MB, sadece telemetri içindi).

Not: emex scraping resmi tarayıcıda yüklemiyor; URL'yi HTML'den parse edip
boyutu ayrı Range GET ile alıyor → resim engellemek scraping'i etkilemez.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:25:51 +03:00
3248ab1c3a Merge pull request 'test(shared): fix normalizeName hyphen test to correct tr-TR output' (#241) from dev into main 2026-07-15 16:55:43 +03:00
a6437dcd67 test(shared): correct normalizeName hyphen test expectation to tr-TR output
The failing test expected normalizeName("ANNA-MARIA")=="Anna-Maria" (dotted i),
but that is internally inconsistent with the suite's own Turkish tests that REQUIRE
the deliberate tr-TR locale: "ALİ YILMAZ"→"Ali Yılmaz" (dotless ı) and "ÇAĞRI"→
"Çağrı". Uppercase Latin "I" (U+0049) is the SAME codepoint as Turkish dotless-I,
so tr-TR lowercases it to "ı" — correct for a TR product (invariant casing would
break every Turkish name: Yilmaz/Çağri, and mangle İ→i̇ with a combining dot). Not
a code bug; expectation corrected to "Anna-Marıa" with an explanatory comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:55:35 +03:00
544b3f25d1 Merge pull request 'fix(vinpin): Rpartstore-down definitive not_found (no budget-burn+poison) + old-Renault tokens + year-cycle' (#240) from dev into main 2026-07-15 15:50:02 +03:00
cc543ace21 fix(vinpin): poll Dialogys header before a definitive not_found (fixA follow-up)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
FIX A made a CLEAN Dialogys not_found definitive on its own while Rpartstore is
down (no retry). But runDialogysSearch inferred not_found from a SINGLE OCR
sample taken after a blind fixed wait — a slow render or a transient OCR glitch
on a DECODABLE Renault read empty at t=afterDialogysSubmit and was mislabelled
not_found, and under FIX A that miss is now terminal (decodeRenaultLocked
returns null, no cheap retry).

Replace the blind wait + single sample with a poll (pollForState) over the
header region for a decodable render, capped at afterDialogysSubmit — mirrors
runRpartstore's poll-then-decide shape. Multi-samples across the same window and
returns early on a hit, so a slow render/OCR glitch no longer produces a false
not_found. Cap unchanged, so a genuine miss consumes no more time than before:
FIX A's no-budget-burn / no-seat-poison guarantee and the definitive-not_found
semantics both hold, and the full-frame fallback is preserved. Working Renault
decodes only get faster (early return). tsc clean; vinpin (119) + extractModelYear
(12) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:48:44 +03:00
c03e7f4079 fix(vinpin): stop budget-burn + seat-poison on undecodable Renault when Rpartstore down
FIX A (runRenaultFlow): when Rpartstore is UNAVAILABLE (down-cooldown or it
never loaded, so `primary` is only a placeholder ambiguous), a CLEAN Dialogys
not_found is now DEFINITIVE. The old gate required BOTH catalogs to say
not_found, so every undecodable Renault while Rpartstore was down got
downgraded to ambiguous → 3x retry → 180s budget → sessionPoisoned, which
then degraded later decodes. A genuinely-ambiguous Dialogys (unreachable)
still retries. When Rpartstore actually ran, both-must-agree is preserved.
runDialogysSearch now logs its outcome + truncated OCR header so this class
is diagnosable from prod logs.

FIX B (vinpin.constants): add old R-number + TR-badge Renault model tokens
(R5/R9/R11/R12/R19/R21/R25, Europa/Broadway/Toros/Flash). R-prefixed form
only — no bare numerics that could false-match year/engine digits.

FIX C (vin-validator extractModelYear): the position-10 year code repeats every
30 years ("T" = 1996 or 2026) with no clean VIN-only rule. New optional
{modelResolved:false} signal: for a brand-only decode of an old-shaped Renault
VIN (Renault WMI + numeric-led VDS type code) whose code pins to the current
cycle's leading edge, roll back one 30-year cycle so a ~1996 R19 isn't labelled
2026. Narrow: model-resolved or modern-shaped VINs are unchanged. Corgi's
WMI-only decoder wired to pass modelResolved:false.

Keeps never-throw / VINPIN_DECODE_BUDGET_MS / sessionPoisoned semantics and the
Fiat + working Renault paths intact. tsc clean; vinpin + corgi + extractModelYear
tests green (new tests cover A and C).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:39:26 +03:00
41f2f1777f Merge pull request 'fix(vinpin): warm Fiat window-fault cold fallback' (#239) from dev into main 2026-07-15 13:56:47 +03:00
d7d78a77a6 fix(vinpin): warm Fiat window-fault falls back to cold (not throw→null)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The last gap: when ensureWarmWindow('fiat') can't bring the Fiat ePER to its VIN
panel (partial Rpartstore-down session leaves it off-panel + re-nav can't recover),
warmDecode threw SessionDropped → re-warm → null, turning a DECODABLE Fiat VIN into
a not_found. Fall back to the proven cold Fiat path instead, so a warm-window fault
never loses a real decode. Completes the FIX2 cold-fallback (previously only the
unusable-parse branch had it; now the can't-reach-panel branch does too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:56:40 +03:00
5a79e94df3 Merge pull request 'fix(vinpin): warm on Fiat+Dialogys anchor (no starvation)' (#238) from dev into main 2026-07-15 13:42:39 +03:00
dac0708cda fix(vinpin): claim warm on Fiat+Dialogys anchor (not full 3-window)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The full 3-window gate never warmed during a prolonged Rpartstore outage, so the
daemon re-attempted warm-up every backoff cycle — each ~2min attempt holds the
single-seat mutex and STARVES real decodes. Anchor warm on Fiat ePER + Dialogys
(the two windows that serve both brands; the Rpartstore-down cooldown routes
Renault to Dialogys anyway, so Rpartstore is an optional bonus). Warm is then
claimed once and HELD (no re-warm loop → no starvation); the Поиск-token foreground
fix makes warm Fiat raise the correct window. Rpartstore rejoins on recovery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:42:31 +03:00
5628cbceaf Merge pull request 'fix(vinpin): claim warm only on full 3-window session' (#237) from dev into main 2026-07-15 13:25:46 +03:00
aa798ec03d fix(vinpin): claim warm ONLY on a full 3-window session (Fiat+Rpartstore+Dialogys)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
A partial warm session (e.g. Rpartstore DOWN → only Fiat+Dialogys) is proven
unstable: re-warm cycles + the missing catalog's launch-error keep knocking the
Fiat window off its VIN panel, so warm Fiat decodes thrash to the 180s budget →
not_found. Require all three windows before this.warm=true; otherwise stay on the
reliable cold path (which decodes Fiat/Renault + cross-brand cleanly). The daemon
backs off + retries, so warm auto-resumes once Rpartstore recovers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:25:39 +03:00