fix(insights): distinguish client-side VIN validation rejects from provider failures #4

Open
root wants to merge 0 commits from fix/insight-vin-validation-distinction into main
Owner

TL;DR

Insight pipeline cmpvfrjgc000114fzc7cdyh66 ("PL24 sağlayıcısında timeout") yanlış çıktı: kullanıcı trial sırasında VIN alanına VW yedek parça numarası girdi (500 907 521, 5Q0 907 521), client-side regex reddetti, provider'a hiç gitmedi. Pipeline yine de P1 provider issue üretti.

Bu PR root cause'u dört katmanda kapatıyor (defense in depth).

Reproducing session

  • Session 019e8400-5204-7d0c-b73d-1e3ed0e22adb (Ahmet üstün, iPhone, trial, /dashboard/search → /)
  • 16:24:51 VIN WVWZZZAUZGW055026 başarılı decode → kategorilerde 3 tur döndü → /'a çıkıp parça numarasını VIN inputuna yazdı → vin_decode_failed × 2, error: "Geçersiz şase numarası. 17 karakter olmalı, I, O, Q harfleri kullanılamaz."
  • LLM (DeepSeek v4-pro, $0.0013) bu yanlış sinyalden P1 provider_quality insight üretti.

Root cause

Katman Bug
tagger.ts vin_decode_failed'ları provider_attempted ?? source ile grupluyordu. provider_attempted yokken source: "landing" (UI lokasyonu) provider sanıldı, 1-provider seti synth edildi → vin_decode_fail_pattern (P1) tag'i.
compress.ts formatCustom whitelist'inde error, source, vin yoktu — LLM gerçek hata mesajını hiç görmedi. Pattern 3 mekanik hipotezi "check provider health" diyordu, koşulsuz.
prompts.ts pickPromptTag vin_decode_fail_pattern varsa direkt provider_quality'ye yönlendiriyordu, input kalitesi kontrolü yoktu.
provider_quality v3 prompt Client-side validation reddi için guardrail yoktu.

Fix (4 katman)

1. tagger.ts

  • isClientValidationReject(e): error regex'i (TR + EN) + VIN şekli kontrolü (≠17 char veya I/O/Q içeriyor).
  • Tüm fail'ler client reject ise → yeni tag vin_decode_client_validation_fail (P3), vin_decode_fail_pattern DEĞİL.
  • Gerçek provider fail'leri artık provider_attempted ZORUNLU (source fallback'i kaldırıldı).

2. compress.ts

  • formatCustom whitelist'ine error, source, vin eklendi → LLM artık tam hatayı görüyor.
  • Pattern 3 mekanik hipotezi 3 dala bölündü: client-reject / real provider fail / single transient. Türkçe açıklama.

3. prompts.ts pickPromptTag

  • vin_decode_client_validation_failux_friction (provider kuralından ÖNCE check ediliyor — spesifik sinyal kazanıyor).

4. provider_quality v4 prompt (yeni versiyon)

  • Açık guardrail: error/source/vin field'larını oku, client-side reddi tespit edersen confidence: 0.15 ile düşür ve hipotezde "Bu provider hatası değil — ux_friction kategorisi" yaz.
  • Gerçek provider verdict için provider_attempted veya upstream endpoint 5xx şartı.

Deploy semantics

seed-runtime.ts::upsertSeedData() worker boot'unda v3'ü deaktive edip v4'ü insert ediyor — manuel SQL gerekmiyor. Tagger + compress değişiklikleri direkt etkili (DB'ye bağımlı değil).

Cleanup

Mevcut yanlış insight cmpvfrjgc000114fzc7cdyh66'yi panel UI'dan dismiss et veya founderSeverityOverride='P3' set et. (Bu PR'da otomatik dismiss yok — geçmiş veriyi temizleme manuel.)

🤖 Generated with Claude Code

## TL;DR Insight pipeline `cmpvfrjgc000114fzc7cdyh66` ("PL24 sağlayıcısında timeout") **yanlış** çıktı: kullanıcı trial sırasında VIN alanına VW yedek parça numarası girdi (`500 907 521`, `5Q0 907 521`), **client-side regex reddetti**, provider'a hiç gitmedi. Pipeline yine de P1 provider issue üretti. Bu PR root cause'u **dört katmanda** kapatıyor (defense in depth). ## Reproducing session - Session `019e8400-5204-7d0c-b73d-1e3ed0e22adb` (Ahmet üstün, iPhone, trial, /dashboard/search → /) - 16:24:51 VIN `WVWZZZAUZGW055026` başarılı decode → kategorilerde 3 tur döndü → `/`'a çıkıp parça numarasını VIN inputuna yazdı → `vin_decode_failed × 2`, error: "Geçersiz şase numarası. 17 karakter olmalı, I, O, Q harfleri kullanılamaz." - LLM (DeepSeek v4-pro, $0.0013) bu yanlış sinyalden P1 `provider_quality` insight üretti. ## Root cause | Katman | Bug | |---|---| | `tagger.ts` | `vin_decode_failed`'ları `provider_attempted ?? source` ile grupluyordu. `provider_attempted` yokken `source: "landing"` (UI lokasyonu) provider sanıldı, 1-provider seti synth edildi → `vin_decode_fail_pattern` (P1) tag'i. | | `compress.ts` | `formatCustom` whitelist'inde `error`, `source`, `vin` yoktu — LLM gerçek hata mesajını hiç görmedi. Pattern 3 mekanik hipotezi "check provider health" diyordu, koşulsuz. | | `prompts.ts pickPromptTag` | `vin_decode_fail_pattern` varsa direkt `provider_quality`'ye yönlendiriyordu, input kalitesi kontrolü yoktu. | | `provider_quality` v3 prompt | Client-side validation reddi için guardrail yoktu. | ## Fix (4 katman) ### 1. `tagger.ts` - `isClientValidationReject(e)`: `error` regex'i (TR + EN) + VIN şekli kontrolü (≠17 char veya I/O/Q içeriyor). - Tüm fail'ler client reject ise → **yeni tag** `vin_decode_client_validation_fail` (P3), `vin_decode_fail_pattern` DEĞİL. - Gerçek provider fail'leri artık `provider_attempted` ZORUNLU (source fallback'i kaldırıldı). ### 2. `compress.ts` - `formatCustom` whitelist'ine `error`, `source`, `vin` eklendi → LLM artık tam hatayı görüyor. - Pattern 3 mekanik hipotezi 3 dala bölündü: client-reject / real provider fail / single transient. Türkçe açıklama. ### 3. `prompts.ts pickPromptTag` - `vin_decode_client_validation_fail` → `ux_friction` (provider kuralından ÖNCE check ediliyor — spesifik sinyal kazanıyor). ### 4. `provider_quality` v4 prompt (yeni versiyon) - Açık guardrail: error/source/vin field'larını oku, client-side reddi tespit edersen `confidence: 0.15` ile düşür ve hipotezde "Bu provider hatası değil — ux_friction kategorisi" yaz. - Gerçek provider verdict için `provider_attempted` veya upstream endpoint 5xx şartı. ## Deploy semantics `seed-runtime.ts::upsertSeedData()` worker boot'unda v3'ü deaktive edip v4'ü insert ediyor — manuel SQL gerekmiyor. Tagger + compress değişiklikleri direkt etkili (DB'ye bağımlı değil). ## Cleanup Mevcut yanlış insight `cmpvfrjgc000114fzc7cdyh66`'yi panel UI'dan dismiss et veya `founderSeverityOverride='P3'` set et. (Bu PR'da otomatik dismiss yok — geçmiş veriyi temizleme manuel.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
root added 1 commit 2026-06-01 23:14:11 +03:00
Reproducing insight cmpvfrjgc000114fzc7cdyh66 (a P1 "PL24 timeout" false
positive): trial user typed VW part numbers ("500 907 521", "5Q0 907 521")
into the VIN field on /; client-side regex rejected them with "Geçersiz şase
numarası. 17 karakter olmalı". No provider was called. The pipeline still
tagged the session as `vin_decode_fail_pattern`, routed to `provider_quality`,
and the LLM dutifully invented a PL24 outage.

Root cause spans three files:

1. tagger.ts grouped vin_decode_failed by `provider_attempted ?? source`. When
   `provider_attempted` is missing, `source: "landing"` (a UI location) was
   treated as a provider name, so a 1-provider set was synthesized and
   `vin_decode_fail_pattern` (P1) was emitted.

2. compress.ts formatCustom whitelist excluded `error`, `source`, `vin`. The
   LLM therefore never saw "Geçersiz şase numarası" or the offending input.
   Pattern 3 mechanical hypothesis told it "check provider health" regardless.

3. prompts.ts pickPromptTag routed any `vin_decode_fail_pattern` straight to
   `provider_quality` with no input-quality check, and the v3 system prompt
   had no guardrail for client-side validation rejects.

Fix:
- tagger: detect client-side rejects by `error` regex (Turkish + English) and
  by VIN shape (length != 17 or contains I/O/Q). When all fails are client
  rejects, emit new tag `vin_decode_client_validation_fail` at P3 instead of
  `vin_decode_fail_pattern` at P1. Real provider failures now require
  `provider_attempted` to be set (no more `source` fallback).
- compress: add `error`, `source`, `vin` to the formatCustom property
  whitelist so the LLM can see the actual failure context. Split Pattern 3
  into client-reject vs. real-provider-failure branches with distinct
  Turkish hypotheses.
- prompts: route `vin_decode_client_validation_fail` to `ux_friction` before
  the provider rule. Ship provider_quality v4 with an explicit guardrail
  instructing the model to return confidence ≤0.15 and reclassify when the
  inlined event properties show client-side rejection.

The seed-runtime upsert path deactivates the active v3 template on next
worker boot and inserts v4 in its place — no manual SQL needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This branch is already included in the target branch. There is nothing to merge.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/insight-vin-validation-distinction:fix/insight-vin-validation-distinction
git checkout fix/insight-vin-validation-distinction
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: root/sp#4