fix(olcum): otomatik üretimin kaynağı gerçek yüzeyden gelir (G3)

/sonuc'taki otomatik üretim kaynağı sabit "giris_donusu" yazıyordu; oysa
?sihirbaz=1 üreten hiçbir yer giriş dönüşü değil — gerçek giriş dönüşü
/listem'e doğrudan iniyor, /sonuc'a uğramıyor. Hero, CTA formu, arama kartı,
+ kapısı, boş liste ekranı ve rapor-yok CTA'sı tek yanlış kovada
toplanıyordu, yani P2'nin ölçmek istediği soru ölçülmüyordu.

Etiket artık ?sihirbaz=1'i üreten tarafta kuruluyor ve /sonuc üzerinden
geçiriliyor; kapıdan gelenler tetikten türetiliyor. giris_donusu yalnız
gerçek callback yolunda kalıyor.

Güvenlik denetimi G3 · docs/ekip-raporlari/2026-09-22/07-guvenlik-denetimi.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-22 01:45:31 +03:00
parent 84fc88e230
commit bca1849eee
9 changed files with 48 additions and 6 deletions

View File

@@ -32,6 +32,7 @@ export default function SonucPage({
sira?: string; sira?: string;
tur?: string; tur?: string;
sihirbaz?: string; sihirbaz?: string;
kaynak?: string;
hazir?: string; hazir?: string;
il?: string | string[]; il?: string | string[];
tip?: string; tip?: string;
@@ -122,6 +123,7 @@ export default function SonucPage({
sira={sira} sira={sira}
tur={turKey} tur={turKey}
otomatikAc={params.sihirbaz === "1"} otomatikAc={params.sihirbaz === "1"}
kaynak={params.kaynak}
secimlerHazir={params.hazir === "1"} secimlerHazir={params.hazir === "1"}
/> />

View File

@@ -91,7 +91,7 @@ export function AramaFunnelKarti({ onSec }: { onSec: () => void }) {
return; return;
} }
if (profil) { if (profil) {
router.push(sonucHref({ sihirbaz: "1" })); router.push(sonucHref({ sihirbaz: "1", kaynak: "arama" }));
return; return;
} }
// Yapay Zeka niyetli CTA: kapı tamamlanınca /sonuc'ta üretim akışı devam // Yapay Zeka niyetli CTA: kapı tamamlanınca /sonuc'ta üretim akışı devam

View File

@@ -298,11 +298,24 @@ export function programEkleIste(opts: {
/** Profil yazıldıktan sonra; bekleyen hedef varsa yönlendirme href'i döner. /** Profil yazıldıktan sonra; bekleyen hedef varsa yönlendirme href'i döner.
* `girisli`, "sihirbaz" hedefinde üretimin otomatik başlayıp başlamayacağını * `girisli`, "sihirbaz" hedefinde üretimin otomatik başlayıp başlamayacağını
* belirler (kapı bileşeni oturumu facet isteğiyle paralel çözer). */ * belirler (kapı bileşeni oturumu facet isteğiyle paralel çözer). */
/** Kapı tetiği → üretim denemesinin ölçüm etiketi. Yalnız `hedef="sihirbaz"`
* dalında kullanılır; listede olmayan tetik `bilinmiyor` sayılır (izinli
* değerlerin tek kaynağı: rapor/components/uretim-basladi-olayi.tsx). */
const KAYNAK_TETIKTEN: Partial<Record<KapiTetik, string>> = {
arti: "arti",
arti_toast: "arti",
listem_bos: "bos_durum",
sihirbaz: "sihirbaz",
sonuc: "sonuc_kapi",
};
export function profilTamamlandi( export function profilTamamlandi(
profil: TercihProfili, profil: TercihProfili,
girisli = false, girisli = false,
): string | null { ): string | null {
const duzenle = durum.duzenle; const duzenle = durum.duzenle;
// durum aşağıda sıfırlanıyor (tetik: "diger"); ölçüm etiketi için önce yakala
const tetik = durum.tetik;
riskleriYenile(profil); riskleriYenile(profil);
const bekleyen = durum.bekleyen; const bekleyen = durum.bekleyen;
const hedef = durum.bekleyenHedef; const hedef = durum.bekleyenHedef;
@@ -332,7 +345,15 @@ export function profilTamamlandi(
// Yapay Zeka niyetli giriş: kapıda sihirbazı zaten doldurdu, /sonuc'ta // Yapay Zeka niyetli giriş: kapıda sihirbazı zaten doldurdu, /sonuc'ta
// akış kesilmesin — girişliyse üretim otomatik başlar (?sihirbaz=1), // akış kesilmesin — girişliyse üretim otomatik başlar (?sihirbaz=1),
// girişsizse ücretsiz tablo + giriş CTA paneli (?hazir=1) karşılar. // girişsizse ücretsiz tablo + giriş CTA paneli (?hazir=1) karşılar.
return sonucHref(girisli ? { sihirbaz: "1" } : { hazir: "1" }); //
// `kaynak`: girişli dalda /sonuc üretimi kendiliğinden başlatıyor, yani
// denemeyi başlatan yüzey bu kapıdır. Tetikten türetiliyor ki hero ya da
// arama kartıyla aynı kovaya düşmesin (bkz. uretim-basladi-olayi.tsx).
return sonucHref(
girisli
? { sihirbaz: "1", kaynak: KAYNAK_TETIKTEN[tetik] ?? "bilinmiyor" }
: { hazir: "1" },
);
} }
if (hedef === "sonuc") return sonucHref(); if (hedef === "sonuc") return sonucHref();
// Düzenleme /sonuc'a götürür: tablo profil çerezinden SSR'lanır // Düzenleme /sonuc'a götürür: tablo profil çerezinden SSR'lanır

View File

@@ -21,7 +21,7 @@ export function RaporYokCta() {
asChild asChild
className="h-11 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600" className="h-11 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
> >
<Link href={profil ? sonucHref({ sihirbaz: "1" }) : "/"}> <Link href={profil ? sonucHref({ sihirbaz: "1", kaynak: "rapor_yok" }) : "/"}>
24&apos;lük listeni oluştur 24&apos;lük listeni oluştur
<ArrowRight className="size-4" aria-hidden /> <ArrowRight className="size-4" aria-hidden />
</Link> </Link>

View File

@@ -25,9 +25,18 @@ import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
const IZINLI_KAYNAKLAR = new Set([ const IZINLI_KAYNAKLAR = new Set([
"sihirbaz", "sihirbaz",
"hazir_cta", "hazir_cta",
// Gerçek giriş dönüşü: /giris callback'i doğrudan /listem'e iner.
"giris_donusu", "giris_donusu",
"bos_durum", "bos_durum",
"odeme_sonrasi", "odeme_sonrasi",
// /sonuc'ta ?sihirbaz=1 ile otomatik başlayan üretimin gerçek yüzeyleri.
// Eskiden hepsi "giris_donusu" sayılıyordu (güvenlik denetimi G3).
"hero",
"cta",
"arama",
"arti",
"sonuc_kapi",
"rapor_yok",
"bilinmiyor", "bilinmiyor",
]); ]);

View File

@@ -169,7 +169,7 @@ export function CtaSiraForm({
const yeniProfil = { sira, tur, secimler }; const yeniProfil = { sira, tur, secimler };
tercihProfiliYaz(yeniProfil); tercihProfiliYaz(yeniProfil);
setAcik(false); setAcik(false);
router.push(sonucHref(girisli ? { sihirbaz: "1" } : { hazir: "1" })); router.push(sonucHref(girisli ? { sihirbaz: "1", kaynak: "cta" } : { hazir: "1" }));
} }
function acikDegisti(v: boolean) { function acikDegisti(v: boolean) {

View File

@@ -101,7 +101,7 @@ export function HeroForm() {
const profil = { sira, tur, secimler }; const profil = { sira, tur, secimler };
tercihProfiliYaz(profil); tercihProfiliYaz(profil);
setModalAcik(false); setModalAcik(false);
router.push(sonucHref(girisli ? { sihirbaz: "1" } : { hazir: "1" })); router.push(sonucHref(girisli ? { sihirbaz: "1", kaynak: "hero" } : { hazir: "1" }));
} }
// Kayıtlı profil varsa sihirbazı atla, doğrudan sonuç sayfasına götür. // Kayıtlı profil varsa sihirbazı atla, doğrudan sonuç sayfasına götür.

View File

@@ -12,11 +12,14 @@ export async function SonucSihirbazi({
sira, sira,
tur, tur,
otomatikAc, otomatikAc,
kaynak,
secimlerHazir, secimlerHazir,
}: { }: {
sira: number; sira: number;
tur: PuanTuruKey; tur: PuanTuruKey;
otomatikAc: boolean; otomatikAc: boolean;
/** ?sihirbaz=1'i üreten yüzey; otomatik üretimin ölçüm etiketi olur. */
kaynak?: string;
secimlerHazir: boolean; secimlerHazir: boolean;
}) { }) {
// Kullanıcının kayıtlı raporu var mı? Yalnızca CTA'nın hangi hâlde render // Kullanıcının kayıtlı raporu var mı? Yalnızca CTA'nın hangi hâlde render
@@ -35,6 +38,7 @@ export async function SonucSihirbazi({
girisliMi={Boolean(session)} girisliMi={Boolean(session)}
raporVar={Boolean(raporSatiri?.result)} raporVar={Boolean(raporSatiri?.result)}
otomatikAc={otomatikAc} otomatikAc={otomatikAc}
kaynak={kaynak}
secimlerHazir={secimlerHazir} secimlerHazir={secimlerHazir}
/> />
); );

View File

@@ -58,6 +58,7 @@ export function SihirbazBolumu({
girisliMi, girisliMi,
raporVar, raporVar,
otomatikAc, otomatikAc,
kaynak,
secimlerHazir, secimlerHazir,
}: { }: {
sira: number; sira: number;
@@ -68,6 +69,7 @@ export function SihirbazBolumu({
raporVar: boolean; raporVar: boolean;
/** ?sihirbaz=1 ile gelindiyse modalı otomatik aç */ /** ?sihirbaz=1 ile gelindiyse modalı otomatik aç */
otomatikAc?: boolean; otomatikAc?: boolean;
kaynak?: string;
/** ?hazir=1 (hero sihirbazından girişsiz geliş): seçimler storage'da, /** ?hazir=1 (hero sihirbazından girişsiz geliş): seçimler storage'da,
panel giriş CTA'sına döner. URL'den gelir ki SSR'da doğru panel panel giriş CTA'sına döner. URL'den gelir ki SSR'da doğru panel
render edilsin. */ render edilsin. */
@@ -222,7 +224,11 @@ export function SihirbazBolumu({
if (otomatikGonder && !otomatikBasladi.current) { if (otomatikGonder && !otomatikBasladi.current) {
otomatikBasladi.current = true; otomatikBasladi.current = true;
// replace: geri tuşu bu sayfaya dönünce tekrar /listem'e sekmesin // replace: geri tuşu bu sayfaya dönünce tekrar /listem'e sekmesin
router.replace(listemUretHref("giris_donusu")); // Etiket ?sihirbaz=1'i ÜRETEN yüzeyden gelir. Eskiden burası sabit
// "giris_donusu" yazıyordu; oysa gerçek giriş dönüşü /listem'e
// doğrudan iner, /sonuc'a hiç uğramaz — hero, katalog, arama, + ve
// ödeme-sonrası tek yanlış kovada toplanıyordu.
router.replace(listemUretHref(kaynak ?? "bilinmiyor"));
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [otomatikGonder]); }, [otomatikGonder]);