@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6

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 (101) hide show
  1. package/.env.example +8 -3
  2. package/.env.production.example +2 -0
  3. package/README.md +229 -193
  4. package/dist/config.js +20 -2
  5. package/dist/knowledge/backendConstraints.js +897 -0
  6. package/dist/knowledge/componentCatalog.js +196 -42
  7. package/dist/knowledge/coverage.js +43 -0
  8. package/dist/knowledge/flowGuards.js +30 -0
  9. package/dist/knowledge/guideSections.js +49 -0
  10. package/dist/knowledge/hydrate.js +97 -0
  11. package/dist/knowledge/languageCodes.js +36 -0
  12. package/dist/knowledge/languages.js +105 -0
  13. package/dist/knowledge/layoutSummary.js +10 -0
  14. package/dist/knowledge/localization.js +51 -0
  15. package/dist/knowledge/normalize.js +449 -16
  16. package/dist/knowledge/outlines.js +136 -12
  17. package/dist/knowledge/propTypes.js +80 -0
  18. package/dist/knowledge/rules.js +618 -43
  19. package/dist/knowledge/schemaGuard.js +199 -0
  20. package/dist/knowledge/styleResolve.js +63 -4
  21. package/dist/observability/trace.js +27 -3
  22. package/dist/platform/errors.js +46 -4
  23. package/dist/platform/http.js +33 -4
  24. package/dist/platform/patch.js +27 -0
  25. package/dist/platform/refs.js +104 -0
  26. package/dist/platform/responses.js +9 -2
  27. package/dist/platform/screenshot.js +527 -16
  28. package/dist/platform/secrets.js +40 -0
  29. package/dist/platform/tokenVault.js +89 -0
  30. package/dist/platform/writeDiff.js +281 -0
  31. package/dist/resources/examples/layout-app-shell.json +1 -1
  32. package/dist/resources/examples/microservice-table-list.json +2 -4
  33. package/dist/resources/examples/page-dynamic-form.json +2 -2
  34. package/dist/resources/examples/page-flex-sections.json +99 -0
  35. package/dist/resources/examples/page-sign-in.json +3 -2
  36. package/dist/resources/guides/component.md +69 -8
  37. package/dist/resources/guides/entity.md +404 -16
  38. package/dist/resources/guides/expressions.md +44 -2
  39. package/dist/resources/guides/external-component.md +9 -0
  40. package/dist/resources/guides/getting-started.md +77 -7
  41. package/dist/resources/guides/layout.md +53 -10
  42. package/dist/resources/guides/microservice.md +226 -13
  43. package/dist/resources/guides/middleware.md +89 -1
  44. package/dist/resources/guides/page.md +383 -46
  45. package/dist/resources/guides/translation.md +126 -8
  46. package/dist/resources/guides/view.md +16 -1
  47. package/dist/resources/guides/workflow.md +198 -27
  48. package/dist/resources/knowledge.js +5 -0
  49. package/dist/resources/rules.js +27 -0
  50. package/dist/resources/schema-fingerprint.json +8 -0
  51. package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
  52. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  53. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  54. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  55. package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
  56. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  57. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  58. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  59. package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
  60. package/dist/resources/schemas/GetRowsQuery.json +3 -0
  61. package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
  62. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
  64. package/dist/resources/schemas/StepDto.json +23 -3
  65. package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
  66. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  67. package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
  68. package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
  69. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  70. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  71. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  72. package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
  73. package/dist/server.js +4 -0
  74. package/dist/tools/access.js +3 -3
  75. package/dist/tools/applications.js +1 -1
  76. package/dist/tools/automations.js +63 -30
  77. package/dist/tools/common.js +39 -0
  78. package/dist/tools/components.js +16 -7
  79. package/dist/tools/datasources.js +83 -21
  80. package/dist/tools/deployments.js +34 -2
  81. package/dist/tools/dtos.js +25 -12
  82. package/dist/tools/entities.js +113 -28
  83. package/dist/tools/externalComponents.js +9 -3
  84. package/dist/tools/files.js +20 -6
  85. package/dist/tools/flows.js +110 -11
  86. package/dist/tools/folders.js +7 -2
  87. package/dist/tools/globalFunctions.js +63 -0
  88. package/dist/tools/layouts.js +103 -29
  89. package/dist/tools/microservices.js +229 -38
  90. package/dist/tools/organizations.js +5 -6
  91. package/dist/tools/pages.js +365 -100
  92. package/dist/tools/platformInfo.js +152 -11
  93. package/dist/tools/projects.js +183 -33
  94. package/dist/tools/queries.js +32 -10
  95. package/dist/tools/rows.js +159 -45
  96. package/dist/tools/screenWrite.js +59 -0
  97. package/dist/tools/screens.js +28 -0
  98. package/dist/tools/translations.js +24 -8
  99. package/dist/tools/views.js +20 -10
  100. package/dist/tools/workflows.js +33 -16
  101. package/package.json +6 -3
@@ -30,6 +30,23 @@ component_get → tek tipin TAM sözleşmesi, üç katman birleşik:
30
30
  `appearance.heightType: 'auto'` tercih edilir. Katalog varsayılanı bazı tiplerde kendi
31
31
  sınırını aşar — körü körüne kopyalama, gateway kırpar ve bildirir.
32
32
 
33
+ ### Arayüz metni ve biçimler uygulama dilini izler
34
+
35
+ Komponentlerin KENDİ arayüz metni (tablo sayfalaması, filtre operatörleri, boş durumlar, kırpma
36
+ düğmeleri) ve sayı/tarih biçimleri uygulamanın aktif dilinden gelir — prop vermene gerek yok.
37
+ İki ayrı kural işliyor ve karıştırılmamalı:
38
+
39
+ - **Metin İngilizceye düşer.** Kütüphanenin sözlüğü tr/en taşır; projede `de` seçiliyse o metinler
40
+ İngilizce çıkar. Yeni dil eklemek sözlüğe satır eklemektir, komponent değişikliği değil.
41
+ - **Biçim düşmez.** Sayı ayraçları ve tarih adları seçilen dilin kendi kuralını kullanır
42
+ (`de` → `1.234,5`). Dili olmayan bir biçim yoktur; dil verilmediğinde taban İngilizcedir.
43
+
44
+ Bazı komponentlerin `locale` prop'u geriye uyum için durur ve **doluysa kazanır** — bilinçli
45
+ sabitleme içindir. Boş bırakmak doğru varsayılandır.
46
+
47
+ Doğrulama mesajını çeviriye bağlamak ayrı bir sözleşmedir (`t:` öneki, `platform_guide('page')`
48
+ §validasyon); prop'ları bağlamak için `set_prop_type` (`platform_guide('translation')`).
49
+
33
50
  ## Olay modeli
34
51
 
35
52
  - Olay adları `component_get.events`'ten okunur, **`on` öneki almaz** (tek istisna
@@ -79,29 +96,73 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
79
96
  |---|---|
80
97
  | `isDroppable` | İçine komponent alır (container/form/repeater true) |
81
98
  | `isScrollable` | İçeriği kendi alanında kaydırır (sabit yükseklikte `overflow-y: auto`) |
82
- | `isWrapper` | Sayfanın/layout'un/widget'ın ANA kabı — salt düzen elemanı: `height:100%` + **`min-height:80vh`**, kap başlığı ÇİZİLMEZ. **Yalnız sayfa boyu kaplaması gereken tek kap için kullan:** iç içe kutuya verirsen 720px'lik pencerede o kutu 36px içerikle bile 576px çizilir (ölçüldü, `platform_guide('page')` Kural 1f). Çerçeve/başlık gizlemek için wrapper değil `style` / `hideBorders` kullan; `states` için micro-op olmadığından düzeltmek düğümü yeniden kurmayı gerektirir |
99
+ | `isWrapper` | Sayfanın/layout'un/widget'ın ANA kabı — salt düzen elemanı: `height:100%` + **`min-height:80vh`**, kap başlığı ÇİZİLMEZ. **Yalnız sayfa boyu kaplaması gereken tek kap için kullan:** iç içe kutuya verirsen 720px'lik pencerede o kutu 36px içerikle bile 576px çizilir (ölçüldü, `platform_guide('page')` Kural 1f). Çerçeve/başlık gizlemek için wrapper değil `style` / `hideBorders` kullan. Kurulmuş bir düğümün bayrağını `resize_component` micro-op'unun `states` alanı değiştirir — düğümü yeniden kurmak gerekmez |
83
100
  | `isSticky` | Kaydırmada yerinde kalır (header/sidebar; z-index'i otomatik) |
101
+ | `isFloating` | Düğümü akıştan çıkarıp yüzdürür (katman davranışı; nadir) |
102
+ | `isQuickStack` | Studio'nun Quick Stack iskelet hücresi işareti — editörde kesikli çerçeve çizer, **runtime'da hiçbir etkisi yoktur**. MCP'den iskelet kurarken yazmak serbesttir ama gerekmez |
84
103
 
85
104
  > **Çocukların DİZİLİMİ `states`'te değil kabın PROP'undadır:** `container` — ve dolayısıyla
86
105
  > `page_container` — `layout: 'flex' | 'grid'` + altı ayar (`direction`, `horizontalAlign`,
87
- > `verticalAlign`, `size`, `gap`, `wrap`) taşır. Katalog varsayılanı `flex`'tir ama varsayılan
88
- > düğüm kurulurken yazılır: `layout` prop'u hiç yoksa renderer IZGARA çizer, o yüzden şemayı sen
89
- > kuruyorsan açıkça yaz. Ortalama/yaslama/aralık işini `style` ile değil bu grupla kur:
90
- > `platform_guide('page')` Kural 1f-2.
106
+ > `verticalAlign`, `size`, `gap`, `wrap`) taşır; **`form` (2026-09-01), `box`/`aibox` ve
107
+ > `layout`/`widget-area` (2026-09-03) da aynı grubu aynı varsayılanlarla taşır** — değer
108
+ > kümesi hepsinde `flex`/`grid`, yalnız `box-section` tek değerlidir. Katalog
109
+ > varsayılanı `flex` + `wrap: true`'dur ama varsayılan düğüm kurulurken yazılır: `layout`
110
+ > prop'u hiç yoksa renderer IZGARA çizer, o yüzden şemayı sen kuruyorsan açıkça yaz.
111
+ > Ortalama/yaslama/aralık işini `style` ile değil bu grupla kur: `platform_guide('page')`
112
+ > Kural 1f-2.
91
113
 
92
114
  | `appearance` | Değerler | Anlamı |
93
115
  |---|---|---|
94
- | `heightType` | `auto` · `fixed` · `fill` | `auto`: içerik kadar (önerilen — Kural 1d); `fixed`: `blocks.h × 10px`; `fill`: **kalanı doldur** — ebeveynin bıraktığı yer (ızgarada `height:100%`, flex sütununda `flex:1 1 auto`, flex satırında `align-self:stretch`). Tam ekran yerleşim kabuğu bununla kurulur, bkz. `platform_guide('layout')` |
95
- | `marginType` / `marginValue` | `normal` · `none` · `custom` + px | Dış boşluk: normal=5px, custom=`marginValue` |
116
+ | `heightType` | `auto` · `fixed` · `fill` | `auto`: içerik kadar (önerilen — Kural 1d); `fixed`: `heightString` varsa o, yoksa `blocks.h × 10px`; `fill`: **kalanı doldur** — ebeveynin bıraktığı yer (ızgarada `height:100%`, flex sütununda `flex:1 1 auto`, flex satırında `align-self:stretch`). Tam ekran yerleşim kabuğu bununla kurulur, bkz. `platform_guide('layout')` |
117
+ | `widthType` | `cover` · `contain` · `auto` · `fixed` · `fill` | İlk ikisi görsel/medya sığdırma. Kalanı **flex (ve eski cssgrid) kabın çocuğunun genişlik modu** (2026-09-01): `auto` içerik kadar, `fixed` `widthString`'den, `fill` kabın tüm genişliği (satırda `flex:1 1 100%`, sütunda `align-self:stretch`). **Yazılmazsa eski 12 kolon `blocks.size.w` payı geçerlidir** — öncelik: `fill` > `fixed` > `auto` > span |
118
+ | `widthString` / `heightString` | CSS uzunluğu | Sabit ölçü; **yalnız ilgili `*Type: 'fixed'` iken okunur** (aksi PAGE-017 uyarısı). Kabul: çıplak sayı (px sayılır) · `px % vw vh vmin vmax rem em ch` birimli değer · `calc(...)`. Negatif/`pt`/`fr`/`auto` GEÇMEZ — platform kaydeder ama panel ve runtime sessizce düşürür (PAGE-018). Eski `widthPx` alanı 2026-09-01'de kaldırıldı |
119
+ | `marginType` / `marginValue` | `normal` · `none` · `custom` + px | Dış boşluk: **yazılmazsa `normal` sayılır** = her kenara 4px (`spacing.ts` `CANVAS_NORMAL_MARGIN`); custom=`marginValue`. Bileşenleri dip dibe dizmek için `none` ŞART — `gap:'none'` olsa bile normal margin komşular arasında 8px bırakır (sıkı dizilim tarifi: `platform_guide('page')` Kural 1f-2) |
96
120
  | `paddingType` / `paddingValue` | `normal` · `none` · `custom` + px | Droppable kapların iç boşluğu: normal=10px |
97
121
  | `alignType` | `left` · `center` · `right` | İçerik hizalama (destekleyen komponentlerde) |
98
- | `widthType` | `cover` · `contain` | Görsel/medya sığdırma davranışı |
99
122
  | `aspectRatio` | sayı | En-boy oranı kilidi (medya komponentleri) |
100
123
 
101
124
  `isHidden` bu sözlüklerde değil PROP'tur ve her komponentte çalışır; flow'lar `margin` /
102
125
  `padding` / `height` prop adlarıyla da appearance'ı setter üzerinden değiştirebilir
103
126
  (RenderComponent bu üç prop'u appearance'a çevirir).
104
127
 
128
+ > **Yerleşim YALNIZ `appearance`'tan geçer — `style`'a yazılan CSS kaçamakları sessizce
129
+ > yutulur.** `style` serbest bir sözlüktür: doğrulama duvarı susar, kayıt 200 döner, `*_get`
130
+ > değeri geri verir ve düzen hiç değişmez. 2026-09-06·2 koşusunda üst çubuktaki "Dashboard"
131
+ > etiketi flex satırında sıkışıp "Dashboar" olarak çizildi; `style.flexShrink: 0` hiçbir şey
132
+ > yapmadı, çözüm `appearance` oldu (`S-11`). Karşılıklar:
133
+ >
134
+ > | `style`'a yazma | Karşılığı |
135
+ > |---|---|
136
+ > | `flexShrink` · `flexGrow` · `flexBasis` · `flex` | `widthType`/`heightType`: küçülmeyi durdurmak `fixed` + `*String`, kalanı almak `fill`, içerik kadar `auto` |
137
+ > | `width` · `minWidth` · `maxWidth` | `widthType: 'fixed'` + `widthString` |
138
+ > | `height` · `minHeight` · `maxHeight` | `heightType: 'fixed'` + `heightString` (ya da `auto`/`fill`) |
139
+ > | `margin` · `padding` | `marginType`/`marginValue` · `paddingType`/`paddingValue` |
140
+ > | `alignSelf` · `justifySelf` | Ebeveynin flex prop'ları (`horizontalAlign`/`verticalAlign`) + çocuğun `*Type`'ı |
141
+ >
142
+ > `style` rengin, tipografinin ve kenarlığın yeridir; kutu modeline ait her şey `appearance`'ta.
143
+
144
+ > **Jeton kümesi kapalıdır ve tanınmayan jeton artık 400 döner.** Yukarıdaki beş alanın
145
+ > (`paddingType` · `marginType` · `widthType` · `heightType` · `alignType`) kabul ettiği değerler
146
+ > platformun kendi enum'larıdır; tanınmayan bir jeton eskiden **sessizce sıfırlanıyordu** —
147
+ > 2026-09-06 koşusunda `paddingType: 'large'` gönderildi, HTTP 200 döndü ve kart `none` ile,
148
+ > yani hiç iç boşluksuz çizildi (`S-2`). Platform bunu artık kabul edilen jetonları sayan bir
149
+ > hatayla reddeder. Aynı kümeler `component_get` yanıtının `appearanceValues` alanında da döner,
150
+ > `states` bayraklarının "true iken ne olur" karşılıkları ise `stateFlags` alanında —
151
+ > `gap` gibi komponente özel jetonlar için `component_get` prop şemasına bakın.
152
+ >
153
+ > **`gap` jetonlarına benzeterek jeton uydurma:** `gap` bir container PROP'udur ve kendi kümesi
154
+ > vardır; `appearance` alanlarıyla aynı sözlüğü paylaşmaz.
155
+
156
+ ## Dosya/görsel taşıyan prop'lar nesne bekler
157
+
158
+ Görsel ya da dosya gösteren her yüzey (`datatable`'ın `image`/`file` kolonu başta olmak üzere)
159
+ düz URL string'i değil **dosya nesnesi** ister: `{ absolute_path, display_name }` (etikette
160
+ `fileName` de okunur). Düz string verilirse kaynak boş kalır ve hücre/kutu **sessizce boş**
161
+ çizilir — istek 200 döner, hata yoktur (ölçüm: 09-04 koşusu Y-21). Entity'nin `FILE` alanı bu
162
+ şekli zaten döner; ham bir URL'yi göstermek istiyorsan ya kolonu `url`/`link` yap ya da veriyi
163
+ `{ absolute_path: '<url>', display_name: '<ad>' }` biçimine çevir. Komponentin kendi sözleşmesi
164
+ için `component_get` `propNotes`.
165
+
105
166
  ## Komponent seçimi
106
167
 
107
168
  "Hangi işe hangi komponent" tablosu ve `table` / `datatable` karşılaştırması:
@@ -4,6 +4,12 @@ Buradaki kurallar gerçek projelerde (qm, hr, sms, klinik) **deneyerek doğrulan
4
4
 
5
5
  ## Slug seçimi — isim uzayı platformla paylaşımlıdır
6
6
 
7
+ > **Yanıttaki `silentlyDropped`'ı oku.** Gateway her yazma çağrısında gönderdiğin gövdeyi
8
+ > kaydedilenle karşılaştırır ve kayda geçmeyen alanları bu listede sayar — istek 200 döner, alan
9
+ > yok sayılmıştır. Bu, hata listesinde hiç görünmeyen tek sinyaldir; yazmadan sonra yanıtın o
10
+ > alanına bakmayı alışkanlık yap. Ayrıntılı örnek: `platform_guide('microservice')`.
11
+
12
+
7
13
  Her environment şeması ~75 platform tablosu taşır ve entity slug'ları bu tablolarla aynı isim
8
14
  uzayındadır. Çakışan slug `Migration failed … relation "X" already exists` verir ve **entity
9
15
  kaydı hiç oluşmaz**. Rezerve adlardan bilinenler: `branches`, `applications`, `caches`,
@@ -48,18 +54,24 @@ doğrudan kullan).
48
54
  silinemez. Buna karşılık **`entity_patch` ile alan EKLENEBİLİR** — telefon, departman, avatar,
49
55
  `musteri_tipi` gibi profil alanları `users`'a eklenebilir.
50
56
  - **Eklediğin alan `nullable: true` olmalı.** Platform bu tabloya EF üzerinden de yazıyor
51
- (`organization_sync_users`) ve EF senin kolonunu bilmiyor; NOT NULL + varsayılansız bir kolon o
52
- yolu kırar.
57
+ (bakım yolları dâhil) ve EF senin kolonunu bilmiyor; NOT NULL + varsayılansız bir kolon o yolu
58
+ kırar.
53
59
  - **`password` alanı `Hasher.Hash`'lidir.** Satır yazarken hash'i ifadeyle ürettir
54
60
  (`crypto.SHA256.encode(...)` — gerçek algoritma için `platform_guide('expressions')`); düz metin
55
61
  yazarsan giriş hiçbir zaman eşleşmez.
56
62
  - **`isAuditTrailActive`'i AÇMA.** `users` tablosunun audit kolonları EF tarafında `Ignore`
57
63
  edilmiş; `sys_*` eklemek EF'in bilmediği kolonlar yaratır.
58
- - **`organization_sync_users` Studio operatörlerini bu tabloya süper yönetici rolüyle kopyalar**
59
- (yalnız production dışı environment'lara). Yani tablo hem platform operatörünü hem uygulama
60
- kullanıcısını taşır ve roller `page.roleIds` yetkilendirmesiyle ortaktır.
64
+ - **Tablo hem platform operatörünü hem uygulama kullanıcısını taşıyabilir** ve roller
65
+ `page.roleIds` yetkilendirmesiyle ortaktır. Ama **yeni projede boş gelir**: Studio operatörü
66
+ kendiliğinden uygulama kullanıcısı olmaz. İlk kullanıcıyı açmanın yolu
67
+ `platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?".
61
68
  - **İlişki aynı datasource içinde kurulur.** `users` built-in datasource'tadır; entity'lerin dış
62
69
  bir DB'deyse `users`'a ilişki kuramazsın.
70
+ - **Sonuç: kimlik gerektiren her uygulama built-in datasource'ta kurulur.** Entity'lerini dış bir
71
+ DB'ye açarsan giriş, `user.id` kapsaması ve roller kullanılamaz hâle gelir — bunlar `users`'a
72
+ ilişki üzerinden bağlıdır. Built-in her projeyle birlikte doğar (`datasource_list` çıktısında
73
+ `isBuiltIn: true`); `datasource_create` yalnız **kullanıcı kendi veritabanını verdiğinde**
74
+ çağrılır ve o kurulum bu bedeli kabul etmiş olur.
63
75
 
64
76
  ### Karar: `users`'ı genişlet mi, ayrı entity mi?
65
77
 
@@ -76,6 +88,50 @@ kurmak. O noktada iki ayrı kullanıcı tablosu, iki ayrı hash düzeni ve `midd
76
88
  ile hiç konuşmayan bir yetkilendirme olur. Gateway bu şekli ENT-009 ile hatırlatır (warn — bilerek
77
89
  yapıyorsan devam et).
78
90
 
91
+ ### Rol yönetimi — `roles` da sıradan bir entity
92
+
93
+ **`role_create` diye bir tool yoktur ve olmayacak.** Rol tablosu built-in datasource'ta yaşar;
94
+ uygulamanın rol açması/düzenlemesi gerekiyorsa yol her domain tablosuyla aynıdır: entity + kendi
95
+ microservice ucun + sayfa. `role_list` yalnız okur.
96
+
97
+ `roles` entity'sinin açtığı alanlar — fiziksel tabloda ayrıca `template_*` kolonları var, hepsi
98
+ nullable, gateway'den görünmez:
99
+
100
+ | alan | not |
101
+ |---|---|
102
+ | `id` | uuid PK; `page.roleIds` ve `microservice.roles` BUNU ister |
103
+ | `name` | **UNIQUE** (`ix_roles_name`) — aynı adı ikinci kez yazan insert veritabanı hatasıyla düşer |
104
+ | `slug` | **yetkilendirmenin karşılaştırdığı alan**; `role_list` bunu DÖNDÜRMEZ |
105
+
106
+ **İki farklı anahtar, karıştırma:**
107
+
108
+ - **id** → `page_create/page_update` ve `microservice_create` `roleIds` alanı (gövdede `roles` diye bir
109
+ yazma alanı YOKTUR; `microservice.roles` middleware'de RUNTIME şeklidir, `{ id, name: '<slug>' }`).
110
+ `role_list`ten okunur.
111
+ - **slug** → `Authorized(user.roles)`, middleware'in kurduğu `user.roles` / `Page.user.roles`.
112
+ Platformun get-user sorgusu `roles`'u `user_roles` üzerinden **slug dizisi** olarak üretir
113
+ (`SELECT … ARRAY(SELECT r.slug FROM roles r INNER JOIN user_roles ur …)`), auth middleware
114
+ tarifi de aynı şeyi yapar — `platform_guide('middleware')` §Rol karşılaştırması SLUG üzerinden.
115
+
116
+ Bu yüzden **slug'sız yazılan rol sessizdir:** insert 200 döner, satır `role_list`te görünür,
117
+ `page.roleIds` ile bağlanır — ama `Authorized(user.roles)` o rol için hiçbir zaman tutmaz. Gateway
118
+ bunu `ROL-002` ile uyarır.
119
+
120
+ Rol açma reçetesi:
121
+
122
+ 1. `entity_get('01917ede-88a2-72ea-8397-2619e5d5dca0')` ile alan id'lerini al.
123
+ 2. Yazma ucunu kur: `microservice_create` `source:'table'`, `entityId` = roles, `operation:'create'`,
124
+ `fields` = `name` + `slug` (slug'ı ifadeyle ada göre üretmek serbest; **`super-admin` slug'ını
125
+ verme** — platform süper yöneticiyi `slug == 'super-admin'` ile arar).
126
+ 3. Rolü kullanıcıya bağlamak ayrı bir satırdır: `user_roles` (`01917ede-be1e-7d62-9a7c-d0ce4e4daaaf`)
127
+ `user_id` + `role_id`; çift **(user_id, role_id) UNIQUE**'tir, aynı bağı iki kez yazma.
128
+ 4. Yeni rol, kullanıcının bir sonraki isteğinde geçerlidir — middleware rolleri her çağrıda
129
+ tablodan okur; jeton tazelemeye gerek yoktur (rolleri jetona kendi gömen bir middleware
130
+ yazdıysan orada geçerli değil).
131
+
132
+ `permissions` / `role_permissions` **Studio'nun yetki matrisidir**, uygulama sayfasının yetkisi
133
+ değil; uygulama tarafında yetkiyi `page.roleIds` ve middleware ifadesi kurar.
134
+
79
135
  ## Alan tipleri (dbType + commonType İKİSİ birden verilir)
80
136
 
81
137
  | dbType (BÜYÜK HARF zorunlu) | commonType |
@@ -101,6 +157,11 @@ yapıyorsan devam et).
101
157
  `description`'a yaz: para → `INT8` kuruş (`*_minor`), süre → `INT4` dakika, yüzde →
102
158
  `INT4` ×100, puan → `INT4` ×10.
103
159
  - `commonType`'sız `UUID`/`BOOL` "isn't a supported database type" hatasıyla reddedilir.
160
+ - **Katalogu okumak gerekiyorsa** (bu datasource'ta hangi `dbType` var, hangisi varsayılan, tip
161
+ hangi seçenekleri kabul ediyor, hangi tipe dönüştürülebilir) `datasource_configuration_options`
162
+ çağır: varsayılan yanıt ~5 KB'lık **özettir**; 816 kayıtlık collation DEĞER listesi yalnız
163
+ `includeCollations: true` ile gelir (tam yanıt ~45 KB). Katalogda görünen bir tipin
164
+ `entity_create` tarafından kabul edileceğini varsayma — yukarıdaki ondalık maddesi bunun kanıtı.
104
165
 
105
166
  ## Alan tipine karar verme (önce buna bak, sonra yaz)
106
167
 
@@ -138,13 +199,35 @@ olduğu için serbest metindir; `course_assignments.semester` (Güz/Bahar/Yaz) e
138
199
  "relationType": "none", "nullable": false,
139
200
  "isEnum": true,
140
201
  "valueOptions": [
141
- { "value": "Aktif", "icon": "#47ad10" },
142
- { "value": "Bırakıldı", "icon": "#888888" },
143
- { "value": "Tamamlandı", "icon": "#0052CC" }
202
+ { "value": "Aktif", "icon": "#47ad10", "order": 1 },
203
+ { "value": "Bırakıldı", "icon": "#888888", "order": 2 },
204
+ { "value": "Tamamlandı", "icon": "#0052CC", "order": 3 }
144
205
  ] }
145
206
  ```
146
207
 
147
208
  - `valueOptions[].icon` bir **hex renktir**, ikon adı değil.
209
+ - **`valueOptions[].order` seçeneğin İŞ SIRASIDIR (2026-09-05).** Huni, aşama, öncelik gibi
210
+ sıralı raporlarda sırayı sorguya statik `VALUES` listesi olarak gömmek gerekmez — sıra alan
211
+ tanımında durur ve `entity_get` ile geri gelir. Verilmezse `0`'dır; sıralı bir enum tanımlarken
212
+ doldurun, çünkü etiket değişince sorguya gömülmüş liste sessizce yanlış sıralar.
213
+ - **Sıra SQL'den de okunur.** `value_options` tenant veritabanında `fields` tablosunda `jsonb`
214
+ kolondur ve anahtarları küçük harftir (`value` · `icon` · `order`). Sıralı bir rapor sırayı
215
+ buradan JOIN'ler:
216
+
217
+ ```sql
218
+ WITH ord AS (
219
+ SELECT opt->>'value' AS status, (opt->>'order')::int AS pos
220
+ FROM fields f CROSS JOIN LATERAL jsonb_array_elements(f.value_options) AS opt
221
+ WHERE f.entity_id = '<entity id>' AND f.slug = 'status')
222
+ SELECT o.status, COALESCE(c.total, 0) AS candidates
223
+ FROM ord o
224
+ LEFT JOIN (SELECT status, COUNT(*) AS total FROM candidates GROUP BY status) c
225
+ ON c.status = o.status
226
+ ORDER BY o.pos
227
+ ```
228
+
229
+ Ölçüldü 2026-09-05 (`query_preview`): dört durumluk bir enum bu sorguyla doğru sırada döndü,
230
+ sorguda tek bir sabit değer yok.
148
231
  - `isEnum` Studio tarafında `valueOptions.length > 0` ile türetilir; yine de ikisini birlikte ver.
149
232
  - **Enum PostgreSQL'de CHECK constraint üretir.** Var olan bir metin alanını enum'a çevirirken
150
233
  tablodaki değerler `valueOptions` içinde yoksa migration `check constraint ... is violated` ile
@@ -181,6 +264,11 @@ Girdi sınırları (`IncrementalCodeInput.TryParse`; ihlal edilirse nesne geçer
181
264
  (`EntityIncrementSequenceService.ResolveStartingCounterAsync`), yani elle `TLP-0005` yazdıktan
182
265
  sonra nesne biçimiyle yazılan ilk satır `TLP-0006` olur. `Counter: 1` vermek "1'den başla"
183
266
  demek değil, "taban 1" demektir.
267
+ - **Kodu kaydetmeden görmek:** `row_next_code` aynı nesneyi alır (`prefix` · `separator` ·
268
+ `counter` · `digitCount`) ve sıradaki kodu sayacı ilerletmeden döner (`persist: false`).
269
+ `counter` ile `digitCount` platform tarafında **zorunludur** (`GetNextIncrementalCodeQuery`
270
+ validator'ı `NotEmpty` der, yani 0 kabul edilmez); eksik gönderirsen uç 400 verir — gateway de
271
+ bu ikisini zorunlu tutar (ölçüm: 09-02 D-2).
184
272
  - `properties.isCaseSensitive` büyük/küçük harf duyarlılığını belirler (`Field.IsCaseSensitiveIncremental`).
185
273
  - Sayfa tarafındaki karşılığı `incrementalinput` komponentidir.
186
274
  - `datasource_list.dataTypes` çıktısında bu tipi ayrı bir grup olarak görürsün; listede
@@ -194,6 +282,15 @@ parametre YOKTUR). Kısıt (`FieldDtoValidator`): `isLocalized: true` iken `prim
194
282
  · `unique` `false` olmalı ve `relationType: 'none'` olmalı — bunlardan biri doluysa platform
195
283
  reddeder.
196
284
 
285
+ **`display: true` alanı `isLocalized` OLABİLİR** (ürün kararı 2026-09-08). Alan companion'a
286
+ taşınır ama okuyucular onu çözer: `entity_get` companion alanını ana listeye katar, `row_list`
287
+ bire-çok ilişkinin etiketini istek dilinde döndürür (`kategori_id: { id, ad }`).
288
+
289
+ > **İlişkinin hedefinde de çalışır.** Çoka-çok listesi `[{ id, <display> }]` olarak istek dilinde
290
+ > gelir, join'li (bire-çok) tablonun çok dilli kolonu mikroservis alan seçicisinde görünür ve
291
+ > `returnScheme`'e girer. Çevirisi olmayan satır varsayılan dile düşer, listeden düşmez.
292
+ > Ölçüldü 2026-09-08 (`dil-iliski2`: `etiketler[].ad` ve `kategori_id.ad` iki dilde).
293
+
197
294
  **Platform bunun için ayrı bir uç açmaz.** Bir entity'de `isLocalized` alanı olduğunda (create ya
198
295
  da update'te) platform otomatik olarak `{entity_slug}_localization` adlı **companion bir entity**
199
296
  kurar/genişletir:
@@ -208,6 +305,29 @@ language Text
208
305
  artı `(relation, language)` üzerinde unique index. Yani bir satırın Türkçe/İngilizce değerleri
209
306
  **ayrı satırlardır**, ana tabloda değil bu companion entity'de yaşar.
210
307
 
308
+ **Satır araçları companion'ı senin yerine konuşur — normalde companion'a hiç dokunmazsın:**
309
+
310
+ | İstediğin | Çağrı |
311
+ |---|---|
312
+ | Satırı bir dilde yaz | `row_create(row, localizations: { tr: {…}, en: {…} })` |
313
+ | Toplu yaz | `row_create(rows: [{ …, localizations: { tr: {…} } }])` — `localizations` SATIRIN İÇİNDE |
314
+ | Yalnız çeviriyi güncelle | `row_update(row: { <pk>: … }, localizations: { en: {…} })` — ana satır değişmese de olur |
315
+ | Satırı bir dilde oku | `row_list(language: 'en')` / `row_get(language: 'en')` — alan düz gelir |
316
+ | Bütün dilleri gör | `row_list(includeTranslations: true)` → satırda `localizations.<dil>.<alan>` |
317
+ | Çok dilli alana filtre/sıralama | `row_list(language, filters)` — alan slug'ı düz yazılır |
318
+
319
+ `language` verilmezse platform `Accept-Language`'a, o da yoksa projenin varsayılan diline düşer;
320
+ bölgeli kod ('tr-TR') kabul edilir ve gateway 'tr'ye indirger. **Okumada çevirisi olmayan alan
321
+ varsayılan dile düşer ama filtre ve sıralama fallback GÖRMEZ** — o satır filtrede eşleşmez ve
322
+ sıralamada `NULL` sayılır.
323
+
324
+ Dil satırlarını platform yazar: `row_delete` companion satırlarını ana satırdan önce kendisi siler,
325
+ desteklenmeyen bir dil kodu 400 döner ve ana satır da yazılmaz. Yanıtta
326
+ `gatewayNormalized: ["platform 'localizations' alanını tanımıyor …"]` görürsen platform bu
327
+ sözleşmeden eski bir sürümdedir; gateway o zaman companion'a kendisi yazar ve `localizations.count`
328
+ ile kaç dil satırı yazdığını söyler — `localizationsFailed` ise ana satır yazılmış, dil satırları
329
+ yazılmamıştır. Aşağıdaki elle tarif yalnız o eski yol ve `rows` ile toplu yazma için gerekir.
330
+
211
331
  **Kullanım (tamamı mevcut genel araçlarla, yeni tool GEREKMEZ):**
212
332
 
213
333
  ```
@@ -219,6 +339,14 @@ artı `(relation, language)` üzerinde unique index. Yani bir satırın Türkçe
219
339
 
220
340
  `{fk_slug}` companion entity'nin `entity_get`'inde görünen ilişki alanının slug'ıdır (ana entity
221
341
  adından türetilir); satırı elle kurmadan önce mutlaka `entity_get` ile doğrula, tahmin etme.
342
+ Companion'ı adından değil **ana entity'nin `localizationEntityId` alanından** bul — ad kalıbı
343
+ (`{slug}_localization`) bir kolaylıktır, bağ ise `entity_get`'in döndürdüğü bu id'dir.
344
+
345
+ > **Toplu uçta `localizations` SATIRIN İÇİNDEDİR** (2026-09-08): `rows: [{ kod: 'A1',
346
+ > localizations: { tr: { ad: 'Kalem' }, en: { ad: 'Pen' } } }]`. Üst düzeye koymak toplu uçta
347
+ > hangi satıra ait olduğunu söyleyemeyeceği için gateway tarafından reddedilir. `localizations`
348
+ > bu yüzden hem okuma hem yazma tarafında **ayrılmış bir anahtardır**: `includeTranslations: true`
349
+ > ile okunan satırda da aynı adla gelir, yani bu adda gerçek bir kolonun olamaz.
222
350
 
223
351
  ```jsonc
224
352
  // GUID anahtar
@@ -249,6 +377,10 @@ adından türetilir); satırı elle kurmadan önce mutlaka `entity_get` ile doğ
249
377
 
250
378
  Gateway bu iki kuralı ENT-006 (PK identity/tip uyumu) ve ENT-007 (enum şekli) olarak denetler.
251
379
 
380
+ > **Çok dilli bir entity TEK adımda silinir.** `entity_delete` ana entity'ye uygulanır, companion
381
+ > da onunla düşer. (Aynı gün daha erken bir ölçümdeki "önce companion'ı sil" adımı artık geçersiz;
382
+ > EF izleme hatası giderildi. Ölçüldü 2026-09-08, `dil-iliski`.)
383
+
252
384
  ## Application bağı — ne yapar, ne yapmaz
253
385
 
254
386
  `applicationIds` **create'te platformca bağlanır** (`CreateEntityCommand` → `EntityService.CreateAsync`
@@ -273,8 +405,9 @@ Karar: **Studio'dan yönetilecek entity'lerde `applicationIds` ver; yalnız API'
273
405
  boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zorunlu kılmaz).
274
406
 
275
407
  > `entity_patch` uyarısı: `EntityDto` çıktıda `applications` (nesne listesi) döner ama
276
- > `UpdateEntityCommand` girdide `applicationIds` (Guid listesi) okur. Gateway bu çeviriyi
277
- > kendisi yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı sessizce siler.**
408
+ > `UpdateEntityCommand` girdide `applicationIds` (Guid listesi) okur. `PATCH /entities/{id}`
409
+ > yolunda bu çeviriyi **platform** yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı
410
+ > sessizce siler.**
278
411
 
279
412
  ## İlişkiler (doğru şekil — 57 alanla doğrulandı)
280
413
 
@@ -300,6 +433,120 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
300
433
  - İki entity'yi önce ilişkisiz oluşturup ilişki alanını sonra `entity_patch` ile eklemek en
301
434
  güvenli sıra (id'ler ancak oluşunca belli olur).
302
435
  - `display: true` entity başına tek alanda olabilir; ilişkilerde görüntülenen değer bu alandır.
436
+ - **Boş ilişki `null` değil boş nesne `{}` döner** (ölçüm: İK koşusu D-8, 2026-09-01). JS'te
437
+ `{}` truthy'dir: `if (row.manager_id)` "yöneticisi var" der. Varlık kontrolünü
438
+ `row.manager_id?.id` üzerinden yap. (Backend'in `null` döndürmesi P0-6 olarak planlı; o gelene
439
+ dek bu şekil geçerlidir.)
440
+
441
+ ## Index'ler
442
+
443
+ `indexes` alanı `entity_create` ve `entity_update` gövdesinde geçer.
444
+ **Studio'da index ekranı YOKTUR** — bu alan platformun tek index yönetim yüzeyidir, yanlış
445
+ yazdığın bir index'i arayüzden görüp düzeltemezsin. Tam şema:
446
+ `ngapps://schemas/CreateEntityCommand` (`EntityIndexDto`).
447
+
448
+ ```jsonc
449
+ { "indexes": [
450
+ { "id": "<guid, opsiyonel — verilmezse platform üretir>",
451
+ "name": "candidate_company_status",
452
+ "unique": true,
453
+ "fieldIds": ["<field id>", "<field id>"] }
454
+ ] }
455
+ ```
456
+
457
+ Alan adları 422 ile kendini söyler; **sessiz olan kurallar** şunlar:
458
+
459
+ - **`unique` — `isUnique` DEĞİL.** Yanlış anahtar hata vermez, platform onu yok sayar ve index
460
+ UNIQUE OLMADAN kurulur. Aynı tuzak `fields` için de var, ama o 422 döner
461
+ (`Indexes[0].FieldIds: 'Field Ids' must not be empty`); doğru ad **`fieldIds`**.
462
+ - **`fieldIds` sırası bileşik index'in KOLON SIRASIDIR.** Dizideki konum `Order` olarak
463
+ yazılır, DDL kolonları o sırayla kurar. `(company_id, status)` ile `(status, company_id)`
464
+ farklı index'lerdir; öndeki kolon önek sorgularını hızlandıran kolondur.
465
+ - **`entity_update` gövdesinde `indexes` YOKSA entity'nin BÜTÜN index'leri silinir** — kayıt da
466
+ düşer, `DROP INDEX` de koşar. Gateway `entity_update` çağrısında alan verilmemişse mevcut
467
+ index'leri gövdeye taşır ve bunu `gatewayNormalized` ile bildirir; silmek istiyorsan
468
+ `indexes: []` yazarak **açıkça** iste.
469
+ - **Var olan bir index'te `name` / `unique` / `fieldIds` değiştirmek İŞE YARAMAZ:** platform farkı
470
+ yalnız `id` üzerinden alır, aynı `id` ile yapılan değişiklik UYGULANMAZ ve hata da vermez.
471
+ Değiştirmenin yolu index'i silip **yeni bir `id`** ile yeniden yazmaktır.
472
+
473
+ **Sıra:** `fieldIds` alan **id'si** ister, slug değil — ve alan id'lerini platform ÜRETİR
474
+ (`FieldDto.Id` gövdede yoksa sunucuda atanır). İki geçerli yol var:
475
+
476
+ 1. Entity'yi index'siz kur, `entity_get view:'outline'` ile alan id'lerini oku, index'i
477
+ `entity_patch` ile ekle. **Varsayılan yol budur.**
478
+ 2. Tek çağrıda kuracaksan `fields[].id`'leri kendin ver ve aynı guid'leri `fieldIds`'e yaz.
479
+ (Ölçüldü 2026-09-07: üç alanı ve bileşik unique index'i olan bir entity tek `entity_create`
480
+ çağrısıyla kuruldu.)
481
+
482
+ Var olmayan bir alan id'si reddedilir ama **hata mesajı yanıltıcıdır**: platform
483
+ `This record is in use. Related table: 'Fields', column: 'Field Id'. Please remove its relations
484
+ first.` der — sanki bir bağımlılık varmış gibi. Gerçek sebep bunun tersidir: o id'de alan YOKTUR.
485
+ Bu mesajı alınca bağımlılık aramaya gitme, `fieldIds`'i alan listesiyle karşılaştır.
486
+
487
+ Doğrulama kuralları (hepsi 422 döner):
488
+
489
+ - `fieldIds` boş olamaz ve aynı alanı iki kez taşıyamaz.
490
+ - `name` zorunlu; **önek hariç** en fazla 32 karakter.
491
+ - Index adları entity içinde benzersizdir (büyük/küçük harf duyarsız).
492
+
493
+ Ada dokunma: platform ada `IX_<entity id'sinin 5 hanesi>_` öneki ekler ve okuma yolu **önekli**
494
+ adı döner. Gidiş-dönüş kararlıdır — okuduğun adı geri yazabilirsin, önek ikinci kez eklenmez.
495
+
496
+ Kurulabilen tek şekil **artan (ASC), çok kolonlu, opsiyonel UNIQUE** b-tree'dir. `DESC`, kısmi
497
+ index (`WHERE`), ifade index'i, `INCLUDE` ve GIN/GiST platform yüzeyinde yoktur; bunlardan birine
498
+ ihtiyacın varsa `platform_gap_report` ile bildir.
499
+
500
+ Platformun kendi ürettiği örnek, bileşik unique için okunabilir bir kalıptır: çoklu dilli alan
501
+ kurulunca yan entity'ye `(relation, language)` üzerinde unique bir index açılır — yani "bir
502
+ satırın her dilde en fazla bir çevirisi olur" kuralı index'le zorlanır.
503
+
504
+ Yazdığın index'i `entity_get view:'outline'` çıktısındaki `indexes` bloğundan doğrula; DDL'in
505
+ gerçekten kurulduğunu görmek istiyorsan `query_preview` ile
506
+ `select indexname, indexdef from pg_indexes where tablename = '<slug>'` sorgusunu koştur —
507
+ `entity_get` yalnız metadata'yı kanıtlar, `CREATE UNIQUE INDEX` satırını değil.
508
+
509
+ ## Backend doğrulama sınırları (ENT-015 · ENT-016 · ENT-017 · ENT-018 · ENT-004 · ENT-013)
510
+
511
+ Bu tablo `CreateEntityCommandValidator` / `UpdateEntityCommandValidator` / `FieldDtoValidator`'ın
512
+ aynasıdır. Gateway aynı kuralı **istek gitmeden** `block` olarak uygular ve mesaj platformun 422
513
+ cümlesidir — 2026-09-08 koşusunda bu kurallar ancak platforma gidip öğrenildi (index adı 33
514
+ karakter, primaryKey yok). Tam katalog, kaynak referanslı JSON: `ngapps://rules/backend-constraints`.
515
+
516
+ | Alan | Kural | ID |
517
+ |---|---|---|
518
+ | `name` | zorunlu, en fazla 255 karakter | ENT-015 |
519
+ | `description` | en fazla 1024 | ENT-015 |
520
+ | `slug` | zorunlu (create) | ENT-015 |
521
+ | `fields[].name` | en fazla 50 | ENT-015 |
522
+ | `fields[].description` | en fazla 512 | ENT-015 |
523
+ | `fields[].slug`, `fields[].dbType` | zorunlu; dbType en fazla 50 | ENT-015 |
524
+ | `fields[]` | **tam 1** alan `primaryKey: true` — "Please select exactly one primary key." | ENT-016 |
525
+ | `fields[]` | adlar tekil (büyük/küçük harf duyarsız): create `name`, update `slug` üzerinden | ENT-016 |
526
+ | `fields[].display` | en fazla 1 alan | ENT-004 |
527
+ | `primaryKey` / `identity` | `nullable: false`; PK ayrıca `hidden: false` | ENT-017 |
528
+ | `isLocalized` | `primaryKey`/`identity`/`unique` yok, `relationType: 'none'` | ENT-017 |
529
+ | `folderId` | yalnız `commonType: 'file'` alanında | ENT-017 |
530
+ | `properties.generationType` | yalnız `commonType` `guid` ya da `number` | ENT-017 |
531
+ | `commonType` / `defaultValueType` / `relationType` | sözlükten; bilinmeyen değer 400 `InvalidEnumTokenException` (`multiFile` diye bir tip YOK) | ENT-017 |
532
+ | `recordTransferMode: 'specific'` | `transferRowIds` dolu, tekil, boşsuz | ENT-018 |
533
+ | `indexes[]` | ad önek hariç en fazla 32, `fieldIds` dolu ve tekil, adlar tekil | ENT-013 |
534
+
535
+ **Birincil anahtar kalıbı** (bu rehberde yoktu; 2026-09-08 koşusunda iki hatalı çağrı + bir
536
+ keşif turuna mal oldu):
537
+
538
+ ```jsonc
539
+ { "slug": "id", "name": "Id", "dbType": "UUID", "commonType": "guid",
540
+ "primaryKey": true, "unique": true, "identity": true, "nullable": false,
541
+ "properties": { "generationType": 0 } }
542
+ ```
543
+
544
+ **Create tuzağı:** create yolu alan adlarını `name` üzerinden tekil arar. `name` yazılmayan iki alan
545
+ aynı (boş) gruba düşer ve platform "Duplicate value: 'Slug'" der — mesaj slug'ı suçlar, sebep ad
546
+ eksikliğidir. Her alana `name` yaz; update yolu aynı denetimi `slug` üzerinden yapar.
547
+
548
+ Satır yazma da aynı katalogdan denetlenir (ROW-002, `row_create` / `row_update`): satır sözlüğü
549
+ boş olamaz, boş anahtar taşıyamaz, bütün değerleri null olamaz; toplu yolda her satır ayrı bakılır.
303
550
 
304
551
  ## TEXT kolonunda JSON: yazarken string, okurken NESNE döner
305
552
 
@@ -328,8 +575,19 @@ bilmezsen "kayıt bozulmuş" sanırsın.
328
575
 
329
576
  ## Satır işlemleri
330
577
 
578
+ - `row_*` tool'ları `entityId` olarak slug kabul eder (`employees`, çakışmada
579
+ `BuiltIn/employees`); gateway id'ye çevirir ve yanıtta `gatewayResolved` ile bildirir.
331
580
  - `row_create/row_update` anahtarları **field slug'larıdır** (name değil): `{"code": "IK"}`.
581
+ - **Toplu yazımın (`rows[]`) id döndürmesine GÜVENME.** 2026-09-05'te uç `data: [{ "<pk slug>":
582
+ değer }, …]` döndürüyordu; 2026-09-07 ölçümünde iki ayrı entity'de **boş gövde** döndü (gateway
583
+ onu `{success: true}` diye gösterir) — satırlar yazıldı ama anahtarlar gelmedi. Fark `B-63`
584
+ olarak takipte. Yazdığın satırların anahtarları lazımsa yanıta bel bağlama, `row_list` çek ve
585
+ bu turu bütçene ekle. Daha iyisi: türetilmiş satırı bu id'lerle kurma — §"Toplu ve türetilmiş
586
+ satır"daki SQL yolunda id hiç dolaşmaz.
332
587
  - Yazdıktan sonra `row_list` ile geri oku; ilişki alanları `{ id, name }` nesnesi olarak döner.
588
+ - **Boş to-one ilişki `null` döner**, boş nesne değil — `if (row.manager_id)` kontrolü doğru
589
+ çalışır (İK koşusu D-8; 2026-09-03 öncesinde `{}` dönüyordu ve JS'te truthy'ydi). Boş
590
+ many-to-many alan `[]` döner.
333
591
  - **`isAuditTrailActive: true` ile KURULAN entity'ye satır yazılamaz.** `sys_*` kolonları create
334
592
  yolunda oluşmaz, yalnız ilk **etkin** `entity_update`'inde eklenir; o ana kadar **her**
335
593
  `row_create` şu hatayla 500 verir:
@@ -366,15 +624,145 @@ bilmezsen "kayıt bozulmuş" sanırsın.
366
624
  - **`defaultValueType: "static"` sessizce `"none"`e düşer** — DB varsayılanı kurulmaz. Bu yüzden
367
625
  her `BOOL`/sayısal alanı **her** `row_create`'te ve her workflow write adımında **açıkça** yaz;
368
626
  "varsayılanı platform koyar" varsayımı `null` satır üretir.
369
- - **`row_list`'in `filters` parametresi çalışmıyor** — denenen her biçim
370
- `Failed to invoke 'CompileNestedCondition'` ile 500 verir. Daraltmayı `selectedFields` +
371
- `order` + `pageLength` ile yap, ya da doğrulamayı entity satırından değil **ucun kendisinden**
372
- (`microservice_execute`) yap.
373
- - **`users`'ta `row_create` verdiğin `id`'yi yok sayar** (PK `identity` + `generationType: 0`);
374
- Studio operatörünü proje tablosuna aynı id ile taşıyamazsın, eşleme e-posta üzerinden kurulur.
627
+ - **`row_list.filters` desteklenir.** Tekil filtre `{ field, operator, value, filterType:'single' }`,
628
+ grup filtre `{ filterType:'group', conditionType, children:[...] }` biçimindedir. Tekil filtrede
629
+ `field`, grupta `children` zorunludur; `in`/`notin` operatörleri liste değer ister. Eksik/yanlış
630
+ şekiller SQL derleyicisine düşmeden alanı ve beklenen şekli söyleyen doğrulama hatası döner.
631
+ - **`row_create` identity Guid birincil anahtara verdiğin `id`'yi artık korur** — 2026-09-04
632
+ öncesinde platform değeri sessizce kendi ürettiğiyle değiştiriyordu (`EntityRuntimeValidator`);
633
+ şimdi değer verilmişse o yazılır, verilmemişse üretilir. Seed ve veri taşımada "gönderilen id ile
634
+ oluşan id farklı" sınıfı buradan geliyordu. `users` tablosunun kendi kısıtları ayrıdır; oradaki
635
+ eşlemeyi e-posta üzerinden kurmak en güvenli yoldur.
375
636
  - Silmeden önce `entity_dependencies` / `dependency_graph`.
376
637
 
638
+ ## Toplu ve türetilmiş satır
639
+
640
+ `row_*` bir **veritabanı yönetim yüzeyidir**: satırı gövdede taşır, iş kurallarını koşturur,
641
+ anahtarı üretir. **Var olan satır kümesinden TÜRETİLEN** bir küme onun işi değildir — durum
642
+ geçmişi, hareket/log tablosu, junction dolgusu, backfill. İki koşu bunu elle yazdı (2026-09-05:
643
+ 802 satır, 2026-09-06: 552 satır); ikincisinde koşunun 65 dakikasının **~21'i** tek bu faza gitti,
644
+ gateway süresi ise aynı fazda toplam 7.9 sn'ydi — maliyetin tamamı gövde yazmaktı.
645
+
646
+ Türetilmiş satırın yolu **ham SQL**'dir: `INSERT … SELECT` kaydedilir ve query kaynaklı bir uçtan
647
+ çalıştırılır. `candidate_id` gibi bağlar `SELECT`'in içinde kalır, hiçbir id yanıttan yanıta
648
+ taşınmaz.
649
+
650
+ | Satırlar nereden geliyor | Yol |
651
+ |---|---|
652
+ | Dışarıdan: kullanıcı verisi, içe aktarma, sabit liste | `row_create` / `row_update` |
653
+ | Var olan satırlardan türetiliyor: geçmiş, hareket, junction, backfill | kayıtlı `INSERT … SELECT` + `source: 'query'` uç |
654
+
655
+ > **Ham SQL UYGULAMA katmanını atlar, VERİTABANI katmanını atlamaz.**
656
+ > Koşmayan: automation'lar (satır olayı hiç doğmaz), `EntityRuntimeValidator`'ın tip ayrıştırma
657
+ > ve identity anahtar üretimi, incremental kod üretimi, çoklu dilli companion yazımı, audit izi.
658
+ > **Koşan:** tablonun bütün veritabanı kısıtları — NOT NULL, FK, UNIQUE ve **enum'un CHECK
659
+ > constraint'i**. Ölçüldü: `source` alanına listede olmayan bir değer yazmak
660
+ > `23514 … violates check constraint "check_<alan id'si>"` veriyor (HTTP 500). Kısıt adı alanın
661
+ > **id'sini** taşır (tireler alt çizgi olur), kolon adını değil — 23514 alırken suçluyu böyle
662
+ > bulursun. Yani enum'a çöp yazamazsın, ama iş kuralın automation'daysa o çalışmaz:
663
+ > seed/backfill için istediğin tam olarak budur, **iş kuralı çalışması gereken satırı bu yoldan
664
+ > yazma.**
665
+
666
+ ### Reçete (uçtan uca ölçüldü, 2026-09-07)
667
+
668
+ ```jsonc
669
+ // 1) sorguyu KAYDET — query_create
670
+ { "name": "Aday Geçmişi Dolgusu", "slug": "seed_candidate_history",
671
+ "datasourceId": "<built-in datasource id>",
672
+ "rawQuery": "INSERT INTO status_history (id, candidate_id, status, occurred_at) SELECT gen_random_uuid(), c.id, c.status, c.applied_at FROM candidate c" }
673
+
674
+ // 2) ucu aç — microservice_create (MS-002 warn'ı bekleniyor: ham SQL bilinçli tercih)
675
+ { "name": "Aday Geçmişi Dolgusu", "slug": "seed-candidate-history",
676
+ "description": "Var olan adaylardan durum geçmişi satırlarını üretir (tek seferlik dolgu).",
677
+ "type": "rest-api", "httpMethod": "post", "url": "seed-candidate-history",
678
+ "source": "query", "queryId": "<query id>" }
679
+
680
+ // 3) çalıştır — microservice_execute { method: 'POST' } → HTTP 204, GÖVDE YOK
681
+ // 4) doğrula — row_list('<hedef entity>') → satırlar burada; sayım tek doğrulama yolu
682
+ ```
683
+
684
+ ### Ölçülmüş sözleşme
685
+
686
+ - **Tablo adı = entity slug'ı, kolon adı = alan slug'ı.** İlişki alanının kolonu da kendi
687
+ slug'ıdır (`src_id`), ayrı bir FK adı türetilmez. Şema öneki YAZMA: bağlantı zaten ortamın
688
+ şemasına bağlıdır.
689
+ - **Anahtar sözleşmesi anahtar TİPİNE göre TERSİNE döner** — ikisi de `identity: true` +
690
+ `generationType: 0` olduğu hâlde:
691
+
692
+ | Anahtar | Ham SQL'de | Yanlışta ne olur |
693
+ |---|---|---|
694
+ | `UUID` / `guid` | anahtarı **SEN ver** (`gen_random_uuid()`) | `23502: null value in column "id" … violates not-null constraint` (HTTP 500) |
695
+ | `INT2/INT4/INT8` / `number` | anahtarı **HİÇ VERME** | `428C9: cannot insert a non-DEFAULT value into column "id" … GENERATED ALWAYS` (HTTP 500) |
696
+
697
+ Sebebi: GUID anahtarı **uygulama** üretiyor (`EntityRuntimeValidator`, `Guid.CreateVersion7()`),
698
+ kolonun veritabanı varsayılanı yok; INT anahtar ise Postgres `GENERATED ALWAYS` identity'sidir.
699
+ Alan tanımındaki `specifications.hasAutoData: true` bir **Studio ipucudur, DB varsayılanı
700
+ değildir** — ham SQL'de sana anahtarı üretmez.
701
+ - **`gen_random_uuid()` v4 üretir**, platformun kendi anahtarları v7'dir (zaman sıralı). Aynı
702
+ tabloda iki sürüm karışır; anahtara göre sıralayan bir ekranın varsa sıralamayı zaman
703
+ kolonundan yap.
704
+ - **`query_preview` yazan sorguyu çalıştırır ve GERİ ALIR ve bunu SÖYLEMEZ.** Yanıt yalnız
705
+ `{ affectedRowCount, elapsedMilliseconds }` taşıyor; `affectedRowCount` gerçek sayıyı döndüğü
706
+ için başarı gibi görünüyor, tabloda satır kalmıyor. Kalıcılığın tek kanıtı sonradan çekilen
707
+ `row_list`'tir.
708
+ - **`query_create` gövdeyi kaydetmeden önce bir kez koşturur ve geri alır** — yazan bir sorguyu
709
+ kaydetmek satır bırakmaz, ama SQL hatası kayıt anında patlar. Yazan sorgunun `returnScheme`'i
710
+ boş döner (yanıt şeması yalnız okuyan sorgudan üretilir).
711
+ - **Uç `204` ve BOŞ gövde döner** — `affectedRowCount` bile gelmez. Kaç satır yazıldığını
712
+ `row_list` ile say.
713
+ - `;` ile birden çok ifade ya da `RETURNING` içeren sorgu `multiple` tipine düşer ve okuma
714
+ yolundan koşar (kaynak: `PostgreSqlConnector.FindQueryType`); tek ifadeli sade `INSERT` için
715
+ tip `insert`'tür.
716
+ - **`WITH … INSERT` (CTE önekli yazma) BUGÜN REDDEDİLİYOR.** Ölçüldü 2026-09-07: `query_create`
717
+ `500 "Please correct the query syntax."`, `query_preview` `400 "Please use a supported query
718
+ type."` veriyor. Sorgu Postgres'te geçerlidir; reddeden, platformun tip sınıflandırıcısıdır
719
+ (`FindMainCommand` `WITH`i çözemeyip `NotSupportedQueryTypeException` atıyor). Depo kodunda
720
+ `WITH` çözümü **var** (`bcf722d1`, 2026-09-04) ama canlıda yok — `B-63`. **Bugünkü yazım:**
721
+ CTE yerine `JOIN (VALUES …) AS x(kolon, kolon)` kullan ve ifade `INSERT` ile başlasın.
722
+
723
+ ### Merdiven kalıbı: aşama geçmişi (S-4'ün tam şekli)
724
+
725
+ Her adayın **bulunduğu aşamaya kadarki** geçişlerini üretir. 300 adaydan **579 satır** tek çağrıda
726
+ yazıldı (ölçüldü 2026-09-07); huni piramidi 210 → 135 → 90 → 60 → 39 → 24 → 12 → 6 → 3, FK'sız
727
+ satır 0, `applied_at`'ten önceye düşen satır 0.
728
+
729
+ ```sql
730
+ INSERT INTO candidate_status_history (id, candidate_id, from_status, to_status, occurred_at)
731
+ SELECT gen_random_uuid(), c.id, p.stage, n.stage,
732
+ c.applied_at + (n.pos - 1) * interval '3 days'
733
+ FROM candidate c
734
+ JOIN (VALUES (1,'Applied'),(2,'Screening'),(3,'Interview'),(4,'Offer'),(5,'Hired'))
735
+ AS cur(pos, stage) ON cur.stage = c.status -- adayın bugünkü aşaması
736
+ JOIN (VALUES (1,'Applied'),(2,'Screening'),(3,'Interview'),(4,'Offer'),(5,'Hired'))
737
+ AS n(pos, stage) ON n.pos > 1 AND n.pos <= cur.pos -- ona kadarki her aşama
738
+ JOIN (VALUES (1,'Applied'),(2,'Screening'),(3,'Interview'),(4,'Offer'),(5,'Hired'))
739
+ AS p(pos, stage) ON p.pos = n.pos - 1 -- bir önceki aşama
740
+ ```
741
+
742
+ Merdiven listesi CTE olamadığı için üç kez tekrarlanır (yukarıdaki `WITH` maddesi). Zaman damgası
743
+ `applied_at`'ten türetildiği için geçmiş **tarihseldir** — `UtcNow()` tuzağı (`AUT-003`) bu yolda
744
+ hiç doğmaz, çünkü automation yoktur.
745
+
746
+ Toplu **güncelleme** de aynı yola aittir: var olan satırlardan hesaplanan bir damga/toplam için
747
+ `UPDATE … FROM` yaz, satırları `row_update`'in gövdesinde taşıma.
748
+
377
749
  ## Büyük entity'lerde
378
750
 
379
751
  `entity_get view:'outline'` alan listesi; `view:'part', part:'fields[slug=email]'` tek alan;
380
752
  değişiklik `entity_patch`.
753
+
754
+ ## Sürüm ve 409 — `entity_patch` tam gövde YAZMAZ
755
+
756
+ `entity_patch` okuduğu tanımın üstüne JSON Patch uygular ama geri **tam gövde göndermez**: op'lar
757
+ platformun `PATCH /entities/{id}` ucuna `{ baseVersion, ops }` olarak gider ve yalnız değişen
758
+ alanlar yazılır. Bir alan adı değişimi `fields` tablosunda **tek** UPDATE üretir (ölçüldü
759
+ 2026-09-08, `pg_stat_user_tables`).
760
+
761
+ `baseVersion` okunan kaydın `version` alanıdır ve zorunludur (eksikse 422). Sen okuduktan sonra
762
+ kayıt değiştiyse platform **409 `AggregateVersionConflictException`** döner ve HİÇBİR op uygulanmaz
763
+ — `entity_get` ile yeniden okuyup patch'i güncel hâle göre kur, aynı op'ları eski sürümle tekrar
764
+ gönderme.
765
+
766
+ `version` bir **string**tir (mikrosaniye zaman damgası, `Number.MAX_SAFE_INTEGER`'ın üstünde):
767
+ sayıya çevirirsen yuvarlanır ve her patch 409 döner. Yanıttaki `newVersion` bir sonraki patch'in
768
+ `baseVersion`'ıdır.