@appaflytech/zeyrek-ai-mcp 0.0.36 → 0.0.40

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 (210) 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 +524 -53
  9. package/dist/knowledge/componentEvents.js +132 -7
  10. package/dist/knowledge/connectDataBind.js +851 -0
  11. package/dist/knowledge/connectDataDefinition.js +344 -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 +60 -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 +876 -0
  20. package/dist/knowledge/mobileComponentEvents.js +259 -0
  21. package/dist/knowledge/mobileStore.js +396 -0
  22. package/dist/knowledge/normalize.js +240 -72
  23. package/dist/knowledge/outlines.js +36 -0
  24. package/dist/knowledge/platform/lint.js +385 -0
  25. package/dist/knowledge/platform/mobile.js +142 -0
  26. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  27. package/dist/knowledge/platform/mobileRules.js +231 -0
  28. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  29. package/dist/knowledge/platform/mobileTabs.js +108 -0
  30. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  31. package/dist/knowledge/platform/profile.js +25 -0
  32. package/dist/knowledge/platform/resolve.js +122 -0
  33. package/dist/knowledge/platform/web.js +39 -0
  34. package/dist/knowledge/policy.js +18 -4
  35. package/dist/knowledge/process.js +510 -0
  36. package/dist/knowledge/reservedTables.js +79 -0
  37. package/dist/knowledge/rowFilter.js +43 -0
  38. package/dist/knowledge/rules.js +485 -22
  39. package/dist/knowledge/schemaGuard.js +11 -0
  40. package/dist/knowledge/styleResolve.js +64 -10
  41. package/dist/knowledge/styleResolveNative.js +529 -0
  42. package/dist/knowledge/themeResolve.js +3 -3
  43. package/dist/observability/trace.js +13 -5
  44. package/dist/platform/argumentAliases.js +128 -0
  45. package/dist/platform/bodyFile.js +103 -0
  46. package/dist/platform/capture/actions.js +398 -0
  47. package/dist/platform/capture/browser.js +1 -0
  48. package/dist/platform/capture/capture.js +683 -0
  49. package/dist/platform/capture/console-log.js +122 -0
  50. package/dist/platform/capture/index.js +11 -0
  51. package/dist/platform/capture/readiness.js +103 -0
  52. package/dist/platform/capture/summary.js +77 -0
  53. package/dist/platform/capture/target.js +5 -0
  54. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  55. package/dist/platform/capture/targets/studio-web.js +5 -0
  56. package/dist/platform/diagnostics.js +10 -4
  57. package/dist/platform/domSummary.js +1 -1
  58. package/dist/platform/errors.js +163 -10
  59. package/dist/platform/http.js +29 -8
  60. package/dist/platform/impersonation.js +45 -0
  61. package/dist/platform/jsonArray.js +24 -0
  62. package/dist/platform/responses.js +39 -5
  63. package/dist/platform/schemaReadMemory.js +60 -0
  64. package/dist/platform/secrets.js +68 -2
  65. package/dist/platform/strictArguments.js +68 -12
  66. package/dist/platform/tokenVault.js +187 -4
  67. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  68. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  69. package/dist/resources/examples/bpm-satinalma.json +376 -0
  70. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  71. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  72. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  73. package/dist/resources/examples/connect-data-query-params.json +50 -0
  74. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  75. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  76. package/dist/resources/examples/middleware-authenticate.json +1 -1
  77. package/dist/resources/examples/mobile-app-shell-drawer.json +244 -0
  78. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  79. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  80. package/dist/resources/examples/mobile-form-screen.json +365 -0
  81. package/dist/resources/examples/mobile-list-detail-form.json +703 -0
  82. package/dist/resources/examples/page-detail-profile.json +2 -1
  83. package/dist/resources/examples/page-dynamic-form.json +3 -2
  84. package/dist/resources/examples/page-flex-sections.json +1 -1
  85. package/dist/resources/examples/page-html-embed.json +1 -1
  86. package/dist/resources/examples/workflow-basic.json +8 -2
  87. package/dist/resources/guides/access.md +810 -0
  88. package/dist/resources/guides/bpm.md +691 -0
  89. package/dist/resources/guides/case.md +153 -0
  90. package/dist/resources/guides/component.md +92 -5
  91. package/dist/resources/guides/decision.md +185 -0
  92. package/dist/resources/guides/entity.md +50 -8
  93. package/dist/resources/guides/expressions.md +14 -5
  94. package/dist/resources/guides/functions.md +1 -1
  95. package/dist/resources/guides/getting-started.md +38 -1
  96. package/dist/resources/guides/layout.md +11 -7
  97. package/dist/resources/guides/merge.md +42 -7
  98. package/dist/resources/guides/microservice.md +88 -3
  99. package/dist/resources/guides/middleware.md +69 -5
  100. package/dist/resources/guides/mobile-layout.md +193 -0
  101. package/dist/resources/guides/mobile-publish.md +148 -0
  102. package/dist/resources/guides/mobile.md +565 -0
  103. package/dist/resources/guides/page.md +469 -82
  104. package/dist/resources/guides/theme.md +8 -2
  105. package/dist/resources/guides/translation.md +1 -1
  106. package/dist/resources/guides/workflow.md +58 -1
  107. package/dist/resources/read.js +41 -0
  108. package/dist/resources/rules.js +11 -5
  109. package/dist/resources/schema-fingerprint.json +7 -5
  110. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  111. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  112. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  113. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  114. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  115. package/dist/resources/schemas/AppSessionDto.json +53 -0
  116. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  117. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  118. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  119. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  120. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  121. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  122. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  123. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  124. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  125. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  126. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  127. package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
  128. package/dist/resources/schemas/DataJobDto.json +6 -1
  129. package/dist/resources/schemas/DecisionDto.json +302 -0
  130. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  131. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  132. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  133. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +24 -0
  134. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  135. package/dist/resources/schemas/OrganizationProjectDto.json +343 -0
  136. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  137. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  138. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  139. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  140. package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
  141. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  142. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  143. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  144. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  145. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  146. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  147. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  148. package/dist/resources/schemas/StartMobileBuildCommand.json +54 -0
  149. package/dist/resources/schemas/StepDto.json +25 -1
  150. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  151. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  152. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  153. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  154. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  155. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  156. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  157. package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
  158. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +333 -0
  159. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  160. package/dist/server.js +31 -1
  161. package/dist/tools/access.js +369 -2
  162. package/dist/tools/accessOperations.js +135 -0
  163. package/dist/tools/accessPolicies.js +170 -0
  164. package/dist/tools/accessRuntime.js +203 -0
  165. package/dist/tools/applications.js +63 -3
  166. package/dist/tools/bpm.js +343 -0
  167. package/dist/tools/bpmGovernance.js +266 -0
  168. package/dist/tools/bpmOperations.js +230 -0
  169. package/dist/tools/bpmReporting.js +154 -0
  170. package/dist/tools/bpmRouting.js +150 -0
  171. package/dist/tools/bpmRuntime.js +396 -0
  172. package/dist/tools/case.js +178 -0
  173. package/dist/tools/caseRuntime.js +89 -0
  174. package/dist/tools/common.js +19 -6
  175. package/dist/tools/components.js +118 -14
  176. package/dist/tools/connectData.js +450 -0
  177. package/dist/tools/connectors.js +193 -0
  178. package/dist/tools/datasources.js +3 -3
  179. package/dist/tools/decisions.js +206 -0
  180. package/dist/tools/dependencies.js +1 -1
  181. package/dist/tools/deployments.js +38 -3
  182. package/dist/tools/designSchemaPatch.js +34 -9
  183. package/dist/tools/dtos.js +36 -5
  184. package/dist/tools/entities.js +79 -12
  185. package/dist/tools/environments.js +1 -1
  186. package/dist/tools/externalComponents.js +26 -5
  187. package/dist/tools/flows.js +119 -13
  188. package/dist/tools/folders.js +1 -1
  189. package/dist/tools/imports.js +5 -2
  190. package/dist/tools/layouts.js +50 -17
  191. package/dist/tools/merges.js +2 -2
  192. package/dist/tools/microservices.js +77 -25
  193. package/dist/tools/mobile.js +280 -0
  194. package/dist/tools/mobileStore.js +572 -0
  195. package/dist/tools/modules.js +48 -0
  196. package/dist/tools/organizationStructure.js +137 -0
  197. package/dist/tools/pages.js +467 -100
  198. package/dist/tools/platformInfo.js +28 -8
  199. package/dist/tools/profileWrite.js +92 -0
  200. package/dist/tools/projects.js +71 -9
  201. package/dist/tools/rows.js +69 -13
  202. package/dist/tools/schemaOps.js +61 -1
  203. package/dist/tools/screenWrite.js +36 -7
  204. package/dist/tools/screens.js +95 -5
  205. package/dist/tools/templates.js +26 -1
  206. package/dist/tools/translations.js +1 -1
  207. package/dist/tools/variables.js +11 -5
  208. package/dist/tools/workflows.js +10 -5
  209. package/package.json +2 -2
  210. package/dist/platform/screenshot.js +0 -1162
@@ -1,5 +1,6 @@
1
1
  import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
2
  import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
3
+ import { withNormalization } from '../knowledge/normalize.js';
3
4
  import { handle } from '../platform/responses.js';
4
5
  import { z } from 'zod';
5
6
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
@@ -29,16 +30,46 @@ export function registerDtoTools(server, platform) {
29
30
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/datatransferobjects/${id}/scheme`, { environment })));
30
31
  server.registerTool('dto_create', {
31
32
  title: 'DTO oluştur',
32
- description: "Yeni DataTransferObject tanımlar (definition = CreateDataTransferObjectCommand). Workflow'un parameterDtoId/returnDtoId sözleşmeleri buradan çıkar; şablon için dto_get kullanın. " + constraintSummary('CreateDataTransferObjectCommand'),
33
+ description: "Yeni DataTransferObject tanımlar (definition = CreateDataTransferObjectCommand). Workflow'un parameterDtoId/returnDtoId sözleşmeleri buradan çıkar; şablon için dto_get kullanın. ALAN LİSTESİ 'children' alanındadır ve create gövdesinde YAZILIR; eşanlamlı olarak 'fields' de kabul edilir (gateway 'children'a çevirir — komut şemasında 'fields' yoktur ve platform onu sessizce düşürürdü, DTO-003). " + constraintSummary('CreateDataTransferObjectCommand'),
33
34
  inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam, policyOverride: policyOverrideParam },
34
35
  annotations: writes
35
36
  }, handle(config, async ({ project, environment, definition, policyOverride }) => {
37
+ const body = { ...definition };
38
+ const notes = [];
39
+ // DTO-003 / S2-28 — alan listesi 'fields' adıyla gelirse SESSİZCE kaybolurdu: komut şemasında
40
+ // öyle bir üye yok, platform bilinmeyen üyeyi atıyor ve yanıt children:[] dönüyor. Ad eşlenir.
41
+ if (Object.hasOwn(body, 'fields')) {
42
+ const alias = body.fields;
43
+ delete body.fields;
44
+ if (body.children === undefined && alias !== undefined && alias !== null) {
45
+ body.children = alias;
46
+ notes.push("'fields' → 'children' olarak yazıldı: create gövdesinde alan listesinin adı 'children'dır, 'fields' sessizce düşerdi (DTO-003)");
47
+ }
48
+ else if (alias !== undefined && alias !== null) {
49
+ notes.push("'fields' yok sayıldı: gövde zaten 'children' taşıyor (DTO-003)");
50
+ }
51
+ }
36
52
  // G-23: 09-05 koşusunda çocuk alanın `type: "text"` değeri sessizce `object`'e çevrildi ve
37
53
  // bu ancak platformun kendi ürettiği bir DTO okunarak fark edildi. Doğrusu `"string"` —
38
- // DTO-001 tip sözlüğünü artık istek gitmeden denetler.
39
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'dto', definition, { operation: 'create' }, policyOverride));
40
- const payload = await platform.tenant('POST', project, '/datatransferobjects', { environment, body: definition });
41
- return attachFindings(withWriteDiff(payload, writeDiff(definition, payload)), findings);
54
+ // DTO-001 tip sözlüğünü artık istek gitmeden denetler. Denetim eşlemeden SONRA koşar ki
55
+ // 'fields' ile gelen çocuklar da (slug zorunluluğu dâhil) ilk adımda yakalansın.
56
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'dto', body, { operation: 'create' }, policyOverride));
57
+ let payload = await platform.tenant('POST', project, '/datatransferobjects', { environment, body });
58
+ // İkinci adım EMNİYET KEMERİDİR, kural değil: 2026-09-20 ölçümünde create çocukları yazdı.
59
+ // Yazmayan bir platform sürümünde (ya da bir alt kümeyi düşürdüğünde) gateway aynı yanıt
60
+ // içinde update ile tamamlar — çağıran iki adımı görmez.
61
+ const requested = Array.isArray(body.children) ? body.children : [];
62
+ const created = (payload?.data ?? payload);
63
+ const savedChildren = created?.children;
64
+ const createdId = typeof created?.id === 'string' ? created.id : undefined;
65
+ if (requested.length > 0 && createdId !== undefined && (!Array.isArray(savedChildren) || savedChildren.length < requested.length)) {
66
+ payload = await platform.tenant('PUT', project, `/datatransferobjects/${createdId}`, {
67
+ environment,
68
+ body: { ...body, id: createdId }
69
+ });
70
+ notes.push(`alan listesi ikinci adımda update ile yazıldı: create ${requested.length} çocuğun ${Array.isArray(savedChildren) ? savedChildren.length : 0} tanesini kaydetti (DTO-003)`);
71
+ }
72
+ return attachFindings(withNormalization(withWriteDiff(payload, writeDiff(body, payload)), notes), findings);
42
73
  }));
43
74
  server.registerTool('dto_update', {
44
75
  title: 'DTO güncelle',
@@ -1,6 +1,8 @@
1
1
  import { guid, entityRef, resolveEntityId, withResolved, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive, microOps } from './common.js';
2
2
  import { RefResolver, isGuid } from '../platform/refs.js';
3
- import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
3
+ import { evaluatePolicy, enforcePolicy, attachFindings, applyPolicyMode } from '../knowledge/policy.js';
4
+ import { changedExistingProtections, fieldProtectionFindings, protectionMaskTypes } from '../knowledge/access.js';
5
+ import { PlatformApiError } from '../platform/errors.js';
4
6
  import { splitJunctionFields, withNormalization } from '../knowledge/normalize.js';
5
7
  import { unmappedMembers, introducedUnmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
6
8
  import { entityOutline, resolvePart, resolvePatchOperations, patchedSections } from '../knowledge/outlines.js';
@@ -8,10 +10,11 @@ import { patchResult, baseVersionOf } from '../platform/patch.js';
8
10
  import { isJunctionField, policyRules } from '../knowledge/rules.js';
9
11
  import { handle } from '../platform/responses.js';
10
12
  import fastJsonPatch from 'fast-json-patch';
13
+ import { jsonArray } from '../platform/jsonArray.js';
11
14
  import { z } from 'zod';
12
15
  const policyOverrideParam = z.boolean().optional()
13
16
  .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
14
- const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
17
+ const patchOperationsParam = jsonArray(z.array(z.record(z.unknown())).min(1))
15
18
  .describe("RFC 6902 JSON Patch işlemleri; path'leri view:'outline'/'part' ile keşfedin");
16
19
  function preserveApplicationBinding(definition) {
17
20
  if (Array.isArray(definition.applicationIds))
@@ -113,7 +116,7 @@ export function registerEntityTools(server, platform) {
113
116
  }));
114
117
  server.registerTool('entity_create', {
115
118
  title: 'Entity oluştur',
116
- description: "Datasource altında yeni entity oluşturur (definition = CreateEntityCommand: name, fields[], applicationIds, ...). İlişkiler ve alan tipi kararı için platform_guide('entity') okuyun. Gövde politika kontrolünden geçer. applicationIds create'te platformca bağlanır (CreateEntityCommandHandler entityApplications'ı Entity ctor'una geçirir); satır uçları bu bağa İHTİYAÇ DUYMAZ — bağ yalnız Studio'nun application'a göre entity listesini ve bağımlılık sorgularını etkiler. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'. SEÇİM LİSTESİNE (enum) TEK DEĞER: listeyi yeniden göndermeyin — ekleme `{op:'add', path:'/fields[slug=status]/valueOptions/-', value:{value:'Onayda', order:4}}`, silme `{op:'remove', path:'/fields[slug=status]/valueOptions[value=Eski]'}`, yeniden adlandırma `{op:'replace', path:'/fields[slug=status]/valueOptions[value=Eski]/value', value:'Yeni'}`; `isEnum` türetilmiştir, ayrıca yazılmaz. Silme/yeniden adlandırmadan ÖNCE o değeri taşıyan satırları taşıyın — yoksa istek reddedilir ve hiçbir şey yazılmaz ('check constraint … is violated by some row') (platform_guide('entity') §'Alan tipine karar verme').",
119
+ description: "Datasource altında yeni entity oluşturur (definition = CreateEntityCommand: name, fields[], applicationIds, ...). İlişkiler ve alan tipi kararı için platform_guide('entity') okuyun. Alan koruması (fields[].protection: masked · encrypted · masked-encrypted) ve türetilmiş sorgu alanı (fields[].derivedFromFieldId + derivationRule) aynı gövdede geçirgen gider — korunamayan tip/display (PROT-003) ve geçersiz türetme kuralı (PROT-004) istek gitmeden durur; kurallar platform_guide('access') §'Alan koruması'. Gövde politika kontrolünden geçer. applicationIds create'te platformca bağlanır (CreateEntityCommandHandler entityApplications'ı Entity ctor'una geçirir); satır uçları bu bağa İHTİYAÇ DUYMAZ — bağ yalnız Studio'nun application'a göre entity listesini ve bağımlılık sorgularını etkiler. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'. SEÇİM LİSTESİNE (enum) TEK DEĞER: listeyi yeniden göndermeyin — ekleme `{op:'add', path:'/fields[slug=status]/valueOptions/-', value:{value:'Onayda', order:4}}`, silme `{op:'remove', path:'/fields[slug=status]/valueOptions[value=Eski]'}`, yeniden adlandırma `{op:'replace', path:'/fields[slug=status]/valueOptions[value=Eski]/value', value:'Yeni'}`; `isEnum` türetilmiştir, ayrıca yazılmaz. Silme/yeniden adlandırmadan ÖNCE o değeri taşıyan satırları taşıyın — yoksa istek reddedilir ve hiçbir şey yazılmaz ('check constraint … is violated by some row') (platform_guide('entity') §'Alan tipine karar verme').",
117
120
  inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
118
121
  annotations: writes
119
122
  }, handle(config, async ({ project, environment, datasourceId, definition, policyOverride }) => {
@@ -128,12 +131,28 @@ export function registerEntityTools(server, platform) {
128
131
  if (!createdId) {
129
132
  throw new Error(`Entity kuruldu ama id okunamadı; m2m alanları (${deferred.map(field => field.slug ?? field.name).join(', ')}) eklenmedi — entity_get ile kaydı bulup entity_patch ile ekleyin.`);
130
133
  }
131
- const current = await platform.tenant('GET', project, `/entities/${datasourceId}/${createdId}`, { environment });
132
- const full = structuredClone((current?.data ?? current));
133
- full.fields = [...(Array.isArray(full.fields) ? full.fields : []), ...deferred];
134
- preserveApplicationBinding(full);
135
- const updated = await platform.tenant('PUT', project, `/entities/${createdId}`, { environment, body: full });
136
- const note = `m2m/çoklu alanlar (${deferred.map(field => field.slug ?? field.name).join(', ')}) ayrı bir güncelleme turunda eklendi: create yolu junction kaydını açar ama fiziksel tabloyu kurmaz (platform bug'ı P-01), update yolu kurar.`;
134
+ // 38/U-15: ertelenen alanlar TEK TEK eklenir. Hepsi tek PUT'ta gidince çoklu enum (VARCHAR + many-to-many +
135
+ // valueOptions) başka bir junction alanıyla birlikteyken platform 400 RowHasExistingRelation ("Related table:
136
+ // 'Entities', column: 'Relation Id'") döndü: çoklu enum alanının relationId'si aynı güncellemede açılan değer
137
+ // entity'sini gösterir ve iki junction'lı yazmada alan kaydı o entity'den önce düşüyor (EntityService.UpdateAsync
138
+ // fieldRepository.CreateRange → manyToManyFieldProcessor/entityRepository.Create). Ölçüldü 2026-09-21 cdt-mcp:
139
+ // m2m+dosya-m2m+çoklu enum, dosya-m2m+çoklu enum, çoklu enum×2 düştü; m2m×2 geçti; tek tek hepsi geçti.
140
+ let updated;
141
+ for (const field of deferred) {
142
+ const current = await platform.tenant('GET', project, `/entities/${datasourceId}/${createdId}`, { environment });
143
+ const full = structuredClone((current?.data ?? current));
144
+ full.fields = [...(Array.isArray(full.fields) ? full.fields : []), field];
145
+ preserveApplicationBinding(full);
146
+ try {
147
+ updated = await platform.tenant('PUT', project, `/entities/${createdId}`, { environment, body: full });
148
+ }
149
+ catch (error) {
150
+ const added = deferred.slice(0, deferred.indexOf(field)).map(item => item.slug ?? item.name);
151
+ const missing = deferred.slice(deferred.indexOf(field)).map(item => item.slug ?? item.name);
152
+ throw new PlatformApiError(`Entity ${createdId} kuruldu${added.length ? `, ${added.join(', ')} eklendi` : ''}; '${String(field.slug ?? field.name)}' eklenirken platform reddetti: ${error instanceof Error ? error.message : String(error)} — eklenmeyen alanlar (${missing.join(', ')}) entity_patch ile tek tek eklenebilir.`, error instanceof PlatformApiError ? error.status : 400, error instanceof PlatformApiError ? error.code : undefined);
153
+ }
154
+ }
155
+ const note = `m2m/çoklu alanlar (${deferred.map(field => field.slug ?? field.name).join(', ')}) ayrı güncelleme turlarında TEK TEK eklendi: create yolu junction kaydını açar ama fiziksel tabloyu kurmaz (platform bug'ı P-01), update yolu kurar; çoklu enum başka bir junction alanıyla aynı güncellemede platformda düşer (38/U-15).`;
137
156
  return attachFindings(withUnmappedMembers(withNormalization(updated, [note]), drift), findings);
138
157
  }));
139
158
  server.registerTool('entity_create_discovered', {
@@ -144,7 +163,7 @@ export function registerEntityTools(server, platform) {
144
163
  }, handle(config, ({ project, environment, datasourceId, definition }) => platform.tenant('POST', project, `/entities/${datasourceId}/discovered`, { environment, body: definition })));
145
164
  server.registerTool('entity_update', {
146
165
  title: 'Entity güncelle',
147
- description: "Entity tanımını günceller (tam gövde). Hedefli değişiklik için entity_patch tercih edin. Gövde politika kontrolünden geçer. DİKKAT: gövdede 'indexes' YOKSA platform entity'nin bütün index'lerini siler — gateway alan verilmemişse mevcut index'leri taşır ve bunu bildirir; silmek için 'indexes: []' yazın. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
166
+ description: "Entity tanımını günceller (tam gövde). VAR OLAN alanın korumasını bu araç değiştiremez (platform 400 ProtectionChangeNotAllowed) — entity_protection_update; yeni alan korumayı gövdeyle alır (PROT-003/PROT-004). Türetilmiş alan eklemek ya da kaynağını/kuralını değiştirmek dolu satırları arka planda dolduran bir derived-fill işi açar (tablo kilitlenmez); bitişi import_history { entityId } ile izleyin. Hedefli değişiklik için entity_patch tercih edin. Gövde politika kontrolünden geçer. DİKKAT: gövdede 'indexes' YOKSA platform entity'nin bütün index'lerini siler — gateway alan verilmemişse mevcut index'leri taşır ve bunu bildirir; silmek için 'indexes: []' yazın. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
148
167
  inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, definition: definitionParam, policyOverride: policyOverrideParam },
149
168
  annotations: updates
150
169
  }, handle(config, async ({ project, environment, datasourceId, id, definition, policyOverride }) => {
@@ -159,7 +178,7 @@ export function registerEntityTools(server, platform) {
159
178
  }));
160
179
  server.registerTool('entity_patch', {
161
180
  title: 'Entity\'ye patch uygula',
162
- description: "Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. 'applications' → 'applicationIds' çevirisini artık platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
181
+ description: "YENİ alanı korumayla ya da türetilmiş alan olarak eklemek için de kullanılır (PROT-003/PROT-004); eklenen ya da kuralı değişen türetilmiş alanı dolu satırlarda derived-fill işi doldurur (tablo kilitlenmez, bitiş import_history { entityId }). VAR OLAN alanın korumasını değiştirmez — platform 400 ProtectionChangeNotAllowed döner, gateway istek göndermeden durdurur ve yol entity_protection_update'tir. Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. 'applications' → 'applicationIds' çevirisini artık platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
163
182
  inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, operations: patchOperationsParam.optional(), ops: patchOperationsParam.optional().describe("'operations' ile aynı alan (eşanlamlı) — sayfa micro-op tool'larıyla aynı adı kullanabilmeniz için"), policyOverride: policyOverrideParam },
164
183
  annotations: updates
165
184
  }, handle(config, async ({ project, environment, datasourceId, id, operations: givenOperations, ops, policyOverride }) => {
@@ -171,6 +190,10 @@ export function registerEntityTools(server, platform) {
171
190
  const resolvedOps = resolvePatchOperations(definition, operations, 'entity_get');
172
191
  // Politika, yazmadan önce sonucu görmeli; yerel kopya YALNIZ değerlendirme için, gövde olarak gönderilmez.
173
192
  const preview = fastJsonPatch.applyPatch(definition, resolvedOps.operations, true, false).newDocument;
193
+ const protectionChanges = changedExistingProtections(current?.data ?? current, preview);
194
+ if (protectionChanges.length > 0) {
195
+ throw new PlatformApiError(`entity_patch: var olan alanın korumasını değiştiriyor (${protectionChanges.join(', ')}) — platform bunu 400 ProtectionChangeNotAllowed ile reddeder, çünkü dolu satırlar arka plan işiyle dönüştürülmelidir. entity_protection_update { entityId, field: '${protectionChanges[0]}', protection } kullanın; istek gönderilmedi. Yeni bir alanı korumayla eklemek entity_patch ile serbesttir.`, 400);
196
+ }
174
197
  const findings = [
175
198
  ...enforcePolicy(evaluatePolicy(config, policyRules, 'entity', preview, { operation: 'update' }, policyOverride)),
176
199
  ...inPlaceIndexEdits(current?.data ?? current, preview)
@@ -195,6 +218,48 @@ export function registerEntityTools(server, platform) {
195
218
  outline: entityOutline(written.data)
196
219
  }, resolver.resolved), drift), findings);
197
220
  }));
221
+ server.registerTool('entity_protection_update', {
222
+ title: 'Alan korumasını değiştir',
223
+ description: "VAR OLAN bir alanın koruma modunu değiştirir (none · masked · encrypted · masked-encrypted): platform gizli kolonları ekler ve dolu satırları arka plan işiyle dönüştürür — yanıt 202 + jobId; ilerleme import_status { jobId } (aynı DataJobs ucu, tür protection-apply / protection-remove). entity_update / entity_patch var olan alanın korumasını DEĞİŞTİREMEZ (400 ProtectionChangeNotAllowed). İş sürerken aynı entity'ye yazım 409; iş hata verirse AYNI çağrı kaldığı yerden sürdürür. PROT-003 istek gitmeden uygulanır; kurallar platform_guide('access') §'Alan koruması'.",
224
+ inputSchema: {
225
+ project: projectParam,
226
+ environment: environmentParam,
227
+ entityId: entityRef,
228
+ field: z.string().min(1).describe("Alanın id'si (GUID) ya da slug'ı"),
229
+ protection: z.object({
230
+ mode: z.enum(['none', 'masked', 'encrypted', 'masked-encrypted']).describe("'none' korumayı kaldırır"),
231
+ mask: z.object({
232
+ type: z.enum(protectionMaskTypes()),
233
+ visible: z.number().int().min(0).optional().describe('last-n / first-n için > 0; phone varsayılanı 2 hane; first-n-last-n\'de baştan görünen karakter sayısı (> 0)'),
234
+ visibleEnd: z.number().int().min(0).optional().describe('Yalnız first-n-last-n: sondan görünen karakter sayısı (> 0) — 10000000146, visible 3 + visibleEnd 2 → 100******46'),
235
+ char: z.string().length(1).optional().describe("Varsayılan '*'; phone maskesinde ayırıcı ya da boşluk (+-()./) olamaz"),
236
+ pattern: z.string().optional().describe("pattern türünde: '#' açık karakter, diğerleri olduğu gibi yazılır")
237
+ }).optional().describe('masked ve masked-encrypted modunda zorunlu'),
238
+ exactSearch: z.boolean().optional().describe('Şifreli modda tam eşleşme araması için blind index (HMAC) kolonu'),
239
+ normalize: z.enum(['none', 'digits', 'lower', 'trim']).optional().describe('Eşleşme ve blind index öncesi değeri normalleştirir (TCKN için digits)')
240
+ }),
241
+ policyOverride: z.boolean().optional().describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)')
242
+ },
243
+ annotations: updates
244
+ }, handle(config, async ({ project, environment, entityId, field, protection, policyOverride }) => {
245
+ const resolver = new RefResolver(platform, project, environment);
246
+ const entity = await resolver.entity(entityId);
247
+ const record = await resolver.entityRecord(entity);
248
+ const fields = (Array.isArray(record.fields) ? record.fields : []);
249
+ const target = fields.find(entry => entry.id === field || entry.slug === field);
250
+ if (!target) {
251
+ throw new PlatformApiError(`entity_protection_update: '${field}' alanı '${entity.slug}' entity'sinde yok. Alanları entity_get view:'outline' ile görün.`, 404);
252
+ }
253
+ const findings = config.policyMode === 'off' ? [] : enforcePolicy(applyPolicyMode(config, fieldProtectionFindings([{ ...target, protection }]), policyOverride));
254
+ const payload = await platform.tenant('POST', project, `/entities/${entity.entityId}/fields/${String(target.id)}/protection`, { environment, body: { protection } });
255
+ const jobId = payload?.data?.jobId ?? payload?.data?.dataJobId;
256
+ return attachFindings(withResolved({
257
+ jobId,
258
+ field: target.slug,
259
+ mode: protection.mode,
260
+ note: `Koruma işi kuyruğa alındı. İlerleme: import_status { jobId: '${String(jobId)}' }. İş bitene kadar '${entity.slug}' entity'sine yazım 409 döner ve okumalar geçiş durumundadır (işlenmiş satırlar maskeli, işlenmemişler düz). Anahtar yapılandırılmamış ortamda iş ProtectionKeyUnavailable ile düşer.`
261
+ }, resolver.resolved), findings);
262
+ }));
198
263
  server.registerTool('entity_delete', {
199
264
  title: 'Entity sil',
200
265
  description: 'Entity tanımını siler; önce entity_dependencies ile bağımlılıklarını kontrol edin.',
@@ -237,13 +302,15 @@ export function registerEntityTools(server, platform) {
237
302
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/entities/${id}/dependencies`, { environment })));
238
303
  server.registerTool('entity_audit_trails', {
239
304
  title: 'Audit kayıtları',
240
- description: 'Entity\'nin satır bazlı audit trail kayıtlarını listeler (audit trail açık olmalı).',
305
+ description: 'Entity\'nin satır bazlı audit trail kayıtlarını listeler (audit trail açık olmalı). `source: CONNECT_DATA` bağlı bileşenden (connectData ucu) gelen yazmaları süzer.',
241
306
  inputSchema: {
242
307
  project: projectParam,
243
308
  environment: environmentParam,
244
309
  entityId: guid,
245
310
  rowId: z.string().optional(),
246
311
  userId: z.string().optional(),
312
+ action: z.enum(['CREATE', 'UPDATE', 'DELETE', 'REVEAL', 'UNRESTRICTED-QUERY']).optional(),
313
+ source: z.enum(['PLATFORM', 'APPLICATION', 'CONNECT_DATA']).optional(),
247
314
  ...pagingParams
248
315
  },
249
316
  annotations: readOnly
@@ -4,7 +4,7 @@ import { handle } from '../platform/responses.js';
4
4
  import { z } from 'zod';
5
5
  /** Aggregate kuralının aynası (`Environment` ctor); gerçek koruma backend'dedir. Kural `common.ts`'te. */
6
6
  const branchName = z.string().regex(branchNamePattern)
7
- .describe("Dal adı: küçük harfle başlar, yalnız küçük harf/rakam/alt çizgi, 3-31 karakter. Bu ad PG şema adı olur.");
7
+ .describe("Dal adı: küçük harfle başlar; küçük harf/rakam/alt çizgi/tire ve '/' ile ayrılmış parçalar (ör. feature/odeme), 3-31 karakter. Bu ad PG şema adı olur ve değişmez; adres parçası (slug) '/' yerine '-' taşır.");
8
8
  function assertBranchName(name) {
9
9
  if (isReservedEnvironmentName(name)) {
10
10
  throw new PlatformApiError(`environment_create: '${name}' rezerve bir şema adı — dal adı olarak kullanılamaz. Başka bir ad seçin.`, 400);
@@ -7,6 +7,23 @@ import { z } from 'zod';
7
7
  import { evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
8
8
  import { policyRules } from '../knowledge/rules.js';
9
9
  import { EXTERNAL_COMPONENT_PROTOCOL_VERSION } from '../knowledge/backendConstraints.js';
10
+ import { resolveProfile } from '../knowledge/platform/resolve.js';
11
+ import { findingLine } from '../knowledge/platform/profile.js';
12
+ /**
13
+ * MOB-006 — harici bileşen protokolü v1 tarayıcıda koşan `.mjs` custom element'tir; mobil
14
+ * uygulamanın native çizeri onu yükleyemez (mobil protokol v2 Faz 3). Profil kapalı derse çağrı
15
+ * platforma GİTMEZ: yüklenen paket hiçbir ekranda çizilmeyecekti.
16
+ */
17
+ async function assertExternalComponentsAllowed(platform, applicationId, tool) {
18
+ const resolved = await resolveProfile(platform, applicationId);
19
+ if (resolved.profile.externalComponents)
20
+ return;
21
+ throw new PlatformApiError(`${tool}: ${findingLine({
22
+ code: 'MOB-006',
23
+ severity: 'warn',
24
+ message: "harici bileşen mobil uygulamada desteklenmiyor — protokol v1 tarayıcıda koşan .mjs custom element'tir, native çizer onu yükleyemez (mobil protokol v2 Faz 3). Çağrı platforma gönderilmedi; mobil ekranı katalog bileşenleriyle kurun (component_list). Katalogda karşılığı olmayan küçük görünüm için geçici yol 'html' bileşenidir: içerik korumalı belgede tema değişkenleriyle çizilir, fireEvent/ngApps.emit ile akışa bağlanır (component_get('html'))."
25
+ })}`, 400);
26
+ }
10
27
  /** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular (backend ExternalComponentRules.CurrentProtocolVersion) */
11
28
  const PROTOCOL_VERSION = EXTERNAL_COMPONENT_PROTOCOL_VERSION;
12
29
  /** Backend `ExternalComponentRules.MaxBundleSizeBytes` ile aynı sınır */
@@ -199,6 +216,7 @@ export function registerExternalComponentTools(server, platform) {
199
216
  },
200
217
  annotations: writes
201
218
  }, handle(config, async ({ project, environment, applicationId, bundlePath, manifestPath, version }) => {
219
+ await assertExternalComponentsAllowed(platform, applicationId, 'external_component_publish');
202
220
  const manifestFile = resolve(manifestPath);
203
221
  const bundleFile = resolve(bundlePath);
204
222
  let manifest;
@@ -224,7 +242,7 @@ export function registerExternalComponentTools(server, platform) {
224
242
  }
225
243
  // EXT-001: protocolVersion, definition.blocks, ayrılmış prop adları ve prop tipleri — kayıt
226
244
  // ucu 422 dönmeden manifest'te yakalanır; paket yükleme ikinci istek olduğu için yarım kayıt kalmaz.
227
- enforcePolicy(evaluatePolicy(config, policyRules, 'externalComponent', { ...manifest, slug }, { operation: 'create' }));
245
+ enforcePolicy(evaluatePolicy(config, policyRules, 'externalComponent', { ...manifest, slug }, { operation: 'create' }), false);
228
246
  const record = await platform.tenant('POST', project, `/components/${applicationId}/external`, {
229
247
  environment,
230
248
  body: {
@@ -262,10 +280,13 @@ export function registerExternalComponentTools(server, platform) {
262
280
  acknowledgeWarnings: z.boolean().optional().describe('Doğrulama uyarılarını okuyup kabul ettiğinizi bildirir; uyarısı olan sürümlerde zorunlu')
263
281
  },
264
282
  annotations: updates
265
- }, handle(config, ({ project, environment, applicationId, slug, version, acknowledgeWarnings }) => platform.tenant('POST', project, `/components/${applicationId}/external/${slug}/activate`, {
266
- environment,
267
- body: { version, acknowledgeWarnings: acknowledgeWarnings === true }
268
- })));
283
+ }, handle(config, async ({ project, environment, applicationId, slug, version, acknowledgeWarnings }) => {
284
+ await assertExternalComponentsAllowed(platform, applicationId, 'external_component_activate');
285
+ return platform.tenant('POST', project, `/components/${applicationId}/external/${slug}/activate`, {
286
+ environment,
287
+ body: { version, acknowledgeWarnings: acknowledgeWarnings === true }
288
+ });
289
+ }));
269
290
  server.registerTool('external_component_versions', {
270
291
  title: 'Paket sürümlerini listele',
271
292
  description: 'Bir harici komponentin yayınlanmış paket sürümlerini, hangisinin aktif olduğunu ve her sürümün doğrulama raporunu listeler. Geri alınacak sürümü buradan seçin.',
@@ -1,6 +1,7 @@
1
1
  import { fetchHost, flowPath, hostParam, unwrap } from './screens.js';
2
2
  import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive, microOps } from './common.js';
3
- import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
3
+ import { applyPolicyMode, evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
4
+ import { connectedSetterFindings } from '../knowledge/connectDataDefinition.js';
4
5
  import { flowBindings, normalizeFlowSteps, targetlessSetterWarnings, unknownSetterTargetWarnings, withNormalization } from '../knowledge/normalize.js';
5
6
  import { flowBodyWarnings } from '../knowledge/coverage.js';
6
7
  import { PlatformApiError } from '../platform/errors.js';
@@ -13,6 +14,8 @@ import { policyRules, flowExpressionSites } from '../knowledge/rules.js';
13
14
  import { functionCallRefs } from '../knowledge/functionGuards.js';
14
15
  import { existingMatch, ifExistsParam, platformRename, withRename } from '../platform/ifExists.js';
15
16
  import { randomUUID } from 'node:crypto';
17
+ import { flowProfileWarnings } from './profileWrite.js';
18
+ import { jsonArray } from '../platform/jsonArray.js';
16
19
  import { z } from 'zod';
17
20
  /**
18
21
  * Akış yazması yanıtında gövdeyi geri döndürmüyor, bu yüzden diff için akışı bir kez okuruz.
@@ -34,9 +37,9 @@ async function flowWriteDrops(platform, host, project, environment, applicationI
34
37
  return emptyWriteDiff();
35
38
  }
36
39
  }
37
- const stepsParam = z.array(z.record(z.unknown())).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code/function adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c. Kullanıcı fonksiyonu çağırmak için 'function' adımı: { type:'function', args:{ functionId:'<function_list id>', functionName:'<ad>', parameters:[{ name:'<bildirimdeki ad>', value:{ type:'static'|'property'|'storage'|'state'|'code', ref } }] } } → çıktı Steps.<adımAdı>.data (ngapps://examples/flow-call-function, platform_guide('functions')).");
40
+ const stepsParam = jsonArray(z.array(z.record(z.unknown()))).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code/function adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c. Kullanıcı fonksiyonu çağırmak için 'function' adımı: { type:'function', args:{ functionId:'<function_list id>', functionName:'<ad>', parameters:[{ name:'<bildirimdeki ad>', value:{ type:'static'|'property'|'storage'|'state'|'code', ref } }] } } → çıktı Steps.<adımAdı>.data (ngapps://examples/flow-call-function, platform_guide('functions')).");
38
41
  const policyOverrideParam = z.boolean().optional();
39
- const patchOperationsParam = z.array(z.record(z.unknown())).min(1).describe("RFC 6902 JSON Patch işlemleri; yolları flow_get çıktısıyla keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/steps[stepId=paint]/args/iteratees[element=tbl_recruiters]/mapping/source' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
42
+ const patchOperationsParam = jsonArray(z.array(z.record(z.unknown())).min(1)).describe("RFC 6902 JSON Patch işlemleri; yolları flow_get çıktısıyla keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/steps[stepId=paint]/args/iteratees[element=tbl_recruiters]/mapping/source' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
40
43
  const flowIdParam = guid.describe("Akış id'si; şemadaki events[].value bu id'dir");
41
44
  function flowSummary(flow) {
42
45
  return {
@@ -56,6 +59,27 @@ function hostNodes(record) {
56
59
  return record.schema ?? record.views ?? [];
57
60
  }
58
61
  const BINDINGS_UNAVAILABLE = 'Barındırıcı kaydı okunamadığı için olay bağları hesaplanamadı; akış listesi yine de günceldir.';
62
+ /**
63
+ * TB-126 / S2-36 — satır aksiyonu taşıyabilen komponentler. Bu tiplerin `actions` prop'unu çoğu
64
+ * zaman YÜKLEME AKIŞI çalışma zamanında yazar (aksiyon role göre eklenip çıkarılıyorsa başka yolu
65
+ * da yoktur), yani bağ statik şemada GÖRÜNMEZ. Böyle bir sayfada "bağsız akışları temizle" refleksi
66
+ * canlı aksiyonu siler; `unboundFlowIds` bu yüzden dürüstçe işaretlenir.
67
+ */
68
+ const RUNTIME_ACTION_TYPES = new Set(['table', 'datatable']);
69
+ const RUNTIME_BINDING_NOTE = 'Bu barındırıcıda satır aksiyonu taşıyan bir tablo var: satır aksiyonları çalışma zamanında yazılabilir, '
70
+ + 'silmeden önce doğrulayın — unboundFlowIds statik şemadan hesaplanır ve çalışma zamanı bağını göremez.';
71
+ function hasRuntimeActionHost(nodes) {
72
+ for (const node of Array.isArray(nodes) ? nodes : []) {
73
+ if (!node || typeof node !== 'object')
74
+ continue;
75
+ const record = node;
76
+ if (typeof record.type === 'string' && RUNTIME_ACTION_TYPES.has(record.type))
77
+ return true;
78
+ if (hasRuntimeActionHost(record.children))
79
+ return true;
80
+ }
81
+ return false;
82
+ }
59
83
  /**
60
84
  * Akış listesine bağları ekler. `danglingBindings` ters yönü söyler: şemada id'si geçen ama
61
85
  * barındırıcıda karşılığı olmayan akış — olay tetiklenir, hiçbir şey koşmaz.
@@ -68,9 +92,12 @@ function flowListResult(flows, record) {
68
92
  const danglingBindings = [...bindings.entries()]
69
93
  .filter(([flowId]) => !known.has(flowId))
70
94
  .flatMap(([flowId, entries]) => entries.map(entry => ({ flowId, ...entry })));
95
+ const unboundFlowIds = flows.map(flow => String(flow.id)).filter(flowId => !bindings.has(flowId.toLowerCase()));
96
+ const mayBeBoundAtRuntime = unboundFlowIds.length > 0 && hasRuntimeActionHost(hostNodes(record));
71
97
  return {
72
98
  flows: flows.map(flow => ({ ...flowSummary(flow), bindings: bindings.get(String(flow.id).toLowerCase()) ?? [] })),
73
- unboundFlowIds: flows.map(flow => String(flow.id)).filter(flowId => !bindings.has(flowId.toLowerCase())),
99
+ unboundFlowIds,
100
+ ...(mayBeBoundAtRuntime ? { mayBeBoundAtRuntime: true, mayBeBoundAtRuntimeNote: RUNTIME_BINDING_NOTE } : {}),
74
101
  ...(danglingBindings.length > 0 ? { danglingBindings } : {})
75
102
  };
76
103
  }
@@ -274,7 +301,7 @@ async function functionWarnings(platform, host, project, environment, applicatio
274
301
  }
275
302
  const missing = fn.parameters.filter(parameter => parameter.required && !given.includes(parameter.name)).map(parameter => parameter.name);
276
303
  if (missing.length > 0) {
277
- warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error dolar ve akış hata gövdesiyle 'always' dalından devam eder.`);
304
+ warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error.message dolar ve .data null'dur; errorSteps yoksa akış 'always' dalından devam eder.`);
278
305
  }
279
306
  }
280
307
  for (const { step, origin, ref } of callSites) {
@@ -295,11 +322,65 @@ async function functionWarnings(platform, host, project, environment, applicatio
295
322
  }
296
323
  return [...warnings];
297
324
  }
325
+ /**
326
+ * FLOW-038 (g-02, ADR 0004 — ZM-07). `microservice.ts` `.finally` üç yoldan birini dener: adımın
327
+ * kendi `error` dalı bağlıysa oradan sürer, yoksa flow `errorSteps`'i (`engine.onError`) devralır,
328
+ * ikisi de yoksa `always` koşar ve storage artık `{ data: null, headers: null, error }`'dır — hata
329
+ * gövdesi ARTIK `data`'da DEĞİL. İkisi de bağlı değilken bu adımın `.data`'sını okuyan bir
330
+ * setter/condition varsa hata anında NULL üzerinden koşar ve yazar hiç fark etmez. Aynı yürüyüş
331
+ * FLOW-024/025'in kullandığı `flowExpressionSites` taramasıdır; setter'ın STORAGE (kod olmayan)
332
+ * referansı ayrıca kendi düz alanından okunur (FLOW-008/009 deseni, `rowsTargetsByStepName`).
333
+ */
334
+ function microserviceAlwaysDataWarnings(steps, errorSteps) {
335
+ const mainSteps = (Array.isArray(steps) ? steps : []).filter(isRecord);
336
+ const hasErrorFlow = (Array.isArray(errorSteps) ? errorSteps : []).filter(isRecord).some(step => String(step.type ?? '') !== 'start');
337
+ const label = (step) => String(step.stepId ?? step.name ?? '?');
338
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
339
+ const warnings = new Set();
340
+ for (const step of mainSteps) {
341
+ if (String(step.type ?? '') !== 'microservice')
342
+ continue;
343
+ const name = String(step.name ?? step.stepId ?? '');
344
+ if (name === '')
345
+ continue;
346
+ const hasErrorBranch = (Array.isArray(step.branches) ? step.branches : [])
347
+ .filter(isRecord)
348
+ .some(branch => String(branch.name ?? '') === 'error' && Boolean(branch.nextStepId));
349
+ if (hasErrorBranch || hasErrorFlow)
350
+ continue;
351
+ const pattern = new RegExp(`Steps\\s*(?:\\.\\s*${escapeRegExp(name)}\\b|\\[\\s*['"]${escapeRegExp(name)}['"]\\s*\\])\\s*\\??\\.\\s*data\\b`);
352
+ const message = (other) => `FLOW-038: '${label(other)}' adımı 'always' dalında '${name}.data' okuyor ama '${name}' adımının ne bağlı bir 'error' dalı ne akışın 'errorSteps'i var — uç hata dönerse bu adımın 'data'sı NULL olur (eskiden hata gövdesiydi, ADR 0004) ve '${label(other)}' boş/null veriyle koşar. '${name}' adımına 'error' dalı bağlayın ya da flow'a errorSteps verin.`;
353
+ for (const other of mainSteps) {
354
+ if (other === step)
355
+ continue;
356
+ if (String(other.type ?? '') === 'setter') {
357
+ const iteratees = Array.isArray(other.args?.iteratees)
358
+ ? other.args.iteratees.filter(isRecord)
359
+ : [];
360
+ for (const iteratee of iteratees) {
361
+ const mapping = (iteratee.mapping ?? {});
362
+ const value = (iteratee.value ?? {});
363
+ const reference = typeof mapping.source === 'string' && mapping.source.length > 0
364
+ ? mapping.source
365
+ : typeof value.ref === 'string' ? value.ref : '';
366
+ if (value.type === 'storage' && (reference === `${name}.data` || reference.startsWith(`${name}.data.`) || reference.startsWith(`${name}.data[`))) {
367
+ warnings.add(message(other));
368
+ }
369
+ }
370
+ }
371
+ for (const site of flowExpressionSites(other)) {
372
+ if (pattern.test(site.expression))
373
+ warnings.add(message(other));
374
+ }
375
+ }
376
+ }
377
+ return [...warnings];
378
+ }
298
379
  export function registerFlowTools(server, platform) {
299
380
  const config = platform.config;
300
381
  server.registerTool('flow_list', {
301
382
  title: 'Akışları listele',
302
- description: "Barındırıcının olay akışlarını, her birine bağlı olaylarla (bindings: [{ target: elementId | 'page', event }]) listeler. unboundFlowIds hiçbir olaya bağlı olmayan (sahipsiz) akışlardır; danglingBindings şemada id'si geçen ama barındırıcıda olmayan akışlardır. Sayfa ve layout akışları önbelleksiz liste ucundan, widget akışları kaydın kendi gövdesinden okunur; bağlar barındırıcının şemasından hesaplanır.",
383
+ description: "Barındırıcının olay akışlarını, her birine bağlı olaylarla (bindings: [{ target: elementId | 'page', event }]) listeler. unboundFlowIds hiçbir olaya bağlı olmayan (sahipsiz) akışlardır; danglingBindings şemada id'si geçen ama barındırıcıda olmayan akışlardır. Yanıtta mayBeBoundAtRuntime: true varsa sayfada table/datatable vardır ve satır aksiyonları (actions[].flow) çalışma zamanında yazılmış olabilir: unboundFlowIds o hâlde KESİN DEĞİLDİR, silmeden önce doğrulayın (TB-126). Sayfa ve layout akışları önbelleksiz liste ucundan, widget akışları kaydın kendi gövdesinden okunur; bağlar barındırıcının şemasından hesaplanır.",
303
384
  inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid },
304
385
  annotations: readOnly
305
386
  }, handle(config, async ({ project, environment, host, applicationId, hostId }) => {
@@ -361,14 +442,18 @@ export function registerFlowTools(server, platform) {
361
442
  ...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
362
443
  };
363
444
  const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
364
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride));
445
+ const findings = [
446
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride)),
447
+ ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(steps, errorSteps ?? [], hostNodes(hostRecord)), policyOverride))
448
+ ];
365
449
  const existing = await existingFlowMatch(platform, host, project, environment, applicationId, hostId, hostRecord, name, slug, flowId, ifExists);
366
450
  const writtenFlowId = existing === undefined ? resolvedFlowId : String(existing.id);
367
451
  const parameterWarnings = [
368
452
  ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
369
453
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
370
454
  ...flowBodyWarnings(steps),
371
- ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
455
+ ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord)),
456
+ ...microserviceAlwaysDataWarnings(steps, errorSteps ?? [])
372
457
  ];
373
458
  // Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
374
459
  // ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
@@ -382,6 +467,7 @@ export function registerFlowTools(server, platform) {
382
467
  // Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
383
468
  // gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
384
469
  const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, writtenFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps }, ifExists === 'duplicate');
470
+ const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
385
471
  return attachFindings(withWriteDiff(withRename(withNormalization({
386
472
  host,
387
473
  hostId,
@@ -393,6 +479,7 @@ export function registerFlowTools(server, platform) {
393
479
  steps: steps.length
394
480
  },
395
481
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
482
+ ...(platformWarnings.length > 0 ? { platformWarnings } : {}),
396
483
  hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
397
484
  }, normalized), drops.renamed), drops), findings);
398
485
  }));
@@ -434,6 +521,7 @@ export function registerFlowTools(server, platform) {
434
521
  const findings = steps || errorSteps
435
522
  ? [
436
523
  ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', authored, { operation: 'update' }, policyOverride)),
524
+ ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(authored.steps, authored.errorSteps, hostNodes(hostRecord)), policyOverride)),
437
525
  ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride).filter(finding => finding.ruleId === 'FLOW-027'))
438
526
  ]
439
527
  : [];
@@ -441,7 +529,8 @@ export function registerFlowTools(server, platform) {
441
529
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
442
530
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
443
531
  ...flowBodyWarnings(flow.steps),
444
- ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
532
+ ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
533
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
445
534
  ];
446
535
  await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
447
536
  // Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
@@ -452,7 +541,9 @@ export function registerFlowTools(server, platform) {
452
541
  ...(steps === undefined ? {} : { steps }),
453
542
  ...(errorSteps === undefined ? {} : { errorSteps })
454
543
  });
455
- return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}) }, normalized), drops), findings);
544
+ const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
545
+ return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
546
+ ...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), findings);
456
547
  }));
457
548
  server.registerTool('flow_patch', {
458
549
  title: "Akışa patch uygula",
@@ -486,12 +577,16 @@ export function registerFlowTools(server, platform) {
486
577
  ...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
487
578
  };
488
579
  const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
489
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride));
580
+ const findings = [
581
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride)),
582
+ ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(flow.steps, flow.errorSteps, hostNodes(hostRecord)), policyOverride))
583
+ ];
490
584
  const parameterWarnings = [
491
585
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
492
586
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
493
587
  ...flowBodyWarnings(flow.steps),
494
- ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
588
+ ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
589
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
495
590
  ];
496
591
  // Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
497
592
  const netOps = fastJsonPatch.compare(current, flow);
@@ -501,6 +596,7 @@ export function registerFlowTools(server, platform) {
501
596
  });
502
597
  const written = patchResult(saved, flow);
503
598
  const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, { steps: flow.steps, errorSteps: flow.errorSteps });
599
+ const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
504
600
  return attachFindings(withWriteDiff(withNormalization({
505
601
  host,
506
602
  hostId,
@@ -508,7 +604,8 @@ export function registerFlowTools(server, platform) {
508
604
  newVersion: written.newVersion,
509
605
  patchTargets: resolved.targets,
510
606
  flow: flowSummary(written.data),
511
- ...(parameterWarnings.length > 0 ? { parameterWarnings } : {})
607
+ ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
608
+ ...(platformWarnings.length > 0 ? { platformWarnings } : {})
512
609
  }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), findings);
513
610
  }));
514
611
  server.registerTool('flow_delete', {
@@ -520,4 +617,13 @@ export function registerFlowTools(server, platform) {
520
617
  await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId } });
521
618
  return { deleted: flowId, host, hostId };
522
619
  }));
620
+ server.registerTool('flow_restore', {
621
+ title: 'Silinen akışı geri yükle',
622
+ description: "Silinmiş bir akışı revizyon defterindeki son içeriğiyle (ad, slug, adımlar, barındırıcı) AYNI id ile yeniden kurar. Sayfa kaydı bağı kalmayan akışı kalıcı siler (FLOW-002, allowFlowDeletion) — Studio'nun eski sekmesi boş şema kaydettiğinde de (ZM-27). Akışı olaya YENİDEN BAĞLAMAZ: geri geldikten sonra set_event ile bağlayın. SINIR: adımları sayfanın bir komponentini hedefliyorsa (tetikleyen element, iterate/parametre komponenti) o komponent de şemada olmalıdır, yoksa geri yükleme reddedilir — sayfa boşaldıysa (ZM-27) page_restore kullanın: sayfayı ve akışlarını birlikte geri kurar. Akış hâlâ varsa 409, saklama süresi dolup revizyonu budanmışsa 404 döner. Silinen akışı revision_list(module: 'flow') satırlarında operation: delete olarak bulun; recordId akış id'sidir.",
623
+ inputSchema: { project: projectParam, environment: environmentParam, flowId: flowIdParam },
624
+ annotations: writes
625
+ }, handle(config, async ({ project, environment, flowId }) => {
626
+ const restored = unwrap(await platform.tenant('POST', project, `/flows/${flowId}/restore`, { environment }));
627
+ return { restored: flowId, flow: restored };
628
+ }));
523
629
  }
@@ -23,7 +23,7 @@ export function registerFolderTools(server, platform) {
23
23
  inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
24
24
  annotations: writes
25
25
  }, handle(config, async ({ project, environment, definition }) => {
26
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'folder', definition, { operation: 'create' }));
26
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'folder', definition, { operation: 'create' }), false);
27
27
  return attachFindings(await platform.tenant('POST', project, '/folders', { environment, body: definition }), findings);
28
28
  }));
29
29
  server.registerTool('folder_update', {
@@ -182,7 +182,10 @@ export function registerImportTools(server, platform) {
182
182
  return { ...payload, note: "Plan henüz çalıştırılmadı: import_run { datasourceId, planId } ile başlatın — planId bu jobId'dir ve plan bir saat geçerlidir." };
183
183
  }
184
184
  if (status === 'failed') {
185
- return { ...payload, note: `İş düştü; \`error\` alanı nedeni söyler.${isImport ? ' Yazılmış partiler yerinde kalır; bugün geri alınamaz.' : ''}` };
185
+ // Koruma işi (entity_protection_update) düşerse alan geçişte kalır ve entity yazımı 409'a kilitlenir;
186
+ // otomatik yeniden deneme yok, kurtarma aynı isteği aynı gövdeyle tekrarlamaktır (TB-31).
187
+ const isProtection = job?.kind === 'protection-apply' || job?.kind === 'protection-remove';
188
+ return { ...payload, note: `İş düştü; \`error\` alanı nedeni söyler.${isImport ? ' Yazılmış partiler yerinde kalır; bugün geri alınamaz.' : ''}${isProtection ? " Koruma işi: alan geçiş durumunda kalır ve entity'ye yazım 409 döner. Nedeni giderdikten sonra AYNI entity_protection_update çağrısını AYNI gövdeyle tekrarlayın — iş kaldığı yerden sürer (TB-31)." : ''}` };
186
189
  }
187
190
  if (status === 'cancelled') {
188
191
  return { ...payload, note: `İş iptal edildi.${isImport ? ' Yazılmış partiler yerinde kalır; bugün geri alınamaz.' : ''}` };
@@ -191,7 +194,7 @@ export function registerImportTools(server, platform) {
191
194
  }));
192
195
  server.registerTool('import_history', {
193
196
  title: 'Veri işleri geçmişi',
194
- description: "Projedeki içe/dışa aktarma ve geri alma işlerini sayfalı listeler; order verilmezse en yeni iş önce gelir. Her satır kind, status, entityIds ve result taşır. kind: 'import' çalıştırılmamış planları (planned) da içerir; datasource filtresi yoktur. Geri alınabilir bir iş aramak (kind: 'import', status: 'completed', undoAvailableUntil geçmemiş) ya da düşen bir işin nedenini bulmak için kullanılır.",
197
+ description: "Projedeki içe/dışa aktarma, geri alma, alan koruması (protection-*) ve türetilmiş alan doldurma (derived-fill) işlerini sayfalı listeler; order verilmezse en yeni iş önce gelir. Her satır kind, status, entityIds ve result taşır. kind: 'import' çalıştırılmamış planları (planned) da içerir; datasource filtresi yoktur. Geri alınabilir bir iş aramak (kind: 'import', status: 'completed', undoAvailableUntil geçmemiş) ya da düşen bir işin nedenini bulmak için kullanılır.",
195
198
  inputSchema: {
196
199
  project: projectParam,
197
200
  environment: environmentParam,