@appaflytech/zeyrek-ai-mcp 0.0.28 → 0.0.31

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 (203) 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 +500 -35
  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 +366 -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/middleware-authenticate.json +1 -1
  76. package/dist/resources/examples/mobile-app-shell-drawer.json +243 -0
  77. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  78. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  79. package/dist/resources/examples/mobile-form-screen.json +365 -0
  80. package/dist/resources/examples/mobile-list-detail-form.json +672 -0
  81. package/dist/resources/examples/page-detail-profile.json +2 -1
  82. package/dist/resources/examples/page-dynamic-form.json +3 -2
  83. package/dist/resources/examples/page-flex-sections.json +1 -1
  84. package/dist/resources/examples/page-html-embed.json +1 -1
  85. package/dist/resources/examples/workflow-basic.json +8 -2
  86. package/dist/resources/guides/access.md +810 -0
  87. package/dist/resources/guides/bpm.md +691 -0
  88. package/dist/resources/guides/case.md +153 -0
  89. package/dist/resources/guides/component.md +92 -5
  90. package/dist/resources/guides/decision.md +185 -0
  91. package/dist/resources/guides/entity.md +50 -8
  92. package/dist/resources/guides/expressions.md +14 -5
  93. package/dist/resources/guides/functions.md +1 -1
  94. package/dist/resources/guides/getting-started.md +38 -1
  95. package/dist/resources/guides/layout.md +11 -7
  96. package/dist/resources/guides/merge.md +37 -3
  97. package/dist/resources/guides/microservice.md +87 -2
  98. package/dist/resources/guides/middleware.md +68 -4
  99. package/dist/resources/guides/mobile-layout.md +193 -0
  100. package/dist/resources/guides/mobile.md +495 -0
  101. package/dist/resources/guides/page.md +456 -75
  102. package/dist/resources/guides/theme.md +8 -2
  103. package/dist/resources/guides/translation.md +1 -1
  104. package/dist/resources/guides/workflow.md +58 -1
  105. package/dist/resources/read.js +41 -0
  106. package/dist/resources/rules.js +11 -5
  107. package/dist/resources/schema-fingerprint.json +7 -5
  108. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  109. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  110. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  111. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  112. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  113. package/dist/resources/schemas/AppSessionDto.json +53 -0
  114. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  115. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  116. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  117. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  118. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  119. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  120. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  121. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  122. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  123. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  124. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  125. package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
  126. package/dist/resources/schemas/DataJobDto.json +6 -1
  127. package/dist/resources/schemas/DecisionDto.json +302 -0
  128. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  129. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  130. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  131. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +21 -1
  132. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  133. package/dist/resources/schemas/OrganizationProjectDto.json +335 -0
  134. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  135. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  136. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  137. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  138. package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
  139. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  140. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  141. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  142. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  143. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  144. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  145. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  146. package/dist/resources/schemas/StepDto.json +25 -1
  147. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  148. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  149. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  150. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  151. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  152. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  153. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  154. package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
  155. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  156. package/dist/server.js +27 -1
  157. package/dist/tools/access.js +369 -2
  158. package/dist/tools/accessOperations.js +135 -0
  159. package/dist/tools/accessPolicies.js +170 -0
  160. package/dist/tools/accessRuntime.js +203 -0
  161. package/dist/tools/applications.js +60 -3
  162. package/dist/tools/bpm.js +343 -0
  163. package/dist/tools/bpmGovernance.js +266 -0
  164. package/dist/tools/bpmOperations.js +230 -0
  165. package/dist/tools/bpmReporting.js +154 -0
  166. package/dist/tools/bpmRouting.js +150 -0
  167. package/dist/tools/bpmRuntime.js +396 -0
  168. package/dist/tools/case.js +178 -0
  169. package/dist/tools/caseRuntime.js +89 -0
  170. package/dist/tools/common.js +7 -0
  171. package/dist/tools/components.js +118 -14
  172. package/dist/tools/connectData.js +449 -0
  173. package/dist/tools/connectors.js +193 -0
  174. package/dist/tools/datasources.js +3 -3
  175. package/dist/tools/decisions.js +206 -0
  176. package/dist/tools/dependencies.js +1 -1
  177. package/dist/tools/deployments.js +38 -3
  178. package/dist/tools/designSchemaPatch.js +34 -9
  179. package/dist/tools/dtos.js +36 -5
  180. package/dist/tools/entities.js +79 -12
  181. package/dist/tools/externalComponents.js +26 -5
  182. package/dist/tools/flows.js +122 -14
  183. package/dist/tools/folders.js +1 -1
  184. package/dist/tools/imports.js +5 -2
  185. package/dist/tools/layouts.js +50 -17
  186. package/dist/tools/merges.js +2 -2
  187. package/dist/tools/microservices.js +77 -25
  188. package/dist/tools/mobile.js +221 -0
  189. package/dist/tools/modules.js +48 -0
  190. package/dist/tools/organizationStructure.js +137 -0
  191. package/dist/tools/pages.js +465 -98
  192. package/dist/tools/platformInfo.js +27 -8
  193. package/dist/tools/profileWrite.js +92 -0
  194. package/dist/tools/projects.js +71 -9
  195. package/dist/tools/rows.js +69 -13
  196. package/dist/tools/schemaOps.js +61 -1
  197. package/dist/tools/screenWrite.js +37 -8
  198. package/dist/tools/screens.js +95 -5
  199. package/dist/tools/templates.js +26 -1
  200. package/dist/tools/translations.js +1 -1
  201. package/dist/tools/variables.js +11 -5
  202. package/dist/tools/workflows.js +10 -5
  203. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { listKnowledgeFiles, readKnowledgeFile } from '../resources/knowledge.js';
2
+ import { readToolResource } from '../resources/read.js';
2
3
  import { splitGuideSections } from '../knowledge/guideSections.js';
3
4
  import { PlatformApiError } from '../platform/errors.js';
4
5
  import { logCapabilityGap } from '../observability/trace.js';
@@ -63,8 +64,10 @@ const guideRouting = {
63
64
  microservice: 'kaynak seçimi (table > query), selectFields disiplini',
64
65
  microfunction: 'kendi runtime\'ında koşan Node/Python fonksiyonu: protokol, yayın döngüsü, step kullanımı',
65
66
  entity: "alan tipleri, ilişkiler, hazır entity'ler",
66
- page: 'sayfa/flow işleri; flex düzen prop grubu ve boyut modları (Kural 1f-2)',
67
- layout: 'yerleşim kabuğu: tek slot, views, mobil yığılma, widthType fill/fixed/auto',
67
+ page: 'sayfa/flow işleri; prop\'u veri kaynağına bağlama (connectData, Kural 1c-3); flex düzen prop grubu ve boyut modları (Kural 1f-2)',
68
+ layout: 'yerleşim kabuğu: tek slot, views, dar ekranda yığılma, widthType fill/fixed/auto',
69
+ mobile: "mobil UYGULAMA (type:'mobile'): kurulum sırası (mobile_app_create), flex-only yerleşim, tipli stil, sekmeler (properties.mobile.tabs) ve çekmece, adım sözlüğü, MOB-* bulguları, page_screenshot device",
70
+ 'mobile-layout': 'mobil ekranın mizanpajı — tek dil: güvenli alan, tek kaydırıcı, 16 dp kenar, gap ritmi, saydam/yüzeyli grup, genişlik, yarıçap/yazı ölçeği; MOB-015…021 reçeteleri ve layoutReport hiza ölçümü',
68
71
  component: 'komponent modeli, olay sözleşmeleri, appearance boyut sözlüğü (widthString/heightString)',
69
72
  html: 'hazır komponent yetmediğinde tek seferlik kaçış kapısı (Mustache, fireEvent, script kuralları)',
70
73
  'external-component': 'kalıcı ihtiyaçta kendi komponentini yazıp yayınlama',
@@ -73,7 +76,10 @@ const guideRouting = {
73
76
  expressions: 'ifade fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…)',
74
77
  functions: "fonksiyonlar, iki kaynak tek biçim: source:'studio' tarayıcıda Functions.<ad>() (kapsam uygulama/sayfa, UI flow 'function' adımı), source:'workflow' sunucuda çıplak ad; parametre/varsayılan/required, tuzaklar; GÖVDE GÜNCELLEME function_update (yalnız değişen alanı gönder, patch aracı yok)",
75
78
  translation: 'sayfa/içerik çevirileri (çoklu dil)',
76
- merge: "dal aç, dalda çalış, development'a merge et; çakışma çözümü ve apply kapıları"
79
+ access: 'kompleks proje yetkisi: organizasyon ağacı, izin kataloğu, kapsam, grant/atama, alan koruması (maske · şifre · türetilmiş alan)',
80
+ merge: "dal aç, dalda çalış, development'a merge et; çakışma çözümü ve apply kapıları",
81
+ bpm: 'süreç motoru: tanım modeli, düğümler, atama sözlüğü, bölge olayları, kutu ve görev eylemleri, bağlayıcı/belge/imza/kanallar (süreç ≠ workflow)',
82
+ decision: 'DMN karar tabloları: model, isabet politikaları, FEEL alt kümesi, DRD, süreçte decision düğümü, DMN XML'
77
83
  };
78
84
  // page.md 123 KB; section'sız dönerse yanıt tool çıktı sınırını aşıyor ve rehber MCP kanalından
79
85
  // HİÇ okunamıyor (27/05 E-1, 09-04 koşusu). Eşik ÖLÇÜLEN kopma noktasından türetilir, "bugün
@@ -92,7 +98,8 @@ export function registerPlatformTools(server, platform) {
92
98
  'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
93
99
  ...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
94
100
  "Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
95
- 'Örnek gövdeler için ngapps://examples kaynaklarına bakın.'
101
+ "Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
102
+ "Örnek gövdeler ve kural katalogları (ngapps://examples/<ad>, ngapps://rules/<ad>) istemci MCP resource okuyamıyorsa da bu araçla okunur: platform_guide({ resource: 'ngapps://rules/connect-data' }) — guide verilmez; dizin için 'ngapps://examples/index'."
96
103
  ].join('\n');
97
104
  server.registerTool('platform_whoami', {
98
105
  title: 'Oturum bilgisi',
@@ -104,13 +111,23 @@ export function registerPlatformTools(server, platform) {
104
111
  title: 'Platform rehberi oku',
105
112
  description: guideDescription,
106
113
  inputSchema: {
107
- guide: (guides.length > 0 ? z.enum(guides) : z.string())
108
- .describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir'),
114
+ guide: (guides.length > 0 ? z.enum(guides) : z.string()).optional()
115
+ .describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir. resource ile birlikte verilmez'),
116
+ resource: z.string().regex(/^ngapps:\/\//).optional()
117
+ .describe("guide yerine bir ngapps:// kaynağı okur (examples ve rules): 'ngapps://examples/connect-data-table-entity', 'ngapps://rules/connect-data', dizinler '…/index'. Resource okuyamayan istemcinin rehberdeki ngapps:// yönlendirmelerini izleme yolu budur (38/M-14)."),
109
118
  section: z.string().optional()
110
119
  .describe("Yalnız bu bölümü döner (yanıttaki 'sections' listesinden bir slug). Verilmezse rehber tam döner; ancak tool yanıt sınırını aşan büyük rehberlerde (page) yalnız giriş + outline döner")
111
120
  },
112
121
  annotations: readOnly
113
- }, handle(config, async ({ guide, section }) => {
122
+ }, handle(config, async ({ guide, section, resource }) => {
123
+ if (resource !== undefined) {
124
+ if (guide !== undefined || section !== undefined)
125
+ throw new PlatformApiError("platform_guide: resource ile guide/section birlikte verilemez — biri ngapps:// kaynağını, diğeri rehber bölümünü okur.", 400);
126
+ const read = readToolResource(resource);
127
+ return 'error' in read ? read : { resource: read.uri, mimeType: read.mimeType, content: read.text };
128
+ }
129
+ if (guide === undefined)
130
+ throw new PlatformApiError("platform_guide: 'guide' (rehber adı) ya da 'resource' (ngapps:// kaynağı) verin.", 400);
114
131
  const available = listKnowledgeFiles('guides').map(file => file.replace(/\.md$/, ''));
115
132
  if (!available.includes(guide)) {
116
133
  return { error: `'${guide}' diye bir rehber yok.`, available };
@@ -154,6 +171,7 @@ export function registerPlatformTools(server, platform) {
154
171
  title: 'Kabiliyet boşluğu bildir',
155
172
  description: "Yapmak İSTEDİĞİNİZ ama platform desteklemediği için yapamadığınız her noktada BUNU ÇAĞIRIN — tasarımı sessizce küçültmeyin. Bu çağrı platforma gitmez, yalnızca günlüğe 'KABİLİYET BOŞLUĞU' bloğu yazar; hata günlüğünde görünmeyen bu kayıtlar ürün yol haritasının girdisidir. Önce rehbere bakıp (platform_guide; hazır komponent yetmiyorsa tek seferlik için 'html' kaçış kapısı, kalıcı bir ihtiyaç içinse 'external-component' — kendi komponentinizi yazıp yayınlayabilirsiniz) gerçekten desteklenmediğinden emin olun: desteklenen bir şeyi 'yok' diye bildirmek yanlış yol haritası üretir.",
156
173
  inputSchema: {
174
+ project: z.string().min(1).optional().describe("Boşluğun görüldüğü proje (ad ya da slug); kayda yazılır. Bu çağrı platforma gitmez, proje yalnız bağlamdır"),
157
175
  goal: z.string().min(1).optional().describe('Ne yapmak istediniz — kullanıcıya görünen sonuç olarak yazın ("randevu listesini hasta adına göre süzmek"). Eşanlamlı kabul edilir: title · summary · what.'),
158
176
  blocker: z.string().min(1).optional().describe('Neden yapamadınız — hangi kabiliyetin eksik olduğu ("uçta X yok", "komponentte Y prop\'u yok"). Eşanlamlı kabul edilir: reason · problem · missing · description.'),
159
177
  severity: z.string().optional().describe("YOL HARİTASINDAKİ SIRAYI BU ALAN BELİRLER, doldurun. blocker: özellik hiç yapılamadı · workaround: daha kötü bir yolla yapıldı · cosmetic: yalnız görsel/ergonomik kayıp. Eşanlamlılar çevrilir (critical/high/blocking→blocker, medium/major→workaround, low/minor/nit→cosmetic). TANINMAYAN değer reddedilir; verilmezse kayıt düşer ama yanıt 'incomplete' ile uyarır ve bildirim orta kovaya yazılır."),
@@ -171,9 +189,10 @@ export function registerPlatformTools(server, platform) {
171
189
  annotations: readOnly
172
190
  }, handle(config, async (input) => {
173
191
  const gap = normalizeGapReport(input);
174
- logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround });
192
+ logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround, project: input.project });
175
193
  return {
176
194
  recorded: true,
195
+ ...(input.project ? { project: input.project } : {}),
177
196
  severity: gap.severity,
178
197
  module: gap.module ?? null,
179
198
  ...(Object.keys(gap.normalizedFrom).length > 0 ? { normalizedFrom: gap.normalizedFrom } : {}),
@@ -0,0 +1,92 @@
1
+ import { findingLines, profileFindings, stateStorageFindings } from '../knowledge/platform/lint.js';
2
+ import { describeShell, mobileLayoutFindings, newFindings } from '../knowledge/platform/mobileLayoutLint.js';
3
+ import { resolveProfile } from '../knowledge/platform/resolve.js';
4
+ import { fetchCatalogSlugs } from './components.js';
5
+ import { flowPath } from './screens.js';
6
+ /**
7
+ * Sayfa/layout/widget yazma yollarının profil kapısı: mobil profilde `MOB-*` bulgularını
8
+ * `layoutWarnings[]` satırı olarak döner. Web profilinde HİÇBİR istek atmaz ve boş dizi döner —
9
+ * web yanıtı profil öncesiyle aynı kalır (G1 golden).
10
+ */
11
+ export async function screenProfileWarnings(platform, resolved, project, environment, applicationId, input) {
12
+ if (resolved.profile.platform === 'web')
13
+ return [];
14
+ const catalog = await fetchCatalogSlugs(platform, project, environment, applicationId).catch(() => undefined);
15
+ // Tam kabuk ağacı yazan layout yolları (create/update/patch) mizanpaj iskeletini de denetler (MOB-015…018).
16
+ const layoutTree = resolved.profile.platform === 'mobile' && input.kind === 'layout' && input.nodes !== undefined
17
+ ? mobileLayoutFindings({ kind: 'layout', tree: input.nodes })
18
+ : [];
19
+ return findingLines([...profileFindings(resolved.profile, { ...input, ...(catalog ? { catalog } : {}) }), ...layoutTree]);
20
+ }
21
+ /**
22
+ * Sayfa yazımının mizanpaj iskeleti (MOB-015…018): sayfanın kabuğu okunur, çünkü üst pay ve app-bar kabukla
23
+ * birlikte karar verilir. `before` verilirse (yama) yalnız yazımın getirdiği bulgular döner. Web profilinde
24
+ * istek atmaz; kabuk okunamazsa kabuğa bağlı kurallar koşmaz ve bilgi satırı döner.
25
+ */
26
+ export async function pageLayoutWarnings(platform, resolved, project, environment, applicationId, input) {
27
+ if (resolved.profile.platform !== 'mobile')
28
+ return [];
29
+ const shell = await pageShell(platform, project, environment, applicationId, input.layoutId);
30
+ const after = mobileLayoutFindings({ kind: 'page', tree: input.tree, shell });
31
+ return findingLines(input.before === undefined ? after : newFindings(mobileLayoutFindings({ kind: 'page', tree: input.before, shell }), after));
32
+ }
33
+ /** Kabuk yamasının getirdiği iskelet bulguları (`layout_schema_patch`); web profilinde boş. */
34
+ export function layoutPatchWarnings(resolved, before, after) {
35
+ if (resolved.profile.platform !== 'mobile')
36
+ return [];
37
+ return findingLines(newFindings(mobileLayoutFindings({ kind: 'layout', tree: before }), mobileLayoutFindings({ kind: 'layout', tree: after })));
38
+ }
39
+ async function pageShell(platform, project, environment, applicationId, layoutId) {
40
+ if (typeof layoutId !== 'string' || layoutId === '')
41
+ return { kind: 'none' };
42
+ try {
43
+ const response = await platform.tenant('GET', project, `/layouts/${applicationId}/${layoutId}`, { environment });
44
+ return Array.isArray(response?.data?.views) ? describeShell(response.data.views) : { kind: 'unknown' };
45
+ }
46
+ catch {
47
+ return { kind: 'unknown' };
48
+ }
49
+ }
50
+ /**
51
+ * `set_event` ile bağlanan akışların adımları (MOB-005). Akış gövdeleri şemada değil ayrı uçtadır;
52
+ * okunamazsa bulgu üretilmez, yazım yine geçer.
53
+ */
54
+ export async function boundFlowSteps(platform, resolved, project, environment, host, applicationId, hostId, ops) {
55
+ if (resolved.profile.platform === 'web')
56
+ return [];
57
+ const flowIds = new Set(ops.filter(entry => entry.op === 'set_event').map(entry => String(entry.eventValue ?? '')).filter(id => id.length > 0));
58
+ if (flowIds.size === 0)
59
+ return [];
60
+ try {
61
+ const hosted = await platform.tenant('GET', project, flowPath(host, applicationId, hostId), { environment });
62
+ return (hosted?.data ?? [])
63
+ .filter(flow => flowIds.has(String(flow.id ?? '')))
64
+ .flatMap(flow => [...(Array.isArray(flow.steps) ? flow.steps : []), ...(Array.isArray(flow.errorSteps) ? flow.errorSteps : [])]);
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ }
70
+ /**
71
+ * Akış yazma yollarının profil kapısı (MOB-005): mobil uygulamada `download`/`setcookie` adımının
72
+ * davranış farkını `platformWarnings`'e yazar. Web profilinde istek atmaz, boş döner.
73
+ */
74
+ /**
75
+ * Değişken yazma yollarının profil kapısı (MOB-014). Yazılan set saklama türü taşımıyorsa uygulama
76
+ * tipi ÇÖZÜLMEZ — ek istek atılmaz, yanıt profil öncesiyle aynı kalır. Tip çözülemediyse (uygulama
77
+ * okunamadı) bulgu üretilmez: gateway bilmediği platform için "uygulanmıyor" demez.
78
+ */
79
+ export async function variableProfileWarnings(platform, applicationId, entries) {
80
+ if (!entries.some(entry => entry.storage !== undefined))
81
+ return [];
82
+ const resolved = await resolveProfile(platform, applicationId);
83
+ if (!resolved.known)
84
+ return [];
85
+ return findingLines(stateStorageFindings(resolved.profile, entries));
86
+ }
87
+ export async function flowProfileWarnings(platform, applicationId, steps) {
88
+ const resolved = await resolveProfile(platform, applicationId);
89
+ if (resolved.profile.platform === 'web')
90
+ return [];
91
+ return findingLines(profileFindings(resolved.profile, { kind: 'page', steps }));
92
+ }
@@ -70,6 +70,32 @@ function rows(payload) {
70
70
  const data = payload?.data;
71
71
  return Array.isArray(data) ? data : [];
72
72
  }
73
+ /**
74
+ * 2026-09-15'ten eski backend'lerde `GET /p/{slug}` (`GetOrganizationProjectBySlugQuery`)
75
+ * `OrganizationProjectDto.Map`'i yayın sürümü vermeden çağırıyordu: `organizationSchema.latestVersion` null
76
+ * gelir, prune onu düşürür ve "latestVersion yoksa yayınlı şema yok" okuması yanlış olurdu (TB-67, backend'de
77
+ * çözüldü). Slug yanıtı sürümü taşıyorsa olduğu gibi döner; taşımıyorsa (eski build) abonelik bloğu id ucundan
78
+ * (`GetOrganizationProjectQuery`) alınır. İd ucu düşerse (o uç `RequirePermission` taşımaz, Organization.API onu
79
+ * yalnız super-admin'e açar) slug yanıtı döner ve söylenir.
80
+ */
81
+ async function withPublishedSchemaVersion(platform, payload) {
82
+ const data = payload?.data;
83
+ if (!data || typeof data.id !== 'string' || data.accessModel !== 'complex')
84
+ return payload;
85
+ const latestVersion = data.organizationSchema?.latestVersion;
86
+ if (latestVersion !== undefined && latestVersion !== null)
87
+ return payload;
88
+ try {
89
+ const byId = await platform.org('GET', `/projects/${data.id}`);
90
+ const organizationSchema = byId?.data?.organizationSchema;
91
+ return organizationSchema && typeof organizationSchema === 'object'
92
+ ? { ...payload, data: { ...data, organizationSchema } }
93
+ : payload;
94
+ }
95
+ catch {
96
+ return withNormalization(payload, ["organizationSchema.latestVersion doğrulanamadı (slug ucu GET /p/{slug} sürüm taşımaz, GET /projects/{id} okunamadı): yokluğu 'yayınlı şema yok' demek DEĞİLDİR — organization_schema_get ile doğrulayın (TB-67)."]);
97
+ }
98
+ }
73
99
  const applicationInput = z.object({
74
100
  slug: z.string().min(1),
75
101
  name: z.string().min(1),
@@ -124,32 +150,35 @@ export function registerProjectTools(server, platform) {
124
150
  const config = platform.config;
125
151
  server.registerTool('project_list', {
126
152
  title: 'Projeleri listele',
127
- description: 'Organizasyondaki projeleri arar ve sayfalı listeler.',
153
+ description: "Organizasyondaki projeleri arar ve sayfalı listeler. Her satır accessModel ('simple' | 'complex') ve organizationSchema aboneliğini taşır (alanların anlamı project_get açıklamasında).",
128
154
  inputSchema: { searchTerm: z.string().optional(), ...pagingParams },
129
155
  annotations: readOnly
130
156
  }, handle(config, args => platform.org('GET', '/projects', { query: args })));
131
157
  server.registerTool('project_get', {
132
158
  title: 'Proje getir',
133
- description: "Tek projeyi id ya da slug ile getirir; environment listesi de yanıttadır. Yanıttaki 'localization' bloğu gateway'in eklediği türetilmiş alandır: platform dil alanlarını null olduklarında serileştirmede düşürüyor, blok bunu görünür kılar ve runtime'ın çevirileri çözüp çözemeyeceğini söyler.",
159
+ description: "Tek projeyi id ya da slug ile getirir; environment listesi de yanıttadır (her ortamda enforcementMode: off | shadow | enforce). Yanıttaki 'localization' bloğu gateway'in eklediği türetilmiş alandır: platform dil alanlarını null olduklarında serileştirmede düşürüyor, blok bunu görünür kılar ve runtime'ın çevirileri çözüp çözemeyeceğini söyler. accessModel projenin türüdür; organizationSchema { receiveUpdates, appliedVersion, latestVersion, customizedAt } kompleks projenin şema aboneliğidir — gateway null alanları düşürür: customizedAt YOKSA proje özelleştirilmemiştir, appliedVersion yoksa şema hiç uygulanmamıştır, latestVersion yoksa organizasyonda yayınlı şema yoktur (platform_guide('getting-started') §'Basit mi kompleks mi'). slug ile çağrıda platformun slug ucu latestVersion'ı taşımaz; gateway organizationSchema bloğunu GET /projects/{id}'den alır, o okuma düşerse yanıtta gatewayNormalized notu olur ve latestVersion'ın yokluğu bir şey söylemez (TB-67).",
134
160
  inputSchema: { id: guid.optional(), slug: z.string().optional().describe('id verilmediyse zorunlu') },
135
161
  annotations: readOnly
136
162
  }, handle(config, async (args) => {
137
163
  if (!args.id && !args.slug) {
138
164
  throw new Error("'id' ya da 'slug' parametrelerinden biri zorunlu.");
139
165
  }
140
- return withLocalization(await platform.org('GET', args.id ? `/projects/${args.id}` : `/p/${args.slug}`));
166
+ if (args.id)
167
+ return withLocalization(await platform.org('GET', `/projects/${args.id}`));
168
+ return withLocalization(await withPublishedSchemaVersion(platform, await platform.org('GET', `/p/${args.slug}`)));
141
169
  }));
142
170
  server.registerTool('project_create', {
143
171
  title: 'Proje oluştur',
144
172
  description: "Yeni proje oluşturur. templateOptions verilmezse 'application' zorunludur (ilk application projeyle birlikte açılır). LoaderImage bu tool'da desteklenmez. " + constraintSummary('CreateOrganizationProjectCommand') + ` Gateway POST'tan önce project_list ile slug'ı arar; doluysa istek gitmez ve 409 anlamlı mesajla döner. DİL: create ucu artık languages/defaultLanguage alanlarını kendisi taşır ve gövdeyle birlikte gider. Vermezseniz gateway varsayılan [${SEED_LANGUAGES.join(', ')}] + '${SEED_DEFAULT_LANGUAGE}' tohumunu koyar ve bunu gatewayNormalized ile bildirir — dilsiz projede yazılan çeviriler runtime'da ham anahtar olarak çizilir (S-15). templateOptions verildiğinde TOHUM atlanır (template kendi dil ayarını taşır); açıkça verdiğiniz diller o yolda da gönderilir.`,
145
173
  inputSchema: {
146
174
  name: z.string().min(1),
147
- slug: z.string().min(1).describe('Organizasyonda tekil; veritabanı adı zeyrek_<org>_<slug> bundan türer ve sonradan değiştirmek bütün runtime rotalarını kırar'),
175
+ slug: z.string().min(1).describe("Veritabanı adı zeyrek_<org>_<slug> ve rol adı bundan türer: 2–40 karakter, yalnız küçük harf ve rakam, parçalar arasında tek '-' ya da '_' ('__' yasak); organizasyonlar arası tekil (PRJ-005). Sonradan değiştirmek bütün runtime rotalarını kırar"),
148
176
  theme: themeInput.optional().describe("Tema nesnesi (ya da JSON metni) — Studio tema panelinin yazdığı kayıtla aynı şekil: mode · palette · paletteDark · fontFamily · typeScale · shadow · presets · componentPresets. Verilmezse proje platformun varsayılan temasıyla (accent #0052CC, Inter 14px) doğar. Kurulmuş bir projenin temasını DEĞİŞTİRMEK için theme_update: kısmi günceller, şema/lint'ten geçirir ve hangi CSS değişkeninin değiştiğini söyler"),
149
177
  languages: z.array(z.string().min(1)).min(1).optional().describe(`Proje dilleri (ISO 639-1; 'tr-TR' gibi bölgeli değer kabul edilir ve 'tr'ye indirgenir, indirgeme gatewayNormalized ile bildirilir). Verilmezse gateway [${SEED_LANGUAGES.join(', ')}] tohumlar — projenin dilsiz doğmasına ve çevirilerin sessizce çözülmemesine izin verilmez`),
150
178
  defaultLanguage: z.string().min(1).optional().describe(`languages içinde olmalı (indirgenmiş hâliyle karşılaştırılır); yalnız defaultLanguage verirseniz languages onunla kurulur. İkisi de yoksa '${SEED_DEFAULT_LANGUAGE}'`),
151
179
  application: applicationInput.optional(),
152
- templateOptions: templateOptionsInput.optional().describe('Verilirse proje template\'ten kurulur (asenkron); sonucu project_list ile izleyin')
180
+ templateOptions: templateOptionsInput.optional().describe('Verilirse proje template\'ten kurulur (asenkron); sonucu project_list ile izleyin'),
181
+ accessModel: z.enum(['simple', 'complex']).optional().describe("Proje türü; varsayılan 'simple' (bugünkü davranış: kimlik ve yetkiyi uygulamanın kendi middleware'i kurar). 'complex': proje organizasyonun YAYINLI SON rol şemasının kopyasıyla doğar (yayın yoksa organization_units/organization_positions boştur — ORG-002), platform yönetimli uygulama oturumu ve karar motoru devrededir, yeni ortamların zorlama modu 'enforce'tur; şema tablolarına genel satır yazımı kapalıdır (ORG-001). templateOptions ile verilirse yok sayılır — template kaynak projenin türünü taşır")
153
182
  },
154
183
  annotations: writes
155
184
  }, handle(config, async (args) => {
@@ -159,7 +188,7 @@ export function registerProjectTools(server, platform) {
159
188
  if (!languages.codes.includes(defaultLanguage.code)) {
160
189
  throw new PlatformApiError(`project_create: defaultLanguage '${defaultLanguage.code}' languages listesinde (${languages.codes.join(', ')}) yok — platform bunu 422 ile reddeder (PRJ-002). Kodu listeye ekleyin ya da listedeki bir kodu varsayılan yapın.`, 422);
161
190
  }
162
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'create' }));
191
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'create' }), false);
163
192
  // PRJ-001: slug çakışması 2026-09-08 öncesi build'de 500 'DatabaseNameAlreadyExistsException'
164
193
  // dönüyordu (3. koşu S-1); arama ucu düşerse ön kontrol atlanır, karar platforma kalır.
165
194
  const existing = rows(await platform.org('GET', '/projects', { query: { searchTerm: args.slug, pageLength: 200 } }).catch(() => undefined))
@@ -189,6 +218,14 @@ export function registerProjectTools(server, platform) {
189
218
  form.set('TemplateOptions.TemplateReceiveUpdates', String(args.templateOptions.templateReceiveUpdates));
190
219
  }
191
220
  }
221
+ // Template yolunda platform kaynak projenin türünü kullanır ve istemcinin gönderdiğini yok
222
+ // sayar (05 §2); göndermek "complex istedim" yanılgısı üretirdi, gönderilmez ve söylenir.
223
+ if (args.accessModel && args.templateOptions) {
224
+ notes.push(`accessModel '${args.accessModel}' gönderilmedi: template yolunda proje türü template'in kaynak projesinden gelir. Sonucu project_get accessModel alanından doğrulayın.`);
225
+ }
226
+ else if (args.accessModel) {
227
+ form.set('AccessModel', args.accessModel);
228
+ }
192
229
  // Dil artık create ucunun kendi alanı (CreateOrganizationProjectCommand.Languages /
193
230
  // DefaultLanguage); ayrı bir PUT'a gerek yok. Template yolunda yalnız TOHUM atlanır: template
194
231
  // kendi dil ayarını getiriyor ve uydurulmuş bir varsayılanla onu ezmek yanlış olur — çağıranın
@@ -203,7 +240,29 @@ export function registerProjectTools(server, platform) {
203
240
  notes.push(`Proje dilleri gateway tarafından varsayılanla kuruldu: languages [${languages.codes.join(', ')}], defaultLanguage '${defaultLanguage.code}'. Dilsiz projede yazılan çeviriler runtime'da ham anahtar olarak çizilir (S-15). Başka dil istiyorsanız project_update ile değiştirin.`);
204
241
  }
205
242
  }
206
- return attachFindings(withNormalization(await platform.org('POST', '/projects', { form }), notes), findings);
243
+ const created = await platform.org('POST', '/projects', { form });
244
+ const schema = (created?.data ?? {});
245
+ if (schema.accessModel === 'complex') {
246
+ // Oluşturma yanıtı 2026-09-15'ten beri sürümü taşır (TB-67 backend'de çözüldü); daha eski build'lerde
247
+ // OrganizationProjectDto.Map sürümsüz çağrılıp alan null gelir — "yayın yok" demeden önce id ucu bir kez okunur.
248
+ let latestVersion = schema.organizationSchema?.latestVersion;
249
+ let versionUnknown = false;
250
+ if ((latestVersion === undefined || latestVersion === null) && typeof schema.id === 'string') {
251
+ try {
252
+ const byId = await platform.org('GET', `/projects/${schema.id}`);
253
+ latestVersion = byId?.data?.organizationSchema?.latestVersion;
254
+ }
255
+ catch {
256
+ versionUnknown = true;
257
+ }
258
+ }
259
+ notes.push(versionUnknown
260
+ ? "Kompleks proje açıldı; organizasyonun yayınlı şema sürümü doğrulanamadı (oluşturma yanıtında latestVersion yok, GET /projects/{id} okunamadı). Organizasyon tablolarının dolu doğup doğmadığını organization_schema_get ve row_list('organization_units') ile doğrulayın (ORG-002, TB-67)."
261
+ : latestVersion === undefined || latestVersion === null
262
+ ? "Kompleks proje açıldı ama organizasyonda YAYINLI şema yok: organization_units / organization_positions tabloları boş doğdu (ORG-002). Şema yönetim konsolunda yayınlanınca abone projeye senkronla gelir; durumu organization_schema_get ve project_get organizationSchema ile izleyin."
263
+ : `Kompleks proje organizasyon şemasının ${String(latestVersion)}. sürümüne abone doğdu; kopya her ortama senkronla yazılır (asenkron — project_get organizationSchema.appliedVersion dolunca tamamdır). Şema tablolarına row_* yazımı kapalıdır (ORG-001); ağacı row_list ile okuyun.`);
264
+ }
265
+ return attachFindings(withNormalization(created, notes), findings);
207
266
  }));
208
267
  server.registerTool('project_update', {
209
268
  title: 'Proje güncelle',
@@ -214,11 +273,12 @@ export function registerProjectTools(server, platform) {
214
273
  slug: z.string().optional(),
215
274
  theme: themeInput.optional().describe("Tema nesnesi (ya da JSON metni); verilen kayıt mevcut temanın YERİNE geçer — bir alanı değiştirmek için theme_update kullanın (kısmi birleştirir, lint'ten geçirir). VERİLMEZSE mevcut tema korunur — gateway projeden okuyup gövdeye geri koyar, çünkü platform Theme alanı gövdede yokken temayı siler ve silinen tema bütün ortamların runtime'ına iner (S-1). Temayı gerçekten temizlemek için theme: {} verin"),
216
275
  languages: z.array(languageCode).min(1).optional().describe("ISO 639-1 iki harfli kod listesi (proje dilleri, platformun 184 kodluk sözlüğünden — PRJ-002). Runtime çevirileri YALNIZ bu kodlarla çözer; liste kurulmadan yazılan çeviriler 200 döner ama ekrana ham anahtar basılır (S-15). Yeni proje bu liste BOŞ doğar — çeviri kullanacaksanız burayı kurmak ilk adımdır"),
217
- defaultLanguage: languageCode.optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir. Runtime kayıtlı dil seçimi yokken buna düşer; boşsa aktif dil hiç seçilmez ve ilk açılışta bütün etiketler ham anahtar çizilir")
276
+ defaultLanguage: languageCode.optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir. Runtime kayıtlı dil seçimi yokken buna düşer; boşsa aktif dil hiç seçilmez ve ilk açılışta bütün etiketler ham anahtar çizilir"),
277
+ revealReasonMinLength: z.number().int().min(0).max(1000).optional().describe("Gerekçeli açmada (access_reveal) kabul edilen en kısa gerekçe; 0 kuralı kapatır. VERİLMEZSE mevcut değer korunur. Runtime reveal yolunda (access_reveal) yalnız kompleks projede etkilidir, Studio ızgara reveal'inde HER projede zorlanır; daha kısa gerekçe 400 RevealReasonTooShort alır ve denetim defterine REVEAL-DENIED düşer (TB-98)")
218
278
  },
219
279
  annotations: updates
220
280
  }, handle(config, async (args) => {
221
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'update' }));
281
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'update' }), false);
222
282
  // Çağıranın hatası okumadan ÖNCE döner: bozuk tema metni için projeyi çekmenin anlamı yok.
223
283
  const requestedTheme = themeField(args.theme, 'project_update');
224
284
  // Platform gövdede slug yoksa onu ADDAN yeniden üretiyor. Yalnız dil ayarını değiştiren bir
@@ -242,6 +302,8 @@ export function registerProjectTools(server, platform) {
242
302
  languages?.codes.forEach(code => form.append('Languages', code));
243
303
  if (defaultLanguage)
244
304
  form.set('DefaultLanguage', defaultLanguage.code);
305
+ if (args.revealReasonMinLength !== undefined)
306
+ form.set('RevealReasonMinLength', String(args.revealReasonMinLength));
245
307
  const payload = await platform.org('PUT', `/projects/${args.id}`, { form });
246
308
  const notes = [
247
309
  ...(languages?.notes ?? []),
@@ -3,16 +3,19 @@ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environme
3
3
  import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
4
4
  import { withNormalization } from '../knowledge/normalize.js';
5
5
  import { RefResolver } from '../platform/refs.js';
6
- import { attachFindings, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
7
- import { policyRules } from '../knowledge/rules.js';
6
+ import { attachFindings, applyPolicyMode, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
7
+ import { protectedFilterFindings, systemEntityWriteFindings, systemManagedEntity } from '../knowledge/access.js';
8
+ import { filterOperatorError, policyRules } from '../knowledge/rules.js';
8
9
  import { languageHeader } from '../knowledge/languageCodes.js';
9
10
  import { handle } from '../platform/responses.js';
10
11
  import { uploadFile } from './files.js';
11
12
  import { PlatformApiError } from '../platform/errors.js';
12
13
  import { secretKeyPattern } from '../platform/secrets.js';
13
14
  import { csvRows } from '../platform/csv.js';
15
+ import { rowFilterSchema } from '../knowledge/rowFilter.js';
14
16
  import { readFile, stat, writeFile } from 'node:fs/promises';
15
17
  import { extname, resolve } from 'node:path';
18
+ import { jsonArray } from '../platform/jsonArray.js';
16
19
  import { z } from 'zod';
17
20
  const rowValues = z.record(z.unknown()).describe("Alan SLUG'ı → değer sözlüğü; anahtarlar entity_get çıktısındaki field SLUG'larıdır (name değil — name kullanmak 'Please enter a valid value' hatası verir)");
18
21
  /**
@@ -35,6 +38,17 @@ function derivedRowFindings(rows) {
35
38
  guide: 'ngapps://guides/entity'
36
39
  }];
37
40
  }
41
+ /**
42
+ * ORG-001'in çözüm sonrası yarısı. Backend yalnız entity id'sine bakar (`SystemManagedEntityIds`); ön
43
+ * denetim GUID'i, öneksiz slug'ı ve yerleşik datasource önekini istek gitmeden tanır, başka adla yazılmış
44
+ * önek ise ancak çözülen id'yle anlaşılır. Dış datasource'taki aynı adlı tablo (`ik/user_positions`) başka
45
+ * bir id'ye çözüldüğü için geçer. Ön denetim zaten eşleştiyse bulgu ikinci kez yazılmaz.
46
+ */
47
+ function resolvedSystemEntityFindings(config, entityId, resolvedEntityId, operation) {
48
+ if (config.policyMode === 'off' || systemManagedEntity(entityId))
49
+ return [];
50
+ return enforcePolicy(applyPolicyMode(config, systemEntityWriteFindings(resolvedEntityId, operation)), false);
51
+ }
38
52
  const bulkBatchSize = 1000;
39
53
  const maxPathBytes = 50 * 1024 * 1024;
40
54
  const maxPathRows = 100_000;
@@ -157,8 +171,8 @@ const templateFormatNotes = {
157
171
  csv: 'csv şablonda ipucu satırı ve `#zeyrek` satırı yoktur; başlıklar alan adıdır.',
158
172
  json: 'JSON şablon `{ meta, data }` gövdesidir; `meta` analizde atlanır, satırlar `data`dadır ve anahtarları alan slug\'ıdır.'
159
173
  };
160
- const rowFiltersParam = z.array(z.record(z.unknown())).optional()
161
- .describe('Platform Filter dizisi; biçim için row_list\'i filtresiz çağırıp Studio\'daki bir filtreyi referans alın');
174
+ const rowFiltersParam = jsonArray(z.array(rowFilterSchema())).optional()
175
+ .describe("Platform Filter dizisi: tekil { filterType: 'single', field, operator, value } ya da grup { filterType: 'group', conditionType: 'and'|'or', children: [...] }; işleçler platform enum'undan (ngapps://schemas/Filter)");
162
176
  const exportScopeParam = z.object({
163
177
  kind: z.enum(['view', 'all', 'selected']).describe("'view' filtre + sıralama ile, 'all' tüm satırlar, 'selected' rowIds listesi"),
164
178
  filters: rowFiltersParam,
@@ -216,7 +230,7 @@ export function registerRowTools(server, platform) {
216
230
  project: projectParam,
217
231
  environment: environmentParam,
218
232
  entityId: entityRef,
219
- selectedFields: z.array(z.string()).optional().describe('Dönen satırlar bu alan adlarına indirgenir, id her zaman dahildir; platform tam satırı hesapladığı için kırpma gateway tarafında yapılır'),
233
+ selectedFields: z.array(z.string()).optional().describe('Dönen satırlar bu alan adlarına indirgenir, id her zaman dahildir; platform tam satırı hesapladığı için kırpma gateway tarafında yapılır. Bilinmeyen alan adı platformda 400 UnknownField döner (id ve sys_* bilinen sayılır) — sessizce düşmez.'),
220
234
  filters: rowFiltersParam,
221
235
  language: languageParam,
222
236
  includeTranslations: includeTranslationsParam,
@@ -224,26 +238,32 @@ export function registerRowTools(server, platform) {
224
238
  },
225
239
  annotations: readOnly
226
240
  }, handle(config, async ({ project, environment, entityId, language, ...body }) => {
241
+ const operatorError = filterOperatorError('row_list', body.filters);
242
+ if (operatorError)
243
+ throw new PlatformApiError(operatorError, 400);
227
244
  const notes = [];
228
245
  const headers = languageHeader(language, notes);
229
246
  const resolver = new RefResolver(platform, project, environment);
230
247
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
248
+ // PROT-001 / PROT-002: korumalı alanda metin araması ve sıralama platformda 400'dür; entity
249
+ // yalnız riskli bir işleç ya da sıralama varsa okunur.
250
+ const protectionFindings = enforcePolicy(applyPolicyMode(config, await protectedFilterFindings(platform, resolver, entityId, body)), false);
231
251
  const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/list`, { environment, body, headers });
232
252
  const wanted = body.selectedFields ?? [];
233
253
  const rows = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.data : undefined;
234
254
  if (!wanted.length || !Array.isArray(rows))
235
- return withNormalization(withResolved(payload, resolver.resolved), notes);
255
+ return attachFindings(withNormalization(withResolved(payload, resolver.resolved), notes), protectionFindings);
236
256
  /**
237
257
  * 'localizations' keep listesine girmezse includeTranslations'ın getirdiği çeviriler tam da
238
258
  * onları isteyen çağrıda kırpılırdı: iki parametre birlikte verildiğinde sessizce boşa çıkar.
239
259
  */
240
260
  const keep = new Set([...wanted.map(field => field.toLowerCase()), 'id', ...(body.includeTranslations ? ['localizations'] : [])]);
241
- return withNormalization(withResolved({
261
+ return attachFindings(withNormalization(withResolved({
242
262
  ...payload,
243
263
  data: rows.map(row => row && typeof row === 'object' && !Array.isArray(row)
244
264
  ? Object.fromEntries(Object.entries(row).filter(([key]) => keep.has(key.toLowerCase())))
245
265
  : row)
246
- }, resolver.resolved), notes);
266
+ }, resolver.resolved), notes), protectionFindings);
247
267
  }));
248
268
  server.registerTool('row_get', {
249
269
  title: 'Satır getir',
@@ -286,17 +306,17 @@ export function registerRowTools(server, platform) {
286
306
  const wantedLocalizations = localizations && Object.keys(localizations).length > 0 ? localizations : undefined;
287
307
  const fileRows = path ? await readRowsFromPath('row_create', path) : undefined;
288
308
  const bulkRows = rows ?? fileRows;
289
- const shapeFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', bulkRows ? { rows: bulkRows } : { row }, { operation: 'create' }));
309
+ const inputFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', { entityId, ...(bulkRows ? { rows: bulkRows } : { row }) }, { operation: 'create' }), false);
290
310
  const resolver = new RefResolver(platform, project, environment);
311
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
312
+ const shapeFindings = [...inputFindings, ...resolvedSystemEntityFindings(config, entityId, resolvedEntityId, 'create')];
291
313
  if (bulkRows) {
292
- const bulkEntityId = await resolveEntityId(resolver, entityId);
293
314
  const bulkDrift = unmappedMembers('CreateBulkRowCommand', { rows: bulkRows, ...(rowContext ? { rowContext } : {}), ...(relationsByName ? { relationsByName } : {}) });
294
- const outcome = await writeInBatches(platform, 'POST', project, environment, headers, bulkEntityId, bulkRows, rowContext, relationsByName);
315
+ const outcome = await writeInBatches(platform, 'POST', project, environment, headers, resolvedEntityId, bulkRows, rowContext, relationsByName);
295
316
  if (fileRows)
296
317
  notes.push(`${path} okundu: ${fileRows.length} satır, ${Math.ceil(fileRows.length / bulkBatchSize)} parti.`);
297
318
  return attachFindings(withUnmappedMembers(withNormalization(withResolved(outcome, resolver.resolved), notes), bulkDrift), [...shapeFindings, ...(fileRows ? [] : derivedRowFindings(bulkRows))]);
298
319
  }
299
- const resolvedEntityId = await resolveEntityId(resolver, entityId);
300
320
  const body = { row, ...(wantedLocalizations ? { localizations: wantedLocalizations } : {}), ...(rowContext ? { rowContext } : {}) };
301
321
  const drift = unmappedMembers('CreateRowCommand', body);
302
322
  const response = await platform.tenantWithHeaders('POST', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body });
@@ -354,10 +374,11 @@ export function registerRowTools(server, platform) {
354
374
  const notes = [];
355
375
  const fileRows = path ? await readRowsFromPath('row_update', path) : undefined;
356
376
  const bulkRows = rows ?? fileRows;
357
- const shapeFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', bulkRows ? { rows: bulkRows } : { row }, { operation: 'update' }));
377
+ const inputFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', { entityId, ...(bulkRows ? { rows: bulkRows } : { row }) }, { operation: 'update' }), false);
358
378
  const headers = languageHeader(language, notes);
359
379
  const resolver = new RefResolver(platform, project, environment);
360
380
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
381
+ const shapeFindings = [...inputFindings, ...resolvedSystemEntityFindings(config, entityId, resolvedEntityId, 'update')];
361
382
  if (bulkRows) {
362
383
  const bulkDrift = unmappedMembers('UpdateBulkRowCommand', { rows: bulkRows, ...(rowContext ? { rowContext } : {}), ...(relationsByName ? { relationsByName } : {}) });
363
384
  const outcome = await writeInBatches(platform, 'PUT', project, environment, headers, resolvedEntityId, bulkRows, rowContext, relationsByName);
@@ -369,6 +390,36 @@ export function registerRowTools(server, platform) {
369
390
  const drift = unmappedMembers('UpdateRowCommand', body);
370
391
  return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body }), resolver.resolved), notes), drift), shapeFindings);
371
392
  }));
393
+ server.registerTool('row_reassign', {
394
+ title: 'Satırların sahibini değiştir',
395
+ description: "Satırların SAHİPLİK alanlarını (sys_owner_unit_id · sys_owner_user_id) değiştirir (POST /{project}/entities/{entityId}/row/reassign). Bu alanlar row_update ile YAZILMAZ: gövdede görülürse istek 400 OwnershipFieldNotWritable alır (eskiden sessizce düşüyordu, S2-7). Hedef satırlar 'rowIds' ile ya da 'filter' ile seçilir; en az biri zorunlu. En az bir hedef sahip verilmelidir: ownerUnitId birime, ownerUserId kişiye devreder — yalnız ownerUserId verilirse birim de o kişinin aktif birincil pozisyonundan türetilir (pozisyonu yoksa satırın birimi olduğu gibi kalır). Her satıra REASSIGN denetim kaydı düşer (eski sahip → yeni sahip). KİMLİK: bu uç öteki row_* araçları gibi bir STUDIO ucudur ve gateway'in platform kimliğiyle koşar — uygulama jetonu (asUser) burada geçmez, 401 alır. Uygulama KULLANICISININ kapsamıyla devretmek (ve 'entity.reassign' kapsamını gerçekten ölçmek) için aynı işi yapan sistem operasyonunu kullanın: systemOperation 'entity.row.reassign' olan bir uç açıp microservice_execute { asUser } ile çağırın. Kapsam kuralı orada işler: çağıran yalnız 'entity.reassign' grant'inin satır kapsamındaki satırları devredebilir; kapsamı unit ya da unit-tree olan çağıran için SAHİPSİZ satırlar (sys_owner_unit_id NULL) da kapsam içi sayılır — şubesi kapanan kayıtlara başka türlü kimse ulaşamazdı (06 §A).",
396
+ inputSchema: {
397
+ project: projectParam,
398
+ environment: environmentParam,
399
+ entityId: entityRef,
400
+ rowIds: z.array(z.string().min(1)).optional().describe("Devredilecek satırların birincil anahtar değerleri; 'filter' ile birlikte verilmez"),
401
+ filter: rowFiltersParam.describe("Devredilecek satırları seçen Platform Filter dizisi (row_list ile aynı biçim); 'rowIds' ile birlikte verilmez"),
402
+ ownerUnitId: guid.optional().describe("Yeni sahip birim (organization_tree_get); verilmezse ownerUserId'nin birimi yazılır"),
403
+ ownerUserId: guid.optional().describe('Yeni sahip kullanıcı (user_list); verilmezse satırın kullanıcı sahibi olduğu gibi kalır')
404
+ },
405
+ annotations: updates
406
+ }, handle(config, async ({ project, environment, entityId, rowIds, filter, ownerUnitId, ownerUserId }) => {
407
+ if (rowIds?.length && filter?.length)
408
+ throw new Error("row_reassign: 'rowIds' ve 'filter' birlikte verilmez — satırları ya id listesiyle ya filtreyle seçin.");
409
+ if (!rowIds?.length && !filter?.length)
410
+ throw new Error("row_reassign: hedef satır seçilmedi — 'rowIds' ya da 'filter' verin.");
411
+ if (!ownerUnitId && !ownerUserId)
412
+ throw new Error("row_reassign: hedef sahip verilmedi — 'ownerUnitId' ve/veya 'ownerUserId' verin.");
413
+ const resolver = new RefResolver(platform, project, environment);
414
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
415
+ const body = {
416
+ ...(rowIds?.length ? { rowIds } : {}),
417
+ ...(filter?.length ? { filter } : {}),
418
+ ...(ownerUnitId ? { ownerUnitId } : {}),
419
+ ...(ownerUserId ? { ownerUserId } : {})
420
+ };
421
+ return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/reassign`, { environment, body }), resolver.resolved);
422
+ }));
372
423
  server.registerTool('row_export', {
373
424
  title: 'Satırları dışa aktar',
374
425
  description: "Bir tablonun satırlarını xlsx / csv / json dosyasına yazar ve dosyayı projenin SÜRÜCÜSÜNE ('Dışa aktarmalar' klasörü) koyar; yanıt yalnız fileId ve sayılardır — dosya içeriği ajanın bağlamına HİÇ girmez, içeriği görmek için file_download kullanın. Kapsam üç şekildedir: 'view' (filtre + sıralama), 'all', 'selected' (rowIds). 5.000 satırın üstünde platform işi arka plana alır ve 202 ile jobId döner; import_status { jobId } ile izlenir, biten işin dosyası result.reportFileId'dedir. CSV UTF-8 BOM'lu yazılır; ayraç ve ondalık bir yerel ayar profilinden gelir (tr-TR `;` · `1,5`, en-US `,` · `1.5`): dışa aktarmada projenin dili seçer, şablonda 'locale' ile seçilir. 'reimportable: true' dosyaya id sütununu ve _zeyrek meta sayfasını ekler: dosya düzenlenip import_analyze → import_plan { mode: 'upsert', keyField: 'id' } ile geri yüklenebilir (Akış C). 'template: true' tablo satırı yazmaz: tablonun alanlarından İKİ SENTETİK örnek satırlı bir şablon üretir ve değiştirilmeden geri yüklenebilir. 'template: true, inline: true' şablonun şeklini (sütunlar, anahtar önerisi, iki satır) yanıta da koyar — toplu yazımdan önce dosyanın nasıl görünmesi gerektiğini indirmeden görmenin yolu budur; satır satır row_create yerine şablon başlıklarıyla CSV → file_upload { text } → import_analyze → import_plan → import_run, reçete platform_guide('entity') §'Dışa aktarma ve geri yükleme'.",
@@ -400,6 +451,9 @@ export function registerRowTools(server, platform) {
400
451
  if ((scope?.rowIds?.length ?? 0) > maxSelectedRowIds) {
401
452
  throw new PlatformApiError(`row_export: ${scope?.rowIds?.length} satır id'si, ${maxSelectedRowIds} sınırının üstünde. Bu kadar satırı seçerek değil filtreyle verin: scope { kind: 'view', filters: [...] }.`);
402
453
  }
454
+ const operatorError = filterOperatorError('row_export', scope?.filters);
455
+ if (operatorError)
456
+ throw new PlatformApiError(operatorError, 400);
403
457
  const resolvedFormat = format ?? 'xlsx';
404
458
  const notes = [];
405
459
  const resolver = new RefResolver(platform, project, environment);
@@ -521,8 +575,10 @@ export function registerRowTools(server, platform) {
521
575
  throw new Error("'id' ve 'ids' birlikte verilemez.");
522
576
  if (!id && !ids)
523
577
  throw new Error("'id' ya da 'ids' parametrelerinden biri zorunlu.");
578
+ enforcePolicy(evaluatePolicy(config, policyRules, 'row', { entityId }, { operation: 'delete' }), false);
524
579
  const resolver = new RefResolver(platform, project, environment);
525
580
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
581
+ resolvedSystemEntityFindings(config, entityId, resolvedEntityId, 'delete');
526
582
  if (ids)
527
583
  return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/bulk/delete`, { environment, body: { rowIds: ids } }), resolver.resolved);
528
584
  return withResolved(await platform.tenant('DELETE', project, `/entities/${resolvedEntityId}/row/${encodeURIComponent(String(id))}`, { environment }), resolver.resolved);