@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +9 -1
  3. package/README.md +14 -38
  4. package/dist/config.js +36 -3
  5. package/dist/knowledge/backendConstraints.js +982 -0
  6. package/dist/knowledge/componentCatalog.js +549 -92
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/coverage.js +43 -0
  9. package/dist/knowledge/flowGuards.js +30 -0
  10. package/dist/knowledge/functionGuards.js +134 -0
  11. package/dist/knowledge/guideSections.js +49 -0
  12. package/dist/knowledge/hydrate.js +97 -0
  13. package/dist/knowledge/languageCodes.js +36 -0
  14. package/dist/knowledge/languages.js +105 -0
  15. package/dist/knowledge/layoutSummary.js +10 -0
  16. package/dist/knowledge/localization.js +51 -0
  17. package/dist/knowledge/normalize.js +500 -16
  18. package/dist/knowledge/outlines.js +167 -12
  19. package/dist/knowledge/policy.js +14 -5
  20. package/dist/knowledge/propTypes.js +80 -0
  21. package/dist/knowledge/rules.js +961 -44
  22. package/dist/knowledge/schemaGuard.js +199 -0
  23. package/dist/knowledge/styleResolve.js +63 -4
  24. package/dist/observability/trace.js +27 -3
  25. package/dist/platform/csv.js +123 -0
  26. package/dist/platform/errors.js +107 -6
  27. package/dist/platform/http.js +34 -4
  28. package/dist/platform/patch.js +27 -0
  29. package/dist/platform/refs.js +104 -0
  30. package/dist/platform/responses.js +25 -6
  31. package/dist/platform/screenshot.js +544 -16
  32. package/dist/platform/secrets.js +40 -0
  33. package/dist/platform/tokenVault.js +89 -0
  34. package/dist/platform/writeDiff.js +281 -0
  35. package/dist/resources/examples/flow-call-function.json +66 -0
  36. package/dist/resources/examples/function-total.json +70 -0
  37. package/dist/resources/examples/layout-app-shell.json +1 -1
  38. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  39. package/dist/resources/examples/microservice-table-list.json +2 -4
  40. package/dist/resources/examples/page-dynamic-form.json +3 -3
  41. package/dist/resources/examples/page-flex-sections.json +99 -0
  42. package/dist/resources/examples/page-sign-in.json +4 -3
  43. package/dist/resources/guides/component.md +81 -8
  44. package/dist/resources/guides/entity.md +712 -20
  45. package/dist/resources/guides/expressions.md +47 -2
  46. package/dist/resources/guides/external-component.md +9 -0
  47. package/dist/resources/guides/functions.md +262 -0
  48. package/dist/resources/guides/getting-started.md +82 -7
  49. package/dist/resources/guides/layout.md +53 -10
  50. package/dist/resources/guides/merge.md +231 -0
  51. package/dist/resources/guides/microfunction.md +166 -0
  52. package/dist/resources/guides/microservice.md +287 -15
  53. package/dist/resources/guides/middleware.md +89 -1
  54. package/dist/resources/guides/modify-existing.md +23 -0
  55. package/dist/resources/guides/page.md +402 -49
  56. package/dist/resources/guides/translation.md +150 -8
  57. package/dist/resources/guides/view.md +16 -1
  58. package/dist/resources/guides/workflow.md +199 -28
  59. package/dist/resources/knowledge.js +5 -0
  60. package/dist/resources/rules.js +27 -0
  61. package/dist/resources/schema-fingerprint.json +8 -0
  62. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  63. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  64. package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
  65. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  66. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  67. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  68. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  69. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  70. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  71. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  72. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  73. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  74. package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
  75. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  76. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  77. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  78. package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
  79. package/dist/resources/schemas/DataJobDto.json +267 -0
  80. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  81. package/dist/resources/schemas/Filter.json +3 -1
  82. package/dist/resources/schemas/GetRowsQuery.json +6 -1
  83. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  84. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  85. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  86. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  87. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  88. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  89. package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
  90. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  91. package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
  92. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  93. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  94. package/dist/resources/schemas/RunImportCommand.json +62 -0
  95. package/dist/resources/schemas/StepDto.json +34 -3
  96. package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
  97. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  98. package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
  99. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  100. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  101. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  102. package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
  103. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  104. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  105. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  106. package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
  107. package/dist/server.js +17 -1
  108. package/dist/tools/access.js +3 -3
  109. package/dist/tools/activities.js +40 -0
  110. package/dist/tools/applications.js +1 -1
  111. package/dist/tools/automations.js +76 -34
  112. package/dist/tools/common.js +55 -0
  113. package/dist/tools/components.js +16 -7
  114. package/dist/tools/datasources.js +83 -21
  115. package/dist/tools/dependencies.js +1 -1
  116. package/dist/tools/deployments.js +34 -2
  117. package/dist/tools/dtos.js +25 -12
  118. package/dist/tools/entities.js +113 -28
  119. package/dist/tools/environments.js +52 -0
  120. package/dist/tools/externalComponents.js +9 -3
  121. package/dist/tools/files.js +57 -42
  122. package/dist/tools/flows.js +205 -13
  123. package/dist/tools/folders.js +7 -2
  124. package/dist/tools/functions.js +331 -0
  125. package/dist/tools/globalFunctions.js +63 -0
  126. package/dist/tools/importSchemas.js +79 -0
  127. package/dist/tools/imports.js +241 -0
  128. package/dist/tools/layouts.js +103 -29
  129. package/dist/tools/merges.js +113 -0
  130. package/dist/tools/microfunctions.js +155 -0
  131. package/dist/tools/microservices.js +270 -40
  132. package/dist/tools/organizations.js +5 -6
  133. package/dist/tools/pages.js +421 -104
  134. package/dist/tools/platformInfo.js +155 -11
  135. package/dist/tools/projects.js +183 -33
  136. package/dist/tools/queries.js +32 -10
  137. package/dist/tools/revisions.js +94 -0
  138. package/dist/tools/rows.js +484 -50
  139. package/dist/tools/screenWrite.js +68 -0
  140. package/dist/tools/screens.js +28 -0
  141. package/dist/tools/templates.js +32 -5
  142. package/dist/tools/translations.js +24 -8
  143. package/dist/tools/views.js +20 -10
  144. package/dist/tools/workflows.js +56 -21
  145. package/package.json +7 -2
@@ -0,0 +1,155 @@
1
+ import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
3
+ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
4
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
5
+ import { policyRules } from '../knowledge/rules.js';
6
+ import { handle } from '../platform/responses.js';
7
+ import { z } from 'zod';
8
+ const policyOverrideParam = z.boolean().optional()
9
+ .describe("true: politika block bulgularını warn'a indirir (bilinçli istisna; yanıtta işaretlenir)");
10
+ const behaviorParam = z.enum(['primitive', 'object', 'array']);
11
+ const statusParam = z.enum(['draft', 'publishing', 'published', 'publish-failed']);
12
+ const environmentVariablesParam = z.record(z.string())
13
+ .describe("Runtime'a geçen env değişkenleri; anahtar ^[A-Z_][A-Z0-9_]*$ ve toplam ≤ 4 KB (Lambda sınırı). Fonksiyon bunları Node'da process.env, Python'da os.environ üzerinden okur — SIR DEĞER KOYMAYIN: kayıt düz metin tutulur");
14
+ // Create ve Update aynı gövdeyi taşır (Update yalnız id ekler): iki tool'un şeması tek yerde durur,
15
+ // backend komutlarındaki alan çifti ayrışırsa `unmappedMembers` iki tool'da da aynı anda konuşur.
16
+ const definitionInput = {
17
+ name: z.string().min(1).max(255).describe("Fonksiyon adı; tenant içinde BENZERSİZ. Slug bundan türetilir ve runtime fonksiyon adına girer (zf-{proje}-{ortam}-{slug}). SLUG DA BENZERSİZDİR: 'Sipariş' ile 'Siparis' farklı adlardır ama aynı slug'ı üretir ve ikincisi 409 alır"),
18
+ description: z.string().optional().describe('Fonksiyonun ne yaptığı; Studio listesinde görünür, çalışma zamanını etkilemez'),
19
+ language: z.enum(['node', 'python']).describe("Çalışma dili. Inline kaynakta dosya adı sabittir: node → function.js (exports.handler = async (args, context) => …), python → function.py (def handler(args, context))"),
20
+ provider: z.enum(['built-in', 'aws-lambda']).describe("'built-in' platformun kendi Fission kümesinde koşar ve runtime hesabı İSTEMEZ (runtimeId boş kalır); 'aws-lambda' müşterinin kendi hesabında koşar ve runtimeId ZORUNLUDUR. 'azure-functions' enum'da tanımlı ama v1'de reddedilir (Faz 2)"),
21
+ runtimeId: guid.optional().describe("AWS Lambda hesabı (microfunction_runtime_list). provider 'built-in' ise VERİLMEZ; 'aws-lambda' ise zorunlu ve hesabın provider'ı fonksiyonunkiyle aynı olmalı"),
22
+ sourceKind: z.enum(['inline', 'archive']).describe("'inline' ⇒ code zorunlu (≤ 512 KB, standart kütüphane; bağımlılık YOK); 'archive' ⇒ önce kayıt açılır, zip Studio'dan yüklenir (MCP'de zip yükleme yok — Faz 2) ve entrypoint 'modül.fonksiyon' olarak verilir"),
23
+ code: z.string().optional().describe("Inline kaynak kodu. Sözleşme: tek bir handler dışa aktarılır, args çağrı argümanları, context SALT OKUNUR anlık görüntüdür (input/variables/steps/user/request/language/project/environment). Yazma yalnız dönüş değeriyle olur. Ayrıntı: platform_guide('microfunction')"),
24
+ entrypoint: z.string().optional().describe("Zip kaynağında 'modül.fonksiyon' (ör. 'handlers/sum.handler'). Inline kaynakta gateway'in de platformun da beklediği sabit 'function.handler'dır — vermeyin"),
25
+ parameterDtoId: guid.optional().describe("Çağrı argümanlarının sözleşmesi (DataTransferObject). Workflow step'i ifadeyi bu şemaya göre yazar; boşsa args = {} gider"),
26
+ parameterBehavior: behaviorParam.optional().describe('parameterDtoId verildiyse zorunlu: primitive | object | array'),
27
+ returnDtoId: guid.optional().describe('Dönüş sözleşmesi; step çıktısının şeması bundan üretilir'),
28
+ returnBehavior: behaviorParam.optional().describe('returnDtoId verildiyse zorunlu: primitive | object | array'),
29
+ timeoutSeconds: z.number().int().min(1).max(900).optional().describe("Fonksiyon bütçesi; varsayılan 30. Built-In üst sınırı 300, Lambda 900. Bütçe dolarsa çağrı MicrofunctionInvocationTimeoutException ile step'in fail dalına düşer"),
30
+ memoryMb: z.number().int().min(128).max(3008).optional().describe("Varsayılan 256. Built-In'de yalnız kaydedilir (küme environment limiti geçerlidir); Lambda'da gerçekten uygulanır"),
31
+ environmentVariables: environmentVariablesParam.optional(),
32
+ publish: z.boolean().optional().describe("true: kayıt açıldıktan/güncellendikten sonra aynı çağrıda yayın istenir ('Kaydet ve yayınla'). Yayın ASENKRONDUR — çağrı status: 'publishing' döner; sonucu microfunction_get ile izleyin (published | publish-failed + publishError)")
33
+ };
34
+ const runtimeInput = {
35
+ name: z.string().min(1).max(255).describe('Hesap adı; benzersiz'),
36
+ provider: z.literal('aws-lambda').optional().describe("v1'de yalnız 'aws-lambda' kabul edilir; gönderilmezse gateway onu koyar"),
37
+ region: z.string().min(1).describe("AWS bölge kodu, ör. 'eu-central-1'"),
38
+ accessKeyId: z.string().min(1).describe('AWS access key id'),
39
+ secretAccessKey: z.string().min(1).describe("AWS secret access key. Kayıt düz metin tutulur; platform DTO'da maskeler (**** + son 4), gateway bu tool'ların yanıtında değeri tümüyle '***' yapar"),
40
+ roleArn: z.string().optional().describe("Lambda execution rolü: arn:aws:iam::<12 hane>:role/<ad>. Rolü müşteri oluşturur; fonksiyon bu rolle koşar")
41
+ };
42
+ export function registerMicrofunctionTools(server, platform) {
43
+ const config = platform.config;
44
+ server.registerTool('microfunction_list', {
45
+ title: 'Microfunction listele',
46
+ description: "Proje kapsamındaki microfunction'ları listeler. Workflow step'i kurmadan ÖNCE bunu status: 'published' ile çağırın: step yalnız yayındaki kaydı koşturur, taslak seçilirse koşum fail dalına düşer (WF-MF-001). Süzgeç ve sayfalama SUNUCUDA uygulanır: searchTerm ad/slug/açıklama içinde arar, sayfa boyu verilmezse gateway 200 gönderir (platform varsayılanı 10'dur).",
47
+ inputSchema: {
48
+ project: projectParam,
49
+ environment: environmentParam,
50
+ status: statusParam.optional().describe("Durum süzgeci; step editörü için 'published'"),
51
+ searchTerm: z.string().optional().describe('Ad, slug ve açıklamada geçen metin; büyük/küçük harf duyarsız'),
52
+ ...pagingParams
53
+ },
54
+ annotations: readOnly
55
+ }, handle(config, ({ project, environment, status, searchTerm, pageIndex, pageLength, order, isDescending }) => platform.tenant('GET', project, '/microfunctions', {
56
+ environment,
57
+ query: { status, searchTerm, pageIndex: pageIndex ?? 0, pageLength: pageLength ?? 200, order, isDescending }
58
+ })));
59
+ server.registerTool('microfunction_get', {
60
+ title: 'Microfunction getir',
61
+ description: "Tek bir microfunction'ın tanımını ve yayın durumunu getirir: status, revision/publishedRevision, hasUnpublishedChanges (kayıt yayında ama üstüne yazılmış), publishError, parameterScheme/returnScheme. Yayın asenkron olduğu için 'publishing' durumunda birkaç saniye arayla tekrar çağırın.",
62
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
63
+ annotations: readOnly
64
+ }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microfunctions/${id}`, { environment })));
65
+ server.registerTool('microfunction_create', {
66
+ title: 'Microfunction tanımla',
67
+ description: "Yeni microfunction kaydeder. Kayıt DRAFT doğar; yayına almadan hiçbir workflow step'i onu koşturamaz (publish: true ile aynı çağrıda yayın isteyebilirsiniz). Yayın adımı kodu runtime'a kurar ve bir 'probe' çağrısıyla duman testi yapar: sözdizimi/import hatası yayın anında publish-failed olur, koşumda değil. Protokol ve sınırlar: platform_guide('microfunction'). " + constraintSummary('CreateMicrofunctionCommand'),
68
+ inputSchema: { project: projectParam, environment: environmentParam, ...definitionInput, policyOverride: policyOverrideParam },
69
+ annotations: writes
70
+ }, handle(config, async ({ project, environment, policyOverride, ...body }) => {
71
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunction', body, { operation: 'create' }, policyOverride));
72
+ const drift = unmappedMembers('CreateMicrofunctionCommand', body);
73
+ return attachFindings(withUnmappedMembers(await platform.tenant('POST', project, '/microfunctions', { environment, body }), drift), findings);
74
+ }));
75
+ server.registerTool('microfunction_update', {
76
+ title: 'Microfunction güncelle',
77
+ description: "Microfunction'ın TAMAMINI değiştirir (PUT — değiştirmediğiniz alanları da gönderin; önce microfunction_get). Her güncelleme revision'ı artırır: yayındaki kayıt güncellenirse runtime ESKİ sürümü koşmaya devam eder ve kayıt hasUnpublishedChanges: true olur — yeni kodu canlıya almak için publish: true verin ya da microfunction_publish çağırın. PROVIDER YAYINDAYKEN DEĞİŞMEZ: bir kez yayınlanmış kaydın provider'ını değiştiren PUT 400 döner (MicrofunctionProviderChangeNotAllowedException) — kaydı silip yeniden oluşturun. " + constraintSummary('UpdateMicrofunctionCommand'),
78
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...definitionInput, policyOverride: policyOverrideParam },
79
+ annotations: updates
80
+ }, handle(config, async ({ project, environment, id, policyOverride, ...body }) => {
81
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunction', body, { operation: 'update' }, policyOverride));
82
+ const drift = unmappedMembers('UpdateMicrofunctionCommand', body);
83
+ return attachFindings(withUnmappedMembers(await platform.tenant('PUT', project, `/microfunctions/${id}`, { environment, body }), drift), findings);
84
+ }));
85
+ server.registerTool('microfunction_publish', {
86
+ title: 'Microfunction yayınla',
87
+ description: "Kaydı runtime'a kurar. Çağrı 202 ile hemen döner ve kayıt 'publishing' olur; sonucu microfunction_get ile izleyin — 'published' (publishedReference: Fission fonksiyon adı ya da Lambda ARN) ya da 'publish-failed' (publishError). Zaten yayınlanmakta olan bir kayıt için çağrı 400 döner.",
88
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
89
+ annotations: updates
90
+ }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microfunctions/${id}/publish`, { environment })));
91
+ server.registerTool('microfunction_delete', {
92
+ title: 'Microfunction sil',
93
+ description: "Microfunction'ı siler ve runtime'daki fonksiyonu kaldırır. Bir workflow step'i kaydı kullanıyorsa 400 ile reddedilir (CantDeleteUsedMicrofunctionException) ve mesaj çağıran akışın adını taşır — önce step'i kaldırın.",
94
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
95
+ annotations: destructive
96
+ }, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/microfunctions/${id}`, { environment })));
97
+ server.registerTool('microfunction_invoke', {
98
+ title: 'Microfunction çağır',
99
+ description: "Yayındaki fonksiyonu GERÇEK runtime'da koşturur ve zarfı döner: { ok, output, error, logs, durationMs }. Test çağrısıdır ama rollback YOKTUR — fonksiyon dış dünyaya ne yaptıysa kalır. ok:false ise error.type/message/stack okunur; aynı hata workflow'da step'i fail dalına düşürür. Taslak kayıtta çağrı MicrofunctionNotPublishedException ile döner. Kaydetmeden koşma yolu yoktur: önce microfunction_update + publish.",
100
+ inputSchema: {
101
+ project: projectParam,
102
+ environment: environmentParam,
103
+ id: guid,
104
+ args: z.record(z.unknown()).optional().describe("Fonksiyona giden argüman nesnesi (handler'ın ilk parametresi); parameterDtoId varsa o şemaya uymalı"),
105
+ preview: z.boolean().optional().describe("true: shim logları (console.log/print) zarfın logs alanında döner. false/boş: logs boş gelir — hata ayıklarken açın")
106
+ },
107
+ annotations: updates
108
+ }, handle(config, ({ project, environment, id, args, preview }) => platform.tenant('POST', project, `/microfunctions/${id}/invoke`, { environment, body: { args, preview } })));
109
+ server.registerTool('microfunction_runtime_list', {
110
+ title: 'Microfunction runtime hesaplarını listele',
111
+ description: "Bulut runtime hesaplarını (v1: AWS Lambda) listeler; secretAccessKey maskeli döner. Built-In fonksiyonların runtime kaydı YOKTUR — liste boşsa yalnız provider: 'built-in' fonksiyon tanımlayabilirsiniz.",
112
+ inputSchema: { project: projectParam, environment: environmentParam },
113
+ annotations: readOnly
114
+ }, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/microfunction-runtimes', { environment })));
115
+ server.registerTool('microfunction_runtime_get', {
116
+ title: 'Microfunction runtime hesabını getir',
117
+ description: 'Tek bir runtime hesabını getirir (secretAccessKey maskeli).',
118
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
119
+ annotations: readOnly
120
+ }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microfunction-runtimes/${id}`, { environment })));
121
+ server.registerTool('microfunction_runtime_create', {
122
+ title: 'Microfunction runtime hesabı tanımla',
123
+ description: "AWS Lambda hesabı kaydeder: bölge + erişim anahtarları + Lambda execution rolü (roleArn). Kimlik bilgileri düz metin tutulur (Datasource emsali) — dar yetkili bir IAM kullanıcısı verin. Kaydettikten sonra microfunction_runtime_test_connection ile doğrulayın. " + constraintSummary('CreateMicrofunctionRuntimeCommand'),
124
+ inputSchema: { project: projectParam, environment: environmentParam, ...runtimeInput, policyOverride: policyOverrideParam },
125
+ annotations: writes
126
+ }, handle(config, async ({ project, environment, policyOverride, ...input }) => {
127
+ const body = { ...input, provider: input.provider ?? 'aws-lambda' };
128
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunctionRuntime', body, { operation: 'create' }, policyOverride));
129
+ const drift = unmappedMembers('CreateMicrofunctionRuntimeCommand', body);
130
+ return attachFindings(withUnmappedMembers(await platform.tenant('POST', project, '/microfunction-runtimes', { environment, body }), drift), findings);
131
+ }));
132
+ server.registerTool('microfunction_runtime_update', {
133
+ title: 'Microfunction runtime hesabını güncelle',
134
+ description: 'Runtime hesabının tamamını değiştirir (PUT). secretAccessKey maskeli okunduğu için güncellemede GERÇEK değeri yeniden gönderin — maskeyi geri yazmayın. ' + constraintSummary('UpdateMicrofunctionRuntimeCommand'),
135
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...runtimeInput, policyOverride: policyOverrideParam },
136
+ annotations: updates
137
+ }, handle(config, async ({ project, environment, id, policyOverride, ...input }) => {
138
+ const body = { ...input, provider: input.provider ?? 'aws-lambda' };
139
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunctionRuntime', body, { operation: 'update' }, policyOverride));
140
+ const drift = unmappedMembers('UpdateMicrofunctionRuntimeCommand', body);
141
+ return attachFindings(withUnmappedMembers(await platform.tenant('PUT', project, `/microfunction-runtimes/${id}`, { environment, body }), drift), findings);
142
+ }));
143
+ server.registerTool('microfunction_runtime_delete', {
144
+ title: 'Microfunction runtime hesabını sil',
145
+ description: 'Runtime hesabını siler. Bir microfunction hesabı kullanıyorsa 400 ile reddedilir (CantDeleteUsedMicrofunctionRuntimeException).',
146
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
147
+ annotations: destructive
148
+ }, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/microfunction-runtimes/${id}`, { environment })));
149
+ server.registerTool('microfunction_runtime_test_connection', {
150
+ title: 'Microfunction runtime bağlantısını test et',
151
+ description: "Kayıtlı hesabın kimlik bilgilerini gerçek bulut ucunda dener (Lambda: ListFunctions). Hata 400 olarak döner ve mesaj AWS'in kendi cümlesidir — yayın denemeden önce buradan doğrulayın.",
152
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
153
+ annotations: updates
154
+ }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microfunction-runtimes/${id}/test-connection`, { environment })));
155
+ }
@@ -1,15 +1,71 @@
1
- import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
1
+ import { guid, appRef, withResolved, projectParam, environmentParam, definitionParam, languageParam, pagingParams, readOnly, writes, updates, destructive, microOps } from './common.js';
2
+ import { languageHeader } from '../knowledge/languageCodes.js';
3
+ import { hydrateMicroserviceRefs } from '../knowledge/hydrate.js';
4
+ import { RefResolver } from '../platform/refs.js';
2
5
  import { normalizeMicroserviceDefinition, withNormalization } from '../knowledge/normalize.js';
3
6
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
4
- import { microserviceOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
7
+ import { microserviceOutline, workflowOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
8
+ import { patchResult, baseVersionOf } from '../platform/patch.js';
5
9
  import { policyRules } from '../knowledge/rules.js';
10
+ import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
11
+ import { identityHeaders } from '../platform/tokenVault.js';
12
+ import { PlatformApiError } from '../platform/errors.js';
6
13
  import { handle } from '../platform/responses.js';
7
14
  import fastJsonPatch from 'fast-json-patch';
8
15
  import { z } from 'zod';
16
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
17
+ import { unmappedMembers, introducedUnmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
9
18
  const policyOverrideParam = z.boolean().optional()
10
19
  .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
20
+ /**
21
+ * MS-015 — query kaynaklı uçta kimlik kapsaması sunucuda ZORLANAMAZ.
22
+ *
23
+ * Query kaynaklı ucun parametreleri yalnız çağrının kendisinden bağlanır (body → query-string →
24
+ * header, `Query.PrepareQueryParameters` → `FindValue`); ifade değerlendirmesi yoktur, yani
25
+ * middleware'in kurduğu `user` bu yola hiç girmez. Uca bir middleware bağlıysa ortada bir kimlik
26
+ * VARDIR ve sorgunun kimlik/tenant adı taşıyan parametresi büyük olasılıkla bir KAPSAMDIR —
27
+ * gövdeye bırakıldığı sürece sahtelenebilir (2026-09-07'de ölçüldü).
28
+ *
29
+ * Kural bu yüzden sorgunun parametre adlarına bakar: bir tur maliyeti vardır ama yalnız
30
+ * "query kaynaklı + middleware bağlı" uçlarda ödenir, ve cümlesi KOŞULLUDUR — kullanıcının
31
+ * serbestçe seçebileceği bir filtreyi suçlamaz. Sorgu okunamazsa kural susar: bir yazmayı
32
+ * kendi teşhis turu yüzünden engellemez.
33
+ */
34
+ const SCOPE_PARAMETER_PATTERN = /(^|_)(user|owner|tenant|company|firma|sirket|musteri|customer|account|organization|org|member|kullanici)(_?(id|key|slug|code))?$/i;
35
+ async function queryScopeFindings(platform, project, environment, definition) {
36
+ // POLICY=off lint'i tamamen kapatır; kural kendi teşhis turunu da o modda atmalıdır.
37
+ if (platform.config.policyMode === 'off')
38
+ return [];
39
+ if (String(definition.source ?? '') !== 'query')
40
+ return [];
41
+ if (!Array.isArray(definition.middlewareIds) || definition.middlewareIds.length === 0)
42
+ return [];
43
+ const queryId = typeof definition.queryId === 'string' ? definition.queryId : null;
44
+ if (!queryId)
45
+ return [];
46
+ let scoped = [];
47
+ try {
48
+ const payload = await platform.tenant('GET', project, `/queries/${queryId}`, { environment });
49
+ scoped = (payload?.data?.parameterScheme ?? [])
50
+ .map(parameter => String(parameter?.name ?? ''))
51
+ .filter(name => name !== '' && SCOPE_PARAMETER_PATTERN.test(name));
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ return scoped.length > 0
57
+ ? [{
58
+ ruleId: 'MS-015',
59
+ severity: 'warn',
60
+ message: `Uç bir middleware'in arkasında ama kaynağı 'query': sorgunun ${scoped.map(name => `'${name}'`).join(', ')} parametresi ÇAĞRIDAN geliyor (body → query-string → header) ve middleware'in kurduğu 'user' bu yola girmiyor. Bu parametre bir kimlik/tenant KAPSAMIYSA çağıran başkasının değerini yazıp o kapsamın verisini okuyabilir (ölçüldü 2026-09-07). Sunucu tarafında zorlamak için aynı sorguyu bir workflow query adımından çağırın: uç source:'workflow' + aynı middlewareIds, adımın rule.expression'ı "{ ${scoped[0]}: user.${scoped[0]} }". Parametre kullanıcının serbestçe seçebileceği bir filtreyse (yönetici raporunda şirket seçimi gibi) bu uyarıyı yok sayın. Reçete: platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'.`,
61
+ guide: 'ngapps://guides/microservice'
62
+ }]
63
+ : [];
64
+ }
11
65
  const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
12
66
  .describe("RFC 6902 JSON Patch işlemleri; path'leri view:'outline'/'part' ile keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/sourceParameters/joins[alias=customers]/path' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
67
+ const asUserParam = z.string().min(1).optional().describe("Uygulama üyesinin kimliği: istek gateway'in servis jetonu yerine bu kimlikle gider. Jeton HEM Authorization başlığında HEM NG_AccessToken çerezinde taşınır, böylece jetonu ÇEREZDEN okuyan middleware de geçer — uca middleware bağlandığı anda bu alan olmadan çağrı 401 döner (09-05 koşusu S-21). İKİ ŞEKİL kabul edilir: (1) ham JWT, (2) `tok_…` referansı (giriş ucunu çağırdığınızda yanıtın tokenRefs alanında döner, 15 dk geçerli). anonymous ile birlikte verilemez.");
68
+ const cookiesParam = z.record(z.string()).optional().describe("İsteğe eklenecek çerezler (ad → değer): uygulamanın KENDİ oturum çerezi buraya verilir (ör. { CORE_Token: '<jwt>' }). asUser verildiğinde gateway NG_AccessToken çerezini zaten kendisi kurar; bu alan yalnız uygulama başka bir çerez adı okuyorsa gerekir. anonymous ile birlikte verilemez.");
13
69
  const executeInput = {
14
70
  project: projectParam,
15
71
  environment: environmentParam,
@@ -21,8 +77,62 @@ const executeInput = {
21
77
  body: z.union([z.record(z.unknown()), z.array(z.record(z.unknown()))]).optional()
22
78
  .describe('İstek gövdesi: tek nesne ya da nesne dizisi (bulk uçlar için)'),
23
79
  headers: z.record(z.string()).optional().describe("İsteğe eklenecek header'lar. 'Authorization' verirseniz gateway KENDİ token'ını KOYMAZ — istek tamamen sizin kimliğinizle gider (middleware'li uçları üye token'ıyla test etmenin yolu budur)"),
24
- anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı olmadan gider — 'korumalı uç tokensız ne döner?' sorusunun cevabı budur (korumalı uçtan 401, anonim uçtan 200 beklenir). Gateway normalde her çağrıya kendi token'ını koyduğu için bu bayrak olmadan anonim erişim testi yapılamaz.")
80
+ anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı olmadan gider — 'korumalı uç tokensız ne döner?' sorusunun cevabı budur (korumalı uçtan 401, anonim uçtan 200 beklenir). Gateway normalde her çağrıya kendi token'ını koyduğu için bu bayrak olmadan anonim erişim testi yapılamaz."),
81
+ asUser: asUserParam,
82
+ cookies: cookiesParam,
83
+ language: languageParam,
84
+ expectFailure: z.boolean().optional().describe("true: bu çağrıda bir hata dönmesi BEKLENİYOR (kasıtlı negatif test — yanlış parola, yetkisiz erişim, geçersiz gövde). Yanıt yine httpStatus'u/hatayı taşır, ama ölçüm aracı bu satırı plansız hata saymaz (page_screenshot'taki expectFailure deseniyle aynı).")
25
85
  };
86
+ /**
87
+ * `anonymous` ile kimlik birlikte verilemez: biri kimliksiz isteği, diğeri belirli bir üyenin
88
+ * kimliğiyle atılan isteği ister. `page_screenshot`'taki kural aynen geçerli (G-20).
89
+ */
90
+ function runIdentity(anonymous, asUser, cookies, headers) {
91
+ if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
92
+ throw new PlatformApiError("anonymous:true ile asUser/cookies birlikte verilemez — biri kimliksiz isteği, diğeri belirli bir üyenin kimliğiyle atılan isteği ister.", 400);
93
+ }
94
+ return identityHeaders(asUser, cookies, headers);
95
+ }
96
+ /**
97
+ * Debug çıktısındaki `executionItems` ucun kendi adımlarıdır; middleware AYRI bir workflow olarak
98
+ * ucun ÖNÜNDE koşar ve ağaçta hiç görünmez — 09-04 koşusunda sessizce açık kalan şirket kapısı
99
+ * (Y-03) bu yüzden adım ağacından teşhis edilemedi. Gateway zinciri kendisi çözüp listeler.
100
+ */
101
+ async function middlewareChain(platform, project, environment, application, microservice, httpStatus) {
102
+ try {
103
+ const resolver = new RefResolver(platform, project, environment);
104
+ const applicationId = await resolver.applicationId(application);
105
+ const list = await platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query: { pageIndex: 0, pageLength: 200 } });
106
+ const record = (list?.data ?? []).find(entry => String(entry.slug ?? '') === microservice);
107
+ if (!record) {
108
+ return { middlewareNote: `'${microservice}' ucu application listesinde bulunamadı; middleware zinciri çözülemedi. Slug'ı microservice_list ile doğrulayın.` };
109
+ }
110
+ // LİSTE DTO'su middlewareIds taşımaz (yalnız id/ad/slug/tip/kaynak) — not bu yüzden bağlı bir
111
+ // uçta bile "bağlı değil" diyordu ve okuyanı yanlış yere baktırıyordu (09-05 koşusu S-21).
112
+ // Kimlik listeden, bağlar DETAYDAN okunur.
113
+ const detail = await platform.tenant('GET', project, `/microservices/${applicationId}/${String(record.id ?? '')}`, { environment });
114
+ const definition = (detail?.data ?? detail ?? {});
115
+ const middlewareIds = (Array.isArray(definition.middlewareIds) ? definition.middlewareIds : []).map(String).slice(0, 10);
116
+ if (middlewareIds.length === 0) {
117
+ return { middlewareNote: "Bu uca middleware bağlı değil (microservice_get middlewareIds boş) — executionItems ağacı isteğin TAMAMINI anlatıyor. Yetki/oturum kapısı bekliyorsanız uca middleware bağlamanız gerekir (platform_guide('middleware'))." };
118
+ }
119
+ const middlewares = [];
120
+ for (const middlewareId of middlewareIds) {
121
+ const detail = await platform.tenant('GET', project, `/workflows/${applicationId}/${middlewareId}`, { environment });
122
+ middlewares.push(workflowOutline((detail?.data ?? detail)));
123
+ }
124
+ const blocked = typeof httpStatus === 'number' && (httpStatus === 401 || httpStatus === 403);
125
+ return {
126
+ middlewares,
127
+ middlewareNote: blocked
128
+ ? `Bu uca ${middlewareIds.length} middleware bağlı ve istek ${httpStatus} ile KESİLDİ — executionItems ağacının kökü ucun kendi adımları değil, kesen middleware'in adımlarıdır. Gateway'in servis jetonu uygulamanın kendi kullanıcısı değildir; uygulama kullanıcısıyla denemek için asUser (giriş ucundan gelen tok_… referansı) verin.`
129
+ : "Bu middleware'ler ucun ÖNÜNDE koşar ve geçirdiler; debug çıktısındaki executionItems ağacı ucun kendi adımlarıdır. Kapının açık kalıp kalmadığını buradaki adımlardan okuyun; middleware ifadelerini ayrıca workflow_get ile inceleyebilirsiniz."
130
+ };
131
+ }
132
+ catch (error) {
133
+ return { middlewareNote: `Middleware zinciri çözülemedi: ${error instanceof Error ? error.message : String(error)}. Uca bağlı middleware'leri microservice_get ile okuyabilirsiniz.` };
134
+ }
135
+ }
26
136
  function executePath(prefix, application, microservice, routeParams) {
27
137
  const routeSuffix = routeParams && routeParams.length > 0
28
138
  ? `/${routeParams.map(encodeURIComponent).join('/')}`
@@ -81,7 +191,7 @@ export function registerMicroserviceTools(server, platform) {
81
191
  inputSchema: {
82
192
  project: projectParam,
83
193
  environment: environmentParam,
84
- applicationId: guid,
194
+ applicationId: appRef,
85
195
  searchTerm: z.string().optional(),
86
196
  httpMethod: z.string().optional(),
87
197
  entityId: guid.optional(),
@@ -91,75 +201,119 @@ export function registerMicroserviceTools(server, platform) {
91
201
  ...pagingParams
92
202
  },
93
203
  annotations: readOnly
94
- }, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query })));
204
+ }, handle(config, async ({ project, environment, applicationId, ...query }) => {
205
+ const resolver = new RefResolver(platform, project, environment);
206
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
207
+ return withResolved(await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}`, { environment, query }), resolver.resolved);
208
+ }));
95
209
  server.registerTool('microservice_get', {
96
210
  title: 'Microservice getir',
97
211
  description: "Microservice tanımını getirir. Büyük tanımda önce view:'outline' (kaynak/alan özeti), sonra view:'part' ile tek parça çekin.",
98
212
  inputSchema: {
99
213
  project: projectParam,
100
214
  environment: environmentParam,
101
- applicationId: guid,
215
+ applicationId: appRef,
102
216
  id: guid,
103
217
  view: z.enum(['full', 'outline', 'part']).optional().describe('varsayılan full'),
104
218
  part: z.string().optional().describe("view:'part' için yol, ör. sourceParameters.joins veya validations[0]")
105
219
  },
106
220
  annotations: readOnly
107
221
  }, handle(config, async ({ project, environment, applicationId, id, view, part }) => {
108
- const payload = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
222
+ const resolver = new RefResolver(platform, project, environment);
223
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
224
+ const payload = await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}/${id}`, { environment });
109
225
  const definition = (payload?.data ?? payload);
110
226
  if (view === 'part') {
111
227
  if (!part)
112
228
  throw new Error("view:'part' için 'part' parametresi zorunlu.");
113
- return { part, value: resolvePart(definition, part) };
229
+ return withResolved({ part, value: resolvePart(definition, part) }, resolver.resolved);
114
230
  }
115
- const resolvedUrl = await resolveRuntimeUrl(platform, project, environment, applicationId, definition);
231
+ const resolvedUrl = await resolveRuntimeUrl(platform, project, environment, resolvedApplicationId, definition);
116
232
  if (view === 'outline')
117
- return { ...microserviceOutline(definition), resolvedUrl };
118
- return { ...payload, resolvedUrl };
233
+ return withResolved({ ...microserviceOutline(definition), resolvedUrl }, resolver.resolved);
234
+ return withResolved({ ...payload, resolvedUrl }, resolver.resolved);
119
235
  }));
120
236
  server.registerTool('microservice_create', {
121
237
  title: 'Microservice oluştur',
122
- description: "Yeni microservice tanımlar (definition = CreateMicroserviceCommand). İlk kullanımdan önce platform_guide('microservice') okuyun: kaynak seçimi (table > query) ve selectFields disiplini. 'description' ZORUNLUDUR (MS-014, block): çağıranın bu uçtan ne elde ettiğini yazın. Auth/oturum gerektiren uçlarda definition.middlewareIds ile middleware bağlayın; kimlik kapsaması filtrede source:'expression' + value:'user.id' ile yapılır (platform_guide('middleware')). Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer.",
123
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
238
+ description: "Yeni microservice tanımlar (definition = CreateMicroserviceCommand). İlk kullanımdan önce platform_guide('microservice') okuyun: kaynak seçimi (table > query) ve selectFields disiplini. 'description' ZORUNLUDUR (MS-014, block): çağıranın bu uçtan ne elde ettiğini yazın. Auth/oturum gerektiren uçlarda definition.middlewareIds ile middleware bağlayın; kimlik kapsaması filtrede source:'expression' + value:'user.id' ile yapılır (platform_guide('middleware')). BU YALNIZ source:'table' UÇLARDA VAR: query kaynaklı ucun parametreleri ÇAĞRIDAN gelir (body → query-string → header) ve middleware'in kurduğu user o yola girmez — kapsamı gövdeye bırakan uç sahtelenebilir (ölçüldü 2026-09-07). Kapsanmış agregasyon ucu sorguyu bir workflow query adımından çağırır: platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'. Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer. " + constraintSummary('CreateMicroserviceCommand'),
239
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, definition: definitionParam, policyOverride: policyOverrideParam },
124
240
  annotations: writes
125
241
  }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
242
+ const resolver = new RefResolver(platform, project, environment);
243
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
244
+ const hydrated = await hydrateMicroserviceRefs(definition, resolver);
126
245
  const normalized = normalizeMicroserviceDefinition(definition);
127
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride));
128
- const payload = await platform.tenant('POST', project, `/microservices/${applicationId}`, { environment, body: definition });
129
- return attachFindings(withNormalization(payload, normalized), findings);
246
+ const findings = [
247
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride)),
248
+ ...await queryScopeFindings(platform, project, environment, definition)
249
+ ];
250
+ const drift = unmappedMembers('CreateMicroserviceCommand', definition);
251
+ const payload = await platform.tenant('POST', project, `/microservices/${resolvedApplicationId}`, { environment, body: definition });
252
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
130
253
  }));
131
254
  server.registerTool('microservice_update', {
132
255
  title: 'Microservice güncelle',
133
- description: 'Microservice tanımını günceller (tam gövde). Hedefli değişiklik için microservice_patch tercih edin. \'description\' ZORUNLUDUR (MS-014, block). Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) \'caution\' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer.',
134
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
256
+ description: 'Microservice tanımını günceller (tam gövde). Hedefli değişiklik için microservice_patch tercih edin. \'description\' ZORUNLUDUR (MS-014, block). Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) \'caution\' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer. ' + constraintSummary('UpdateMicroserviceCommand'),
257
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
135
258
  annotations: updates
136
259
  }, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
260
+ const resolver = new RefResolver(platform, project, environment);
261
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
262
+ const hydrated = await hydrateMicroserviceRefs(definition, resolver);
137
263
  const normalized = normalizeMicroserviceDefinition(definition);
138
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'update' }, policyOverride));
139
- const payload = await platform.tenant('PUT', project, `/microservices/${applicationId}/${id}`, { environment, body: definition });
140
- return attachFindings(withNormalization(payload, normalized), findings);
264
+ const findings = [
265
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'update' }, policyOverride)),
266
+ ...await queryScopeFindings(platform, project, environment, definition)
267
+ ];
268
+ const drift = unmappedMembers('UpdateMicroserviceCommand', definition);
269
+ const payload = await platform.tenant('PUT', project, `/microservices/${resolvedApplicationId}/${id}`, { environment, body: definition });
270
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
141
271
  }));
142
272
  server.registerTool('microservice_patch', {
143
273
  title: 'Microservice\'e patch uygula',
144
274
  description: 'Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı çeker, JSON Patch uygular, politika kontrolünden geçirir, geri yazar. Yanıt outline özetidir.',
145
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, operations: patchOperationsParam, policyOverride: policyOverrideParam },
275
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid, 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 },
146
276
  annotations: updates
147
- }, handle(config, async ({ project, environment, applicationId, id, operations, policyOverride }) => {
148
- const current = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
277
+ }, handle(config, async ({ project, environment, applicationId, id, operations: givenOperations, ops, policyOverride }) => {
278
+ const operations = microOps('microservice_patch', ops, givenOperations);
279
+ const resolver = new RefResolver(platform, project, environment);
280
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
281
+ const current = await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}/${id}`, { environment });
149
282
  const definition = structuredClone((current?.data ?? current));
150
283
  const resolved = resolvePatchOperations(definition, operations);
151
284
  const patched = fastJsonPatch.applyPatch(definition, resolved.operations, true, false).newDocument;
285
+ // Patch gövdesi tam PUT olarak geri yazılır: okunan kayıtta olmayan alan yazmada da SİLİNİR.
286
+ // 09-04 koşusunda gidiş-dönüş 'description'ı düşürdü ve MS-014 patch'i bloke etti; genel
287
+ // MS-014 mesajı sebebi söylemediği için tuzağı burada adıyla anlatıyoruz (Y-16).
288
+ if (typeof patched.description !== 'string' || patched.description.trim() === '') {
289
+ throw new PlatformApiError(`microservice_patch: okunan kayıtta 'description' yok, bu gövdeyi geri yazmak açıklamayı SİLER (MS-014 de bloke eder). Aynı çağrıya açıklamayı ekleyin: { "op": "add", "path": "/description", "value": "<ucun ne işe yaradığı>" }. Kayıtta bir açıklama olduğunu düşünüyorsanız microservice_get ile kontrol edin — platform okuma yolunda alanı düşürüyorsa (09-04 Y-16) tek turda yeniden yazmak gerekir.`, 400);
290
+ }
152
291
  const normalized = normalizeMicroserviceDefinition(patched);
153
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', patched, { operation: 'update' }, policyOverride));
154
- await platform.tenant('PUT', project, `/microservices/${applicationId}/${id}`, { environment, body: patched });
155
- return attachFindings(withNormalization({ applied: operations.length, outline: microserviceOutline(patched) }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), findings);
292
+ const findings = [
293
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', patched, { operation: 'update' }, policyOverride)),
294
+ ...await queryScopeFindings(platform, project, environment, patched)
295
+ ];
296
+ const drift = introducedUnmappedMembers('UpdateMicroserviceCommand', (current?.data ?? current), patched);
297
+ // Gövde değil FARK gönderilir: gateway normalizasyonları da op'a çevrilsin diye net diff
298
+ // okunan kayıt ile son hâl arasından hesaplanır (tam PUT yok, yalnız değişen alanlar yazılır).
299
+ const netOps = fastJsonPatch.compare(definition, patched);
300
+ const saved = await platform.tenant('PATCH', project, `/microservices/${resolvedApplicationId}/${id}`, {
301
+ environment,
302
+ body: { baseVersion: baseVersionOf(definition, 'microservice_get'), ops: netOps }
303
+ });
304
+ const written = patchResult(saved, patched);
305
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization({ applied: operations.length, newVersion: written.newVersion, outline: microserviceOutline(written.data) }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), resolver.resolved), writeDiff(patched, written.data)), drift), findings);
156
306
  }));
157
307
  server.registerTool('microservice_delete', {
158
308
  title: 'Microservice sil',
159
309
  description: 'Microservice tanımını siler.',
160
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
310
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid },
161
311
  annotations: destructive
162
- }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/microservices/${applicationId}/${id}`, { environment })));
312
+ }, handle(config, async ({ project, environment, applicationId, id }) => {
313
+ const resolver = new RefResolver(platform, project, environment);
314
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
315
+ return withResolved(await platform.tenant('DELETE', project, `/microservices/${resolvedApplicationId}/${id}`, { environment }), resolver.resolved);
316
+ }));
163
317
  server.registerTool('microservice_clone', {
164
318
  title: 'Microservice klonla',
165
319
  description: 'Microservice\'in kopyasını oluşturur.',
@@ -168,31 +322,107 @@ export function registerMicroserviceTools(server, platform) {
168
322
  }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microservices/${id}/clone`, { environment })));
169
323
  server.registerTool('microservice_execute', {
170
324
  title: 'Microservice çalıştır',
171
- description: 'Kayıtlı microservice\'i gerçek runtime yolundan çalıştırır (POST /{project}/m/{application}/{microservice SLUG\'ı}). Dönen httpStatus microservice\'in kendi sonucudur (hata da geçerli çıktıdır); beklenmedik sonuçta microservice_debug ile adım adım inceleyin. Anonim erişim testi için anonymous: true.',
325
+ description: "Kayıtlı microservice'i gerçek runtime yolundan çalıştırır (POST /{project}/m/{application}/{microservice SLUG'ı}). Dönen httpStatus microservice'in kendi sonucudur (hata da geçerli çıktıdır); beklenmedik sonuçta microservice_debug ile adım adım inceleyin. Anonim erişim testi için anonymous: true. Yanıt 'headers' alanında platformun döndüğü header'ları taşır (küçük harf; ör. headers['total-count'] — sayfalı tablo akışlarının okuduğu sözleşme; set-cookie/authorization elenir).",
172
326
  inputSchema: executeInput,
173
327
  annotations: writes
174
- }, handle(config, ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous }) => platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query, body, headers, anonymous })));
328
+ }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous, asUser, cookies, language }) => {
329
+ const notes = [];
330
+ const requestHeaders = { ...runIdentity(anonymous, asUser, cookies, headers), ...languageHeader(language, notes) };
331
+ return withNormalization(await platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query, body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true }), notes);
332
+ }));
175
333
  server.registerTool('microservice_debug', {
176
334
  title: 'Microservice debug',
177
- description: 'Microservice\'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı).',
335
+ description: "Microservice'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı). executionItems YALNIZ ucun kendi adımlarını kapsar — ucun ÖNÜNDE koşan middleware'ler ayrıca 'middlewares' alanında adım özetiyle listelenir (09-04 Y-04).",
178
336
  inputSchema: executeInput,
179
337
  annotations: writes
180
- }, handle(config, ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous }) => platform.tenantRaw(method, project, executePath('debug', application, microservice, routeParams), { environment, query, body, headers, anonymous })));
338
+ }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous, asUser, cookies, language }) => {
339
+ const notes = [];
340
+ const requestHeaders = { ...runIdentity(anonymous, asUser, cookies, headers), ...languageHeader(language, notes) };
341
+ const payload = await platform.tenantRaw(method, project, executePath('debug', application, microservice, routeParams), { environment, query, body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true });
342
+ return withNormalization({ ...payload, ...await middlewareChain(platform, project, environment, application, microservice, payload?.httpStatus) }, notes);
343
+ }));
181
344
  server.registerTool('microservice_preview', {
182
345
  title: 'Microservice önizle',
183
- description: 'Kaydedilmemiş microservice tanımını çalıştırır; kaydetmeden önce preview ile doğrulayın. definition = PreviewMicroserviceCommand gövdesi (previewParameters dahil).',
184
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
346
+ description: "Microservice tanımını çalıştırır: kaydedilmemiş gövde için definition, KAYITLI bir uç için yalnız id verin (gateway tanımı kendisi çeker — tam gövdeyi bağlama almanız gerekmez). previewParameters ayrı alandır ve ikisiyle de çalışır. Preview tek transaction'da koşar ve sonunda ROLLBACK edilir — kalıcı satır yazmaz; code step'teki dış HTTP çağrıları istisnadır. Davranış kuralları: platform_guide('microservice').",
347
+ inputSchema: {
348
+ project: projectParam,
349
+ environment: environmentParam,
350
+ applicationId: appRef,
351
+ id: guid.optional().describe('Kayıtlı microservice id\'si: verilirse tanım platformdan çekilir ve definition zorunlu olmaz (microservice_list / project_overview)'),
352
+ definition: definitionParam.optional(),
353
+ previewParameters: z.record(z.unknown()).optional().describe("Test girdisi: { body: [{…}] } · { queryString: {…} } · { routeParams: {…} }. Tanımsız bir şekil SESSİZCE yutulur (bütün sayaçlar 0 döner) — şekli önce microservice_get çıktısındaki parametre tanımıyla eşleyin."),
354
+ asUser: asUserParam,
355
+ cookies: cookiesParam,
356
+ policyOverride: policyOverrideParam,
357
+ language: languageParam
358
+ },
185
359
  annotations: writes
186
- }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
360
+ }, handle(config, async ({ project, environment, applicationId, id, definition: given, previewParameters, asUser, cookies, policyOverride, language }) => {
361
+ const resolver = new RefResolver(platform, project, environment);
362
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
363
+ if (given === undefined && id === undefined) {
364
+ throw new PlatformApiError('microservice_preview: definition ya da id verin — kaydedilmemiş bir gövdeyi önizlemek için definition, kayıtlı bir ucu önizlemek için id (gateway tanımı kendisi çeker).', 400);
365
+ }
366
+ const stored = id === undefined
367
+ ? undefined
368
+ : await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}/${id}`, { environment });
369
+ const base = stored === undefined ? {} : structuredClone((stored.data ?? stored));
370
+ delete base.id;
371
+ const definition = {
372
+ ...base,
373
+ ...(given ?? {}),
374
+ ...(previewParameters === undefined ? {} : { previewParameters })
375
+ };
376
+ const hydrated = await hydrateMicroserviceRefs(definition, resolver);
187
377
  const normalized = normalizeMicroserviceDefinition(definition);
188
378
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'preview' }, policyOverride));
189
- const payload = await platform.tenantRaw('POST', project, `/microservices/${applicationId}/preview`, { environment, body: definition });
190
- return attachFindings(withNormalization(payload, normalized), findings);
379
+ // Önizleme de ucun middleware'lerini koşturur: kimlik verilmezse korumalı uç 401 döner ve kuru
380
+ // koşu aracı tam gerektiği anda kullanılamaz hâle gelir (09-05 koşusu S-21).
381
+ const payload = await platform.tenantRaw('POST', project, `/microservices/${resolvedApplicationId}/preview`, { environment, body: definition, headers: { ...identityHeaders(asUser, cookies, undefined), ...languageHeader(language, normalized) }, mirrorAuthorizationCookie: true });
382
+ return attachFindings(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), findings);
191
383
  }));
192
384
  server.registerTool('microservice_job_history', {
193
385
  title: 'Job geçmişi',
194
- description: 'Zamanlanmış (job tipli) microservice\'in çalışma geçmişini listeler.',
195
- inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...pagingParams },
386
+ description: 'Zamanlanmış (job tipli) microservice\'in çalışma geçmişini listeler. status ile yalnız running | completed | failed koşular süzülür; order TriggeredAt | CompletedAt | ExecutionStatus | DurationMs.',
387
+ inputSchema: {
388
+ project: projectParam,
389
+ environment: environmentParam,
390
+ id: guid,
391
+ status: z.enum(['running', 'completed', 'failed']).optional().describe('Yalnız bu durumdaki koşular'),
392
+ ...pagingParams
393
+ },
196
394
  annotations: readOnly
197
395
  }, handle(config, ({ project, environment, id, ...query }) => platform.tenant('GET', project, `/microservices/${id}/job-history`, { environment, query })));
396
+ server.registerTool('microservice_schedule_get', {
397
+ title: 'Zamanlama durumu',
398
+ description: 'Zamanlanmış (schedule-job) microservice\'in canlı zamanlama durumunu döner: state (scheduled | paused | notScheduled | error | ended), isRunning, nextFireTimeUtc, previousFireTimeUtc, sıradaki 5 koşu (upcomingFireTimesUtc), cron + timeZoneId, serverTimeUtc ve son koşunun özeti (lastExecution). "Bir sonraki sefer ne zaman koşacak?" sorusunu cron\'u yorumlamadan buradan cevaplayın. paused iken nextFireTimeUtc null ve upcoming boştur. notScheduled: kayıt var ama zamanlayıcıda trigger yok — en geç 5 dk içinde uzlaştırma kurar ya da servisi kaydedin (microservice_update). ended: tanımın sonraki koşusu yok (once geçti, end on/after doldu). schedule alanı tanımın kendisidir. Schedule tipli olmayan servis 400 döner.',
399
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
400
+ annotations: readOnly
401
+ }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microservices/${id}/schedule`, { environment })));
402
+ server.registerTool('microservice_schedule_forecast', {
403
+ title: 'Zamanlama öngörüsü',
404
+ description: 'Bir zamanlama tanımını (typeParameters.schedule biçimi) KAYDETMEDEN doğrular ve sıradaki 5 koşuyu UTC olarak döner: { isValid, errors[{property,message}], upcomingFireTimesUtc[] }. microservice_create/update öncesi tanımın beklenen saatlerde koştuğunu buradan görün; alan listesi için platform_guide(\'microservice\') §Zamanlanmış iş.',
405
+ inputSchema: {
406
+ project: projectParam,
407
+ environment: environmentParam,
408
+ schedule: z.record(z.unknown()).describe('ScheduleDefinition: mode, timeZoneId, startAt, every, unit, timeOfDay, daysOfWeek, daysOfMonth, cronExpression, end')
409
+ },
410
+ annotations: readOnly
411
+ }, handle(config, ({ project, environment, schedule }) => platform.tenant('POST', project, '/microservices/schedule/preview', { environment, body: { schedule } })));
412
+ server.registerTool('microservice_schedule_control', {
413
+ title: 'Zamanlamayı yönet',
414
+ description: 'Zamanlanmış microservice\'i duraklatır (pause), sürdürür (resume) ya da cron\'u beklemeden bir kez koşturur (trigger). pause/resume yalnız zamanlayıcıdaki durumu değiştirir, tanımı değil; duraklatma cron güncellemesi ve deployment senkronundan sonra da korunur. resume: duraklatılmışken bir koşu kaçırıldıysa iş bir kez hemen koşar. trigger KUYRUĞA ALIR, sonucu beklemez: deployment ya da template kurulumu sürüyorsa koşu veto edilir ve geçmişe hiç düşmez — sonucu microservice_job_history ile doğrulayın. Zamanlayıcıda kaydı olmayan iş 400 (ScheduleJobNotRegisteredException) döner. Production ortamında üç eylem de 400 CantWriteInProductionException ile reddedilir; trigger ayrıca deployment/template kurulumu sürerken reddedilir.',
415
+ inputSchema: {
416
+ project: projectParam,
417
+ environment: environmentParam,
418
+ id: guid,
419
+ action: z.enum(['pause', 'resume', 'trigger']).describe('pause | resume | trigger')
420
+ },
421
+ annotations: writes
422
+ }, handle(config, async ({ project, environment, id, action }) => {
423
+ await platform.tenant('POST', project, `/microservices/${id}/schedule/${action}`, { environment });
424
+ return action === 'trigger'
425
+ ? { action, accepted: true, note: 'Kuyruğa alındı; koşuyu microservice_job_history ile doğrulayın.' }
426
+ : { action, accepted: true, note: 'Güncel durum için microservice_schedule_get.' };
427
+ }));
198
428
  }