@appaflytech/zeyrek-ai-mcp 0.0.7 → 0.0.9

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 (54) hide show
  1. package/README.md +27 -21
  2. package/dist/config.js +2 -0
  3. package/dist/knowledge/backendConstraints.js +3 -0
  4. package/dist/knowledge/componentCatalog.js +172 -33
  5. package/dist/knowledge/componentEvents.js +35 -4
  6. package/dist/knowledge/errorPath.js +1 -1
  7. package/dist/knowledge/normalize.js +474 -23
  8. package/dist/knowledge/propTypes.js +5 -4
  9. package/dist/knowledge/rules.js +113 -6
  10. package/dist/knowledge/styleResolve.js +52 -5
  11. package/dist/knowledge/themeResolve.js +936 -0
  12. package/dist/knowledge/themeRules.js +221 -0
  13. package/dist/knowledge/themeSchema.js +335 -0
  14. package/dist/platform/domSummary.js +144 -0
  15. package/dist/platform/http.js +10 -2
  16. package/dist/platform/screenshot.js +492 -29
  17. package/dist/platform/screenshotMemory.js +55 -0
  18. package/dist/platform/strictArguments.js +32 -0
  19. package/dist/platform/writeDiff.js +6 -3
  20. package/dist/resources/examples/layout-app-shell.json +1 -1
  21. package/dist/resources/examples/page-sign-in.json +1 -1
  22. package/dist/resources/examples/theme-brand.json +162 -0
  23. package/dist/resources/guides/component.md +8 -2
  24. package/dist/resources/guides/expressions.md +6 -0
  25. package/dist/resources/guides/getting-started.md +6 -0
  26. package/dist/resources/guides/layout.md +36 -26
  27. package/dist/resources/guides/microservice.md +160 -6
  28. package/dist/resources/guides/page.md +165 -59
  29. package/dist/resources/guides/theme.md +210 -0
  30. package/dist/resources/guides/translation.md +13 -10
  31. package/dist/resources/guides/workflow.md +141 -7
  32. package/dist/resources/schema-fingerprint.json +2 -2
  33. package/dist/resources/schemas/CreateMicroserviceCommand.json +4 -1
  34. package/dist/resources/schemas/CreateWorkflowCommand.json +4 -1
  35. package/dist/resources/schemas/PreviewMicroserviceCommand.json +4 -1
  36. package/dist/resources/schemas/PreviewWorkflowCommand.json +4 -1
  37. package/dist/resources/schemas/StepDto.json +4 -1
  38. package/dist/resources/schemas/UpdateMicroserviceCommand.json +4 -1
  39. package/dist/resources/schemas/UpdateWorkflowCommand.json +4 -1
  40. package/dist/server.js +52 -42
  41. package/dist/tools/components.js +8 -0
  42. package/dist/tools/designSchemaPatch.js +152 -0
  43. package/dist/tools/flows.js +24 -21
  44. package/dist/tools/layouts.js +8 -8
  45. package/dist/tools/microservices.js +74 -10
  46. package/dist/tools/pages.js +275 -332
  47. package/dist/tools/projects.js +66 -9
  48. package/dist/tools/queries.js +72 -6
  49. package/dist/tools/schemaOps.js +280 -0
  50. package/dist/tools/screenWrite.js +2 -1
  51. package/dist/tools/screens.js +0 -7
  52. package/dist/tools/themes.js +163 -0
  53. package/dist/tools/workflows.js +10 -6
  54. package/package.json +1 -1
@@ -8,6 +8,7 @@ import { z } from 'zod';
8
8
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
9
9
  import { policyRules } from '../knowledge/rules.js';
10
10
  import { constraintSummary } from '../knowledge/backendConstraints.js';
11
+ import { readProjectTheme } from '../knowledge/themeSchema.js';
11
12
  import { readProjectLanguages, runtimeResolvesTranslations, resolveSeedLanguages, SEED_LANGUAGES, SEED_DEFAULT_LANGUAGE } from '../knowledge/languages.js';
12
13
  const languageCode = z.string().regex(/^[a-z]{2}$/, 'ISO 639-1 iki harfli küçük dil kodu (tr, en, de …)');
13
14
  function localizationFix(state) {
@@ -80,6 +81,45 @@ const templateOptionsInput = z.object({
80
81
  templateId: guid,
81
82
  templateReceiveUpdates: z.boolean().optional()
82
83
  });
84
+ const themeInput = z.union([z.string(), z.record(z.unknown())]);
85
+ /**
86
+ * `Theme` form alanının değeri. Platform alanı JSON METNİ olarak alıyor
87
+ * (`UpdateOrganizationProjectCommand.Theme` bir `string`), ama ajan doğal olarak nesne yazıyor:
88
+ * nesne gateway'de serileştirilir. Metin verildiyse JSON olduğu İSTEK GİTMEDEN denetlenir —
89
+ * geçersiz metin platformda `ToJsonDocument()` sırasında patlar ve mesaj hangi alanın bozuk
90
+ * olduğunu söylemez.
91
+ */
92
+ export function themeField(theme, tool) {
93
+ if (theme === undefined)
94
+ return undefined;
95
+ if (typeof theme !== 'string')
96
+ return JSON.stringify(theme);
97
+ try {
98
+ JSON.parse(theme);
99
+ }
100
+ catch (error) {
101
+ throw new PlatformApiError(`${tool}: 'theme' metni JSON olarak çözülemedi (${error.message}). Temayı nesne olarak verin — gateway serileştirir.`, 400);
102
+ }
103
+ return theme;
104
+ }
105
+ /**
106
+ * `PUT /projects/{id}` gövdesinin ortak üçlüsü.
107
+ *
108
+ * Uç bir REPLACE'tir ve eksik alan "koru" değil **SİL** demektir: `OrganizationProject.Override`
109
+ * `Theme = theme;` satırını koşulsuz çalıştırır (`OrganizationProject.cs:103`), `Updated` olayı da
110
+ * sonucu her aktif ortamın tenant kaydına kopyalar
111
+ * (`UpdateProjectWhenOrganizationProjectUpdatedEventHandler.cs:36`). Yani yalnız dil ayarını
112
+ * değiştiren bir çağrı markanın tamamını düşürüyordu ve yanıt 200 dönüyordu (S-1). Gövdeyi kuran
113
+ * tek yer burasıdır ki aynı uca yazan her tool aynı korumayı paylaşsın.
114
+ */
115
+ export function projectUpdateForm(fields) {
116
+ const form = new URLSearchParams({ Name: fields.name });
117
+ if (fields.slug)
118
+ form.set('Slug', fields.slug);
119
+ if (fields.theme !== undefined)
120
+ form.set('Theme', fields.theme);
121
+ return form;
122
+ }
83
123
  export function registerProjectTools(server, platform) {
84
124
  const config = platform.config;
85
125
  server.registerTool('project_list', {
@@ -105,7 +145,7 @@ export function registerProjectTools(server, platform) {
105
145
  inputSchema: {
106
146
  name: z.string().min(1),
107
147
  slug: z.string().min(1).describe('Organizasyonda tekil; veritabanı adı zeyrek_<org>_<slug> bundan türer ve sonradan değiştirmek bütün runtime rotalarını kırar'),
108
- theme: z.string().optional().describe('Tema JSON\'u string olarak'),
148
+ theme: themeInput.optional().describe("Tema nesnesi (ya da JSON metni) — Studio tema panelinin yazdığı kayıtla aynı şekil: mode · palette · paletteDark · fontFamily · typeScale · shadow · presets · componentPresets. Verilmezse proje platformun varsayılan temasıyla (accent #0052CC, Inter 14px) doğar. Kurulmuş bir projenin temasını DEĞİŞTİRMEK için theme_update: kısmi günceller, şema/lint'ten geçirir ve hangi CSS değişkeninin değiştiğini söyler"),
109
149
  languages: z.array(z.string().min(1)).min(1).optional().describe(`Proje dilleri (ISO 639-1; 'tr-TR' gibi bölgeli değer kabul edilir ve 'tr'ye indirgenir, indirgeme gatewayNormalized ile bildirilir). Verilmezse gateway [${SEED_LANGUAGES.join(', ')}] tohumlar — projenin dilsiz doğmasına ve çevirilerin sessizce çözülmemesine izin verilmez`),
110
150
  defaultLanguage: z.string().min(1).optional().describe(`languages içinde olmalı (indirgenmiş hâliyle karşılaştırılır); yalnız defaultLanguage verirseniz languages onunla kurulur. İkisi de yoksa '${SEED_DEFAULT_LANGUAGE}'`),
111
151
  application: applicationInput.optional(),
@@ -130,8 +170,9 @@ export function registerProjectTools(server, platform) {
130
170
  }
131
171
  const form = new URLSearchParams({ Name: args.name, Slug: args.slug });
132
172
  const notes = [];
133
- if (args.theme)
134
- form.set('Theme', args.theme);
173
+ const theme = themeField(args.theme, 'project_create');
174
+ if (theme !== undefined)
175
+ form.set('Theme', theme);
135
176
  if (args.application) {
136
177
  form.set('Application.Slug', args.application.slug);
137
178
  form.set('Application.Name', args.application.name);
@@ -171,13 +212,15 @@ export function registerProjectTools(server, platform) {
171
212
  id: guid,
172
213
  name: z.string().min(1).optional().describe('Verilmezse mevcut ad korunur — yalnız dil ayarını değiştirmek için adı tekrar göndermeniz gerekmez'),
173
214
  slug: z.string().optional(),
174
- theme: z.string().optional(),
215
+ theme: themeInput.optional().describe("Tema nesnesi (ya da JSON metni); verilen kayıt mevcut temanın YERİNE geçer — bir alanı değiştirmek için theme_update kullanın (kısmi birleştirir, lint'ten geçirir). VERİLMEZSE mevcut tema korunur — gateway projeden okuyup gövdeye geri koyar, çünkü platform Theme alanı gövdede yokken temayı siler ve silinen tema bütün ortamların runtime'ına iner (S-1). Temayı gerçekten temizlemek için theme: {} verin"),
175
216
  languages: z.array(languageCode).min(1).optional().describe("ISO 639-1 iki harfli kod listesi (proje dilleri, platformun 184 kodluk sözlüğünden — PRJ-002). Runtime çevirileri YALNIZ bu kodlarla çözer; liste kurulmadan yazılan çeviriler 200 döner ama ekrana ham anahtar basılır (S-15). Yeni proje bu liste BOŞ doğar — çeviri kullanacaksanız burayı kurmak ilk adımdır"),
176
217
  defaultLanguage: languageCode.optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir. Runtime kayıtlı dil seçimi yokken buna düşer; boşsa aktif dil hiç seçilmez ve ilk açılışta bütün etiketler ham anahtar çizilir")
177
218
  },
178
219
  annotations: updates
179
220
  }, handle(config, async (args) => {
180
221
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'update' }));
222
+ // Çağıranın hatası okumadan ÖNCE döner: bozuk tema metni için projeyi çekmenin anlamı yok.
223
+ const requestedTheme = themeField(args.theme, 'project_update');
181
224
  // Platform gövdede slug yoksa onu ADDAN yeniden üretiyor. Yalnız dil ayarını değiştiren bir
182
225
  // çağrı bu yüzden 'test_dev' slug'ını 'mcp-benchmark' yapmıştı ve o an bütün runtime URL'leri
183
226
  // kırılmıştı (09-05 Corecruiter koşusu, S-11). Gateway mevcut değerleri okuyup gövdeye koyar;
@@ -185,16 +228,15 @@ export function registerProjectTools(server, platform) {
185
228
  const current = ((await platform.org('GET', `/projects/${args.id}`))?.data ?? {});
186
229
  const currentSlug = typeof current.slug === 'string' ? current.slug : undefined;
187
230
  const currentName = typeof current.name === 'string' ? current.name : undefined;
231
+ // Tema da aynı korumaya muhtaç ve aynı okumadan gelir — ek istek yok (S-1, projectUpdateForm).
232
+ const currentTheme = current.theme !== null && typeof current.theme === 'object' ? current.theme : undefined;
188
233
  const name = args.name ?? currentName;
189
234
  const slug = args.slug ?? currentSlug;
190
235
  if (name === undefined) {
191
236
  throw new PlatformApiError(`'${args.id}' projesinin adı okunamadı; name alanını açıkça verin (platform bu uçta adı zorunlu tutuyor).`, 400);
192
237
  }
193
- const form = new URLSearchParams({ Name: name });
194
- if (slug)
195
- form.set('Slug', slug);
196
- if (args.theme)
197
- form.set('Theme', args.theme);
238
+ const theme = requestedTheme ?? (currentTheme === undefined ? undefined : JSON.stringify(currentTheme));
239
+ const form = projectUpdateForm({ name, slug, theme });
198
240
  const languages = args.languages ? normalizeLanguageCodes(args.languages, 'languages') : undefined;
199
241
  const defaultLanguage = args.defaultLanguage ? normalizeLanguageCode(args.defaultLanguage, 'defaultLanguage') : undefined;
200
242
  languages?.codes.forEach(code => form.append('Languages', code));
@@ -212,6 +254,9 @@ export function registerProjectTools(server, platform) {
212
254
  : []),
213
255
  ...(args.name === undefined && currentName !== undefined
214
256
  ? [`project_update gövdesine mevcut ad ('${currentName}') gateway tarafından kondu (name verilmedi).`]
257
+ : []),
258
+ ...(args.theme === undefined && currentTheme !== undefined
259
+ ? ["project_update gövdesine mevcut TEMA gateway tarafından kondu: platform Theme alanı gövdede yokken temayı SİLİYOR (Override koşulsuz yazar) ve silinen tema bütün ortamların runtime'ına iner — yalnız dil ayarını değiştiren bir çağrı markanın tamamını düşürüyordu (S-1). Temayı gerçekten temizlemek istiyorsanız theme: {} verin."]
215
260
  : [])
216
261
  ];
217
262
  return attachFindings(withNormalization(payload, notes), findings);
@@ -278,9 +323,20 @@ export function registerProjectTools(server, platform) {
278
323
  // Dil durumu haritanın parçası: `pick` null/undefined alanları eler, yani dilsiz projede
279
324
  // languages haritadan da sessizce düşerdi — eksikliği görünmez kılan tam o davranış (`S-15`).
280
325
  const languageState = readProjectLanguages(projectPayload);
326
+ const themeSummary = readProjectTheme(projectPayload);
281
327
  return {
282
328
  project: pick(projectData, ['id', 'slug', 'name', 'languages', 'defaultLanguage']),
283
329
  ...(languageState ? { localization: localizationBlock(languageState) } : {}),
330
+ // Tema haritanın parçası: projenin markası olup olmadığı, sayfa kurmadan önce bilinmesi
331
+ // gereken bir şeydir ve `pick` onu göstermezdi.
332
+ theme: themeSummary === undefined
333
+ ? { hasTheme: false }
334
+ : {
335
+ hasTheme: true,
336
+ mode: themeSummary.mode === 'dark' ? 'dark' : 'light',
337
+ presets: Object.keys(themeSummary.presets ?? {}),
338
+ componentPresets: Object.keys(themeSummary.componentPresets ?? {})
339
+ },
284
340
  environments: (Array.isArray(projectData.environments) ? projectData.environments : []).map(entry => pick(entry, ['id', 'name'])),
285
341
  environment: platform.resolveEnvironment(environment),
286
342
  applications,
@@ -300,6 +356,7 @@ export function registerProjectTools(server, platform) {
300
356
  ...(languageState && !runtimeResolvesTranslations(languageState)
301
357
  ? [`DİKKAT: projenin dil ayarı eksik (localization bloğuna bakın) — çeviri yazsanız bile runtime her etiketi ham anahtar olarak basar. Etiketleri translation_* üzerinden verecekseniz ÖNCE ${localizationFix(languageState)}.`]
302
358
  : []),
359
+ "Tema/preset'ler: theme_get (view:'resolved' ekrana inen --ng-* değerlerini verir).",
303
360
  "Detay: entity_get/microservice_get/workflow_get (büyük kayıtlarda view:'outline'); değişiklik: *_patch. Automation'lar entity başına: automation_list(entityId) — ağaçtaki automationCount yalnız platform sayıyorsa görünür."
304
361
  ].join(' ')
305
362
  };
@@ -6,10 +6,57 @@ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.j
6
6
  import { handle } from '../platform/responses.js';
7
7
  import { z } from 'zod';
8
8
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
9
- import { policyRules } from '../knowledge/rules.js';
9
+ import { policyRules, rawQueryParameterNames } from '../knowledge/rules.js';
10
+ import { PlatformApiError } from '../platform/errors.js';
10
11
  import { constraintSummary } from '../knowledge/backendConstraints.js';
11
12
  const policyOverrideParam = z.boolean().optional()
12
13
  .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
14
+ function inferredParameterType(value) {
15
+ if (typeof value === 'number')
16
+ return Number.isInteger(value) ? 'number' : 'float';
17
+ if (typeof value === 'boolean')
18
+ return 'bool';
19
+ if (Array.isArray(value))
20
+ return 'array';
21
+ if (value !== null && typeof value === 'object')
22
+ return 'object';
23
+ return 'text';
24
+ }
25
+ /**
26
+ * G-13 — `PreviewQueryCommand` değeri yalnız `parameters[].value`'dan okur; üst düzey `parameters`
27
+ * argümanı şemada yokken SDK tarafından hata vermeden atılıyordu ve sorgu NULL'la koşuyordu.
28
+ * Argüman artık komutun kendi alanına bağlanır.
29
+ */
30
+ function bindPreviewParameters(definition, parameters, notes) {
31
+ const names = Object.keys(parameters ?? {});
32
+ if (parameters === undefined || names.length === 0)
33
+ return definition;
34
+ const rows = (Array.isArray(definition.parameters) ? definition.parameters : []);
35
+ const conflicts = names.filter(name => {
36
+ const row = rows.find(entry => entry?.name === name);
37
+ return row !== undefined && row.value !== undefined && JSON.stringify(row.value) !== JSON.stringify(parameters[name]);
38
+ });
39
+ if (conflicts.length > 0) {
40
+ throw new PlatformApiError(`parameters ile definition.parameters aynı adı farklı değerle taşıyor: ${conflicts.map(name => `'${name}'`).join(', ')}. Değeri tek yerde verin.`, 400);
41
+ }
42
+ const bound = rows.map(row => typeof row?.name === 'string' && row.name in parameters ? { ...row, value: parameters[row.name] } : row);
43
+ const added = names.filter(name => !rows.some(row => row?.name === name));
44
+ notes.push(`parameters definition.parameters[].value'ya bağlandı: ${names.join(', ')}${added.length > 0 ? ` (tipi değerden çıkarılıp eklenen satır: ${added.join(', ')})` : ''}`);
45
+ return {
46
+ ...definition,
47
+ parameters: [...bound, ...added.map(name => ({ name, type: inferredParameterType(parameters[name]), value: parameters[name] }))]
48
+ };
49
+ }
50
+ /** `language` sayılmaz: platform `{{language}}` kullanan sorguya değeri kendisi ekler (`PreviewQueryCommand.cs`). */
51
+ function unboundPreviewParameters(definition) {
52
+ const rows = (Array.isArray(definition.parameters) ? definition.parameters : []);
53
+ return rawQueryParameterNames(definition.rawQuery)
54
+ .filter(name => name.toLowerCase() !== 'language')
55
+ .filter(name => {
56
+ const value = rows.find(row => row?.name === name)?.value;
57
+ return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');
58
+ });
59
+ }
13
60
  export function registerQueryTools(server, platform) {
14
61
  const config = platform.config;
15
62
  server.registerTool('query_list', {
@@ -26,7 +73,7 @@ export function registerQueryTools(server, platform) {
26
73
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/queries/${id}`, { environment })));
27
74
  server.registerTool('query_create', {
28
75
  title: 'Query oluştur',
29
- description: "Yeni query tanımlar (ham SQL). definition = CreateQueryCommand gövdesi. AGREGASYON VE RAPORLAMANIN YOLU BUDUR (SUM/COUNT/AVG/GROUP BY, dönemsel özet, KPI) — view_* bir TAM METİN ARAMA indeksidir, özet üretmez (09-02 M-3). VAR OLAN SATIRLARDAN TÜRETİLEN SATIR YAZMANIN DA YOLU BUDUR: 'INSERT … SELECT' kaydedip query kaynaklı bir uçtan çalıştırmak, geçmiş/hareket/junction tablosunu TEK çağrıda doldurur ve hiçbir id elle taşınmaz (ölçüldü 2026-09-07; reçete platform_guide('entity') §'Toplu ve türetilmiş satır'). Kayıt sırasında gövde bir kez ÇALIŞTIRILIR ama geri alınır — yazan sorguyu kaydetmek satır bırakmaz ve 'returnScheme' boş döner. Kayıtlı query'nin parametreleri parameterScheme ile tanımlanır ve runtime'da bağlanır; tüketimi source:'query' microservice'idir. PARAMETRENİN DEĞERİ HER ZAMAN ÇAĞRIDAN GELİR (body → query-string → header): parametreye ifade kaynağı verilemez (parameters[].source komut şemasında yoktur, platform sessizce atar) ve middleware'in kurduğu user bu yola girmez. Kimlik/tenant kapsamı sunucu tarafında zorlanacaksa sorguyu bir workflow query adımından çağırın — reçete platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'. BOŞ STRING parametre NULL'a çevrilir: '= @p' koşulu hiçbir satırla eşleşmez ve sorgu sessizce 0 satır döner — 'filtre yok' için (@p IS NULL OR ...) yazın ya da sentinel kullanın. Yorum içindeki belirteç parametre sayılmaz. SQL çalıştırılamazsa kayıt YİNE de oluşur: returnScheme boş kalır ve yanıttaki message nedeni söyler — SQL'i düzeltip query_update ile kaydedin. SORGUNUN KENDİ AÇIKLAMASI VAR: `description` alanını doldurun — sorgu listesinde ad ve slug dışında ipucu yoktur ve ucun açıklaması (MS-014) bunun yerine geçmez, çünkü bir sorgu birden çok uçtan tüketilir; boş bırakılırsa gateway `QRY-006` uyarısı düşürür (yazmayı durdurmaz). " + constraintSummary('CreateQueryCommand'),
76
+ description: "Yeni query tanımlar (ham SQL). definition = CreateQueryCommand gövdesi. AGREGASYON VE RAPORLAMANIN YOLU BUDUR (SUM/COUNT/AVG/GROUP BY, dönemsel özet, KPI) — view_* bir TAM METİN ARAMA indeksidir, özet üretmez (09-02 M-3). VAR OLAN SATIRLARDAN TÜRETİLEN SATIR YAZMANIN DA YOLU BUDUR: 'INSERT … SELECT' kaydedip query kaynaklı bir uçtan çalıştırmak, geçmiş/hareket/junction tablosunu TEK çağrıda doldurur ve hiçbir id elle taşınmaz (ölçüldü 2026-09-07; reçete platform_guide('entity') §'Toplu ve türetilmiş satır'). Kayıt sırasında gövde bir kez ÇALIŞTIRILIR ama geri alınır — yazan sorguyu kaydetmek satır bırakmaz ve 'returnScheme' boş döner. Kayıtlı query'nin parametreleri parameterScheme ile tanımlanır ve runtime'da bağlanır; tüketimi source:'query' microservice'idir. PARAMETRENİN DEĞERİ HER ZAMAN ÇAĞRIDAN GELİR (body → query-string → header): parametreye ifade kaynağı verilemez (parameters[].source komut şemasında yoktur, platform sessizce atar) ve middleware'in kurduğu user bu yola girmez. Kimlik/tenant kapsamı sunucu tarafında zorlanacaksa sorguyu bir workflow query adımından çağırın — reçete platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'. BOŞ STRING parametre NULL'a çevrilir: '= @p' koşulu hiçbir satırla eşleşmez ve sorgu sessizce 0 satır döner — 'filtre yok' için (@p IS NULL OR ...) yazın ya da sentinel kullanın; (@p = '' OR ...) kalıbı hiçbir zaman eşleşmez ve gateway `QRY-007` uyarısı düşürür. Yorum içindeki belirteç parametre sayılmaz. SQL çalıştırılamazsa kayıt YİNE de oluşur: returnScheme boş kalır ve yanıttaki message nedeni söyler — SQL'i düzeltip query_update ile kaydedin. SORGUNUN KENDİ AÇIKLAMASI VAR: `description` alanını doldurun — sorgu listesinde ad ve slug dışında ipucu yoktur ve ucun açıklaması (MS-014) bunun yerine geçmez, çünkü bir sorgu birden çok uçtan tüketilir; boş bırakılırsa gateway `QRY-006` uyarısı düşürür (yazmayı durdurmaz). " + constraintSummary('CreateQueryCommand'),
30
77
  inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam, policyOverride: policyOverrideParam },
31
78
  annotations: writes
32
79
  }, handle(config, async ({ project, environment, definition, policyOverride }) => {
@@ -59,12 +106,31 @@ export function registerQueryTools(server, platform) {
59
106
  server.registerTool('query_preview', {
60
107
  title: 'Query önizle',
61
108
  description: "Kaydedilmemiş query tanımını datasource üzerinde çalıştırır. definition = PreviewQueryCommand gövdesi. Preview ROLLBACK edilir: yazan bir sorgu (INSERT/UPDATE/DELETE) çalışır, affectedRowCount gerçek sayıyı döner ama SATIR KALMAZ (ölçüldü, 09-04 Y-19) — yazmayı doğrulamak için sorguyu kaydedip microservice/workflow üzerinden çalıştırın. YANITTA ROLLBACK İŞARETİ YOKTUR: 2026-09-07'de ölçüldü, yazan sorgunun yanıtı yalnız { affectedRowCount, elapsedMilliseconds } taşıyor — sayı başarı gibi görünür, tek kanıt sonradan row_list çekmektir. BOŞ STRING parametre NULL'a çevrilir (üç koşuda ölçüldü): '= @p' hiçbir satırla eşleşmez, 0 satır döner ve hata yoktur. Davranış kuralları: platform_guide('microservice'). " + constraintSummary('PreviewQueryCommand'),
62
- inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, definition: definitionParam, language: languageParam, policyOverride: policyOverrideParam },
109
+ inputSchema: {
110
+ project: projectParam,
111
+ environment: environmentParam,
112
+ datasourceId: guid,
113
+ definition: definitionParam,
114
+ parameters: z.record(z.unknown()).optional()
115
+ .describe("Önizleme değerleri (ad → değer). definition.parameters[] içindeki aynı adlı satırın value'suna yazılır; satır yoksa tipi değerden çıkarılarak eklenir (tam sayı → number, ondalık → float, true/false → bool, diğerleri → text). Aynı ad definition.parameters'ta FARKLI bir value ile de varsa çağrı gönderilmez."),
116
+ language: languageParam,
117
+ policyOverride: policyOverrideParam
118
+ },
63
119
  annotations: writes
64
- }, handle(config, async ({ project, environment, datasourceId, definition, language, policyOverride }) => {
65
- const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'query', definition, { operation: 'preview' }, policyOverride));
120
+ }, handle(config, async ({ project, environment, datasourceId, definition: given, parameters, language, policyOverride }) => {
66
121
  const notes = [];
122
+ const definition = bindPreviewParameters(given, parameters, notes);
123
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'query', definition, { operation: 'preview' }, policyOverride));
67
124
  const headers = languageHeader(language, notes);
68
- return attachFindings(withNormalization(await platform.tenant('POST', project, `/queries/${datasourceId}/preview`, { environment, body: definition, headers }), notes), findings);
125
+ const payload = await platform.tenant('POST', project, `/queries/${datasourceId}/preview`, { environment, body: definition, headers });
126
+ const unbound = unboundPreviewParameters(definition);
127
+ const withUnbound = unbound.length > 0 && payload && typeof payload === 'object' && !Array.isArray(payload)
128
+ ? {
129
+ ...payload,
130
+ unboundParameters: unbound,
131
+ unboundParametersNote: "rawQuery bu parametrelere başvuruyor ama değerleri yok ya da boş string (platform boş string'i NULL'a çevirir) — sorgu onları NULL ile koştu. Değeri üst düzey 'parameters' ya da definition.parameters[].value ile verin."
132
+ }
133
+ : payload;
134
+ return attachFindings(withNormalization(withUnbound, notes), findings);
69
135
  }));
70
136
  }
@@ -0,0 +1,280 @@
1
+ import { isFlexContainer, schemaOpValidationErrors, FLEX_LAYOUT_PROP_NAMES, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
2
+ import { findNode, outlineAppliedPatch } from './screens.js';
3
+ import { setPropTypeErrors } from '../knowledge/propTypes.js';
4
+ import { PlatformApiError } from '../platform/errors.js';
5
+ import { z } from 'zod';
6
+ /*
7
+ * Şema micro-op'larının gateway tarafı: sayfa (`page_schema_patch`), layout (`layout_schema_patch`)
8
+ * ve widget (`component_schema_patch`) aynı sunucu sözlüğünü (`PageSchemaMicroOpApplier`) konuşur,
9
+ * bu yüzden op planlama ve istek öncesi reddetme tek yerde durur.
10
+ */
11
+ /**
12
+ * Yazma yanıtındaki şema gövdesinin biçimi (B-55 · S-9). Varsayılan `outline`: gövde ajanın
13
+ * aynı turda kendi gönderdiği içeriğin yankısıdır, kanıt ise pointer'da ve `silentlyDropped`ta
14
+ * durur. Bayrağı bilmeyen ajan pahalı olanı ödemesin diye kırpma OPT-OUT'tur (aynı karar:
15
+ * `DS-004`, `datasource_configuration_options`).
16
+ */
17
+ export const writeViewParam = z.enum(['outline', 'full']).optional().describe("Yanıttaki şema gövdesinin biçimi. outline (VARSAYILAN): komponent ağaçları iskelete iner (id/elementId/name/type/children), appliedPatch her op'un op+path'ini verir — 'yalnız o komponente mi dokunuldu' kanıtı JSON Pointer'dadır, 'kaydedildi mi' kanıtı silentlyDropped'tadır. full: platformun döndürdüğü gövde aynen gelir.");
18
+ /**
19
+ * `appliedPatch` zarfın altında da gelebilir (`{ data: { appliedPatch } }`), düz de.
20
+ */
21
+ export function outlineWriteResponse(response) {
22
+ const inner = response.data;
23
+ const container = (inner && typeof inner === 'object' && !Array.isArray(inner) ? inner : response);
24
+ if (Array.isArray(container.appliedPatch))
25
+ container.appliedPatch = outlineAppliedPatch(container.appliedPatch);
26
+ }
27
+ export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue} | remove_event {target, event} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
28
+ /**
29
+ * PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
30
+ * komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
31
+ * tipini ve o tipin katalogdaki olay listesini pozitif olarak çözebildiğinde konuşur (okunan ağaçta
32
+ * olmayan hedefler — sayfa yamasında layout düğümleri — ve katalogda olay tanımlamayan tipler
33
+ * doğrulanmadan geçer).
34
+ */
35
+ export function unknownEventOps(ops, typesByElementId, eventsBySlug) {
36
+ return ops
37
+ .map(op => op)
38
+ .filter(op => op.op === 'set_event')
39
+ .flatMap(op => {
40
+ const target = String(op.target ?? '');
41
+ const event = String(op.event ?? '');
42
+ const type = typesByElementId.get(target);
43
+ const declared = type ? eventsBySlug.get(type) : undefined;
44
+ if (!event || !declared || declared.includes(event))
45
+ return [];
46
+ return [`'${target}' (${type}) komponenti '${event}' diye bir olay YAYMAZ. Yaydıkları: ${declared.join(', ')}. Platform bu op'u 200 ile kabul eder ama bağ sessizce ölü kalır — akış hiçbir zaman çalışmaz. Doğru adı component_get(slug:'${type}') çıktısındaki events listesinden alın.`];
47
+ });
48
+ }
49
+ /**
50
+ * Ön kontroller eskiden İLK hatada fırlatıyordu: dört op'u yanlış olan bir yama dört ayrı turda
51
+ * ancak görülüyordu ve her tur tüm planı yeniden gönderiyordu (S-11, 2026-09-15). Artık op başına
52
+ * toplanır ve tek yanıtta döner. Yama yine ATOMİKTİR — birbirine bağlı op'lar (add_component +
53
+ * set_event) yarım kalmasın diye geçenler de gönderilmez.
54
+ */
55
+ export function rejectedOpsMessage(unordered) {
56
+ const rejected = [...unordered].sort((left, right) => left.index - right.index);
57
+ const lines = rejected.map(entry => `#${entry.index} ${entry.op}${entry.target ? ` '${entry.target}'` : ''} [${entry.code}]: ${entry.message}`);
58
+ const validationHint = rejected.some(entry => entry.code === 'PAGE-016')
59
+ ? " PAGE-016 çalışan biçimi dört alanı da taşır: { type: 'required'|'email'|'regex'|'max-length'|'min-length'|'max-value'|'min-value'|'value-options', message: '<kullanıcıya görünen metin>', value: <eşik/desen/true>, isActive: true }."
60
+ : '';
61
+ return `Ön kontrol ${rejected.length} op'u reddetti; yama atomik olduğu için HİÇBİR op gönderilmedi (istek gönderilmedi). Düşen op'ları düzeltip TÜM listeyi tek çağrıda yeniden gönderin.${validationHint}\n${lines.join('\n')}\nrejectedOps: ${JSON.stringify(rejected)}`;
62
+ }
63
+ export function collectTypes(nodes, into) {
64
+ for (const node of nodes ?? []) {
65
+ if (typeof node.elementId === 'string' && typeof node.type === 'string')
66
+ into.set(node.elementId, node.type);
67
+ collectTypes(node.children, into);
68
+ }
69
+ }
70
+ export function removeNodeFromTree(nodes, target, parent) {
71
+ for (let index = 0; index < nodes.length; index++) {
72
+ if (nodes[index].elementId === target)
73
+ return { node: nodes.splice(index, 1)[0], parent };
74
+ const found = removeNodeFromTree(nodes[index].children ?? [], target, nodes[index]);
75
+ if (found)
76
+ return found;
77
+ }
78
+ return undefined;
79
+ }
80
+ function setDesktopPosition(node, position) {
81
+ const blocks = Array.isArray(node.blocks) ? node.blocks : [];
82
+ const block = blocks.find(entry => Number(entry?.breakpoint) === 1024);
83
+ if (block)
84
+ block.position = { x: Number(position.x ?? 0), y: Number(position.y ?? 0) };
85
+ }
86
+ /**
87
+ * PAGE-010 boşluk analizi patch'in SONUCUNU görmek zorunda: tek başına op'lardaki y değerleri,
88
+ * kapsayıcının mevcut çocukları bilinmeden anlamsız. Op'lar okunan şemanın kopyasına uygulanır
89
+ * (add: parent'ın children'ına ekle — kökler retargetRootAdds ile page_container'a gittiği için
90
+ * burada da öyle çözülür; move: düğümü taşı — parent verilmemişse aynı ebeveynde kalır — ve verilen
91
+ * position'ı desktop block'una yaz; ikisinde de index platformdaki gibi diziye yerleşir) ve analiz
92
+ * yalnız dokunulan kapsayıcılarda koşar.
93
+ */
94
+ export function simulateLayoutOps(schema, ops) {
95
+ const touched = new Set();
96
+ const attach = (node, requestedParent, index) => {
97
+ const parentId = typeof requestedParent === 'string' && requestedParent !== '' && requestedParent !== 'root'
98
+ ? requestedParent
99
+ : PAGE_CONTAINER_ELEMENT_ID;
100
+ const parent = findNode(schema, parentId);
101
+ const siblings = parent ? [...(parent.children ?? [])] : schema;
102
+ const insertAt = typeof index === 'number' && Number.isFinite(index) ? Math.max(0, Math.min(index, siblings.length)) : siblings.length;
103
+ siblings.splice(insertAt, 0, node);
104
+ if (parent) {
105
+ parent.children = siblings;
106
+ touched.add(String(parent.elementId ?? parentId));
107
+ }
108
+ else {
109
+ touched.add('kök');
110
+ }
111
+ };
112
+ for (const op of ops) {
113
+ if (op.op === 'add_component' && op.component && typeof op.component === 'object') {
114
+ attach(JSON.parse(JSON.stringify(op.component)), op.parent, op.index);
115
+ }
116
+ // Düzen prop'ları (layout/flex grubu) simülasyona girer: PAGE-010 flex kapsayıcının içini
117
+ // analiz etmez, PAGE-015 ise op'tan SONRAKİ layout değerine bakar — ikisi de op uygulanmadan
118
+ // doğru karar veremez.
119
+ if (op.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(op.prop ?? ''))) {
120
+ const target = findNode(schema, String(op.target ?? ''));
121
+ const codeWrapper = op.value && typeof op.value === 'object' && op.value.type === 'code';
122
+ if (target)
123
+ target.props = { ...(target.props ?? {}), [String(op.prop)]: codeWrapper ? op.value : { type: 'static', ref: null, data: op.value } };
124
+ }
125
+ if (op.op === 'move_component') {
126
+ const moved = removeNodeFromTree(schema, String(op.target ?? ''));
127
+ if (!moved)
128
+ continue;
129
+ if (op.position && typeof op.position === 'object')
130
+ setDesktopPosition(moved.node, op.position);
131
+ attach(moved.node, op.parent ?? moved.parent?.elementId ?? 'root', op.index);
132
+ }
133
+ }
134
+ return touched;
135
+ }
136
+ /**
137
+ * F-8 — olay kaldırma 2026-09-14'e kadar gateway emülasyonuydu (şemayı okuyup tam PUT). Artık
138
+ * platformun `remove_event` op'u; `target: 'page'` sayfa düzeyi olayları hedefler ve olmayan olay
139
+ * sessizce yutulmaz, `missing-event` ile 422 döner.
140
+ */
141
+ export function planEventRemovals(ops) {
142
+ return ops.map(op => {
143
+ const target = String(op.target ?? '');
144
+ const event = String(op.event ?? '');
145
+ if (!target || !event)
146
+ throw new PlatformApiError("remove_event için 'target' ve 'event' zorunludur.", 400);
147
+ return `${target}.${event}`;
148
+ });
149
+ }
150
+ function findParentNode(nodes, target) {
151
+ for (const node of nodes) {
152
+ if ((node.children ?? []).some(child => child.elementId === target))
153
+ return node;
154
+ const deeper = findParentNode(node.children ?? [], target);
155
+ if (deeper)
156
+ return deeper;
157
+ }
158
+ return undefined;
159
+ }
160
+ /**
161
+ * G-25 — `states` bir prop değil düğüm alanı ve `replace_prop` onu yazamaz. Anahtar kümesi beyaz
162
+ * listeyle sınırlı: platform bilinmeyen anahtarı `unknown-states-field` ile reddeder ama ajan mesajı
163
+ * burada, istek gönderilmeden daha açık.
164
+ */
165
+ const WRITABLE_STATES = new Set(['isWrapper', 'isScrollable', 'isSticky', 'isDroppable', 'isFloating', 'isQuickStack']);
166
+ /**
167
+ * `resize_component` gateway'in op adıdır (G-14): ızgarada boyut `blocks[].size`, flex'te
168
+ * `appearance`, bayraklar `states`. 2026-09-14'ten beri sunucu üçünü de kendi op'larıyla yazar
169
+ * (`set_block`, `set_appearance`, `set_states`); gateway yalnız çevirir. Tam GET + tam PUT yolu
170
+ * kalktı — bir sayfaya dokunan tek istek PATCH'tir.
171
+ */
172
+ export function resizeServerOps(op) {
173
+ const target = String(op.target ?? '');
174
+ const size = op.size && typeof op.size === 'object' ? op.size : undefined;
175
+ const appearance = op.appearance && typeof op.appearance === 'object' ? op.appearance : undefined;
176
+ const states = op.states && typeof op.states === 'object' ? op.states : undefined;
177
+ return [
178
+ ...(size ? [{ op: 'set_block', target, breakpoint: Number(op.breakpoint ?? 1024), size }] : []),
179
+ ...(appearance ? [{ op: 'set_appearance', target, appearance }] : []),
180
+ ...(states ? [{ op: 'set_states', target, states }] : [])
181
+ ];
182
+ }
183
+ /**
184
+ * Sunucu hedefi ve bloğu kendisi doğrular; burada okunan sayfadan yapılan kontrol ajana bağlamı
185
+ * olan bir mesaj ve PAGE-019/020 uyarıları içindir. `size` yalnız verilen kenarı taşır: eksik
186
+ * kenar okunan bloktan tamamlanır, yoksa sunucu 0 yazardı.
187
+ */
188
+ export function planResizes(schema, ops, readTool = 'page_schema_get') {
189
+ const serverOps = [];
190
+ const applied = [];
191
+ const warnings = [];
192
+ for (const op of ops) {
193
+ const target = String(op.target ?? '');
194
+ const size = op.size && typeof op.size === 'object' ? op.size : undefined;
195
+ const appearance = op.appearance && typeof op.appearance === 'object' ? op.appearance : undefined;
196
+ const states = op.states && typeof op.states === 'object' ? op.states : undefined;
197
+ const breakpoint = Number(op.breakpoint ?? 1024);
198
+ if (!target)
199
+ throw new PlatformApiError("resize_component için 'target' zorunludur.", 400);
200
+ if (!size && !appearance && !states) {
201
+ throw new PlatformApiError(`resize_component '${target}' için 'size' ({ w, h } — ızgara), 'appearance' ({ widthType, widthString, heightType, heightString } — flex) ya da 'states' ({ isWrapper, isScrollable, isSticky, isDroppable, isFloating, isQuickStack }) verilmelidir; üçü de boş.`, 400);
202
+ }
203
+ if (states) {
204
+ const unknownStates = Object.keys(states).filter(key => !WRITABLE_STATES.has(key));
205
+ if (unknownStates.length > 0) {
206
+ throw new PlatformApiError(`resize_component '${target}': bilinmeyen states anahtarı (${unknownStates.join(', ')}). Yazılabilir bayraklar: ${[...WRITABLE_STATES].join(', ')}.`, 400);
207
+ }
208
+ }
209
+ const node = findNode(schema, target);
210
+ if (!node)
211
+ throw new PlatformApiError(`resize_component hedefi bulunamadı: '${target}'. ${readTool} ile elementId'leri listeleyin.`, 400);
212
+ const parent = findParentNode(schema, target);
213
+ const inFlex = parent !== undefined && isFlexContainer(parent);
214
+ let resolved = op;
215
+ if (size) {
216
+ const blocks = Array.isArray(node.blocks) ? node.blocks : [];
217
+ const block = blocks.find(entry => Number(entry?.breakpoint) === breakpoint);
218
+ if (!block) {
219
+ throw new PlatformApiError(`resize_component '${target}' düğümünde ${breakpoint} breakpoint'li blok yok (var olanlar: ${blocks.map(entry => entry?.breakpoint).join(', ') || 'hiç'}). Farklı bir breakpoint verin ya da düğümü add_component ile blokları kurulu olarak ekleyin.`, 400);
220
+ }
221
+ const current = (block.size ?? {});
222
+ const merged = { w: Number(size.w ?? current.w ?? 0), h: Number(size.h ?? current.h ?? 0) };
223
+ resolved = { ...op, size: merged };
224
+ applied.push(`${target}.size=${JSON.stringify(merged)}`);
225
+ if (inFlex) {
226
+ warnings.push(`PAGE-019 (uyarı): '${target}' düğümünün ebeveyni flex kapsayıcı; orada yerleşimi 'blocks[].size' DEĞİL appearance (widthType/widthString/heightType/heightString) belirler. Boyut yazıldı ama ekranda etkisi olmayabilir — flex'te ölçü vermek için aynı op'a 'appearance' koyun.`);
227
+ }
228
+ }
229
+ if (states) {
230
+ applied.push(`${target}.states=${JSON.stringify(states)}`);
231
+ if (states.isWrapper === true) {
232
+ warnings.push(`PAGE-020 (uyarı): '${target}' düğümüne isWrapper:true yazıldı — runtime sarmalayıcı düğüme 'min-height: 80vh' veriyor ve bu, sayfanın altına yüzlerce piksel ölü boşluk ekleyebilir (ölçüldü 09-05: dokuz düğüm ~1600px). style ile ezilemez; gerekmiyorsa isWrapper:false bırakın.`);
233
+ }
234
+ }
235
+ if (appearance) {
236
+ applied.push(`${target}.appearance=${JSON.stringify(appearance)}`);
237
+ if (!inFlex && parent !== undefined) {
238
+ warnings.push(`PAGE-019 (uyarı): '${target}' düğümünün ebeveyni flex DEĞİL; ızgarada yerleşimi 'blocks[].size' belirler ve appearance genişlik/yükseklik alanları etkisiz kalabilir. Izgarada ölçü vermek için aynı op'a 'size' koyun.`);
239
+ }
240
+ }
241
+ serverOps.push(...resizeServerOps(resolved));
242
+ }
243
+ return { serverOps, applied, warnings };
244
+ }
245
+ /**
246
+ * `set_prop_type` (P0-2) 2026-09-14'ten beri platformun op'u; taşıyıcı kuralı
247
+ * (`PROP_TYPE_CARRIER`) iki tarafta aynı. Hedef okunan sayfada aranır ki ajan "bulunamadı"yı
248
+ * bağlamıyla alsın; `value` eşanlamlısı sunucunun `data` alanına taşınır.
249
+ */
250
+ export function planPropTypes(schema, ops, readTool = 'page_schema_get') {
251
+ const missing = ops.map(op => String(op.target ?? '')).find(target => !findNode(schema, target));
252
+ if (missing !== undefined) {
253
+ throw new PlatformApiError(`set_prop_type hedefi bulunamadı: '${missing}'. ${readTool} ile elementId'leri listeleyin.`, 400);
254
+ }
255
+ return {
256
+ serverOps: ops.map(({ value, ...op }) => (op.data === undefined && value !== undefined ? { ...op, data: value } : op)),
257
+ applied: ops.map(op => {
258
+ const current = findNode(schema, String(op.target))?.props;
259
+ return `${op.target}.${op.prop}: ${current?.[String(op.prop)]?.type ?? '(yok)'} → ${op.propType}`;
260
+ })
261
+ };
262
+ }
263
+ /**
264
+ * İstek gitmeden reddedilen gövde hataları. `normalizeSchemaOps` ÖNCE koşmalıdır: PAGE-016 yalnız
265
+ * normalizasyonun tamamlayamadığı validation gövdelerini sayar.
266
+ */
267
+ export function assertOpBodies(opRecords, propTypeOps) {
268
+ const propTypeIssues = setPropTypeErrors(propTypeOps);
269
+ if (propTypeIssues.length > 0)
270
+ throw new PlatformApiError(`set_prop_type gövdesi eksik (istek gönderilmedi): ${propTypeIssues.join(' · ')}`, 400);
271
+ const sizedMoves = opRecords.filter(entry => entry.op === 'move_component' && entry.size !== undefined);
272
+ if (sizedMoves.length > 0) {
273
+ throw new PlatformApiError(`move_component yalnız 'position' uygular; gövdeye konan 'size' platformda SESSİZCE düşer (ölçüldü 2026-09-04, appliedPatch'te yalnız /blocks/0/position görünür). Boyut için resize_component kullanın: { op: 'resize_component', target: '<elementId>', size: { w, h } } — flex kapsayıcı içindeyse 'appearance'. Etkilenen hedefler: ${sizedMoves.map(entry => entry.target).join(' · ')}`, 400);
274
+ }
275
+ const validationIssues = schemaOpValidationErrors(opRecords);
276
+ if (validationIssues.length > 0) {
277
+ throw new PlatformApiError(`PAGE-016 — validation gövdesi eksik (istek gönderilmedi): ${validationIssues.join(' · ')}. `
278
+ + "Çalışan biçim dört alanı da taşır: { type: 'required'|'email'|'regex'|'max-length'|'min-length'|'max-value'|'min-value'|'value-options', message: '<kullanıcıya görünen metin>', value: <eşik/desen/true>, isActive: true }.", 400);
279
+ }
280
+ }
@@ -4,7 +4,8 @@ import { applyCatalogLayoutRules } from './components.js';
4
4
  import { PlatformApiError } from '../platform/errors.js';
5
5
  const GUARD_CODES = {
6
6
  page: { events: 'PAGE-011', widgets: 'PAGE-012', noun: 'sayfa', tree: 'schema' },
7
- layout: { events: 'LAY-004', widgets: 'LAY-005', noun: 'layout', tree: 'views' }
7
+ layout: { events: 'LAY-004', widgets: 'LAY-005', noun: 'layout', tree: 'views' },
8
+ widget: { events: 'PAGE-011', widgets: 'PAGE-012', noun: 'widget', tree: 'schema' }
8
9
  };
9
10
  /**
10
11
  * JS tanımlayıcısı olmayan ya da AYRILMIŞ `name` alanlarını toplar; ad
@@ -152,10 +152,3 @@ export async function fetchHost(platform, host, project, environment, applicatio
152
152
  }
153
153
  return record;
154
154
  }
155
- export function hostUpdateBody(host, record, overrides = {}) {
156
- if (host === 'page')
157
- return pageUpdateBody(record, overrides);
158
- if (host === 'layout')
159
- return layoutUpdateBody(record, overrides);
160
- return componentUpdateBody(record, overrides);
161
- }