@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.env.example +8 -3
  2. package/.env.production.example +2 -0
  3. package/README.md +229 -193
  4. package/dist/config.js +20 -2
  5. package/dist/knowledge/backendConstraints.js +897 -0
  6. package/dist/knowledge/componentCatalog.js +196 -42
  7. package/dist/knowledge/coverage.js +43 -0
  8. package/dist/knowledge/flowGuards.js +30 -0
  9. package/dist/knowledge/guideSections.js +49 -0
  10. package/dist/knowledge/hydrate.js +97 -0
  11. package/dist/knowledge/languageCodes.js +36 -0
  12. package/dist/knowledge/languages.js +105 -0
  13. package/dist/knowledge/layoutSummary.js +10 -0
  14. package/dist/knowledge/localization.js +51 -0
  15. package/dist/knowledge/normalize.js +449 -16
  16. package/dist/knowledge/outlines.js +136 -12
  17. package/dist/knowledge/propTypes.js +80 -0
  18. package/dist/knowledge/rules.js +618 -43
  19. package/dist/knowledge/schemaGuard.js +199 -0
  20. package/dist/knowledge/styleResolve.js +63 -4
  21. package/dist/observability/trace.js +27 -3
  22. package/dist/platform/errors.js +46 -4
  23. package/dist/platform/http.js +33 -4
  24. package/dist/platform/patch.js +27 -0
  25. package/dist/platform/refs.js +104 -0
  26. package/dist/platform/responses.js +9 -2
  27. package/dist/platform/screenshot.js +527 -16
  28. package/dist/platform/secrets.js +40 -0
  29. package/dist/platform/tokenVault.js +89 -0
  30. package/dist/platform/writeDiff.js +281 -0
  31. package/dist/resources/examples/layout-app-shell.json +1 -1
  32. package/dist/resources/examples/microservice-table-list.json +2 -4
  33. package/dist/resources/examples/page-dynamic-form.json +2 -2
  34. package/dist/resources/examples/page-flex-sections.json +99 -0
  35. package/dist/resources/examples/page-sign-in.json +3 -2
  36. package/dist/resources/guides/component.md +69 -8
  37. package/dist/resources/guides/entity.md +404 -16
  38. package/dist/resources/guides/expressions.md +44 -2
  39. package/dist/resources/guides/external-component.md +9 -0
  40. package/dist/resources/guides/getting-started.md +77 -7
  41. package/dist/resources/guides/layout.md +53 -10
  42. package/dist/resources/guides/microservice.md +226 -13
  43. package/dist/resources/guides/middleware.md +89 -1
  44. package/dist/resources/guides/page.md +383 -46
  45. package/dist/resources/guides/translation.md +126 -8
  46. package/dist/resources/guides/view.md +16 -1
  47. package/dist/resources/guides/workflow.md +198 -27
  48. package/dist/resources/knowledge.js +5 -0
  49. package/dist/resources/rules.js +27 -0
  50. package/dist/resources/schema-fingerprint.json +8 -0
  51. package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
  52. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  53. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  54. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  55. package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
  56. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  57. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  58. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  59. package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
  60. package/dist/resources/schemas/GetRowsQuery.json +3 -0
  61. package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
  62. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
  64. package/dist/resources/schemas/StepDto.json +23 -3
  65. package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
  66. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  67. package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
  68. package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
  69. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  70. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  71. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  72. package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
  73. package/dist/server.js +4 -0
  74. package/dist/tools/access.js +3 -3
  75. package/dist/tools/applications.js +1 -1
  76. package/dist/tools/automations.js +63 -30
  77. package/dist/tools/common.js +39 -0
  78. package/dist/tools/components.js +16 -7
  79. package/dist/tools/datasources.js +83 -21
  80. package/dist/tools/deployments.js +34 -2
  81. package/dist/tools/dtos.js +25 -12
  82. package/dist/tools/entities.js +113 -28
  83. package/dist/tools/externalComponents.js +9 -3
  84. package/dist/tools/files.js +20 -6
  85. package/dist/tools/flows.js +110 -11
  86. package/dist/tools/folders.js +7 -2
  87. package/dist/tools/globalFunctions.js +63 -0
  88. package/dist/tools/layouts.js +103 -29
  89. package/dist/tools/microservices.js +229 -38
  90. package/dist/tools/organizations.js +5 -6
  91. package/dist/tools/pages.js +365 -100
  92. package/dist/tools/platformInfo.js +152 -11
  93. package/dist/tools/projects.js +183 -33
  94. package/dist/tools/queries.js +32 -10
  95. package/dist/tools/rows.js +159 -45
  96. package/dist/tools/screenWrite.js +59 -0
  97. package/dist/tools/screens.js +28 -0
  98. package/dist/tools/translations.js +24 -8
  99. package/dist/tools/views.js +20 -10
  100. package/dist/tools/workflows.js +33 -16
  101. package/package.json +6 -3
@@ -1,7 +1,38 @@
1
- import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
1
+ import { findLocalizationCompanion, localizationRows, platformIgnoredLocalizations } from '../knowledge/localization.js';
2
+ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environmentParam, languageParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
3
+ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
4
+ import { withNormalization } from '../knowledge/normalize.js';
5
+ import { RefResolver } from '../platform/refs.js';
6
+ import { attachFindings, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
7
+ import { policyRules } from '../knowledge/rules.js';
8
+ import { languageHeader } from '../knowledge/languageCodes.js';
2
9
  import { handle } from '../platform/responses.js';
3
10
  import { z } from 'zod';
4
11
  const rowValues = z.record(z.unknown()).describe("Alan SLUG'ı → değer sözlüğü; anahtarlar entity_get çıktısındaki field SLUG'larıdır (name değil — name kullanmak 'Please enter a valid value' hatası verir)");
12
+ /**
13
+ * `row_*` DBMS düzeyi bir satır yüzeyidir; var olan satır kümesinden TÜRETİLEN satırları
14
+ * (geçmiş, hareket, log, junction, backfill) gövdede taşımak onun işi değildir — 2026-09-05'te
15
+ * 802, 2026-09-06'da 552 satır bu yüzden elle yazıldı (`B-34`, `B-50`) ve ikinci koşunun 65
16
+ * dakikasının ~21'i buraya gitti. Eşik büyük tutuldu: bulgu her toplu yazmada bağırırsa koşunun
17
+ * sinyal oranını kendisi düşürür, bu yüzden yalnız "elle taşınamayacak kadar büyük" dizilerde
18
+ * konuşur ve cümlesi KOŞULLUDUR — dışarıdan gelen veriyi (içe aktarma, kullanıcı girdisi)
19
+ * yazan çağıranı suçlamaz.
20
+ */
21
+ const DERIVED_ROW_THRESHOLD = 50;
22
+ function derivedRowFindings(rows) {
23
+ if (!rows || rows.length < DERIVED_ROW_THRESHOLD)
24
+ return [];
25
+ return [{
26
+ ruleId: 'ROW-001',
27
+ severity: 'warn',
28
+ message: `Gövdede ${rows.length} satır var. Bu satırlar VAR OLAN satırlardan türetiliyorsa (geçmiş/hareket/log/junction tablosu, backfill) onları gövdede taşımayın: tek bir kayıtlı 'INSERT … SELECT' sorgusu aynı işi TEK çağrıda yapar ve hiçbir id elle taşınmaz (ölçüldü 2026-09-07). Satırlar dışarıdan geliyorsa (içe aktarma, kullanıcı verisi) bu çağrı doğru yoldur. Ham SQL yolu UYGULAMA katmanını atlar (automation tetiklenmez, incremental sayaç ilerlemez, localization companion yazılmaz) ama veritabanı kısıtları — NOT NULL, FK, UNIQUE, enum CHECK — aynen uygulanır. Reçete, merdiven kalıbı ve anahtar sözleşmesi: platform_guide('entity') §'Toplu ve türetilmiş satır'.`,
29
+ guide: 'ngapps://guides/entity'
30
+ }];
31
+ }
32
+ const includeTranslationsParam = z.boolean().optional()
33
+ .describe("Çok dilli alanların TÜM dillerini de getirir: satıra 'localizations: { <dil>: { <alan>: <değer> } }' eklenir. Verilmezse satır yalnız istek dilinde düzleştirilmiş gelir (çevirisi olmayan alan varsayılan dile düşer).");
34
+ const localizationsParam = z.record(z.record(z.unknown())).optional()
35
+ .describe("Dil kodu → çok dilli alan değerleri; ör. { tr: { name: 'Yıllık izin' }, en: { name: 'Annual leave' } }. Verilen her dil upsert edilir, verilmeyen dile dokunulmaz. Dil kodu projenin dil listesinde değilse platform 400 döner ve ana satır da yazılmaz.");
5
36
  export function registerRowTools(server, platform) {
6
37
  const config = platform.config;
7
38
  server.registerTool('row_list', {
@@ -10,106 +41,189 @@ export function registerRowTools(server, platform) {
10
41
  inputSchema: {
11
42
  project: projectParam,
12
43
  environment: environmentParam,
13
- entityId: guid,
44
+ entityId: entityRef,
14
45
  selectedFields: z.array(z.string()).optional().describe('Dönen satırlar bu alan adlarına indirgenir, id her zaman dahildir; platform tam satırı hesapladığı için kırpma gateway tarafında yapılır'),
15
46
  filters: z.array(z.record(z.unknown())).optional().describe('Platform Filter dizisi; biçim için row_list\'i filtresiz çağırıp Studio\'daki bir filtreyi referans alın'),
47
+ language: languageParam,
48
+ includeTranslations: includeTranslationsParam,
16
49
  ...pagingParams
17
50
  },
18
51
  annotations: readOnly
19
- }, handle(config, async ({ project, environment, entityId, ...body }) => {
20
- const payload = await platform.tenant('POST', project, `/entities/${entityId}/row/list`, { environment, body });
52
+ }, handle(config, async ({ project, environment, entityId, language, ...body }) => {
53
+ const notes = [];
54
+ const headers = languageHeader(language, notes);
55
+ const resolver = new RefResolver(platform, project, environment);
56
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
57
+ const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/list`, { environment, body, headers });
21
58
  const wanted = body.selectedFields ?? [];
22
- if (!wanted.length || !payload || typeof payload !== 'object' || Array.isArray(payload))
23
- return payload;
24
- const rows = payload.data;
25
- if (!Array.isArray(rows))
26
- return payload;
27
- const keep = new Set([...wanted.map(field => field.toLowerCase()), 'id']);
28
- return {
59
+ const rows = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.data : undefined;
60
+ if (!wanted.length || !Array.isArray(rows))
61
+ return withNormalization(withResolved(payload, resolver.resolved), notes);
62
+ /**
63
+ * 'localizations' keep listesine girmezse includeTranslations'ın getirdiği çeviriler tam da
64
+ * onları isteyen çağrıda kırpılırdı: iki parametre birlikte verildiğinde sessizce boşa çıkar.
65
+ */
66
+ const keep = new Set([...wanted.map(field => field.toLowerCase()), 'id', ...(body.includeTranslations ? ['localizations'] : [])]);
67
+ return withNormalization(withResolved({
29
68
  ...payload,
30
69
  data: rows.map(row => row && typeof row === 'object' && !Array.isArray(row)
31
70
  ? Object.fromEntries(Object.entries(row).filter(([key]) => keep.has(key.toLowerCase())))
32
71
  : row)
33
- };
72
+ }, resolver.resolved), notes);
34
73
  }));
35
74
  server.registerTool('row_get', {
36
75
  title: 'Satır getir',
37
76
  description: 'Tek satırı id ile getirir.',
38
- inputSchema: { project: projectParam, environment: environmentParam, entityId: guid, id: z.string().min(1) },
77
+ inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, id: z.string().min(1), language: languageParam, includeTranslations: includeTranslationsParam },
39
78
  annotations: readOnly
40
- }, handle(config, ({ project, environment, entityId, id }) => platform.tenant('GET', project, `/entities/${entityId}/row/${encodeURIComponent(id)}`, { environment })));
79
+ }, handle(config, async ({ project, environment, entityId, id, language, includeTranslations }) => {
80
+ const notes = [];
81
+ const headers = languageHeader(language, notes);
82
+ const resolver = new RefResolver(platform, project, environment);
83
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
84
+ const payload = await platform.tenant('GET', project, `/entities/${resolvedEntityId}/row/${encodeURIComponent(id)}`, { environment, headers, ...(includeTranslations ? { query: { includeTranslations: true } } : {}) });
85
+ return withNormalization(withResolved(payload, resolver.resolved), notes);
86
+ }));
41
87
  server.registerTool('row_create', {
42
88
  title: 'Satır ekle',
43
- description: "Tek satır için 'row', çoklu ekleme için 'rows' verin (ikisi birden verilmez).",
89
+ description: "Tek satır için 'row', çoklu ekleme için 'rows' verin (ikisi birden verilmez). Çoklu dilli entity'de 'localizations: { tr: {…}, en: {…} }' ile dil satırlarını da aynı çağrıda yazar: companion entity'yi ve FK'yı gateway bulur (yalnız 'row' ile). 'rows' TEK TRANSACTION'da yazılır: bir satır doğrulamadan düşerse HİÇBİRİ yazılmaz ve hata hangi satır indeksinin düştüğünü söyler. Automation'lar satır BAŞINA tetiklenir — sync (isQueue:true) automation'lar aynı transaction'da satır satır koşar, biri düşerse toplu yazma geri alınır. Tek çağrıda en çok 1000 satır. SINIR: row_* bir veritabanı yönetim yüzeyidir — satırları GÖVDEDE taşır. VAR OLAN satırlardan türetilen bir küme (geçmiş/hareket/log/junction, backfill) bu yola AİT DEĞİLDİR; onu tek bir kayıtlı 'INSERT … SELECT' sorgusu + query kaynaklı uç yazar, reçete platform_guide('entity') §'Toplu ve türetilmiş satır'. Toplu ucun id döndürmesine GÜVENMEYİN: 2026-09-07 ölçümünde boş gövde döndü (2026-09-05'te anahtar listesi dönüyordu, fark B-63) — id gerekiyorsa ardından row_list çekin. TOPLU eklemede dil değerlerini satırın KENDİ içine koyun ('rows: [{ kod: 'A1', localizations: { tr: {…} } }]') — üst düzey 'localizations' toplu uçta hangi satıra ait olduğunu söyleyemez. 'row' içine düz çok dilli slug yazarsanız değer İSTEK DİLİ için kaydedilir — dili 'language' ile seçin.",
44
90
  inputSchema: {
45
91
  project: projectParam,
46
92
  environment: environmentParam,
47
- entityId: guid,
93
+ entityId: entityRef,
48
94
  row: rowValues.optional(),
49
- rows: z.array(z.record(z.unknown())).optional()
95
+ rows: z.array(z.record(z.unknown())).optional(),
96
+ localizations: localizationsParam,
97
+ language: languageParam,
98
+ rowContext: z.record(z.unknown()).optional()
99
+ .describe("Satır olayının DIŞINDAN automation'a geçirilecek değerler. Tetiklenen automation bunları ifadelerinde `rowContext.<anahtar>` ile okur — zaman damgası, göç kaynağı, toplu iş kimliği gibi. Tarihsel (geçmiş tarihli) veri üretmenin yolu budur: automation `UtcNow()` yerine `rowContext.occurredAt`'i yazabilir. Verilmezse automation bağlamı değişmez.")
50
100
  },
51
101
  annotations: writes
52
- }, handle(config, ({ project, environment, entityId, row, rows }) => {
102
+ }, handle(config, async ({ project, environment, entityId, row, rows, localizations, rowContext, language }) => {
53
103
  if (row && rows)
54
104
  throw new Error("'row' ve 'rows' birlikte verilemez.");
55
- if (rows)
56
- return platform.tenant('POST', project, `/entities/${entityId}/row/bulk`, { environment, body: { rows } });
57
- if (row)
58
- return platform.tenant('POST', project, `/entities/${entityId}/row`, { environment, body: { row } });
59
- throw new Error("'row' ya da 'rows' parametrelerinden biri zorunlu.");
105
+ if (localizations && !row)
106
+ throw new Error("Üst düzey 'localizations' yalnız 'row' ile verilir: toplu uçta hangi satıra ait olduğu belirsizdir. Toplu eklemede dil değerlerini satırın kendi içine koyun — rows: [{ …, localizations: { tr: {…} } }].");
107
+ if (!row && !rows)
108
+ throw new Error("'row' ya da 'rows' parametrelerinden biri zorunlu.");
109
+ const notes = [];
110
+ const headers = languageHeader(language, notes);
111
+ const wantedLocalizations = localizations && Object.keys(localizations).length > 0 ? localizations : undefined;
112
+ const shapeFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', rows ? { rows } : { row }, { operation: 'create' }));
113
+ const resolver = new RefResolver(platform, project, environment);
114
+ if (rows) {
115
+ const bulkEntityId = await resolveEntityId(resolver, entityId);
116
+ const bulkBody = { rows, ...(rowContext ? { rowContext } : {}) };
117
+ const bulkDrift = unmappedMembers('CreateBulkRowCommand', bulkBody);
118
+ return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('POST', project, `/entities/${bulkEntityId}/row/bulk`, { environment, headers, body: bulkBody }), resolver.resolved), notes), bulkDrift), [...shapeFindings, ...derivedRowFindings(rows)]);
119
+ }
120
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
121
+ const body = { row, ...(wantedLocalizations ? { localizations: wantedLocalizations } : {}), ...(rowContext ? { rowContext } : {}) };
122
+ const drift = unmappedMembers('CreateRowCommand', body);
123
+ const response = await platform.tenantWithHeaders('POST', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body });
124
+ const created = response.body;
125
+ if (!wantedLocalizations || !platformIgnoredLocalizations(response.headers)) {
126
+ return attachFindings(withUnmappedMembers(withNormalization(withResolved(created, resolver.resolved), notes), drift), shapeFindings);
127
+ }
128
+ notes.push("platform 'localizations' alanını tanımıyor (X-Unmapped-Members); dil satırlarını gateway companion entity'ye yazdı.");
129
+ const entity = await resolver.entity(entityId);
130
+ let mainRowId = created.data?.id;
131
+ try {
132
+ // Satır sözlüğü alan SLUG'larıyla anahtarlanır ve PK slug'ı 'id' olmak ZORUNDA değildir
133
+ // (CreateRowCommandHandler `entityDto.PrimaryKey()` kullanır). 'id' yoksa gerçek PK'yı
134
+ // bulmadan yazmak FK'sı NULL dil satırları üretir ve bunu başarı diye raporlardı.
135
+ if (mainRowId === undefined || mainRowId === null) {
136
+ const primaryKey = (await resolver.entityFields(entity)).find(field => field.primaryKey);
137
+ mainRowId = primaryKey ? created.data?.[primaryKey.slug] : undefined;
138
+ }
139
+ if (mainRowId === undefined || mainRowId === null) {
140
+ throw new Error("Ana satır yazıldı ama yanıtta birincil anahtar okunamadı; dil satırları FK'sız yazılmadı (FK'sı boş satır sessiz yanlış olurdu).");
141
+ }
142
+ const companion = await findLocalizationCompanion(resolver, entity);
143
+ const localizationBatch = localizationRows(companion, mainRowId, wantedLocalizations);
144
+ await platform.tenant('POST', project, `/entities/${companion.entityId}/row/bulk`, { environment, body: { rows: localizationBatch } });
145
+ return attachFindings(withUnmappedMembers(withNormalization(withResolved({ ...created, localizations: { entityId: companion.entityId, languages: Object.keys(wantedLocalizations), count: localizationBatch.length } }, resolver.resolved), notes), drift), shapeFindings);
146
+ }
147
+ catch (error) {
148
+ const message = error instanceof Error ? error.message : String(error);
149
+ return attachFindings(withUnmappedMembers(withNormalization(withResolved({ ...created, localizationsFailed: { mainRowId, error: message, note: 'Ana satır YAZILDI, dil satırları yazılamadı; gateway geri alamaz. Ya companion entity\'ye row_create ile elle yazın ya da ana satırı row_delete ile silip tekrar deneyin.' } }, resolver.resolved), notes), drift), shapeFindings);
150
+ }
60
151
  }));
61
152
  server.registerTool('row_update', {
62
153
  title: 'Satır güncelle',
63
- description: "Tek satır için 'row', çoklu güncelleme için 'rows' verin; satır id alanı gövdede olmalıdır.",
154
+ description: "Tek satır için 'row', çoklu güncelleme için 'rows' verin; satır id alanı gövdede olmalıdır. 'rows' TEK TRANSACTION'da yazılır: bir satır düşerse HİÇBİRİ yazılmaz ve hata hangi satır indeksinin düştüğünü söyler. Automation'lar satır BAŞINA tetiklenir — sync (isQueue:true) automation'lar aynı transaction'da satır satır koşar. Tek çağrıda en çok 1000 satır. Aynı sınır row_create'te olduğu gibi geçerlidir: var olan satırlardan HESAPLANAN toplu güncelleme (durum/toplam/damga backfill'i) kayıtlı bir 'UPDATE … FROM' sorgusuna aittir — platform_guide('entity') §'Toplu ve türetilmiş satır'. Çeviri güncellemek için 'localizations: { tr: {…}, en: {…} }' verin — ana satır hiç değişmese de yalnız çeviriler yazılabilir. TOPLU güncellemede aynı nesne satırın KENDİ içine konur ('rows: [{ id: 1, localizations: { tr: {…} } }]'); 'row' içindeki düz çok dilli slug İSTEK DİLİNE yazılır.",
64
155
  inputSchema: {
65
156
  project: projectParam,
66
157
  environment: environmentParam,
67
- entityId: guid,
158
+ entityId: entityRef,
68
159
  row: rowValues.optional(),
69
- rows: z.array(z.record(z.unknown())).optional()
160
+ rows: z.array(z.record(z.unknown())).optional(),
161
+ localizations: localizationsParam,
162
+ language: languageParam,
163
+ rowContext: z.record(z.unknown()).optional()
164
+ .describe("Satır olayının DIŞINDAN automation'a geçirilecek değerler. Tetiklenen automation bunları ifadelerinde `rowContext.<anahtar>` ile okur — zaman damgası, göç kaynağı, toplu iş kimliği gibi. Tarihsel (geçmiş tarihli) veri üretmenin yolu budur: automation `UtcNow()` yerine `rowContext.occurredAt`'i yazabilir. Verilmezse automation bağlamı değişmez.")
70
165
  },
71
166
  annotations: updates
72
- }, handle(config, ({ project, environment, entityId, row, rows }) => {
167
+ }, handle(config, async ({ project, environment, entityId, row, rows, localizations, rowContext, language }) => {
73
168
  if (row && rows)
74
169
  throw new Error("'row' ve 'rows' birlikte verilemez.");
75
- if (rows)
76
- return platform.tenant('PUT', project, `/entities/${entityId}/row/bulk`, { environment, body: { rows } });
77
- if (row)
78
- return platform.tenant('PUT', project, `/entities/${entityId}/row`, { environment, body: { row } });
79
- throw new Error("'row' ya da 'rows' parametrelerinden biri zorunlu.");
170
+ if (localizations && !row)
171
+ throw new Error("Üst düzey 'localizations' yalnız 'row' ile verilir: toplu uçta hangi satıra ait olduğu belirsizdir. Toplu güncellemede dil değerlerini satırın kendi içine koyun — rows: [{ id, localizations: { tr: {…} } }].");
172
+ if (!row && !rows)
173
+ throw new Error("'row' ya da 'rows' parametrelerinden biri zorunlu.");
174
+ const shapeFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', rows ? { rows } : { row }, { operation: 'update' }));
175
+ const notes = [];
176
+ const headers = languageHeader(language, notes);
177
+ const resolver = new RefResolver(platform, project, environment);
178
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
179
+ if (rows) {
180
+ const bulkBody = { rows, ...(rowContext ? { rowContext } : {}) };
181
+ const bulkDrift = unmappedMembers('UpdateBulkRowCommand', bulkBody);
182
+ return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/row/bulk`, { environment, headers, body: bulkBody }), resolver.resolved), notes), bulkDrift), [...shapeFindings, ...derivedRowFindings(rows)]);
183
+ }
184
+ const body = { row, ...(localizations ? { localizations } : {}), ...(rowContext ? { rowContext } : {}) };
185
+ const drift = unmappedMembers('UpdateRowCommand', body);
186
+ return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body }), resolver.resolved), notes), drift), shapeFindings);
80
187
  }));
81
188
  server.registerTool('row_delete', {
82
189
  title: 'Satır sil',
83
- description: "Tek satır için 'id', çoklu silme için 'ids' verin.",
190
+ description: "Tek satır için 'id', çoklu silme için 'ids' verin. Çok dilli entity'de dil satırlarını platform ana satırdan ÖNCE kendisi siler; eski platform sürümünde yabancı anahtar hatası dönerse önce companion entity'nin satırlarını silin.",
84
191
  inputSchema: {
85
192
  project: projectParam,
86
193
  environment: environmentParam,
87
- entityId: guid,
194
+ entityId: entityRef,
88
195
  id: z.string().optional(),
89
196
  ids: z.array(z.union([z.string(), z.number()])).optional()
90
197
  },
91
198
  annotations: destructive
92
- }, handle(config, ({ project, environment, entityId, id, ids }) => {
199
+ }, handle(config, async ({ project, environment, entityId, id, ids }) => {
93
200
  if (id && ids)
94
201
  throw new Error("'id' ve 'ids' birlikte verilemez.");
202
+ if (!id && !ids)
203
+ throw new Error("'id' ya da 'ids' parametrelerinden biri zorunlu.");
204
+ const resolver = new RefResolver(platform, project, environment);
205
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
95
206
  if (ids)
96
- return platform.tenant('POST', project, `/entities/${entityId}/row/bulk/delete`, { environment, body: { rowIds: ids } });
97
- if (id)
98
- return platform.tenant('DELETE', project, `/entities/${entityId}/row/${encodeURIComponent(id)}`, { environment });
99
- throw new Error("'id' ya da 'ids' parametrelerinden biri zorunlu.");
207
+ return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/bulk/delete`, { environment, body: { rowIds: ids } }), resolver.resolved);
208
+ return withResolved(await platform.tenant('DELETE', project, `/entities/${resolvedEntityId}/row/${encodeURIComponent(String(id))}`, { environment }), resolver.resolved);
100
209
  }));
101
210
  server.registerTool('row_next_code', {
102
211
  title: 'Sıradaki kodu üret',
103
- description: 'Incremental tipli alan için sıradaki kodu döner.',
212
+ description: "Incremental tipli alan için sıradaki kodu döner ve sayacı İLERLETMEZ (persist:false) — kodun nasıl görüneceğini kaydetmeden görmek için. Gövde row_create'in Incremental alanına yazdığınız nesneyle aynıdır: { prefix, separator, counter, digitCount }. 'counter' ve 'digitCount' platform tarafında ZORUNLUDUR (GetNextIncrementalCodeQueryValidator: NotEmpty) — eksik verilirse istek 400 döner, bu yüzden gateway de ikisini zorunlu tutar.",
104
213
  inputSchema: {
105
214
  project: projectParam,
106
215
  environment: environmentParam,
107
- entityId: guid,
216
+ entityId: entityRef,
108
217
  fieldId: guid,
109
- prefix: z.string().optional(),
110
- separator: z.string().optional(),
111
- digitCount: z.number().int().optional()
218
+ prefix: z.string().optional().describe("Kodun önüne gelen sabit ('EMP')"),
219
+ separator: z.string().optional().describe("Önek ile sayaç arasındaki ayraç ('-'); verilmezse boş"),
220
+ counter: z.number().int().min(1).describe('Sayacın BAŞLANGIÇ değeri — platform zorunlu tutar (NotEmpty, yani 0 kabul edilmez). Alanın kayıtlı sayacını bilmiyorsanız entity_get ile alanın tanımına bakın.'),
221
+ digitCount: z.number().int().min(1).describe("Sayacın basamak sayısı (4 → '0006'); platform zorunlu tutar (NotEmpty)")
112
222
  },
113
223
  annotations: readOnly
114
- }, handle(config, ({ project, environment, entityId, ...body }) => platform.tenant('POST', project, `/entities/${entityId}/row/next-code`, { environment, body })));
224
+ }, handle(config, async ({ project, environment, entityId, ...body }) => {
225
+ const resolver = new RefResolver(platform, project, environment);
226
+ const resolvedEntityId = await resolveEntityId(resolver, entityId);
227
+ return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/next-code`, { environment, body }), resolver.resolved);
228
+ }));
115
229
  }
@@ -0,0 +1,59 @@
1
+ import { invalidScreenEventNames, invalidWidgetTypeNodes, nestedRepeaterNodes, nestedRepeaterMessage, blockSizeNote, heightTypeNote, sizeStringNote } from '../knowledge/normalize.js';
2
+ import { applyCatalogLayoutRules } from './components.js';
3
+ import { PlatformApiError } from '../platform/errors.js';
4
+ const GUARD_CODES = {
5
+ page: { events: 'PAGE-011', widgets: 'PAGE-012', noun: 'sayfa', tree: 'schema' },
6
+ layout: { events: 'LAY-004', widgets: 'LAY-005', noun: 'layout', tree: 'views' }
7
+ };
8
+ /** JS tanımlayıcısı olmayan `name` alanlarını toplar; ad `const <ad> = Components["<ad>"]` üretimine girer. */
9
+ export function invalidComponentNames(nodes) {
10
+ const found = [];
11
+ for (const node of nodes ?? []) {
12
+ const name = node.name;
13
+ if (typeof name === 'string' && name.length > 0 && !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name.replace(/-/g, ''))) {
14
+ found.push(`'${name}' (${node.elementId ?? '?'})`);
15
+ }
16
+ found.push(...invalidComponentNames(node.children));
17
+ }
18
+ return found;
19
+ }
20
+ /**
21
+ * Sayfa ve layout yazma yollarının ORTAK kapısı: olay adı (PAGE-011 · LAY-004), widget düğümü
22
+ * (PAGE-012 · LAY-005), komponent adı (PAGE-009) ve iç içe repeater (PAGE-013) denetimleri —
23
+ * hepsi istek gitmeden 400.
24
+ *
25
+ * PAGE-009 ve PAGE-013'ün layout ayağı 2026-09-09'da eklendi: ikisi de yalnız sayfa yollarında
26
+ * koşuyordu, oysa layout `views`'ü aynı runtime'dan geçer — bozuk ad o layout'a bağlı HER
27
+ * sayfanın ifadelerini öldürür, iç içe repeater'ın geri yazma kırılması ise yapısaldır ve
28
+ * barındıranın sayfa mı layout mu olduğuna bakmaz.
29
+ */
30
+ export function assertScreenWriteGuards(kind, screen) {
31
+ const codes = GUARD_CODES[kind];
32
+ const badEvents = invalidScreenEventNames(screen.events, codes.events);
33
+ if (badEvents.length > 0)
34
+ throw new PlatformApiError(`Geçersiz ${codes.noun} olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
35
+ const badWidgets = invalidWidgetTypeNodes(screen.schema, codes.widgets);
36
+ if (badWidgets.length > 0)
37
+ throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
38
+ const badNames = invalidComponentNames(screen.schema);
39
+ if (badNames.length > 0) {
40
+ throw new PlatformApiError(`PAGE-009 — komponent adı JS tanımlayıcısı değil (istek gönderilmedi): ${badNames.join(' · ')}. `
41
+ + 'Runtime her komponent için `const <ad> = Components["<ad>"]` üretir; boşluklu ya da geçersiz bir ad '
42
+ + 'SAYFADAKİ TÜM ifadeleri "Missing initializer in const declaration" ile öldürür (layout adları her sayfaya sızar). '
43
+ + `name alanını elementId ile aynı yaz; okunabilir Türkçe başlık için props.title / props.text / props.label kullan (${codes.noun} ${codes.tree}).`, 400);
44
+ }
45
+ const nested = nestedRepeaterNodes(screen.schema);
46
+ if (nested.length > 0)
47
+ throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
48
+ }
49
+ /**
50
+ * Katalog kurallarını (blok sınırı · auto-height · boyut string'i) ağaca uygular ve
51
+ * `withNormalization` kuyruğuna giren üç notu döndürür. Boş/verilmemiş ağaçta katalog
52
+ * okunmaz — uygulanacak düğüm yoktur, notlar da boş çıkardı.
53
+ */
54
+ export async function screenCatalogNotes(platform, project, environment, applicationId, nodes) {
55
+ if (!Array.isArray(nodes) || nodes.length === 0)
56
+ return [];
57
+ const catalog = await applyCatalogLayoutRules(platform, project, environment, applicationId, nodes);
58
+ return [...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes)];
59
+ }
@@ -31,6 +31,33 @@ export function summarize(node, styleSource) {
31
31
  }
32
32
  return summary;
33
33
  }
34
+ /**
35
+ * Yazma yanıtındaki `appliedPatch` gövdesini iskelete indirger (B-55 · S-9).
36
+ *
37
+ * Alanın KANIT değeri path'indedir: "gerçekten yalnız o komponente mi dokunuldu" sorusunu
38
+ * JSON Pointer cevaplar (`page.md` Kural 2). `value` ise ajanın aynı turda KENDİ gönderdiği
39
+ * gövdenin yankısıdır — `add_component`'te eklenen alt ağacın tamamı, `replace_prop`'ta
40
+ * yazılan Prop'un tamamı (`AppliedJsonPatch(Op, Path, Value)`). 2026-09-06·2 koşusunda on
41
+ * dört yazma çağrısının yanıtı bağlamın en büyük tek kalemiydi.
42
+ *
43
+ * Kanıt kaybı yok: yamanın KAYDEDİLDİĞİNİ `appliedPatch` zaten söylemiyordu (G-23). Onu
44
+ * yamadan sonra okunan şemayla karşılaştıran `schemaOpDrops` söylüyor ve sonucu yanıtta
45
+ * `silentlyDropped` olarak duruyor. Tam gövde `view: 'full'` ile alınır.
46
+ */
47
+ export function outlineAppliedPatch(applied) {
48
+ if (!Array.isArray(applied))
49
+ return applied;
50
+ return applied.map(entry => {
51
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
52
+ return entry;
53
+ const record = entry;
54
+ const value = record.value;
55
+ const node = value && typeof value === 'object' && !Array.isArray(value) && typeof value.elementId === 'string'
56
+ ? summarize(value)
57
+ : undefined;
58
+ return { op: record.op, path: record.path, ...(node ? { component: node } : {}) };
59
+ });
60
+ }
34
61
  /** elementId ya da id ile ağaçta düğüm bulur. */
35
62
  export function findNode(nodes, target) {
36
63
  for (const node of nodes ?? []) {
@@ -77,6 +104,7 @@ export function pageUpdateBody(page, overrides = {}) {
77
104
  name: page.name,
78
105
  slug: page.slug,
79
106
  browserTitle: page.browserTitle ?? '',
107
+ browserTitleKey: page.browserTitleKey ?? '',
80
108
  schema: page.schema ?? [],
81
109
  events: asArray(page.events).filter(event => event?.value),
82
110
  middlewareIds: page.middlewareIds ?? [],
@@ -1,22 +1,38 @@
1
1
  import { guid, projectParam, environmentParam, readOnly, updates } 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 { readProjectLanguages, translationLanguageFindings } from '../knowledge/languages.js';
6
+ import { policyRules } from '../knowledge/rules.js';
4
7
  const translationKeyInput = z.object({
5
8
  key: z.string().min(1),
6
- values: z.record(z.string()).describe("Dil kodu (ISO 639-1) → çeviri metni; kodlar projenin languages listesinde (project_get/project_update) tanımlı olmalı"),
9
+ values: z.record(z.string().regex(/^[a-z]{2}$/, 'ISO 639-1 iki harfli küçük dil kodu (tr, en, de …); tr-TR gibi bölge ekli kod reddedilir'), z.string()).describe("Dil kodu (ISO 639-1, iki küçük harf) → çeviri metni. Platform kodu yalnız 184 kodluk ISO sözlüğüne göre doğrular (TR-001); PROJENİN languages listesine göre doğrulamaz — o bağı gateway kurar (TR-002)"),
7
10
  componentId: guid.optional().describe("Çeviriyi belirli bir komponente bağlar; o komponent silinince kaydı da silinir")
8
11
  });
12
+ /**
13
+ * Yazma öncesi projenin dil durumunu okur. Proje okunamazsa (uç düşer, yetki yoksa) kontrol
14
+ * ATLANIR ve karar platforma kalır — `project_create`'in slug ön aramasındaki (PRJ-001) davranışın
15
+ * aynısı. Uydurma bulgu üretilmez.
16
+ */
17
+ async function languageFindings(platform, project, keys) {
18
+ const slug = platform.resolveProject(project);
19
+ const codes = keys.flatMap(entry => Object.keys(entry.values ?? {}));
20
+ if (codes.length === 0)
21
+ return [];
22
+ const payload = await platform.org('GET', `/p/${slug}`).catch(() => undefined);
23
+ return translationLanguageFindings(readProjectLanguages(payload), codes, slug);
24
+ }
9
25
  export function registerTranslationTools(server, platform) {
10
26
  const config = platform.config;
11
27
  server.registerTool('translation_list', {
12
28
  title: 'Çevirileri listele',
13
- description: "Application'ın tüm çeviri kayıtlarını (dil × anahtar × değer) düz liste olarak döner. Proje dilleri/varsayılan dil için project_get; sayfada bir prop'u çeviriye bağlamak için platform_guide('translation').",
29
+ description: "Application'ın tüm çeviri kayıtlarını (dil × anahtar × değer) düz liste olarak döner. Proje dilleri/varsayılan dil için project_get (yanıtındaki localization bloğu runtime'ın bu çevirileri çözüp çözemeyeceğini söyler); sayfada bir prop'u çeviriye bağlamak için platform_guide('translation').",
14
30
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid },
15
31
  annotations: readOnly
16
32
  }, handle(config, ({ project, environment, applicationId }) => platform.tenant('GET', project, `/translations/${applicationId}`, { environment })));
17
33
  server.registerTool('translation_update', {
18
34
  title: 'Çevirileri güncelle',
19
- description: "Bir ya da daha çok çeviri anahtarını toplu ekler/günceller (keys) ve/veya siler (deletedKeys) — tek çağrıda ikisi de verilebilir. 'keys' üzerinden yeni bir anahtar da açılabilir (upsert). 'values'taki dil kodları projenin languages listesinde tanımlı olmalı, aksi hâlde platform reddeder.",
35
+ description: "Bir ya da daha çok çeviri anahtarını toplu ekler/günceller (keys) ve/veya siler (deletedKeys) — tek çağrıda ikisi de verilebilir. 'keys' üzerinden yeni bir anahtar da açılabilir (upsert; verilmeyen diller silinmez, korunur). DİKKAT: çeviri yazmak tek başına YETMEZ — runtime bir etiketi ancak projenin languages listesi ve defaultLanguage kuruluysa çözer, aksi hâlde ekrana ham anahtar basar ve yazma yine 200 döner (ölçüldü 2026-09-09, S-15). Gateway yazmadan önce projeyi okur ve bağ kopuksa TR-002 caution'ı düşer; dilleri project_update(languages, defaultLanguage) ile kurun. Backend ön kontrolü (TR-001, block): anahtarlar boş olamaz ve tekil, dil kodları ISO 639-1 listesinden, deletedKeys boş olamaz.",
20
36
  inputSchema: {
21
37
  project: projectParam,
22
38
  environment: environmentParam,
@@ -25,13 +41,13 @@ export function registerTranslationTools(server, platform) {
25
41
  deletedKeys: z.array(z.string().min(1)).optional().describe('Silinecek anahtar adları')
26
42
  },
27
43
  annotations: updates
28
- }, handle(config, ({ project, environment, applicationId, keys, deletedKeys }) => {
44
+ }, handle(config, async ({ project, environment, applicationId, keys, deletedKeys }) => {
29
45
  if (!keys?.length && !deletedKeys?.length) {
30
46
  throw new Error("'keys' ya da 'deletedKeys' parametrelerinden en az biri dolu olmalı.");
31
47
  }
32
- return platform.tenant('PUT', project, `/translations/${applicationId}`, {
33
- environment,
34
- body: { keys: keys ?? [], deletedKeys: deletedKeys ?? [] }
35
- });
48
+ const body = { keys: keys ?? [], deletedKeys: deletedKeys ?? [] };
49
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'translation', body, { operation: 'update' }));
50
+ const languages = await languageFindings(platform, project, keys ?? []);
51
+ return attachFindings(await platform.tenant('PUT', project, `/translations/${applicationId}`, { environment, body }), [...findings, ...languages]);
36
52
  }));
37
53
  }
@@ -3,6 +3,12 @@ import { guid, projectParam, environmentParam, definitionParam, pagingParams, re
3
3
  import { PlatformApiError } from '../platform/errors.js';
4
4
  import { handle } from '../platform/responses.js';
5
5
  import { z } from 'zod';
6
+ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
7
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
8
+ import { policyRules } from '../knowledge/rules.js';
9
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
10
+ const policyOverrideParam = z.boolean().optional()
11
+ .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
6
12
  export function registerViewTools(server, platform) {
7
13
  const config = platform.config;
8
14
  const normalizeSource = (definition) => {
@@ -19,29 +25,33 @@ export function registerViewTools(server, platform) {
19
25
  }, handle(config, ({ project, environment, ...query }) => platform.tenant('GET', project, '/views', { environment, query })));
20
26
  server.registerTool('view_get', {
21
27
  title: 'View getir',
22
- description: 'Tek view tanımını getirir; view_create için en iyi gövde şablonudur.',
28
+ description: "Tek view tanımını getirir; view_create için en iyi gövde şablonudur. Hatırlatma: view TAM METİN ARAMA indeksidir, agregasyon/raporlama aracı değildir — özet için query_create (09-02 M-3).",
23
29
  inputSchema: { project: projectParam, environment: environmentParam, id: guid },
24
30
  annotations: readOnly
25
31
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/views/${id}`, { environment })));
26
32
  server.registerTool('view_create', {
27
33
  title: 'View oluştur',
28
- description: "TAM METİN ARAMA indeksi tanımlar (definition = CreateViewCommand; gövde şeması ngapps://schemas/CreateViewCommand, analizör sözlüğü platform_guide('view')) — raporlama/agregasyon aracı DEĞİLDİR. Seçilen tablo/alanlar analizörden (tokenizer/dil) geçirilip slug adlı projeksiyon tablosuna indekslenir; tüketimi source:'view' microservice'idir ve o uç ZORUNLU 'searchText' parametresi ister (+limit/offset). Rapor/özet için raw query (query_create) + query kaynaklı microservice kullanın. Birden çok entity verilebilir (entities: [{entityId, alias}], fields alias'la bağlanır: viewEntityAlias) ama bu JOIN DEĞİLDİR: her tablo ayrı ayrı indekslenir, aralarında ilişki tanımı yoktur ve eşleşme hangi tablodan geldiyse dönen kayıt o tablonun satırıdır. İlişkili içeriği tek kayıtta aratmak için denormalize bir alan kullanın (yorum metnini talebe yazan automation + tek entity'li view).",
29
- inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
34
+ description: "TAM METİN ARAMA indeksi tanımlar (definition = CreateViewCommand; gövde şeması ngapps://schemas/CreateViewCommand, analizör sözlüğü platform_guide('view')) — raporlama/agregasyon aracı DEĞİLDİR. Seçilen tablo/alanlar analizörden (tokenizer/dil) geçirilip slug adlı projeksiyon tablosuna indekslenir; tüketimi source:'view' microservice'idir ve o uç ZORUNLU 'searchText' parametresi ister (+limit/offset). Rapor/özet için raw query (query_create) + query kaynaklı microservice kullanın. Birden çok entity verilebilir (entities: [{entityId, alias}], fields alias'la bağlanır: viewEntityAlias) ama bu JOIN DEĞİLDİR: her tablo ayrı ayrı indekslenir, aralarında ilişki tanımı yoktur ve eşleşme hangi tablodan geldiyse dönen kayıt o tablonun satırıdır. İlişkili içeriği tek kayıtta aratmak için denormalize bir alan kullanın (yorum metnini talebe yazan automation + tek entity'li view). " + constraintSummary('CreateViewCommand'),
35
+ inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam, policyOverride: policyOverrideParam },
30
36
  annotations: writes
31
- }, handle(config, async ({ project, environment, definition }) => {
37
+ }, handle(config, async ({ project, environment, definition, policyOverride }) => {
32
38
  const fixes = normalizeSource(definition);
39
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'view', definition, { operation: 'create' }, policyOverride));
40
+ const drift = unmappedMembers('CreateViewCommand', definition);
33
41
  const response = await platform.tenant('POST', project, '/views', { environment, body: definition });
34
- return withNormalization(response, viewAliasNote(fixes));
42
+ return attachFindings(withUnmappedMembers(withNormalization(response, viewAliasNote(fixes)), drift), findings);
35
43
  }));
36
44
  server.registerTool('view_update', {
37
45
  title: 'View güncelle',
38
- description: "View tanımını günceller (definition = UpdateViewCommand, şema ngapps://schemas/UpdateViewCommand); mevcut hâli view_get ile alın. UpdateViewCommand'da datasourceId YOKTUR (create'te bağlanır). DİKKAT: view_get alanları 'viewEntityId' ile döndürür, yazma tarafı 'viewEntityAlias' ister — okuduğunuzu birebir geri yazarsanız gateway id'yi alias'a çevirir, çeviremezse isteği reddeder.",
39
- inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam },
46
+ description: "View tanımını günceller (definition = UpdateViewCommand, şema ngapps://schemas/UpdateViewCommand); mevcut hâli view_get ile alın. UpdateViewCommand'da datasourceId YOKTUR (create'te bağlanır). DİKKAT: view_get alanları 'viewEntityId' ile döndürür, yazma tarafı 'viewEntityAlias' ister — okuduğunuzu birebir geri yazarsanız gateway id'yi alias'a çevirir, çeviremezse isteği reddeder. " + constraintSummary('UpdateViewCommand'),
47
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
40
48
  annotations: updates
41
- }, handle(config, async ({ project, environment, id, definition }) => {
49
+ }, handle(config, async ({ project, environment, id, definition, policyOverride }) => {
42
50
  const fixes = normalizeSource(definition);
51
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'view', definition, { operation: 'update' }, policyOverride));
52
+ const drift = unmappedMembers('UpdateViewCommand', definition);
43
53
  const response = await platform.tenant('PUT', project, `/views/${id}`, { environment, body: definition });
44
- return withNormalization(response, viewAliasNote(fixes));
54
+ return attachFindings(withUnmappedMembers(withNormalization(response, viewAliasNote(fixes)), drift), findings);
45
55
  }));
46
56
  server.registerTool('view_delete', {
47
57
  title: 'View sil',
@@ -51,7 +61,7 @@ export function registerViewTools(server, platform) {
51
61
  }, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/views/${id}`, { environment })));
52
62
  server.registerTool('view_rebuild', {
53
63
  title: 'View yeniden kur',
54
- description: 'View projeksiyon tablosunu yeniden inşa eder (veri değişikliklerinden sonra senkron için).',
64
+ description: "View projeksiyon tablosunu yeniden inşa eder (veri değişikliklerinden sonra senkron için). Rebuild ARAMA indeksini tazeler; sayım/özet üretmez — raporlama için query_create (09-02 M-3).",
55
65
  inputSchema: { project: projectParam, environment: environmentParam, id: guid },
56
66
  annotations: writes
57
67
  }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/views/${id}/rebuild`, { environment })));