@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.
- package/.env.example +8 -3
- package/.env.production.example +2 -0
- package/README.md +229 -193
- package/dist/config.js +20 -2
- package/dist/knowledge/backendConstraints.js +897 -0
- package/dist/knowledge/componentCatalog.js +196 -42
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +449 -16
- package/dist/knowledge/outlines.js +136 -12
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +618 -43
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/errors.js +46 -4
- package/dist/platform/http.js +33 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +9 -2
- package/dist/platform/screenshot.js +527 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +2 -2
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +3 -2
- package/dist/resources/guides/component.md +69 -8
- package/dist/resources/guides/entity.md +404 -16
- package/dist/resources/guides/expressions.md +44 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/getting-started.md +77 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/microservice.md +226 -13
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/page.md +383 -46
- package/dist/resources/guides/translation.md +126 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +198 -27
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
- package/dist/resources/schemas/GetRowsQuery.json +3 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
- package/dist/resources/schemas/StepDto.json +23 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
- package/dist/server.js +4 -0
- package/dist/tools/access.js +3 -3
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +63 -30
- package/dist/tools/common.js +39 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +20 -6
- package/dist/tools/flows.js +110 -11
- package/dist/tools/folders.js +7 -2
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/microservices.js +229 -38
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +365 -100
- package/dist/tools/platformInfo.js +152 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/rows.js +159 -45
- package/dist/tools/screenWrite.js +59 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +33 -16
- package/package.json +6 -3
package/dist/tools/layouts.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
|
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ığı.
|
|
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
|
-
|
|
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
|
|
94
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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), ...
|
|
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:
|
|
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 }) =>
|
|
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:
|
|
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
|
|
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,
|
|
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:
|
|
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 =
|
|
128
|
-
|
|
129
|
-
|
|
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:
|
|
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 =
|
|
139
|
-
|
|
140
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
154
|
-
|
|
155
|
-
|
|
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:
|
|
309
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid },
|
|
161
310
|
annotations: destructive
|
|
162
|
-
}, handle(config, ({ project, environment, applicationId, id }) =>
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
184
|
-
inputSchema: {
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
}
|