﻿# ANINDA YETKI VERME - Kesin Mimari (Arastirma Sonucu)

**Tarih:** 28.09.2026
**Soru:** Uretici uygulamadan bagli cihaza aninda (1-2 sn) yetki/lisans verebilir miyiz?
**Cevap:** **EVET.** Mevcut sistemde calisan altyapi bulundu. Sunucuda yeni kod gerekiyor (kucuk).

---

## 1. KESIN CEVAP

### 1.1 Mevcut Sistemde Ne Var?

**Tetikleyici motoru** (`apps/uyar_dokum/uyar/tetikleyici_motor.go`):

| Yetenek | Satir | Durum |
|---------|-------|-------|
| `cihaz_komut` eylemi | :195, :744 | **VAR** - sokete emit ediyor |
| Hedef cihaz secimi | :749 | **VAR** - `hedefCihazId` |
| Manuel tetikleme endpoint'i | `tetikleyici_routes.go:19` | **VAR** |
| Kural yenileme endpoint'i | `tetikleyici_routes.go:20` | **VAR** |
| Kurali DB'den yukleme | :85 | **VAR** - 60 sn periyot |
| Aninda yenileme | :81 | **VAR** - `KurallariYenile()` |

**Ucak yolu:**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle
  {"tetikleyiciId": "<kural id>"}
    |
    v
manuelTetikle() -> TetikleyiciMotorOrnek.OlayIsle(olay)
    |
    v
kuralEslesiyor() -> kuraliCalistir()
    |
    v
eylemCihazKomut() -> socket.Emit("FikoYT.sistem.io.cihaz.komut", ...)
    |
    v
fiko_socket_io.go:472 -> soketCihazMap ile hedef cihaza yonlendir
    |
    v
FIKOCAD KOMUTU ALIR VE CALISTIRIR
```

### 1.2 Bekle - TetikleyiciRouting Bagli mi?

`tetikleyici_routes.go:18` - `TetikleyiciRouting(router)` fonksiyonu **tanimli**
ama **cagrildigi yer bulunamadi** (main.go'da yok).

**Bu bir sorun.** Kontrol ettim:
- `main.go` icinde `uyar_app_main.Ayarla(app)` var
- `Ayarla` -> `TetikleyiciRouting` cagriliyor mu? Dogrulamam gerekiyor.

Eger cagrilmiyorsa, **endpoint'ler aktif degil** ve tetikleyici calismiyor.

### 1.3 Baska Bir Yol: Dogrudan Emit

`fikoyt_soket_io.go:472` - `client.On("FikoYT.sistem.io.cihaz.komut", ...)`
bu bir **istemciden sunucuya gelen** istek. Sunucu bunu alip hedef cihaza
yonlendiriyor.

**Yani:** Baska bir yoneticu (admin paneli) sokete bu mesaji atiyor, sunucu
hedefe iletiyor. Bu tam olarak "uzaktan komut yolla" demek.

**Kimin attigi belirsiz** - admin panelinden mi, yoksa baska bir yerden mi?
Bu da arastirilmali.

---

## 2. EN TEMIZ YOL (Onerim)

Tetikleyici motoruna **hic dokunmadan**, sadece mevcut `kokDB` + `Fsid` header
mekanizmasini kullanmak.

### 2.1 Fsid Header Mekanizmasi (Mevcut!)

`fikoyt_ctx.go:151-169`:
```go
func alClientSocket(c fiber.Ctx) func() *socket.RemoteSocket {
    return func() *socket.RemoteSocket {
        fsidHeader := c.GetReqHeaders()["Fsid"]     // <-- HTTP header
        if len(fsidHeader) > 0 {
            fsid := fsidHeader[0]
            socket_ortak.FikoSocketIo.FetchSockets()(func(sockets []*socket.RemoteSocket, _ error) {
                for _, v := range sockets {
                    if v.Id() == socket.SocketId(fsid) { rslt = v; break }
                }
            })
        }
        return rslt
    }
}
```

**Bu `FikoYT.IoClient()` her HTTP isteginde erisilebilir.** Yani bir HTTP
istegi geldiğinde, `Fsid` header'ina yazilan sokete **erisilebiliyor.**

### 2.2 Yeni Endpoint (Tek Fonksiyon - ~20 Satir)

Bu olmadan "aninda" olmaz. Ama **kucuk ve zorunlu:**

```go
// kokDB/routes.go veya yeni dosya
func fikoyt_sistem_uzak_komut(c fiber.Ctx) error {
    FikoYT := c.Locals("FikoYTCtx").(*FikoYTCtx.FikoYTCtx)
    if FikoYT.AktifKullanici == nil { return fiber.NewError(401, "Oturum gerekli") }
    if !yoneticiMi(FikoYT) { return fiber.NewError(403, "Yetki gerekli") }

    cihazKimlik, _ := FikoYT.Istek.Json["cihaz_kimlik"].(string)
    komutKodu,   _ := FikoYT.Istek.Json["komut_kodu"].(string)
    imza,        _ := FikoYT.Istek.Json["imza"].(string)
    if cihazKimlik == "" || komutKodu == "" { return fiber.NewError(400, "Eksik alan") }

    // Hedef cihazin soketini bul
    soketCihazMapMu.RLock()
    var hedefSoketId string
    for sid, ck := range soketCihazMap {
        if ck == cihazKimlik { hedefSoketId = sid; break }
    }
    soketCihazMapMu.RUnlock()
    if hedefSoketId == "" { return fiber.NewError(404, "Cihaz bagli degil") }

    data := map[string]interface{}{
        "cihaz_kimlik": cihazKimlik,
        "komut_kodu":   komutKodu,
        "imza":         imza,
        "kaynak":       "uretici",
        "zaman":        time.Now().Format(time.RFC3339),
    }
    socket_ortak.FikoSocketIo.FetchSockets()(func(sockets []*socket.RemoteSocket, err error) {
        for _, s := range sockets {
            if string(s.Id()) == hedefSoketId {
                s.Emit("FikoYT.sistem.io.cihaz.komut", data)   // <-- ANINDA
            }
        }
    })
    return c.JSON(fiber.Map{"basarili": true})
}
```

Route kaydi (1 satir):
```go
fapiv1.Post("/fikoyt.com.FikoYT.sistem.uzak.komut", fikoyt_sistem_uzak_komut)
```

**Toplam: ~25 satir Go kodu.** Bu "sunucu degisikligi yok" hedefini bozar ama
**"aninda" olmasini saglar.** Bu bir trade-off.

### 2.3 Alternatif: Tetikleyici Motorunu Kullanmak (SIFIR SUNUCU KODU)

Eger `TetikleyiciRouting` zaten bagliysa:

```
1. Uretici: kokDB .ekle -> hareketler koleksiyonuna kural
   {
     "_id": "<yeni id>",
     "vt": "FikoYT.sistem.tetikleyiciler",
     "ad": "Aninda Lisans - ABC",
     "aktif": true,
     "kosul": {"cihazId": "a1b2...", "olayKodu": "manuel"},
     "eylemler": [{
       "tip": "cihaz_komut",
       "hedefCihazId": "a1b2...",
       "komutKodu": "lisans.yenile.FKC-001"
     }],
     "beklemeMs": 0
   }

2. Uretici: POST /fapiv1/.../tetikleyici.yenile   (KurallariYenile - ANINDA)
3. Uretici: POST /fapiv1/.../tetikleyici.manuel-tetikle {"tetikleyiciId":"<id>"}
4. Motor: OlayIsle -> kuralEslesiyor -> eylemCihazKomut -> emit
5. Fikocad: komutu alir
```

**Sunucu kodu: SIFIR** (sadece 2 HTTP cagrisi).

**Gecikme: ~100-500 ms** (motor ayni process icinde calisir).

**Bu en iyi yol - SADECE TetikleyiciRouting bagliysa.**

---

## 3. FIKOCAD TARAFI - KOMUT NASIL CALISTIRILIR?

### 3.1 Merkezi Komut Akisi

`cekirdek.hpp:43-75`:
```cpp
bool komutCalistir(const MenuOge& oge, const char* kaynak = "") {
    const std::string ad = oge.ad;
    const std::string kategori = oge.grup;
    auto calistir = oge.calistir;              // <-- FONKSIYON KOPYALANIR
    const bool izin = (!lisansKapisi_ || lisansKapisi_(oge)) &&     // <-- GATE
                      (!oge.etkinMi || oge.etkinMi(*this));
    if (izin && calistir) {
        try { calistir(*this); basarili = true; } catch (...) {}
    }
    olaylar_.yayinla(KomutCalisti{ad, kategori, kaynak, basarili}, ...);
    return basarili;
}
```

**Kritik: `oge.calistir` bir `std::function`.** Uzaktan gelen `MenuOge` ile
calistirilirsa **tam olarak normal komut gibi calisir** - gate, olay, kayit, hepsi.

### 3.2 MenuOge Nasil Bulunur?

`kayit.hpp:131`:
```cpp
const std::vector<MenuOge>& tumKayit() const { return ogeler_; }
```

**`tumKayit()` tum kayitlari dondurur.** Kimlik ile arama:
```cpp
MenuOge* bulMenuOge(Cekirdek& c, const std::string& kimlik) {
    for (const MenuOge& o : c.menuler().tumKayit()) {
        if (komutKimligi(o) == kimlik) return const_cast<MenuOge*>(&o);
    }
    return nullptr;
}
```

`komutKimligi(o)` (`kayit.hpp:75`):
```cpp
return o.kimlik.empty() ? (o.grup + "/" + o.ad) : o.kimlik;
```

### 3.3 Uzaktan Komut Calistirma (Fikocad Tarafi)

```cpp
// lisans_uzaktan.cpp - yeni dosya
void uzaktanKomutCalistir(const KomutData& k) {
    // 1. IMAZAYI DOGRULA (sistem guvenligi)
    if (!komutImzaDogrula(k)) {
        gunluk::uyari("Uzak komut reddedildi - gecersiz imza");
        olayRaporla("komutReddedildi", k.komut_kodu, "gecersiz imza");
        return;
    }

    // 2. KOMUTU BUL
    MenuOge* oge = menuOgeBul(cekirdek, k.komut_kodu);
    if (!oge) {
        komutCevapYolla(k, false, "Komut bulunamadi: " + k.komut_kodu);
        return;
    }

    // 3. BEYAZ LISTE (uzaktan calistirilabilir komutlar)
    if (!uzakKomutBeyazListede(oge->kimlik)) {
        komutCevapYolla(k, false, "Yetkisiz komut");
        return;
    }

    // 4. MERKEZI KAPIDAN CALISTIR
    //    komutCalistir() kendi lisans kapısını uygular - ekstra kontrol gereksiz
    const bool basarili = cekirdek.komutCalistir(*oge, "uzaktan");

    // 5. CEVAP
    komutCevapYolla(k, basarili, basarili ? "OK" : "Komut basarisiz");
}
```

**Nokta 4 onemli:** `komutCalistir` zaten `lisansKapisi_(oge)` kontrolu yapiyor.
Yani **ayri lisans kontrolune gerek yok** - merkezi gate zaten uygulaniyor.

### 3.4 Beyaz Liste

```cpp
// Uzaktan calistirilabilecek komutlar (sadece veri okuyan/kaydeden)
static const char* UZAK_BEYAZ[] = {
    "gorunum.tumunu_sigdir",     // tumunu sigdir
    "gorunum.secimi_cercevele",  // secimi cercevele
    "gorunum.izometrik",          // gorunum
    "gorunum.ust",
    "gorunum.on",
    "gorunum.ortografik",         // O
    "secim.kip_govde",           // secim kipleri
    "secim.kip_yuz",
    "secim.kip_kenar",
    "olcum.pul",                 // olcum
    "dosya.fark",                // CAD fark (salt okuma)
    "arac.guncelleme",            // guncelleme kontrolu
    "arac.geri_bildirim",        // geri bildirim
    "sistem.temizle",            // gecici dosya temizle
    "lisans.yenile",             // LISANS YENILE (ozel)
    "lisans.komutlari",           // KOMUT YETKILENDIR (ozel)
    "lisans.kilitle",             // KILITLE (ozel)
};
```

**TEHLIKELI OLANLAR HARIC:**
- `dosya.sil` - dosya silme
- `ayar.*` - ayar degistirme
- `arac.kapat` - program kapatma
- `sifirla.*` - sifirlama

---

## 4. KRITIK CATISMA: MUHUR vs UZAKTAN YETKI

### 4.1 Problem

`lisans.hpp:323-341`:
```cpp
void muhurle() { muhur_ = politikaOzeti(); }
bool muhurGecerli() const { return muhur_ == 0 || muhur_ == politikaOzeti(); }

bool saglikKontrol() {
    if (muhurGecerli()) return true;
    // Politika BOZULMUS - hepsini temizle
    izinliKomutlar_.clear();
    haklar_.clear();
    // ...
    kip_ = Kip::Kisitli;
    return false;                    // <-- EN KISITLI KIPE DUSUR
}
```

### 4.2 Catisma

Uzaktan yetki verildiginde:
```
1. Mevcut politika yuklendi, muhurle() cagrildi -> muhur_ = OZET1
2. Uzaktan "dokum.kumas" komutu icin komutIzinVer() cagrildi
3. Politika DEGISTI -> politikaOzeti() = OZET2  (artik OZET2)
4. muhur_ hala OZET1
5. saglikKontrol() -> muhurGecerli() = (OZET1 == OZET2) = FALSE
6. -> TUM YETKILER SILINIR, en kisitli kipe dusulur
```

**Yani uzaktan yetki verirseniz, 1.5 saniye sonra TUM YETKILER KAYBOLUR.**

### 4.3 Cozum

`komutIzinVer` cagrildiktan **hemen sonra** `muhurle()` cagrilmali:

```cpp
void uzaktanKomutCalistir(const KomutData& k) {
    // ... imza + beyaz liste kontrolu ...

    if (k.komut_kodu == "lisans.komutlari") {
        // Yeni yetkileri ekle
        for (const std::string& komut : k.yeniKomutlar) {
            lisansSistemi().komutIzinVer(komut);
        }
        // !!! KRITIK: mührü yenile
        lisansSistemi().muhurle();

        komutCevapYolla(k, true, "Yetkiler guncellendi");
        return;
    }
}
```

**Bu "zafiyet" degil, mevcut korumanin dogru kullanimi.** Mühur politikayı
kilitleyen bir mekanizma; resmi kanaldan (uzak yetki) politika degisince
yeniden kilitlemek dogru davranis.

### 4.4 Ek Önlem: Politika Sürümü

Uzaktan gelen komutta bir `surum` alani olsun:
```json
{"komut_kodu":"lisans.komutlari","surum":5,"yeniKomutlar":["dokum.kumas"]}
```

Fikocad mevcut `politikaSurumu()` ile kontrol eder:
```cpp
if (k.surum <= lisansSistemi().politikaSurumu()) {
    komutCevapYolla(k, false, "Eski surum - yoksayildi");
    return;   // tekrar oynatma (replay) korumasi
}
```

---

## 5. BELIRSIZLIK COZULDU: TetikleyiciRouting BAGLI DEGIL

### 5.1 Arastirma Sonucu

**`TetikleyiciRouting()` HIC CAGRILMIYOR.**

Kanit zinciri:
```
main.go:52          uyar_app_main.Ayarla(app)
  -> uyar_main.go    Ayarla() fonksiyonu
       Uyar := app.Group("/uyar")
       Uyar.Use(cors.New(...))
       uyar_api_v1.Init(app, Uyar)     // sadece 3 modul
       uyar_admin.Init(app, Uyar)      // admin

uyar_api_v1.go:11-21
  func Init(app, Uyar) {
      Api := Uyar.Group("/api")
      uyar_kiosk.Init(app, Api)
      uyar_login.Init(app, Api)
      freecad.Init(app, Api)
      // <-- TetikleyiciRouting YOK
  }
```

**Sonuc:**
| Durum | Anlami |
|-------|--------|
| `/uyar/api/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle` | **YOK** (route kaydi yok) |
| `tetikleyici.yenile` endpoint'i | **YOK** |
| `TetikleyiciMotorOrnek.Baslat()` | **VAR** (main.go:60) - motor calisir |
| `cihaz_komut` eylemi | **VAR** ama tetiklenemiyor (manuel endpoint yok) |

### 5.2 Bu Ne Anlama Geliyor?

**Yol A (sifir sunucu kodu) KULLANILAMAZ.** Manuel tetikleme endpoint'i route
tablosunda degil.

Tetikleyici motoru `Baslat()` ile kurallari yukluyor ve 60 sn'de bir
yeniliyor, ama **olay gelmedigi surece kural calismaz.** `eylemCihazKomut`
sadece `OlayIsle()` ile tetiklenir; `OlayIsle`'i cagiran HTTP endpoint'i yok.

**Tek istisna:** `main.go:82` - mail dinleyici tetikleyiciyi cagirir:
```go
go uyar_app_main.TetikleyiciMotorOrnek.OlayIsle(map[string]interface{}{
    "olay_kodu": "mail_geldi", "cihaz_kimlik": "mail-dinleyici", ...
})
```
Yani **sadece mail olaylari** tetikleyiciyi calistiriyor.

### 5.3 Cozum: 1 Satir Route Kaydi

Dosya: `apps/uyar_dokum/uyar/uyar_main.go`

```go
func Ayarla(app *fiber.App) {
    Uyar := app.Group("/uyar")
    // ... cors ayarlari ...
    uyar_api_v1.Init(app, Uyar)
    uyar_admin.Init(app, Uyar)
    TetikleyiciRouting(Uyar)      // <-- BU SATIR EKLE (1 satir)
}
```

**Bu tek satirlik degisiklikle:**
- `/uyar/api/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle` aktif olur
- `/uyar/api/fikoyt.com.FikoYT.sistem.tetikleyici.yenile` aktif olur
- `/uyar/api/fikoyt.com.FikoYT.sistem.tetikleyici.durum` aktif olur
- **Tum aninda yetki verme altyapisi calisir**

**Sunucu degisikligi: 1 SATIR.** (Onceki tahmin 25 satirdi - bu cok daha iyi.)

### 5.4 Dogrulama

Route tanimi (`tetikleyici_routes.go:18`):
```go
func TetikleyiciRouting(router fiber.Router) {
    router.Post("/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle", manuelTetikle)
    router.Post("/fikoyt.com.FikoYT.sistem.tetikleyici.durum", tetikleyiciDurum)
    router.Post("/fikoyt.com.FikoYT.sistem.tetikleyici.yenile", tetikleyiciYenile)
}
```

`manuelTetikle` **zaten yetki kontrolu yapıyor**:
```go
if FikoYT.AktifKullanici == nil { 401 }
...
if !aktif { 400 }   // sadece aktif kurallar
go TetikleyiciMotorOrnek.OlayIsle(olay)
```

**Oturum + aktif kural kontrolu mevcut.** Yonetici yetkisi kontrolu yok ama
oturum kontrolu var - eklenmesi onerilir.

### 5.5 Yol B Degerlendirmesi (Gerekli Degil)

Onceki plandaki 25 satirlik `/uzak.komut` endpoint'ine **gerek yok.**
1 satirlik route baglantisi tum islevi karsiliyor.

**Guncellenmis sunucu degisikligi: 1 SATIR + (oneri) 1 satirlik yetki kontrolu.**
## 6. ANINDA AKIS (Uretici -> Fikocad)

```
URETICI (tiklar: "Bu cihaza dokum yetkisi ver")
    |
    | 1. Oturum acar (Super Yonetici)
    | 2. Cihaz listesini ceker (kokDB .aggregate)
    |
    | 3. Kural yazar (hareketler koleksiyonu):
    |    POST /uyar/api/fikoyt.com.FikoYT.sistem.DB.hareketler.ekle
    |    {vt:"FikoYT.sistem.tetikleyiciler", ad:"...", aktif:true,
    |     kosul:{cihazId:"a1b2...", olayKodu:"manuel"},
    |     eylemler:[{tip:"cihaz_komut", hedefCihazId:"a1b2...",
    |                 komutKodu:"lisans.komutlari", ...}]}
    |
    | 4. Motoru aninda yenile:
    |    POST /uyar/api/fikoyt.com.FikoYT.sistem.tetikleyici.yenile
    |    (KurallariYenile -> kurallariYukle)
    |
    | 5. Manuel tetikle:
    |    POST /uyar/api/fikoyt.com.FikoYT.sistem.tetikleyici.manuel-tetikle
    |    {tetikleyiciId:"<yeni kural id>"}
    |
    v
SUNUCU  OlayIsle -> kuralEslesiyor -> kuraliCalistir -> eylemCihazKomut
    |    socket.Emit("FikoYT.sistem.io.cihaz.komut", komutData)
    |
    v  (soketCihazMap ile hedef cihaza yonlendirilir)
    |
FIKOCAD  event alir (dinleyici EKLEMELI)
    |  1. imza dogrula
    |  2. beyaz liste kontrolu
    |  3. menuOge bul + cekirdek.komutCalistir()   <-- merkezi gate otomatik
    |  4. lisans.komutlari ise: komutIzinVer() + muhurle()   <-- KRITIK
    |  5. emit("FikoYT.sistem.io.cihaz.komut-cevap", sonuc)
    |
    v
URETICI  cevap gorur: "Yetki verildi - 3 komut eklendi" (ANINDA)
```

**Toplam gecikme: 200-800 ms**

---

## 7. OZET

| Soru | Cevap |
|------|-------|
| Aninda yetki verebilir miyiz? | **EVET** |
| Altyapi mevcut mu? | **EVET** - `cihaz_komut` + `manuel-tetikle` + `soketCihazMap` |
| Hedefli mi? | **EVET** - cihaz bazli soket yonlendirme |
| Cevap var mi? | **EVET** - `komut-cevap` |
| Gecikme | 200-800 ms |
| **Sunucu degisikligi** | **1 SATIR** (`TetikleyiciRouting(Uyar)`) |
| Fikocad degisikligi | Event dinleyici + beyaz liste + imza + muhurle |
| Kritik tuzak | **`muhurle()` unutulursa TUM yetkiler kaybolur** |

### 7.1 Yapilmasi Gerekenler

**SUNUCU (1 satir):**
```go
// apps/uyar_dokum/uyar/uyar_main.go - Ayarla()
TetikleyiciRouting(Uyar)
```

**FIKOCAD (ana is):**
1. `/isans.socket.io/` -> `/socket.io/` gecis + `Cihaz_kimlik` header
2. `FikoYT.sistem.io.cihaz.komut` event dinleyici
3. Imla dogrulama (uretici imzasi) + beyaz liste
4. `lisans.komutlari` komutu: `komutIzinVer()` + **`muhurle()`**
5. `komut-cevap` gonderme
6. Online-first durum makinesi (kopma suresi 30 sn, onceki rapor)

**URETICI:**
1. Cihaz listesi (kokDB aggregate)
2. Kural yaz + yenile + tetikle (3 HTTP cagri)
3. Cevap goster

### 7.2 Kritik Uyarilar

**1. Muhur tuzagi (EN ONEMLI):**
`lisans.hpp:323-341` - Uzaktan yetki verilince politika degisir, muhr uyusmaz,
`saglikKontrol()` **tum yetkileri siler.** `muhurle()` çağrılmazsa 1.5 sn sonra
her şey kilitlenir.

**2. Soket yolu:** Fikocad `/isans` kanalini kullanıyor, komutlar `/socket.io/`
kanalinda. **Gecis zorunlu** - yoksa komutlar gelmez.

**3. Cihaz kimligi eslesmesi:** `soketCihazMap` `Cihaz_kimlik` header'inden dolar.
Fikocad bu header'i gondermezse cihaz bazli yonlendirme calismaz.

**4. `manuel-tetikle` yetki kontrolu:** Oturum kontrolu var (`AktifKullanici == nil`),
yonetici yetkisi kontrolu **yok**. Oneri: `yoneticiMi()` eklenmesi.

### 7.3 Diger Benzer Senaryolar

Ayni altyapi ile yapilabilir:
- Lisans uzat (`lisans.uzat`)
- Lisans iptal (`lisans.iptal`)
- Komut kaldir (`lisans.komutlari` - negatif)
- Kota sifirla (`lisans.kota.sifirla`)
- Cihaz kilitle (`lisans.kilitle`)
- Guncelleme tetikle (`arac.guncelleme`)
- Dosya kaydet (`dosya.kaydet`) - dikkatli
- Gorus degistir (`gorunum.*`) - guvenli

