@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,11 +1,18 @@
1
- import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
- import { normalizeComponentDefaults, componentDefaultsNote, blockSizeNote, heightTypeNote, invalidScreenEventNames, invalidWidgetTypeNodes, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
1
+ import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps } from './common.js';
2
+ import { resolvePatchOperations } from '../knowledge/outlines.js';
3
+ import { patchResult, baseVersionOf } from '../platform/patch.js';
4
+ import fastJsonPatch from 'fast-json-patch';
5
+ import { normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
3
6
  import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode } from './screens.js';
4
- import { applyCatalogLayoutRules } from './components.js';
7
+ import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
8
+ import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../knowledge/propTypes.js';
9
+ import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
5
10
  import { PlatformApiError } from '../platform/errors.js';
6
11
  import { handle } from '../platform/responses.js';
7
12
  import { z } from 'zod';
8
- const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir ve sayfa micro-op hattı (page_schema_patch) layout'a uygulanmaz. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür.");
13
+ const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
14
+ .describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
15
+ const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir ve sayfa micro-op hattı (page_schema_patch) layout'a uygulanmaz. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür. 2026-09-08'den beri views, page_schema_patch'in doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile REDDEDİLİR (kabul edilen değer listesiyle) ve hiçbir şey kaydedilmez — eskiden sessizce kaydediliyordu (P1-2).");
9
16
  function countSlots(nodes) {
10
17
  if (!Array.isArray(nodes))
11
18
  return 0;
@@ -42,7 +49,7 @@ export function registerLayoutTools(server, platform) {
42
49
  }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment })));
43
50
  server.registerTool('layout_schema_get', {
44
51
  title: "Layout komponent ağacını oku",
45
- description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. DİKKAT: layout'ta micro-op hattı YOKTUR (backend'de PATCH /layouts/.../schema ucu yok), yani buradan okunan updatedAt bir expectedUpdatedAt kontrolünde kullanılamaz; değişiklik layout_update ile tam gövde olarak yazılır.",
52
+ description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Platformda layout micro-op ucu (sayfadaki PATCH /pages/.../schema karşılığı) yoktur, ama PATCH /layouts/{app}/{id} vardır: hedefli değişiklik için layout_patch kullanın — gateway okur, JSON Patch'i çözer ve FARKI `{ baseVersion, ops }` olarak platforma yazar; tam gövde PUT'u atılmaz. Buradaki updatedAt'i ayrıca expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
46
53
  inputSchema: {
47
54
  project: projectParam,
48
55
  environment: environmentParam,
@@ -71,7 +78,7 @@ export function registerLayoutTools(server, platform) {
71
78
  }));
72
79
  server.registerTool('layout_create', {
73
80
  title: 'Layout oluştur',
74
- description: "Yeni layout oluşturur. Şablon için mevcut bir layout'u layout_get ile alın; slot kurgusunu page_state çıktısındaki layoutSlots ile doğrulayın.",
81
+ description: "Yeni layout oluşturur. Şablon için mevcut bir layout'u layout_get ile alın; slot kurgusunu page_state çıktısındaki layoutSlots ile doğrulayın. views tam doğrulamadan geçer (tanınmayan prop · geçersiz enum · çakışan elementId · blok sınırı) ve tek hata bile tüm gövdeyi düşürür.",
75
82
  inputSchema: {
76
83
  project: projectParam,
77
84
  environment: environmentParam,
@@ -83,29 +90,23 @@ export function registerLayoutTools(server, platform) {
83
90
  },
84
91
  annotations: writes
85
92
  }, handle(config, async ({ project, environment, applicationId, name, views, events, parameters }) => {
86
- const badEvents = invalidScreenEventNames(events, 'LAY-004');
87
- if (badEvents.length > 0)
88
- throw new PlatformApiError(`Geçersiz layout olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
89
- const badWidgets = invalidWidgetTypeNodes(views, 'LAY-005');
90
- if (badWidgets.length > 0)
91
- throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
93
+ assertScreenWriteGuards('layout', { schema: views, events });
92
94
  const fixed = normalizeComponentDefaults(views);
93
- const catalog = (views ?? []).length > 0
94
- ? await applyCatalogLayoutRules(platform, project, environment, applicationId, views)
95
- : { clamped: [], heights: [] };
96
- const gaps = verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)');
95
+ const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, views);
96
+ const gaps = [...verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)'), ...flexSizeWarnings(views ?? [], 'LAY-007 (uyarı)', 'LAY-008 (uyarı)')];
97
97
  const response = await platform.tenant('POST', project, `/layouts/${applicationId}`, {
98
98
  environment,
99
99
  body: { name, views: views ?? [], events: events ?? [], parameters: parameters ?? null }
100
100
  });
101
- const normalized = withNormalization(response, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...slotWarning(views)]);
101
+ const drops = writeDiff({ name, views: views ?? [], events: events ?? [] }, response);
102
+ const normalized = withWriteDiff(withNormalization(response, [...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(views)]), drops);
102
103
  if (gaps.length === 0 || !normalized || typeof normalized !== 'object')
103
104
  return normalized;
104
105
  return { ...normalized, layoutWarnings: gaps };
105
106
  }));
106
107
  server.registerTool('layout_update', {
107
108
  title: 'Layout güncelle',
108
- description: "Layout'u günceller. PUT replace olduğu için gateway kaydı önce okur, yalnız verdiğiniz alanları değiştirip tam gövdeyi geri yazar — views göndermezseniz mevcut ağaç korunur.",
109
+ description: "Layout'u günceller. PUT replace olduğu için gateway kaydı önce okur, yalnız verdiğiniz alanları değiştirip tam gövdeyi geri yazar — views göndermezseniz mevcut ağaç korunur. Güncelleme yolunda yapısal doğrulama koşar (çakışan elementId · kayıtsız komponent tipi · eksik states/appearance/blok); prop adı ve enum değeri denetimi YALNIZ layout_create'te tamdır, çünkü tam denetim katalog değişince kayıtlı layout'ları geriye dönük kilitler (C-13).",
109
110
  inputSchema: {
110
111
  project: projectParam,
111
112
  environment: environmentParam,
@@ -119,24 +120,97 @@ export function registerLayoutTools(server, platform) {
119
120
  annotations: updates
120
121
  }, handle(config, async ({ project, environment, applicationId, id, ...changes }) => {
121
122
  const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
122
- const badEvents = invalidScreenEventNames(overrides.events, 'LAY-004');
123
- if (badEvents.length > 0)
124
- throw new PlatformApiError(`Geçersiz layout olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
125
- const badWidgets = invalidWidgetTypeNodes(overrides.views, 'LAY-005');
126
- if (badWidgets.length > 0)
127
- throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
123
+ assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events });
128
124
  const current = await fetchHost(platform, 'layout', project, environment, applicationId, id);
129
125
  const fixed = normalizeComponentDefaults(overrides.views);
130
- const catalog = overrides.views === undefined
131
- ? { clamped: [], heights: [] }
132
- : await applyCatalogLayoutRules(platform, project, environment, applicationId, overrides.views);
126
+ const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
133
127
  const body = layoutUpdateBody({ ...current, applicationId }, overrides);
134
128
  await platform.tenant('PUT', project, `/layouts/${applicationId}/${id}`, { environment, body });
135
- const gaps = overrides.views === undefined ? [] : verticalGapWarnings(body.views, 'LAY-003 (uyarı)');
129
+ const gaps = overrides.views === undefined ? [] : [...verticalGapWarnings(body.views, 'LAY-003 (uyarı)'), ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)')];
136
130
  const summary = { id, changed: Object.keys(overrides), preserved: { viewNodes: body.views.length } };
137
131
  if (gaps.length > 0)
138
132
  summary.layoutWarnings = gaps;
139
- return withNormalization(summary, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...slotWarning(body.views)]);
133
+ return withNormalization(summary, [...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(body.views)]);
134
+ }));
135
+ server.registerTool('layout_patch', {
136
+ title: "Layout'a patch uygula",
137
+ description: "Tam ağacı bağlama almadan hedefli değişiklik: gateway layout'u çeker, JSON Patch'i ve kendi normalizasyonlarını uygular, doğrulamadan geçirir ve FARKI platformun PATCH /layouts/{app}/{id} ucuna `{ baseVersion, ops }` olarak yazar — tek başlık düzeltmesi için 12 düğümlük ağacı yeniden göndermeniz gerekmez (09-04 Y-25) ve tam gövde PUT'u ATILMAZ. Yol İNDEKS yerine SEÇİCİ alabilir: '/views/0/children[elementId=baslik]/props/content/data'. Sürüm kontrolü platformda: okunan kaydın `version` değeri `baseVersion` olarak gider, araya başka bir yazma girdiyse 409 döner ve HİÇBİR op uygulanmaz. expectedUpdatedAt verirseniz gateway ayrıca istek gitmeden önce yerel kontrol yapar. Ayrıca 'set_prop_type' micro-op'unu tanır ({target, prop, propType: 'static'|'code'|'translation', ref?, data?}) — layout header/sidebar etiketini çeviriye bağlamanın yolu budur; JSON Patch ile aynı listede karışık verilebilir, micro-op'lar önce uygulanır.",
138
+ inputSchema: {
139
+ project: projectParam,
140
+ environment: environmentParam,
141
+ applicationId: guid,
142
+ id: guid,
143
+ operations: patchOperationsParam.optional(),
144
+ ops: patchOperationsParam.optional().describe("'operations' ile aynı alan (eşanlamlı); JSON Patch işlemleriyle set_prop_type micro-op'ları KARIŞIK verilebilir"),
145
+ expectedUpdatedAt: z.string().optional().describe("layout_schema_get çıktısındaki updatedAt; eşleşmezse patch uygulanmaz (araya giren yazmayı ezmeyi engeller)")
146
+ },
147
+ annotations: updates
148
+ }, handle(config, async ({ project, environment, applicationId, id, operations: givenOperations, ops, expectedUpdatedAt }) => {
149
+ const operations = microOps('layout_patch', ops, givenOperations);
150
+ const current = await fetchHost(platform, 'layout', project, environment, applicationId, id);
151
+ const audit = current.audit;
152
+ const updatedAt = audit?.updatedAt ?? audit?.createdAt;
153
+ if (expectedUpdatedAt !== undefined && String(updatedAt ?? '') !== expectedUpdatedAt) {
154
+ throw new PlatformApiError(`layout_patch: layout '${expectedUpdatedAt}' okumasından beri değişti (şimdiki updatedAt: ${String(updatedAt ?? 'bilinmiyor')}). Ağacı layout_schema_get ile yeniden okuyup patch'i güncel hâle göre kurun.`, 409);
155
+ }
156
+ // `applyPropTypesToSchema` ağacı YERİNDE değiştiriyor; net fark bozulmadan hesaplansın diye
157
+ // karşılaştırmanın tabanı micro-op'lardan ÖNCE ayrı bir klondan alınır.
158
+ const original = structuredClone(current);
159
+ const document = structuredClone(current);
160
+ const propTypeOps = operations.filter(isSetPropTypeOp);
161
+ const patchOps = operations.filter(op => !isSetPropTypeOp(op));
162
+ // Micro-op'lar JSON Patch'ten ÖNCE koşar: patch yolları ağaçtaki mevcut indekslere göre
163
+ // çözülüyor ve set_prop_type düğüm eklemiyor/çıkarmıyor, yalnız bir prop'un tipini
164
+ // değiştiriyor — bu sıra iki hattın birbirini bozmadığı tek sıra.
165
+ const propTypeIssues = setPropTypeErrors(propTypeOps);
166
+ if (propTypeIssues.length > 0)
167
+ throw new PlatformApiError(`layout_patch set_prop_type reddedildi (istek gönderilmedi): ${propTypeIssues.join(' · ')}`, 400);
168
+ const propTypes = propTypeOps.length > 0
169
+ ? applyPropTypesToSchema((document.views ?? []), propTypeOps)
170
+ : { applied: [], missingTargets: [] };
171
+ if (propTypes.missingTargets.length > 0) {
172
+ throw new PlatformApiError(`set_prop_type hedefi bulunamadı: '${propTypes.missingTargets[0]}'. layout_schema_get ile elementId'leri listeleyin.`, 400);
173
+ }
174
+ const resolved = patchOps.length > 0
175
+ ? resolvePatchOperations(document, patchOps)
176
+ : { operations: [], targets: [], notes: [] };
177
+ const patched = patchOps.length > 0
178
+ ? fastJsonPatch.applyPatch(document, resolved.operations, true, false).newDocument
179
+ : document;
180
+ const overrides = Object.fromEntries(['name', 'views', 'events', 'parameters']
181
+ .filter(key => patched[key] !== undefined)
182
+ .map(key => [key, patched[key]]));
183
+ assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events });
184
+ const fixed = normalizeComponentDefaults(overrides.views);
185
+ const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
186
+ const body = layoutUpdateBody({ ...current, applicationId }, overrides);
187
+ // Gövde değil FARK gönderilir: gateway normalizasyonları (komponent varsayılanları, katalog
188
+ // kuralları, value'suz olayların elenmesi) son hâle işlendikten sonra net diff hesaplanır.
189
+ const finalDocument = { ...patched, name: body.name, parameters: body.parameters, views: body.views, events: body.events };
190
+ const netOps = fastJsonPatch.compare(original, finalDocument);
191
+ const saved = await platform.tenant('PATCH', project, `/layouts/${applicationId}/${id}`, {
192
+ environment,
193
+ body: { baseVersion: baseVersionOf(document, 'layout_get'), ops: netOps }
194
+ });
195
+ const written = patchResult(saved, finalDocument);
196
+ const gaps = [...verticalGapWarnings(body.views, 'LAY-003 (uyarı)'), ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)')];
197
+ const summary = {
198
+ id,
199
+ applied: patchOps.length,
200
+ newVersion: written.newVersion,
201
+ targets: resolved.targets,
202
+ viewNodes: body.views.length
203
+ };
204
+ if (propTypes.applied.length > 0)
205
+ summary.propTypes = propTypes.applied;
206
+ if (gaps.length > 0)
207
+ summary.layoutWarnings = gaps;
208
+ return withNormalization(summary, [
209
+ ...componentDefaultsNote(fixed),
210
+ ...catalogNotes,
211
+ ...slotWarning(body.views),
212
+ ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)
213
+ ]);
140
214
  }));
141
215
  server.registerTool('layout_delete', {
142
216
  title: 'Layout sil',
@@ -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,61 @@ 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
25
84
  };
85
+ /**
86
+ * `anonymous` ile kimlik birlikte verilemez: biri kimliksiz isteği, diğeri belirli bir üyenin
87
+ * kimliğiyle atılan isteği ister. `page_screenshot`'taki kural aynen geçerli (G-20).
88
+ */
89
+ function runIdentity(anonymous, asUser, cookies, headers) {
90
+ if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
91
+ 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);
92
+ }
93
+ return identityHeaders(asUser, cookies, headers);
94
+ }
95
+ /**
96
+ * Debug çıktısındaki `executionItems` ucun kendi adımlarıdır; middleware AYRI bir workflow olarak
97
+ * ucun ÖNÜNDE koşar ve ağaçta hiç görünmez — 09-04 koşusunda sessizce açık kalan şirket kapısı
98
+ * (Y-03) bu yüzden adım ağacından teşhis edilemedi. Gateway zinciri kendisi çözüp listeler.
99
+ */
100
+ async function middlewareChain(platform, project, environment, application, microservice, httpStatus) {
101
+ try {
102
+ const resolver = new RefResolver(platform, project, environment);
103
+ const applicationId = await resolver.applicationId(application);
104
+ const list = await platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query: { pageIndex: 0, pageLength: 200 } });
105
+ const record = (list?.data ?? []).find(entry => String(entry.slug ?? '') === microservice);
106
+ if (!record) {
107
+ return { middlewareNote: `'${microservice}' ucu application listesinde bulunamadı; middleware zinciri çözülemedi. Slug'ı microservice_list ile doğrulayın.` };
108
+ }
109
+ // LİSTE DTO'su middlewareIds taşımaz (yalnız id/ad/slug/tip/kaynak) — not bu yüzden bağlı bir
110
+ // uçta bile "bağlı değil" diyordu ve okuyanı yanlış yere baktırıyordu (09-05 koşusu S-21).
111
+ // Kimlik listeden, bağlar DETAYDAN okunur.
112
+ const detail = await platform.tenant('GET', project, `/microservices/${applicationId}/${String(record.id ?? '')}`, { environment });
113
+ const definition = (detail?.data ?? detail ?? {});
114
+ const middlewareIds = (Array.isArray(definition.middlewareIds) ? definition.middlewareIds : []).map(String).slice(0, 10);
115
+ if (middlewareIds.length === 0) {
116
+ 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'))." };
117
+ }
118
+ const middlewares = [];
119
+ for (const middlewareId of middlewareIds) {
120
+ const detail = await platform.tenant('GET', project, `/workflows/${applicationId}/${middlewareId}`, { environment });
121
+ middlewares.push(workflowOutline((detail?.data ?? detail)));
122
+ }
123
+ const blocked = typeof httpStatus === 'number' && (httpStatus === 401 || httpStatus === 403);
124
+ return {
125
+ middlewares,
126
+ middlewareNote: blocked
127
+ ? `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.`
128
+ : "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."
129
+ };
130
+ }
131
+ catch (error) {
132
+ return { middlewareNote: `Middleware zinciri çözülemedi: ${error instanceof Error ? error.message : String(error)}. Uca bağlı middleware'leri microservice_get ile okuyabilirsiniz.` };
133
+ }
134
+ }
26
135
  function executePath(prefix, application, microservice, routeParams) {
27
136
  const routeSuffix = routeParams && routeParams.length > 0
28
137
  ? `/${routeParams.map(encodeURIComponent).join('/')}`
@@ -81,7 +190,7 @@ export function registerMicroserviceTools(server, platform) {
81
190
  inputSchema: {
82
191
  project: projectParam,
83
192
  environment: environmentParam,
84
- applicationId: guid,
193
+ applicationId: appRef,
85
194
  searchTerm: z.string().optional(),
86
195
  httpMethod: z.string().optional(),
87
196
  entityId: guid.optional(),
@@ -91,75 +200,119 @@ export function registerMicroserviceTools(server, platform) {
91
200
  ...pagingParams
92
201
  },
93
202
  annotations: readOnly
94
- }, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query })));
203
+ }, handle(config, async ({ project, environment, applicationId, ...query }) => {
204
+ const resolver = new RefResolver(platform, project, environment);
205
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
206
+ return withResolved(await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}`, { environment, query }), resolver.resolved);
207
+ }));
95
208
  server.registerTool('microservice_get', {
96
209
  title: 'Microservice getir',
97
210
  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
211
  inputSchema: {
99
212
  project: projectParam,
100
213
  environment: environmentParam,
101
- applicationId: guid,
214
+ applicationId: appRef,
102
215
  id: guid,
103
216
  view: z.enum(['full', 'outline', 'part']).optional().describe('varsayılan full'),
104
217
  part: z.string().optional().describe("view:'part' için yol, ör. sourceParameters.joins veya validations[0]")
105
218
  },
106
219
  annotations: readOnly
107
220
  }, handle(config, async ({ project, environment, applicationId, id, view, part }) => {
108
- const payload = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
221
+ const resolver = new RefResolver(platform, project, environment);
222
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
223
+ const payload = await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}/${id}`, { environment });
109
224
  const definition = (payload?.data ?? payload);
110
225
  if (view === 'part') {
111
226
  if (!part)
112
227
  throw new Error("view:'part' için 'part' parametresi zorunlu.");
113
- return { part, value: resolvePart(definition, part) };
228
+ return withResolved({ part, value: resolvePart(definition, part) }, resolver.resolved);
114
229
  }
115
- const resolvedUrl = await resolveRuntimeUrl(platform, project, environment, applicationId, definition);
230
+ const resolvedUrl = await resolveRuntimeUrl(platform, project, environment, resolvedApplicationId, definition);
116
231
  if (view === 'outline')
117
- return { ...microserviceOutline(definition), resolvedUrl };
118
- return { ...payload, resolvedUrl };
232
+ return withResolved({ ...microserviceOutline(definition), resolvedUrl }, resolver.resolved);
233
+ return withResolved({ ...payload, resolvedUrl }, resolver.resolved);
119
234
  }));
120
235
  server.registerTool('microservice_create', {
121
236
  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 },
237
+ 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'),
238
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, definition: definitionParam, policyOverride: policyOverrideParam },
124
239
  annotations: writes
125
240
  }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
241
+ const resolver = new RefResolver(platform, project, environment);
242
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
243
+ const hydrated = await hydrateMicroserviceRefs(definition, resolver);
126
244
  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);
245
+ const findings = [
246
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride)),
247
+ ...await queryScopeFindings(platform, project, environment, definition)
248
+ ];
249
+ const drift = unmappedMembers('CreateMicroserviceCommand', definition);
250
+ const payload = await platform.tenant('POST', project, `/microservices/${resolvedApplicationId}`, { environment, body: definition });
251
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
130
252
  }));
131
253
  server.registerTool('microservice_update', {
132
254
  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 },
255
+ 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'),
256
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
135
257
  annotations: updates
136
258
  }, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
259
+ const resolver = new RefResolver(platform, project, environment);
260
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
261
+ const hydrated = await hydrateMicroserviceRefs(definition, resolver);
137
262
  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);
263
+ const findings = [
264
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'update' }, policyOverride)),
265
+ ...await queryScopeFindings(platform, project, environment, definition)
266
+ ];
267
+ const drift = unmappedMembers('UpdateMicroserviceCommand', definition);
268
+ const payload = await platform.tenant('PUT', project, `/microservices/${resolvedApplicationId}/${id}`, { environment, body: definition });
269
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
141
270
  }));
142
271
  server.registerTool('microservice_patch', {
143
272
  title: 'Microservice\'e patch uygula',
144
273
  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 },
274
+ 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
275
  annotations: updates
147
- }, handle(config, async ({ project, environment, applicationId, id, operations, policyOverride }) => {
148
- const current = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
276
+ }, handle(config, async ({ project, environment, applicationId, id, operations: givenOperations, ops, policyOverride }) => {
277
+ const operations = microOps('microservice_patch', ops, givenOperations);
278
+ const resolver = new RefResolver(platform, project, environment);
279
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
280
+ const current = await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}/${id}`, { environment });
149
281
  const definition = structuredClone((current?.data ?? current));
150
282
  const resolved = resolvePatchOperations(definition, operations);
151
283
  const patched = fastJsonPatch.applyPatch(definition, resolved.operations, true, false).newDocument;
284
+ // Patch gövdesi tam PUT olarak geri yazılır: okunan kayıtta olmayan alan yazmada da SİLİNİR.
285
+ // 09-04 koşusunda gidiş-dönüş 'description'ı düşürdü ve MS-014 patch'i bloke etti; genel
286
+ // MS-014 mesajı sebebi söylemediği için tuzağı burada adıyla anlatıyoruz (Y-16).
287
+ if (typeof patched.description !== 'string' || patched.description.trim() === '') {
288
+ 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);
289
+ }
152
290
  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);
291
+ const findings = [
292
+ ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', patched, { operation: 'update' }, policyOverride)),
293
+ ...await queryScopeFindings(platform, project, environment, patched)
294
+ ];
295
+ const drift = introducedUnmappedMembers('UpdateMicroserviceCommand', (current?.data ?? current), patched);
296
+ // Gövde değil FARK gönderilir: gateway normalizasyonları da op'a çevrilsin diye net diff
297
+ // okunan kayıt ile son hâl arasından hesaplanır (tam PUT yok, yalnız değişen alanlar yazılır).
298
+ const netOps = fastJsonPatch.compare(definition, patched);
299
+ const saved = await platform.tenant('PATCH', project, `/microservices/${resolvedApplicationId}/${id}`, {
300
+ environment,
301
+ body: { baseVersion: baseVersionOf(definition, 'microservice_get'), ops: netOps }
302
+ });
303
+ const written = patchResult(saved, patched);
304
+ 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
305
  }));
157
306
  server.registerTool('microservice_delete', {
158
307
  title: 'Microservice sil',
159
308
  description: 'Microservice tanımını siler.',
160
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
309
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid },
161
310
  annotations: destructive
162
- }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/microservices/${applicationId}/${id}`, { environment })));
311
+ }, handle(config, async ({ project, environment, applicationId, id }) => {
312
+ const resolver = new RefResolver(platform, project, environment);
313
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
314
+ return withResolved(await platform.tenant('DELETE', project, `/microservices/${resolvedApplicationId}/${id}`, { environment }), resolver.resolved);
315
+ }));
163
316
  server.registerTool('microservice_clone', {
164
317
  title: 'Microservice klonla',
165
318
  description: 'Microservice\'in kopyasını oluşturur.',
@@ -168,26 +321,64 @@ export function registerMicroserviceTools(server, platform) {
168
321
  }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microservices/${id}/clone`, { environment })));
169
322
  server.registerTool('microservice_execute', {
170
323
  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.',
324
+ 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
325
  inputSchema: executeInput,
173
326
  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 })));
327
+ }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous, asUser, cookies, language }) => {
328
+ const notes = [];
329
+ const requestHeaders = { ...runIdentity(anonymous, asUser, cookies, headers), ...languageHeader(language, notes) };
330
+ return withNormalization(await platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query, body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true }), notes);
331
+ }));
175
332
  server.registerTool('microservice_debug', {
176
333
  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ı).',
334
+ 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
335
  inputSchema: executeInput,
179
336
  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 })));
337
+ }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous, asUser, cookies, language }) => {
338
+ const notes = [];
339
+ const requestHeaders = { ...runIdentity(anonymous, asUser, cookies, headers), ...languageHeader(language, notes) };
340
+ const payload = await platform.tenantRaw(method, project, executePath('debug', application, microservice, routeParams), { environment, query, body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true });
341
+ return withNormalization({ ...payload, ...await middlewareChain(platform, project, environment, application, microservice, payload?.httpStatus) }, notes);
342
+ }));
181
343
  server.registerTool('microservice_preview', {
182
344
  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 },
345
+ 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').",
346
+ inputSchema: {
347
+ project: projectParam,
348
+ environment: environmentParam,
349
+ applicationId: appRef,
350
+ id: guid.optional().describe('Kayıtlı microservice id\'si: verilirse tanım platformdan çekilir ve definition zorunlu olmaz (microservice_list / project_overview)'),
351
+ definition: definitionParam.optional(),
352
+ 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."),
353
+ asUser: asUserParam,
354
+ cookies: cookiesParam,
355
+ policyOverride: policyOverrideParam,
356
+ language: languageParam
357
+ },
185
358
  annotations: writes
186
- }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
359
+ }, handle(config, async ({ project, environment, applicationId, id, definition: given, previewParameters, asUser, cookies, policyOverride, language }) => {
360
+ const resolver = new RefResolver(platform, project, environment);
361
+ const resolvedApplicationId = await resolver.applicationId(applicationId);
362
+ if (given === undefined && id === undefined) {
363
+ 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);
364
+ }
365
+ const stored = id === undefined
366
+ ? undefined
367
+ : await platform.tenant('GET', project, `/microservices/${resolvedApplicationId}/${id}`, { environment });
368
+ const base = stored === undefined ? {} : structuredClone((stored.data ?? stored));
369
+ delete base.id;
370
+ const definition = {
371
+ ...base,
372
+ ...(given ?? {}),
373
+ ...(previewParameters === undefined ? {} : { previewParameters })
374
+ };
375
+ const hydrated = await hydrateMicroserviceRefs(definition, resolver);
187
376
  const normalized = normalizeMicroserviceDefinition(definition);
188
377
  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);
378
+ // Önizleme de ucun middleware'lerini koşturur: kimlik verilmezse korumalı uç 401 döner ve kuru
379
+ // koşu aracı tam gerektiği anda kullanılamaz hâle gelir (09-05 koşusu S-21).
380
+ const payload = await platform.tenantRaw('POST', project, `/microservices/${resolvedApplicationId}/preview`, { environment, body: definition, headers: { ...identityHeaders(asUser, cookies, undefined), ...languageHeader(language, normalized) }, mirrorAuthorizationCookie: true });
381
+ return attachFindings(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), findings);
191
382
  }));
192
383
  server.registerTool('microservice_job_history', {
193
384
  title: 'Job geçmişi',
@@ -56,10 +56,9 @@ export function registerOrganizationTools(server, platform) {
56
56
  inputSchema: { components: z.array(componentInput).min(1) },
57
57
  annotations: updates
58
58
  }, handle(config, ({ components }) => platform.org('POST', '/organizations/components/upsert', { body: components })));
59
- server.registerTool('organization_sync_users', {
60
- title: 'Kullanıcıları tenant şemalarına kopyala',
61
- description: 'Organizasyon kullanıcılarını, eksik oldukları üretim-dışı environment şemalarına süper yönetici rolüyle kopyalar. Yeni proje/environment kurulumundan sonra çalıştırılır.',
62
- inputSchema: {},
63
- annotations: writes
64
- }, handle(config, () => platform.org('POST', '/organizations/sync-users')));
59
+ // `POST /organizations/sync-users` bir BAKIM ucudur: özellik geliştirirken migration/veri
60
+ // düzeltmesi için yazılmış, ürün yüzeyinin parçası değil (ürün sahibi 2026-09-05'te bunu
61
+ // söyledi ve uçları temizleyecek). Araç olarak yayınlamak ajanı desteklenmeyen bir yola
62
+ // sokuyordu; kaldırıldı. İlk kullanıcının nasıl açılacağı için
63
+ // platform_guide('getting-started') §"İlk kullanıcıyı kim açar?".
65
64
  }