@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6

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 (101) hide show
  1. package/.env.example +8 -3
  2. package/.env.production.example +2 -0
  3. package/README.md +229 -193
  4. package/dist/config.js +20 -2
  5. package/dist/knowledge/backendConstraints.js +897 -0
  6. package/dist/knowledge/componentCatalog.js +196 -42
  7. package/dist/knowledge/coverage.js +43 -0
  8. package/dist/knowledge/flowGuards.js +30 -0
  9. package/dist/knowledge/guideSections.js +49 -0
  10. package/dist/knowledge/hydrate.js +97 -0
  11. package/dist/knowledge/languageCodes.js +36 -0
  12. package/dist/knowledge/languages.js +105 -0
  13. package/dist/knowledge/layoutSummary.js +10 -0
  14. package/dist/knowledge/localization.js +51 -0
  15. package/dist/knowledge/normalize.js +449 -16
  16. package/dist/knowledge/outlines.js +136 -12
  17. package/dist/knowledge/propTypes.js +80 -0
  18. package/dist/knowledge/rules.js +618 -43
  19. package/dist/knowledge/schemaGuard.js +199 -0
  20. package/dist/knowledge/styleResolve.js +63 -4
  21. package/dist/observability/trace.js +27 -3
  22. package/dist/platform/errors.js +46 -4
  23. package/dist/platform/http.js +33 -4
  24. package/dist/platform/patch.js +27 -0
  25. package/dist/platform/refs.js +104 -0
  26. package/dist/platform/responses.js +9 -2
  27. package/dist/platform/screenshot.js +527 -16
  28. package/dist/platform/secrets.js +40 -0
  29. package/dist/platform/tokenVault.js +89 -0
  30. package/dist/platform/writeDiff.js +281 -0
  31. package/dist/resources/examples/layout-app-shell.json +1 -1
  32. package/dist/resources/examples/microservice-table-list.json +2 -4
  33. package/dist/resources/examples/page-dynamic-form.json +2 -2
  34. package/dist/resources/examples/page-flex-sections.json +99 -0
  35. package/dist/resources/examples/page-sign-in.json +3 -2
  36. package/dist/resources/guides/component.md +69 -8
  37. package/dist/resources/guides/entity.md +404 -16
  38. package/dist/resources/guides/expressions.md +44 -2
  39. package/dist/resources/guides/external-component.md +9 -0
  40. package/dist/resources/guides/getting-started.md +77 -7
  41. package/dist/resources/guides/layout.md +53 -10
  42. package/dist/resources/guides/microservice.md +226 -13
  43. package/dist/resources/guides/middleware.md +89 -1
  44. package/dist/resources/guides/page.md +383 -46
  45. package/dist/resources/guides/translation.md +126 -8
  46. package/dist/resources/guides/view.md +16 -1
  47. package/dist/resources/guides/workflow.md +198 -27
  48. package/dist/resources/knowledge.js +5 -0
  49. package/dist/resources/rules.js +27 -0
  50. package/dist/resources/schema-fingerprint.json +8 -0
  51. package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
  52. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  53. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  54. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  55. package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
  56. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  57. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  58. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  59. package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
  60. package/dist/resources/schemas/GetRowsQuery.json +3 -0
  61. package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
  62. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
  64. package/dist/resources/schemas/StepDto.json +23 -3
  65. package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
  66. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  67. package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
  68. package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
  69. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  70. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  71. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  72. package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
  73. package/dist/server.js +4 -0
  74. package/dist/tools/access.js +3 -3
  75. package/dist/tools/applications.js +1 -1
  76. package/dist/tools/automations.js +63 -30
  77. package/dist/tools/common.js +39 -0
  78. package/dist/tools/components.js +16 -7
  79. package/dist/tools/datasources.js +83 -21
  80. package/dist/tools/deployments.js +34 -2
  81. package/dist/tools/dtos.js +25 -12
  82. package/dist/tools/entities.js +113 -28
  83. package/dist/tools/externalComponents.js +9 -3
  84. package/dist/tools/files.js +20 -6
  85. package/dist/tools/flows.js +110 -11
  86. package/dist/tools/folders.js +7 -2
  87. package/dist/tools/globalFunctions.js +63 -0
  88. package/dist/tools/layouts.js +103 -29
  89. package/dist/tools/microservices.js +229 -38
  90. package/dist/tools/organizations.js +5 -6
  91. package/dist/tools/pages.js +365 -100
  92. package/dist/tools/platformInfo.js +152 -11
  93. package/dist/tools/projects.js +183 -33
  94. package/dist/tools/queries.js +32 -10
  95. package/dist/tools/rows.js +159 -45
  96. package/dist/tools/screenWrite.js +59 -0
  97. package/dist/tools/screens.js +28 -0
  98. package/dist/tools/translations.js +24 -8
  99. package/dist/tools/views.js +20 -10
  100. package/dist/tools/workflows.js +33 -16
  101. package/package.json +6 -3
@@ -1,9 +1,12 @@
1
- import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
1
+ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive, languageParam } from './common.js';
2
+ import { RefResolver } from '../platform/refs.js';
2
3
  import { normalizeWorkflowDefinition, withNormalization } from '../knowledge/normalize.js';
3
4
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
4
5
  import { policyRules } from '../knowledge/rules.js';
6
+ import { languageHeader } from '../knowledge/languageCodes.js';
5
7
  import { handle } from '../platform/responses.js';
6
8
  import { z } from 'zod';
9
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
7
10
  const policyOverrideParam = z.boolean().optional()
8
11
  .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
9
12
  export function registerAutomationTools(server, platform) {
@@ -11,93 +14,123 @@ export function registerAutomationTools(server, platform) {
11
14
  server.registerTool('automation_list', {
12
15
  title: 'Automation listele',
13
16
  description: 'Entity\'ye bağlı automation\'ları listeler.',
14
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, searchTerm: z.string().optional(), ...pagingParams },
17
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, searchTerm: z.string().optional(), ...pagingParams },
15
18
  annotations: readOnly
16
- }, handle(config, ({ project, environment, entityId, ...query }) => platform.tenant('GET', project, `/entities/${entityId}/automation/list`, { environment, query })));
19
+ }, handle(config, async ({ project, environment, entityId, ...query }) => {
20
+ const resolver = new RefResolver(platform, project, environment);
21
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
22
+ return withResolved(await platform.tenant('GET', project, `/entities/${resolvedEntityId}/automation/list`, { environment, query }), resolver.resolved);
23
+ }));
17
24
  server.registerTool('automation_get', {
18
25
  title: 'Automation getir',
19
26
  description: 'Tek automation\'ı step ağacıyla getirir.',
20
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, id: guid },
27
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, id: guid },
21
28
  annotations: readOnly
22
- }, handle(config, ({ project, environment, entityId, id }) => platform.tenant('GET', project, `/entities/${entityId}/automation/${id}`, { environment })));
29
+ }, handle(config, async ({ project, environment, entityId, id }) => {
30
+ const resolver = new RefResolver(platform, project, environment);
31
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
32
+ return withResolved(await platform.tenant('GET', project, `/entities/${resolvedEntityId}/automation/${id}`, { environment }), resolver.resolved);
33
+ }));
23
34
  server.registerTool('automation_create', {
24
35
  title: 'Automation oluştur',
25
- description: "Entity'ye automation bağlar. definition = CreateWorkflowCommand gövdesi ('automation' nesnesi içermeli; entityId route'tan set edilir). İlk kullanımdan önce platform_guide('workflow') okuyun; gövde politika kontrolünden geçer.",
26
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
36
+ description: "Entity'ye automation bağlar. definition = CreateWorkflowCommand gövdesi ('automation' nesnesi içermeli; entityId route'tan set edilir). İlk kullanımdan önce platform_guide('workflow') okuyun; gövde politika kontrolünden geçer. " + constraintSummary('CreateWorkflowCommand'),
37
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, definition: definitionParam, policyOverride: policyOverrideParam },
27
38
  annotations: writes
28
39
  }, handle(config, async ({ project, environment, entityId, definition, policyOverride }) => {
29
40
  const normalized = normalizeWorkflowDefinition(definition);
30
41
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'create', workflowKind: 'automation' }, policyOverride));
31
- const payload = await platform.tenant('POST', project, `/entities/${entityId}/automation`, { environment, body: definition });
32
- return attachFindings(withNormalization(payload, normalized), findings);
42
+ const resolver = new RefResolver(platform, project, environment);
43
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
44
+ const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/automation`, { environment, body: definition });
45
+ return attachFindings(withResolved(withNormalization(payload, normalized), resolver.resolved), findings);
33
46
  }));
34
47
  server.registerTool('automation_update', {
35
48
  title: 'Automation güncelle',
36
- description: "Automation tanımını günceller. definition = UpdateWorkflowCommand gövdesi ('automation' nesnesi içermeli); gövde politika kontrolünden geçer.",
37
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
49
+ description: "Automation tanımını günceller. definition = UpdateWorkflowCommand gövdesi ('automation' nesnesi içermeli); gövde politika kontrolünden geçer. " + constraintSummary('UpdateWorkflowCommand'),
50
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
38
51
  annotations: updates
39
52
  }, handle(config, async ({ project, environment, entityId, id, definition, policyOverride }) => {
40
53
  const normalized = normalizeWorkflowDefinition(definition);
41
54
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: 'automation' }, policyOverride));
42
- const payload = await platform.tenant('PUT', project, `/entities/${entityId}/automation/${id}`, { environment, body: definition });
43
- return attachFindings(withNormalization(payload, normalized), findings);
55
+ const resolver = new RefResolver(platform, project, environment);
56
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
57
+ const payload = await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/automation/${id}`, { environment, body: definition });
58
+ return attachFindings(withResolved(withNormalization(payload, normalized), resolver.resolved), findings);
44
59
  }));
45
60
  server.registerTool('automation_delete', {
46
61
  title: 'Automation sil',
47
62
  description: 'Automation\'ı siler.',
48
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, id: guid },
63
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, id: guid },
49
64
  annotations: destructive
50
- }, handle(config, ({ project, environment, entityId, id }) => platform.tenant('DELETE', project, `/entities/${entityId}/automation/${id}`, { environment })));
65
+ }, handle(config, async ({ project, environment, entityId, id }) => {
66
+ const resolver = new RefResolver(platform, project, environment);
67
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
68
+ return withResolved(await platform.tenant('DELETE', project, `/entities/${resolvedEntityId}/automation/${id}`, { environment }), resolver.resolved);
69
+ }));
51
70
  server.registerTool('automation_reorder', {
52
71
  title: 'Automation sırasını değiştir',
53
72
  description: "Entity'nin automation'larının çalışma sırasını toplu günceller; aynı operasyona bağlı birden çok automation'da sıra davranışı belirler.",
54
73
  inputSchema: {
55
74
  project: projectParam,
56
75
  environment: environmentParam,
57
- entityId: guid,
76
+ entityId: entityRef,
58
77
  automations: z.array(z.object({ id: guid, order: z.number().int().min(0) })).min(1)
59
78
  .describe('Tüm automation\'lar için (id, order) çiftleri; listeyi automation_list ile kurun')
60
79
  },
61
80
  annotations: updates
62
- }, handle(config, ({ project, environment, entityId, automations }) => platform.tenant('PUT', project, `/entities/${entityId}/automation/orders`, { environment, body: { automations } })));
81
+ }, handle(config, async ({ project, environment, entityId, automations }) => {
82
+ const resolver = new RefResolver(platform, project, environment);
83
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
84
+ return withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/automation/orders`, { environment, body: { automations } }), resolver.resolved);
85
+ }));
63
86
  server.registerTool('automation_trigger', {
64
87
  title: 'Automation tetikle',
65
- description: 'Automation\'ı elle tetikler. Fire-and-forget çalışır: sonuç bu yanıtta dönmez, automation_histories ile izleyin.',
88
+ description: "Automation'ı elle tetikler — CANLI çalıştırır, gerçek satır yazar. Fire-and-forget: sonuç bu yanıtta dönmez, automation_histories + row_list ile izleyin. VERİ ÜRETMEK İÇİN KULLANMAYIN: rowContext almaz, bu yüzden yazdığı her zaman damgası UtcNow() olur — tarihsel bir tohumun üstünde tek bir doğrulama çağrısı huni ortalamasını bozar (B-52). Akışın ŞEKLİNİ sınamak istiyorsanız automation_preview kullanın: automation da bir workflow'dur, preview tek transaction'da koşar ve geri alınır, yani hiçbir satır kalmaz.",
66
89
  inputSchema: {
67
90
  project: projectParam,
68
91
  environment: environmentParam,
69
- entityId: guid,
92
+ entityId: entityRef,
70
93
  id: guid,
71
94
  operation: z.string().optional(),
72
95
  recordIds: z.array(z.union([z.string(), z.number()])).optional().describe('Tetiklenecek satır id\'leri')
73
96
  },
74
97
  annotations: writes
75
- }, handle(config, ({ project, environment, entityId, id, operation, recordIds }) => platform.tenant('POST', project, `/entities/${entityId}/automation/${id}/trigger`, {
76
- environment,
77
- body: { operation, recordIds: recordIds ?? [] }
78
- })));
98
+ }, handle(config, async ({ project, environment, entityId, id, operation, recordIds }) => {
99
+ const resolver = new RefResolver(platform, project, environment);
100
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
101
+ return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/automation/${id}/trigger`, {
102
+ environment,
103
+ body: { operation, recordIds: recordIds ?? [] }
104
+ }), resolver.resolved);
105
+ }));
79
106
  server.registerTool('automation_histories', {
80
107
  title: 'Automation geçmişi',
81
108
  description: 'Automation çalıştırma geçmişini listeler; automation_trigger sonrası sonucu buradan doğrulayın.',
82
109
  inputSchema: {
83
110
  project: projectParam,
84
111
  environment: environmentParam,
85
- entityId: guid,
112
+ entityId: entityRef,
86
113
  id: guid,
87
114
  status: z.string().optional().describe('EventStatus filtresi'),
88
115
  ...pagingParams
89
116
  },
90
117
  annotations: readOnly
91
- }, handle(config, ({ project, environment, entityId, id, ...query }) => platform.tenant('GET', project, `/entities/${entityId}/automation/${id}/histories`, { environment, query })));
118
+ }, handle(config, async ({ project, environment, entityId, id, ...query }) => {
119
+ const resolver = new RefResolver(platform, project, environment);
120
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
121
+ return withResolved(await platform.tenant('GET', project, `/entities/${resolvedEntityId}/automation/${id}/histories`, { environment, query }), resolver.resolved);
122
+ }));
92
123
  server.registerTool('automation_preview', {
93
124
  title: 'Automation önizle',
94
- description: 'Kaydedilmemiş automation tanımını çalıştırır; kaydetmeden önce preview ile doğrulayın. definition = PreviewWorkflowCommand gövdesi.',
95
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
125
+ description: "Kaydedilmemiş automation tanımını çalıştırır; kaydetmeden önce preview ile doğrulayın. definition = PreviewWorkflowCommand gövdesi. Preview tek transaction'da koşar ve sonunda ROLLBACK edilir — kalıcı satır yazmaz (ölçüldü, İK koşusu W-1); code step'teki dış HTTP çağrıları istisnadır. Davranış kuralları: platform_guide('workflow').",
126
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, definition: definitionParam, policyOverride: policyOverrideParam, language: languageParam },
96
127
  annotations: writes
97
- }, handle(config, async ({ project, environment, entityId, definition, policyOverride }) => {
128
+ }, handle(config, async ({ project, environment, entityId, definition, policyOverride, language }) => {
98
129
  const normalized = normalizeWorkflowDefinition(definition);
99
130
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'preview', workflowKind: 'automation' }, policyOverride));
100
- const payload = await platform.tenant('POST', project, `/entities/${entityId}/automation/preview`, { environment, body: definition });
101
- return attachFindings(withNormalization(payload, normalized), findings);
131
+ const resolver = new RefResolver(platform, project, environment);
132
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
133
+ const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/automation/preview`, { environment, body: definition, headers: languageHeader(language, normalized) });
134
+ return attachFindings(withResolved(withNormalization(payload, normalized), resolver.resolved), findings);
102
135
  }));
103
136
  }
@@ -1,11 +1,50 @@
1
+ import { PlatformApiError } from '../platform/errors.js';
2
+ import { isGuid } from '../platform/refs.js';
1
3
  import { z } from 'zod';
2
4
  export const guid = z.string().uuid();
5
+ export const entityRef = z.string().min(1)
6
+ .describe("Entity id (GUID) ya da slug'ı ('employees' ya da 'BuiltIn/employees'); slug verilirse gateway id'ye çevirir ve yanıtta gatewayResolved ile bildirir");
7
+ export const appRef = z.string().min(1)
8
+ .describe("Application id (GUID) ya da slug'ı; slug verilirse gateway id'ye çevirir ve yanıtta gatewayResolved ile bildirir");
9
+ /** GUID verildiğinde ağaç HİÇ çekilmez — slug ek yoldur, mevcut GUID'li çağrıların maliyeti değişmez. */
10
+ export async function resolveEntityId(resolver, ref) {
11
+ return isGuid(ref) ? ref : (await resolver.entity(ref)).entityId;
12
+ }
13
+ /**
14
+ * Kök `entityId`'yi hem `RefResolver.entity` hem `hydrateMicroserviceRefs` günlüğe yazar (biri
15
+ * çözümü, öteki gövdeye yazmayı bildirir) — aynı satır iki kez görünmesin diye tekilleştirilir.
16
+ */
17
+ export function withResolved(payload, resolved) {
18
+ const unique = [...new Set(resolved)];
19
+ if (unique.length === 0)
20
+ return payload;
21
+ if (payload && typeof payload === 'object' && !Array.isArray(payload))
22
+ return { ...payload, gatewayResolved: unique };
23
+ return { data: payload, gatewayResolved: unique };
24
+ }
3
25
  export const projectParam = z.string().min(1).optional()
4
26
  .describe('Proje slug\'ı; boşsa DEFAULT_PROJECT kullanılır');
5
27
  export const environmentParam = z.string().min(1).optional()
6
28
  .describe('Environment adı (X-Environment); boşsa DEFAULT_ENVIRONMENT kullanılır');
29
+ export const languageParam = z.string().min(1).optional()
30
+ .describe("İstek dili (ISO 639-1, ör. 'en'); X-Data-Language olarak gider. Çok dilli alanlar bu dilde okunur/yazılır, workflow'da context.language ve ham sorguda {{language}} bu değeri alır. Verilmezse platform Accept-Language'a, o da yoksa projenin varsayılan diline düşer. Bölgeli değer ('tr-TR') kabul edilir ve gateway indirger.");
7
31
  export const definitionParam = z.record(z.unknown())
8
32
  .describe('API komut gövdesi birebir geçirilir; şablon için ilgili *_get çıktısını ya da ngapps://schemas kaynağını kullanın');
33
+ /**
34
+ * Micro-op listesi iki adla dolaşıyordu: sayfa tool'ları `ops`, entity/microservice/workflow
35
+ * patch'leri `operations`. Ajan ikisini karıştırınca ilk çağrı şema hatasıyla düşüyordu
36
+ * (09-04 Y-07). İki ad da her yerde kabul edilir; ikisi birden verilirse çağrı durur.
37
+ */
38
+ export function microOps(tool, ops, operations) {
39
+ if (ops !== undefined && operations !== undefined) {
40
+ throw new PlatformApiError(`${tool}: 'ops' ve 'operations' birlikte verilemez — ikisi aynı listenin iki adıdır, hangisinin geçerli olduğu belirsiz kalır. Birini seçin.`, 400);
41
+ }
42
+ const resolved = ops ?? operations;
43
+ if (resolved === undefined || resolved.length === 0) {
44
+ throw new PlatformApiError(`${tool}: uygulanacak micro-op listesi boş — 'ops' (ya da eşanlamlısı 'operations') en az bir op taşımalı.`, 400);
45
+ }
46
+ return resolved;
47
+ }
9
48
  export const pagingParams = {
10
49
  pageIndex: z.number().int().min(0).optional().describe('0 tabanlı sayfa numarası'),
11
50
  pageLength: z.number().int().min(1).max(200).optional().describe('Sayfa boyu; platform varsayılanı 10'),
@@ -1,7 +1,7 @@
1
1
  import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
2
- import { normalizeComponentDefaults, componentDefaultsNote, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, withNormalization } from '../knowledge/normalize.js';
2
+ import { normalizeComponentDefaults, componentDefaultsNote, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, withNormalization } from '../knowledge/normalize.js';
3
3
  import { eventContractsFor, eventPayloadNote } from '../knowledge/componentEvents.js';
4
- import { knowledgeFor, knowledgeNote } from '../knowledge/componentCatalog.js';
4
+ import { knowledgeFor, knowledgeNote, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
5
5
  import { componentUpdateBody, fetchHost, schemaParam, unwrap } from './screens.js';
6
6
  import { PlatformApiError } from '../platform/errors.js';
7
7
  import { handle } from '../platform/responses.js';
@@ -72,10 +72,10 @@ export function componentLayoutRules(components) {
72
72
  export async function applyCatalogLayoutRules(platform, project, environment, applicationId, nodes) {
73
73
  try {
74
74
  const rules = await fetchComponentLayoutRules(platform, project, environment, applicationId);
75
- return { clamped: clampBlockSizes(nodes, rules.maxSizes), heights: normalizeHeightTypes(nodes, rules.autoHeights) };
75
+ return { clamped: clampBlockSizes(nodes, rules.maxSizes), heights: normalizeHeightTypes(nodes, rules.autoHeights), sizes: normalizeSizeStrings(nodes) };
76
76
  }
77
77
  catch {
78
- return { clamped: [], heights: normalizeHeightTypes(nodes, new Map()) };
78
+ return { clamped: [], heights: normalizeHeightTypes(nodes, new Map()), sizes: normalizeSizeStrings(nodes) };
79
79
  }
80
80
  }
81
81
  /**
@@ -182,6 +182,14 @@ export function registerComponentTools(server, platform) {
182
182
  ...(knowledge.guide ? { guide: knowledge.guide } : {})
183
183
  }
184
184
  : { knowledgeNote }),
185
+ ...(componentSlug && INTERNAL_ROLES_BY_TYPE[componentSlug]
186
+ ? {
187
+ internalRoles: INTERNAL_ROLES_BY_TYPE[componentSlug],
188
+ internalRolesNote: "Bu komponentin İÇ kontrolleri dışarıdan adreslenebilir: page_screenshot actions hedefine '<elementId>__<rol>' yazın (ör. 'tbl_vacancy__pager-next'). Kimlikler runtime'ın data-element-id özniteliğidir; ikinci bir mekanizma yoktur."
189
+ }
190
+ : {}),
191
+ appearanceValues: APPEARANCE_TOKEN_SETS,
192
+ stateFlags: STATE_FLAG_NOTES,
185
193
  validations: definition.validations,
186
194
  sizes: definition.blocks
187
195
  };
@@ -217,7 +225,7 @@ export function registerComponentTools(server, platform) {
217
225
  const fixed = normalizeComponentDefaults(body.schema);
218
226
  const catalog = (body.schema ?? []).length > 0
219
227
  ? await applyCatalogLayoutRules(platform, project, environment, applicationId, body.schema)
220
- : { clamped: [], heights: [] };
228
+ : { clamped: [], heights: [], sizes: [] };
221
229
  return platform.tenant('POST', project, `/components/${applicationId}`, {
222
230
  environment,
223
231
  body: {
@@ -228,7 +236,7 @@ export function registerComponentTools(server, platform) {
228
236
  schema: [],
229
237
  ...Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined))
230
238
  }
231
- }).then(response => withNormalization(response, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights)]));
239
+ }).then(response => withNormalization(response, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes)]));
232
240
  }));
233
241
  server.registerTool('component_update', {
234
242
  title: 'Komponent güncelle',
@@ -267,9 +275,10 @@ export function registerComponentTools(server, platform) {
267
275
  const rules = componentLayoutRules(components);
268
276
  const clamped = overrides.schema === undefined ? [] : clampBlockSizes(overrides.schema, rules.maxSizes);
269
277
  const heights = overrides.schema === undefined ? [] : normalizeHeightTypes(overrides.schema, rules.autoHeights);
278
+ const sizes = overrides.schema === undefined ? [] : normalizeSizeStrings(overrides.schema);
270
279
  const body = componentUpdateBody({ ...detailed, applicationId }, overrides);
271
280
  await platform.tenant('PUT', project, `/components/${applicationId}/${slug}`, { environment, body });
272
- return withNormalization({ slug, changed: Object.keys(overrides), preserved: { schemaNodes: body.schema.length } }, [...componentDefaultsNote(fixed), ...blockSizeNote(clamped), ...heightTypeNote(heights)]);
281
+ return withNormalization({ slug, changed: Object.keys(overrides), preserved: { schemaNodes: body.schema.length } }, [...componentDefaultsNote(fixed), ...blockSizeNote(clamped), ...heightTypeNote(heights), ...sizeStringNote(sizes)]);
273
282
  }));
274
283
  server.registerTool('component_delete', {
275
284
  title: 'Komponent sil',
@@ -1,21 +1,58 @@
1
1
  import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { withNormalization } from '../knowledge/normalize.js';
3
+ import { PlatformApiError } from '../platform/errors.js';
2
4
  import { handle } from '../platform/responses.js';
3
5
  import { z } from 'zod';
6
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
7
+ import { policyRules } from '../knowledge/rules.js';
8
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
9
+ /**
10
+ * B-56 — tam yanıtın %79'u (35.645 / 45.026 karakter, 2026-09-07 ölçümü) 816 satırlık ICU
11
+ * collation sözlüğü. Ajanın alan tipi kararına giren şey collation DEĞERLERİ değil, tipin
12
+ * collation KABUL ETTİĞİ bilgisidir ve o zaten options anahtarında duruyor.
13
+ *
14
+ * Backend'in özet ucu (b-12) aynı projeksiyonu sunucu tarafında yapar; bu fonksiyon o uç
15
+ * bulunmayan sürümler için aynı şekli tam yanıttan üretir. Eleme ve düzleştirme dışında bir şey
16
+ * yapmaz — tam yanıtta olmayan hiçbir alanı uydurmaz.
17
+ */
18
+ function configurationSummary(body) {
19
+ const groups = new Map();
20
+ for (const group of body.dataTypes ?? []) {
21
+ const key = String(group.commonType ?? '');
22
+ const merged = groups.get(key) ?? { commonType: group.commonType, defaultDbType: undefined, types: [] };
23
+ for (const type of group.types ?? []) {
24
+ if (merged.defaultDbType === undefined && type.isDefault === true)
25
+ merged.defaultDbType = type.dbType;
26
+ merged.types.push({
27
+ dbType: type.dbType,
28
+ options: (type.options ?? []).map(option => option.key),
29
+ convertibleTypes: type.convertibleTypes ?? []
30
+ });
31
+ }
32
+ groups.set(key, merged);
33
+ }
34
+ const definitions = Object.fromEntries(Object.entries(body.definitions ?? {})
35
+ .filter(([name]) => name !== 'collation')
36
+ .map(([name, entries]) => [name, (entries ?? []).map(entry => entry.value)]));
37
+ return { dataTypes: [...groups.values()], definitions: definitions };
38
+ }
39
+ // Sınırlar CreateDatasourceCommandValidator / UpdateDatasourceCommandValidator'ın aynasıdır (DS-001):
40
+ // dbName ve host backend'de NotNull().NotEmpty() — eski 'optional' şekil her çağrıda 422 yiyordu.
4
41
  const connectionInput = {
5
- name: z.string().min(1),
6
- description: z.string().optional(),
7
- dbName: z.string().optional(),
8
- type: z.string().describe('DatasourceType (ör. Postgres); datasource_resources tool\'u geçerli tipleri listeler'),
9
- host: z.string().optional(),
10
- port: z.number().int().optional(),
11
- properties: z.record(z.unknown()).optional(),
42
+ name: z.string().min(1).max(50).describe('En fazla 50 karakter'),
43
+ description: z.string().max(255).optional().describe('En fazla 255 karakter'),
44
+ dbName: z.string().min(1).max(63).describe('Bağlanılacak veritabanı adı; zorunlu, en fazla 63 karakter'),
45
+ type: z.enum(['postgresql', 'mongodb']).describe("DatasourceType, küçük harf: 'postgresql' | 'mongodb'; yapılandırma ayrıntısı datasource_resources"),
46
+ host: z.string().min(1).max(255).describe('Zorunlu, en fazla 255 karakter'),
47
+ port: z.number().int().min(0).max(65535).optional(),
48
+ properties: z.record(z.unknown()).optional().describe("postgresql: { initialDatabase ≤63, userName ≤63, password ≤1024 } üçü zorunlu · mongodb: { authenticationType: none|password|ldap|kerberos|x.509, … }; 'password' iken authenticationDatabase/userName/password zorunlu (DS-001)"),
12
49
  connectionOptions: z.record(z.string()).optional()
13
50
  };
14
51
  export function registerDatasourceTools(server, platform) {
15
52
  const config = platform.config;
16
53
  server.registerTool('datasource_list', {
17
54
  title: 'Datasource listele',
18
- description: 'Projedeki datasource\'ları listeler; tree=true entity ağacıyla birlikte döner.',
55
+ description: "Projedeki datasource'ları listeler; tree=true entity ağacıyla birlikte döner. isBuiltIn: true olan kayıt projenin KENDİ veritabanıdır ve entity'ler oraya açılır; bu bayrak yalnız tree'siz listede döner (ağaç ucu taşımaz) — ağaçla çalışırken built-in'i project_overview çıktısından tanıyın.",
19
56
  inputSchema: {
20
57
  project: projectParam,
21
58
  environment: environmentParam,
@@ -28,38 +65,44 @@ export function registerDatasourceTools(server, platform) {
28
65
  }, handle(config, ({ project, environment, tree, ...query }) => platform.tenant('GET', project, tree ? '/datasources/tree' : '/datasources', { environment, query })));
29
66
  server.registerTool('datasource_get', {
30
67
  title: 'Datasource getir',
31
- description: 'Tek datasource\'u id ile getirir.',
68
+ description: "Tek datasource'u id ile getirir. Parola yanıtta gateway tarafından maskelenir (gatewayMasked); built-in datasource'un kimlik bilgisi hiçbir tool'dan okunamaz ve entity açmak için gerekmez.",
32
69
  inputSchema: { project: projectParam, environment: environmentParam, id: guid },
33
70
  annotations: readOnly
34
71
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/datasources/${id}`, { environment })));
35
72
  server.registerTool('datasource_create', {
36
73
  title: 'Datasource oluştur',
37
- description: 'Projeye yeni datasource ekler; önce datasource_test_connection ile bağlantıyı doğrulamak iyi pratiktir.',
74
+ description: "Projeye YENİ BİR DIŞ veritabanı bağlar. YALNIZ kullanıcı kendi veritabanını verdiğinde çağırın (host/port/kullanıcı/parola elinizde). Her proje project_create ile birlikte kendi veritabanını ve silinemez bir built-in datasource'unu alır; entity'ler, hazır users/roles tabloları ve işlerin çoğu oradadır — datasource_list çıktısında isBuiltIn: true olan kayıt odur. Temiz bir isim alanı istiyorsanız datasource değil YENİ PROJE açın: veritabanı proje başına, şema environment başına ayrılır; tek datasource içinde şema/önek vermenin yolu yoktur. Parola zorunludur ve built-in'inki hiçbir tool'dan okunamaz — kimlik bilgisi elinizde yoksa bu tool'un yeri yoktur. Bağlantıyı önce datasource_test_connection ile doğrulayın. " + constraintSummary('CreateDatasourceCommand'),
38
75
  inputSchema: { project: projectParam, environment: environmentParam, ...connectionInput },
39
76
  annotations: writes
40
- }, handle(config, ({ project, environment, ...body }) => platform.tenant('POST', project, '/datasources', { environment, body })));
77
+ }, handle(config, async ({ project, environment, ...body }) => {
78
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'datasource', body, { operation: 'create' }));
79
+ return attachFindings(await platform.tenant('POST', project, '/datasources', { environment, body }), findings);
80
+ }));
41
81
  server.registerTool('datasource_update', {
42
82
  title: 'Datasource güncelle',
43
- description: 'Datasource bağlantı bilgilerini günceller.',
83
+ description: "Dış bir datasource'un bağlantı bilgilerini günceller; YALNIZ kullanıcı yeni bağlantı bilgisi verdiğinde çağırın. Built-in datasource'ta bu çağrı SESSİZCE DÜŞER: platform yalnız connectionOptions'ı yazar, name/host/port/dbName/properties atılır ve çağrı yine 200 döner (DatasourceService.UpdateAsync). " + constraintSummary('UpdateDatasourceCommand'),
44
84
  inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...connectionInput },
45
85
  annotations: updates
46
- }, handle(config, ({ project, environment, id, ...body }) => platform.tenant('PUT', project, `/datasources/${id}`, { environment, body })));
86
+ }, handle(config, async ({ project, environment, id, ...body }) => {
87
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'datasource', body, { operation: 'update' }));
88
+ return attachFindings(await platform.tenant('PUT', project, `/datasources/${id}`, { environment, body }), findings);
89
+ }));
47
90
  server.registerTool('datasource_delete', {
48
91
  title: 'Datasource sil',
49
- description: 'Datasource\'u siler; altındaki entity tanımları da etkilenir.',
92
+ description: "Datasource'u siler; altındaki entity tanımları da etkilenir. Projenin built-in datasource'u SİLİNEMEZ (BuiltInDatasourceCannotDeletedException).",
50
93
  inputSchema: { project: projectParam, environment: environmentParam, id: guid },
51
94
  annotations: destructive
52
95
  }, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/datasources/${id}`, { environment })));
53
96
  server.registerTool('datasource_test_connection', {
54
97
  title: 'Bağlantıyı test et',
55
- description: "Kayıtlı datasource için 'id', kaydedilmemiş bağlantı için type/host/port alanlarını verin.",
98
+ description: "Kullanıcının verdiği bir DIŞ bağlantıyı datasource_create öncesi doğrular: kayıtlı datasource için 'id', kaydedilmemiş bağlantı için type/host/port alanlarını verin. Built-in datasource'u test etmenin anlamı yoktur — bağlantısını platform kurar ve parolası hiçbir tool'dan okunamaz.",
56
99
  inputSchema: {
57
100
  project: projectParam,
58
101
  environment: environmentParam,
59
102
  id: guid.optional(),
60
- type: z.string().optional(),
61
- host: z.string().optional(),
62
- port: z.number().int().optional(),
103
+ type: z.enum(['postgresql', 'mongodb']).optional(),
104
+ host: z.string().min(1).max(255).optional().describe("'id' verilmediyse zorunlu; en fazla 255 karakter (TestConnectionCommandValidator)"),
105
+ port: z.number().int().min(0).max(65535).optional(),
63
106
  properties: z.record(z.unknown()).optional(),
64
107
  connectionOptions: z.record(z.string()).optional()
65
108
  },
@@ -75,8 +118,27 @@ export function registerDatasourceTools(server, platform) {
75
118
  }, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/datasources/resources', { environment })));
76
119
  server.registerTool('datasource_configuration_options', {
77
120
  title: 'Yapılandırma seçenekleri',
78
- description: 'Kayıtlı datasource\'un tipine özel yapılandırma seçeneklerini döner.',
79
- inputSchema: { project: projectParam, environment: environmentParam, id: guid },
121
+ description: "Datasource'un alan tipi kataloğunu döner: commonType başına dbType listesi, varsayılan tip, tipin kabul ettiği seçenek anahtarları (length/precision/collation/...) ve dönüştürülebilir tipler; ayrıca ilişki kuralları (onUpdate/onDelete) ve identity generation tipleri. VARSAYILAN YANIT ÖZETTİR (~5 KB): collation DEĞER listesi (816 kayıt, tam yanıtın %79'u) dışarıdadır — tipin collation kabul edip etmediği zaten options'ta yazar. Collation değerleri gerçekten gerekiyorsa includeCollations: true ile tam yanıtı isteyin (~45 KB).",
122
+ inputSchema: {
123
+ project: projectParam,
124
+ environment: environmentParam,
125
+ id: guid,
126
+ includeCollations: z.boolean().optional()
127
+ .describe("true: platformun tam yanıtı döner (816 kayıtlık collation sözlüğü dâhil, ~45 KB). Yalnız bir alana collation SEÇECEKSENİZ gerekir.")
128
+ },
80
129
  annotations: readOnly
81
- }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/datasources/${id}/configuration-options`, { environment })));
130
+ }, handle(config, async ({ project, environment, id, includeCollations }) => {
131
+ if (includeCollations)
132
+ return platform.tenant('GET', project, `/datasources/${id}/configuration-options`, { environment });
133
+ try {
134
+ return await platform.tenant('GET', project, `/datasources/${id}/configuration-options/summary`, { environment });
135
+ }
136
+ catch (error) {
137
+ // Yalnız "uç yok" durumunda yedek yola düşülür; başka hata yutulmaz.
138
+ if (!(error instanceof PlatformApiError) || error.status !== 404)
139
+ throw error;
140
+ }
141
+ const full = await platform.tenant('GET', project, `/datasources/${id}/configuration-options`, { environment });
142
+ return withNormalization({ data: configurationSummary(full?.data ?? {}) }, ["özet ucu bu backend sürümünde YOK (404): tam yanıt çekilip özet gateway tarafında üretildi — collation DEĞERLERİ düştü, geri kalan alanlar dokunulmadan geldi. Değerler gerekiyorsa includeCollations: true."]);
143
+ }));
82
144
  }
@@ -10,8 +10,40 @@ export function registerDeploymentTools(server, platform) {
10
10
  }, handle(config, ({ projectId, ...body }) => platform.org('POST', `/deployments/${projectId}/deploy`, { body })));
11
11
  server.registerTool('deployment_status', {
12
12
  title: 'Deployment durumu',
13
- description: 'Projenin son deployment durumunu döner.',
13
+ description: "Projenin son deployment durumunu döner. 'projectId' ZORUNLUDUR (project_list/project_get'ten alın) — verilmezse çağrı MCP şema katmanında reddedilir, platforma hiç gitmez. Hiç deployment yapılmamış projede yanıt BOŞ gelir; o durumda tool 'hiç deployment kaydı yok' bilgisini açıkça döner, yani boş yanıtı 'tool çalışmadı' diye okumayın.",
14
14
  inputSchema: { projectId: guid },
15
15
  annotations: readOnly
16
- }, handle(config, ({ projectId }) => platform.org('GET', `/deployments/${projectId}`)));
16
+ }, handle(config, async ({ projectId }) => {
17
+ const payload = await platform.org('GET', `/deployments/${projectId}`);
18
+ const record = payload && typeof payload === 'object' ? payload : undefined;
19
+ const data = record?.data;
20
+ // Platform hiç kayıt yokken `{"data":null,"message":null}` döner. Eski kural yalnız `data`
21
+ // ANAHTARI HİÇ YOKSA zarfa bakıyordu; `data: null` + `message: null` iki anahtar saydığı için
22
+ // "dolu" görünüyor, yanıt olduğu gibi geçiyor ve null'lar süzülünce tool 2 BAYT dönüyordu
23
+ // (2026-09-06 çağrı #181 — S-11 · B-36'nın üçüncü ölçümü).
24
+ const isEmpty = data === undefined || data === null
25
+ ? Object.values(record ?? {}).every(value => value === null || value === undefined)
26
+ : Array.isArray(data) ? data.length === 0 : Object.keys(data).length === 0;
27
+ // "Tool çalışmadı" ile "kayıt yok" boş `{}` yanıtında ayırt edilemiyordu (09-05 S-10).
28
+ if (!isEmpty)
29
+ return payload;
30
+ // Environment listesi BİR EK istek: 2026-09-06 koşusu deployment_start'ı atlayabilmek için
31
+ // tam olarak bu bilgiyi ayrı bir project_get turuyla aldı, yani ek istek bir tur EKLEMİYOR,
32
+ // bir turu buraya taşıyor. Okunamazsa boş sonuç sözleşmesi yine döner.
33
+ const project = await platform.org('GET', `/projects/${projectId}`).catch(() => undefined);
34
+ const projectData = project?.data;
35
+ const environments = Array.isArray(projectData?.environments)
36
+ ? projectData.environments.map(entry => ({
37
+ id: entry.id,
38
+ name: entry.name,
39
+ isProduction: entry.isProduction === true
40
+ }))
41
+ : undefined;
42
+ return {
43
+ projectId,
44
+ deployments: [],
45
+ note: "Bu projede hiç deployment kaydı yok — çağrı BAŞARILI, sonuç boş. Deployment başlatmak için deployment_start (kaynak/hedef environment id'leri aşağıdaki environments listesindedir).",
46
+ ...(environments ? { environments } : {})
47
+ };
48
+ }));
17
49
  }
@@ -1,6 +1,12 @@
1
1
  import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
2
3
  import { handle } from '../platform/responses.js';
3
4
  import { z } from 'zod';
5
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
6
+ import { policyRules } from '../knowledge/rules.js';
7
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
8
+ const policyOverrideParam = z.boolean().optional()
9
+ .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
4
10
  export function registerDtoTools(server, platform) {
5
11
  const config = platform.config;
6
12
  server.registerTool('dto_list', {
@@ -23,28 +29,35 @@ export function registerDtoTools(server, platform) {
23
29
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/datatransferobjects/${id}/scheme`, { environment })));
24
30
  server.registerTool('dto_create', {
25
31
  title: 'DTO oluştur',
26
- description: "Yeni DataTransferObject tanımlar (definition = CreateDataTransferObjectCommand). Workflow'un parameterDtoId/returnDtoId sözleşmeleri buradan çıkar; şablon için dto_get kullanın.",
27
- inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
32
+ description: "Yeni DataTransferObject tanımlar (definition = CreateDataTransferObjectCommand). Workflow'un parameterDtoId/returnDtoId sözleşmeleri buradan çıkar; şablon için dto_get kullanın. " + constraintSummary('CreateDataTransferObjectCommand'),
33
+ inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam, policyOverride: policyOverrideParam },
28
34
  annotations: writes
29
- }, handle(config, ({ project, environment, definition }) => platform.tenant('POST', project, '/datatransferobjects', { environment, body: definition })));
35
+ }, handle(config, async ({ project, environment, definition, policyOverride }) => {
36
+ // G-23: 09-05 koşusunda çocuk alanın `type: "text"` değeri sessizce `object`'e çevrildi ve
37
+ // bu ancak platformun kendi ürettiği bir DTO okunarak fark edildi. Doğrusu `"string"` —
38
+ // DTO-001 tip sözlüğünü artık istek gitmeden denetler.
39
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'dto', definition, { operation: 'create' }, policyOverride));
40
+ const payload = await platform.tenant('POST', project, '/datatransferobjects', { environment, body: definition });
41
+ return attachFindings(withWriteDiff(payload, writeDiff(definition, payload)), findings);
42
+ }));
30
43
  server.registerTool('dto_update', {
31
44
  title: 'DTO güncelle',
32
- description: 'DTO tanımını günceller (definition = UpdateDataTransferObjectCommand); mevcut hâli dto_get ile alın.',
33
- inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam },
45
+ description: 'DTO tanımını günceller (definition = UpdateDataTransferObjectCommand); mevcut hâli dto_get ile alın. ' + constraintSummary('UpdateDataTransferObjectCommand'),
46
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
34
47
  annotations: updates
35
- }, handle(config, ({ project, environment, id, definition }) => platform.tenant('PUT', project, `/datatransferobjects/${id}`, { environment, body: definition })));
48
+ }, handle(config, async ({ project, environment, id, definition, policyOverride }) => {
49
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'dto', definition, { operation: 'update' }, policyOverride));
50
+ const payload = await platform.tenant('PUT', project, `/datatransferobjects/${id}`, { environment, body: definition });
51
+ return attachFindings(withWriteDiff(payload, writeDiff(definition, payload)), findings);
52
+ }));
36
53
  server.registerTool('dto_clone', {
37
54
  title: 'DTO klonla',
38
55
  description: "DTO'nun kopyasını oluşturur.",
39
56
  inputSchema: { project: projectParam, environment: environmentParam, id: guid },
40
57
  annotations: writes
41
58
  }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/datatransferobjects/${id}/clone`, { environment })));
42
- server.registerTool('dto_sync_primitives', {
43
- title: 'İlkel DTO tiplerini senkronla',
44
- description: "Platformun ilkel (primitive) DTO tanımlarını projeye yeniden yazar; DTO listesinde temel tipler eksikse çalıştırılır. Bakım işidir.",
45
- inputSchema: { project: projectParam, environment: environmentParam },
46
- annotations: updates
47
- }, handle(config, ({ project, environment }) => platform.tenant('POST', project, '/datatransferobjects/sync-primitives', { environment })));
59
+ // `POST /datatransferobjects/sync-primitives` de bir BAKIM ucudur (yukarıdaki sync-users ile
60
+ // aynı sınıf); araç olarak yayınlanmıyor.
48
61
  server.registerTool('dto_delete', {
49
62
  title: 'DTO sil',
50
63
  description: "DTO'yu siler; workflow/query'lerde referans varsa platform reddeder.",