@appaflytech/zeyrek-ai-mcp 0.0.28 → 0.0.30

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 (201) hide show
  1. package/.env.example +15 -0
  2. package/README.md +5 -5
  3. package/dist/config.js +37 -0
  4. package/dist/index.js +2 -0
  5. package/dist/knowledge/access.js +802 -0
  6. package/dist/knowledge/backendConstraints.js +18 -1
  7. package/dist/knowledge/case.js +41 -0
  8. package/dist/knowledge/componentCatalog.js +494 -34
  9. package/dist/knowledge/componentEvents.js +132 -7
  10. package/dist/knowledge/connectDataBind.js +819 -0
  11. package/dist/knowledge/connectDataDefinition.js +341 -0
  12. package/dist/knowledge/connectDataExecute.js +93 -0
  13. package/dist/knowledge/connectDataRules.js +128 -0
  14. package/dist/knowledge/decision.js +121 -0
  15. package/dist/knowledge/expressionPrelude.js +59 -0
  16. package/dist/knowledge/layoutSummary.js +140 -2
  17. package/dist/knowledge/microserviceParameters.js +58 -0
  18. package/dist/knowledge/mining.js +94 -0
  19. package/dist/knowledge/mobileComponentCatalog.js +859 -0
  20. package/dist/knowledge/mobileComponentEvents.js +239 -0
  21. package/dist/knowledge/normalize.js +236 -67
  22. package/dist/knowledge/outlines.js +36 -0
  23. package/dist/knowledge/platform/lint.js +385 -0
  24. package/dist/knowledge/platform/mobile.js +142 -0
  25. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  26. package/dist/knowledge/platform/mobileRules.js +231 -0
  27. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  28. package/dist/knowledge/platform/mobileTabs.js +108 -0
  29. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  30. package/dist/knowledge/platform/profile.js +25 -0
  31. package/dist/knowledge/platform/resolve.js +122 -0
  32. package/dist/knowledge/platform/web.js +39 -0
  33. package/dist/knowledge/policy.js +18 -4
  34. package/dist/knowledge/process.js +510 -0
  35. package/dist/knowledge/reservedTables.js +79 -0
  36. package/dist/knowledge/rowFilter.js +43 -0
  37. package/dist/knowledge/rules.js +319 -21
  38. package/dist/knowledge/schemaGuard.js +11 -0
  39. package/dist/knowledge/styleResolve.js +50 -4
  40. package/dist/knowledge/styleResolveNative.js +527 -0
  41. package/dist/knowledge/themeResolve.js +3 -3
  42. package/dist/observability/trace.js +6 -3
  43. package/dist/platform/argumentAliases.js +128 -0
  44. package/dist/platform/bodyFile.js +103 -0
  45. package/dist/platform/capture/actions.js +333 -0
  46. package/dist/platform/capture/browser.js +1 -0
  47. package/dist/platform/{screenshot.js → capture/capture.js} +105 -519
  48. package/dist/platform/capture/console-log.js +122 -0
  49. package/dist/platform/capture/index.js +11 -0
  50. package/dist/platform/capture/readiness.js +103 -0
  51. package/dist/platform/capture/summary.js +77 -0
  52. package/dist/platform/capture/target.js +5 -0
  53. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  54. package/dist/platform/capture/targets/studio-web.js +5 -0
  55. package/dist/platform/diagnostics.js +10 -4
  56. package/dist/platform/domSummary.js +1 -1
  57. package/dist/platform/errors.js +162 -10
  58. package/dist/platform/http.js +29 -8
  59. package/dist/platform/impersonation.js +45 -0
  60. package/dist/platform/jsonArray.js +24 -0
  61. package/dist/platform/responses.js +39 -5
  62. package/dist/platform/schemaReadMemory.js +60 -0
  63. package/dist/platform/secrets.js +17 -1
  64. package/dist/platform/strictArguments.js +68 -12
  65. package/dist/platform/tokenVault.js +187 -4
  66. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  67. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  68. package/dist/resources/examples/bpm-satinalma.json +376 -0
  69. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  70. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  71. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  72. package/dist/resources/examples/connect-data-query-params.json +50 -0
  73. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  74. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  75. package/dist/resources/examples/mobile-app-shell-drawer.json +243 -0
  76. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  77. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  78. package/dist/resources/examples/mobile-form-screen.json +365 -0
  79. package/dist/resources/examples/mobile-list-detail-form.json +672 -0
  80. package/dist/resources/examples/page-detail-profile.json +2 -1
  81. package/dist/resources/examples/page-dynamic-form.json +3 -2
  82. package/dist/resources/examples/page-flex-sections.json +1 -1
  83. package/dist/resources/examples/page-html-embed.json +1 -1
  84. package/dist/resources/examples/workflow-basic.json +8 -2
  85. package/dist/resources/guides/access.md +796 -0
  86. package/dist/resources/guides/bpm.md +686 -0
  87. package/dist/resources/guides/case.md +153 -0
  88. package/dist/resources/guides/component.md +91 -4
  89. package/dist/resources/guides/decision.md +185 -0
  90. package/dist/resources/guides/entity.md +50 -8
  91. package/dist/resources/guides/expressions.md +14 -5
  92. package/dist/resources/guides/functions.md +1 -1
  93. package/dist/resources/guides/getting-started.md +38 -1
  94. package/dist/resources/guides/layout.md +11 -7
  95. package/dist/resources/guides/microservice.md +81 -2
  96. package/dist/resources/guides/middleware.md +44 -2
  97. package/dist/resources/guides/mobile-layout.md +193 -0
  98. package/dist/resources/guides/mobile.md +495 -0
  99. package/dist/resources/guides/page.md +454 -75
  100. package/dist/resources/guides/theme.md +8 -2
  101. package/dist/resources/guides/translation.md +1 -1
  102. package/dist/resources/guides/workflow.md +58 -1
  103. package/dist/resources/read.js +41 -0
  104. package/dist/resources/rules.js +11 -5
  105. package/dist/resources/schema-fingerprint.json +7 -5
  106. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  107. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  108. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  109. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  110. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  111. package/dist/resources/schemas/AppSessionDto.json +53 -0
  112. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  113. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  114. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  115. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  116. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  117. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  118. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  119. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  120. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  121. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  122. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  123. package/dist/resources/schemas/CreateWorkflowCommand.json +143 -1
  124. package/dist/resources/schemas/DataJobDto.json +6 -1
  125. package/dist/resources/schemas/DecisionDto.json +302 -0
  126. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  127. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  128. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  129. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +21 -1
  130. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  131. package/dist/resources/schemas/OrganizationProjectDto.json +335 -0
  132. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  133. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  134. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  135. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  136. package/dist/resources/schemas/PreviewWorkflowCommand.json +22 -1
  137. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  138. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  139. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  140. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  141. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  142. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  143. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  144. package/dist/resources/schemas/StepDto.json +22 -1
  145. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  146. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  147. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  148. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  149. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  150. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  151. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  152. package/dist/resources/schemas/UpdateWorkflowCommand.json +143 -1
  153. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  154. package/dist/server.js +27 -1
  155. package/dist/tools/access.js +369 -2
  156. package/dist/tools/accessOperations.js +135 -0
  157. package/dist/tools/accessPolicies.js +170 -0
  158. package/dist/tools/accessRuntime.js +203 -0
  159. package/dist/tools/applications.js +60 -3
  160. package/dist/tools/bpm.js +343 -0
  161. package/dist/tools/bpmGovernance.js +266 -0
  162. package/dist/tools/bpmOperations.js +230 -0
  163. package/dist/tools/bpmReporting.js +154 -0
  164. package/dist/tools/bpmRouting.js +150 -0
  165. package/dist/tools/bpmRuntime.js +396 -0
  166. package/dist/tools/case.js +178 -0
  167. package/dist/tools/caseRuntime.js +89 -0
  168. package/dist/tools/common.js +7 -0
  169. package/dist/tools/components.js +118 -14
  170. package/dist/tools/connectData.js +449 -0
  171. package/dist/tools/connectors.js +193 -0
  172. package/dist/tools/datasources.js +3 -3
  173. package/dist/tools/decisions.js +206 -0
  174. package/dist/tools/dependencies.js +1 -1
  175. package/dist/tools/deployments.js +2 -2
  176. package/dist/tools/designSchemaPatch.js +34 -9
  177. package/dist/tools/dtos.js +36 -5
  178. package/dist/tools/entities.js +79 -12
  179. package/dist/tools/externalComponents.js +26 -5
  180. package/dist/tools/flows.js +122 -14
  181. package/dist/tools/folders.js +1 -1
  182. package/dist/tools/imports.js +5 -2
  183. package/dist/tools/layouts.js +50 -17
  184. package/dist/tools/merges.js +1 -1
  185. package/dist/tools/microservices.js +77 -25
  186. package/dist/tools/mobile.js +221 -0
  187. package/dist/tools/modules.js +48 -0
  188. package/dist/tools/organizationStructure.js +137 -0
  189. package/dist/tools/pages.js +465 -98
  190. package/dist/tools/platformInfo.js +27 -8
  191. package/dist/tools/profileWrite.js +92 -0
  192. package/dist/tools/projects.js +71 -9
  193. package/dist/tools/rows.js +69 -13
  194. package/dist/tools/schemaOps.js +61 -1
  195. package/dist/tools/screenWrite.js +37 -8
  196. package/dist/tools/screens.js +95 -5
  197. package/dist/tools/templates.js +26 -1
  198. package/dist/tools/translations.js +1 -1
  199. package/dist/tools/variables.js +11 -5
  200. package/dist/tools/workflows.js +10 -5
  201. package/package.json +2 -2
@@ -7,6 +7,11 @@ export class PlatformApiError extends Error {
7
7
  * için sayıya ihtiyaç duyar, çağıran hangi satırı düzelteceğini ancak öyle bilir.
8
8
  */
9
9
  rowIndex;
10
+ /**
11
+ * Platformun ham `message`'ı (yalnız 403'te doldurulur). `message` gateway ipucunu da taşır ve ipucunda
12
+ * izin slug'ları geçer ('entity.reveal grant'i'); `accessDecision` izni ondan okursa yanlış izin yazar.
13
+ */
14
+ apiMessage;
10
15
  constructor(message, status, code) {
11
16
  super(message);
12
17
  this.status = status;
@@ -32,9 +37,12 @@ function collectValidationMessages(payload) {
32
37
  continue;
33
38
  const record = entry;
34
39
  const isPageSchemaShape = 'rule' in record || 'componentId' in record || 'suggestions' in record;
40
+ const isDefinitionFindingShape = 'path' in record && 'message' in record && 'code' in record;
35
41
  messages.push(isPageSchemaShape
36
42
  ? formatPageSchemaError(record)
37
- : `${record.key ?? '?'}: ${record.value ?? ''}`);
43
+ : isDefinitionFindingShape
44
+ ? `${String(record.path)} [${String(record.code)}]: ${String(record.message)}`
45
+ : `${record.key ?? '?'}: ${record.value ?? ''}`);
38
46
  }
39
47
  }
40
48
  else if (payload.errors && typeof payload.errors === 'object') {
@@ -106,6 +114,39 @@ function importHint(code, path) {
106
114
  }
107
115
  return '';
108
116
  }
117
+ function processHint(code, path) {
118
+ if (code === 'DefinitionInvalid' && /\/bpm\/definitions\/[^/]+\/override(\/|$)/.test(path))
119
+ return " Uyarlama platform doğrulamasından geçmedi; yukarıdaki path [kod] satırlarına bakın. RegionLocked/ParamNotEditable: yol uyarlamaya kapalı — process_override_get yanıtındaki editableScopes'a göre kurun (bölge çocuğu '/nodes[id=<bölge>]/nodes[id=<adım>]/<alan>'); RoleUnknown/WorkflowUnknown: referans bu ortamda yok. Kurallar platform_guide('bpm') §Uyarlama.";
120
+ if (code === 'TaskAlreadyClaimed')
121
+ return ' Görev başkası tarafından üstlenildi ya da siz okuduktan sonra değişti. process_task_get ile yeniden okuyup güncel version değeriyle tekrar deneyin; görev başkasındaysa eylem sizin değildir.';
122
+ if (code === 'TaskStateTransitionNotAllowed')
123
+ return " Görevin durumu bu eylemi almıyor (ör. waiting-info iken approve, tamamlanmış görevde claim). process_task_get'teki state ve actions listesine bakın.";
124
+ if (code === 'TaskCommentRequired')
125
+ return " Bu karar yorum ister (düğümün commentRequired kuralı). Aynı çağrıyı 'comment' ile tekrarlayın.";
126
+ if (code === 'UndoWindowClosed')
127
+ return " Geri alma penceresi kapandı ya da karardan sonra başka bir insan görevi açıldı; geri alma yerine process_instance_update ya da yeni karar gerekir.";
128
+ if (code === 'InstanceStateTransitionNotAllowed')
129
+ return " Örnek bu durumda bu müdahaleyi almaz (ör. tamamlanmış örneği askıya alma). process_instance_get ile state'i okuyun.";
130
+ if (code === 'WithdrawNotAllowed')
131
+ return " Tanımın withdrawPolicy'si geri çekmeye izin vermiyor ya da ilk karar verilmiş.";
132
+ if (code === 'DefinitionInvalid')
133
+ return " Tanım platform doğrulamasından geçmedi; yukarıdaki path [kod] satırlarını düzeltip process_definition_preview ile yeniden doğrulayın.";
134
+ if (code === 'OpenTasksExist')
135
+ return " Kullanıcının ya da birimin açık süreç görevi var. process_handover_preview ile kalemleri görün, process_handover_create ile devredin; sonra işlemi tekrarlayın.";
136
+ if (code === 'HandoverIncomplete')
137
+ return " Kararı olmayan görev grupları mesajda. Her assigned-tasks / candidate-tasks kalemi için karar verin ya da force:true ile geri dönüşü kabul edin: assigned-tasks yöneticiye (yoksa admin-queue), candidate-tasks gruba (aday kalmazsa admin-queue).";
138
+ if (code === 'DelegationOverlap')
139
+ return " Aynı kişinin bu aralık ve süreç filtresiyle çakışan vekaleti var. process_delegation_list ile görüp bitirin ya da aralığı değiştirin.";
140
+ if (code === 'DelegationChainNotAllowed')
141
+ return " Vekalet zincirlenemez: ya vekil aynı aralıkta kendi görevlerini devretmiş ya da vekaleti veren kişi o aralıkta başkasının vekili. Başka vekil ya da aralık seçin.";
142
+ if (code === 'PublishApprovalRequired')
143
+ return " Tanım dört göz yayın politikasında (publishPolicy: four-eyes) ve ortamda process-publish süreci kurulu. Aynı çağrıyı process_definition_publish { reason } ile tekrarlayın: yayın isteği açılır, onaylanınca sürüm aktif olur.";
144
+ if (code === 'ScenarioFailed')
145
+ return " Tanım requireGreenScenarios: true ve adı geçen senaryo bu taslağın sağlamasında yeşil koşmadı. process_scenario_execute { definitionId } ile koşun, sapmaları giderin (ya da senaryoyu process_scenario_update ile güncelleyin), sonra yeniden yayınlayın.";
146
+ if (code === 'BatchOperationNotUndoable')
147
+ return " Yalnız tamamlanmış reassign / extend-due işlemleri bir kez geri alınır; start ve cancel geri alınamaz.";
148
+ return '';
149
+ }
109
150
  function pageSchemaHint(code, path) {
110
151
  if (code === 'PageSchemaVersionConflictException') {
111
152
  return ' Sayfa siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı. page_schema_get ile yeniden okuyup planı tazeleyin, yeni `version` değerini `baseVersion` olarak gönderin.';
@@ -142,12 +183,20 @@ function bulkRowHint(code) {
142
183
  function versionConflictHint(code, path) {
143
184
  if (code !== 'AggregateVersionConflictException')
144
185
  return '';
145
- const readTool = /\/entities(\/|$)/.test(path) ? 'entity_get'
146
- : /\/workflows(\/|$)/.test(path) ? 'workflow_get'
147
- : /\/flows(\/|$)/.test(path) ? 'flow_get'
148
- : /\/layouts(\/|$)/.test(path) ? 'layout_get'
149
- : /\/microservices(\/|$)/.test(path) ? 'microservice_get'
150
- : 'ilgili *_get';
186
+ if (/\/bpm\/tasks(\/|$)/.test(path)) {
187
+ return ' Görev siz okuduktan sonra değişti (başka biri üstlendi ya da karar verdi) ve eylem UYGULANMADI. process_task_get ile yeniden okuyun; görev hâlâ size açıksa güncel `version` değeriyle eylemi tekrarlayın, değilse eylem sizin değildir.';
188
+ }
189
+ if (/\/bpm\/definitions\/[^/]+\/override(\/|$)/.test(path)) {
190
+ return ' Uyarlama ya da aktif sürüm siz okuduktan sonra değişti (başka bir yönetici kaydetti ya da geliştirici yeni sürüm yayınladı) ve HİÇBİR op yazılmadı. process_override_get ile yeniden okuyun; conflictState conflicted ise patch yeni sürüme göre kurulmalı. process_override_update aynı çağrıda güncel sürümü okur — patch güncel uyarlamaya uyuyorsa çağrıyı tekrarlayın.';
191
+ }
192
+ const readTool = /\/bpm\/definitions(\/|$)/.test(path) ? 'process_definition_get'
193
+ : /\/bpm\/instances(\/|$)/.test(path) ? 'process_instance_get'
194
+ : /\/entities(\/|$)/.test(path) ? 'entity_get'
195
+ : /\/workflows(\/|$)/.test(path) ? 'workflow_get'
196
+ : /\/flows(\/|$)/.test(path) ? 'flow_get'
197
+ : /\/layouts(\/|$)/.test(path) ? 'layout_get'
198
+ : /\/microservices(\/|$)/.test(path) ? 'microservice_get'
199
+ : 'ilgili *_get';
151
200
  return ` Kayıt siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı (mesajdaki beklenen/mevcut sürüm). ${readTool} ile yeniden okuyup patch'i güncel hâle göre kurun, yeni \`version\` değerini \`baseVersion\` olarak gönderin — aynı ops'u eski sürümle tekrar göndermek aradaki değişikliği ezer.`;
152
201
  }
153
202
  /**
@@ -177,6 +226,69 @@ function diagnoseKnownFailure(status, message, code, context) {
177
226
  }
178
227
  return '';
179
228
  }
229
+ /**
230
+ * Erişim politikası ve alan koruması hataları `InvalidParameterException` kodu altında gelir ve
231
+ * ayırt edici anahtar MESAJDADIR (`AccessScopeNotSupported: entity.create`); koşul ve koruma
232
+ * hatalarının bir kısmı ise kendi exception adını `code`'da taşır. İkisi de burada tek sözlükle
233
+ * bir sonraki adıma çevrilir (rol yönetim şeması g-21).
234
+ */
235
+ const accessErrorHints = [
236
+ ['ConnectDataDefinitionInvalid', " Bağlantı tanımı platform şemasına uymuyor (bilinmeyen anahtar, bozuk enum ya da eksik alan) — mesajdaki yolu düzeltin; kural adları connect-data-* (PAGE-035 · CD-004). Tanım şekli: platform_guide('page') §Kural 1c-3."],
237
+ ['ConnectDataPropNotConnected', " Bu prop veri kaynağına bağlı değil — önce connect_data_bind (ya da page_schema_patch set_connect_data) ile bağlayın; bir prop'un bağlantısını connect_data_get okur."],
238
+ ['ConnectDataFilterNotAllowed', " Filtre, arama ya da sıralama bu bağlantıda izinli değil: alan tanımın behavior.filterable/searchable listesinde olmalı ve işleç connect_data_describe'daki operators içinde; korumalı alanda yalnız eq · neq · in · notin, korumalı alan sıralanmaz; tanım filtresindeki $parametre params ile verilmeli (CD-002)."],
239
+ ['ConnectDataFieldNotEditable', " Alan tanımda seçili ama bağlantı ucundan yazılamaz: edit.editable listesinde değil ya da dosya alanı (dosya bu uçtan hiç yazılmaz). Yazılacaksa connect_data_bind { edit: { editable: [..., '<alan>'] } }; dosyayı file_upload ile yükleyip satırı row_update ile güncelleyin."],
240
+ ['ConnectDataFieldNotSelected', " İstenen alan tanımın seçtiği alanlardan değil (fields/expand), lookup ilişki alanında değil ya da yazılan alan edit.editable listesinde değil (expand yolu hiç yazılamaz) — connect_data_get ile tanıma bakın; gerekirse connect_data_bind ile alanı ekleyin ya da edit.editable'a koyun."],
241
+ ['ConnectDataWriteDisabled', " Tanımda bu yazma eylemi kapalı (behavior.edit) ya da edit.bulk kapalıyken birden çok satır/kimlik gönderildi — politika izin verse bile motor reddeder. Açmak için connect_data_bind { edit: { <op>: true } } (toplu için bulk: true)."],
242
+ ['ConnectDataWriteModeNotAllowed', " Tanımın edit.writeMode'u atomic: satırların hepsi ya da hiçbiri yazılır. Kısmi yazma (satır başına errors[]) için connect_data_bind { edit: { writeMode: 'partial' } }, ya da isteği mode göndermeden atın."],
243
+ ['yet: partial.', " Kısmi yazma (mode partial) yalnız built-in veritabanındaki entity'de çalışır — savepoint harici datasource bağlantısını kapsamaz. Bu kaynakta atomic yazın (mode göndermeyin, tanımda writeMode: 'atomic')."],
244
+ ['yet: create.', " Bu kaynak türü YAZMAZ: create/update/delete yalnız entity kaynağında çalışır (query · view · workflow · microservice okur). Yazma gereken ekranı entity kaynağına bağlayın, ya da yazmayı bir flow adımına bırakın."],
245
+ ['yet: update.', " Bu kaynak türü YAZMAZ: create/update/delete yalnız entity kaynağında çalışır (query · view · workflow · microservice okur). Yazma gereken ekranı entity kaynağına bağlayın, ya da yazmayı bir flow adımına bırakın."],
246
+ ['yet: delete.', " Bu kaynak türü YAZMAZ: create/update/delete yalnız entity kaynağında çalışır (query · view · workflow · microservice okur). Yazma gereken ekranı entity kaynağına bağlayın, ya da yazmayı bir flow adımına bırakın."],
247
+ ['yet: lookup.', " Lookup yalnız entity kaynağında çalışır: ilişki hücresinin hedef tablosu entity'dir. Query/view/workflow/microservice kaynağında ilişki seçimi yoktur."],
248
+ ['yet: datasource.', " Harici datasource (REST) kaynağı motorda YOK (b-33 kapsam dışı): platformda kayıtlı yürütücüsü yoktur. entity · query · view · workflow · microservice türlerinden birini seçin."],
249
+ ['ConnectDataSourceUnsupported', " Kaynak bağlanabilir değil. Başlıca nedenler: uca middleware bağlı, yazan tablo/sistem ucu, dönüş şeması yok (workflow'a dizi dönen çıktı DTO'su, query'ye çalıştırılmış örnek gerekir), sorgu salt-okunur tek SELECT değil ya da kaynak zaten connectData microservice'i (tanımı doğrudan bağlayın, connect_data_get ile okuyun). Mesajın sonundaki değer bir op adıysa (create/update/delete/lookup) neden kaynağın bağlanabilirliği değil o işlemi desteklememesidir. connect_data_describe kaynağın ne yapabildiğini söyler."],
250
+ ['ConnectDataMicroserviceInUse', " Bu connectData microservice'i başka bir prop'ta, başka bir sayfada ya da bir flow'da kullanılıyor: connect_data_detach tanımı geri koyup microservice'i silmek ister. Kullanımları dependency_graph ile bulup kaldırın, sonra yeniden deneyin."],
251
+ ['ConnectDataRelationTargetNotFound', " İlişki alanına yazılan hedef kimlik hedef tabloda yok ya da bu kullanıcı onu göremiyor — yanıtın field'ı düşen alanı söyler. Var olan bir kimliği alanın lookup'ıyla seçin (connect_data_execute op: 'lookup', field: '<alan>'); m2m listesinde her kimlik ayrı denetlenir."],
252
+ ['ConnectDataSourceFailed', " Kaynak (query/view/workflow/microservice) çalışırken düştü — hata kaynağın kendisinden gelir: microservice_execute / query_preview / workflow_preview ile kaynağı tek başına koşturup gerçek nedeni görün."],
253
+ ['ProtectionOperatorNotAllowed', " Korumalı alanda yalnız eq · neq · in · notin · isnull · isnotnull kullanılır; şifreli modda eşitlik için alanın protection.exactSearch'ü açık olmalı (PROT-001). Kısmi arama için türetilmiş alan — platform_guide('access') §'Arama ve sıralama'."],
254
+ ['ProtectionSortNotAllowed', " Korumalı alana göre sıralanamaz (PROT-002); başka bir alana ya da türetilmiş alana göre sıralayın."],
255
+ ['ProtectionFieldTypeNotSupported', " Koruma yalnız düz METİN alana konur — ilişki, sayı, çok dilli, birincil anahtar ve identity alanı korunamaz (PROT-003)."],
256
+ ['ProtectionDisplayFieldNotAllowed', " Display alanı korunamaz: ilişkilerde görünen ad olarak her yerde düz dönerdi (PROT-003). Önce başka bir alanı display yapın."],
257
+ ['ProtectionDerived', " Türetilmiş alan derivedFromFieldId + derivationRule ister; slice length > 0, regex pattern derlenmeli (PROT-004)."],
258
+ ['ProtectionChangeNotAllowed', " Var olan alanın korumasını entity_update / entity_patch değiştiremez; dolu satırlar arka plan işiyle dönüştürülür — entity_protection_update (202 + jobId, ilerleme import_status)."],
259
+ ['ProtectionMigrationInProgress', " Bu entity'de koruma işi sürüyor; ilerlemeyi GET /{proje}/DataJobs/{id} ile izleyin. İş hata verdiyse AYNI isteği aynı gövdeyle tekrar göndermek kaldığı yerden devam ettirir."],
260
+ ['ProtectionKeyUnavailable', " Ortamda alan koruması anahtarı yapılandırılmamış — insan işi: docs/ortam.md §Alan koruması anahtarları."],
261
+ ['AccessScopeRequiresOwnerFields', " Entity'de sys_owner_user_id / sys_owner_unit_id yok (ACC-003): owner-fields işi (POST /{proje}/DataJobs/owner-fields/{datasourceId}) ya da scope: 'all' + koşul."],
262
+ ['AccessResourceUnknown', " Anahtar kaynak kataloğunda yok — access_resource_list ile 'key' değerini alın (K-31); katalog entity/sayfa yazıldıkça kendiliğinden eşitlenir."],
263
+ ['AccessConditionInvalid', " Koşul alanı grant'in entity'sinin korumasız, kataloğa girmiş bir alanı olmalı ve değer işlece uymalı (ACC-001/ACC-002) — alanlar: access_resource_list { type: 'field' }."],
264
+ ['AccessScopeNotSupported', " Kapsam yalnız entity.read/update/delete/export/reveal/reassign izinlerinde (ACC-001)."],
265
+ ['AccessFieldAccessNotSupported', " fieldAccess yalnız 'field.read' izninde (ACC-001)."],
266
+ ['AccessDenyFieldAccessNotSupported', " Reddet grant'i alan erişimi taşımaz; kapsam ve koşul taşıyabilir, o zaman yalnız satır süzer (ACC-001) — koşulsuz scope: 'all' hariç, o kapsamsız gibi keser (ACC-042)."],
267
+ ['AccessOverrideRequiresUserWithEndDate', " overridesDeny yalnız subjectType 'user' ve dolu endsAt ile yazılır (ACC-009)."],
268
+ ['OrganizationPositionSeatOccupied', " Tek koltuklu pozisyonda aynı dönemde başka bir kalıcı atama var; vekâlet için assignmentType 'acting', kalıcı ikinci kişi için pozisyonu isMultiSeat yapın (TB-96, ORG-003)."],
269
+ ['OrganizationStructureNodeInUse', " Düğüm DOLU. BİRİM için: altında aktif alt birim ya da aktif pozisyon var. POZİSYON için ÜÇ sebepten biri: koltukta oturan kullanıcı, o pozisyona bağlı (idari ya da işlevsel) başka bir aktif pozisyon, ya da pozisyonu managerPositionId olarak gösteren bir birim — mesaj hangisi olduğunu söylemez. Sıra yapraktan köke: user_position_replace ile koltukları boşalt → yöneticiyi gösteren birimi organization_unit_update ile managerPositionId'siz yaz → ast pozisyonları organization_position_deactivate → alt birimler → organization_unit_deactivate. Durumu organization_tree_get (includeInactive: true) ve user_position_list gösterir."],
270
+ ['UserPositionPeriodInvalid', " Atamanın endsAt'i startsAt'ten sonra olmalı (ORG-003)."],
271
+ ['UserPositionPrimaryDuplicate', " Bir kullanıcının en çok bir birincil ataması olur (ORG-003)."],
272
+ ['RevealReasonTooShort', " Gerekçe projenin asgari uzunluğunun altında; project_get → revealReasonMinLength (TB-98, ACC-010). Reddedilen deneme denetim defterine REVEAL-DENIED olarak düşer."],
273
+ ['EnforcementOffRequiresConfirmation', " off'a geçiş korumalı alanları herkese açık yapar ve satır kapsamını kaldırır; bilinçliyseniz confirm: true (TB-99, ACC-007)."],
274
+ ['AccessConditionRequiresScope', " Koşul yalnız kapsamla birlikte yazılır; saf koşul için scope: 'all' (K-11, ACC-001)."],
275
+ ['AccessPermissionUnknown', " İzin slug'ı katalogda yok — access_permission_list (katalog yalnız kompleks projede dolu)."],
276
+ ['AccessPolicySlugDuplicate', ' Politika slug\'ı projede tekildir; access_policy_list ile mevcut politikayı bulun.'],
277
+ ['EntityHasNoOwnerFields', " Entity'de sahiplik alanı (sys_owner_user_id / sys_owner_unit_id) yok, devredilecek bir sahiplik de yok: owner-fields işi (POST /{proje}/DataJobs/owner-fields/{datasourceId}) alanları açar. Basit projede bu alanlar hiç kurulmaz."],
278
+ ['OwnershipFieldNotWritable', " Sahiplik alanı satır güncellemesiyle yazılmaz (ORG-006): row_reassign kullanın, ya da uygulama kullanıcısının kapsamıyla devretmek için systemOperation 'entity.row.reassign' olan bir uç + microservice_execute { asUser }."],
279
+ ['UserCannotTransferToSelf', " Silinen kullanıcının kayıtları yine kendisine devredilemez: transferToUserId başka bir kullanıcı olmalı, ya da hiç verilmemeli (o zaman sahiplik birime düşer)."],
280
+ ['OrganizationStructureParentInactive', " Üst düğüm pasifken alt düğüm etkinleştirilemez: kökten yaprağa ilerleyin — önce üst birimi organization_unit_activate ile, pozisyonda ise önce birimi, sonra idari/işlevsel üst pozisyonu organization_position_activate ile açın."],
281
+ ['OrganizationSchemaRootCount', " Şema taslağında TEK kök birim olur: parentId'siz ikinci birim reddedilir. organization_schema_get ile kökü bulup yeni birimi onun (ya da bir altının) parentId'si ile açın."],
282
+ ['OrganizationSchemaCycle', " Taslakta döngü var: bir birim/pozisyon kendi atasını ebeveyn gösteriyor. organization_schema_get ile zinciri izleyin."],
283
+ ['OrganizationSchemaDuplicate', " Taslakta aynı kod (birim/pozisyon) ya da aynı rol slug'ı iki kez geçiyor; kodlar şema genelinde tekildir."],
284
+ ['OrganizationSchemaReference', " Taslakta çözülemeyen referans var: pozisyonun unitId/roleId'si ya da birimin managerPositionId'si taslakta olmayan bir düğümü gösteriyor. Önce hedefi ekleyin, sonra gösteren düğümü yazın."],
285
+ ['SystemEntityWriteNotAllowed', " Organizasyon ve erişim tabloları genel satır yoluyla yazılmaz (ORG-001) — platform_guide('access') §'Organizasyon ağacı'."]
286
+ ];
287
+ function accessHint(code, message) {
288
+ const text = `${code ?? ''} ${message ?? ''}`;
289
+ const found = accessErrorHints.find(([key]) => text.includes(key));
290
+ return found ? found[1] : '';
291
+ }
180
292
  const MaxErrorCauseWalk = 32;
181
293
  const MaxErrorPathShown = 6;
182
294
  const MaxErrorTextLength = 400;
@@ -262,16 +374,45 @@ export function mapPlatformError(status, payload, context) {
262
374
  if (validationMessages.length > 0) {
263
375
  return new PlatformApiError(`Doğrulama hatası (${location}): ${validationMessages.join(' ; ')}. Gövde şeması için ilgili ngapps://schemas kaynağına ya da mevcut bir kaydın *_get çıktısına bakın.`, status, code);
264
376
  }
265
- return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${importHint(code, context.path)}`, status, code);
377
+ return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${importHint(code, context.path)}${accessHint(code, apiMessage)}`, status, code);
266
378
  }
267
379
  if (status === 401) {
380
+ // Uygulama oturumu 401'i gateway kimliğinin değil uygulama kullanıcısının sorunudur; USERNAME/PASSWORD
381
+ // ipucu burada yanlış yere baktırırdı (g-31).
382
+ if (code === 'AppSessionRequiredException' || code === 'AppSessionInvalidException') {
383
+ return new PlatformApiError(`Uygulama oturumu ${code === 'AppSessionRequiredException' ? 'yok' : 'geçersiz ya da süresi dolmuş'} (${location}): ${apiMessage ?? ''} Kompleks proje + enforce ortamda oturumsuz her uç ve sayfa isteği 401 döner. Parolasız kullanıcı gözü için aynı çağrıyı asUserId: '<users satır id'si>' ile tekrarlayın (development/dal); oturum açılmış üye için access_session_create ile giriş yapıp tokenRefs'teki tok_… değerini asUser olarak verin.`, status, code);
384
+ }
385
+ // Yanlış e-posta/parola 2026-09-22'den beri kendi kodunu taşır (backend ADR 0004, K-3): oturum
386
+ // hatası değildir, yenileme ya da gateway parolası ipucu yanlış yere baktırırdı.
387
+ if (code === 'AppSignInFailedException') {
388
+ return new PlatformApiError(`Uygulama girişi reddedildi (${location}): e-posta ya da parola yanlış, ya da kullanıcı bu projenin 'users' tablosunda yok. ${apiMessage ?? ''} Yeni kompleks projede users boş gelir — platform_guide('getting-started') §'Kompleks projede ilk kullanıcı'. Hız sınırı 5 dk'da 10 denemedir; tekrar denemeden önce kimlik bilgisini doğrulayın.`, status, code);
389
+ }
390
+ if (code === 'ReauthenticationRequiredException') {
391
+ return new PlatformApiError(`İmza için yeniden kimlik doğrulaması reddedildi (${location}): password (verildiyse email de) görevi imzalayan kullanıcının gerçek parolası olmalı; gateway token'ı yenilemez, istek tekrarlanmadı (BPM-018).`, status, code);
392
+ }
268
393
  const hint = context.authMode === 'local'
269
394
  ? 'Gateway konfigürasyonundaki USERNAME/PASSWORD doğru mu kontrol edin.'
270
395
  : 'İstemciden geçirilen Authorization header süresi dolmuş olabilir; yeni token alıp MCP kaydını güncelleyin.';
271
396
  return new PlatformApiError(`Kimlik doğrulanamadı (${location}). ${hint}`, status, code);
272
397
  }
273
398
  if (status === 403) {
274
- return new PlatformApiError(`Yetki yok (${location}). Token'ın sahibi bu işlem için yetkili değil.`, status, code);
399
+ if (code === 'PublishApprovalRequired') {
400
+ return new PlatformApiError(`Yayın dört göz onayı istiyor (${location}) [PublishApprovalRequired].${processHint(code, context.path)}`, status, code);
401
+ }
402
+ const runtimeHint = code === 'CsrfTokenMismatchException'
403
+ ? " Oturum çerezle geldi ve durum değiştiren istekte X-ZY-CSRF yok: asUser kullanın (Bearer'lı istek CSRF istemez)."
404
+ : code === 'AccessQueryNotScopedException'
405
+ ? ' Sorgu kapsamlı/korumalı bir tabloya dokunuyor: kullanıcıya özel veri için table kaynağı, tüm veriyi özetleyen rapor için sorguda isUnrestricted (ACC-005).'
406
+ : code === 'AccessFieldWriteDeniedException' || code === 'AccessFilterDeniedException' || code === 'AccessRevealDeniedException'
407
+ ? ' Alan erişimi reddetti: access_preview fieldAccess (canWrite · canFilter); reveal için kapsamlı entity.reveal grant\'i.'
408
+ : code === 'AccessOverridePermissionRequiredException'
409
+ ? " Bu istek bir kullanıcı istisnasını yazıyor, genişletiyor ya da istisna taşıyan politikanın grant'lerine dokunuyor; access.override.manage izni gerekir (ACC-009)."
410
+ : context.api === 'tenant' && code === 'ForbiddenException'
411
+ ? ' Karar motoru reddetti; mesaj HANGİ iznin reddettiğini bilinçli olarak söylemez. Nedenini access_preview { userId, permission, resourceKey } ile sorun.'
412
+ : '';
413
+ const forbidden = new PlatformApiError(`Yetki yok (${location}). Token'ın sahibi bu işlem için yetkili değil.${runtimeHint}${runtimeHint ? '' : accessHint(code, apiMessage)}`, status, code);
414
+ forbidden.apiMessage = apiMessage;
415
+ return forbidden;
275
416
  }
276
417
  if (status === 404) {
277
418
  const environmentHint = context.environment
@@ -280,6 +421,17 @@ export function mapPlatformError(status, payload, context) {
280
421
  if (context.method !== 'GET' && /\/flows(\/|$)/.test(context.path)) {
281
422
  return new PlatformApiError(`Adımda referans verilen kayıt çözümlenemedi (${location}). Bu uçta 404 akışın kendisini DEĞİL bir adım argümanını kastediyor: flow-trigger adımının args'ında ne flowId ne triggerElementId var (ya da triggerElementId sayfadaki hiçbir elementId ile eşleşmiyor), setter adımının iteratees[].element'i bulunamıyor, ya da bir adım silinmiş bir komponente bakıyor. Adım argümanlarını page_schema_get'teki elementId'lerle karşılaştırın; flow-trigger'ın iki geçerli şekli için platform_guide('page') Kural 1c.`, status, code);
282
423
  }
424
+ // Yalnız ConnectData kodlarının ipucu bu dala girer: Access*/Protection* kodlarıyla gelen bir 404
425
+ // buradan geçerse environment/import ipuçlarını ve '*_list ile doğrulayın' yönlendirmesini kaybeder (S8 incelemesi M-10).
426
+ const connectDataHint = code?.startsWith('ConnectData') ? accessHint(code, apiMessage) : '';
427
+ if (connectDataHint || (context.method === 'POST' && /\/connect-data\/[^/]+\/[^/]+\//.test(context.path))) {
428
+ return new PlatformApiError(`Kayıt bulunamadı (${location}): ${apiMessage ?? ''}${connectDataHint || " Bağlantı ucunda 404: uygulama/sayfa slug'ı ya da elementId yolu çözmüyor, veya yazılan satır ya da ilişki hedefi bu öznenin kapsamında değil (K-16). Kapsamdaki satırları connect_data_execute op: 'list' (aynı asUser) ile görün; ilişki hedefini op: 'lookup' ile seçin."}`, status, code);
429
+ }
430
+ // İlişki hedefi yok (o2m RelationTargetRowNotFound, m2m ManyToManyTargetRowNotFound): kayıt/environment değil,
431
+ // gövdedeki bir ilişki kimliği düşüyor — mesaj alanın adını taşır (38/U-6).
432
+ if (code && /^(RelationTargetRowNotFound|ManyToManyTargetRowNotFound)(Exception)?$/.test(code)) {
433
+ return new PlatformApiError(`İlişki hedefi bulunamadı (${location}): ${apiMessage ?? ''} Gövdedeki ilişki alanına yazılan kimlik hedef tabloda yok; var olan kimlikleri hedef entity'de row_list ile alın (m2m alanı tam liste bekler: ['<id>', …]). Hiçbir satır yazılmadı.`, status, code);
434
+ }
283
435
  return new PlatformApiError(`Kayıt bulunamadı (${location}).${environmentHint} id/slug değerini ilgili *_list tool'uyla doğrulayın.${importHint(code, context.path)}`, status, code);
284
436
  }
285
437
  if (status === 409 || status === 422) {
@@ -288,7 +440,7 @@ export function mapPlatformError(status, payload, context) {
288
440
  : '';
289
441
  const validationMessages = collectValidationMessages(body);
290
442
  const detail = validationMessages.length > 0 ? ` → ${validationMessages.join(' ; ')}` : '';
291
- const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}`, status, code);
443
+ const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${processHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}${accessHint(code, apiMessage)}`, status, code);
292
444
  if (typeof body.rowIndex === 'number')
293
445
  rejected.rowIndex = body.rowIndex;
294
446
  return rejected;
@@ -6,6 +6,20 @@ import { randomUUID } from 'node:crypto';
6
6
  import { request } from 'undici';
7
7
  export const requestAuthStorage = new AsyncLocalStorage();
8
8
  const hiddenResponseHeaders = new Set(['set-cookie', 'authorization', 'proxy-authorization']);
9
+ /**
10
+ * g-08: yürütme yanıtının varsayılan başlıkları — işin okuduğu olanlar. `total-count` sayfalı tablo
11
+ * akışlarının sözleşmesidir (İK koşusu M-8), `location` yönlendirmenin, `x-request-id`/`traceparent`
12
+ * platform günlüğünde aynı isteği bulmanın anahtarıdır. Cloudflare ve sunucu başlıkları (`cf-ray`,
13
+ * `server`, `alt-svc`, `date` …) ajan bağlamında yer kaplıyordu ve hiçbir karar onlara bakmıyordu.
14
+ */
15
+ const essentialResponseHeaders = new Set(['content-type', 'content-length', 'content-disposition', 'location', 'total-count', 'x-request-id', 'traceparent']);
16
+ export function headersForMode(headers, mode) {
17
+ if (mode === 'none')
18
+ return undefined;
19
+ if (mode === 'all')
20
+ return headers;
21
+ return Object.fromEntries(Object.entries(headers).filter(([name]) => essentialResponseHeaders.has(name)));
22
+ }
9
23
  /**
10
24
  * Sayfalı tablo akışları `Total-Count` header'ına bağlı ama execute ailesi yalnız gövdeyi
11
25
  * döndürüyordu; sözleşme hiçbir tool'dan doğrulanamıyordu (İK koşusu M-8). Gizli header'lar
@@ -153,13 +167,15 @@ export class Platform {
153
167
  body = JSON.stringify(options.body);
154
168
  }
155
169
  const startedAt = Date.now();
156
- logStep('istek →', `${method} ${url.href} · ${api} api${options.environment ? ` · env=${options.environment}` : ''}${options.anonymous ? ' · ANONİM (Authorization header YOK)' : ''}${isRetry ? ' · 401 sonrası tekrar' : ''}`, logsDetail() && (options.multipart || options.form || options.body !== undefined)
157
- ? [`gövde: ${renderJson(options.multipart
158
- ? describeMultipart(options.multipart)
159
- : options.form
160
- ? Object.fromEntries(options.form)
161
- : options.body)}`]
162
- : []);
170
+ logStep('istek →', `${method} ${url.href} · ${api} api${options.environment ? ` · env=${options.environment}` : ''}${options.anonymous ? ' · ANONİM (Authorization header YOK)' : ''}${isRetry ? ' · 401 sonrası tekrar' : ''}`, options.hideRequestBody === true
171
+ ? ['gövde: (gizli — kimlik bilgisi taşıyor, günlüğe yazılmaz)']
172
+ : logsDetail() && (options.multipart || options.form || options.body !== undefined)
173
+ ? [`gövde: ${renderJson(options.multipart
174
+ ? describeMultipart(options.multipart)
175
+ : options.form
176
+ ? Object.fromEntries(options.form)
177
+ : options.body)}`]
178
+ : []);
163
179
  let statusCode;
164
180
  let payload;
165
181
  let responseBytes = 0;
@@ -196,7 +212,12 @@ export class Platform {
196
212
  logStep('yanıt ←', `HTTP ${statusCode} · ${Date.now() - startedAt} ms · ${responseBytes} B`, statusCode >= 400 || logsDetail()
197
213
  ? [`gövde: ${options.expectBinary && statusCode < 400 ? `<ikili içerik · ${responseBytes} B>` : renderJson(payload)}`]
198
214
  : []);
199
- if (statusCode === 401 && this.config.authMode === 'local' && !isRetry && !options.anonymous) {
215
+ // Çağıranın kendi Authorization'ı (asUser), imza yeniden kimlik doğrulaması ya da uygulama girişinin yanlış
216
+ // parolası (AppSignInFailedException, access.session.create — 2026-09-22) 401'i gateway token'ının süresiyle
217
+ // ilgili değildir: tekrar aynı reddi alır, üstelik parola denemesi iki kez sayılır (Faz 8 incelemesi).
218
+ const retriesWithFreshToken = callerHeaders.authorization === undefined
219
+ && !['ReauthenticationRequiredException', 'AppSignInFailedException'].includes(String(payload?.code));
220
+ if (statusCode === 401 && this.config.authMode === 'local' && !isRetry && !options.anonymous && retriesWithFreshToken) {
200
221
  this.tokens.invalidate();
201
222
  logStep('token ⟳', 'token reddedildi — önbellek boşaltıldı, istek yeni token ile bir kez daha denenecek');
202
223
  return this.send(api, method, path, options, raw, true);
@@ -0,0 +1,45 @@
1
+ import { PlatformApiError } from './errors.js';
2
+ /** `NgApps.Infrastructure/Domain/HttpHeaders.cs` `ImpersonatedUserId`. */
3
+ export const IMPERSONATED_USER_HEADER = 'impersonated-user-id';
4
+ /**
5
+ * 38/M-1 — kompleks projede parolasız kullanıcı gözü. `AppSessionMiddleware` (Tenant.API, satır 76)
6
+ * production DIŞINDA, kimliği doğrulanmış platform isteği bu başlığı taşırsa ve istekte geçerli bir
7
+ * uygulama jetonu YOKSA uygulama öznesini o kullanıcı olarak kurar; `UserMiddleware` başlığı
8
+ * production'da Execute eyleminde düşürür. Bu yüzden:
9
+ *
10
+ * - `asUser` (uygulama jetonu) ile birlikte verilemez: jeton geçerliyse platform başlığı hiç okumaz ve
11
+ * istek jetonun sahibi olarak gider — hangi kimliğin kazandığı çağırana belirsiz kalırdı.
12
+ * - Runtime aracında production hedefi istek gitmeden durur: başlık orada düşürülür ve istek SESSİZCE
13
+ * gateway'in yönetici kimliğiyle giderdi.
14
+ * - Kullanıcı projede yoksa platform başlığı yine sessizce yok sayar (`userRepository.FindByIdAsync`
15
+ * null) — aynı sessiz düşme; gateway kimliği bir kez okur (GET /users/{id}).
16
+ */
17
+ export async function impersonationHeaders(platform, tool, project, environment, input, options) {
18
+ const { asUserId } = input;
19
+ if (asUserId === undefined)
20
+ return undefined;
21
+ if (input.asUser !== undefined) {
22
+ throw new PlatformApiError(`${tool}: asUserId ile asUser birlikte verilemez — asUser geçerli bir uygulama jetonuysa platform impersonated-user-id başlığını okumaz ve istek jetonun sahibi olarak gider. Parolasız kullanıcı gözü için yalnız asUserId, oturum açılmış üye için yalnız asUser verin.`, 400);
23
+ }
24
+ if (input.anonymous === true) {
25
+ throw new PlatformApiError(`${tool}: asUserId ile anonymous: true birlikte verilemez — biri kimliksiz isteği, diğeri belirli bir kullanıcının gözünü ister.`, 400);
26
+ }
27
+ if (input.ownAuthorization === true) {
28
+ throw new PlatformApiError(`${tool}: asUserId ile headers.Authorization birlikte verilemez — kendi jetonunuzla giden istekte platform başlığı değil jetonun sahibini özne yapar. Birini seçin.`, 400);
29
+ }
30
+ if (options.runtime && platform.resolveEnvironment(environment) === 'production') {
31
+ throw new PlatformApiError(`${tool}: asUserId production'da kullanılamaz — platform impersonated-user-id başlığını production'da düşürür ve istek sessizce gateway'in yönetici kimliğiyle giderdi. Kullanıcı gözüyle doğrulamayı development'ta (ya da bir dalda) yapın; production'da o kullanıcı olarak sınamak için access_session_create + asUser.`, 400);
32
+ }
33
+ let user;
34
+ try {
35
+ user = (await platform.tenant('GET', project, `/users/${encodeURIComponent(asUserId)}`, { environment }))?.data;
36
+ }
37
+ catch (error) {
38
+ if (!(error instanceof PlatformApiError) || error.status !== 404)
39
+ throw error;
40
+ }
41
+ if (!user) {
42
+ throw new PlatformApiError(`${tool}: asUserId '${asUserId}' bu projede bir kullanıcı değil — platform tanımadığı kimliği sessizce yok sayar ve istek gateway'in kimliğiyle giderdi. Kullanıcı id'sini user_list ile doğrulayın.`, 404);
43
+ }
44
+ return { [IMPERSONATED_USER_HEADER]: asUserId };
45
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ /*
3
+ * Bazı MCP istemcileri dizi parametresini JSON METNİ olarak gönderir ve bunu parametre adına göre
4
+ * tutarsız yapar: `page_schema_patch` aynı gövdeyi `ops` ile kabul edip `operations` ile
5
+ * "Expected array, received string" diye reddediyordu; `flow_create` `steps`'i kabul edip
6
+ * `errorSteps`'i reddediyordu (Senaryo 02 koşusu, S2-26 / TB-118). Şema tarafında metni çözersek
7
+ * hangi adın kullanıldığı ve istemcinin ne yaptığı önemsizleşir.
8
+ */
9
+ export function jsonArray(schema) {
10
+ return z.preprocess(value => {
11
+ if (typeof value !== 'string')
12
+ return value;
13
+ const trimmed = value.trim();
14
+ if (!trimmed.startsWith('['))
15
+ return value;
16
+ try {
17
+ const parsed = JSON.parse(trimmed);
18
+ return Array.isArray(parsed) ? parsed : value;
19
+ }
20
+ catch {
21
+ return value;
22
+ }
23
+ }, schema);
24
+ }
@@ -1,8 +1,36 @@
1
1
  import { compactDiagnostics } from './diagnostics.js';
2
+ import { argumentNotes } from './argumentAliases.js';
3
+ import { tokenTtlMinutes } from './tokenVault.js';
2
4
  import { logError, logStep } from '../observability/trace.js';
3
5
  import { PlatformApiError, describeErrorBody } from './errors.js';
4
6
  import { PolicyBlockError } from '../knowledge/policy.js';
5
7
  import { maskSecrets } from './secrets.js';
8
+ const STACK_FRAMES_KEPT = 3;
9
+ /**
10
+ * Platformun hata gövdesindeki `stackTrace` yüzlerce satırdır ve çağıranın işine yarayan kısmı
11
+ * ilk birkaç karedir: hatayı atan servis ve satır. Tamamını taşımak uzun bir koşuda bağlamın
12
+ * büyük bölümünü yer (2026-09-19 senaryo koşusunda tek 404 ~4 bin token). Gateway izi ilk
13
+ * STACK_FRAMES_KEPT kareye indirir ve kaç karenin düştüğünü söyler; tam iz platform günlüğündedir.
14
+ */
15
+ export function collapseStackTraces(value) {
16
+ if (Array.isArray(value))
17
+ return value.map(collapseStackTraces);
18
+ if (value && typeof value === 'object') {
19
+ const result = {};
20
+ for (const [key, entry] of Object.entries(value)) {
21
+ if (key === 'stackTrace' && typeof entry === 'string') {
22
+ const frames = entry.split('\n');
23
+ result[key] = frames.length <= STACK_FRAMES_KEPT
24
+ ? entry
25
+ : `${frames.slice(0, STACK_FRAMES_KEPT).join('\n')}\n … (+${frames.length - STACK_FRAMES_KEPT} kare gateway tarafından kırpıldı; tam iz platform günlüğünde)`;
26
+ continue;
27
+ }
28
+ result[key] = collapseStackTraces(entry);
29
+ }
30
+ return result;
31
+ }
32
+ return value;
33
+ }
6
34
  export function prune(value) {
7
35
  if (Array.isArray(value))
8
36
  return value.map(prune);
@@ -140,7 +168,7 @@ function asStructured(payload) {
140
168
  return { data: payload };
141
169
  }
142
170
  export function toolResult(payload, config, expectFailure) {
143
- const pruned = prune(compactDiagnostics(payload));
171
+ const pruned = collapseStackTraces(prune(compactDiagnostics(payload)));
144
172
  const { payload: safe, masked, tokenRefs } = maskSecrets(pruned);
145
173
  const runtimeErrors = runtimeErrorSummary(safe);
146
174
  if (runtimeErrors.length > 0) {
@@ -162,15 +190,17 @@ export function toolResult(payload, config, expectFailure) {
162
190
  ...(runtimeErrors.length > 0 ? { runtimeErrors } : {}),
163
191
  ...(runtimeErrors.length > 0 && expectFailure === true ? { expectedFailure: true } : {}),
164
192
  ...(masked.length > 0 ? { gatewayMasked: masked } : {}),
165
- ...(Object.keys(tokenRefs).length > 0 ? { tokenRefs, tokenRefNote: "Maskelenen JWT'ler gateway içinde tutuldu; bu referansları page_screenshot / page_runtime_get 'asUser' alanına verebilirsiniz (15 dk geçerli). Jetonun kendisi yanıta ve günlüğe hiç girmez." } : {}),
166
- ...(truncated ? { truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' } : {})
193
+ ...(Object.keys(tokenRefs).length > 0 ? { tokenRefs, tokenRefNote: `Maskelenen JWT'ler gateway içinde tutuldu; bu referansları microservice_execute / microservice_debug / page_runtime_get / page_screenshot 'asUser' alanına verebilirsiniz (${tokenTtlMinutes()} dk geçerli; kompleks projede referans access_session_create'ten gelir). Jetonun kendisi yanıta ve günlüğe hiç girmez.` } : {}),
194
+ ...(truncated ? { truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' } : {}),
195
+ ...(argumentNotes().aliasesApplied.length > 0 ? { aliasesApplied: argumentNotes().aliasesApplied } : {}),
196
+ ...(argumentNotes().bodyFile ? { bodyFile: argumentNotes().bodyFile } : {})
167
197
  };
168
198
  return {
169
199
  content: [{ type: 'text', text: JSON.stringify(enriched) }],
170
200
  structuredContent: enriched
171
201
  };
172
202
  }
173
- export function toolError(error) {
203
+ export function toolError(error, expectFailure) {
174
204
  logError(error);
175
205
  // Politika durdurması bilinçli bir karardır, "beklenmeyen" değil (33/S-5).
176
206
  const message = error instanceof PlatformApiError || error instanceof PolicyBlockError
@@ -178,8 +208,12 @@ export function toolError(error) {
178
208
  : error instanceof Error
179
209
  ? `Beklenmeyen gateway hatası: ${error.message}`
180
210
  : `Beklenmeyen gateway hatası: ${String(error)}`;
211
+ // Kasıtlı negatif testte (bayat version 409, yetkisiz kişi 403) platform hatası da bir KANITTIR:
212
+ // ölçüm aracı bu satırı plansız hata saymasın diye işaret taşınır.
213
+ const isExpectedPlatformError = expectFailure === true && (error instanceof PlatformApiError || error instanceof PolicyBlockError);
181
214
  return {
182
215
  content: [{ type: 'text', text: message }],
216
+ ...(isExpectedPlatformError ? { structuredContent: { expectedFailure: true } } : {}),
183
217
  isError: true
184
218
  };
185
219
  }
@@ -195,7 +229,7 @@ export function handle(config, run) {
195
229
  return toolResult(await run(args), config, expectFailure);
196
230
  }
197
231
  catch (error) {
198
- return toolError(error);
232
+ return toolError(error, args?.expectFailure === true);
199
233
  }
200
234
  };
201
235
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Sayfa okumalarının belleği. İki soru cevaplar:
3
+ *
4
+ * - "Bu sayfa sürümü kırpık okundu mu" (ZM-28, PAGE-042): `page_schema_get(mode:'full',
5
+ * allowTruncated:true)` sığmayan şemayı kırparak döndüğünde anahtar işaretlenir; aynı sürümü
6
+ * `baseVersion` olarak veren `page_schema_patch` uyarı taşır — plan, düşen düğümleri görmeden
7
+ * yapılmış olabilir.
8
+ * - "Bu gateway sayfanın hangi sürümünü en son gördü" (PAGE-043, `plans/mobil-plantcare-acigi`
9
+ * g-05): `page_get`, `page_schema_get`, `page_schema_patch`'in `newVersion`'ı ve `page_update`'in
10
+ * kendi yazdığı sürüm hatırlanır; `page_update` `baseVersion` verilmediğinde onu gönderir. Hiç
11
+ * okunmamış sayfaya sürümsüz tam gövde yazılmaz.
12
+ *
13
+ * SÜREÇ İÇİNDEDİR ve yalnız anahtar/sürüm tutar (gövde yok); `CaptureMemory` ile aynı halka:
14
+ * yeniden yazılan anahtar sona gider, en eskisi düşer.
15
+ */
16
+ export function schemaReadKey(project, environment, pageId, version) {
17
+ return `${project}/${environment}/${pageId}@${version}`;
18
+ }
19
+ export function pageReadKey(project, environment, pageId) {
20
+ return `${project}/${environment}/${pageId}`;
21
+ }
22
+ export class SchemaReadMemory {
23
+ limit;
24
+ truncated = new Set();
25
+ versions = new Map();
26
+ constructor(limit = 200) {
27
+ this.limit = limit;
28
+ }
29
+ rememberVersion(key, version) {
30
+ this.versions.delete(key);
31
+ this.versions.set(key, version);
32
+ while (this.versions.size > this.limit) {
33
+ const oldest = this.versions.keys().next().value;
34
+ if (oldest === undefined)
35
+ break;
36
+ this.versions.delete(oldest);
37
+ }
38
+ }
39
+ lastVersion(key) {
40
+ return this.versions.get(key);
41
+ }
42
+ markTruncated(key) {
43
+ this.truncated.delete(key);
44
+ this.truncated.add(key);
45
+ while (this.truncated.size > this.limit) {
46
+ const oldest = this.truncated.values().next().value;
47
+ if (oldest === undefined)
48
+ break;
49
+ this.truncated.delete(oldest);
50
+ }
51
+ }
52
+ wasTruncated(key) {
53
+ return this.truncated.has(key);
54
+ }
55
+ }
56
+ /**
57
+ * SÜREÇ düzeyinde tek bellek: HTTP taşıması her POST için yeni bir gateway sunucusu kurduğundan
58
+ * (`sharedCaptureMemory` notu) okuma ile yazma ayrı `registerPageTools` çağrılarında koşar.
59
+ */
60
+ export const sharedSchemaReadMemory = new SchemaReadMemory();
@@ -3,6 +3,13 @@
3
3
  * anahtarları da yakalıyordu. Günlükte zararsızdı, tool yanıtında veri kaybı olurdu.
4
4
  */
5
5
  export const secretKeyPattern = /password|passwd|secret|token|authorization|api[-_]?key|credential|connection[-_]?string/i;
6
+ /**
7
+ * Sırrın DEĞERİNİ değil adını, yerini ya da türünü taşıyan anahtarlar: `credentialRef`, `tokenUrl`,
8
+ * `apiKeyHeader`, `secretFields`, `credentialKind`. Maskelenirlerse süreç tanımındaki
9
+ * `connector.credentialRef` get → update turunda `***` olarak geri yazılır ve düğüm kasayı bulamaz
10
+ * (BPM-017, Faz 8 incelemesi). Yalnız yanıt maskesinde uygulanır; günlük maskesi (`trace.ts`) geniş kalır.
11
+ */
12
+ export const secretNameKeyPattern = /(ref|refs|url|uri|header|fields|kind|name|names|id|ids)$/i;
6
13
  import { looksLikeJwt, rememberToken } from './tokenVault.js';
7
14
  const maskedValue = '***';
8
15
  const maxPaths = 20;
@@ -16,7 +23,7 @@ function walk(value, path, masked, refs, depth) {
16
23
  const result = {};
17
24
  for (const [key, entry] of Object.entries(value)) {
18
25
  const child = path === '' ? key : `${path}.${key}`;
19
- if (secretKeyPattern.test(key) && entry !== null && entry !== undefined && entry !== '') {
26
+ if (secretKeyPattern.test(key) && !secretNameKeyPattern.test(key) && entry !== null && entry !== undefined && entry !== '') {
20
27
  if (masked.length < maxPaths)
21
28
  masked.push(child);
22
29
  // Maskelenen değer bir JWT ise kimlik testinin yolu kapanmasın: jeton kasada tutulur ve
@@ -38,3 +45,12 @@ export function maskSecrets(payload) {
38
45
  const tokenRefs = {};
39
46
  return { payload: walk(payload, '', masked, tokenRefs, 0), masked, tokenRefs };
40
47
  }
48
+ /**
49
+ * Serbest METİNDEKİ kimlik parçaları (konsol satırı, hata yığını): JWT biçimli dize ve `Bearer …`.
50
+ * `maskSecrets` anahtar adına bakar; tarayıcı konsolu anahtarsız düz metin taşır (g-06).
51
+ */
52
+ export function maskSecretText(text) {
53
+ return text
54
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, `Bearer ${maskedValue}`)
55
+ .replace(/\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, maskedValue);
56
+ }