@appaflytech/zeyrek-ai-mcp 0.0.37 → 0.0.41

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 (53) hide show
  1. package/dist/knowledge/componentCatalog.js +457 -156
  2. package/dist/knowledge/componentEvents.js +49 -8
  3. package/dist/knowledge/connectDataBind.js +10 -2
  4. package/dist/knowledge/expressionPrelude.js +1 -0
  5. package/dist/knowledge/mobileComponentCatalog.js +21 -9
  6. package/dist/knowledge/mobileComponentEvents.js +21 -1
  7. package/dist/knowledge/mobileStore.js +75 -0
  8. package/dist/knowledge/normalize.js +52 -17
  9. package/dist/knowledge/platform/mobile.js +1 -1
  10. package/dist/knowledge/platform/mobileRules.js +1 -1
  11. package/dist/knowledge/platform/web.js +1 -1
  12. package/dist/knowledge/rules.js +244 -25
  13. package/dist/knowledge/styleResolve.js +27 -8
  14. package/dist/knowledge/styleResolveNative.js +4 -2
  15. package/dist/knowledge/themeResolve.js +1 -1
  16. package/dist/knowledge/themeSchema.js +2 -1
  17. package/dist/observability/trace.js +7 -2
  18. package/dist/platform/capture/actions.js +24 -1
  19. package/dist/platform/capture/summary.js +4 -1
  20. package/dist/platform/secrets.js +50 -2
  21. package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
  22. package/dist/resources/examples/mobile-list-detail-form.json +3 -0
  23. package/dist/resources/guides/component.md +1 -1
  24. package/dist/resources/guides/entity.md +16 -0
  25. package/dist/resources/guides/expressions.md +7 -6
  26. package/dist/resources/guides/merge.md +5 -4
  27. package/dist/resources/guides/microservice.md +2 -2
  28. package/dist/resources/guides/middleware.md +3 -1
  29. package/dist/resources/guides/mobile-publish.md +24 -0
  30. package/dist/resources/guides/mobile.md +50 -25
  31. package/dist/resources/guides/page.md +61 -23
  32. package/dist/resources/guides/theme.md +1 -0
  33. package/dist/resources/guides/translation.md +41 -1
  34. package/dist/resources/guides/workflow.md +31 -1
  35. package/dist/resources/schema-fingerprint.json +3 -3
  36. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +2 -1
  37. package/dist/resources/schemas/OrganizationProjectDto.json +8 -0
  38. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +59 -0
  39. package/dist/tools/applications.js +6 -3
  40. package/dist/tools/common.js +9 -5
  41. package/dist/tools/entities.js +41 -4
  42. package/dist/tools/environments.js +1 -1
  43. package/dist/tools/flows.js +5 -1
  44. package/dist/tools/layouts.js +22 -7
  45. package/dist/tools/mobile.js +15 -1
  46. package/dist/tools/mobileStore.js +21 -5
  47. package/dist/tools/pages.js +31 -45
  48. package/dist/tools/queries.js +1 -1
  49. package/dist/tools/rows.js +14 -3
  50. package/dist/tools/screenWrite.js +2 -2
  51. package/dist/tools/screens.js +39 -0
  52. package/dist/tools/translations.js +148 -6
  53. package/package.json +1 -1
@@ -135,9 +135,9 @@ alanlar denetlenmez); katalog okunamazsa denetim susar ve yazma eskisi gibi geç
135
135
  **Düğümü silip yeniden kurmayın:** o yol ona yazan akış setter'larını sessizce öldürür
136
136
  (`FLOW-018`, aşağıda Kural 2).
137
137
  - **Aynı ızgara hücrelerini iki komponente veremezsin.** Studio'da komponent tuvale bırakıldığında
138
- çakışan kardeşleri aşağı itme işini studio-api'nin sayfa kurma servisi yapar
139
- (`SchemaHelper.reorderComponents` → `draggableElement.moveItem`); komple şema yazan `page_create`
140
- / `page_update` o servisten geçmez, yani verdiğin koordinatlar olduğu gibi kayda girer ve tuvalde
138
+ çakışan kardeşleri aşağı itme işini Studio'nun ızgara motoru yapar
139
+ (`@ng-apps/screen-builder` `moveItem`); komple şema yazan `page_create`
140
+ / `page_update` o motordan geçmez, yani verdiğin koordinatlar olduğu gibi kayda girer ve tuvalde
141
141
  komponentler üst üste biner. **Gateway aynı algoritmayı şemanın tamamına uyguluyor** (kök dizi +
142
142
  her `children`, yalnız desktop `blocks[]` girdisi; `x`/`w`/`h` ve dizi sırası korunur, çakışanın
143
143
  `position.y`'si aşağı kayar) ve yanıtta `gatewayNormalized: "çakışan komponentler ızgarada aşağı
@@ -174,10 +174,9 @@ alanlar denetlenmez); katalog okunamazsa denetim susar ve yazma eskisi gibi geç
174
174
  ## Kural 1b — `page_build` YOK
175
175
 
176
176
  Entity'den otomatik sayfa üreten `page_build` tool'u **kaldırıldı**. Platformun
177
- `POST /pages/{app}/build/{page}` ucu üretimi `PageBuilderApiOptions:Url` adresindeki **ayrı bir
178
- servise** delege ediyor (`PageBuilderApiClient`); dev'de `localhost:4000`, prod'da `studio-api…`.
179
- O servis bu ürünün parçası değil ve ayakta olmayabilir. Sayfa/komponent şemasının tam karşılığı
180
- platformda zaten var → doğru yol yukarıdaki micro-op hattıdır.
177
+ `POST /pages/{app}/build/{page}` ucu ve onun delege ettiği sayfa üretme servisi de 2026-09-18'de
178
+ kaldırıldı; otomatik sayfa üretimi yok. Sayfa/komponent şemasının tam karşılığı platformda zaten
179
+ var → doğru yol yukarıdaki micro-op hattıdır.
181
180
 
182
181
  ## Kural 1c — Komponente VERİ bağlama: `mount` olayı + flow (setter adımı)
183
182
 
@@ -283,7 +282,8 @@ dalı bulamaz ve akışı **sessizce durdurur** (`flow-engine.ts:79`); Studio da
283
282
  İstisnalar: `condition` → dal adı rule id'sidir (ve o id bir **GUID**'dir — aşağıdaki tuzak 6),
284
283
  `confirm` → `yes|no|always`, `microservice` →
285
284
  `always` (eski akışlarda `success` de çalışır) **+ `error`** — bağlarsan hata anında `always`
286
- yerine ondan sürer (2026-09-26, ADR 0004; ayrıntı aşağıdaki §"4xx de hatadır").
285
+ yerine ondan sürer (2026-09-26, ADR 0004; ayrıntı aşağıdaki §"4xx de hatadır"), `validation` →
286
+ `success` (hepsi geçti) | `error` (biri düştü), ardından her durumda `always` (kuralları aşağıda).
287
287
 
288
288
  **2. `iteratees[].type` yalnız `change` | `fill`.** `setter.ts` başka değerde hiçbir dala
289
289
  girmez — adım çalışır, hiçbir şey yapmaz. `change` tek prop'a değer yazar, `fill` diziyi
@@ -446,13 +446,15 @@ göstermek error flow'un işidir.
446
446
  > | `Steps.<adımAdı>.error.body` | Ucun hata gövdesi — eski `Steps.<adımAdı>.data`'nın taşındığı yer | `microservice.ts` `.catch` |
447
447
  > | `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` |
448
448
  >
449
- > **`alert` adımının `message`'ı DÜZ METİNDİR — ifade yazarsan ekranda o ifade görünür.**
450
- > `alert.ts` `task.argument.message`'ı doğrudan `toast.error()`'a geçirir ve `args` hiçbir yerde
451
- > değerlendirilmez (`Runtime.svelte` `argument: step.args`): `{ "type": "alert", "args": {
452
- > "message": "Steps.error.message", "alertType": "error" } }` kullanıcıya harfi harfine
453
- > `Steps.error.message` gösterir. Sabit bir uyarı yetiyorsa `alert` en ucuz adımdır; **ucun ne
454
- > dediğini göstereceksen bir `setter` (ya da `code`) adımı zorunludur** — `value.type: 'code'`
455
- > ifadeyi orada gerçekten çalıştırır:
449
+ > **`alert` adımının `message`'ı ve `description`'ı METİN ŞABLONUDUR — yalnız `{{ }}` içi çözülür.**
450
+ > Bileşen prop bağlamasıyla aynı çözücü (`@ng-apps/flow` `resolveStepText` → `runPropertyCode`; web ve
451
+ > cihaz aynı): `{ "type": "alert", "args": { "message": "{{ Steps.error?.message ?? 'İşlem başarısız' }}",
452
+ > "alertType": "error" } }` ucun mesajını gösterir, `"{{ t('kayit.tamam') }}"` etkin dildeki çeviriyi.
453
+ > `{{ }}` DIŞINDA yazılan ifade çözülmez: `"message": "Steps.error.message"` kullanıcıya harfi harfine
454
+ > `Steps.error.message` gösterir (FLOW-022 uyarır). `{{ }}` içermeyen metin olduğu gibi kalır. 2026-10-08
455
+ > öncesi runtime'da (yeniden yayımlanmamış kurulum, eski mobil derleme) bu alanlar düz metindi; orada
456
+ > ucun mesajı bir `setter` (ya da `code`) adımıyla bir bileşene yazılır — mesajı ekranda kalıcı
457
+ > göstermek için bugün de bu yol geçerlidir:
456
458
  >
457
459
  > ```jsonc
458
460
  > // errorSteps: start → bu setter. Metni bir notification/text komponentine yazar.
@@ -464,13 +466,14 @@ göstermek error flow'un işidir.
464
466
  > "branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }] }
465
467
  > ```
466
468
 
467
- > **`confirm` adımının diyalog çerçevesi de yazılabilir.** `args` dört düz metin alanı taşır:
469
+ > **`confirm` adımının diyalog çerçevesi de yazılabilir.** `args` dört metin alanı taşır:
468
470
  > `message` (gövde), `title` (başlık), `confirmText` (onay düğmesi, `yes` dalı), `cancelText`
469
471
  > (vazgeç düğmesi, `no` dalı). Son üçü isteğe bağlıdır; boş bırakılan alan dile göre sabit metne
470
472
  > düşer ("Emin misiniz?" / "Evet" / "Hayır" — `dialog.functions.ts` `resolveDialogFrame`).
471
- > Dördü de `alert`'in `message`'ı gibi DÜZ METİNDİR (FLOW-022 hepsini denetler):
472
- > `{ "type": "confirm", "args": { "message": "Bu risk ve 4 aksiyonu silinecek.", "title": "Riski sil",
473
- > "confirmText": "Sil", "cancelText": "Vazgeç" } }`.
473
+ > Dördü de `alert`'in `message`'ı gibi METİN ŞABLONUDUR (FLOW-022 hepsini denetler); çok dilli
474
+ > uygulamada çeviri anahtarıyla yazılır:
475
+ > `{ "type": "confirm", "args": { "message": "{{ t('risk.sil.onay') }}", "title": "{{ Row.name }} silinsin mi?",
476
+ > "confirmText": "{{ t('sil') }}", "cancelText": "Vazgeç" } }`.
474
477
 
475
478
  > **Hangi yolu seçeceksin — adımın kendi `error` dalı mı, `errorSteps` mi, ana akışta
476
479
  > `condition` mı?** Bir hatada sırayla denenir (yukarıdaki 4xx bölümü) — üçü birden TANIMLI
@@ -529,6 +532,30 @@ Tarif: `platform_guide('middleware')`.
529
532
  - Adım hedef akışı **bekler** (`await engine.run`), sonra `always` dalından devam eder.
530
533
  - Gateway boş/yarım `args`'ı **FLOW-014** ile yakalar.
531
534
 
535
+ **`validation` adımının kuralları `args.validations` dizisindedir** (kök ADR 0014). Her kural
536
+ `{ id?, component, property, type, errorMessage, settings }`: `component` sayfadaki bileşenin
537
+ **elementId**'si (boş → bileşensiz kural; sayfada olmayan ad 404), `property` okunacak prop
538
+ (çoğunlukla `value`), `type` ∈ `required · email · regex · min-length · max-length · min-value ·
539
+ max-value`, `errorMessage` düz metin ya da `t:` anahtarı (düşen alanın `invalidText`'ine yazılır),
540
+ `settings` tek parametredir — runtime **ilk değeri** okur (`{ "value": 8 }`, `{ "pattern": "^\\d+$" }`;
541
+ parametresiz tipte `{}`). Kurallar dizi sırasıyla koşar; kuralsız adım yükte `[]` döner ve her
542
+ çalıştırmada `success`'e gider.
543
+
544
+ ```jsonc
545
+ { "stepId": "kontrol", "name": "kontrol", "type": "validation", "kind": "normal",
546
+ "args": { "validations": [
547
+ { "component": "email", "property": "value", "type": "email", "errorMessage": "t:validation.email", "settings": {} },
548
+ { "component": "sifre", "property": "value", "type": "min-length", "errorMessage": "En az 8 karakter", "settings": { "value": 8 } } ] },
549
+ "branches": [ { "id": "<GUID>", "name": "success", "nextStepId": "kaydet" },
550
+ { "id": "<GUID>", "name": "error", "nextStepId": null } ] }
551
+ ```
552
+
553
+ - `always` `success`/`error`'dan **sonra** da koşar — kaydetmeyi `always`'e bağlarsan düşen
554
+ formda da kaydeder.
555
+ - Bu şekil komponentin kendi `validations`'ından (`set_validations { type, message, value }`)
556
+ farklıdır; `message`/`value` burada sessizce düşer. Tanınmayan `type` runtime'da **geçer** sayılır
557
+ (platform kümeyi doğrulamaz). Gateway ikisini de **FLOW-041** ile yakalar.
558
+
532
559
  > **`code` adımının gövdesi `args.value` alanındadır — düz JS metni olarak.** Runtime onu
533
560
  > `(async () => { <value> })()` içinde koşturur (`code.ts:18`), yani `await` serbesttir ve dönüş
534
561
  > değeri akışa taşınmaz; etkiyi `Page`/`state`/komponent yazarak üretirsin. **Alan boşsa adım
@@ -619,7 +646,7 @@ değer tipleri: `static · page · property · storage · variable · code · it
619
646
  > **`platform_guide('functions')`**.
620
647
  >
621
648
  > **`share` adımı ("Share")** metin ve bağlantı paylaşır: `args: { title, message, path }`. `title`
622
- > DÜZ METİNDİR (ifade yazılırsa FLOW-022 uyarır); `message` ve `path` `storage.value` gibi
649
+ > DÜZ METİNDİR — `{{ }}` şablonu da çözülmez (ifade ya da `{{ }}` yazılırsa FLOW-022 uyarır); `message` ve `path` `storage.value` gibi
623
650
  > İFADEDİR — düz metin tırnak ister (`"'Şuna bak'"`, `"'https://…/urun/' + Page.queryParams.id"`).
624
651
  > Bağlantının adı `url` DEĞİL `path`'tir: backend `Title`/`Message`/`Path` alanlarında saklar ve
625
652
  > tanımadığı anahtarı 200 ile sessizce düşürür (FLOW-035 `url`/`link`/`text`'i ve tırnaksız adresi
@@ -879,6 +906,14 @@ Tire yerine `_` kullan.
879
906
  > `component_schema_patch` yamalarında), `layout_create`, `layout_update(views)` ve `layout_patch`;
880
907
  > `page_schema_validate` aynı kararı verir. Ad doldurulduktan sonra denetlenir: geçersiz bir
881
908
  > elementId adsız gelirse o da reddedilir.
909
+ >
910
+ > **`name` hiç verilmezse** gateway onu `elementId` ile tamamlar ve `gatewayNormalized`'da `name`
911
+ > diye bildirir: adsız düğümü platform kabul eder ama sayfada bir akış ya da `{{ }}` kod prop'u
912
+ > varsa FlowEngine `runCode` "Cannot read properties of undefined (reading 'catch')" ile düşer, akış
913
+ > `running:1`'de takılır ve hata adsız düğümü işaret etmez. Tamamlama PAGE-009 denetiminden ÖNCE
914
+ > koşar, yani `elementId` bu kuralın geçerli saydığı bir ad değilse (`'Ana Baslik'`, `'1abc'`,
915
+ > `'Functions'`) tamamlanan ad da reddedilir. Yine de her `add_component` gövdesine
916
+ > `name = elementId` yaz; tamamlama bir güvenlik ağıdır.
882
917
 
883
918
  ## Kural 1d — Sayfa iskeleti: `page_container` ve boyutlandırma
884
919
 
@@ -930,13 +965,16 @@ kapsayıcının kardeşi olarak yaşar (qm `[SY] Profil bilgilerim`: `page_conta
930
965
  > Bu tiplerde `size.h` gerçek yüksekliktir — göz kararı değil, içeriğe göre hesapla
931
966
  > (bir satır ≈ 10px; Kural 1f).
932
967
 
933
- ## Kural 1d-2 — Sayfanın ve layout'un KENDİ olayları: `mount` · `destroy`
968
+ ## Kural 1d-2 — Sayfanın ve layout'un KENDİ olayları: `mount` · `destroy` (mobil sayfada + `focus`)
934
969
 
935
970
  Komponent olaylarından AYRI olarak sayfa ve layout kayıtları kendi olay listesini taşır
936
971
  (`events: [{ name, value: <flow id> }]`) ve geçerli adlar SABİTTİR: **`mount`**
937
972
  (yüklendiğinde) ve **`destroy`** (sayfadan/layouttan ayrılırken — runtime destroy akışlarını
938
- navigasyondan ÖNCE koşar). Başka ad platformca kabul edilir ama bağ sessizce ölü kalır —
939
- gateway **PAGE-011 / LAY-004** ile istek gitmeden reddeder.
973
+ navigasyondan ÖNCE koşar). **Mobil uygulamanın SAYFASI** bir ad daha alır: **`focus`** — ekran
974
+ her göründüğünde koşar (ilk açılışta `mount`'tan sonra, sonra yığından geri dönüşte ve sekme
975
+ değişiminde; `platform_guide('mobile')`). Başka ad platformca kabul edilir ama bağ sessizce ölü
976
+ kalır — gateway **PAGE-011 / LAY-004** ile istek gitmeden reddeder; `focus` layout'ta ve web
977
+ sayfasında da reddedilir (web sayfayı her gezinmede baştan kurar, `mount` yeter).
940
978
 
941
979
  - Bağlama yolu `set_event` DEĞİLDİR (o komponent hedefler): sayfada `page_create` /
942
980
  `page_update`'in `events` parametresi, layout'ta `layout_create/update`'in `events`'i.
@@ -18,6 +18,7 @@ yazmalarının aksine burada "önce development'ta dene" diye bir ayrım YOKTUR.
18
18
  | Alan | Ne yapar | Ürettiği değişken(ler) |
19
19
  |---|---|---|
20
20
  | `mode` | `'light'` (varsayılan) \| `'dark'` — yayınlanan uygulamanın modu | palet tabanını seçer |
21
+ | `direction` | `'ltr'` \| `'rtl'` \| `'auto'` — sayfanın okuma yönü; `auto` uygulama dilinden türetir (`ar` `he` `fa` `ur` `ps` `sd` `ug` `yi` `dv` → rtl). Ayar YOKSA `dir` hiç yazılmaz (sayfa üst öğenin yönünü miras alır): `ar` ile kurulmuş mevcut proje bu ayar açılmadan aynalanmaz. Sayfa oluşturucu tuvali yönü uygulamaz. Grid yerleşimi kendiliğinden aynalanır; bileşenlerin ok simgeleri ve fiziksel `left/right` CSS'i bileşen bazında düzeltilir (ADR 0006) | CSS değişkeni üretmez, kap `dir` özniteliğine yazılır |
21
22
  | `palette` | AYDINLIK moddaki renk override'ları; `accent.onPrimary` isteğe bağlıdır — accent ÜSTÜNDEKİ metin, verilmezse kontrasttan türer (`#4787FA` gibi açık bir mavi siyah metin üretir, tasarımda beyazsa buraya yazın) | `--ng-accent-*` · `--ng-background-*` · `--ng-text-*` · `--ng-border-*` · `--ng-status-*` · `--ng-text-on-accent` |
22
23
  | `paletteDark` | KARANLIK moddaki renk override'ları | aynı küme |
23
24
  | `fontFamily` · `headingFontFamily` | Gövde ve başlık yazı tipi (Google Fonts adı) | `--ng-font` · `--ng-font-heading` |
@@ -55,7 +55,8 @@ diye bir alan **hiç görünmez**; blok tam olarak bunu görünür kılmak için
55
55
  | Tool | Ne yapar |
56
56
  |---|---|
57
57
  | `translation_list` | Application'ın tüm `(language, key, value)` kayıtlarını düz liste döner |
58
- | `translation_update` | `keys` (ekle/güncelle, `componentId` opsiyonel) ve/veya `deletedKeys` (sil) — ikisi de aynı çağrıda verilebilir, en az biri dolu olmalı |
58
+ | `translation_update` | `keys` (ekle/güncelle, `componentId` opsiyonel) ve/veya `deletedKeys` (sil) — ikisi de aynı çağrıda verilebilir, en az biri dolu olmalı. `file` kipi: doldurulmuş Excel'i içe aktarır (aşağıda) |
59
+ | `translation_export` | Çevirmene verilecek Excel dosyasını yerel yola yazar (aşağıda) |
59
60
  | `project_update` | `languages`/`defaultLanguage` — proje dillerini kurar/değiştirir |
60
61
 
61
62
  ```jsonc
@@ -194,6 +195,45 @@ Runtime `t:` ile başlayan bir mesajı anahtar sayar ve normal zincirden çözer
194
195
  olduğu gibi gösterilir. Çözüm çalışma anında olduğu için şema varsayılanlarını değiştirmek
195
196
  gerekmez — `schema.ts`'e dokunulmaz, katalog yeniden yüklenmez.
196
197
 
198
+ ## Dosyayla toplu çeviri (Excel)
199
+
200
+ İnsan bir çevirmene yeni bir dil yaptıracaksa: sözlük Excel'e iner, çevirmen hedef sütunu doldurur,
201
+ dosya geri yüklenir. **Çeviriyi sen yapacaksan dosyaya gerek yok** — `translation_list` ile oku,
202
+ `translation_update { keys }` ile yaz.
203
+
204
+ ```jsonc
205
+ // 1. dışa aktar — dosya YALNIZ outPath'e yazılır, Sürücü'ye yüklenmez
206
+ { "applicationId": "<app id>", "targetLanguage": "es", "outPath": "C:/ceviri/crm-es.xlsx" } // translation_export
207
+ // → { keys: 120, targetMissing: 120, sourceLanguage: "en", note: "… TR-004 …" }
208
+
209
+ // 2. çevirmen C sütununu doldurur; 3. önce kuru koşu
210
+ { "applicationId": "<app id>", "file": "C:/ceviri/crm-es.xlsx", "targetLanguage": "es", "dryRun": true } // translation_update
211
+ // → { created: 118, skippedEmpty: 2, unknownKeyCount: 0, targetLanguageInProject: false, note: "… ekleyecek …" }
212
+
213
+ // 4. gerçek içe aktarma
214
+ { "applicationId": "<app id>", "file": "C:/ceviri/crm-es.xlsx", "targetLanguage": "es" } // translation_update
215
+ // → { created: 118, languageAdded: true, … }
216
+ ```
217
+
218
+ Dosya: tek sayfa, `A` Key · `B` kaynak dil (yalnız referans, okunmaz) · `C` hedef dil; başlıklar
219
+ `Key` · `Default Language (EN)` · `Target Language (ES)`. İçe aktarma **başlık satırını okumaz**:
220
+ değerler `translation_update`'in zorunlu `targetLanguage`'ındaki dile yazılır, `C1`'deki `(ES)` yalnız
221
+ çevirmen içindir ve dili belirlemez. Gizli `_zeyrek`
222
+ sayfası dosyanın hangi application'dan çıktığını söyler; başka bir application'ın dosyası yine
223
+ yazılır ama yanıt `applicationMismatch: true` ve not taşır.
224
+
225
+ - **Dolu hücre mevcut değeri ezer**, boş/boşluk hücre atlanır, hiçbir şey silinmez.
226
+ - **Application'da olmayan key açılmaz** — `unknownKeys`'e düşer. Yeni key `keys` kipiyle açılır.
227
+ - Key eşleşmesi önce birebir, yoksa büyük/küçük harf duyarsız tek aday; aynı key dosyada iki satırda
228
+ ise 422 ve hiçbir şey yazılmaz.
229
+ - **Hedef dil projede yoksa içe aktarma onu projeye ekler — bütün ortamlara** (TR-004). Önce
230
+ `project_update` gerekmez; ama dil production'a da gider ve orada çeviriler deployment'la gelene
231
+ kadar runtime varsayılan dile düşer.
232
+ - Dosya platformda **saklanmaz**: ne dışa aktarma ne içe aktarma Sürücü'ye bir şey bırakır.
233
+ - Studio'nun çeviri panelindeki JSON içe aktarma bundan farklıdır: o yalnız boşları doldurur ve
234
+ projede olmayan dili atlar.
235
+ - Sınırlar: `.xlsx` (`.xls`, csv, json reddedilir), 10 MB, 10.000 satır.
236
+
197
237
  ## Bilinen sınırlar
198
238
 
199
239
  - **Flow adımlarında Translation kaynağı YOK.** `value.type` sözlüğünde (`platform_guide('page')`
@@ -286,7 +286,10 @@ Global-error dalında `error` değişkeni tanımlıdır:
286
286
  ```
287
287
 
288
288
  Fail dalında aynı zincir `<adım adı>.error.cause`'tadır (`<adım adı>.error` = `{ code, message, detail,
289
- type, cause }`).
289
+ type, cause }`). Serbest `error` değişkenini YALNIZ global-error dalı kurar
290
+ (`ActivityContext.EnterGlobalErrorHandling`): bağlı `fail` dalının hedefi `error.message` okursa
291
+ `undefined` alır — orada `<adım adı>.error.message` / `.code` okuyun (WF-035 uyarır; hedef aynı zamanda
292
+ global-error dalının hedefi olsa da).
290
293
 
291
294
  `error.step` hangi adımın patladığını söyler. `input`/`data` alanları ifade değerlerini ve adım
292
295
  çıktısını taşır — gövdeye yalnız ihtiyaç duyduğun alt kümeyi yaz (gateway'in varsayılanı
@@ -575,6 +578,33 @@ Tam örnek, ölçülen çıktı şekli ve `code` çocuğu tuzağının çıkış
575
578
  > `The JSON value could not be converted to System.Guid` ile reddetti. Geçerli önek harfleri
576
579
  > yalnız `a`–`f`'tir.
577
580
 
581
+ ## `file-upload` adımı: çıktı, dosya alanının tablo okuma biçimidir (K10, 2026-10-08)
582
+
583
+ Adım `rule.fileString`'i (base64, JS ifadesi) `rule.folderId` klasörüne `rule.fileName` (JS ifadesi;
584
+ boşsa GUID) adıyla yazar, dosyayı geri okur ve **tablo okumasının FILE alanında döndürdüğü nesneyi**
585
+ üretir (`FileUploadStep.OutputSchema`; iki yol aynı `FilePathMapper`'ı kullanır):
586
+
587
+ ```json
588
+ {
589
+ "id": "0199c3a2-7b1e-7c4d-9a10-3f2b8c6d5e41",
590
+ "path": "uploads/photo.png",
591
+ "absolute_path": "https://acme.example.com/dev/uploads/photo.png",
592
+ "type": "ref",
593
+ "value": "0199c3a2-7b1e-7c4d-9a10-3f2b8c6d5e41",
594
+ "fileName": "photo.png"
595
+ }
596
+ ```
597
+
598
+ - Sonraki adımlar **adım adı + `.data`** ile okur: adres `yukle.data.absolute_path`, kimlik
599
+ `yukle.data.id` (`value` aynı id'dir). `absolute_path` = uygulamanın dosya sunum ucu (org slug +
600
+ ortamın depolama öneki) + `/` + `path`; `path` boşsa `""`. Görsel için ikinci okuma gerekmez.
601
+ - **Aynı içerik klasörde varsa** yeni dosya yazılmaz: `id`, adres ve `fileName` var olan dosyanındır.
602
+ - **Mobil görsel:** `response` adımı nesneyi döndürür (`bodyExpression: "({ foto: yukle.data })"`),
603
+ sayfa akışı `Steps.<sayfa adımı>.data.foto`'yu `image.src`'ye verir — nesnenin kendisi ya da
604
+ `.absolute_path`; `.value` dosya kimliğidir, görsel çizilmez (`platform_guide('mobile')` §Dosya ve fotoğraf).
605
+ - 2026-10-08'den eski backend imajı yalnız `{ id }` döndürür: `yukle.data.absolute_path` `undefined`
606
+ çıkıyorsa imaj bayattır. Kaynak: backend `FileUploadStepTests`; canlı koşuyla henüz ölçülmedi.
607
+
578
608
  ## Table step kuralları
579
609
 
580
610
  - `rule` içinde `datasourceId` + `entityId` zorunlu; `selectFields`/`filters` öğelerinde
@@ -1,7 +1,7 @@
1
1
  {
2
- "hash": "ca0c6a8ebec39612cfa74fe6e1cb170b141038c1fbf63804e3ae3dd0cc418ec9",
3
- "generatedAt": "2026-10-06T09:02:39.999Z",
4
- "types": 335,
2
+ "hash": "1766032527c4215282c73fb003cbe3526babafabc8588b80289dc28fd4e8b393",
3
+ "generatedAt": "2026-10-12T12:46:03.355Z",
4
+ "types": 337,
5
5
  "files": 167,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
7
7
  "missing": [
@@ -273,7 +273,8 @@
273
273
  "authority-limit",
274
274
  "case-instance",
275
275
  "partner-relationship",
276
- "connector-credential"
276
+ "connector-credential",
277
+ "monitoring"
277
278
  ],
278
279
  "type": "string",
279
280
  "format": ""
@@ -194,6 +194,14 @@
194
194
  "type": "string",
195
195
  "nullable": true
196
196
  },
197
+ "slug": {
198
+ "type": "string",
199
+ "nullable": true
200
+ },
201
+ "displayName": {
202
+ "type": "string",
203
+ "nullable": true
204
+ },
197
205
  "isProduction": {
198
206
  "type": "boolean"
199
207
  },
@@ -117,6 +117,9 @@
117
117
  "review": {
118
118
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreReviewModel"
119
119
  },
120
+ "ads": {
121
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdsModel"
122
+ },
120
123
  "expectedUpdatedAt": {
121
124
  "type": "string",
122
125
  "format": "date-time",
@@ -255,6 +258,40 @@
255
258
  },
256
259
  "additionalProperties": false
257
260
  },
261
+ "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdsModel": {
262
+ "type": "object",
263
+ "properties": {
264
+ "enabled": {
265
+ "type": "boolean"
266
+ },
267
+ "iosAppId": {
268
+ "type": "string",
269
+ "nullable": true
270
+ },
271
+ "androidAppId": {
272
+ "type": "string",
273
+ "nullable": true
274
+ },
275
+ "testMode": {
276
+ "type": "boolean"
277
+ },
278
+ "consentForm": {
279
+ "type": "boolean"
280
+ },
281
+ "appOpenUnit": {
282
+ "type": "string",
283
+ "nullable": true
284
+ },
285
+ "units": {
286
+ "type": "array",
287
+ "items": {
288
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdUnitModel"
289
+ },
290
+ "nullable": true
291
+ }
292
+ },
293
+ "additionalProperties": false
294
+ },
258
295
  "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAppleCategoriesModel": {
259
296
  "type": "object",
260
297
  "properties": {
@@ -268,6 +305,28 @@
268
305
  }
269
306
  },
270
307
  "additionalProperties": false
308
+ },
309
+ "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdUnitModel": {
310
+ "type": "object",
311
+ "properties": {
312
+ "name": {
313
+ "type": "string",
314
+ "nullable": true
315
+ },
316
+ "format": {
317
+ "type": "string",
318
+ "nullable": true
319
+ },
320
+ "ios": {
321
+ "type": "string",
322
+ "nullable": true
323
+ },
324
+ "android": {
325
+ "type": "string",
326
+ "nullable": true
327
+ }
328
+ },
329
+ "additionalProperties": false
271
330
  }
272
331
  }
273
332
  }
@@ -3,11 +3,14 @@ import { handle } from '../platform/responses.js';
3
3
  import { forgetApplication, readApplicationPlatform, resolveApplication } from '../knowledge/platform/resolve.js';
4
4
  import { mobileTabsProblems, readMobileTabs } from '../knowledge/platform/mobileTabs.js';
5
5
  import { findingLine } from '../knowledge/platform/profile.js';
6
+ import { listComponents } from './components.js';
6
7
  import { z } from 'zod';
7
8
  /**
8
9
  * MOB-007 (sekme ayarı) — `properties.mobile.tabs` yazan mobil uygulama çağrısının denetimi. Yazım
9
10
  * platforma YİNE gider (D3; backend `properties`'i doğrulamaz): bulgu `platformWarnings`'e düşer.
10
- * Sayfa ve widget listesi okunamazsa o denetimler atlanır, biçim denetimi yine koşar.
11
+ * Sayfa ve widget listesi okunamazsa o denetimler atlanır, biçim denetimi yine koşar. Widget listesi
12
+ * `listComponents`'tan gelir: uç `type` verilmezse yalnız web bileşenlerini döner ve mobil widget'ı
13
+ * gösteren sekme ayarı yanlış MOB-007 alırdı (ARC-245).
11
14
  */
12
15
  export async function mobileTabsWarnings(platform, applicationId, projectSlug, homepageId, properties) {
13
16
  const tabs = readMobileTabs(properties);
@@ -19,8 +22,8 @@ export async function mobileTabsWarnings(platform, applicationId, projectSlug, h
19
22
  .catch(() => undefined)
20
23
  : undefined;
21
24
  const widgetIds = applicationId && projectSlug
22
- ? await platform.tenant('GET', projectSlug, `/components/${applicationId}`, { query: { pageIndex: 0, pageLength: 999 } })
23
- .then(response => new Set((response?.data ?? []).filter(entry => entry.kind === 'widget').map(entry => String(entry.id ?? ''))))
25
+ ? await listComponents(platform, projectSlug, undefined, applicationId)
26
+ .then(components => new Set(components.filter(entry => entry.kind === 'widget').map(entry => String(entry.id ?? ''))))
24
27
  .catch(() => undefined)
25
28
  : undefined;
26
29
  return mobileTabsProblems(tabs, {
@@ -61,12 +61,12 @@ export const pagingParams = {
61
61
  isDescending: z.boolean().optional()
62
62
  };
63
63
  /**
64
- * Dal adı kuralı: `Environment` ctor'unun aynası, gerçek koruma backend'dedir. Ad yalnız
65
- * `environment_create`'i değil, `page_screenshot`'ın `/branch/{env}` şeridini de ilgilendirdiği
66
- * için tek yerde durur — iki kopya birbirinden ayrılırsa gateway kabul ettiği bir adı
67
- * görüntüleyemez hâle gelir.
64
+ * Dal adı kuralı: `Environment` ctor'unun birebir aynası (`/` ve `-` serbest, 3-31 karakter),
65
+ * gerçek koruma backend'dedir. Ad yalnız `environment_create`'i değil, `page_screenshot`'ın
66
+ * `/branch/{slug}` şeridini de ilgilendirdiği için tek yerde durur — iki kopya birbirinden
67
+ * ayrılırsa gateway kabul ettiği bir adı görüntüleyemez hâle gelir.
68
68
  */
69
- export const branchNamePattern = /^[a-z][a-z0-9_]{2,30}$/;
69
+ export const branchNamePattern = /^(?=.{3,31}$)[a-z][a-z0-9_-]*(\/[a-z0-9][a-z0-9_-]*)*$/;
70
70
  const reservedEnvironmentNames = new Set(['development', 'production', 'public', 'information_schema', 'dev', 'view', 'api', 'files', 'hubs']);
71
71
  /** Sabit ortamlar, url sözcükleri ve postgres şemaları: dal adı olamazlar */
72
72
  export function isReservedEnvironmentName(name) {
@@ -76,6 +76,10 @@ export function isReservedEnvironmentName(name) {
76
76
  export function isBranchName(name) {
77
77
  return branchNamePattern.test(name) && !isReservedEnvironmentName(name);
78
78
  }
79
+ /** Dalın adres parçası: her `/` → `-` (backend `Environment.Slug`); `feature/x` → `feature-x` */
80
+ export function branchSlug(name) {
81
+ return name.replace(/\//g, '-');
82
+ }
79
83
  export const readOnly = { readOnlyHint: true, openWorldHint: false };
80
84
  export const writes = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
81
85
  export const updates = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
@@ -26,6 +26,40 @@ function preserveApplicationBinding(definition) {
26
26
  if (applicationIds.length > 0)
27
27
  definition.applicationIds = applicationIds;
28
28
  }
29
+ /**
30
+ * 37 S-1: platform PATCH op'larını `EntityDto` OKUMA şekline uygular (`PatchEntityCommandHandler` →
31
+ * `UpdateEntityCommand.FromDto`, `ApplicationIds = Applications.Select(a => a.ApplicationId)`). Bağ
32
+ * `/applications` yolunda `{ applicationId }` öğeleriyle yazılır; `/applicationIds` belgede yoktur
33
+ * (gateway önizlemesi `OPERATION_PATH_UNRESOLVABLE` ile düşer), çıplak guid 500 `JsonException`,
34
+ * `{ id }` boş `applicationId` ile "This record is in use" verir. Ajan yazma adını bilerek geldiği
35
+ * için reddetmek yerine çevrilir ve `gatewayNormalized` ile söylenir (microservice `/roleIds` emsali).
36
+ */
37
+ function normalizeApplicationBindingOps(operations) {
38
+ const notes = [];
39
+ const asBinding = (entry) => {
40
+ if (typeof entry === 'string')
41
+ return { applicationId: entry };
42
+ if (entry && typeof entry === 'object' && !('applicationId' in entry) && typeof entry.id === 'string')
43
+ return { applicationId: entry.id };
44
+ return entry;
45
+ };
46
+ for (const operation of operations) {
47
+ const path = typeof operation.path === 'string' ? operation.path : '';
48
+ if (path === '/applicationIds' || path.startsWith('/applicationIds/')) {
49
+ operation.path = `/applications${path.slice('/applicationIds'.length)}`;
50
+ notes.push(`patch yolu '${path}' → '${operation.path}': yamalanan belge entity_get'in OKUMA şeklidir (EntityDto) ve uygulama bağı orada 'applications' [{ applicationId }] alanıdır; 'applicationIds' yalnız create/update GÖVDESİNDE vardır.`);
51
+ }
52
+ const target = operation.path;
53
+ if (target !== '/applications' && !target.startsWith('/applications/'))
54
+ continue;
55
+ const before = JSON.stringify(operation.value);
56
+ operation.value = target === '/applications' && Array.isArray(operation.value) ? operation.value.map(asBinding) : target.split('/').length === 3 ? asBinding(operation.value) : operation.value;
57
+ if (JSON.stringify(operation.value) !== before) {
58
+ notes.push(`'${target}' değeri { applicationId } öğesine çevrildi — çalışan şekil: { op: 'replace', path: '/applications', value: [{ applicationId: '<application id>' }] }.`);
59
+ }
60
+ }
61
+ return notes;
62
+ }
29
63
  /**
30
64
  * `UpdateEntityCommandHandler` gövdedeki `Indexes` null ise onu BOŞ LİSTEYE çeviriyor
31
65
  * (`request.Indexes?…ToList() ?? []`) ve `Entity.FindProcessedIndexes` boş listede entity'nin
@@ -178,7 +212,7 @@ export function registerEntityTools(server, platform) {
178
212
  }));
179
213
  server.registerTool('entity_patch', {
180
214
  title: 'Entity\'ye patch uygula',
181
- description: "YENİ alanı korumayla ya da türetilmiş alan olarak eklemek için de kullanılır (PROT-003/PROT-004); eklenen ya da kuralı değişen türetilmiş alanı dolu satırlarda derived-fill işi doldurur (tablo kilitlenmez, bitiş import_history { entityId }). VAR OLAN alanın korumasını değiştirmez — platform 400 ProtectionChangeNotAllowed döner, gateway istek göndermeden durdurur ve yol entity_protection_update'tir. Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. 'applications' → 'applicationIds' çevirisini artık platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
215
+ description: "YENİ alanı korumayla ya da türetilmiş alan olarak eklemek için de kullanılır (PROT-003/PROT-004); eklenen ya da kuralı değişen türetilmiş alanı dolu satırlarda derived-fill işi doldurur (tablo kilitlenmez, bitiş import_history { entityId }). VAR OLAN alanın korumasını değiştirmez — platform 400 ProtectionChangeNotAllowed döner, gateway istek göndermeden durdurur ve yol entity_protection_update'tir. Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. UYGULAMA BAĞI '/applications' yolundadır ve öğeleri { applicationId } nesneleridir: {op:'replace', path:'/applications', value:[{applicationId:'<application id>'}]} — tam listeyi yazar; tek bağ eklemek {op:'add', path:'/applications/-', value:{applicationId:'<application id>'}}. '/applicationIds' yolunu ya da çıplak guid / { id } öğesini gateway bu şekle çevirir ve gatewayNormalized ile söyler; 'applications' → 'applicationIds' gövde çevirisini platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
182
216
  inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, operations: patchOperationsParam.optional(), ops: patchOperationsParam.optional().describe("'operations' ile aynı alan (eşanlamlı) — sayfa micro-op tool'larıyla aynı adı kullanabilmeniz için"), policyOverride: policyOverrideParam },
183
217
  annotations: updates
184
218
  }, handle(config, async ({ project, environment, datasourceId, id, operations: givenOperations, ops, policyOverride }) => {
@@ -187,7 +221,10 @@ export function registerEntityTools(server, platform) {
187
221
  const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
188
222
  const current = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
189
223
  const definition = structuredClone((current?.data ?? current));
190
- const resolvedOps = resolvePatchOperations(definition, operations, 'entity_get');
224
+ // Bağ yolu seçici çözümünden ÖNCE çevrilir: '/applicationIds' belgede yoktur (37 S-1).
225
+ const bindingOperations = structuredClone(operations);
226
+ const bindingNotes = normalizeApplicationBindingOps(bindingOperations);
227
+ const resolvedOps = resolvePatchOperations(definition, bindingOperations, 'entity_get');
191
228
  // Politika, yazmadan önce sonucu görmeli; yerel kopya YALNIZ değerlendirme için, gövde olarak gönderilmez.
192
229
  const preview = fastJsonPatch.applyPatch(definition, resolvedOps.operations, true, false).newDocument;
193
230
  const protectionChanges = changedExistingProtections(current?.data ?? current, preview);
@@ -208,7 +245,7 @@ export function registerEntityTools(server, platform) {
208
245
  });
209
246
  const written = patchResult(saved, preview);
210
247
  const touched = patchedSections(written.data, resolvedOps.operations);
211
- return attachFindings(withUnmappedMembers(withResolved({
248
+ return attachFindings(withUnmappedMembers(withResolved(withNormalization({
212
249
  applied: operations.length,
213
250
  newVersion: written.newVersion,
214
251
  patchTargets: resolvedOps.targets,
@@ -216,7 +253,7 @@ export function registerEntityTools(server, platform) {
216
253
  // S-8: yazılanın kanıtı yanıtta dursun; dokunulmayan bölümler outline'da kalır.
217
254
  ...(Object.keys(touched).length > 0 ? { touched } : {}),
218
255
  outline: entityOutline(written.data)
219
- }, resolver.resolved), drift), findings);
256
+ }, bindingNotes), resolver.resolved), drift), findings);
220
257
  }));
221
258
  server.registerTool('entity_protection_update', {
222
259
  title: 'Alan korumasını değiştir',
@@ -4,7 +4,7 @@ import { handle } from '../platform/responses.js';
4
4
  import { z } from 'zod';
5
5
  /** Aggregate kuralının aynası (`Environment` ctor); gerçek koruma backend'dedir. Kural `common.ts`'te. */
6
6
  const branchName = z.string().regex(branchNamePattern)
7
- .describe("Dal adı: küçük harfle başlar, yalnız küçük harf/rakam/alt çizgi, 3-31 karakter. Bu ad PG şema adı olur.");
7
+ .describe("Dal adı: küçük harfle başlar; küçük harf/rakam/alt çizgi/tire ve '/' ile ayrılmış parçalar (ör. feature/odeme), 3-31 karakter. Bu ad PG şema adı olur ve değişmez; adres parçası (slug) '/' yerine '-' taşır.");
8
8
  function assertBranchName(name) {
9
9
  if (isReservedEnvironmentName(name)) {
10
10
  throw new PlatformApiError(`environment_create: '${name}' rezerve bir şema adı — dal adı olarak kullanılamaz. Başka bir ad seçin.`, 400);
@@ -515,10 +515,14 @@ export function registerFlowTools(server, platform) {
515
515
  };
516
516
  const authored = { steps: steps ?? [], errorSteps: errorSteps ?? [] };
517
517
  const normalized = [...normalizeFlowSteps(authored.steps), ...normalizeFlowSteps(authored.errorSteps)];
518
+ // Kurallar yalnız GÖNDERİLEN adımlara bakar (kayıtlı gövdeyi suçlamamak için); stepId
519
+ // benzersizliği ise birleşik akışta ölçülür: yalnız steps verilip kayıtlı errorSteps'le
520
+ // çakışan kimlik de platformda 500 verir.
518
521
  const findings = steps || errorSteps
519
522
  ? [
520
523
  ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', authored, { operation: 'update' }, policyOverride)),
521
- ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(authored.steps, authored.errorSteps, hostNodes(hostRecord)), policyOverride))
524
+ ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(authored.steps, authored.errorSteps, hostNodes(hostRecord)), policyOverride)),
525
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride).filter(finding => finding.ruleId === 'FLOW-027'))
522
526
  ]
523
527
  : [];
524
528
  const parameterWarnings = [
@@ -3,7 +3,7 @@ import { resolvePatchOperations } from '../knowledge/outlines.js';
3
3
  import { patchResult, baseVersionOf } from '../platform/patch.js';
4
4
  import fastJsonPatch from 'fast-json-patch';
5
5
  import { fillComponentNames, componentNameNote, normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
6
- import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode } from './screens.js';
6
+ import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode, readFullSchema } from './screens.js';
7
7
  import { assertScreenWriteGuards, assertScreenCatalog } from './screenWrite.js';
8
8
  import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../knowledge/propTypes.js';
9
9
  import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
@@ -58,24 +58,39 @@ export function registerLayoutTools(server, platform) {
58
58
  }));
59
59
  server.registerTool('layout_schema_get', {
60
60
  title: "Layout komponent ağacını oku",
61
- description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
61
+ description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update. full ve subtree yanıtı nodeCount taşır; full tek yanıta sığmazsa HATA döner (sessiz kırpma yok): cursor:'0' ile parça parça okuyun — page_schema_get ile aynı sözleşme (items[] { parentElementId, index, node }, nextCursor bitene kadar; layout okuma arasında değişirse 409). allowTruncated:true kırpılmış gövdeyi açıkça verir — o gövdeyi layout_update'e geri yazmayın.",
62
62
  inputSchema: {
63
63
  project: projectParam,
64
64
  environment: environmentParam,
65
65
  applicationId: guid,
66
66
  id: guid,
67
- mode: modeParam,
68
- componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
67
+ mode: modeParam.describe("summary (varsayılan): yalnız id/elementId/name/type ağacı — hedef bulmak için bunu kullanın. subtree: componentId ile tek alt ağaç. full: tüm views; tek yanıta sığmazsa HATA döner — cursor:'0' ile parça parça okuyun (nextCursor bitene kadar) ya da allowTruncated:true ile kırpılmış gövdeyi açıkça isteyin."),
68
+ componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si"),
69
+ cursor: z.string().min(1).optional().describe("Yalnız mode:'full': parçalı okuma. İlk çağrıda '0', sonra yanıttaki nextCursor; nextCursor gelmeyince views bitmiştir. Layout okuma arasında değişirse (updatedAt) 409 döner — cursor:'0' ile baştan başlayın."),
70
+ allowTruncated: z.boolean().optional().describe("Yalnız mode:'full': true verilirse sığmayan views hata yerine kırpılarak döner (nodeCount toplamı söyler, düşen düğümler söylenmez). Varsayılan false.")
69
71
  },
70
72
  annotations: readOnly
71
- }, handle(config, async ({ project, environment, applicationId, id, mode, componentId }) => {
73
+ }, handle(config, async ({ project, environment, applicationId, id, mode, componentId, cursor, allowTruncated }) => {
72
74
  const layout = await fetchHost(platform, 'layout', project, environment, applicationId, id);
73
75
  const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
74
76
  const audit = layout.audit;
75
77
  const updatedAt = audit?.updatedAt ?? audit?.createdAt;
76
78
  const views = (layout.views ?? []);
77
- if (mode === 'full')
78
- return { updatedAt, ...typeField, views };
79
+ if (cursor !== undefined && mode !== 'full') {
80
+ throw new PlatformApiError(`cursor yalnız mode:'full' ile çalışır (verilen mode: '${mode ?? 'summary'}'); parçalı okuma tüm views içindir. mode:'full' ile cursor:'0' verin ya da cursor'ı kaldırın.`, 400);
81
+ }
82
+ if (mode === 'full') {
83
+ return readFullSchema({
84
+ noun: 'Layout',
85
+ field: 'views',
86
+ nodes: views,
87
+ versionKey: String(updatedAt ?? ''),
88
+ base: { updatedAt, ...typeField },
89
+ cursor,
90
+ allowTruncated,
91
+ maxResponseBytes: config.maxResponseBytes
92
+ });
93
+ }
79
94
  if (mode === 'subtree') {
80
95
  if (!componentId)
81
96
  throw new PlatformApiError("mode='subtree' için componentId zorunludur.");