@appaflytech/zeyrek-ai-mcp 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.env.example +15 -0
  2. package/dist/config.js +9 -0
  3. package/dist/index.js +2 -0
  4. package/dist/knowledge/componentCatalog.js +4 -2
  5. package/dist/knowledge/componentEvents.js +3 -3
  6. package/dist/knowledge/mobileComponentCatalog.js +308 -50
  7. package/dist/knowledge/mobileComponentEvents.js +130 -3
  8. package/dist/knowledge/normalize.js +16 -6
  9. package/dist/knowledge/platform/lint.js +110 -3
  10. package/dist/knowledge/platform/mobile.js +1 -1
  11. package/dist/knowledge/platform/mobileRules.js +70 -11
  12. package/dist/knowledge/platform/web.js +1 -1
  13. package/dist/knowledge/rules.js +106 -11
  14. package/dist/knowledge/styleResolve.js +49 -3
  15. package/dist/knowledge/styleResolveNative.js +104 -4
  16. package/dist/observability/trace.js +1 -1
  17. package/dist/platform/argumentAliases.js +128 -0
  18. package/dist/platform/bodyFile.js +103 -0
  19. package/dist/platform/capture/capture.js +29 -5
  20. package/dist/platform/capture/console-log.js +80 -0
  21. package/dist/platform/capture/index.js +1 -0
  22. package/dist/platform/diagnostics.js +10 -4
  23. package/dist/platform/http.js +23 -7
  24. package/dist/platform/responses.js +6 -2
  25. package/dist/platform/schemaReadMemory.js +60 -0
  26. package/dist/platform/secrets.js +9 -0
  27. package/dist/platform/strictArguments.js +68 -12
  28. package/dist/platform/tokenVault.js +187 -4
  29. package/dist/resources/guides/expressions.md +1 -1
  30. package/dist/resources/guides/microservice.md +9 -2
  31. package/dist/resources/guides/middleware.md +5 -1
  32. package/dist/resources/guides/mobile.md +165 -10
  33. package/dist/resources/guides/page.md +161 -41
  34. package/dist/resources/guides/theme.md +4 -1
  35. package/dist/resources/guides/workflow.md +7 -1
  36. package/dist/resources/schema-fingerprint.json +2 -2
  37. package/dist/server.js +1 -1
  38. package/dist/tools/datasources.js +1 -1
  39. package/dist/tools/designSchemaPatch.js +4 -3
  40. package/dist/tools/externalComponents.js +1 -1
  41. package/dist/tools/flows.js +70 -4
  42. package/dist/tools/layouts.js +5 -5
  43. package/dist/tools/microservices.js +26 -10
  44. package/dist/tools/pages.js +192 -36
  45. package/dist/tools/platformInfo.js +4 -1
  46. package/dist/tools/rows.js +7 -1
  47. package/dist/tools/schemaOps.js +1 -1
  48. package/dist/tools/screenWrite.js +4 -3
  49. package/dist/tools/screens.js +81 -0
  50. package/package.json +1 -1
@@ -226,7 +226,7 @@ döndürür; hata yok, sayfalama yok. (Bağımsız `pagination` komponenti bu tu
226
226
  | `dropdown.search` · `multi-select.search` | `{ searchText }` | `self.searchText` |
227
227
  | `tabs.tab` | `{ selected }` | `self.selected` ya da `event.data.selected` |
228
228
  | `chart.dataPointClick` | `{ name, seriesName, value, dataIndex, row }` | `self.row` (kaynak satırın tamamı) |
229
- | `repeater.change` · `repeater.select` | `{ selectedIndex }` | `self.selectedIndex` · `self.selectedItem` (akış satır bağlamında koşmaz, `item`/`Row` boş) |
229
+ | `repeater.change` · `repeater.select` | `{ selectedIndex }` | `self.selectedIndex` · `self.selectedItem` (akış satır bağlamında koşmaz, `item`/`Row` boş); satırdaki bir düğmenin akışında setter'ın yazım kapsamı OLAYIN kaynağı değil HEDEFİN ağaçtaki yeriyle belirlenir — satırın şablonu içindeki bir hedefe satır bazlı (`_repeaterValues`), dışındaki bir hedefe (modal, sayfa düzeyi metin) sayfa kapsamında yazılır (ZM-08, 2026-09-26); iç içe repeater zaten `PAGE-013` ile reddedilir, ama bir DIŞ repeater'ın şablonuna İÇ satırdan yazan bir akış varsa yazım dış satırın değil İÇ satırın kimliğiyle anahtarlanır — bilinen açık, PAGE-013 riski düşük tutar |
230
230
  | `html` aksiyonu (`fireEvent`) | ikinci argüman | **`event.data`** |
231
231
  | `kanban.cardMove` | `{ id, row, from, to }` | **`event.data`** (self'e yazılmaz) |
232
232
  | `kanban.cardAdd` · `kanban.cardRemove` | `{ column }` · `{ id, row, column }` | **`event.data`** (self'e yazılmaz; kart eklenmez/gizlenmez, akış yazıp `rows`'u tazeler) |
@@ -249,7 +249,8 @@ dalı bulamaz ve akışı **sessizce durdurur** (`flow-engine.ts:79`); Studio da
249
249
  `sourceBranch === 'always'` ile çizdiği için panelde **Start kartından sonrası boş görünür**.
250
250
  İstisnalar: `condition` → dal adı rule id'sidir (ve o id bir **GUID**'dir — aşağıdaki tuzak 6),
251
251
  `confirm` → `yes|no|always`, `microservice` →
252
- `always` (eski akışlarda `success` de çalışır).
252
+ `always` (eski akışlarda `success` de çalışır) **+ `error`** — bağlarsan hata anında `always`
253
+ yerine ondan sürer (2026-09-26, ADR 0004; ayrıntı aşağıdaki §"4xx de hatadır").
253
254
 
254
255
  **2. `iteratees[].type` yalnız `change` | `fill`.** `setter.ts` başka değerde hiçbir dala
255
256
  girmez — adım çalışır, hiçbir şey yapmaz. `change` tek prop'a değer yazar, `fill` diziyi
@@ -375,16 +376,22 @@ Satır eşlemeli tablo yüklemesinin tam şekli (yükleniyor bayrağı → uç
375
376
  göstermek error flow'un işidir.
376
377
 
377
378
  > **4xx de hatadır ve `always` dalı seni KURTARMAZ.** `microservice.ts` `.catch` bloğu
378
- > reddedilen her istekte koşar — 401/403/404/422 dâhil. Sonrası iki yoldan biridir ve ikisi de
379
- > "ekranda hiçbir şey olmadı" ile biter:
379
+ > reddedilen her istekte koşar — 401/403/404/422 dâhil — ve storage'a HER ZAMAN
380
+ > `{ data: null, headers: null, error: { status, message, body } }` yazar (ADR 0004,
381
+ > 2026-09-26 — eskiden hata gövdesi `data`'ya yazılıyordu). `.finally` sonra ÜÇ yoldan birini
382
+ > dener, SIRAYLA:
380
383
  >
381
- > 1. **Hata yolu VARSA** `engine.onError` true döner ve `.finally` `return` eder —
382
- > `engine.next('always')` **hiç çağrılmaz**. Yani `always` ile `errorSteps` birbirini dışlar;
383
- > ikisini birden kuramazsın, hata yolu ana akışın devamını devralır.
384
- > 2. **Hata yolu YOKSA** `always` koşar, ama storage artık `{ error, data }`'dır ve
385
- > `Steps.<ad>.data` başarı gövdesi değil **hata gövdesidir**. Sıradaki `condition`
386
- > `!!Steps.signIn.data.token` gibi bir ifadeyi `undefined` üzerinden değerlendirir, sessizce
387
- > diğer dala düşer ve `else` dalı bağlı değilse akış orada biter.
384
+ > 1. **Adımın kendi `error` dalı BAĞLIYSA** akış oradan sürer — `always` **hiç koşmaz**
385
+ > (`hasBranch('error')`). Bu dal `Steps.<ad>.error` okuyabilir; ana akışın kalanı hiç çalışmaz.
386
+ > 2. **`error` dalı yoksa, `errorSteps` VARSA** `engine.onError` true döner ve `.finally` `return`
387
+ > eder — yine `always` koşmaz, flow'un hata akışı devralır (aşağıdaki karşılaştırma tablosu).
388
+ > 3. **İkisi de yoksa** `always` koşar, ama `Steps.<ad>.data` artık **NULL**dır — başarı gövdesi
389
+ > değil, hiç gövde değil. Sıradaki bir `condition` `!!Steps.signIn.data.token` gibi bir
390
+ > ifadeyi `null.token` üzerinden dener; `runCode` hatayı yutar (K2), ifade `undefined` döner ve
391
+ > akış sessizce diğer dala düşer — `else` dalı bağlı değilse akış orada biter. `data`yı bir
392
+ > liste prop'una yazan bir setter artık patlayan bir nesne değil `null` görür (repeater/table
393
+ > `null`'ı boş liste sayar), ama hata OLDUĞUNU fark etmeden geçer — gateway bunu **FLOW-028**
394
+ > ile uyarır.
388
395
  >
389
396
  > **`errorSteps` yalnız `start` taşıyorsa hata yolu YOKTUR:** `hasErrorFlow`
390
397
  > `errorTasks.some(t => t.type !== 'start')` arar. Start'ın ardına en az bir adım bağla —
@@ -393,15 +400,18 @@ göstermek error flow'un işidir.
393
400
  > **Mobil uygulama:** mobil runtime (`@ng-apps/ui-native` `NativeRuntime`) `errorSteps`'i web gibi
394
401
  > `errorTasks`'a eşler — hata yolu iki platformda aynı koşar.
395
402
 
396
- > **Hata metni ÜÇ ayrı yerde durur ve ikisi kullanıcıya gösterilecek metin DEĞİLDİR.**
403
+ > **Hata metni birden çok yerde durur, hepsi aynı şey değildir (2026-09-26 itibarıyla, ADR 0004).**
397
404
  > `onError` hata akışını koşturmadan önce `machine.setStorage('error', errorInfo)` yapar
398
- > (`flow-engine.ts`), yani hata akışının adımları `Steps.error`'ı okuyabilir:
405
+ > (`flow-engine.ts`), yani hata akışının adımları `Steps.error`'ı okuyabilir; patlayan adımın
406
+ > KENDİ storage'ı ise (bağlı `error` dalı da `always` da) `Steps.<adımAdı>.error` nesnesini taşır:
399
407
  >
400
408
  > | İfade | Ne taşır | Kaynak |
401
409
  > |---|---|---|
402
- > | **`Steps.error.message`** | **Ucun kendi mesajı** — `err.response.data.message`; boşsa HTTP durum metnine düşer. **Kullanıcıya gösterilecek metin budur.** Yanında `Steps.error.code` · `Steps.error.step.name` (patlayan adım) · `Steps.error.step.data` (ucun gövdesi) gelir | `microservice.ts` `err.reason \|\| HttpStatusText[err.code]` → `flow-engine.ts` `onError` |
403
- > | `Steps.<adımAdı>.error` | Yalnız HTTP **durum metni** — 401 için `'Unauthorized'`. Hata OLDUĞUNU anlamaya yarar, göstermeye değil | `microservice.ts` `.catch` `HttpStatusText[err.code]` |
404
- > | `Steps.<adımAdı>.data` | Ucun **hata gövdesi** (`err.response.data`) — başarı gövdesi değil. `.message` alanı burada da vardır | `microservice.ts` `.catch` |
410
+ > | **`Steps.error.message`** | **Yalnız `errorSteps` içinde** — ucun kendi mesajı, `err.reason` → HTTP durum metni → `'Service request failed'`. Yanında `Steps.error.code` · `Steps.error.step.name` (patlayan adım) · `Steps.error.step.data` (ucun hata gövdesi; parametre ifadesi patladıysa `null`) gelir | `microservice.ts` `err.reason \|\| HttpStatusText[err.code]` → `flow-engine.ts` `onError` |
411
+ > | `Steps.<adımAdı>.error.message` | Patlayan adımın KENDİ storage'ında ucun mesajı — `error` dalında da `always`'te de okunur, sıra aynı: `err.reason` → HTTP durum metni → `'Service request failed'` | `microservice.ts` `.catch` |
412
+ > | `Steps.<adımAdı>.error.status` | HTTP durum KODU (sayı) — parametre ifadesi patladıysa `null` | `microservice.ts` `.catch` |
413
+ > | `Steps.<adımAdı>.error.body` | Ucun hata gövdesi — eski `Steps.<adımAdı>.data`'nın taşındığı yer | `microservice.ts` `.catch` |
414
+ > | `Steps.<adımAdı>.data` | **HER ZAMAN `null`**'dır — başarı gövdesi değil, hiçbir gövde değil (eskiden hata gövdesiydi) | `microservice.ts` `.catch` |
405
415
  >
406
416
  > **`alert` adımının `message`'ı DÜZ METİNDİR — ifade yazarsan ekranda o ifade görünür.**
407
417
  > `alert.ts` `task.argument.message`'ı doğrudan `toast.error()`'a geçirir ve `args` hiçbir yerde
@@ -429,15 +439,24 @@ göstermek error flow'un işidir.
429
439
  > `{ "type": "confirm", "args": { "message": "Bu risk ve 4 aksiyonu silinecek.", "title": "Riski sil",
430
440
  > "confirmText": "Sil", "cancelText": "Vazgeç" } }`.
431
441
 
432
- > **Hangi yolu seçeceksin — `errorSteps` mi, ana akışta `condition` mı?** İkisi birden
433
- > kurulamaz (yukarıdaki 1. madde), yani bu bir tercih değil bir karar:
442
+ > **Hangi yolu seçeceksin — adımın kendi `error` dalı mı, `errorSteps` mi, ana akışta
443
+ > `condition` mı?** Bir hatada sırayla denenir (yukarıdaki 4xx bölümü) — üçü birden TANIMLI
444
+ > olabilir ama aynı anda yalnız biri koşar: **`error` dalı bağlıysa o kazanır**, yoksa
445
+ > `errorSteps`, ikisi de yoksa `always` + `condition`:
434
446
  >
435
- > | | `errorSteps` | ana akışta `condition Steps.<adımAdı>.error` |
436
- > |---|---|---|
437
- > | Ana akışın kalanı | **koşmaz** — hata akışı devralır | koşar, dalı sen seçersin |
438
- > | Yükleniyor bayrağı · modal kapatma · tablo tazeleme | hata akışında **yeniden kurmak zorundasın**; kurmazsan bayrak açık kalır | zaten ana akışta duruyor |
439
- > | Hata metni | `Steps.error.message` (yukarıdaki tablo) | `Steps.<adımAdı>.data.message`, hata olduğunu `Steps.<adımAdı>.error` söyler |
440
- > | Ne zaman doğru | hata anında akış **tamamen başka bir yola** sapacaksa (başarısız giriş, yetkisiz sayfa) | kalan adımların çoğu hata hâlinde de koşacaksa — **form gönderimlerinin varsayılanı budur** (Kural 7) |
447
+ > | | adımın kendi `error` dalı (g-02) | `errorSteps` | ana akışta `condition !!Steps.<adımAdı>.error` |
448
+ > |---|---|---|---|
449
+ > | Kapsam | yalnız BU adımın hatası | akıştaki HERHANGİ bir adımın hatası | yalnız BU adımın hatası |
450
+ > | Ana akışın kalanı | **koşmaz** — bu dal devralır | **koşmaz** — hata akışı devralır | koşar, dalı sen seçersin |
451
+ > | Yükleniyor bayrağı · modal kapatma · tablo tazeleme | dalda **yeniden kurmak zorundasın** | hata akışında **yeniden kurmak zorundasın**; kurmazsan bayrak açık kalır | zaten ana akışta duruyor |
452
+ > | Hata metni | `Steps.<adımAdı>.error.message` | `Steps.error.message` (yukarıdaki tablo) | `Steps.<adımAdı>.error.message`; hata olduğunu `!!Steps.<adımAdı>.error` söyler — `.error.status` parametre ifadesi patladıysa `null`dur, o yolu KAÇIRIR |
453
+ > | Ne zaman doğru | hata anında BU adımın sonrası **tamamen başka bir yola** sapacaksa | hata anında akış **tamamen başka bir yola** sapacaksa (başarısız giriş, yetkisiz sayfa) | kalan adımların çoğu hata hâlinde de koşacaksa — **form gönderimlerinin varsayılanı budur** (Kural 7) |
454
+ >
455
+ > **"Ana akışta condition" sütunu `.error`'u okumalı, `.data`'yı değil.** `Steps.<adımAdı>.data`'nın
456
+ > DOLU/BOŞ olmasına bakarak başarı/hata ayırmak (`data.token` gibi bir alanın varlığını yoklamak)
457
+ > artık yanıltıcıdır — `data` hata anında zaten `null`'dur (yukarıdaki §"4xx de hatadır"). Böyle bir
458
+ > `condition`'ı gateway **FLOW-028** ile uyarır; adıma ayrıca bir `error` dalı bağlayın (aşağıdaki
459
+ > 7c örneği) — `error` dalı bağlıysa uyarı düşer, çünkü hata artık `always`'e hiç uğramaz.
441
460
 
442
461
  Zorunlu filtresi olan uçlarda (`patient-*`, `doctor-appointments` gibi `isRequired: true`
443
462
  filtreli microservice'ler) parametreyi adımda ver, yoksa uç `MissingParameterException` atar:
@@ -514,7 +533,7 @@ Tarif: `platform_guide('middleware')`.
514
533
 
515
534
  **Sözlükler** (`StepType.cs`, `StepParameterValueType.cs` — hepsi küçük harf):
516
535
  `start · microservice · setter · setcookie · validation · condition · redirect · alert ·
517
- confirm · flow-trigger · storage · navigate · code · download · function · end`
536
+ confirm · flow-trigger · storage · navigate · code · download · function · share · end`
518
537
  değer tipleri: `static · page · property · storage · variable · code · item · state`
519
538
 
520
539
  > **Sözlükte olmak KOŞACAĞI anlamına gelmez — koşan tipler barındırıcıdan gelir.** Motor yedi
@@ -527,7 +546,7 @@ değer tipleri: `static · page · property · storage · variable · code · it
527
546
  > |---|---|---|---|
528
547
  > | `start` · `setter` · `code` · `condition` · `microservice` · `validation` · `flow-trigger` · `function` | ✅ | ✅ | `flow-engine.ts` — motorun kendisi |
529
548
  > | `alert` · `redirect` · `setcookie` · `storage` | ✅ | ✅ | barındırıcı; mobilde `setcookie` cihaz deposuna yazar (`MOB-005`) |
530
- > | `confirm` · `download` | ✅ | ✅ | `Runtime.svelte` · `runtime-tasks.ts` (`download` mobilde açma/paylaşım — `MOB-005`) |
549
+ > | `confirm` · `download` · `share` | ✅ | ✅ | `Runtime.svelte` · `runtime-tasks.ts` (`download` mobilde açma/paylaşım — `MOB-005`; `share` aşağıda) |
531
550
  > | `navigate` | ❌ | ✅ (`redirect` takma adı) | web'deki karşılığı `redirect`; gateway `redirect`'e çevirir |
532
551
  > | `end` · `query` | ❌ | ❌ | hiçbir yerde — üstelik `query` backend sözlüğünde de yok (`StepType.cs` onu saymaz), yalnız frontend enum'unda duruyor |
533
552
  >
@@ -564,6 +583,19 @@ değer tipleri: `static · page · property · storage · variable · code · it
564
583
  > adımında ve `{{ }}` bağlamasında `Functions.<ad>(…)` ile de çağrılır; gateway bilinmeyen adı ve
565
584
  > fazla argümanı FLOW-025 ile uyarır. Tam gövde `ngapps://examples/flow-call-function`, kurallar
566
585
  > **`platform_guide('functions')`**.
586
+ >
587
+ > **`share` adımı ("Share")** metin ve bağlantı paylaşır: `args: { title, message, path }`. `title`
588
+ > DÜZ METİNDİR (ifade yazılırsa FLOW-022 uyarır); `message` ve `path` `storage.value` gibi
589
+ > İFADEDİR — düz metin tırnak ister (`"'Şuna bak'"`, `"'https://…/urun/' + Page.queryParams.id"`).
590
+ > Bağlantının adı `url` DEĞİL `path`'tir: backend `Title`/`Message`/`Path` alanlarında saklar ve
591
+ > tanımadığı anahtarı 200 ile sessizce düşürür (FLOW-027 `url`/`link`/`text`'i ve tırnaksız adresi
592
+ > bloklar). Bağlantı ŞEMALI TAM ADRES olmalı (`https://…`, `mailto:…`): şemasız değer (`www.…`,
593
+ > `urun/42`) adres sayılmaz, metnin sonuna eklenir. İfadenin sonucu yalnız dize ya da sayıysa
594
+ > paylaşılır; `false` (`koşul && 'x'`), `null` ve nesne boş sayılır. Cihazda paylaşım sayfası açılır
595
+ > (Android'de bağlantı metnin sonuna eklenir), web'de tarayıcının paylaşım penceresi, yoksa ya da
596
+ > reddederse metin + bağlantı panoya yazılır ve bildirim çıkar. Tek dal `always`: iOS'ta sayfa
597
+ > kapanınca, Android'de seçici açılır açılmaz (RN seçicinin kapanışını bildirmez) sürer; metin de
598
+ > bağlantı da boşsa hiçbir şey açılmaz, akış hemen sürer.
567
599
 
568
600
  > Bir microservice adımı kaydedildiğinde platform `args.microserviceSchema`'yı
569
601
  > (parameterScheme + returnScheme + `isReturnArray`) kendisi doldurur — `flow_get` ile geri
@@ -1081,15 +1113,22 @@ UYGULAMAZ** — koordinatları aynen kaydeder (aşağı itme yalnız `page_creat
1081
1113
  > SONRAKİ ağaca bakarak. Layout'ta da geçerlidir: kırılma
1082
1114
  > `_repeaterValues` geri yazmasındadır, barındıranın sayfa mı layout mu olduğuna bakmaz.
1083
1115
  >
1116
+ > **Mobil uygulamada** başlıklar altında gruplu liste (güne/kategoriye göre) iç içe repeater
1117
+ > gerektirmez: tek `grouped-list` (`groupBy` + başlık `{{ Section.title }}` + öğe şablonu) —
1118
+ > `platform_guide('mobile')` §Güne göre görev listesi. PAGE-013 mesajı mobilde bunu önerir.
1119
+ >
1084
1120
  > **Yapılacak:** satır içi listeyi tek bir `textarea`'da **satır başına bir öğe** olarak topla
1085
1121
  > (`options_text`), kaydederken `split('\n')`'le böl. Ölçülmüş çalışan sürüm: anket editörü
1086
1122
  > (`anket-admin/anket-duzenle`). Gerçekten satır satır düzenleme gerekiyorsa iç listeyi AYRI bir
1087
1123
  > sayfada/modalda yönet — iç içe repeater bunu karşılamıyor.
1088
1124
 
1089
1125
  > **Repeater satırını akıştan silmek/eklemek:** repeater ÇOCUĞUNDAKİ bir butonun click akışında
1090
- > `RowIndex` doğru satırı verir, ve hedef repeater'ın KENDİSİ olduğu için `rows`'a yazmak serbesttir
1091
- > (`isRepeaterSelf` dalı satır-değeri yoluna girmez). Ölçülmüş desen:
1092
- > `q_list.rows = (q_list.rows || []).filter(function(r, i){ return i !== RowIndex; })`.
1126
+ > tıklanan satır olay makineye gitmeden SEÇİLİR, yani `q_list.selectedIndex` o satırdır — her runtime
1127
+ > sürümünde. Hedef repeater'ın KENDİSİ olduğu için `rows`'a yazmak serbesttir (`isRepeaterSelf` dalı
1128
+ > satır-değeri yoluna girmez). Desen:
1129
+ > `q_list.rows = (q_list.rows || []).filter(function(r, i){ return i !== q_list.selectedIndex; })`.
1130
+ > `RowIndex` de aynı satırı verir, ama yalnız ARC-121 düzeltmesini (2026-09-27) taşıyan runtime'da;
1131
+ > eskisinde akışta tanımsızdı.
1093
1132
  > Ekleme aynı şekilde `.slice()` + `push` ile yapılır; satır numarasını ekledikten sonra yeniden ata.
1094
1133
 
1095
1134
  **Gönderimi saklamak:** satır başına normalize kayıt yerine tek `TEXT` kolonda anlık görüntü
@@ -1363,6 +1402,16 @@ alanları silinir. Gateway bu yüzden `page_update`'te sayfayı önce okur, yaln
1363
1402
  alanları değiştirip tam gövdeyi geri yazar (`layout_update` ve `component_update` de aynı).
1364
1403
  Yine de `schema` parametresini elle doldurursan okuduğun hâli değil, **verdiğin liste** yazılır.
1365
1404
 
1405
+ **`page_update` sürümsüz yazmaz (PAGE-039).** Platform sayfa PUT'unu `baseVersion` olmadan reddeder.
1406
+ `baseVersion` vermezsen gateway bu sayfada en son gördüğü sürümü gönderir: `page_get` ya da
1407
+ `page_schema_get` yanıtındaki `version`, `page_schema_patch`'in `newVersion`'ı ya da önceki
1408
+ `page_update`'in yazdığı sürüm. Hiçbiri yoksa istek **gönderilmez** — önce oku. Sayfa o sürümden beri
1409
+ değiştiyse (Studio'da açık sekme, başka ajan) 409 döner; mesaj güncel sürümü ve son yazmayı (zaman,
1410
+ kullanıcı, kaynak, revizyon) söyler ve hiçbir şey yazılmaz. Doğru yol yeniden okuyup değişikliği
1411
+ `page_schema_patch` ile yapmaktır; aradaki değişikliği bilerek ezmek için `force: true` son sürümü
1412
+ okuyup onun üstüne yazar ve yanıt `forced: true` taşır. Sebep: Bitki Bakım koşusunda sürümsüz bir
1413
+ tam gövde yazımı ana sayfayı ve profili boşalttı, akışları sildirdi (14:02).
1414
+
1366
1415
  ### Atomiklik: 500 KB'lık şemada tek komponente dokunmak
1367
1416
 
1368
1417
  `page_schema_patch` şemanın tamamını göndermez — yalnız op'ları gönderir. Tam gövde ne LLM
@@ -1372,7 +1421,8 @@ ve geri yazar. Sözleşme (`PatchPageSchemaCommandHandler`):
1372
1421
  | Aşama | Ne yapar |
1373
1422
  |---|---|
1374
1423
  | Kilit | Sayfa başına 60 sn'lik dağıtık kilit; başka bir aktör yazıyorsa istek `PageSchemaLockedException` ile döner (409) |
1375
- | Sürüm | `baseVersion` verdiysen `UpdatedAt.Ticks` ile karşılaştırılır; tutmazsa `PageSchemaVersionConflictException` (409) ve **hiçbir op uygulanmaz** |
1424
+ | Sürüm | `baseVersion` verdiysen `UpdatedAt.Ticks` ile karşılaştırılır; tutmazsa `PageSchemaVersionConflictException` (409) ve **hiçbir op uygulanmaz** — bütün op'lar etkisizse hariç (aşağıdaki "Tekrar" satırı) |
1425
+ | Tekrar | `add_component`'in `elementId`'si zaten varsa ve düğüm istenen ebeveynde, istenen içerikle duruyorsa op **etkisizdir** (hata değil; konum karşılaştırılmaz). Hedef düğümü değiştirmeyen `replace_prop`/`set_*`/`add_event` da etkisizdir. Bütün op'lar etkisizse yama yazılmaz, sürüm artmaz ve eski `baseVersion`'a rağmen 200 `noop: true` döner; sürüm eskiyse `stale: true` ve `newVersion` sizin gönderdiğinizdir. Aynı id'de farklı düğüm hâlâ 422 `duplicate-id`'dir |
1376
1426
  | Uygulama | Her op hedefini ağaçta arar (`Find` → JSON Pointer) ve **yalnız o düğüme** dokunur; kardeşler, üst düğümler ve diğer alt ağaçlar bit düzeyinde korunur |
1377
1427
  | Hep-ya-hiç | Op'lar önce bellekte uygulanır; **tek bir hata** (bilinmeyen op, bulunamayan hedef, tekrar eden elementId, doğrulama) tüm komutu düşürür — kısmi yazma yoktur |
1378
1428
  | Doğrulama | Tam şema `PageSchemaValidator`'dan geçer (422 + alan bazlı `errors`) |
@@ -1381,7 +1431,18 @@ ve geri yazar. Sözleşme (`PatchPageSchemaCommandHandler`):
1381
1431
 
1382
1432
  Yanıt üç alan döner: `applied` (uygulanan op sayısı), **`appliedPatch`** (her op'un şemada tam
1383
1433
  olarak hangi JSON Pointer'a ne yaptığı — RFC 6902 biçiminde; "gerçekten yalnız o komponente mi
1384
- dokunuldu" sorusunun kanıtı) ve **`newVersion`** (`UpdatedAt.Ticks`).
1434
+ dokunuldu" sorusunun kanıtı) ve **`newVersion`** (`UpdatedAt.Ticks`). Etkisiz op varsa `unchanged`
1435
+ (elementId listesi) ve `alreadyApplied` cümlesi gelir; hepsi etkisizse `noop: true`.
1436
+
1437
+ **Yanıt kaybolursa aynı gövdeyle tekrarla.** Ağ koptu, zaman aşımı oldu ve yamanın uygulanıp
1438
+ uygulanmadığını bilmiyorsan aynı `page_schema_patch`'i **aynı gövde ve aynı `baseVersion`** ile
1439
+ yeniden gönder. Uygulanmışsa `noop: true` + `alreadyApplied` döner, hiçbir şey ikinci kez yazılmaz;
1440
+ uygulanmamışsa yama şimdi uygulanır. `add_component` (gövdede `id` verme — platform her istekte yeni
1441
+ `id` üretir, karşılaştırma onu yok sayar), aynı değeri yazan `replace_prop`/`set_*` ve aynı bağı kuran
1442
+ `add_event` tekrarda etkisizdir. İki istisna: `remove_component` ikinci kez `unknown-target` döner — o
1443
+ durumda `page_schema_get` ile bak; eklediğin düğümün bir prop'unu aynı yamanın sonraki op'u
1444
+ değiştirdiyse tekrar `duplicate-id` alır. Yanıt `stale: true` taşıyorsa sayfa sürümünden beri başka bir
1445
+ yazımla da değişmiştir: devam etmeden önce yeniden oku. `idempotencyKey` yoktur ve gerekmez.
1385
1446
 
1386
1447
  > **`appliedPatch` gövdesi VARSAYILAN OLARAK iskelettir.** Girdi `{ op, path }` döner; eklenen
1387
1448
  > düğüm `component` altında yalnız `id`/`elementId`/`name`/`type`/`children` taşır. Kanıt zaten
@@ -1401,12 +1462,29 @@ yamanın `baseVersion`'ıdır. Kontrol platformda atomiktir (satır kilidi + sü
1401
1462
  `UPDATE`'te), yani iki çağrı arasına giren yazarı da yakalar. `baseVersion` vermezsen gateway yalnız
1402
1463
  bu çağrıda zaten okuduğu sayfanın sürümünü gönderir; hiç okumadıysa yama sürümsüz gider ve son
1403
1464
  yazan kazanır. Eski `expectedUpdatedAt` hâlâ çalışır ama `baseVersion`'ın yerini tutmaz.
1465
+
1466
+ **Studio da sürüm gönderir (2026-09-26, ZM-27).** Önceden Studio'da açık kalan bir sekme, yayını
1467
+ kaçırdığı yamalarının üstüne kendi eski (bir kez boş) şemasını yazıyor ve bağı kalmayan akışları
1468
+ sildiriyordu. Artık Studio'nun her kaydı sürüm taşır: eski sekme 409 alır ve taslağını senin
1469
+ yazdığının üstüne birleştirir. `revision_list`'te yamandan sonra `source: studio` satırı görürsen bu
1470
+ artık bir üzerine yazma değil, birleştirilmiş bir kayıttır — yine de `revision_diff` ile bak.
1404
1471
  Yanıttaki `touched`, kaydı değişen elementId'leri listeler (eklenen alt ağaç dâhil).
1405
1472
 
1406
1473
  > **Büyük şemada çalışma sırası:** `mode:'summary'` ile ağacı çıkar (props yok, 500 KB'lık sayfada
1407
1474
  > bile küçük) → hedefin `elementId`'sini al → gerekiyorsa `mode:'subtree'` ile yalnız o dalı oku →
1408
1475
  > op'ları `page_schema_validate` ile kuru koştur → `page_schema_patch`. `mode:'full'` yalnız
1409
1476
  > gerçekten tüm şema gerektiğinde çağrılır.
1477
+ >
1478
+ > **`full` sığmazsa hata verir, kırpmaz.** Yanıt boyut sınırını aşan şemada `mode:'full'` hata döner
1479
+ > ve düğüm sayısını söyler. Tamamı gerekiyorsa `cursor:'0'` ile oku ve `nextCursor` gelmeyene kadar
1480
+ > sür: her parça `items[]` taşır, kalem `parentElementId` düğümünün `children[index]`'inin yerine
1481
+ > geçer (`parentElementId` yoksa kök dizinin `[index]`'i); alt ağacı sığmayan düğüm kabuk gelir
1482
+ > (`children: [{ elementId, deferred: true }]`) ve sonraki kalemler onu doldurur. Toplanan düğüm sayısı
1483
+ > `nodeCount`'a eşit değilse okuma eksiktir. Okuma arasında sayfa değişirse 409 — baştan başla.
1484
+ > `allowTruncated:true` kırpılmış gövdeyi açıkça verir (`truncated: true`); o gövdeye dayanan
1485
+ > `page_schema_patch` PAGE-038 uyarısı taşır. **`page_update`'e kırpık ya da yarım şema gönderme:**
1486
+ > PUT tam REPLACE'tir, eksik düğümler geri dönüşsüz silinir — gateway düğüm eksilten şemayı
1487
+ > `allowNodeLoss:true` olmadan PAGE-037 ile reddeder.
1410
1488
 
1411
1489
  > **`remove_component`'in yan etkisi — tek istisna.** Sayfa kaydedilirken artık hiçbir komponent
1412
1490
  > olayının, prop'unun ya da sayfa olayının referans vermediği akışlar platformca **kalıcı olarak
@@ -1831,15 +1909,24 @@ page_container
1831
1909
  ```
1832
1910
  start → [setter] btn.isLoading = true, btn.isDisabled = true, notification gizle
1833
1911
  → [microservice] sign-in (POST; email + password body'den)
1834
- → [condition] !!Steps.signIn.data.token
1835
- ├─ always → [setter] buton durumunu sıfırla
1836
- ├─ if → [setcookie] { name:'APP_Token', value:'Steps.signIn.data.token',
1837
- │ expiresInHours: 240 }
1838
- │ → [redirect] hedef sayfaya
1839
- └─ else → [setter] notification.content =
1840
- { type:'code', ref:'Steps.signIn.data.message || "Giriş başarısız"' }
1912
+ ├─ error → [setter] btn.isLoading=false, btn.isDisabled=false,
1913
+ │ notification.content =
1914
+ │ { type:'code', ref:'Steps.signIn.error?.message ?? "Giriş başarısız"' }
1915
+ └─ always → [condition] !!Steps.signIn.data.token
1916
+ ├─ always → [setter] buton durumunu sıfırla
1917
+ ├─ if → [setcookie] { name:'APP_Token', value:'Steps.signIn.data.token',
1918
+ │ expiresInHours: 240 }
1919
+ │ → [redirect] hedef sayfaya
1920
+ └─ else → [setter] notification.content =
1921
+ { type:'code', ref:'Steps.signIn.error?.message ?? "Giriş başarısız"' }
1841
1922
  ```
1842
1923
 
1924
+ - **`sign-in` adımının `error` dalı burada ZORUNLUDUR, süs değil.** Dal bağlı değilken bu şablonun
1925
+ `condition`'ı `Steps.signIn.data.token`'ı okur ve gateway'i **FLOW-028** ile uyarır (yukarıdaki
1926
+ "Hangi yolu seçeceksin" notu): hata anında `data` `null`'dur, token okuması sessizce `undefined`
1927
+ döner. Dal bağlanınca hata onun üzerinden sürer — `always`/`condition` yalnız BAŞARIDA çalışır,
1928
+ yani `data.token` orada gerçek bir iş kuralıdır (uç 200 dönüp token'sız yanıt verirse), hata
1929
+ algılama YÖNTEMİ değil.
1843
1930
  - **Cookie adı middleware'in okuduğu adla birebir aynı olmalı** (`cookie.APP_Token` —
1844
1931
  `platform_guide('middleware')` Tarif 1). Uyuşmazlık sessizce "hep 401" demektir.
1845
1932
  - `redirect` adımı `isRaw: true` iken `path` **çok satırlı JS bloğudur** (son ifade değeri döner) —
@@ -2029,9 +2116,14 @@ hiçbiri** bağlı değildi; üç portalın da rol menüsü boş çıkıyor, pan
2029
2116
  > (`dto_get` ile bak: genelde `role`, `role_name`, `roles_csv` vardır). Sayfa tarafında CSV
2030
2117
  > kullan — ayrıntı ve şablon `platform_guide('expressions')` §2.
2031
2118
  >
2032
- > **Uç hata dönerse `.data` dizi değildir.** 401/404 sonrası `Steps.x.data` bir nesnedir ve
2033
- > `rows || []` bunu yakalamaz (nesne truthy) → `rows.filter is not a function` /
2034
- > `(items ?? []).map is not a function`. `Array.isArray(Steps.x?.data) ? Steps.x.data : []` yaz.
2119
+ > **Uç hata dönerse `.data` NULL'dur, hata gövdesi değil (ADR 0004, 2026-09-26).** 2026-09-26'ya
2120
+ > kadar `Steps.x.data` 401/404 sonrası hata gövdesiydi ve `rows || []` bunu yakalamıyordu (nesne
2121
+ > truthy) → `rows.filter is not a function` / `(items ?? []).map is not a function`; artık uç hata
2122
+ > dönünce `data` **null**'dur ve bu adım boş veriyle koşar — `rows` gibi bir listeye bağlarsan
2123
+ > bileşen `null`'ı boş liste sayar, `Array.isArray(...) ? ... : []` sarmalamaya artık gerek yok.
2124
+ > Hata OLDUĞUNU fark etmeden geçmemek için `error` dalı bağlayın (yukarıdaki §"4xx de hatadır",
2125
+ > gateway bunu bağlı değilken **FLOW-028** ile uyarır); hata gövdesini görmek istiyorsan
2126
+ > `Steps.x.error.body`'yi okuyun.
2035
2127
 
2036
2128
  ## ⚠️ `page_schema_patch` `html` komponentini reddediyor — katalogda mükerrer kayıt (2026-08-19)
2037
2129
 
@@ -2094,6 +2186,31 @@ micro-op hattına dönün.
2094
2186
  > uyarı yoktu. Silinmesini bilerek istiyorsanız çağrıyı `allowFlowDeletion: true` ile tekrarlayın;
2095
2187
  > yanıt `deletedFlows` ile hangi akışların gittiğini listeler.
2096
2188
  >
2189
+ > **Silinen akış geri kurulabilir (2026-09-26, ZM-27).** Sayfa kaydının sildiği akış artık revizyon
2190
+ > defterine `operation: delete` satırı olarak düşer (kaynağı `studio` ya da `mcp`). `flow_restore`
2191
+ > akışı son revizyonundaki ad, slug ve adımlarla AYNI id ile yeniden kurar; olaya bağlamaz —
2192
+ > geri geldikten sonra `set_event`. İki sınır: revizyon saklama süresi (budanan akış 404) ve
2193
+ > adımların hedeflediği komponentler — akış bir komponenti tetikleyen/parametre olarak
2194
+ > gösteriyorsa o komponent şemada durmalıdır (canlı ölçüldü: `flow_restore` "Trigger Element Id"
2195
+ > dış anahtarına çarpar).
2196
+ >
2197
+ > **Boşalan sayfa: `page_restore` (2026-09-26).** Sayfayı ve akışlarını birlikte geri kurar:
2198
+ > `revision_list(module: 'page', recordId: <pageId>)` → boşalmadan önceki revizyonu bul (`source` ve
2199
+ > `correlationId` yardım eder; `revision_get` ile içeriğine bak) → `page_restore({ revisionNo })`.
2200
+ > Platform önce eksik akışları adımsız kurar, sonra şemayı eski komponent id'leriyle yazar, sonra
2201
+ > adımları yazar — akış→komponent ve olay→akış dış anahtarları bu sırayla tek transaction'da çözülür.
2202
+ > Yanıt `restoredFlows` ve `deletedFlows` (N'den sonra bağlanmış, şimdi yetim kalan) döner.
2203
+ > Revizyonu kalmamış bir akış varsa 409 ve hiçbir şey yazılmaz.
2204
+ >
2205
+ > **Doğru revizyonu seç.** Şemayı yüklemeyen komutların (`UpdateProjectCommand`,
2206
+ > `UpdateApplicationCommand`…) yazdığı sayfa revizyonları komponent taşımaz; komponenti olan sayfayı
2207
+ > böyle bir revizyona döndürmek 409 `PageRevisionEmptyException` ile reddedilir (bilerek boşaltmak için
2208
+ > `allowEmpty: true`). `correlationId`'si `PatchPageSchemaCommand` ya da `UpdatePageCommand` olan
2209
+ > revizyonu seç. İki sınır daha: N'den beri silinen layout/rol/middleware/özel komponent ya da başka
2210
+ > sayfanın aldığı slug geri yüklemeyi dış anahtar/benzersizlik hatasıyla düşürür (transaction geri
2211
+ > alınır, veri kaybı yok); yalnız başka bir akışın adımından çağrılan (olaya bağlı olmayan) akışlar
2212
+ > geri kurulmaz — onlar için `flow_restore`.
2213
+ >
2097
2214
  > **FLOW-018 artık silinen düğümün ALT AĞACINI da görüyor.** Eskiden yalnız op'un kendi
2098
2215
  > `target`'ına bakılıyordu: bir kapsayıcı silinince ÇOCUĞUNA yazan setter sessizce kopuyor ve
2099
2216
  > uyarı hiç çıkmıyordu (09-05, `sign_in_form` → `btn_sign_in`).
@@ -2137,6 +2254,9 @@ micro-op hattına dönün.
2137
2254
  style + Kural 1h deseniyle yeniden kurulunca kart 400px'e ve ortaya oturdu. Gateway bunu **PAGE-014 (uyarı)** ile
2138
2255
  yanıtın `layoutWarnings` alanında bildirir. **Düzeni `style` ile kurmaya hiç kalkışma:**
2139
2256
  ortalama/yaslama/aralık işi kapsayıcının kendi `layout: 'flex'` prop grubuna aittir (Kural 1f-2).
2257
+ **İstisna `html`:** onun `style`'ı yazarın işaretlemesine kapsamlı giren CSS metnidir (web'de iç
2258
+ seçicili sınıf, mobilde belgenin stil bloğu) — string asıl biçimdir ve PAGE-014 onda susar. Mobilde
2259
+ `html`'e nesne `style` verme: çizilmez (MOB-002).
2140
2260
  - **Menüde "bulunduğun sayfa" vurgusu: `sidebar`/`layout`'ta `activeHref`, `header`'da
2141
2261
  `activeNavHref`.** `sidebar` ve `layout` menü ağacını ortak renderer'la çizer: `activeHref` boşken
2142
2262
  tarayıcıdaki adrese uyan satırı kendileri vurgular, prop yazılırsa o satır vurgulanır (akıştan
@@ -98,7 +98,10 @@ theme_update({ theme: {
98
98
  ```
99
99
 
100
100
  **Yazı tipi ve ölçek:** `fontFamily` panelin Google Fonts listesinden bir ad olmalı — runtime
101
- fontu ADIYLA Google'dan çeker, tanınmayan ad sessizce sans-serif'e düşer (THEME-008).
101
+ fontu ADIYLA Google'dan çeker, tanınmayan ad sessizce sans-serif'e düşer (THEME-008). Mobilde de
102
+ aynı kural geçerlidir: cihaz aynı adı Google Fonts'tan TTF olarak indirip `expo-font` ile yükler
103
+ (400 ve 700 kalınlık) — Android'de kalın yüz SENTETİKTİR (iOS gerçek kalın dosyayı çizer,
104
+ ZM-10, 2026-09-26).
102
105
 
103
106
  ```jsonc
104
107
  theme_update({ theme: {
@@ -648,9 +648,15 @@ Tam örnek, ölçülen çıktı şekli ve `code` çocuğu tuzağının çıkış
648
648
 
649
649
  ```js
650
650
  // dizi (isSingle:false) · nesne (isSingle:true) · düz değer (yalnız pk dönen yazma)
651
- (Array.isArray(x.data) ? (x.data[0] && x.data[0].id) : (x.data && x.data.id !== undefined ? x.data.id : x.data))
651
+ (x.data && typeof x.data !== 'string' && typeof x.data.length === 'number' ? (x.data[0] && x.data[0].id) : (x.data && x.data.id !== undefined ? x.data.id : x.data))
652
652
  ```
653
653
 
654
+ Diziyi `Array.isArray` ile değil `.length` ile tanıyın: 2026-09-27'den önceki backend'de tablo
655
+ adımının listesi Jint'e sarılı bir CLR nesnesi olarak iniyordu ve `Array.isArray(x.data)` `false`
656
+ dönüyordu — koşuda (2026-09-24) giriş akışı bu yüzden her kullanıcıyı 401 ile reddetti
657
+ (`zeyrek-backend/docs/incident-log.md` #89). `.length`, `.map`, `.filter` o sürümde de çalışıyordu;
658
+ düzeltilmiş backend'de `Array.isArray` de doğrudur.
659
+
654
660
  Şekli bilmiyorsan bu yazımı kullan; `workflow_preview` çıktısında `steps[].output`'a bakarak
655
661
  hangi vakada olduğunu da görebilirsin.
656
662
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "hash": "f37265d598ea468deb2157165891629c2e88c7fd2238e82ec60cda7f58f7de4c",
3
- "generatedAt": "2026-09-18T12:57:15.416Z",
2
+ "hash": "466761837eb9a463fe5984ca119fc04fbdc946cf363177b71efd1b195b8729ca",
3
+ "generatedAt": "2026-09-27T17:47:09.197Z",
4
4
  "types": 186,
5
5
  "files": 108,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
package/dist/server.js CHANGED
@@ -84,7 +84,7 @@ const moduleRegistrations = {
84
84
  mobile: registerMobileTools,
85
85
  };
86
86
  export function createGatewayServer(platform) {
87
- const server = rejectUnknownArguments(instrumentServer(new McpServer({ name: "ngapps", version: "0.1.0" }), platform.config));
87
+ const server = rejectUnknownArguments(instrumentServer(new McpServer({ name: "ngapps", version: "0.1.0" }), platform.config), platform);
88
88
  for (const moduleName of platform.config.modules) {
89
89
  moduleRegistrations[moduleName](server, platform);
90
90
  }
@@ -118,7 +118,7 @@ export function registerDatasourceTools(server, platform) {
118
118
  }, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/datasources/resources', { environment })));
119
119
  server.registerTool('datasource_configuration_options', {
120
120
  title: 'Yapılandırma seçenekleri',
121
- description: "Datasource'un alan tipi kataloğunu döner: commonType başına dbType listesi, varsayılan tip, tipin kabul ettiği seçenek anahtarları (length/precision/collation/...) ve dönüştürülebilir tipler; ayrıca ilişki kuralları (onUpdate/onDelete) ve identity generation tipleri. VARSAYILAN YANIT ÖZETTİR (~5 KB): collation DEĞER listesi (816 kayıt, tam yanıtın %79'u) dışarıdadır — tipin collation kabul edip etmediği zaten options'ta yazar. Collation değerleri gerçekten gerekiyorsa includeCollations: true ile tam yanıtı isteyin (~45 KB).",
121
+ description: "Datasource'un alan tipi kataloğunu döner: commonType başına dbType listesi, varsayılan tip, tipin kabul ettiği seçenek anahtarları (length/precision/collation/...) ve dönüştürülebilir tipler; ayrıca ilişki kuralları (onUpdate/onDelete) ve identity generation tipleri. İlişki alanına yazarken kuralı SAYI olarak verin (`None` 0 · `Cascade` 1 · `SetNull` 2 · `SetDefault` 3 — `Rule` sırası): her backend sürümünde çalışır. Katalog adı listeler; `onDelete` adı (`Cascade`) 2026-09-27'den sonraki backend'de de kabul edilir, `onUpdate` adı hâlâ reddedilir (500). VARSAYILAN YANIT ÖZETTİR (~5 KB): collation DEĞER listesi (816 kayıt, tam yanıtın %79'u) dışarıdadır — tipin collation kabul edip etmediği zaten options'ta yazar. Collation değerleri gerçekten gerekiyorsa includeCollations: true ile tam yanıtı isteyin (~45 KB).",
122
122
  inputSchema: {
123
123
  project: projectParam,
124
124
  environment: environmentParam,
@@ -87,7 +87,8 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
87
87
  if (deletedFlows.length > 0 && allowFlowDeletion !== true)
88
88
  throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
89
89
  const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
90
- assertScreenWriteGuards(spec.kind, { schema: added });
90
+ const resolved = await resolveProfile(platform, applicationId);
91
+ assertScreenWriteGuards(spec.kind, { schema: added }, resolved.profile.platform);
91
92
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
92
93
  const flowWarnings = opRecords.some(entry => entry.op === 'remove_component')
93
94
  ? [...orphanedFlowWarnings(nodes, events, opRecords), ...staleSetterWarnings(Array.isArray(record.flows) ? record.flows : [], opRecords, nodes)]
@@ -102,12 +103,12 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
102
103
  || (entry.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(entry.prop ?? ''))));
103
104
  const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
104
105
  if (nested.length > 0)
105
- throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
106
- const resolved = await resolveProfile(platform, applicationId);
106
+ throw new PlatformApiError(nestedRepeaterMessage(nested, resolved.profile.platform), 400);
107
107
  const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
108
108
  const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
109
109
  kind: spec.kind,
110
110
  ops: [...opRecords, ...propTypeOps],
111
+ savedNodes: nodes,
111
112
  steps: await boundFlowSteps(platform, resolved, project, environment, host, applicationId, id, opRecords)
112
113
  });
113
114
  const layoutWarnings = reshaped
@@ -21,7 +21,7 @@ async function assertExternalComponentsAllowed(platform, applicationId, tool) {
21
21
  throw new PlatformApiError(`${tool}: ${findingLine({
22
22
  code: 'MOB-006',
23
23
  severity: 'warn',
24
- message: "harici bileşen mobil uygulamada desteklenmiyor — protokol v1 tarayıcıda koşan .mjs custom element'tir, native çizer onu yükleyemez (mobil protokol v2 Faz 3). Çağrı platforma gönderilmedi; mobil ekranı katalog bileşenleriyle kurun (component_list)."
24
+ message: "harici bileşen mobil uygulamada desteklenmiyor — protokol v1 tarayıcıda koşan .mjs custom element'tir, native çizer onu yükleyemez (mobil protokol v2 Faz 3). Çağrı platforma gönderilmedi; mobil ekranı katalog bileşenleriyle kurun (component_list). Katalogda karşılığı olmayan küçük görünüm için geçici yol 'html' bileşenidir: içerik korumalı belgede tema değişkenleriyle çizilir, fireEvent/ngApps.emit ile akışa bağlanır (component_get('html'))."
25
25
  })}`, 400);
26
26
  }
27
27
  /** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular (backend ExternalComponentRules.CurrentProtocolVersion) */
@@ -275,7 +275,7 @@ async function functionWarnings(platform, host, project, environment, applicatio
275
275
  }
276
276
  const missing = fn.parameters.filter(parameter => parameter.required && !given.includes(parameter.name)).map(parameter => parameter.name);
277
277
  if (missing.length > 0) {
278
- warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error dolar ve akış hata gövdesiyle 'always' dalından devam eder.`);
278
+ warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error.message dolar ve .data null'dur; errorSteps yoksa akış 'always' dalından devam eder.`);
279
279
  }
280
280
  }
281
281
  for (const { step, origin, ref } of callSites) {
@@ -296,6 +296,60 @@ async function functionWarnings(platform, host, project, environment, applicatio
296
296
  }
297
297
  return [...warnings];
298
298
  }
299
+ /**
300
+ * FLOW-028 (g-02, ADR 0004 — ZM-07). `microservice.ts` `.finally` üç yoldan birini dener: adımın
301
+ * kendi `error` dalı bağlıysa oradan sürer, yoksa flow `errorSteps`'i (`engine.onError`) devralır,
302
+ * ikisi de yoksa `always` koşar ve storage artık `{ data: null, headers: null, error }`'dır — hata
303
+ * gövdesi ARTIK `data`'da DEĞİL. İkisi de bağlı değilken bu adımın `.data`'sını okuyan bir
304
+ * setter/condition varsa hata anında NULL üzerinden koşar ve yazar hiç fark etmez. Aynı yürüyüş
305
+ * FLOW-024/025'in kullandığı `flowExpressionSites` taramasıdır; setter'ın STORAGE (kod olmayan)
306
+ * referansı ayrıca kendi düz alanından okunur (FLOW-008/009 deseni, `rowsTargetsByStepName`).
307
+ */
308
+ function microserviceAlwaysDataWarnings(steps, errorSteps) {
309
+ const mainSteps = (Array.isArray(steps) ? steps : []).filter(isRecord);
310
+ const hasErrorFlow = (Array.isArray(errorSteps) ? errorSteps : []).filter(isRecord).some(step => String(step.type ?? '') !== 'start');
311
+ const label = (step) => String(step.stepId ?? step.name ?? '?');
312
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
313
+ const warnings = new Set();
314
+ for (const step of mainSteps) {
315
+ if (String(step.type ?? '') !== 'microservice')
316
+ continue;
317
+ const name = String(step.name ?? step.stepId ?? '');
318
+ if (name === '')
319
+ continue;
320
+ const hasErrorBranch = (Array.isArray(step.branches) ? step.branches : [])
321
+ .filter(isRecord)
322
+ .some(branch => String(branch.name ?? '') === 'error' && Boolean(branch.nextStepId));
323
+ if (hasErrorBranch || hasErrorFlow)
324
+ continue;
325
+ const pattern = new RegExp(`Steps\\s*(?:\\.\\s*${escapeRegExp(name)}\\b|\\[\\s*['"]${escapeRegExp(name)}['"]\\s*\\])\\s*\\??\\.\\s*data\\b`);
326
+ const message = (other) => `FLOW-028: '${label(other)}' adımı 'always' dalında '${name}.data' okuyor ama '${name}' adımının ne bağlı bir 'error' dalı ne akışın 'errorSteps'i var — uç hata dönerse bu adımın 'data'sı NULL olur (eskiden hata gövdesiydi, ADR 0004) ve '${label(other)}' boş/null veriyle koşar. '${name}' adımına 'error' dalı bağlayın ya da flow'a errorSteps verin.`;
327
+ for (const other of mainSteps) {
328
+ if (other === step)
329
+ continue;
330
+ if (String(other.type ?? '') === 'setter') {
331
+ const iteratees = Array.isArray(other.args?.iteratees)
332
+ ? other.args.iteratees.filter(isRecord)
333
+ : [];
334
+ for (const iteratee of iteratees) {
335
+ const mapping = (iteratee.mapping ?? {});
336
+ const value = (iteratee.value ?? {});
337
+ const reference = typeof mapping.source === 'string' && mapping.source.length > 0
338
+ ? mapping.source
339
+ : typeof value.ref === 'string' ? value.ref : '';
340
+ if (value.type === 'storage' && (reference === `${name}.data` || reference.startsWith(`${name}.data.`) || reference.startsWith(`${name}.data[`))) {
341
+ warnings.add(message(other));
342
+ }
343
+ }
344
+ }
345
+ for (const site of flowExpressionSites(other)) {
346
+ if (pattern.test(site.expression))
347
+ warnings.add(message(other));
348
+ }
349
+ }
350
+ }
351
+ return [...warnings];
352
+ }
299
353
  export function registerFlowTools(server, platform) {
300
354
  const config = platform.config;
301
355
  server.registerTool('flow_list', {
@@ -369,7 +423,8 @@ export function registerFlowTools(server, platform) {
369
423
  ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
370
424
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
371
425
  ...flowBodyWarnings(steps),
372
- ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
426
+ ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord)),
427
+ ...microserviceAlwaysDataWarnings(steps, errorSteps ?? [])
373
428
  ];
374
429
  // Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
375
430
  // ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
@@ -438,7 +493,8 @@ export function registerFlowTools(server, platform) {
438
493
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
439
494
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
440
495
  ...flowBodyWarnings(flow.steps),
441
- ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
496
+ ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
497
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
442
498
  ];
443
499
  await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
444
500
  // Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
@@ -490,7 +546,8 @@ export function registerFlowTools(server, platform) {
490
546
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
491
547
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
492
548
  ...flowBodyWarnings(flow.steps),
493
- ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
549
+ ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
550
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
494
551
  ];
495
552
  // Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
496
553
  const netOps = fastJsonPatch.compare(current, flow);
@@ -521,4 +578,13 @@ export function registerFlowTools(server, platform) {
521
578
  await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId } });
522
579
  return { deleted: flowId, host, hostId };
523
580
  }));
581
+ server.registerTool('flow_restore', {
582
+ title: 'Silinen akışı geri yükle',
583
+ description: "Silinmiş bir akışı revizyon defterindeki son içeriğiyle (ad, slug, adımlar, barındırıcı) AYNI id ile yeniden kurar. Sayfa kaydı bağı kalmayan akışı kalıcı siler (FLOW-002, allowFlowDeletion) — Studio'nun eski sekmesi boş şema kaydettiğinde de (ZM-27). Akışı olaya YENİDEN BAĞLAMAZ: geri geldikten sonra set_event ile bağlayın. SINIR: adımları sayfanın bir komponentini hedefliyorsa (tetikleyen element, iterate/parametre komponenti) o komponent de şemada olmalıdır, yoksa geri yükleme reddedilir — sayfa boşaldıysa (ZM-27) page_restore kullanın: sayfayı ve akışlarını birlikte geri kurar. Akış hâlâ varsa 409, saklama süresi dolup revizyonu budanmışsa 404 döner. Silinen akışı revision_list(module: 'flow') satırlarında operation: delete olarak bulun; recordId akış id'sidir.",
584
+ inputSchema: { project: projectParam, environment: environmentParam, flowId: flowIdParam },
585
+ annotations: writes
586
+ }, handle(config, async ({ project, environment, flowId }) => {
587
+ const restored = unwrap(await platform.tenant('POST', project, `/flows/${flowId}/restore`, { environment }));
588
+ return { restored: flowId, flow: restored };
589
+ }));
524
590
  }