VIC — Valora Intelligence Center

WhatsApp üzerinden çalışan, danışman-öncelikli gayrimenkul CRM'i. Müşteri chatbot'u değil — danışmanın cebindeki ofis.

01 Genel Bakış

Emlak danışmanları bilgisayar başına geçmeden tüm iş süreçlerini WhatsApp'tan halletsin: portföy ekle/güncelle, talep eşleştir, gösterim ayarla, teklif/pazarlık yönet, komisyon hesapla.

21
Postgres tablosu
10
Edge Function
~9.4K
Deno/TS LOC
5
Fine-tuning round
5
RLS katmanı
4
Cron job
3
Konuşma modu
<3s
E2E response

Tasarım Felsefesi

1. LLM asla state sahibi değil. Mekanik DB state ground truth. LLM sadece yargı + dil.
2. Danışman-öncelikli. Müşteri için kapalı; danışman tam yetkili. Müşteri herhangi bir aksiyon için danışmanına yönlendirilir.
3. Sıfır sunucu, sıfır container. Tamamı serverless Edge Function + `pg_cron`. Supabase + WhatsApp Cloud + Vertex AI üçlüsü yeter.
4. Async batch + idempotency claim. Webhook hızla 200 OK döner, gerçek iş kuyruğa düşer, atomic claim ile tek cevap.

02 Teknoloji Stack

WhatsApp Cloud API

MesajlaşmaVerified

Business Solution Provider, system user token (kalıcı). Webhook → Edge Function. Phone Number ID + WABA ID üzerinden tüm template + freeform mesaj akışı.

Supabase (Postgres + Edge)

BackendServerless

10 Edge Function (Deno runtime), 21 tablo, RLS aktif, Storage bucket (portfolio-media, documents), Vault (secrets), pg_cron + pg_net extensions.

Vertex AI fine-tuned Gemini

LLMCustom

Gemini 2.5 Flash 5 round eğitildi (R5: 482 train + 54 val + 237 DPO pair). OAuth refresh token + token cache. Birincil model.

Gemini 2.5 Flash (fallback)

LLM Fallback

Vertex 5xx/timeout durumunda devreye girer. Aynı sistem prompt, daha agresif post-processing.

pg_cron + pg_net

Scheduler

Postgres içinden HTTP POST ile Edge Function tetikleme. 4 farklı cron job (1dk, 1dk, 15dk, haftalık).

GCP OAuth + Vault

Auth

Refresh token ile Vertex erişimi. Service role key Vault'ta. pg_cron job'ları içinden çağrıda secret çekiliyor.

Hesap Haritası

AyloMaylo (****@****.com) └── Supabase: valora-vic (****, eu-central-1) └── GCP: VIC-Valora (Vertex AI fine-tuning, europe-west4) Can Gokturk (****@****.com) └── Meta Developer App: valora-vic Valora Gayrimenkul (****@****.com) └── Meta Business Portfolio + WABA └── **** *** ** ** (WhatsApp Cloud, verified)

03 Mimari — Data Flow

Webhook ultra-hızlı row INSERT eder, gerçek iş cron-driven batch processor'da olur. Tüm fonksiyonlar service role key ile DB'ye yazar.

flowchart TB WA[WhatsApp Cloud API] WH[whatsapp-webhook
363 LOC] PM[process-messages
1000 LOC] VR[vic-respond
2348 LOC
Vertex + Flash] PFM[portfolio-manage
874 LOC] DM[deal-manage
1933 LOC] MN[match-notify
674 LOC] PC[proactive-check
756 LOC] SE[session-extract
477 LOC] SM[send-message
340 LOC] AWT[admin-wa-templates
417 LOC] DB[(Postgres
21 tables)] CRON{{pg_cron
1dk · 1dk · 15dk · weekly}} WA -->|inbound webhook| WH WH -->|INSERT row| DB WH -.->|200 OK <1s| WA CRON -->|1dk| PM CRON -->|1dk| SE CRON -->|15dk| PC CRON -->|weekly Sat| PC PM -->|batch per contact| VR PM -->|portfolio intent| PFM PM -->|deal intent| DM PM -->|interactive_button| DM PM -->|interactive_button| PFM DB -.->|trigger: portfolio/request new| MN DB -.->|trigger: deal status change| DM DB -.->|trigger: portfolio deactivate| DM VR --> SM PFM --> SM DM --> SM MN --> SM PC --> SM SM -->|outbound API call| WA SM -->|INSERT outbound| DB AWT -.->|external admin| WA classDef func fill:#1c2128,stroke:#58a6ff,color:#e6edf3 classDef store fill:#1c2128,stroke:#bc8cff,color:#e6edf3 classDef ext fill:#1c2128,stroke:#3fb950,color:#e6edf3 class WH,PM,VR,PFM,DM,MN,PC,SE,SM,AWT func class DB,CRON store class WA ext

Async Batch Pattern (kritik)

Naif yaklaşım: her webhook'ta LLM çağır → kullanıcı 5 mesaj atınca 5 cevap döner. VIC'in çözümü:

webhook → INSERT message_log row (processed_at = NULL) → 200 OK in <1s │ ▼ pg_cron (her 1dk) → process-messages 1. Pending contact'ları bul 2. 5sn batch window (sleep) 3. Atomic claim: UPDATE WHERE processed_at IS NULL RETURNING 4. Per-contact tüm pending mesajları tek prompta ver 5. Tek LLM çağrısı → tek cevap
Idempotency claim discipline: processed_at pipeline metadata değil — claim column. UPDATE ... WHERE claimed_at IS NULL RETURNING race condition'sız batch effect üretir. Bu pattern her kuyruk akışında tekrarlanır (notification nudge dedupe, session extraction, vs).

04 ERD — Entity Relationship Diagram

21 tablo, ~38 foreign key constraint. Live Supabase schema'dan üretildi. Tam çözünürlük + pan/zoom için yeni sekmede aç.

🗺️ ERD'yi Tam Ekran Aç (yeni sekme) Pan + zoom + PNG/SVG indir · 21 tablo · attribute-rich
erDiagram contacts { uuid id PK varchar phone UK varchar bsuid UK varchar contact_type "identity" varchar onboarding_status bool consent_transfer "KVKK" } agent_profiles { uuid contact_id PK varchar role "agent broker admin owner" varchar territory } portfolios { varchar id PK "2026-001" uuid agent_id FK varchar type varchar listing_type varchar district bigint listed_price varchar status } portfolio_confidential { varchar portfolio_id PK varchar owner_name bigint min_price "dip fiyat" numeric commission_rate } portfolio_media { uuid id PK varchar portfolio_id FK text storage_path } portfolio_history { uuid id PK varchar portfolio_id FK varchar event_type jsonb old_value jsonb new_value } requests { uuid id PK uuid customer_id FK varchar request_type jsonb criteria varchar status } sessions { uuid id PK uuid contact_id FK text status text intent text district numeric max_price } market_signals { uuid id PK text query_raw int match_count "0 means signal" } portfolio_request_matches { uuid id PK varchar portfolio_id FK uuid request_id FK smallint match_score bool notified } showings { uuid id PK varchar portfolio_id FK uuid agent_id FK uuid customer_id FK uuid buying_agent_id FK timestamptz scheduled_at varchar status } deals { uuid id PK varchar portfolio_id FK uuid listing_agent_id FK uuid buying_agent_id FK varchar status bigint offer_price bigint commission_total } deal_confirmations { uuid id PK uuid deal_id FK varchar role bool confirmed } deal_negotiations { uuid id PK uuid deal_id FK bigint amount varchar status } platform_commissions { uuid id PK uuid deal_id FK varchar role numeric rate bigint amount } agent_visibility { uuid id PK varchar portfolio_id FK uuid agent_id FK varchar visibility_level } message_log { uuid id PK uuid from_contact FK uuid to_contact FK varchar message_type text content timestamptz processed_at "CLAIM" } interaction_tracker { uuid contact_id PK timestamptz last_inbound timestamptz nudge_sent_at } pending_notifications { uuid id PK uuid contact_id FK text body timestamptz delivered_at } tasks { uuid id PK uuid agent_id FK varchar task_type timestamptz due_at } documents { uuid id PK varchar type uuid deal_id FK text file_url } contacts ||--o| agent_profiles : "1:1 authority" contacts ||--|| interaction_tracker : "24h window" contacts ||--o{ portfolios : "agent_id" portfolios ||--o| portfolio_confidential : "broker-only" portfolios ||--o{ portfolio_media : "" portfolios ||--o{ portfolio_history : "audit" contacts ||--o{ requests : "customer_id" contacts ||--o{ sessions : "" sessions ||--o{ message_log : "" sessions }o--o| requests : "extraction" contacts ||--o{ market_signals : "" portfolios ||--o{ portfolio_request_matches : "matcher" requests ||--o{ portfolio_request_matches : "matcher" portfolio_request_matches ||--o{ deals : "match_id" portfolios ||--o{ deals : "" requests ||--o{ deals : "" contacts ||--o{ deals : "listing+buying" portfolios ||--o{ showings : "" requests ||--o{ showings : "" contacts ||--o{ showings : "agent+customer" deals ||--o{ showings : "" deals ||--o{ deal_confirmations : "dual" deals ||--o{ deal_negotiations : "ping-pong" deals ||--o{ platform_commissions : "split" deals ||--o{ agent_visibility : "unlock" portfolios ||--o{ agent_visibility : "" contacts ||--o{ message_log : "from+to" contacts ||--o{ pending_notifications : "" contacts ||--o{ tasks : "" portfolios ||--o{ tasks : "" contacts ||--o{ documents : "" portfolios ||--o{ documents : "" showings ||--o{ documents : "" deals ||--o{ documents : ""

Mantıksal Gruplar

👤 Identity

contacts agent_profiles interaction_tracker

Kullanıcı kimliği + agent yetki hiyerarşisi + WhatsApp 24h window state.

🏢 Portföy

portfolios portfolio_confidential portfolio_media portfolio_history

İlan ana tablosu + gizli alanlar (dip fiyat, sahip) + medya + audit log.

🎯 Talep & Eşleştirme

requests portfolio_request_matches market_signals

Müşteri talepleri + deterministic SQL match sonuçları + boşa düşen sorgular.

🤝 Deal Pipeline

deals deal_confirmations deal_negotiations showings

State machine + dual confirmation + ping-pong pazarlık + gösterim akışı.

💰 Komisyon

platform_commissions agent_visibility

Per-party komisyon split + deal kapanınca agent görünürlük artırımı.

💬 Conversation

message_log sessions pending_notifications

Tüm mesaj kaydı + 90sn-grouped session + 24h window queue.

📋 Operations

tasks documents

VIC'in kendine çıkardığı task'lar + sözleşme/sertifika dosyaları.

🔐 Onboarding

contacts kolonları: onboarding_status, terms_accepted_at, consent_transfer, consent_training, consent_marketing

KVKK Md. 9 açık rıza state machine. Cross-border (Gemini abroad) zorunlu, training/marketing opsiyonel.

05 Tablolar — Tam Schema

Her tablo card'ına tıkla, kolon detayları açılır. Live Supabase'den çekildi (****).

06 Edge Functions

Hepsi Deno runtime, hepsi service role key ile DB'ye yazar. verify_jwt internal olanlarda kapalı (production scar #1, bkz. §11).

whatsapp-webhook

363 LOCverify_jwt: ON

WhatsApp Cloud API webhook endpoint. INSERT message_log + upsert contacts (BSUID → phone fallback). 200 OK in <1s zorunlu (Meta retry policy).

  • Status update handling (delivered/read)
  • Reaction filter (LLM'e gönderilmez)
  • Voice → media_url, transcript flow'a girer

process-messages

1000 LOCverify_jwt: OFFcron 1dk

Async batch processor. Pending mesajları atomic claim, per-contact batch, intent router.

  • 5sn batch window
  • Interactive button dispatch (stateful)
  • Slash command routing (/davet, /yardim, vs.)
  • Portfolio / deal / conversation flow ayrıştırması

vic-respond

2348 LOCverify_jwt: OFF

LLM yanıt motoru. Vertex AI fine-tuned (birincil) + Gemini 2.5 Flash (fallback). 3 mod: agent / customer / unknown.

  • FIRST vs ONGOING prompt ayrımı (caching dostu)
  • İngilizce system prompt (tokenizer optimal)
  • Portfolio context injection
  • Truncation guard (cümle sonuna keser)
  • Post-processing: intro strip, isim seyrekleştirme

portfolio-manage

874 LOCverify_jwt: OFF

Portföy CRUD: create / update / deactivate / reactivate / list / detail / summary. Gemini ile structured data extraction.

  • Insan dostu ID: 2026-001 (SQL function)
  • CREATE: full listing data extraction
  • UPDATE: diff extraction
  • portfolio_history audit log her CRUD'da
  • portfolio_confidential broker+ modda yazılabilir

deal-manage

1933 LOCverify_jwt: OFF

Deal state machine: matched → showing_scheduled → shown → offer → closed_won/lost. Ping-pong pazarlık. Cross-agent notification.

  • handleSetOffer (her teklif counterpart'a iletilir)
  • handleStatusChanged trigger
  • handleShowingRequest / Confirm / Reschedule
  • parseDateTimeText (gün adı, dönem, buçuk, voice)
  • Duplicate showing guard

match-notify

674 LOCverify_jwt: OFF

Yeni portföy / talep → SQL match → bildirim. DB trigger'dan tetiklenir.

  • find_matching_portfolios RPC
  • find_matching_requests RPC
  • property_type hard filter (F7 fix)
  • 24h window dışıysa pending_notifications kuyruğu

proactive-check

756 LOCverify_jwt: OFFcron 15dk + Sat

5 nudge sinyali + haftalık broker raporu. Her sinyal kendine ait RPC.

  • 3-day stale followup
  • Showing reminder customer (T-2h)
  • Showing reminder agent (T-1h)
  • Unactioned matches (24h+)
  • Stale deal check (7d+)

session-extract

477 LOCverify_jwt: OFFcron 1dk

90sn inaktif session'ı kapatır, Gemini Flash ile intent + entities çıkartır.

  • close_stale_sessions() SQL function
  • sessions → top-level indexed (intent, district, max_price)
  • Yeni request INSERT (criteria JSONB)
  • market_signals (boşa düşen sorgu)

send-message

340 LOCverify_jwt: OFF

Tek outbound noktası. WA Cloud API çağrısı + INSERT outbound row.

  • 24h window kontrolü
  • Template fallback (window kapalıysa)
  • Interactive buttons / lists / CTA
  • Body sanitization (\n, \t guard)

admin-wa-templates

417 LOCverify_jwt: ON

Template yönetimi: create / list / delete. Meta WhatsApp Graph API wrapper. Admin-only.

  • conversational_automation config (welcome msg + ice breakers + commands)
  • Template body validation (newline/tab guard)
  • allow_category_change: false

07 Cron Jobs & Triggers

Postgres içinden pg_cron + pg_net ile HTTP POST. Authentication için vault.decrypted_secrets'tan service_role_key çekilir.

Scheduled Jobs

-- F4b: Proactive checks
SELECT cron.schedule('proactive-check', '*/15 * * * *',
  $$ SELECT trigger_proactive_check('regular'); $$
);

-- Weekly broker report (Saturday 10:00 TR)
SELECT cron.schedule('weekly-report', '0 7 * * 6',
  $$ SELECT trigger_proactive_check('weekly_report'); $$
);

-- F7b: Session extraction (90sn idle threshold)
SELECT cron.schedule('extract-closed-sessions', '*/1 * * * *',
  $$ SELECT trigger_session_extraction(); $$
);

-- F2: Process inbound messages
SELECT cron.schedule('process-messages', '*/1 * * * *',
  $$ SELECT trigger_process_messages(); $$
);

DB Triggers

-- Yeni / reactive portföy → match-notify
CREATE TRIGGER trg_match_on_portfolio
  AFTER INSERT OR UPDATE ON portfolios
  FOR EACH ROW EXECUTE FUNCTION trigger_match_portfolio();

-- Yeni talep → match-notify
CREATE TRIGGER trg_match_on_request
  AFTER INSERT ON requests
  FOR EACH ROW EXECUTE FUNCTION trigger_match_request();

-- Deal status değişti → deal-manage notification
CREATE TRIGGER trg_deal_status_change
  BEFORE UPDATE ON deals
  FOR EACH ROW EXECUTE FUNCTION trigger_deal_status_change();

-- Portföy deaktive ediliyor + aktif deal var → deal-manage uyarısı
CREATE TRIGGER trg_portfolio_deal_check
  BEFORE UPDATE ON portfolios
  FOR EACH ROW EXECUTE FUNCTION trigger_portfolio_deal_check();

SECURITY DEFINER RPC'ler

  • find_matching_portfolios(district, ptype, type, rooms, min, max, currency) — talep → ilan
  • find_matching_requests(district, ptype, listing_type, rooms, price, currency) — ilan → talep
  • find_stale_followups() — 3 gün cevapsız müşteri
  • find_showing_reminders_customer/agent() — T-2h / T-1h
  • find_unactioned_matches() — 24h+ aksiyon alınmamış match
  • find_stale_deals() — 7 gün inaktif aktif deal
  • find_unconfirmed_deals() — 48h+ confirmation bekleyen
  • close_stale_sessions() — 90sn idle session kapatır
  • generate_portfolio_id()2026-001 formatı

08 LLM Mimarisi

İki katmanlı model — fine-tuned Vertex AI birincil, base Flash fallback. Prompt İngilizce, code-level enforcement zorunlu.

İki Katmanlı LLM

vic-respond input │ ├─→ Vertex AI fine-tuned (vic-tuned-v5) │ endpoint: **** │ project: **** / europe-west4 │ OAuth refresh token + cache │ │ ┌─ success → post-processing → return │ └─ 5xx / timeout → fallback ↓ │ └─→ Gemini 2.5 Flash (base) API key aynı system prompt daha agresif post-processing

Fine-Tuning Round'ları

R1 + R2

SUCCEEDED

Prompt tuning baseline. SFT 209 konuşma, 1426 turn.

R3

SUCCEEDED

261 train + 29 val. Identity leak fix, thinking token sızıntısı, refusal nuance.

R4

SUCCEEDED

278 train + 31 val. Gerçek WA konuşmalarından düzeltmeler. Agent portfolio access. tunedModelDisplayName API değişikliği.

R5 (production)

3-model synth

482 train + 54 val. Claude+ChatGPT+Gemini sentetik. Anti-fabrication, persona çeşitleme, çoklu dil, 237 DPO pair (henüz SFT-only).

Prompt Yapısı

  • İngilizce system prompt — tokenizer + IF performansı için optimal
  • FIRST vs ONGOING ayrı prompt — LLM "Merhaba ben VIC" demesin diye kod-seviyesi tanıtma kontrolü
  • Implicit caching uyumlu — sabit kısım önce, dynamic context sonra
  • 3 mod:
    • agent — full portföy + komisyon + diğer agent'ların ilanları
    • customer — filtered, qualifying flow
    • unknown — onboarding / KVKK flow
  • Truncation guard — sadece MAX_TOKENS finish reason'da tetiklenir, son .!? karakterine keser
  • Refusal kuralları — legal/tax + competitor → kod reddi; genel şirket bilgisi → LLM cevap
  • Refusal nuance — kişiye özel bilgi → danışmana yönlendir; genel kamu bilgisi → VIC cevap verebilir

Code-Level Enforcement (LLM'e güvenmek yasak)

  • Post-processing: intro strip ("Merhaba!" temizliği), filler temizliği, isim seyrekleştirme
  • Reaction filter: emoji-only reaction mesajları LLM'e gitmez
  • Interactive button routing: button click'ler regex match → dedicated handler
  • Telefon numarası uydurma engeli (regex check)
  • Duplicate showing guard

09 İş Akışları

İki örnek: portföy ekleme (LLM extraction) ve cross-agent deal pazarlığı (state machine).

Akış 1 — Portföy Ekleme (Agent)

sequenceDiagram participant A as Agent (WhatsApp) participant WH as whatsapp-webhook participant PM as process-messages participant PFM as portfolio-manage participant G as Gemini Flash participant DB as Postgres A->>WH: "yeni ilan: Bornova 3+1 daire 12M" WH->>DB: INSERT message_log
(processed_at=NULL) WH-->>A: 200 OK Note over PM: pg_cron (1dk later) PM->>DB: atomic claim PM->>PM: detectPortfolioIntent() ✓ PM->>PFM: route(action: create) PFM->>G: extract listing JSON G-->>PFM: {type, district, rooms, price...} PFM->>DB: INSERT portfolios
+ portfolio_history PFM->>DB: portfolio_id = "2026-021" Note over DB: TRIGGER fires DB->>DB: trg_match_on_portfolio DB->>+match-notify: HTTP POST (pg_net) PFM->>send-message: "Portföy eklendi: 2026-021" send-message->>A: WA reply Note over match-notify: bg
find_matching_requests
→ notify matching agents

Akış 2 — Cross-Agent Deal Pazarlığı (Ping-Pong)

sequenceDiagram participant BA as Buying Agent participant LA as Listing Agent participant DM as deal-manage participant DB as Postgres participant SM as send-message BA->>DM: "portföy 2026-005 için 12M teklif" DM->>DB: INSERT deals (status: offer) DM->>DB: INSERT deal_negotiations
(amount: 12M, status: pending) DM->>SM: notify listing_agent SM->>LA: [Kabul] [Reddet] [Karşı Teklif] LA->>DM: tap [Karşı Teklif]
"13M" DM->>DB: deal_negotiations
(amount: 13M, status: countered) DM->>SM: notify buying_agent SM->>BA: [Kabul] [Reddet] [Karşı Teklif] BA->>DM: tap [Kabul] DM->>DB: UPDATE deals
status: closed_won Note over DB: TRIGGER fires DB->>DM: trg_deal_status_change DM->>DB: INSERT platform_commissions DM->>DB: INSERT agent_visibility
(unlock full info) DM->>SM: notify both agents SM->>BA: "Teklif kabul edildi" SM->>LA: "Anlaşma kapandı"

Akış 3 — 24h Window Closed: Nudge Pattern

WhatsApp template body parameters \n / \t kabul etmiyor. Çok satırlı match notification'lar template ile gönderilemez. Çözüm: rich body pending_notifications kuyruğa, single-line nudge template gönder, kullanıcı cevap verince 24h window açılır, kuyruk drain edilir.
match-notify generates "5 ilan eşleşti" rich body │ │ contact.last_inbound > 24h ago? │ ├─ NO → SM direct freeform send → ✓ │ └─ YES → ↓ │ ▼ INSERT pending_notifications (body, buttons) interaction_tracker.nudge_sent_at IS NULL? ├─ YES → send single-line nudge template │ atomic claim: UPDATE SET nudge_sent_at = now() │ WHERE contact_id = X AND nudge_sent_at IS NULL │ RETURNING (dedupe) └─ NO → skip ...user replies to nudge → 24h window OPENS... │ ▼ vic-respond inbound handler: SELECT pending_notifications WHERE delivered_at IS NULL → freeform send each (\n OK) → UPDATE delivered_at = now() → UPDATE interaction_tracker.nudge_sent_at = NULL

10 Yetki Modeli — 5 Katmanlı RLS

Identity (kim olduğun) ve authority (ne yapabildiğin) ayrı tablolarda. F11 migration ile schema temizlendi.

Identity vs Authority Ayrımı (F11)

-- Before: çakışan değerler iki tabloda
contacts.contact_type     IN (customer, agent, manager, broker, owner, ...)
agent_profiles.role       IN (agent, senior_agent, broker, admin, owner)
-- Permission check'ler ambiguous, kod identity ile authority'yi conflate ediyordu

-- After: tek source of truth per concept
contacts.contact_type     IN (customer, agent, external_agent, lead, unknown)
                            -- Identity / category
agent_profiles.role       IN (agent, senior_agent, broker, admin, owner)
                            -- Hierarchy within agent network

5 Katmanlı Görünürlük

1. Herkese Açık

Fotoğraf, m², ilan başlığı. Public website + portföy paylaşımı.

2. Müşteri

+ Adres yaklaşımı, oda planı, semt. Onaylı müşteri.

3. Valora-İçi (Agent)

+ Tam adres, müsait saatler, sahip iletişimi (filtered).

4. Sahip-Özel

+ Sahip notları, özel talimatlar. portfolio_confidential.

5. Broker / Admin / Owner

+ Dip fiyat, komisyon, gerçek pazarlık marjı, tüm finansal veri.

Mod seçimi: mode = customer | agent sadece. resolveAgentTier(contact) ayrı bir adım, prompt variant ve görünürlük seviyesini belirler. Mode ile tier ayrı.

11 Production Scar'lar

Live'da yanan ve fix'lenen, dokümante edilmiş hatalar. Her biri tekrarlama riskine karşı kodla + memory ile kilitli.

#1 — Supabase verify_jwt + yeni sb_secret format çatışması

Supabase yeni service role key formatını sb_secret_... (JWT değil, ~41 char) yaptı. CLI default --verify-jwt ON deploy ediyor. Internal function-to-function çağrıları "Invalid JWT" (HTTP 401) ile silently break oluyordu — process-messages → vic-respond, vic-respond → send-message, hepsi koptu.

Fix: config.toml içinde her internal function için [functions.X] verify_jwt = false. admin-wa-templates verified kalır (extra layer, dış JWT-format key ile invoke ediliyor). Probe: Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")?.startsWith("sb_secret_").

#2 — WhatsApp template body newline / tab guard

(#100) Param text cannot have new-line/tab characters or more than 4 consecutive spaces runtime error. Çok satırlı match/proactive bildirimler template ile gönderilemez (24h window kapalı kullanıcılar için kritik).

Fix: "Nudge template + freeform-on-reply" pattern. Rich body pending_notifications kuyruğa, single-line nudge template gönder, kullanıcı cevap verince vic-respond kuyruğu drain eder. Atomic claim UPDATE interaction_tracker SET nudge_sent_at = now() WHERE contact_id = $1 AND nudge_sent_at IS NULL RETURNING dedupe.

#3 — Inbound message_type routing footgun

"Text dışında her şey fallback" early-return blanket-block ediyordu interactive_button + interactive_list tiplerini → buton tıklamaları çalışmıyordu (onboarding role selection, KVKK consent, deal action). Belirti: button taps "Mesajınızı aldım" generic reply, DB state değişmez. Grandfathered kullanıcılarda fark edilmiyordu çünkü onboarding tamamlanmıştı.

Fix: Whitelist gerçek media tiplerine (image | audio | video | document | location), interactive_* flow through handler chain'e.

#4 — Identity vs authority schema collision (F11)

İki tabloda aynı role değerleri (broker, owner) permission check'leri ambiguous yapıyordu. contact_type = 'broker'agent_profiles.role = 'broker' mı? Kod conflate ediyordu, tier-based prompt seçimi yanlış sonuçlar üretiyordu.

Fix: F11 migration — contacts.contact_type sadece identity (customer/agent/external_agent/lead/unknown), agent_profiles.role sadece hiyerarşi. PostgREST embed (contact, agent_profiles(role)) ile tier ayrı resolve edilir.

#5 — BSUID'yi PK olarak kullanma

WhatsApp Business Solution UID numara değişince yenileniyor (kullanıcı SIM değiştirirse). 30 gün kuralı telefon görünürlüğünü etkiler, BSUID'yi değil. WABA portfolio transfer = potansiyel BSUID reset riski.

Fix: UUID PK + BSUID secondary unique index. Lookup chain: BSUID → phone fallback. Haziran 2026'da BSUID ile mesaj gönderme aktif olacak ama PK olmaya hâlâ uygun değil.

#6 — Matcher property_type hard filter eksikti (F7)

find_matching_portfolios / find_matching_requests RPC'lerinde property_type sadece scoring'deydi, WHERE'de değildi. Cross-category match leaked through — daire talebi dükkân ilanıyla match'liyordu (district aligned olunca). Yanlış agent bildirimleri.

Fix: F7 migration — property_type hard WHERE filter olarak iki RPC'ye eklendi. Mevcut yanlış 2 row pruned.

#7 — Session extraction cadence güvensizdi (F7b)

3 dakika idle threshold + 2 dakika cron = worst-case agent visibility latency ~5 dakika. Event-driven trigger missed olursa (non-portfolio reply, heuristic edge case), session geç kapanıyor → request geç INSERT ediliyor → match geç tetikleniyor.

Fix: F7b migration — close_stale_sessions threshold 3dk → 90sn, cron 2dk → 1dk. Worst-case ~150sn.

#8 — Meta template name lifecycle locks

DELETE /{waba}/message_templates?name=X sonrasında Meta name+language pair'i saatlerce locked tutuyor ("Message template language is being deleted", subcode 2388023). Yeniden aynı isimle deploy edilemez.

Fix: Reuse etme — _v2 / _v3 bump. Body design kuralları: {{1}} tek başına olamaz (subcode 2388047), variable başta/sonda olamaz (subcode 2388299), allow_category_change: false zorunlu (Meta'nın UTILITY → MARKETING silently flip etmesini engeller — KVKK + per-message cost).

12 Roadmap — Yapılacaklar

Tamamlanan Faz'lar

F1-F5c DONE

Webhook + session-based intent extraction + portfolio CRUD + matching trigger + proactive checks + deal pipeline + ping-pong negotiation + cross-agent flow.

F6 Interactive + Commission DONE

Buttons, lists, CTA + deal_negotiations + platform_commissions + agent_visibility tabloları.

F7 / F7b DONE

Matcher property_type hard filter + session extraction cadence tightening.

F8 Onboarding + KVKK DONE

Onboarding state machine (pending_role → pending_terms → pending_transfer → completed). KVKK Md. 9 cross-border açık rıza zorunlu, training + marketing opsiyonel.

F10 Pending Notifications DONE

24h window queue + nudge dedupe pattern.

F11 Role Consolidation DONE

Identity vs authority schema ayrımı. CHECK constraint tightening.

F12 Conversational Automation DONE

Meta WhatsApp commands + ice breakers (slash command handler).

F13 /davet DONE

Admin WhatsApp'tan invite link generation (agent onboarding temeli).

F14 Role Authority DONE

contact_type identity + agent_profiles.role authority consolidation kodda aktif.

Bekleyen

F6/F7 Notification Detayı PLAN

Owner notification (teklif forward, kapanış) + customer notification (gösterim onayı, arama sonucu, teklif durumu) tam akışı.

F8 Commission Automation PLAN

Komisyon hesaplama + platform_commissions otomatik populate + invoice flow.

F9 Public Listings PLAN

valoragayrimenkul.com → Supabase → Next.js ISR SEO sayfaları. Public portföy görünümü.

F10 MCP Server PLAN

Can + VAEL'e direkt CRM erişimi (Cloud Run, Supabase Edge wrap).

Agent Onboarding Flow WIP

Admin WA'dan /davet **** → otomatik invite link → yeni agent self-onboarding. F13 ile temeli kuruldu, end-to-end flow eksik.