@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7

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 (145) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +9 -1
  3. package/README.md +14 -38
  4. package/dist/config.js +36 -3
  5. package/dist/knowledge/backendConstraints.js +982 -0
  6. package/dist/knowledge/componentCatalog.js +549 -92
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/coverage.js +43 -0
  9. package/dist/knowledge/flowGuards.js +30 -0
  10. package/dist/knowledge/functionGuards.js +134 -0
  11. package/dist/knowledge/guideSections.js +49 -0
  12. package/dist/knowledge/hydrate.js +97 -0
  13. package/dist/knowledge/languageCodes.js +36 -0
  14. package/dist/knowledge/languages.js +105 -0
  15. package/dist/knowledge/layoutSummary.js +10 -0
  16. package/dist/knowledge/localization.js +51 -0
  17. package/dist/knowledge/normalize.js +500 -16
  18. package/dist/knowledge/outlines.js +167 -12
  19. package/dist/knowledge/policy.js +14 -5
  20. package/dist/knowledge/propTypes.js +80 -0
  21. package/dist/knowledge/rules.js +961 -44
  22. package/dist/knowledge/schemaGuard.js +199 -0
  23. package/dist/knowledge/styleResolve.js +63 -4
  24. package/dist/observability/trace.js +27 -3
  25. package/dist/platform/csv.js +123 -0
  26. package/dist/platform/errors.js +107 -6
  27. package/dist/platform/http.js +34 -4
  28. package/dist/platform/patch.js +27 -0
  29. package/dist/platform/refs.js +104 -0
  30. package/dist/platform/responses.js +25 -6
  31. package/dist/platform/screenshot.js +544 -16
  32. package/dist/platform/secrets.js +40 -0
  33. package/dist/platform/tokenVault.js +89 -0
  34. package/dist/platform/writeDiff.js +281 -0
  35. package/dist/resources/examples/flow-call-function.json +66 -0
  36. package/dist/resources/examples/function-total.json +70 -0
  37. package/dist/resources/examples/layout-app-shell.json +1 -1
  38. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  39. package/dist/resources/examples/microservice-table-list.json +2 -4
  40. package/dist/resources/examples/page-dynamic-form.json +3 -3
  41. package/dist/resources/examples/page-flex-sections.json +99 -0
  42. package/dist/resources/examples/page-sign-in.json +4 -3
  43. package/dist/resources/guides/component.md +81 -8
  44. package/dist/resources/guides/entity.md +712 -20
  45. package/dist/resources/guides/expressions.md +47 -2
  46. package/dist/resources/guides/external-component.md +9 -0
  47. package/dist/resources/guides/functions.md +262 -0
  48. package/dist/resources/guides/getting-started.md +82 -7
  49. package/dist/resources/guides/layout.md +53 -10
  50. package/dist/resources/guides/merge.md +231 -0
  51. package/dist/resources/guides/microfunction.md +166 -0
  52. package/dist/resources/guides/microservice.md +287 -15
  53. package/dist/resources/guides/middleware.md +89 -1
  54. package/dist/resources/guides/modify-existing.md +23 -0
  55. package/dist/resources/guides/page.md +402 -49
  56. package/dist/resources/guides/translation.md +150 -8
  57. package/dist/resources/guides/view.md +16 -1
  58. package/dist/resources/guides/workflow.md +199 -28
  59. package/dist/resources/knowledge.js +5 -0
  60. package/dist/resources/rules.js +27 -0
  61. package/dist/resources/schema-fingerprint.json +8 -0
  62. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  63. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  64. package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
  65. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  66. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  67. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  68. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  69. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  70. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  71. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  72. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  73. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  74. package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
  75. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  76. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  77. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  78. package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
  79. package/dist/resources/schemas/DataJobDto.json +267 -0
  80. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  81. package/dist/resources/schemas/Filter.json +3 -1
  82. package/dist/resources/schemas/GetRowsQuery.json +6 -1
  83. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  84. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  85. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  86. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  87. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  88. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  89. package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
  90. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  91. package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
  92. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  93. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  94. package/dist/resources/schemas/RunImportCommand.json +62 -0
  95. package/dist/resources/schemas/StepDto.json +34 -3
  96. package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
  97. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  98. package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
  99. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  100. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  101. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  102. package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
  103. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  104. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  105. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  106. package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
  107. package/dist/server.js +17 -1
  108. package/dist/tools/access.js +3 -3
  109. package/dist/tools/activities.js +40 -0
  110. package/dist/tools/applications.js +1 -1
  111. package/dist/tools/automations.js +76 -34
  112. package/dist/tools/common.js +55 -0
  113. package/dist/tools/components.js +16 -7
  114. package/dist/tools/datasources.js +83 -21
  115. package/dist/tools/dependencies.js +1 -1
  116. package/dist/tools/deployments.js +34 -2
  117. package/dist/tools/dtos.js +25 -12
  118. package/dist/tools/entities.js +113 -28
  119. package/dist/tools/environments.js +52 -0
  120. package/dist/tools/externalComponents.js +9 -3
  121. package/dist/tools/files.js +57 -42
  122. package/dist/tools/flows.js +205 -13
  123. package/dist/tools/folders.js +7 -2
  124. package/dist/tools/functions.js +331 -0
  125. package/dist/tools/globalFunctions.js +63 -0
  126. package/dist/tools/importSchemas.js +79 -0
  127. package/dist/tools/imports.js +241 -0
  128. package/dist/tools/layouts.js +103 -29
  129. package/dist/tools/merges.js +113 -0
  130. package/dist/tools/microfunctions.js +155 -0
  131. package/dist/tools/microservices.js +270 -40
  132. package/dist/tools/organizations.js +5 -6
  133. package/dist/tools/pages.js +421 -104
  134. package/dist/tools/platformInfo.js +155 -11
  135. package/dist/tools/projects.js +183 -33
  136. package/dist/tools/queries.js +32 -10
  137. package/dist/tools/revisions.js +94 -0
  138. package/dist/tools/rows.js +484 -50
  139. package/dist/tools/screenWrite.js +68 -0
  140. package/dist/tools/screens.js +28 -0
  141. package/dist/tools/templates.js +32 -5
  142. package/dist/tools/translations.js +24 -8
  143. package/dist/tools/views.js +20 -10
  144. package/dist/tools/workflows.js +56 -21
  145. package/package.json +7 -2
@@ -43,23 +43,119 @@ servis formunda görünür ve `microservice_get` outline görünümünde geri ok
43
43
  (varsayılan 20) ve `offset` alır. Kurulum sırası: `view_create` (aranacak tablo/alanlar +
44
44
  analizör) → `view_rebuild` → `source:'view'` microservice. Satış özeti / top-N gibi
45
45
  rapor işleri view'e DEĞİL raw query'e aittir (`query_create` + query kaynaklı uç).
46
+ **Alan kapsamı:** view kaynaklı uç `selectFields` boşken yalnız view'ın indekslediği
47
+ alanları + birincil anahtarı döndürür. İndekslenmemiş kolon (maaş, TCKN) kendiliğinden
48
+ gelmez; daha fazlasını istiyorsan `selectFields` ile açıkça iste (İK koşusu M-7).
46
49
  - `workflow`: çok adımlı iş mantığı gerekiyorsa microservice'i workflow'a bağla
47
- (`workflowId`).
50
+ (`workflowId`). **Yanıtı açık bir DTO sözleşmesine bağlamanın tek yolu da budur** — DTO
51
+ alanları uçta değil akıştadır; bkz. aşağıda §"Yanıt sözleşmesi (DTO) uca DEĞİL workflow'a
52
+ bağlanır".
48
53
  - `query`: yalnız ham SQL gerçekten şartsa. Politika lint'i her query kullanımında gerekçe
49
54
  ister (MS-002, warn).
50
- 2. **`selectFields` asla boş bırakılmaz.** Boş = platform TÜM alanları döndürür; join'li
51
- sorguda maliyet katlanır. Join varken boş bırakmak block'tur (MS-001).
55
+
56
+ > **BOŞ STRING PARAMETRE `NULL`'A ÇEVRİLİR — sorgu sessizce 0 satır döner.** Platform
57
+ > önizleme/çalıştırma parametrelerini normalize ederken boş ya da yalnız boşluktan oluşan
58
+ > değeri `null` yapar. `WHERE company_id = @company` yazıp `company: ''` gönderirsen koşul
59
+ > `= NULL` olur ve **hiçbir satırla eşleşmez**: HTTP 200, boş sonuç, hata yok, uyarı yok.
60
+ > Üç koşuda üst üste ölçüldü (09-02, 09-04 birinci ve dördüncü koşu) ve üçünde de teşhis
61
+ > ancak `SELECT (@p IS NULL)` gibi bir sonda ile yapılabildi.
62
+ >
63
+ > **Parametre listesi ZORUNLUDUR.** `@token` kullanan bir sorguyu `query_create` ile
64
+ > kaydederken her belirteci `parameters` içinde `{ name, type, value }` olarak bildir. Eksik
65
+ > bırakılırsa istek **400** döner ve mesaj hangi belirtecin bildirilmediğini adıyla söyler.
66
+ > (2026-09-04'ten önce aynı durum `500 "Please correct the query syntax."` veriyordu ve SQL'i
67
+ > suçluyordu; kaydetme yolu gövdeyi önce bir kez çalıştırıp yanıt şemasını ürettiği için
68
+ > bağlanmamış belirteç sözdizimi hatası gibi görünüyordu.) `value` yalnız kayıt anında
69
+ > kullanılır: SQL doğrulanır ve `returnScheme` ondan üretilir.
70
+ > Yorum satırındaki `{{ad}}`/`@ad` belirteçleri parametre sayılmaz — kaydetme yolu
71
+ > sorguyu yorumlarından arındırdıktan sonra tarar. SQL çalıştırılamazsa kayıt **yine de**
72
+ > oluşur: `returnScheme` boş kalır ve yanıttaki `message` nedeni söyler; SQL'i düzeltip
73
+ > `query_update` ile kaydet.
74
+ >
75
+ > "Filtre yok" demek istiyorsan iki doğru kalıptan birini kullan:
76
+ > `(@company IS NULL OR c.company_id::text = @company)` ya da bir sentinel değer
77
+ > (`'all'`) ve SQL'de ona bakan bir dal. Aynı davranış workflow query adımında da geçerli;
78
+ > oradaki anlatım `platform_guide('workflow')` "Query adımı" bölümünde.
79
+ >
80
+ > **KAPSAM UYARISI: query kaynaklı ucun parametreleri ÇAĞRIDAN gelir.** Middleware'in
81
+ > kurduğu `user` bu yola girmez — kimlik/tenant kapsamını gövdeye yazan bir uç
82
+ > sahtelenebilir (ölçüldü 2026-09-07). Kapsam sunucuda zorlanacaksa sorguyu bir workflow
83
+ > query adımından çağır; reçete aşağıda §"Kimlik kapsaması × uç kaynağı".
84
+ 2. **`selectFields` asla boş bırakılmaz.** Boş = platform TÜM alanları döndürür (view kaynaklı
85
+ uçta: yalnız view'ın indekslediği alanlar + birincil anahtar); join'li sorguda maliyet
86
+ katlanır. Join varken boş bırakmak block'tur (MS-001).
52
87
  3. **Join'ler ilişkilerden türetilir.** `sourceParameters.joins` öğesi:
53
88
  `{ alias, entityId, fieldId, path, joins }` — iç içe join mümkündür. Her join'in getirdiği
54
89
  alanlar da `selectFields`'ta `{ alias, fieldId, path, fieldSlug }` ile açıkça seçilir.
55
90
  **`path` boş bırakılamaz** — Studio join'li seçimleri yalnız `path` üzerinden eşleştirir
56
91
  (aşağıdaki bölüme bak).
92
+ 3b. **Kayıtlı ucu önizleme:** `microservice_preview` tam gövde istemez — `id` verirsen gateway
93
+ tanımı platformdan çeker ve yalnız `previewParameters`'ı senden alır (09-04 Y-09). Şekil
94
+ `{ body: [{…}] }` · `{ queryString: {…} }` · `{ routeParams: {…} }`; **tanımsız bir şekil
95
+ sessizce yutulur** ve bütün sayaçlar 0 döner (09-02 M-2).
57
96
  4. **HTTP sözleşmesi:** `type: 'rest-api'` + `httpMethod: get|post|put|delete` + `url`
58
97
  (route şablonu). Zamanlanmış işler `type: 'schedule-job'`, kuyruk tüketicileri `consumer`.
98
+ 4b. **`query_preview` yazma sorgusunu ÇALIŞTIRIR, GERİ ALIR ve BUNU SÖYLEMEZ.**
99
+ `INSERT`/`UPDATE`/`DELETE` önizlediğinde `affectedRowCount` **gerçek** sayıyı döndürür ama
100
+ transaction geri alınır ve tabloda satır kalmaz. **Yanıtta rollback işareti yoktur** — yalnız
101
+ `{ affectedRowCount, elapsedMilliseconds }` gelir (2026-09-07'de ölçüldü; `rolledBack` alanı
102
+ yalnız OKUYAN sorgunun yanıt şeklinde var ve orada hep `false`). Yani sayıya bakan çağıran
103
+ yazdığını sanır: 09-04 4. koşusunda 624 satırlık bir seed böyle "yazıldı" sanıldı. Kalıcı
104
+ yazma için sorguyu kaydedip query kaynaklı bir uçtan ya da workflow'dan çalıştır; kalıcılığın
105
+ tek kanıtı sonradan çekilen `row_list`'tir. **Var olan satırlardan türetilmiş satır üretmenin
106
+ uçtan uca reçetesi** (anahtar sözleşmesi dâhil) `platform_guide('entity')` §"Toplu ve
107
+ türetilmiş satır" bölümündedir; aynı davranış `platform_guide('workflow')` "Query adımı"
108
+ bölümünde de yazılıdır.
109
+
110
+ 4c. **Sayfalama toplamı `Total-Count` header'ından gelir.** Sayfalı bir uç kurduğunda toplam
111
+ kayıt sayısı gövdede değil header'dadır (`microservice_execute` yanıtında `headers` altında,
112
+ küçük harfle `total-count`). Tabloya bağlarken sayfa göstergesinin okuduğu sözleşme budur;
113
+ ayrıntı `platform_guide('page')` sayfalama bölümü. Alternatif olarak sorgunun kendisi
114
+ `COUNT(*) OVER()` ile toplamı satıra koyabilir — o zaman header'a hiç bağımlı kalmazsın.
115
+
59
116
  5. **Doğrulama akışı:** `microservice_preview` (kaydetmeden) → kaydet → `microservice_execute`
60
117
  (gerçek runtime yolu) → sonuç beklenmedikse **aynı parametrelerle `microservice_debug`** —
61
118
  adım adım executionItems ağacı döner.
62
119
 
120
+ ## Yanıt sözleşmesi (DTO) uca DEĞİL workflow'a bağlanır (MS-018, block)
121
+
122
+ `returnDtoId` · `parameterDtoId` · `returnBehavior` · `parameterBehavior` **microservice
123
+ komutunda yoktur** (`Create/UpdateMicroserviceCommand`). Gövdeye yazarsan platform 200 döner ve
124
+ alanı sessizce düşürür: uç sözleşmesiz kalır, ne hata ne uyarı gelir (2026-09-09 koşusu `S-2`).
125
+ Gateway bu gövdeyi artık MS-018 ile **platforma hiç göndermeden** reddeder.
126
+
127
+ Yanıt şeklini kaynak belirler:
128
+
129
+ | Kaynak | Yanıt şeklini ne belirler | Açık DTO sözleşmesi |
130
+ |---|---|---|
131
+ | `table` | `sourceParameters.selectFields` (+ join alanları) | yok |
132
+ | `query` | sorgunun `returnScheme`'i — kayıt anında SQL'den üretilir | yok |
133
+ | `view` | view'ın indekslediği alanlar + birincil anahtar (`selectFields` ile genişletilir) | yok |
134
+ | `workflow` | akışın `returnDtoId` + `returnBehavior` sözleşmesi | **var** |
135
+
136
+ Yani "yanıtın alanları şu DTO'dan olsun" diyeceksen uç **workflow kaynaklı** kurulur:
137
+
138
+ 1. `dto_list` ile mevcut DTO'yu bul (platform entity başına DTO üretir) ya da `dto_create` → `dtoId`
139
+ 2. `workflow_create` → `returnDtoId: dtoId` + `returnBehavior: 'object' | 'array' | 'primitive'`.
140
+ WF-002: `type:'workflow'` akışta `returnDtoId` **zorunludur** (yoksa akış `unready` kalır);
141
+ `returnBehavior`'ı boş bırakırsan gateway `object` ile doldurup `gatewayNormalized` der.
142
+ 3. `microservice_create` → `source: 'workflow'` + `workflowId` — DTO alanı **uca yazılmaz**.
143
+ 4. Aynı uç `middlewareIds` ile kimlik kapsamasını da taşır (§"Kimlik kapsaması × uç kaynağı").
144
+
145
+ Kayıtlı bir akışa sözleşmeyi sonradan bağlamak tek çağrıdır:
146
+ `workflow_patch(id: <workflowId>, ops: [{ op: 'add', path: '/returnDtoId', value: <dtoId> }, { op: 'add', path: '/returnBehavior', value: 'object' }])`.
147
+
148
+ **Sözleşmenin bağlandığını uçtan oku:** workflow kaynaklı uçta `microservice_get` yanıtındaki
149
+ `returnScheme` **DTO'nun alanlarını** taşır; table/query/view kaynaklı uçta şema kaynaktan türer.
150
+ Bağlanmamış sözleşmenin belirtisi budur — uç 200 döner ama `returnScheme` DTO'yu değil kaynağı
151
+ yansıtır (2026-09-09'da ölçüldü: `returnDtoId` yazılan table kaynaklı uçta şema `['id','name']`
152
+ kaldı, DTO'nun beş alanı gelmedi).
153
+
154
+ Sözleşme yalnız **2xx** yanıtlarda uygulanır: 401/422 döndüren bir `response` adımının gövdesi
155
+ DTO'dan süzülmez (WF-026 — `platform_guide('workflow')` §Hata yolu). Sözleşme **süzer, uydurmaz**:
156
+ `bodyExpression`'ın ürettiği DTO dışı alan yanıttan düşer, DTO'da olup üretilmeyen alan gelmez
157
+ (ölçüm: `gizliNot` alanı yanıtta yok, `slot_minutes` de yok).
158
+
63
159
  ## Asgari doğru table-source microservice (platformda doğrulanmış kurallar)
64
160
 
65
161
  - `operation` enum'u **tam olarak** şudur ([MicroserviceTableOperation](../../../../zeyrek-backend/src/NgApps.Domain/Contexts/Studio/Microservices/MicroserviceTableOperation.cs)):
@@ -69,18 +165,35 @@ servis formunda görünür ve `microservice_get` outline görünümünde geri ok
69
165
  yapmasını iki ayrı uç + akışta condition dallanmasıyla kurmayın: tek `operation: 'upsert'`
70
166
  ucu yeterlidir. Aynı işlem workflow **table step**'inde de desteklenir
71
167
  ([TableStep](../../../../zeyrek-backend/src/NgApps.Domain/Contexts/Flow/Workflows/Steps/TableStep.cs)).
168
+
169
+ - **Upsert eşleşmesi `filters` ile kurulur, PK `fields`'a KONMAZ** (ölçüm: İK koşusu M-4,
170
+ 2026-09-01). PK'yı `fields`'a koyarsan `AlwaysGeneratedFieldCantBeWritten` alırsın. Doğru şekil:
171
+ `filters`'a `source: "body"` + `isRequired: false` bir id filtresi; gövdede id **varsa** eşleşen
172
+ satır güncellenir, **yoksa** yeni satır açılır. `isRequired: true` yaparsan "yeni kayıt" yolu
173
+ kapanır (id gelmeyince uç reddeder).
174
+
175
+ > **Upsert'te üretilen alan tuzağı.** `operation: 'upsert'` UPDATE yolunda da alan ifadelerini
176
+ > yeniden değerlendirir; sicil/kod gibi üretilen bir değeri `source: 'expression'` ile yazarsan
177
+ > güncellemede **sessizce değişir** (İK koşusu M-3: EMP-0005 → EMP-0006). Alan bazlı
178
+ > `writeOn: create | update | always` platformda **yok**; o gelene kadar oluşturma ve güncelleme
179
+ > için iki ayrı uç kurun. Gateway **MS-016** ile uyarır.
72
180
  - **`count` vardır** — kayıt sayısı için tüm satırları çekip uzunluğuna bakmayın; panel/KPI
73
181
  sayaçlarının doğru şekli budur.
74
182
  - Gövde kökünde **`entityId` zorunlu**. (Eski "kökte datasourceId zorunlu" bilgisi yanlıştı —
75
183
  komutta öyle bir alan yok, gönderilirse yok sayılır; asıl zorunluluk selectFields'taki fieldId.)
76
- - `selectFields[]` öğelerinde **`fieldId` zorunlu** (yalnız `fieldSlug` yetmez); kök tablo
77
- alanlarında alias = **entity'nin slug'ı** ve `path` **yazılmaz**. Alias verilmezse
184
+ - `selectFields[]` / `filters[]` öğelerinde platform **`fieldId`** ister; gateway'e **`fieldSlug`**
185
+ vermen yeter — kök entity'nin alanları için id'yi gateway çözer ve `gatewayResolved` ile bildirir
186
+ (join'lenen entity'lerin alanlarında hâlâ `fieldId` gerekir). Kök `entityId` de slug olabilir.
187
+ Kök tablo alanlarında alias = **entity'nin slug'ı** ve `path` **yazılmaz**. Alias verilmezse
78
188
  `ArgumentNullException(key)`, farklı alias verilirse "missing FROM-clause entry" gelir.
79
189
  - `sort` ve `limit` **her zaman** verilir (MS-008, block) — aşağıya bak.
80
190
  - Filtrelerde `value` bir **JavaScript ifadesidir** — sabit metin tırnak içinde
81
- (`"'ACTIVE'"`), dinamik değer ifade olarak (`"input.status"`).
191
+ (`"'ACTIVE'"`), dinamik değer ifade olarak (`"input.status"`). **İki operatör bunun dışındadır:**
192
+ `isnull` ve `isnotnull` değer taşımaz — `value` verilmez, `source` da zorunlu değildir; verirsen
193
+ platform yok sayar, doğrulama da değer beklemez.
82
194
  - **Operatör sözlüğü kapalıdır:** `eq · neq · lt · lte · gt · gte · contains · icontains ·
83
- in · notin · startswith · endswith · istartswith · iendswith`. **`like` YOKTUR** — metin
195
+ in · notin · startswith · endswith · istartswith · iendswith · isnull · isnotnull`.
196
+ **`like` YOKTUR** — metin
84
197
  araması `contains`/`icontains` iledir. Kritik tuzak: platform bilinmeyen operatörü 400 ile
85
198
  reddetmez, **sessizce `eq`'e düşürür** (enum çözümlemesi unknown'da default'a düşer) — uç
86
199
  testten geçer ama arama hiçbir şey döndürmez. Gateway MS-012/WF-015 bunu istek gitmeden keser.
@@ -90,6 +203,11 @@ servis formunda görünür ve `microservice_get` outline görünümünde geri ok
90
203
  `{ alias, fieldId, fieldSlug }`). Aynı kural workflow table create/update step'lerinde de
91
204
  geçerlidir. Boş `returnFields` ile write operasyonu yazarsan MS-009 uyarır; buna karşılık
92
205
  write operasyonunda boş `selectFields` **uyarı üretmez** (platform onu zaten yok sayar).
206
+ - **Platformun ürettiği alanları `fields` içine koymayın.** Primary key/identity gibi
207
+ `alwaysGenerated` alanlar yazılırsa istek 400 döner ve hatalı alan slug'larını sayar; bu alanlar
208
+ create'te platformca üretilir, upsert eşleşmesi gerekiyorsa değer `fields` yerine `filters`ta
209
+ taşınır. Incremental alanın create/update zamanlaması ayrı tasarım konusudur; mevcut sınır için
210
+ [`writeOn` tasarımına](../../../../zeyrek-backend/docs/alan-yazma-zamani/01-tasarim.md) bakın.
93
211
 
94
212
  ```jsonc
95
213
  {
@@ -208,6 +326,19 @@ karşılamayı imkânsız kılar. Karar kuralı:
208
326
  "isRequired": false, "children": [], "order": 0 }
209
327
  ```
210
328
 
329
+ > **`source: "query-string"` / `"body"` / `"header"` filtresinde `value` VERİLMEZ** (ölçüm: İK
330
+ > koşusu M-2, 2026-09-01). Değer çağrıdan gelir, parametre adı alan slug'ından türer. `value`
331
+ > yazarsan platform onu JS ifadesi sanıp eval eder → `JavaScriptException: <ad> is not defined`
332
+ > ile 500. `value` yalnız `source: "expression"` ve `"static"` kaynaklarında anlamlıdır. Sabit
333
+ > ön-süzme (tenant, aktiflik) `expression` ile yapılır. Gateway **MS-015** lint'i bu
334
+ > kombinasyonu istek platforma gitmeden yakalar.
335
+
336
+ > **Dış kaynaklı filtreye (`query-string` / `body` / `header`) `value` yazılmaz.** Değer çağrıdan
337
+ > gelir; tanımdaki `value` çalışma zamanında yok sayılır — parametre gelmezse filtre atlanır ve uç
338
+ > **süzülmemiş** tam liste döner (2026-09-03 öncesinde aynı alan JS olarak değerlendirilip 500
339
+ > veriyordu; İK koşusu M-2). Tanımdan gelen sabit değer istiyorsan `source: 'static'` ya da
340
+ > `'expression'` kullan.
341
+
211
342
  ### Dinamik filtre (`enableDynamicFilters`) — süzülebilir tablonun uç şekli
212
343
 
213
344
  `enableDynamicFilters: true` verilen uç, süzme koşullarını **tanımdan değil çağrıdan** alır.
@@ -267,12 +398,69 @@ hem `joins` ile getirin hem `selectFields`'a koyun; ondan sonra `filters[0][fiel
267
398
  > (qm kanıtı: `sy-users-get-my-profile`). Ayrıntı ve tarifler: `platform_guide('middleware')`.
268
399
  > Middleware bağlamadan `user.` okuyan expression yazarsan MS-011 uyarır — runtime'da
269
400
  > `user is not defined` 500'ü yersin.
401
+ >
402
+ > **`middlewareIds`'i `microservice_create` GÖVDESİNDE ver, sonradan patch'leme.** Ucu kurup
403
+ > ardından `microservice_patch` ile `/middlewareIds` yazmak her uç için fazladan bir tur demektir:
404
+ > 2026-09-06 koşusunda dokuz uç kuruldu ve dokuz patch çağrısının dokuzu da yalnız bu yolu yazdı
405
+ > (~3 dk). Alan create gövdesinde zaten kabul ediliyor.
270
406
 
271
407
  > Sayfa tarafındaki karşılığı: kullanıcı ekrandan süzüyorsa (`table` + `filterMode` açık, ya da
272
408
  > `datatable` + `isFilterable` kolonlar) uç `enableDynamicFilters: true` olmalı; komponent seçilen
273
409
  > koşulları kendi `filters` prop'una yazar ve akış onu uca `filters` parametresi olarak geçirir.
274
410
  > Süzme yoksa statik query-string filtreleri yeterlidir — bkz. `platform_guide('page')` Kural 1e.
275
411
 
412
+ ### Kimlik kapsaması × uç kaynağı — query kaynaklı uç BAĞLAM GÖRMEZ
413
+
414
+ Yukarıdaki `source: 'expression'` + `value: 'user.id'` kalıbı **yalnız table kaynaklı** uçlarda
415
+ vardır. Agregasyon/rapor uçları query kaynaklı olduğu için aynı kalıp orada yoktur ve
416
+ **parametrelerinin tamamı çağrıdan gelir** — kapsamı gövdeye yazan bir uç sahtelenebilir.
417
+
418
+ | Uç kaynağı | Parametre/filtre nereden gelir | Kapsam sunucuda zorlanır mı |
419
+ |---|---|---|
420
+ | `table` | `filters[]` · `source:'expression'`, `value:'user.id'` | **Evet** (MS-011 middleware bağını denetler) |
421
+ | `query` | Yalnız çağrının kendisi: **body → query-string → header** (`Query.PrepareQueryParameters` → `FindValue`) | **Hayır** — ifade değerlendirmesi yoktur |
422
+ | `workflow` | Adım ifadeleri; `user.*` bağlı middleware'den gelir | **Evet** (ölçüldü 2026-09-07) |
423
+ | `view` | Arama sözleşmesi (`searchText`) + view'ın indekslediği alanlar | **ölçülmedi** — kapsam şartsa workflow yolunu kullan |
424
+
425
+ Query parametresine ifade kaynağı **eklenemez**: `parameters[].source` komut şemasında yoktur,
426
+ platform anahtarı sessizce atar (gateway `unmappedMembers` ile söyler) ve kayıtlı şema
427
+ `source: 'body'` kalır. Bu bir eksik değil, modülün tanımı: query modülü SQL seviyesinde çalışan
428
+ ham SQL yüzeyidir, istek bağlamını bilmez.
429
+
430
+ **Ölçüm (2026-09-07, aynı sorgu, aynı middleware — middleware `user.owner_key = 'OWNER-A'` kurdu):**
431
+
432
+ | Uç | Gövde | Yanıt |
433
+ |---|---|---|
434
+ | `source:'query'` | `{ owner_key:'OWNER-A' }` | `{ owner_key:'OWNER-A', row_count:3, total:60 }` |
435
+ | `source:'query'` | `{ owner_key:'OWNER-B' }` | `{ owner_key:'OWNER-B', row_count:2, total:300 }` ← **başkasının verisi** |
436
+ | `source:'workflow'` | `{ owner_key:'OWNER-B' }` | `{ owner_key:'OWNER-A', row_count:3, total:60 }` ← gövde **yok sayıldı** |
437
+ | `source:'workflow'` | `{ owner_key:'OWNER-B', min_amount:20 }` | `{ owner_key:'OWNER-A', row_count:2, total:50 }` ← kapsam dışı filtre gövdeden okundu |
438
+
439
+ #### Reçete: kapsanmış agregasyon ucu
440
+
441
+ Sorguyu değiştirmeye gerek yok; onu **bir katman yukarıdan** çağırırsın.
442
+
443
+ 1. `query_create` — agregasyonu kapsam parametresiyle yaz:
444
+ `… WHERE company_id = @company_id AND created_at >= @from`, `parameters` ile her belirteci bildir.
445
+ 2. `workflow_create` (`type: 'workflow'`) — start → **query adımı** → response. Adımın bağlaması
446
+ kapsamı middleware'den, kalan filtreleri gövdeden alır:
447
+ `rule.expression = "{ company_id: user.company_id, from: input.from }"`
448
+ (bağlamanın iki yazımı ve canlı motorun tuzağı: `platform_guide('workflow')` §"Query adımı").
449
+ 3. `microservice_create` — `source: 'workflow'`, `workflowId`, **`middlewareIds` dolu**.
450
+ 4. Doğrula: aynı ucu gövdeye **başka birinin** kapsam değerini yazarak çağır; yanıt değişmemeli.
451
+
452
+ `middlewareIds` boşken bu akış `500 JavaScriptException: user is not defined` verir (ölçüldü) —
453
+ MS-011'in workflow tarafındaki karşılığıdır ve gateway o dizinin boş kalmasını göremez, çünkü
454
+ uçtan görünen tek şey `workflowId`'dir.
455
+
456
+ Middleware tarafındaki karşılığı ve `user` değişkenini kuran akış:
457
+ `platform_guide('middleware')` Tarif 3c.
458
+
459
+ **Her agregasyonu sarmalama.** Kapsam gerekmeyen bir özet (herkese açık istatistik, zaten
460
+ middleware'in reddettiği bir panelde tüm şirketleri gösteren yönetici raporu) düz `source:'query'`
461
+ kalır: fazladan workflow, DTO ve response adımı bedavaya gelmez. Ölçü basit — **bu parametreyi
462
+ çağıran seçebilmeli mi?** Hayırsa sarmala.
463
+
276
464
  ### `sort` ve `limit` her zaman verilir
277
465
 
278
466
  **MS-008 (block).** Platform bunları null bırakır; Studio formu ise `data.sort.name` /
@@ -285,6 +473,24 @@ Gateway bu kuralları MS-005/MS-006 olarak istek gitmeden denetler; platform val
285
473
  hatası alırsan `microservice_debug` çıktısıyla raporla — o zaman gerçek bir bug'dır.
286
474
  Query source'a kaçış yalnız ham SQL şartsa; query yazarken parser `SELECT` sonrası satır sonu
287
475
  kabul etmez — ilk kolon `SELECT` ile aynı satırda olmalı ("Please use a supported query type").
476
+ Kayıtlı query'nin `@ad` / `{{ad}}` belirteçleri uçta **body → query-string → header** sırasıyla
477
+ bağlanır; bunun için parametreler query kaydedilirken `parameters` ile bildirilmiş olmalıdır
478
+ (`platform_guide('workflow')` §"Query adımı"). Davranış `query_preview` ile aynıdır.
479
+
480
+ **`language` AYRILMIŞ bir parametredir.** Ham sorguda `{{language}}` (ya da `@language`)
481
+ kullanırsan platform onu isteğin diliyle kendisi doldurur — `parameters` listesine yazmana gerek
482
+ YOKTUR ve yazarsan doğrulama onu zorunlu saymaz. Çok dilli companion tablosunu elle join eden bir
483
+ raporun dili böyle alınır:
484
+
485
+ ```sql
486
+ SELECT p.id, COALESCE(l.title, d.title) AS title
487
+ FROM products p
488
+ LEFT JOIN products_localization l ON l.product_id = p.id AND l.language = {{language}}
489
+ LEFT JOIN products_localization d ON d.product_id = p.id AND d.language = 'en'
490
+ ```
491
+
492
+ `query_preview` da aynı parametreyi ekleyerek koşar, yani şema çıkarımı `{{language}}` yüzünden
493
+ "eksik parametre" diye patlamaz.
288
494
 
289
495
  ## Middleware'siz uçta çok adımlı yazım ATOMİK DEĞİL (ölçüm 2026-08-19)
290
496
 
@@ -328,6 +534,62 @@ kabul etti. Ayrıntı ve doğru rol kapısı tarifi: `platform_guide('middleware
328
534
  > rota `/{project}/m/{application}/{microservice SLUG'ı}`'dır ( `url` alanı rotaya GİRMEZ) ve
329
535
  > `Host: <org>.<suffix>` + `X-Environment` başlıkları zorunludur.
330
536
 
537
+ ## Alias nerede geçerli
538
+
539
+ Alias bir **SQL tablo takma adıdır**: yalnız okunan tarafta anlamı vardır. Sözleşme iki ayrı
540
+ tiple modellenmiştir ve ikisi karıştırılırsa alan sessizce düşer.
541
+
542
+ | Yer | Tip | Alanlar |
543
+ |---|---|---|
544
+ | `selectFields[]` · `returnFields[]` | `TableSelect` | `alias` · `fieldId` · `path` · `fieldSlug` |
545
+ | `filters[]` · `joins[]` | filtre/join | `alias` (+ kendi alanları) |
546
+ | `sourceParameters.alias` | kök alias | join varken ZORUNLU, kök entity'nin slug'ı |
547
+ | **`fields[]` (yazılacak alanlar)** | `TableField` | `fieldId` · `source` · `value` · `folderId` · `order` · `writeOn` — **`alias` YOK** |
548
+
549
+ `fields[]` yalnız `create` / `update` / `upsert` operasyonlarında bulunur ve WHERE değil SET
550
+ üretir: hangi tabloya yazılacağı zaten `entityId` ile bellidir, takma ada ihtiyaç yoktur. Aynı
551
+ `TableField` tipi workflow table adımlarının `rule.fields[]` listesinde de kullanılır.
552
+
553
+ Yazma alanına `alias` koyarsan istek **hata vermez**, alan model bağlamada düşer. Gateway bunu
554
+ istek gitmeden gövdeden çıkarır ve `gatewayNormalized` ile bildirir — böylece `silentlyDropped`
555
+ listesi yalnız gerçek kayıpları gösterir.
556
+
557
+ ## Zamanlanmış iş (`type: 'schedule-job'`) — `typeParameters.schedule`
558
+
559
+ Zamanlama tek bir tanımdır; `cronExpression` tek başına verilirse **UTC cron** sayılır (eski gövde).
560
+ Yeni işte tanımı `typeParameters.schedule` ile yazın ve kaydetmeden önce
561
+ `microservice_schedule_forecast` ile sıradaki koşuları görün.
562
+
563
+ | Alan | Değer |
564
+ |---|---|
565
+ | `mode` | `once` · `interval` · `cron` |
566
+ | `timeZoneId` | IANA (`Europe/Istanbul`); yoksa `UTC`. Saatler BU dilimde yorumlanır |
567
+ | `startAt` | yerel tarih-saat, dilimsiz (`2026-09-14T09:00:00`); once/interval için, cron'da `end.type:'after'` ile zorunlu |
568
+ | `every` · `unit` | 1–1000 · `minute` `hour` `day` `week` `month` |
569
+ | `timeOfDay` | `HH:mm` — `day`/`week`/`month` için zorunlu |
570
+ | `daysOfWeek` | `monday`…`sunday` — `week` için en az bir |
571
+ | `daysOfMonth` | 1–31, `-1` = ayın son günü — `month` için en az bir |
572
+ | `cronExpression` | Quartz cron (6–7 alan, `?` zorunlu) — yalnız `cron` modu |
573
+ | `end` | `{type:'never'}` · `{type:'on', at}` · `{type:'after', count}` (1–10000) |
574
+
575
+ ```json
576
+ { "mode": "interval", "timeZoneId": "Europe/Istanbul", "startAt": "2026-09-14T00:00:00",
577
+ "every": 2, "unit": "week", "daysOfWeek": ["tuesday", "thursday"], "timeOfDay": "09:00",
578
+ "end": { "type": "after", "count": 30 } }
579
+ ```
580
+
581
+ - Aralık başlangıçtan sayılır: "7 dakikada bir" saat başında sıfırlanmaz, "3 günde bir" ay sonunda
582
+ başa sarmaz, "2 haftada bir" gerçekten iki haftada birdir. Cron'da bunlar DOĞRU İFADE EDİLEMEZ
583
+ (`MON/2` = haftada 3 gün) — bu ihtiyaçlar için `interval` kullanın.
584
+ - `after N` başlangıçtan hesaplanan N'inci koşuda biter; `microservice_schedule_control trigger`
585
+ sayılmaz. Biten iş `microservice_schedule_get`'te `state: 'ended'` görünür.
586
+ - Geçersiz tanım `422` + alan adıyla lokalize mesaj döner (bozuk cron artık `500` değil). `after` ile
587
+ toplam süre sınırlıdır (gün × sayı ≤ 36500, hafta ≤ 5200, ay ≤ 1200).
588
+ - **`schedule` ile `cronExpression` çelişemez.** `microservice_get` ikisini de döndürür; güncellemede
589
+ yalnız `typeParameters.schedule`'ı değiştirin (cron modunda `schedule.cronExpression`). Yalnız
590
+ `cronExpression`'ı değiştiren PUT/PATCH `422 ScheduleCronConflict` alır — sessizce eski cron
591
+ kalmaz. `typeParameters: null` işi zamanlayıcıdan siler.
592
+
331
593
  ## Sık hatalar
332
594
 
333
595
  - `description` boş (MS-014, block — uç listesinde ne işe yaradığı okunamaz)
@@ -336,13 +598,23 @@ kabul etti. Ayrıntı ve doğru rol kapısı tarifi: `platform_guide('middleware
336
598
  - `operation: 'list'` → geçersiz; `get` + `isSingle: false` kullanın (PLT-006)
337
599
  - Üst seviye `entityId` eksik (MS-004, warn)
338
600
  - Gerekçesiz `source: 'query'` (MS-002, warn)
601
+ - Backend zorunlulukları (MS-017, block — istek gitmeden): `name` ve `url` boş olamaz (update'te
602
+ `slug` da); `queryId`/`entityId`/`workflowId` verilirse boş GUID olamaz; `source: 'expression'`
603
+ alan ve filtrede `value` dolu; tekil filtrede `fieldId`; `writeOn` yalnız `operation: 'upsert'`
604
+ ucunda; join'de `entityId` + `fieldId`. Tam katalog: `ngapps://rules/backend-constraints`.
605
+ - Query kaydı (QRY-001, block): `rawQuery`'deki her `@ad` / `{{ad}}` `parameters[]`'ta
606
+ `{ name, type }` ile bildirilir — yalnız create denetler, update sessiz geçer ve sorgu çalışma
607
+ anında düşer; `slug`/`rawQuery`/`datasourceId` zorunlu; `outputType` `array` | `object`.
339
608
  - Execute'ta dönen `httpStatus` microservice'in kendi sonucudur — 4xx/5xx görünce önce
340
609
  `microservice_debug` ile hangi adımın ürettiğine bak, tanımı ona göre düzelt.
341
- - **`microservice_update` / `microservice_patch`, `operation: 'get'` tipli microservice'lerde
342
- HER ZAMAN 500 verir** (`The instance of entity type 'Application' cannot be tracked` —
343
- platform regresyonu, P-02 olarak düzeltilecek). Ayrım **operasyona** bağlıdır, tool'a değil:
344
- `operation: 'create' | 'update' | 'delete' | 'upsert'` kayıtlarında PUT/PATCH sorunsuz çalışır
345
- (crm koşumunda `update` uçlarında yeniden doğrulandı, 2026-08-25); yalnız `get` (liste ya da
346
- `isSingle`) uçlarında patlar. Get-tipi bir microservice'i değiştirmen gerekiyorsa **`microservice_delete` +
347
- `microservice_create`** kullan ve tanımı ilk POST'ta doğru üret; silme kümesini kendi
348
- ürettiğin slug listesiyle sınırla. `entity_patch`, `workflow_patch`, `row_update` sağlamdır.
610
+ - **`microservice_update` / `microservice_patch` tüm operasyon tiplerinde çalışır.** Eski
611
+ "`operation: 'get'` tipli kayıtta HER ZAMAN 500 (`Application cannot be tracked`)" kuralı P-02
612
+ regresyonuydu; 2026-08-16'da düzeltildi, 2026-09-01 İK koşusunda get ucunda
613
+ `microservice_patch` ile canlı doğrulandı (M-9). Get-tipi bir microservice'i değiştirmek için
614
+ artık delete+create gerekmez. `entity_patch`, `workflow_patch`, `row_update` sağlamdır.
615
+ - **`microservice_patch` tam gövde geri yazar: okunan kayıtta olmayan alan SİLİNİR.** 09-04
616
+ koşusunda gidiş-dönüşte `description` düştü ve MS-014 patch'i bloke etti (Y-16). Gateway artık
617
+ açıklamasız bir patch gövdesini hiç göndermez; hata mesajı düzeltmeyi verir
618
+ (`{ "op": "add", "path": "/description", "value": "…" }`). Patch yanıtındaki `silentlyDropped`
619
+ alanı, gönderilen ama kaydedilmeyen alanları listeler — boş değilse platform bir alanı yutmuş
620
+ demektir.
@@ -7,6 +7,12 @@ kestirmeden reddetmek (auth guard), (2) isteğin devamında kullanılacak **değ
7
7
  istenen her uca/sayfaya bağlanır. Buradaki kurallar backend kodundan ve qm projesinin canlı
8
8
  kayıtlarından doğrulandı.
9
9
 
10
+ > **Middleware `microservice_debug` ağacında görünmez.** `executionItems` yalnız ucun kendi
11
+ > adımlarını izler; middleware ucun ÖNÜNDE ayrı bir workflow olarak koşar. Gateway bu boşluğu
12
+ > yanıta `middlewares` alanı ekleyerek kapatır (uca bağlı middleware workflow'ları, adım
13
+ > özetiyle) — kapı 200 dönerken bile açık kalmış olabilir, adımları oradan oku (ölçüm: 09-04
14
+ > koşusu Y-04, Y-03).
15
+
10
16
  **Önce "gerekli mi?" sorusu.** Middleware kurulum listesinin zorunlu maddesi değildir: oturum
11
17
  kavramı olmayan bir üründe auth middleware'i, ayar tablosu olmayan bir üründe setter gereksizdir
12
18
  (karar tablosu: `platform_guide('getting-started')`). Buna karşılık **kimlik kurduysan eksiksiz
@@ -249,11 +255,35 @@ qm kanıtı: `sy-users-get-my-profile` (`id eq user.id`), `qm-document-revision-
249
255
  değil, middleware değişkenidir. (Sayfalardaki sabit `patient_id` benzeri geçici çözümlerin
250
256
  kalıcı hâli de bu.)
251
257
 
258
+ ## Tarif 3c — Agregasyon (query kaynaklı) ucun kapsaması: sorguyu WORKFLOW'dan çağır
259
+
260
+ Tarif 3 **table kaynaklı** uçlar içindir; expression filtresi orada vardır. Bir KPI/rapor ucu
261
+ ham SQL istediği için query kaynaklıdır ve o yolda expression yoktur: kayıtlı sorgunun
262
+ parametreleri **yalnız çağrıdan** (body → query-string → header) bağlanır, `user` oraya hiç
263
+ girmez. Kapsamı gövdeye koyarsan çağıran başkasının kapsamını yazabilir — 2026-09-07'de ölçüldü:
264
+ middleware `OWNER-A` kimliğini kurmuşken gövdeye `OWNER-B` yazan çağrı `OWNER-B`'nin verisini
265
+ döndürdü.
266
+
267
+ Kapsam bir katman yukarıda kurulur:
268
+
269
+ 1. Sorguyu kapsam parametresiyle kaydet (`… WHERE company_id = @company_id`).
270
+ 2. Bir `type: 'workflow'` akışı aç: start → **query adımı** → response. Adımın bağlaması
271
+ `rule.expression`'da: `{ company_id: user.company_id, from: input.from }` — kapsam
272
+ middleware'den, kalan filtreler gövdeden.
273
+ 3. Ucu `source: 'workflow'` + `middlewareIds` dolu olarak kur.
274
+
275
+ Ölçüm: gövdeye kasten yanlış `company_id` yazıldığında yanıt değişmedi; `middlewareIds` boş
276
+ bırakıldığında akış `500 JavaScriptException: user is not defined` verdi. Reçetenin tamamı
277
+ `platform_guide('microservice')` §"Kimlik kapsaması × uç kaynağı", bağlamanın yazım tuzağı
278
+ `platform_guide('workflow')` §"Query adımı".
279
+
252
280
  ## Tuzaklar
253
281
 
254
282
  - Expression'daki değişken adı, bağlı middleware'lerden birinin kurduğu adla **birebir**
255
283
  eşleşmeli — eşleşmezse runtime'da `user is not defined` 500'ü. Gateway MS-011 uyarır:
256
- `user.` okuyan expression varken `middlewareIds` boşsa warn alırsın.
284
+ `user.` okuyan expression varken `middlewareIds` boşsa warn alırsın. **Aynı 500'ü workflow
285
+ kaynaklı uçta gateway GÖREMEZ** (uçtan görünen tek şey `workflowId`'dir): `user.*` okuyan bir
286
+ akışı bağladığın uçta `middlewareIds`'i doldurmayı sen garanti edeceksin.
257
287
  - `set-user` adı sabittir; ikinci kullanıcı değişkeni gerekiyorsa `variable` step'i kullan.
258
288
  - Middleware'i `workflow_preview` ile test etmek sınırlıdır (HTTP bağlamı preview'da yoktur);
259
289
  gerçek doğrulama, middleware'i bağladığın ucu `microservice_execute` ile çağırmaktır —
@@ -265,6 +295,17 @@ kalıcı hâli de bu.)
265
295
  token'ını KOYMAZ, istek tamamen verdiğin kimlikle gider. Tarayıcı yolunu test etmek için
266
296
  token'ı cookie header'ıyla da geçebilirsin (`headers: { Cookie: '<ad>=<token>' }`).
267
297
 
298
+ ## Ön koşul: uygulamanın kullanıcısı olmalı
299
+
300
+ Middleware bağlamadan önce `users` tablosunda satır olduğundan emin ol — `row_list('users')` tek
301
+ çağrı. Yeni projede o tablo **boş** gelir ve Studio operatörü uygulamanın kullanıcısı değildir;
302
+ kapı bağlanınca her istek 401 döner ve bu ancak sayfa boş açılınca fark edilir (09-02 ve 09-04
303
+ koşularında ikisinde de yaşandı).
304
+
305
+ İlk kullanıcıyı açmanın desteklenen tek yolu bootstrap desenidir:
306
+ `platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?". Kısa yol arama — bakım uçları
307
+ (`Sync*`) ürün yüzeyinin parçası değildir ve gateway onları araç olarak yayınlamaz.
308
+
268
309
  ## Korumalı bir ucu TEST ederken: token ÇEREZDEN okunur
269
310
 
270
311
  `microservice_execute` ile middleware'li bir ucu denerken `Authorization: Bearer <token>` vermek
@@ -279,3 +320,50 @@ headers: { "Cookie": "ANKET_Token=<token>" } → 200
279
320
  Token'ı önce giriş ucundan al (`microservice_execute` ile `giris`), sonra `Cookie` başlığıyla
280
321
  korumalı ucu çağır. Çerez adı middleware'in `cookie.<ad>` ifadesindedir; `workflow_get` ile oku,
281
322
  tahmin etme. **401 alman middleware'in çalıştığının kanıtıdır** — uç bozuk değildir.
323
+
324
+ ## Korumalı SAYFAYI görsel doğrulama — jeton bağlama hiç girmeden
325
+
326
+ Giriş ucunun yanıtındaki jeton gateway tarafından maskelenir (`access_token: "***"`), yani jetonu
327
+ okuyup elle taşıyamazsın — ve taşımaya çalışmak uygulamanın yanıt sözleşmesini geçici olarak
328
+ bozmak demektir (ölçüm 2026-09-04, benchmark 4. koşu). Doğru yol dört adımdır:
329
+
330
+ 1. `microservice_execute` ile giriş ucunu çağır. Yanıtta jeton `***`, ama `tokenRefs` alanında
331
+ maskelenen her JWT için kısa ömürlü bir referans döner: `{ "body.access_token": "tok_…" }`.
332
+ 2. `page_screenshot` (ya da `page_runtime_get`) çağrısına `asUser: "tok_…"` ver. Gateway referansı
333
+ kendi içinde çözer; jeton ne yanıta ne günlüğe girer. Referans 15 dakika geçerlidir, süresi
334
+ dolarsa çağrı **hata verir** — sessizce kimliksiz isteğe düşmez.
335
+ 3. Uygulamanın kendi oturum çerezi ayrı bir addaysa (`cookie.CORE_Token` gibi) onu
336
+ `cookies: { "CORE_Token": "…" }` ile ekle; `asUser` her zaman `NG_AccessToken` adıyla taşınır.
337
+ 4. Kapının gerçekten kapalı olduğunu `page_runtime_get(anonymous: true)` ile kanıtla: beklenen
338
+ sonuç `middlewareBlocked: true` + 401'dir.
339
+
340
+
341
+ ## Korumalı ucu teşhis etmek: `asUser`
342
+
343
+ Bir uca `middlewareIds` bağladığın anda `microservice_execute`, `microservice_preview` ve
344
+ `microservice_debug` gateway'in **servis jetonuyla** gider ve o jeton uygulamanın kendi
345
+ `users` tablosuna çözülmez — üçü de 401 döner. Ölçüldü 2026-09-05: agregasyonlar korumaya
346
+ alındıktan sonra kuru koşu ve adım adım teşhis araçlarının ikisi de kullanılamaz hâle geldi.
347
+
348
+ Üçü de artık `asUser` (ve gerekiyorsa `cookies`) alır ve jetonu **hem** `Authorization` başlığında
349
+ **hem** `NG_AccessToken` çerezinde taşır — middleware jetonu çerezden okuduğu için tek kanal
350
+ yetmiyordu:
351
+
352
+ ```jsonc
353
+ // 1) uygulamanın kendi giriş ucunu anonim çağır, jeton referansını al
354
+ { "tool": "microservice_execute", "application": "<app>", "microservice": "sign-in",
355
+ "anonymous": true, "body": { "email": "…", "password": "…" } }
356
+ // → tokenRefs: { "body.access_token": "tok_…" } (jeton hiç görünmez, 15 dk geçerli)
357
+
358
+ // 2) korumalı ucu O KİMLİKLE çalıştır
359
+ { "tool": "microservice_debug", "application": "<app>", "microservice": "funnel",
360
+ "asUser": "tok_…", "body": { … } }
361
+ ```
362
+
363
+ Uygulaman jetonu kendi çerez adından okuyorsa (`CORE_Token` gibi) `cookies: { CORE_Token: "<jwt>" }`
364
+ ver. `anonymous: true` ile `asUser` birlikte verilemez — biri kimliksiz isteği, diğeri belirli bir
365
+ üyenin kimliğiyle atılan isteği ister.
366
+
367
+ `microservice_debug` yanıtındaki `middlewareNote` artık gerçeği söyler: middleware koştu ve
368
+ geçirdi mi, yoksa isteği kesti mi. Not eskiden ucun LİSTE kaydına bakıyordu ve liste DTO'su
369
+ `middlewareIds` taşımadığı için bağlı bir uçta bile "bağlı değil" diyordu.
@@ -25,6 +25,29 @@ yeniden yazımlarda.
25
25
  `view:'part'` ile doğrulayarak yaz.
26
26
  - Silme yerine `replace` tercih et; `remove` kullanacaksan önce o path'i `part` ile görüntüle.
27
27
  - Yanıttaki outline özeti değişikliğin uygulandığı hâli gösterir — teyit için oku.
28
+ - **Kullanıcı fonksiyonunda patch aracı yok:** `function_update` tam gövde yazar ama vermediğin
29
+ alanları mevcut kayıttan doldurur — yalnız `body` göndermen yeter (fonksiyonun tek büyük alanı
30
+ gövdedir). Adı değiştirmeden önce `dependency_graph` (`module:'ui-function'`) ile çağıranları
31
+ bul: `{{ }}`/`code` ifadelerindeki `Functions.<eskiAd>(` çağrıları metinseldir ve kırılır.
32
+
33
+ ## Değiştirmeden önce geçmişe bak
34
+
35
+ Platform her tanım yazmasını append-only bir deftere düşürür. Bir kaydı değiştirmeden önce son
36
+ revizyonunu okumak, başka birinin işinin üstüne yazmayı önler:
37
+
38
+ ```
39
+ revision_list {projectId, module:'page', recordId, pageLength: 3}
40
+ → son satırda revisionNo, operation, actor ve source
41
+ revision_diff {projectId, module:'page', recordId}
42
+ → son iki sürüm arasındaki alan bazlı fark + summaryText
43
+ ```
44
+
45
+ `source` işin nereden geldiğini söyler: `mcp` bu gateway'in kendi izidir, `studio` bir insanın
46
+ Studio'da yaptığıdır, `merge` / `deployment` / `template` / `revert` ise taşıma yollarıdır.
47
+ Son revizyon `studio` ise ve dakikalar önceyse, üstüne yazmadan önce kullanıcıya sor.
48
+
49
+ Gövdenin tamamı gerekirse `revision_get {projectId, id}` — ama büyük sayfalarda MB'lar tutar ve
50
+ yanıt kırpılabilir; ne değiştiğini anlamak için `revision_diff` yeter.
28
51
 
29
52
  ## Uyum kuralları
30
53