@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/entities.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
1
|
+
import { guid, entityRef, resolveEntityId, withResolved, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive, microOps } from './common.js';
|
|
2
|
+
import { RefResolver, isGuid } from '../platform/refs.js';
|
|
2
3
|
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
3
4
|
import { splitJunctionFields, withNormalization } from '../knowledge/normalize.js';
|
|
4
|
-
import {
|
|
5
|
+
import { unmappedMembers, introducedUnmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
6
|
+
import { entityOutline, resolvePart, resolvePatchOperations, patchedSections } from '../knowledge/outlines.js';
|
|
7
|
+
import { patchResult, baseVersionOf } from '../platform/patch.js';
|
|
5
8
|
import { isJunctionField, policyRules } from '../knowledge/rules.js';
|
|
6
9
|
import { handle } from '../platform/responses.js';
|
|
7
10
|
import fastJsonPatch from 'fast-json-patch';
|
|
@@ -20,6 +23,52 @@ function preserveApplicationBinding(definition) {
|
|
|
20
23
|
if (applicationIds.length > 0)
|
|
21
24
|
definition.applicationIds = applicationIds;
|
|
22
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* `UpdateEntityCommandHandler` gövdedeki `Indexes` null ise onu BOŞ LİSTEYE çeviriyor
|
|
28
|
+
* (`request.Indexes?…ToList() ?? []`) ve `Entity.FindProcessedIndexes` boş listede entity'nin
|
|
29
|
+
* bütün index'lerini silinecek kümeye koyuyor: kayıt düşer, `DROP INDEX` koşar, hata dönmez.
|
|
30
|
+
* Domain katmanı null'ı özellikle koruyor, komut katmanı bu korumayı geçersiz kılıyor.
|
|
31
|
+
*
|
|
32
|
+
* Gateway alan verilmemişse mevcut index'leri gövdeye taşır. Bilinçli silme yolu kapanmaz:
|
|
33
|
+
* `indexes: []` AÇIKÇA yazılmışsa buraya hiç girilmez.
|
|
34
|
+
*/
|
|
35
|
+
async function carryIndexes(platform, resolver, project, environment, datasourceId, id, body) {
|
|
36
|
+
const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
|
|
37
|
+
const current = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
38
|
+
const definition = (current?.data ?? current);
|
|
39
|
+
const indexes = Array.isArray(definition?.indexes) ? definition.indexes : [];
|
|
40
|
+
if (indexes.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
body.indexes = indexes;
|
|
43
|
+
return [`Gövdede 'indexes' yoktu: platform bu durumda entity'nin BÜTÜN index'lerini siler, bu yüzden mevcut ${indexes.length} index gövdeye taşındı. Silmek istiyorsanız 'indexes: []' yazarak açıkça isteyin.`];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `Entity.FindProcessedIndexes` farkı YALNIZ `id` üzerinden alıyor: aynı `id` ile gelen index ne
|
|
47
|
+
* eklenenler ne silinenler kümesinde olduğu için `EntityService.UpdateAsync` ona hiç dokunmuyor.
|
|
48
|
+
* Ad, unique bayrağı ya da alan listesi değişse bile ne kayıt ne DDL güncellenir; istek 200 döner
|
|
49
|
+
* ve yama uygulanmış görünür. Politika kuralı olamaz — kural yalnız yamalanmış gövdeyi görür,
|
|
50
|
+
* bu karşılaştırma ÖNCEKİ hâli de ister.
|
|
51
|
+
*/
|
|
52
|
+
function inPlaceIndexEdits(before, after) {
|
|
53
|
+
const asIndexes = (value) => {
|
|
54
|
+
const source = (value && typeof value === 'object' ? value : {});
|
|
55
|
+
return Array.isArray(source.indexes)
|
|
56
|
+
? source.indexes.filter((index) => Boolean(index) && typeof index === 'object')
|
|
57
|
+
: [];
|
|
58
|
+
};
|
|
59
|
+
const shape = (index) => JSON.stringify([index.name ?? null, index.unique ?? false, Array.isArray(index.fieldIds) ? index.fieldIds : []]);
|
|
60
|
+
const original = new Map(asIndexes(before)
|
|
61
|
+
.filter(index => typeof index.id === 'string')
|
|
62
|
+
.map(index => [index.id, shape(index)]));
|
|
63
|
+
return asIndexes(after)
|
|
64
|
+
.filter(index => typeof index.id === 'string' && original.has(index.id) && original.get(index.id) !== shape(index))
|
|
65
|
+
.map(index => ({
|
|
66
|
+
ruleId: 'ENT-014',
|
|
67
|
+
severity: 'warn',
|
|
68
|
+
message: `'${index.id}' id'li index'in tanımı değişti ama platform var olan bir index'i YERİNDE GÜNCELLEMEZ: farkı yalnız 'id' üzerinden alır, aynı id'yi taşıyan kaydı ne siler ne yeniden kurar. İstek 200 dönse de veritabanında hiçbir şey değişmez. Index'i kaldırıp yeni bir id ile yeniden ekleyin.`,
|
|
69
|
+
guide: 'ngapps://guides/entity'
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
23
72
|
export function registerEntityTools(server, platform) {
|
|
24
73
|
const config = platform.config;
|
|
25
74
|
server.registerTool('entity_list', {
|
|
@@ -38,39 +87,42 @@ export function registerEntityTools(server, platform) {
|
|
|
38
87
|
}, handle(config, ({ project, environment, datasourceId, ...query }) => platform.tenant('GET', project, `/entities/${datasourceId}`, { environment, query })));
|
|
39
88
|
server.registerTool('entity_get', {
|
|
40
89
|
title: 'Entity getir',
|
|
41
|
-
description: "Entity'yi alan tanımlarıyla getirir. Çok alanlı entity'de önce view:'outline' (alan listesi), sonra view:'part' ile tek alan çekin.",
|
|
90
|
+
description: "Entity'yi alan tanımlarıyla getirir. Çok alanlı entity'de önce view:'outline' (alan listesi), sonra view:'part' ile tek alan çekin. Çok dilli entity'de kök alanlar companion bağını taşır: parent'ta `localizationEntityId`, companion'da `localizationParentId` — companion'ı ad kalıbından TAHMİN ETMEYİN, bu bağdan bulun.",
|
|
42
91
|
inputSchema: {
|
|
43
92
|
project: projectParam,
|
|
44
93
|
environment: environmentParam,
|
|
45
|
-
datasourceId: guid,
|
|
46
|
-
id:
|
|
94
|
+
datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'),
|
|
95
|
+
id: entityRef,
|
|
47
96
|
view: z.enum(['full', 'outline', 'part']).optional().describe('varsayılan full'),
|
|
48
97
|
part: z.string().optional().describe("view:'part' için yol, ör. fields[slug=email] veya indexes[0]")
|
|
49
98
|
},
|
|
50
99
|
annotations: readOnly
|
|
51
100
|
}, handle(config, async ({ project, environment, datasourceId, id, view, part }) => {
|
|
52
|
-
const
|
|
101
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
102
|
+
const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
|
|
103
|
+
const payload = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
53
104
|
const definition = (payload?.data ?? payload);
|
|
54
105
|
if (view === 'outline')
|
|
55
|
-
return entityOutline(definition);
|
|
106
|
+
return withResolved(entityOutline(definition), resolver.resolved);
|
|
56
107
|
if (view === 'part') {
|
|
57
108
|
if (!part)
|
|
58
109
|
throw new Error("view:'part' için 'part' parametresi zorunlu.");
|
|
59
|
-
return { part, value: resolvePart(definition, part) };
|
|
110
|
+
return withResolved({ part, value: resolvePart(definition, part) }, resolver.resolved);
|
|
60
111
|
}
|
|
61
|
-
return payload;
|
|
112
|
+
return withResolved(payload, resolver.resolved);
|
|
62
113
|
}));
|
|
63
114
|
server.registerTool('entity_create', {
|
|
64
115
|
title: 'Entity oluştur',
|
|
65
|
-
description: "Datasource altında yeni entity oluşturur (definition = CreateEntityCommand: name, fields[], applicationIds, ...). İlişkiler ve alan tipi kararı için platform_guide('entity') okuyun. Gövde politika kontrolünden geçer. applicationIds create'te platformca bağlanır (CreateEntityCommandHandler entityApplications'ı Entity ctor'una geçirir); satır uçları bu bağa İHTİYAÇ DUYMAZ — bağ yalnız Studio'nun application'a göre entity listesini ve bağımlılık sorgularını etkiler.",
|
|
116
|
+
description: "Datasource altında yeni entity oluşturur (definition = CreateEntityCommand: name, fields[], applicationIds, ...). İlişkiler ve alan tipi kararı için platform_guide('entity') okuyun. Gövde politika kontrolünden geçer. applicationIds create'te platformca bağlanır (CreateEntityCommandHandler entityApplications'ı Entity ctor'una geçirir); satır uçları bu bağa İHTİYAÇ DUYMAZ — bağ yalnız Studio'nun application'a göre entity listesini ve bağımlılık sorgularını etkiler. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
|
|
66
117
|
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
67
118
|
annotations: writes
|
|
68
119
|
}, handle(config, async ({ project, environment, datasourceId, definition, policyOverride }) => {
|
|
69
120
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'entity', definition, { operation: 'create' }, policyOverride));
|
|
70
121
|
const { base, deferred } = splitJunctionFields(definition, isJunctionField);
|
|
122
|
+
const drift = unmappedMembers('CreateEntityCommand', base);
|
|
71
123
|
const payload = await platform.tenant('POST', project, `/entities/${datasourceId}`, { environment, body: base });
|
|
72
124
|
if (deferred.length === 0)
|
|
73
|
-
return attachFindings(payload, findings);
|
|
125
|
+
return attachFindings(withUnmappedMembers(payload, drift), findings);
|
|
74
126
|
const created = (payload?.data ?? payload);
|
|
75
127
|
const createdId = String(created?.id ?? '');
|
|
76
128
|
if (!createdId) {
|
|
@@ -82,7 +134,7 @@ export function registerEntityTools(server, platform) {
|
|
|
82
134
|
preserveApplicationBinding(full);
|
|
83
135
|
const updated = await platform.tenant('PUT', project, `/entities/${createdId}`, { environment, body: full });
|
|
84
136
|
const note = `m2m/çoklu alanlar (${deferred.map(field => field.slug ?? field.name).join(', ')}) ayrı bir güncelleme turunda eklendi: create yolu junction kaydını açar ama fiziksel tabloyu kurmaz (platform bug'ı P-01), update yolu kurar.`;
|
|
85
|
-
return attachFindings(withNormalization(updated, [note]), findings);
|
|
137
|
+
return attachFindings(withUnmappedMembers(withNormalization(updated, [note]), drift), findings);
|
|
86
138
|
}));
|
|
87
139
|
server.registerTool('entity_create_discovered', {
|
|
88
140
|
title: 'Keşfedilen entity\'leri içe al',
|
|
@@ -92,34 +144,67 @@ export function registerEntityTools(server, platform) {
|
|
|
92
144
|
}, handle(config, ({ project, environment, datasourceId, definition }) => platform.tenant('POST', project, `/entities/${datasourceId}/discovered`, { environment, body: definition })));
|
|
93
145
|
server.registerTool('entity_update', {
|
|
94
146
|
title: 'Entity güncelle',
|
|
95
|
-
description:
|
|
96
|
-
inputSchema: { project: projectParam, environment: environmentParam,
|
|
147
|
+
description: "Entity tanımını günceller (tam gövde). Hedefli değişiklik için entity_patch tercih edin. Gövde politika kontrolünden geçer. DİKKAT: gövdede 'indexes' YOKSA platform entity'nin bütün index'lerini siler — gateway alan verilmemişse mevcut index'leri taşır ve bunu bildirir; silmek için 'indexes: []' yazın. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
|
|
148
|
+
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
97
149
|
annotations: updates
|
|
98
|
-
}, handle(config, async ({ project, environment, id, definition, policyOverride }) => {
|
|
150
|
+
}, handle(config, async ({ project, environment, datasourceId, id, definition, policyOverride }) => {
|
|
99
151
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'entity', definition, { operation: 'update' }, policyOverride));
|
|
100
|
-
const
|
|
101
|
-
|
|
152
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
153
|
+
const body = { ...definition };
|
|
154
|
+
const notes = Object.hasOwn(body, 'indexes') ? [] : await carryIndexes(platform, resolver, project, environment, datasourceId, id, body);
|
|
155
|
+
const entityId = await resolveEntityId(resolver, id);
|
|
156
|
+
const drift = unmappedMembers('UpdateEntityCommand', definition);
|
|
157
|
+
const payload = await platform.tenant('PUT', project, `/entities/${entityId}`, { environment, body });
|
|
158
|
+
return attachFindings(withUnmappedMembers(withResolved(withNormalization(payload, notes), resolver.resolved), drift), findings);
|
|
102
159
|
}));
|
|
103
160
|
server.registerTool('entity_patch', {
|
|
104
161
|
title: 'Entity\'ye patch uygula',
|
|
105
|
-
description: "Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı
|
|
106
|
-
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, id:
|
|
162
|
+
description: "Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. 'applications' → 'applicationIds' çevirisini artık platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
|
|
163
|
+
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, 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 },
|
|
107
164
|
annotations: updates
|
|
108
|
-
}, handle(config, async ({ project, environment, datasourceId, id, operations, policyOverride }) => {
|
|
109
|
-
const
|
|
165
|
+
}, handle(config, async ({ project, environment, datasourceId, id, operations: givenOperations, ops, policyOverride }) => {
|
|
166
|
+
const operations = microOps('entity_patch', ops, givenOperations);
|
|
167
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
168
|
+
const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
|
|
169
|
+
const current = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
110
170
|
const definition = structuredClone((current?.data ?? current));
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
171
|
+
const resolvedOps = resolvePatchOperations(definition, operations, 'entity_get');
|
|
172
|
+
// Politika, yazmadan önce sonucu görmeli; yerel kopya YALNIZ değerlendirme için, gövde olarak gönderilmez.
|
|
173
|
+
const preview = fastJsonPatch.applyPatch(definition, resolvedOps.operations, true, false).newDocument;
|
|
174
|
+
const findings = [
|
|
175
|
+
...enforcePolicy(evaluatePolicy(config, policyRules, 'entity', preview, { operation: 'update' }, policyOverride)),
|
|
176
|
+
...inPlaceIndexEdits(current?.data ?? current, preview)
|
|
177
|
+
];
|
|
178
|
+
// Yalnız YAMANIN eklediği anahtar bildirilir: gövdenin geri kalanını gateway GET'ten taşıdı
|
|
179
|
+
// ve orada komutta olmayan yedi yapısal alan var (ölçüldü — g-03 Adım 0). Onları bildirmek
|
|
180
|
+
// kanalı gürültüye boğar ve gerçek sinyali gömer.
|
|
181
|
+
const drift = introducedUnmappedMembers('UpdateEntityCommand', (current?.data ?? current), preview);
|
|
182
|
+
const saved = await platform.tenant('PATCH', project, `/entities/${entity.entityId}`, {
|
|
183
|
+
environment,
|
|
184
|
+
body: { baseVersion: baseVersionOf(definition, 'entity_get'), ops: resolvedOps.operations }
|
|
185
|
+
});
|
|
186
|
+
const written = patchResult(saved, preview);
|
|
187
|
+
const touched = patchedSections(written.data, resolvedOps.operations);
|
|
188
|
+
return attachFindings(withUnmappedMembers(withResolved({
|
|
189
|
+
applied: operations.length,
|
|
190
|
+
newVersion: written.newVersion,
|
|
191
|
+
patchTargets: resolvedOps.targets,
|
|
192
|
+
...(resolvedOps.notes.length > 0 ? { resolvedPaths: resolvedOps.notes } : {}),
|
|
193
|
+
// S-8: yazılanın kanıtı yanıtta dursun; dokunulmayan bölümler outline'da kalır.
|
|
194
|
+
...(Object.keys(touched).length > 0 ? { touched } : {}),
|
|
195
|
+
outline: entityOutline(written.data)
|
|
196
|
+
}, resolver.resolved), drift), findings);
|
|
116
197
|
}));
|
|
117
198
|
server.registerTool('entity_delete', {
|
|
118
199
|
title: 'Entity sil',
|
|
119
200
|
description: 'Entity tanımını siler; önce entity_dependencies ile bağımlılıklarını kontrol edin.',
|
|
120
|
-
inputSchema: { project: projectParam, environment: environmentParam, id:
|
|
201
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: entityRef },
|
|
121
202
|
annotations: destructive
|
|
122
|
-
}, handle(config, ({ project, environment, id }) =>
|
|
203
|
+
}, handle(config, async ({ project, environment, id }) => {
|
|
204
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
205
|
+
const entityId = await resolveEntityId(resolver, id);
|
|
206
|
+
return withResolved(await platform.tenant('DELETE', project, `/entities/${entityId}`, { environment }), resolver.resolved);
|
|
207
|
+
}));
|
|
123
208
|
server.registerTool('entity_clone', {
|
|
124
209
|
title: 'Entity klonla',
|
|
125
210
|
description: 'Entity\'nin kopyasını oluşturur.',
|
|
@@ -4,8 +4,11 @@ import { PlatformApiError } from '../platform/errors.js';
|
|
|
4
4
|
import { handle } from '../platform/responses.js';
|
|
5
5
|
import { basename, join, resolve } from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
8
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
9
|
+
import { EXTERNAL_COMPONENT_PROTOCOL_VERSION } from '../knowledge/backendConstraints.js';
|
|
10
|
+
/** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular (backend ExternalComponentRules.CurrentProtocolVersion) */
|
|
11
|
+
const PROTOCOL_VERSION = EXTERNAL_COMPONENT_PROTOCOL_VERSION;
|
|
9
12
|
/** Backend `ExternalComponentRules.MaxBundleSizeBytes` ile aynı sınır */
|
|
10
13
|
const maxBundleBytes = 2 * 1024 * 1024;
|
|
11
14
|
const slugPattern = /^x-[a-z][a-z0-9-]*$/;
|
|
@@ -185,7 +188,7 @@ export function registerExternalComponentTools(server, platform) {
|
|
|
185
188
|
}));
|
|
186
189
|
server.registerTool('external_component_publish', {
|
|
187
190
|
title: 'Harici komponenti yayınla',
|
|
188
|
-
description: "Komponent kaydını ve paketini platforma yükler. Paket sunucudaki doğrulama hattından geçer (şema + güvenlik taraması); block bulgusu varsa depolamaya hiç yazılmaz ve rapor döner. Kabul edilen paket taslak (draft) olarak kaydedilir — Studio önizlemesinde görünür, yayınlanmış uygulamada görünmez; yayına almak için external_component_activate. Dosyalar gateway sürecinin okuyabildiği yerel yollardır, içerikleri model bağlamına girmez.",
|
|
191
|
+
description: "Komponent kaydını ve paketini platforma yükler. Paket sunucudaki doğrulama hattından geçer (şema + güvenlik taraması); block bulgusu varsa depolamaya hiç yazılmaz ve rapor döner. Kabul edilen paket taslak (draft) olarak kaydedilir — Studio önizlemesinde görünür, yayınlanmış uygulamada görünmez; yayına almak için external_component_activate. Dosyalar gateway sürecinin okuyabildiği yerel yollardır, içerikleri model bağlamına girmez. Backend ön kontrolü (EXT-001, block): protocolVersion 1, definition.blocks dolu, props[].name ayrılmış DOM adı ya da zx/on önekli olamaz, props[].type sözlükten (ngapps://rules/backend-constraints).",
|
|
189
192
|
inputSchema: {
|
|
190
193
|
project: projectParam,
|
|
191
194
|
environment: environmentParam,
|
|
@@ -219,6 +222,9 @@ export function registerExternalComponentTools(server, platform) {
|
|
|
219
222
|
if (bytes.byteLength > maxBundleBytes) {
|
|
220
223
|
throw new PlatformApiError(`external_component_publish: paket ${maxBundleBytes} bayt sınırını aşıyor (${bytes.byteLength} B).`);
|
|
221
224
|
}
|
|
225
|
+
// EXT-001: protocolVersion, definition.blocks, ayrılmış prop adları ve prop tipleri — kayıt
|
|
226
|
+
// ucu 422 dönmeden manifest'te yakalanır; paket yükleme ikinci istek olduğu için yarım kayıt kalmaz.
|
|
227
|
+
enforcePolicy(evaluatePolicy(config, policyRules, 'externalComponent', { ...manifest, slug }, { operation: 'create' }));
|
|
222
228
|
const record = await platform.tenant('POST', project, `/components/${applicationId}/external`, {
|
|
223
229
|
environment,
|
|
224
230
|
body: {
|
package/dist/tools/files.js
CHANGED
|
@@ -4,9 +4,12 @@ import { handle } from '../platform/responses.js';
|
|
|
4
4
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { basename, extname, resolve } from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { FORBIDDEN_NAME_CHARACTERS } from '../knowledge/backendConstraints.js';
|
|
7
8
|
const maxInlineBytes = 8 * 1024 * 1024;
|
|
8
9
|
const maxUploadBytes = 50 * 1024 * 1024;
|
|
9
|
-
|
|
10
|
+
/** Metin içerik bağlamdan geçtiği için base64'ün 8 MB sınırından çok daha dar tutulur (G-26). */
|
|
11
|
+
const maxTextBytes = 1024 * 1024;
|
|
12
|
+
const forbiddenNameChars = FORBIDDEN_NAME_CHARACTERS;
|
|
10
13
|
const mimeTypes = {
|
|
11
14
|
'.css': 'text/css',
|
|
12
15
|
'.csv': 'text/csv',
|
|
@@ -51,20 +54,22 @@ export function registerFileTools(server, platform) {
|
|
|
51
54
|
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/files/${id}`, { environment })));
|
|
52
55
|
server.registerTool('file_upload', {
|
|
53
56
|
title: 'Dosya yükle',
|
|
54
|
-
description: "Dosyayı projenin depolamasına yükler ve FileDto (id + serve adresleri) döner. İçerik
|
|
57
|
+
description: "Dosyayı projenin depolamasına yükler ve FileDto (id + serve adresleri) döner. İçerik ÜÇ yoldan verilir: `text` — düz metin içerik (SVG, CSV, Markdown, JSON…) + `filename`; base64'e çevirmeye gerek yok, bunu gateway yapar. `path` — gateway sürecinin okuyabildiği yerel yol (büyük ve ikili dosyalar için). `content` — base64 (8 MB'a kadar) + `filename`. Platform sınırı 50 MB; dosya adı \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez. Yüklenen dosya sayfada `image`/`file-viewer` komponentiyle gösterilir ya da satır alanına yol olarak yazılır.",
|
|
55
58
|
inputSchema: {
|
|
56
59
|
project: projectParam,
|
|
57
60
|
environment: environmentParam,
|
|
58
61
|
path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel dosya yolu; `content` ile birlikte verilmez"),
|
|
59
62
|
content: z.string().min(1).optional().describe('Base64 kodlanmış dosya içeriği; `filename` ile birlikte zorunludur'),
|
|
63
|
+
text: z.string().min(1).optional().describe("Düz metin içerik (UTF-8): SVG, CSV, Markdown, JSON, düz metin. `filename` ile birlikte zorunludur ve base64'e çevirmeye gerek yoktur — yalnız MCP araçlarıyla çalışan bir ajanın elinde base64 kodlayıcı yok, bu alan o boşluğu kapatır (G-26). İkili dosyalar için `path` ya da `content` kullanın."),
|
|
60
64
|
filename: z.string().min(1).optional().describe("`content` ile zorunlu; `path` verildiğinde dosya adını ezer"),
|
|
61
65
|
contentType: z.string().min(1).optional().describe('Belirtilmezse uzantıdan çözülür, bilinmeyen uzantıda application/octet-stream'),
|
|
62
66
|
folderId: guid.optional().describe('Hedef klasör (folder_list); boşsa kök')
|
|
63
67
|
},
|
|
64
68
|
annotations: writes
|
|
65
|
-
}, handle(config, async ({ project, environment, path, content, filename, contentType, folderId }) => {
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
}, handle(config, async ({ project, environment, path, content, text, filename, contentType, folderId }) => {
|
|
70
|
+
const given = [path, content, text].filter(value => value !== undefined);
|
|
71
|
+
if (given.length !== 1) {
|
|
72
|
+
throw new PlatformApiError("file_upload: `path`, `content` ya da `text` parametrelerinden tam olarak biri verilmeli — `path` gateway'in okuyabildiği yerel dosya, `content` base64 içerik, `text` düz metin (ikisi de `filename` ile).");
|
|
68
73
|
}
|
|
69
74
|
let bytes;
|
|
70
75
|
let name;
|
|
@@ -78,6 +83,15 @@ export function registerFileTools(server, platform) {
|
|
|
78
83
|
}
|
|
79
84
|
name = filename ?? basename(absolute);
|
|
80
85
|
}
|
|
86
|
+
else if (text !== undefined) {
|
|
87
|
+
if (!filename)
|
|
88
|
+
throw new PlatformApiError('file_upload: `text` verildiğinde `filename` zorunludur.');
|
|
89
|
+
bytes = Buffer.from(text, 'utf8');
|
|
90
|
+
if (bytes.byteLength > maxTextBytes) {
|
|
91
|
+
throw new PlatformApiError(`file_upload: \`text\` içeriği ${maxTextBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` ya da \`content\` kullanın.`);
|
|
92
|
+
}
|
|
93
|
+
name = filename;
|
|
94
|
+
}
|
|
81
95
|
else {
|
|
82
96
|
if (!filename)
|
|
83
97
|
throw new PlatformApiError('file_upload: `content` verildiğinde `filename` zorunludur.');
|
|
@@ -94,7 +108,7 @@ export function registerFileTools(server, platform) {
|
|
|
94
108
|
}
|
|
95
109
|
const offendingChar = [...forbiddenNameChars].find(character => name.includes(character));
|
|
96
110
|
if (offendingChar) {
|
|
97
|
-
throw new PlatformApiError(`file_upload: dosya adı '${offendingChar}' karakterini içeriyor; platform doğrulaması bunu reddeder. Yasak karakterler: ${forbiddenNameChars}`);
|
|
111
|
+
throw new PlatformApiError(`file_upload: dosya adı '${offendingChar}' karakterini içeriyor; platform doğrulaması bunu 422 ile reddeder (FILE-001, UploadFileCommandValidator). Yasak karakterler: ${forbiddenNameChars}`);
|
|
98
112
|
}
|
|
99
113
|
const resolvedType = contentType ?? mimeTypes[extname(name).toLowerCase()] ?? 'application/octet-stream';
|
|
100
114
|
const multipart = folderId
|
package/dist/tools/flows.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
import { fetchHost, flowPath, hostParam, hostUpdateBody, unwrap } from './screens.js';
|
|
2
|
-
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive, microOps } from './common.js';
|
|
3
3
|
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
4
|
-
import { normalizeFlowSteps, withNormalization } from '../knowledge/normalize.js';
|
|
4
|
+
import { normalizeFlowSteps, unknownSetterTargetWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
5
5
|
import { flowBodyWarnings } from '../knowledge/coverage.js';
|
|
6
6
|
import { PlatformApiError } from '../platform/errors.js';
|
|
7
7
|
import { handle } from '../platform/responses.js';
|
|
8
|
+
import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
|
|
9
|
+
import { resolvePatchOperations } from '../knowledge/outlines.js';
|
|
10
|
+
import { patchResult, baseVersionOf } from '../platform/patch.js';
|
|
11
|
+
import fastJsonPatch from 'fast-json-patch';
|
|
8
12
|
import { policyRules } from '../knowledge/rules.js';
|
|
9
13
|
import { randomUUID } from 'node:crypto';
|
|
10
14
|
import { z } from 'zod';
|
|
15
|
+
/**
|
|
16
|
+
* Akış yazması yanıtında gövdeyi geri döndürmüyor, bu yüzden diff için akışı bir kez okuruz.
|
|
17
|
+
* ÖLÇÜLDÜ 2026-09-05 (Corecruiter koşusu, `S-19`): `code` adımının gövdesi kaydedilmedi,
|
|
18
|
+
* `flow_create` 200 döndü, `flow_get` adımı gösterdi ama `args` içinde kodu taşıyan alan yoktu —
|
|
19
|
+
* teşhis ~10 dakika sürdü ve tek belirti filtrenin hiçbir şey yapmamasıydı. Okuma başarısız
|
|
20
|
+
* olursa yazma yine başarılıdır: diff bir teşhis eklentisidir, yazmanın sonucu değil.
|
|
21
|
+
*/
|
|
22
|
+
async function flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, sent) {
|
|
23
|
+
try {
|
|
24
|
+
const saved = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
|
|
25
|
+
return writeDiff(sent, saved);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { dropped: [], normalized: [] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
11
31
|
const stepsParam = z.array(z.record(z.unknown())).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c.");
|
|
12
32
|
const policyOverrideParam = z.boolean().optional();
|
|
33
|
+
const patchOperationsParam = z.array(z.record(z.unknown())).min(1).describe("RFC 6902 JSON Patch işlemleri; yolları flow_get çıktısıyla keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/steps[stepId=paint]/args/iteratees[element=tbl_recruiters]/mapping/source' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
|
|
13
34
|
const flowIdParam = guid.describe("Akış id'si; şemadaki events[].value bu id'dir");
|
|
14
35
|
function flowSummary(flow) {
|
|
15
36
|
return {
|
|
@@ -22,7 +43,15 @@ function flowSummary(flow) {
|
|
|
22
43
|
}
|
|
23
44
|
async function hostEnvelope(platform, host, project, environment, applicationId, hostId) {
|
|
24
45
|
const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
25
|
-
return { [host]: hostUpdateBody(host, { ...record, applicationId }) };
|
|
46
|
+
return { record, envelope: { [host]: hostUpdateBody(host, { ...record, applicationId }) } };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* FLOW-024'ün arayacağı düğüm kümesi. Ham kaydın TAMAMI gezilmez: kayıt kendi `flows`'unu da
|
|
50
|
+
* taşıyabilir ve oradaki bayat element id'leri kümeyi kirletip aramayı sessizce boşa çıkarır.
|
|
51
|
+
* page/component düğümleri `schema`'da, layout'unkiler `views`'ta durur.
|
|
52
|
+
*/
|
|
53
|
+
function hostNodes(record) {
|
|
54
|
+
return record.schema ?? record.views ?? [];
|
|
26
55
|
}
|
|
27
56
|
async function requiredParameterWarnings(platform, project, environment, applicationId, steps) {
|
|
28
57
|
const warnings = [];
|
|
@@ -146,7 +175,7 @@ export function registerFlowTools(server, platform) {
|
|
|
146
175
|
},
|
|
147
176
|
annotations: writes
|
|
148
177
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, policyOverride }) => {
|
|
149
|
-
const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
178
|
+
const { record: hostRecord, envelope } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
150
179
|
const resolvedFlowId = flowId ?? randomUUID();
|
|
151
180
|
const flow = {
|
|
152
181
|
id: resolvedFlowId,
|
|
@@ -160,16 +189,20 @@ export function registerFlowTools(server, platform) {
|
|
|
160
189
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride));
|
|
161
190
|
const parameterWarnings = [
|
|
162
191
|
...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
|
|
163
|
-
...flowBodyWarnings(steps)
|
|
192
|
+
...flowBodyWarnings(steps),
|
|
193
|
+
...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
|
|
164
194
|
];
|
|
165
195
|
await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow, ...envelope } });
|
|
166
|
-
|
|
196
|
+
// Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
|
|
197
|
+
// gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
|
|
198
|
+
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, resolvedFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps });
|
|
199
|
+
return attachFindings(withWriteDiff(withNormalization({
|
|
167
200
|
host,
|
|
168
201
|
hostId,
|
|
169
202
|
flow: { id: resolvedFlowId, name, slug, steps: steps.length },
|
|
170
203
|
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
171
204
|
hint: "Akışı bir olaya bağlamak için: page_schema_patch ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
|
|
172
|
-
}, normalized), findings);
|
|
205
|
+
}, normalized), drops), findings);
|
|
173
206
|
}));
|
|
174
207
|
server.registerTool('flow_update', {
|
|
175
208
|
title: 'Akış güncelle',
|
|
@@ -192,7 +225,7 @@ export function registerFlowTools(server, platform) {
|
|
|
192
225
|
const current = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
|
|
193
226
|
if (!current.id)
|
|
194
227
|
throw new PlatformApiError(`'${flowId}' id'li akış bulunamadı; flow_list ile doğrulayın.`);
|
|
195
|
-
const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
228
|
+
const { record: hostRecord, envelope } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
196
229
|
const flow = {
|
|
197
230
|
id: flowId,
|
|
198
231
|
name: name ?? current.name,
|
|
@@ -208,10 +241,76 @@ export function registerFlowTools(server, platform) {
|
|
|
208
241
|
: [];
|
|
209
242
|
const parameterWarnings = [
|
|
210
243
|
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
211
|
-
...flowBodyWarnings(flow.steps)
|
|
244
|
+
...flowBodyWarnings(flow.steps),
|
|
245
|
+
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
|
|
212
246
|
];
|
|
213
247
|
await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow, ...envelope } });
|
|
214
|
-
|
|
248
|
+
// Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
|
|
249
|
+
// "düştü" demek yanlış alarm olurdu.
|
|
250
|
+
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, {
|
|
251
|
+
...(name === undefined ? {} : { name }),
|
|
252
|
+
...(slug === undefined ? {} : { slug }),
|
|
253
|
+
...(steps === undefined ? {} : { steps }),
|
|
254
|
+
...(errorSteps === undefined ? {} : { errorSteps })
|
|
255
|
+
});
|
|
256
|
+
return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}) }, normalized), drops), findings);
|
|
257
|
+
}));
|
|
258
|
+
server.registerTool('flow_patch', {
|
|
259
|
+
title: "Akışa patch uygula",
|
|
260
|
+
description: "Tam gövdeyi bağlama almadan hedefli değişiklik: gateway akışı çeker, JSON Patch uygular, politika kontrolünden geçirip geri yazar. flow_update REPLACE olduğu için tek bir ifadeyi düzeltmek 10 adımlı gövdenin tamamını yeniden göndermek demekti (09-05 koşusunda aynı 11 KB'lik gövde beş kez gönderildi). Dizi öğelerinde indeks yerine SEÇİCİ kullanın — adım ve iteratee sırası platformda değişebilir: '/steps[stepId=paint]/args/iteratees[element=tbl_funnel]/mapping/mappings/share/ref' ya da '/steps[stepId=load_summary]/args/parameters[name=company]/value/ref'. Seçici çözülemezse istek HİÇ gönderilmez.",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
project: projectParam,
|
|
263
|
+
environment: environmentParam,
|
|
264
|
+
host: hostParam,
|
|
265
|
+
applicationId: guid,
|
|
266
|
+
hostId: guid,
|
|
267
|
+
flowId: flowIdParam,
|
|
268
|
+
operations: patchOperationsParam.optional(),
|
|
269
|
+
ops: patchOperationsParam.optional().describe("'operations' ile aynı alan (eşanlamlı) — sayfa micro-op tool'larıyla aynı adı kullanabilmeniz için"),
|
|
270
|
+
policyOverride: policyOverrideParam
|
|
271
|
+
},
|
|
272
|
+
annotations: updates
|
|
273
|
+
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId, operations: givenOperations, ops, policyOverride }) => {
|
|
274
|
+
const operations = microOps('flow_patch', ops, givenOperations);
|
|
275
|
+
const current = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
|
|
276
|
+
if (!current.id)
|
|
277
|
+
throw new PlatformApiError(`'${flowId}' id'li akış bulunamadı; flow_list ile doğrulayın.`);
|
|
278
|
+
const document = structuredClone(current);
|
|
279
|
+
const resolved = resolvePatchOperations(document, operations, 'flow_get');
|
|
280
|
+
const patched = fastJsonPatch.applyPatch(document, resolved.operations, true, false).newDocument;
|
|
281
|
+
const { record: hostRecord } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
282
|
+
const flow = {
|
|
283
|
+
...patched,
|
|
284
|
+
id: flowId,
|
|
285
|
+
steps: patched.steps ?? [],
|
|
286
|
+
errorSteps: patched.errorSteps ?? [],
|
|
287
|
+
...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
|
|
288
|
+
};
|
|
289
|
+
const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
|
|
290
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride));
|
|
291
|
+
const parameterWarnings = [
|
|
292
|
+
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
293
|
+
...flowBodyWarnings(flow.steps),
|
|
294
|
+
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
|
|
295
|
+
];
|
|
296
|
+
// Gövde değil FARK gönderilir: barındırıcı zarfı (envelope) yazma yolunda gerekmez, platform
|
|
297
|
+
// PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
|
|
298
|
+
const netOps = fastJsonPatch.compare(current, flow);
|
|
299
|
+
const saved = await platform.tenant('PATCH', project, flowPath(host, applicationId, hostId, flowId), {
|
|
300
|
+
environment,
|
|
301
|
+
body: { baseVersion: baseVersionOf(current, 'flow_get'), ops: netOps }
|
|
302
|
+
});
|
|
303
|
+
const written = patchResult(saved, flow);
|
|
304
|
+
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, { steps: flow.steps, errorSteps: flow.errorSteps });
|
|
305
|
+
return attachFindings(withWriteDiff(withNormalization({
|
|
306
|
+
host,
|
|
307
|
+
hostId,
|
|
308
|
+
applied: operations.length,
|
|
309
|
+
newVersion: written.newVersion,
|
|
310
|
+
patchTargets: resolved.targets,
|
|
311
|
+
flow: flowSummary(written.data),
|
|
312
|
+
...(parameterWarnings.length > 0 ? { parameterWarnings } : {})
|
|
313
|
+
}, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), findings);
|
|
215
314
|
}));
|
|
216
315
|
server.registerTool('flow_delete', {
|
|
217
316
|
title: 'Akış sil',
|
|
@@ -219,7 +318,7 @@ export function registerFlowTools(server, platform) {
|
|
|
219
318
|
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
|
|
220
319
|
annotations: destructive
|
|
221
320
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
|
|
222
|
-
const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
321
|
+
const { envelope } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
223
322
|
await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId, ...envelope } });
|
|
224
323
|
return { deleted: flowId, host, hostId };
|
|
225
324
|
}));
|
package/dist/tools/folders.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
2
|
import { handle } from '../platform/responses.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
5
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
4
6
|
export function registerFolderTools(server, platform) {
|
|
5
7
|
const config = platform.config;
|
|
6
8
|
server.registerTool('folder_list', {
|
|
@@ -17,10 +19,13 @@ export function registerFolderTools(server, platform) {
|
|
|
17
19
|
}, handle(config, ({ project, environment, id, breadcrumb }) => platform.tenant('GET', project, breadcrumb ? `/folders/${id}/breadcrumb` : `/folders/${id}`, { environment })));
|
|
18
20
|
server.registerTool('folder_create', {
|
|
19
21
|
title: 'Klasör oluştur',
|
|
20
|
-
description: "Yeni klasör oluşturur (definition = CreateFolderCommand: { name, parentId? }). DİKKAT: fiziksel klasör adı ve depolama yolu ASCII'ye katlanır ('Donanım' → 'Donanim') ve aynı üst klasörde çakışırsa sonuna ' (1)' eklenir — kullanıcıya gösterilen displayName ham adı korur. Yanıttaki name/path katlanmış hâldir, dosya yollarını oradan okuyun.",
|
|
22
|
+
description: "Yeni klasör oluşturur (definition = CreateFolderCommand: { name, parentId? }). DİKKAT: fiziksel klasör adı ve depolama yolu ASCII'ye katlanır ('Donanım' → 'Donanim') ve aynı üst klasörde çakışırsa sonuna ' (1)' eklenir — kullanıcıya gösterilen displayName ham adı korur. Yanıttaki name/path katlanmış hâldir, dosya yollarını oradan okuyun. Backend ön kontrolü (FLD-001, block): name zorunlu ve \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez.",
|
|
21
23
|
inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
|
|
22
24
|
annotations: writes
|
|
23
|
-
}, handle(config, ({ project, environment, definition }) =>
|
|
25
|
+
}, handle(config, async ({ project, environment, definition }) => {
|
|
26
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'folder', definition, { operation: 'create' }));
|
|
27
|
+
return attachFindings(await platform.tenant('POST', project, '/folders', { environment, body: definition }), findings);
|
|
28
|
+
}));
|
|
24
29
|
server.registerTool('folder_update', {
|
|
25
30
|
title: 'Klasör yetkilerini güncelle',
|
|
26
31
|
description: "Klasörün YETKİLENDİRMESİNİ günceller — `UpdateFolderCommand`'ın alanları yalnız `middlewareIds` ve `roleIds`'tir. Ad değiştirmek için folder_rename kullanın; **klasör TAŞIMA (parentId değiştirme) platformda desteklenmiyor** — gövdeye parentId/name koyarsanız sessizce yok sayılır ve istek yine 200 döner (etkinlik turu Ç3).",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { handle } from '../platform/responses.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
5
|
+
const nameParam = z.string().min(1).regex(identifier, 'Geçerli bir JavaScript tanımlayıcısı olmalı')
|
|
6
|
+
.describe("Motorun bağlayacağı ad; ifadelerde bu adla çağrılır (sum(1, 2)). Sistem fonksiyonlarının adları (Now, UtcNow, GenerateGuid, crypto…) rezervedir ve reddedilir");
|
|
7
|
+
const functionParam = z.string().min(1)
|
|
8
|
+
.describe("Klasik ya da arrow JavaScript FONKSİYON İFADESİ: '(left, right) => left + right' ya da 'function (a) { return a * 2 }'. Deyim değil ifade olmalı — 'return a + b' gibi bir gövde tek başına reddedilir. Platform kaydetmeden önce gerçek bir Jint motorunda çalıştırıp sonucun çağrılabilir olduğunu doğrular; SÖZDİZİMİ ve çağrılabilirlik denetlenir, gövdenin içindeki serbest değişkenler DENETLENMEZ");
|
|
9
|
+
const descriptionParam = z.string().min(1)
|
|
10
|
+
.describe('Fonksiyonun ne işe yaradığı; Studio kod editöründe autocomplete infobox başlığı olarak görünür. Zorunludur');
|
|
11
|
+
const schemaParam = z.array(z.object({
|
|
12
|
+
name: z.string().min(1).regex(identifier, 'Geçerli bir JavaScript tanımlayıcısı olmalı')
|
|
13
|
+
.describe('Parametre adı; fonksiyon içinde benzersiz olmalı'),
|
|
14
|
+
description: z.string().min(1).describe('Parametrenin ne olduğu; infobox\'ta parametre satırı olarak görünür. Zorunludur')
|
|
15
|
+
})).describe('Parametre şeması, SIRALI. Yalnız dokümantasyon ve autocomplete imzası için kullanılır — motor çağrıyı bu şemaya göre DOĞRULAMAZ, eksik argüman undefined gelir. Parametresiz fonksiyonda boş dizi verin');
|
|
16
|
+
export function registerGlobalFunctionTools(server, platform) {
|
|
17
|
+
const config = platform.config;
|
|
18
|
+
server.registerTool('global_function_list', {
|
|
19
|
+
title: 'Global fonksiyonları listele',
|
|
20
|
+
description: "Tenant kapsamındaki global JavaScript fonksiyonlarını listeler. Bunlar her workflow/automation/middleware koşumunda ve Studio preview'ında motora bağlanır; ifadelerde çıplak adla çağrılırlar.",
|
|
21
|
+
inputSchema: { project: projectParam, environment: environmentParam },
|
|
22
|
+
annotations: readOnly
|
|
23
|
+
}, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/global-functions', { environment })));
|
|
24
|
+
server.registerTool('global_function_get', {
|
|
25
|
+
title: 'Global fonksiyonu getir',
|
|
26
|
+
description: 'Tek bir global fonksiyonun adını, gövdesini, açıklamasını ve parametre şemasını getirir.',
|
|
27
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
28
|
+
annotations: readOnly
|
|
29
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/global-functions/${id}`, { environment })));
|
|
30
|
+
server.registerTool('global_function_create', {
|
|
31
|
+
title: 'Global fonksiyon tanımla',
|
|
32
|
+
description: "Yeni global fonksiyon kaydeder. Ad tenant içinde BENZERSİZDİR. Fonksiyon açıklaması ve HER parametre açıklaması zorunludur — Studio bunları autocomplete infobox'ında gösterir. Gövde bağlamdan bağımsız olmalı: içinde 'input' ya da bir adım adı gibi SERBEST DEĞİŞKEN kullanırsanız kayıt geçer ama fonksiyon o değişkenin tanımlı olmadığı her akışta 'X is not defined' ile patlar; dinamik veriyi parametre olarak geçirin.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
project: projectParam,
|
|
35
|
+
environment: environmentParam,
|
|
36
|
+
name: nameParam,
|
|
37
|
+
function: functionParam,
|
|
38
|
+
description: descriptionParam,
|
|
39
|
+
schema: schemaParam
|
|
40
|
+
},
|
|
41
|
+
annotations: writes
|
|
42
|
+
}, handle(config, ({ project, environment, name, function: body, description, schema }) => platform.tenant('POST', project, '/global-functions', { environment, body: { name, function: body, description, schema } })));
|
|
43
|
+
server.registerTool('global_function_update', {
|
|
44
|
+
title: 'Global fonksiyonu güncelle',
|
|
45
|
+
description: "Global fonksiyonun tamamını değiştirir (PUT — dört alanı da gönderin). ADI DEĞİŞTİRMEK, fonksiyonu çağıran bir workflow varsa 400 ile reddedilir (CantRenameUsedGlobalFunctionException); gövde/açıklama/şema değişikliği serbesttir. Adı gerçekten değiştirmeniz gerekiyorsa önce çağıran akışlardaki ifadeleri güncelleyin.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
project: projectParam,
|
|
48
|
+
environment: environmentParam,
|
|
49
|
+
id: guid,
|
|
50
|
+
name: nameParam,
|
|
51
|
+
function: functionParam,
|
|
52
|
+
description: descriptionParam,
|
|
53
|
+
schema: schemaParam
|
|
54
|
+
},
|
|
55
|
+
annotations: updates
|
|
56
|
+
}, handle(config, ({ project, environment, id, name, function: body, description, schema }) => platform.tenant('PUT', project, `/global-functions/${id}`, { environment, body: { name, function: body, description, schema } })));
|
|
57
|
+
server.registerTool('global_function_delete', {
|
|
58
|
+
title: 'Global fonksiyonu sil',
|
|
59
|
+
description: "Global fonksiyonu siler. Fonksiyonu çağıran bir workflow varsa 400 ile reddedilir (CantDeleteUsedGlobalFunctionException) ve mesaj çağıran akışların adlarını sayar. Eşleşme tam tanımlayıcı sınırıyla aranır: 'checksum' ya da 'mySum' içeren bir ifade 'sum' çağrısı sayılmaz.",
|
|
60
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
61
|
+
annotations: destructive
|
|
62
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/global-functions/${id}`, { environment })));
|
|
63
|
+
}
|