@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7

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 (145) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +9 -1
  3. package/README.md +14 -38
  4. package/dist/config.js +36 -3
  5. package/dist/knowledge/backendConstraints.js +982 -0
  6. package/dist/knowledge/componentCatalog.js +549 -92
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/coverage.js +43 -0
  9. package/dist/knowledge/flowGuards.js +30 -0
  10. package/dist/knowledge/functionGuards.js +134 -0
  11. package/dist/knowledge/guideSections.js +49 -0
  12. package/dist/knowledge/hydrate.js +97 -0
  13. package/dist/knowledge/languageCodes.js +36 -0
  14. package/dist/knowledge/languages.js +105 -0
  15. package/dist/knowledge/layoutSummary.js +10 -0
  16. package/dist/knowledge/localization.js +51 -0
  17. package/dist/knowledge/normalize.js +500 -16
  18. package/dist/knowledge/outlines.js +167 -12
  19. package/dist/knowledge/policy.js +14 -5
  20. package/dist/knowledge/propTypes.js +80 -0
  21. package/dist/knowledge/rules.js +961 -44
  22. package/dist/knowledge/schemaGuard.js +199 -0
  23. package/dist/knowledge/styleResolve.js +63 -4
  24. package/dist/observability/trace.js +27 -3
  25. package/dist/platform/csv.js +123 -0
  26. package/dist/platform/errors.js +107 -6
  27. package/dist/platform/http.js +34 -4
  28. package/dist/platform/patch.js +27 -0
  29. package/dist/platform/refs.js +104 -0
  30. package/dist/platform/responses.js +25 -6
  31. package/dist/platform/screenshot.js +544 -16
  32. package/dist/platform/secrets.js +40 -0
  33. package/dist/platform/tokenVault.js +89 -0
  34. package/dist/platform/writeDiff.js +281 -0
  35. package/dist/resources/examples/flow-call-function.json +66 -0
  36. package/dist/resources/examples/function-total.json +70 -0
  37. package/dist/resources/examples/layout-app-shell.json +1 -1
  38. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  39. package/dist/resources/examples/microservice-table-list.json +2 -4
  40. package/dist/resources/examples/page-dynamic-form.json +3 -3
  41. package/dist/resources/examples/page-flex-sections.json +99 -0
  42. package/dist/resources/examples/page-sign-in.json +4 -3
  43. package/dist/resources/guides/component.md +81 -8
  44. package/dist/resources/guides/entity.md +712 -20
  45. package/dist/resources/guides/expressions.md +47 -2
  46. package/dist/resources/guides/external-component.md +9 -0
  47. package/dist/resources/guides/functions.md +262 -0
  48. package/dist/resources/guides/getting-started.md +82 -7
  49. package/dist/resources/guides/layout.md +53 -10
  50. package/dist/resources/guides/merge.md +231 -0
  51. package/dist/resources/guides/microfunction.md +166 -0
  52. package/dist/resources/guides/microservice.md +287 -15
  53. package/dist/resources/guides/middleware.md +89 -1
  54. package/dist/resources/guides/modify-existing.md +23 -0
  55. package/dist/resources/guides/page.md +402 -49
  56. package/dist/resources/guides/translation.md +150 -8
  57. package/dist/resources/guides/view.md +16 -1
  58. package/dist/resources/guides/workflow.md +199 -28
  59. package/dist/resources/knowledge.js +5 -0
  60. package/dist/resources/rules.js +27 -0
  61. package/dist/resources/schema-fingerprint.json +8 -0
  62. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  63. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  64. package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
  65. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  66. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  67. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  68. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  69. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  70. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  71. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  72. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  73. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  74. package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
  75. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  76. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  77. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  78. package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
  79. package/dist/resources/schemas/DataJobDto.json +267 -0
  80. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  81. package/dist/resources/schemas/Filter.json +3 -1
  82. package/dist/resources/schemas/GetRowsQuery.json +6 -1
  83. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  84. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  85. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  86. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  87. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  88. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  89. package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
  90. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  91. package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
  92. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  93. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  94. package/dist/resources/schemas/RunImportCommand.json +62 -0
  95. package/dist/resources/schemas/StepDto.json +34 -3
  96. package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
  97. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  98. package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
  99. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  100. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  101. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  102. package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
  103. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  104. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  105. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  106. package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
  107. package/dist/server.js +17 -1
  108. package/dist/tools/access.js +3 -3
  109. package/dist/tools/activities.js +40 -0
  110. package/dist/tools/applications.js +1 -1
  111. package/dist/tools/automations.js +76 -34
  112. package/dist/tools/common.js +55 -0
  113. package/dist/tools/components.js +16 -7
  114. package/dist/tools/datasources.js +83 -21
  115. package/dist/tools/dependencies.js +1 -1
  116. package/dist/tools/deployments.js +34 -2
  117. package/dist/tools/dtos.js +25 -12
  118. package/dist/tools/entities.js +113 -28
  119. package/dist/tools/environments.js +52 -0
  120. package/dist/tools/externalComponents.js +9 -3
  121. package/dist/tools/files.js +57 -42
  122. package/dist/tools/flows.js +205 -13
  123. package/dist/tools/folders.js +7 -2
  124. package/dist/tools/functions.js +331 -0
  125. package/dist/tools/globalFunctions.js +63 -0
  126. package/dist/tools/importSchemas.js +79 -0
  127. package/dist/tools/imports.js +241 -0
  128. package/dist/tools/layouts.js +103 -29
  129. package/dist/tools/merges.js +113 -0
  130. package/dist/tools/microfunctions.js +155 -0
  131. package/dist/tools/microservices.js +270 -40
  132. package/dist/tools/organizations.js +5 -6
  133. package/dist/tools/pages.js +421 -104
  134. package/dist/tools/platformInfo.js +155 -11
  135. package/dist/tools/projects.js +183 -33
  136. package/dist/tools/queries.js +32 -10
  137. package/dist/tools/revisions.js +94 -0
  138. package/dist/tools/rows.js +484 -50
  139. package/dist/tools/screenWrite.js +68 -0
  140. package/dist/tools/screens.js +28 -0
  141. package/dist/tools/templates.js +32 -5
  142. package/dist/tools/translations.js +24 -8
  143. package/dist/tools/views.js +20 -10
  144. package/dist/tools/workflows.js +56 -21
  145. package/package.json +7 -2
@@ -0,0 +1,199 @@
1
+ import { unstoredKeys } from '../platform/writeDiff.js';
2
+ import { logStep } from '../observability/trace.js';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { readFileSync } from 'node:fs';
5
+ import { join, dirname } from 'node:path';
6
+ const schemasDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'schemas');
7
+ const documents = new Map();
8
+ /**
9
+ * Yazma gövdesindeki TANINMAYAN anahtarı platform sessizce yok sayar: `isUnique` gönderilen bir
10
+ * index UNIQUE OLMADAN kurulur, istek 200 döner (ölçüldü 2026-09-07, `ENT-013`).
11
+ *
12
+ * Backend bunun için `X-Unmapped-Members` başlığını yazıyor ama kanal olarak KULLANILAMAZ:
13
+ * yalnız İLK eşleşmeyen anahtarı bildiriyor ve gateway'in GET→PUT gidiş-dönüşü gövdeye yedi
14
+ * yapısal anahtar koyuyor (`slug` · `datasourceId` · `primaryKey` · `type` · `applications` ·
15
+ * `automationCount` · `audit`). Sırada `slug` önce geldiği için başlık sıradan bir `entity_patch`
16
+ * çağrısında hep `slug` der ve ajanın yazdığı `indexes[0].isUnique` ASLA görünmez — kanal
17
+ * sinyali maskeler (g-03 Adım 0 ölçümü).
18
+ *
19
+ * Bu yüzden denetim gateway'in kendi taşıdığı şemalardan yapılır: çevrimdışı çalışır, TÜM
20
+ * anahtarları görür ve `introducedUnmappedMembers` sayesinde ajanın yazdığını gateway'in
21
+ * taşıdığından ayırabilir.
22
+ */
23
+ function schemaDocument(command) {
24
+ if (documents.has(command))
25
+ return documents.get(command);
26
+ // Ad dosya yoluna giriyor: dizin dışına çıkan ya da uzantı taşıyan bir değer okunmaz.
27
+ const document = /^[A-Za-z0-9_]+$/.test(command)
28
+ ? (() => {
29
+ try {
30
+ return JSON.parse(readFileSync(join(schemasDirectory, `${command}.json`), 'utf8'));
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ })()
36
+ : undefined;
37
+ documents.set(command, document);
38
+ return document;
39
+ }
40
+ /** `$ref` ya da tek elemanlı `allOf: [{ $ref }]` sarmalının hedef adı; ikincisi Swashbuckle'ın kısıt/nullable eklenmiş referans için ürettiği biçimdir. */
41
+ function referenceName(node) {
42
+ if (!node || typeof node !== 'object')
43
+ return undefined;
44
+ const record = node;
45
+ if (typeof record.$ref === 'string')
46
+ return record.$ref.replace('#/components/schemas/', '');
47
+ const allOf = record.allOf;
48
+ if (Array.isArray(allOf) && allOf.length === 1)
49
+ return referenceName(allOf[0]);
50
+ return undefined;
51
+ }
52
+ function resolve(document, node) {
53
+ if (!node || typeof node !== 'object')
54
+ return undefined;
55
+ const record = node;
56
+ const reference = record.$ref;
57
+ if (typeof reference === 'string') {
58
+ // Üretilen şemalarda tek biçim var: '#/components/schemas/<tam ad>'.
59
+ const name = reference.replace('#/components/schemas/', '');
60
+ const components = document.components?.schemas;
61
+ const target = components?.[name];
62
+ return target && typeof target === 'object' ? target : undefined;
63
+ }
64
+ // FluentValidation kısıtları swagger'a aktığından beri (2026-09-08) referanslı bir özellik
65
+ // `allOf: [{ $ref }]` + kendi anahtarları (nullable, minLength…) şeklinde gelebiliyor; hedef
66
+ // çözülür, özelliğin kendi anahtarları üstüne yazılır.
67
+ const allOf = record.allOf;
68
+ if (Array.isArray(allOf) && allOf.length === 1) {
69
+ const inner = resolve(document, allOf[0]);
70
+ const { allOf: _dropped, ...own } = record;
71
+ return inner ? { ...inner, ...own } : undefined;
72
+ }
73
+ return record;
74
+ }
75
+ function walk(document, schema, value, path, into) {
76
+ if (!schema || !value || typeof value !== 'object')
77
+ return;
78
+ if (Array.isArray(value)) {
79
+ const items = resolve(document, schema.items);
80
+ value.forEach((entry, index) => walk(document, items, entry, `${path}[${index}]`, into));
81
+ return;
82
+ }
83
+ const properties = schema.properties;
84
+ if (!properties)
85
+ return;
86
+ for (const [key, entry] of Object.entries(value)) {
87
+ const child = path === '' ? key : `${path}.${key}`;
88
+ // `additionalProperties: false` olmayan bir düğümde serbest anahtar meşrudur (ör. `properties`
89
+ // alanı gibi şemasız sözlükler) — orada susmak yanlış alarmı önler.
90
+ if (!Object.hasOwn(properties, key)) {
91
+ // `writeDiff`'in gerekçeli istisnaları burada da geçerli: gateway'in bilerek gönderdiği,
92
+ // platformun `fieldId`'den çözüp saklamadığı kolaylık alanları hata değildir. Tek küme
93
+ // tutulur — ikiye ayrılırsa ayrışırlar.
94
+ if (schema.additionalProperties === false && !unstoredKeys.has(key))
95
+ into.push(child);
96
+ continue;
97
+ }
98
+ walk(document, resolve(document, properties[key]), entry, child, into);
99
+ }
100
+ }
101
+ /**
102
+ * `command` üretilen şema dosyasının adıdır (`ngapps://schemas/<command>`); bilinmeyen ad
103
+ * sessizce boş liste döner — şema yoksa iddia da yoktur.
104
+ */
105
+ export function unmappedMembers(command, body) {
106
+ const document = schemaDocument(command);
107
+ if (!document)
108
+ return [];
109
+ const found = [];
110
+ walk(document, document.schema, body, '', found);
111
+ return found;
112
+ }
113
+ /**
114
+ * Yamanın GETİRDİĞİ eşleşmeyen anahtarları döner. Gateway'in GET'ten taşıdığı yapısal alanlar iki
115
+ * tarafta da bulunduğu için elenir; geriye yalnız çağıranın eklediği kalır.
116
+ */
117
+ export function introducedUnmappedMembers(command, before, after) {
118
+ const existing = new Set(unmappedMembers(command, before));
119
+ return unmappedMembers(command, after).filter(member => !existing.has(member));
120
+ }
121
+ /**
122
+ * Yanıt yan kanalı (`runtimeErrors` · `gatewayMasked` · `silentlyDropped` ile aynı desen).
123
+ * Alan adı `secretKeyPattern` ile eşleşmemeli, yoksa değeri sessizce `***` olur — GW-001 dersi;
124
+ * `entities.test.ts` bunu yanıt üzerinde doğruluyor.
125
+ */
126
+ export function withUnmappedMembers(payload, members) {
127
+ if (members.length === 0 || !payload || typeof payload !== 'object')
128
+ return payload;
129
+ logStep('şema ✕', `${members.length} anahtar komut şemasında yok — platform onları sessizce yok sayar`, members);
130
+ return {
131
+ ...payload,
132
+ unmappedMembers: members,
133
+ unmappedMembersNote: "Bu anahtarlar gönderdiğiniz komutun şemasında YOK: platform onları hata vermeden YOK SAYAR, yani o alanlar kaydedilmedi. Doğru adlar için ngapps://schemas kaynağına bakın."
134
+ };
135
+ }
136
+ const numericKinds = ['minLength', 'maxLength', 'minimum', 'maximum', 'minItems'];
137
+ function limitOf(path, schema, required) {
138
+ const limit = { path };
139
+ if (required)
140
+ limit.required = true;
141
+ for (const kind of numericKinds) {
142
+ if (typeof schema[kind] === 'number')
143
+ limit[kind] = schema[kind];
144
+ }
145
+ if (typeof schema.pattern === 'string' && schema.pattern !== '')
146
+ limit.pattern = schema.pattern;
147
+ return Object.keys(limit).length > 1 ? limit : undefined;
148
+ }
149
+ function collectSchemaLimits(document, schema, path, chain, into) {
150
+ const properties = schema?.properties;
151
+ if (!schema || !properties)
152
+ return;
153
+ const required = new Set(Array.isArray(schema.required) ? schema.required.map(String) : []);
154
+ for (const [key, raw] of Object.entries(properties)) {
155
+ const childPath = path === '' ? key : `${path}.${key}`;
156
+ const child = resolve(document, raw);
157
+ if (!child)
158
+ continue;
159
+ const limit = limitOf(childPath, child, required.has(key));
160
+ if (limit)
161
+ into.push(limit);
162
+ if (child.type === 'array') {
163
+ const items = resolve(document, child.items);
164
+ if (!items)
165
+ continue;
166
+ const itemLimit = limitOf(`${childPath}[]`, items, false);
167
+ if (itemLimit)
168
+ into.push(itemLimit);
169
+ const itemsName = referenceName(child.items);
170
+ // Özyineli tip (FieldDto.fields → FieldDto) bir kez yürünür; katalog da yalnız ilk seviyeyi tanır.
171
+ if (itemsName !== undefined && chain.includes(itemsName))
172
+ continue;
173
+ collectSchemaLimits(document, items, `${childPath}[]`, itemsName !== undefined ? [...chain, itemsName] : chain, into);
174
+ continue;
175
+ }
176
+ const childName = referenceName(raw);
177
+ if (childName !== undefined && chain.includes(childName))
178
+ continue;
179
+ collectSchemaLimits(document, child, childPath, childName !== undefined ? [...chain, childName] : chain, into);
180
+ }
181
+ }
182
+ /** Şema dokümanındaki (`{ schema, components }`) bütün alan kısıtlarını katalog yol notasyonuyla (`fields[].name`) döner. */
183
+ export function schemaLimitsOf(document) {
184
+ if (!document)
185
+ return [];
186
+ const found = [];
187
+ const rootName = referenceName(document.schema);
188
+ collectSchemaLimits(document, resolve(document, document.schema), '', rootName !== undefined ? [rootName] : [], found);
189
+ return found;
190
+ }
191
+ /**
192
+ * Paketlenmiş swagger şemasından FluentValidation kaynaklı kısıtlar (`MicroElements.Swashbuckle.FluentValidation`,
193
+ * backend 2026-09-08): NotEmpty → required (+ minLength 1), MaximumLength → maxLength, Matches → pattern,
194
+ * InclusiveBetween → minimum/maximum. `npm run generate-schemas` yenilenmeden eski şemada kısıt yoktur ve
195
+ * bu liste boş döner — o durumda `backendConstraints.ts` kataloğu tek başına konuşur.
196
+ */
197
+ export function schemaLimits(command) {
198
+ return schemaLimitsOf(schemaDocument(command));
199
+ }
@@ -156,7 +156,8 @@ export const STYLE_GROUP_FIELDS = {
156
156
  'borderBottom',
157
157
  'borderLeft',
158
158
  'shadow',
159
- 'contentOverflow'
159
+ 'contentOverflow',
160
+ 'opacity'
160
161
  ],
161
162
  typographyStyle: [
162
163
  'textColor',
@@ -171,6 +172,8 @@ export const STYLE_GROUP_FIELDS = {
171
172
  ],
172
173
  hoverStyle: ['background', 'borderColor', 'shadow', 'lift', 'transitionMs']
173
174
  };
175
+ /** `overflowTarget` için "kökün kendisi" işareti (frontend `ROOT_TARGET` kopyası). */
176
+ export const ROOT_TARGET = '&';
174
177
  export function composeTypedStyle(input, ext) {
175
178
  const groups = {};
176
179
  if (isPlainObject(input.surfaceStyle)) {
@@ -198,8 +201,19 @@ export function composeTypedStyle(input, ext) {
198
201
  groups.border = `1px ${surface.borderStyle || 'solid'} ${surface.borderColor || DEFAULT_DIVIDER_COLOR}`;
199
202
  }
200
203
  }
204
+ // Opaklık yüzdedir (eski Surface bloğuyla aynı sözleşme): 100 ve boş
205
+ // yazılmaz, aralık dışı yok sayılır.
206
+ const opacity = asNumber(surface.opacity);
207
+ if (opacity !== undefined && opacity >= 0 && opacity < 100) {
208
+ groups.opacity = String(opacity / 100);
209
+ }
210
+ // `ROOT_TARGET` (`&`) kökün kendisidir: kendi derleyicisi olmayan kaplarda
211
+ // yüzey düğüm kutusuna yazılır ve kaydırılan da odur.
201
212
  if (isSet(surface.contentOverflow) && ext?.overflowTarget) {
202
- nest(groups, ext.overflowTarget, { overflow: surface.contentOverflow });
213
+ if (ext.overflowTarget === ROOT_TARGET)
214
+ groups.overflow = surface.contentOverflow;
215
+ else
216
+ nest(groups, ext.overflowTarget, { overflow: surface.contentOverflow });
203
217
  }
204
218
  }
205
219
  if (isPlainObject(input.typographyStyle)) {
@@ -414,6 +428,44 @@ export const TYPED_STYLE_COMPONENTS = {
414
428
  omit: { hoverStyle: ['borderColor'] }
415
429
  }
416
430
  };
431
+ /**
432
+ * Kendi derleyicisi OLMAYAN kap bileşenleri (2026-09-04): şemalarında
433
+ * `surfaceStyle` grubu var ama bileşen prop'u okumaz — render katmanı yüzeyi
434
+ * düğüm kutusuna (`.ng-app__node-item`) genel yolla yazar, taşma kökün
435
+ * kendisine gider (`packages/ng-apps-ui/src/style-groups/node-surface.ts`).
436
+ * Bu tipler `TYPED_STYLE_COMPONENTS`'a GİRMEZ: `style` kaçış kanalı onlarda
437
+ * eski birleşik kaskadla (tema+preset+own) çözülmeye devam eder.
438
+ */
439
+ export const NODE_SURFACE_TYPES = new Set([
440
+ 'table',
441
+ 'datatable',
442
+ 'form',
443
+ 'collapsible-container',
444
+ 'drawer',
445
+ 'modal',
446
+ 'popover',
447
+ 'tabs',
448
+ 'stepper',
449
+ 'repeater',
450
+ 'html',
451
+ 'file-viewer',
452
+ 'document-editor',
453
+ 'layout',
454
+ 'layout-region',
455
+ 'layout-shell',
456
+ 'qm-content',
457
+ 'qm-sidebar',
458
+ 'auth-layout'
459
+ ]);
460
+ const NODE_SURFACE_EXTENSION = { overflowTarget: ROOT_TARGET };
461
+ /** Düğüm kutusuna inen yüzey; kap tipi değilse ya da grup boşsa yok. */
462
+ export function composeNodeSurfaceStyle(type, surfaceStyle) {
463
+ if (!type || !NODE_SURFACE_TYPES.has(type))
464
+ return undefined;
465
+ if (!isPlainObject(surfaceStyle))
466
+ return undefined;
467
+ return composeTypedStyle({ surfaceStyle }, NODE_SURFACE_EXTENSION);
468
+ }
417
469
  /** text derlemesi — testler ve dış tüketiciler için sarmalayıcı. */
418
470
  export function composeTextStyle(input) {
419
471
  return composeTypedStyle(input, TYPED_STYLE_COMPONENTS.text.ext);
@@ -467,9 +519,16 @@ export function resolveNodeStyleReport(node, source) {
467
519
  own: isPlainObject(ownStyle) ? Object.keys(ownStyle).filter(key => key !== STYLE_PRESET_KEY).length > 0 : Boolean(ownStyle)
468
520
  };
469
521
  if (!config) {
470
- // Kayıtsız tip: eski birleşik kaskad.
522
+ // Kayıtsız tip: eski birleşik kaskad. Kap tiplerinde tipli yüzey grubu
523
+ // düğüm kutusuna ayrıca iner; rapor onu kaskadın ÜSTÜNE koyar (kutu,
524
+ // bileşenin kökünü sarar).
471
525
  const cascaded = resolveNodeStyle(ownStyle, source, node.type);
472
- return cascaded === undefined ? { styleLayers: layers } : { resolvedStyle: cascaded, styleLayers: layers };
526
+ const nodeSurface = composeNodeSurfaceStyle(node.type, rawProp(props, 'surfaceStyle'));
527
+ const typedLayers = nodeSurface ? { ...layers, typedGroups: ['surfaceStyle'] } : layers;
528
+ const resolved = cascaded === undefined && nodeSurface === undefined
529
+ ? undefined
530
+ : deepMergeStyle((cascaded ?? {}), nodeSurface ?? {});
531
+ return resolved === undefined ? { styleLayers: typedLayers } : { resolvedStyle: resolved, styleLayers: typedLayers };
473
532
  }
474
533
  // Tipli komponent gruplar boşken de tipli yoldan geçer: uzantı emekli
475
534
  // prop'ları okuyabilir (text'in isBold/align/color takma adı) ve K4 sırası
@@ -1,11 +1,11 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
+ import { secretKeyPattern } from '../platform/secrets.js';
4
5
  const defaultDirectory = join(process.cwd(), 'logs');
5
6
  const activeTrace = new AsyncLocalStorage();
6
7
  const activeRequest = new AsyncLocalStorage();
7
8
  const inFlight = new Set();
8
- const secretKeyPattern = /pass|secret|token|authorization|api[-_]?key|credential|connectionstring/i;
9
9
  const detailIndent = 26;
10
10
  let level = 'off';
11
11
  let logFile;
@@ -180,6 +180,25 @@ function renderTrace(trace, outcome) {
180
180
  lines.push('');
181
181
  return `${lines.join('\n')}\n`;
182
182
  }
183
+ /**
184
+ * `runtimeErrors` kayıtlarını DÜZ METNE çevirir. Eskiden `runtimeErrors.map(String)` yazılıyordu
185
+ * ve nesne `[object Object]`e düşüyordu: 2026-09-06 koşusunda tool mesajı hangi adımın neden
186
+ * düştüğünü söylemiyordu, oysa doğru cümle aynı yanıtın içinde duruyordu (`S-4`).
187
+ *
188
+ * Yalnız İLK hata yazılır ve kalan sayılır — mesaj bir özet satırıdır, liste değil. Kullanıcı
189
+ * verisi taşıyabildiği için cümle 300 karakterde kesilir; tamamı günlüktedir.
190
+ */
191
+ export function describeRuntimeErrors(errors) {
192
+ const first = (errors[0] ?? {});
193
+ const where = [first.step?.name, first.element ? `${first.element}${first.property ? `.${first.property}` : ''}` : undefined]
194
+ .filter(Boolean)
195
+ .join(' → ');
196
+ const message = typeof first.message === 'string' && first.message.length > 0
197
+ ? first.message
198
+ : JSON.stringify(first);
199
+ const rest = errors.length > 1 ? ` (+${errors.length - 1} hata daha)` : '';
200
+ return `${where ? `${where} — ` : ''}${clip(message, 300)}${rest} · tamamı günlükte`;
201
+ }
183
202
  function describeResult(result) {
184
203
  const call = result;
185
204
  const text = Array.isArray(call?.content)
@@ -187,9 +206,14 @@ function describeResult(result) {
187
206
  : '';
188
207
  if (call?.isError)
189
208
  return { ok: false, summary: clip(text, 400) };
190
- const runtimeErrors = call?.structuredContent?.runtimeErrors;
209
+ const structured = call?.structuredContent;
210
+ const runtimeErrors = structured?.runtimeErrors;
191
211
  if (Array.isArray(runtimeErrors) && runtimeErrors.length > 0) {
192
- return { ok: false, summary: `çalışma anı hatası (platform HTTP 200 döndü): ${clip(runtimeErrors.map(String).join(' | '), 400)}` };
212
+ // Kasıtlı negatif test bir kusur değil bir KANIT: çağıran beklediğini söylediyse satır
213
+ // başarılı sayılır ve ölçüm aracının plansız hata oranını şişirmez (S-4).
214
+ return structured?.expectedFailure === true
215
+ ? { ok: true, summary: `beklenen çalışma anı hatası (negatif test): ${describeRuntimeErrors(runtimeErrors)}` }
216
+ : { ok: false, summary: `çalışma anı hatası (platform HTTP 200 döndü): ${describeRuntimeErrors(runtimeErrors)}` };
193
217
  }
194
218
  const effect = summarizeEffect(text);
195
219
  return { ok: true, summary: `${Buffer.byteLength(text, 'utf8')} B yanıt${effect ? ` · ${effect}` : ''}` };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Gateway'in kendi minimal RFC 4180 okuyucusu. `packages/ng-apps-ui`'daki `csv-parse.ts`
3
+ * kopyalanmaz: o dosya tarayıcı için yazılmıştır ve bu proje frontend workspace'ine dahil
4
+ * değildir (sözleşme D5) — kopya iki yerde birden bozulurdu.
5
+ *
6
+ * Değerler HEP string döner. Tip dönüşümü backend'in işidir: `1.234,56` ile `1,234.56` arasındaki
7
+ * fark kültüre bağlıdır ve gateway istek kültürünü bilmez.
8
+ */
9
+ /**
10
+ * Ayraç ilk satırdan tahmin edilir. Türkçe Excel `;` ile yazar ve o dosyalarda virgül ONDALIK
11
+ * AYRACIDIR — yanlış tahmin `1,5` değerini iki sütuna böler, hem de sessizce.
12
+ */
13
+ export function detectDelimiter(firstLine) {
14
+ let semicolons = 0;
15
+ let commas = 0;
16
+ let quoted = false;
17
+ for (const character of firstLine) {
18
+ if (character === '"')
19
+ quoted = !quoted;
20
+ else if (quoted)
21
+ continue;
22
+ else if (character === ';')
23
+ semicolons += 1;
24
+ else if (character === ',')
25
+ commas += 1;
26
+ }
27
+ return semicolons > commas ? ';' : ',';
28
+ }
29
+ function stripBom(text) {
30
+ return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
31
+ }
32
+ function firstLineOf(text) {
33
+ let quoted = false;
34
+ for (let index = 0; index < text.length; index += 1) {
35
+ const character = text[index];
36
+ if (character === '"')
37
+ quoted = !quoted;
38
+ else if (!quoted && (character === '\n' || character === '\r'))
39
+ return text.slice(0, index);
40
+ }
41
+ return text;
42
+ }
43
+ /**
44
+ * Satır satır hücre dizisi. Tırnaklı hücre ayraç ve satır sonu taşıyabilir, `""` tek tırnaktır;
45
+ * tamamen boş satırlar atılır — Excel'in dosya sonuna bıraktığı boş satır aksi hâlde her alanı
46
+ * boş bir kayıt üretirdi.
47
+ */
48
+ export function parseCsv(text, delimiter) {
49
+ const content = stripBom(text);
50
+ const separator = delimiter ?? detectDelimiter(firstLineOf(content));
51
+ const rows = [];
52
+ let row = [];
53
+ let value = '';
54
+ let quoted = false;
55
+ let index = 0;
56
+ const endValue = () => {
57
+ row.push(value);
58
+ value = '';
59
+ };
60
+ const endRow = () => {
61
+ endValue();
62
+ if (row.some(cell => cell.length > 0))
63
+ rows.push(row);
64
+ row = [];
65
+ };
66
+ while (index < content.length) {
67
+ const character = content[index];
68
+ if (quoted) {
69
+ if (character === '"') {
70
+ if (content[index + 1] === '"') {
71
+ value += '"';
72
+ index += 2;
73
+ continue;
74
+ }
75
+ quoted = false;
76
+ index += 1;
77
+ continue;
78
+ }
79
+ value += character;
80
+ index += 1;
81
+ continue;
82
+ }
83
+ if (character === '"' && value.length === 0) {
84
+ quoted = true;
85
+ index += 1;
86
+ continue;
87
+ }
88
+ if (character === separator) {
89
+ endValue();
90
+ index += 1;
91
+ continue;
92
+ }
93
+ if (character === '\r' || character === '\n') {
94
+ endRow();
95
+ index += character === '\r' && content[index + 1] === '\n' ? 2 : 1;
96
+ continue;
97
+ }
98
+ value += character;
99
+ index += 1;
100
+ }
101
+ if (value.length > 0 || row.length > 0)
102
+ endRow();
103
+ return rows;
104
+ }
105
+ /**
106
+ * Başlık satırını anahtar yapan satır sözlükleri. Başlık boş olan sütun atlanır (Excel'in sağa
107
+ * doğru uzattığı boş sütunlar aksi hâlde `''` anahtarlı bir alan üretirdi) ve fazladan hücre
108
+ * taşıyan satır kırpılır.
109
+ */
110
+ export function csvRows(text, delimiter) {
111
+ const grid = parseCsv(text, delimiter);
112
+ if (grid.length === 0)
113
+ return [];
114
+ const headers = (grid[0] ?? []).map(header => header.trim());
115
+ return grid.slice(1).map(cells => {
116
+ const row = {};
117
+ headers.forEach((header, column) => {
118
+ if (header.length > 0)
119
+ row[header] = cells[column] ?? '';
120
+ });
121
+ return row;
122
+ });
123
+ }
@@ -1,6 +1,12 @@
1
1
  export class PlatformApiError extends Error {
2
2
  status;
3
3
  code;
4
+ /**
5
+ * Toplu satır yazmasında düşen satırın parti içi indeksi (`BulkRowErrorResponse.rowIndex`).
6
+ * Mesajda da geçer ama metinden ayıklanamaz: parti döngüsü dosya satır numarasını hesaplamak
7
+ * için sayıya ihtiyaç duyar, çağıran hangi satırı düzelteceğini ancak öyle bilir.
8
+ */
9
+ rowIndex;
4
10
  constructor(message, status, code) {
5
11
  super(message);
6
12
  this.status = status;
@@ -45,8 +51,62 @@ function collectValidationMessages(payload) {
45
51
  * Sayfa şeması patch'inin üç reddi de "hiçbir op uygulanmadı" anlamına gelir (uygulayıcı önce tüm
46
52
  * op'ları bellekte işler, tek hata bile tüm komutu düşürür) — ajanın kısmi uygulama varsayıp
47
53
  * planın kalanını göndermesi bu yüzden yanlış olur.
54
+ *
55
+ * Aynı duvar 2026-09-08'den beri sayfa/layout KURMA yolunda da koşuyor (P1-2: layout hattı aynı
56
+ * hatalı prop'ları sessizce kaydediyordu). Orada op listesi yoktur ve `page_schema_validate` de
57
+ * uygulanmaz — ipucu bu yüzden yola göre ayrışır.
48
58
  */
49
- function pageSchemaHint(code) {
59
+ /**
60
+ * Merge apply'ın iki 409'u da aynı şeyi söyler: plan artık uygulanabilir değil. Ajan bunu
61
+ * 'geçici hata, tekrar dene' diye okuyup aynı çağrıyı yollamasın diye yol açıkça yazılır.
62
+ */
63
+ function mergeHint(path) {
64
+ if (!/\/merges\//.test(path))
65
+ return '';
66
+ return " Üç sebepten biri: çözülmemiş çakışma, ya da plan alındığından beri DALIN veya development’ın değişmesi (MergePlanStaleException) — ikincisinde planın taşıdığı liste eskidir ve uygulansaydı sonradan yapılan iş sessizce düşerdi. merge_get ile planı yeniden okuyun (isStale), bayatsa merge_refresh ile tazeleyin — yeni plan açmanız gerekmez. Aynı merge_apply çağrısını tekrarlamak sonucu değiştirmez.";
67
+ }
68
+ function mergeRevertHint(code) {
69
+ if (code === 'MergeRevertWindowExpiredException') {
70
+ return " Geri alma penceresi doldu: pencere kapanınca merge öncesi anlık görüntü de silinir, bu yüzden plan artık geri alınamaz. merge_list status:'applied' ile pencere içindeki bir plan seçin.";
71
+ }
72
+ if (code === 'MergePlanNotRevertableException') {
73
+ return ' Bu plan bir GERİ ALMA planı (kind=revert) ve geri almanın geri alması yoktur. Geri almayı iptal etmek istiyorsanız dalı yeniden merge edin.';
74
+ }
75
+ if (code === 'MergePlanNotAppliedException') {
76
+ return " Plan uygulanmış DEĞİL — durumu mesajda yazıyor (failed / discarded / reverted / planned…). Yalnız 'applied' ve 'applied-with-drift' geri alınabilir; merge_list status:'applied' ile uygun planı seçin. Zaten geri alınmış bir planın durumu 'reverted'dır ve ikinci kez geri alınamaz.";
77
+ }
78
+ return '';
79
+ }
80
+ /**
81
+ * İçe aktarma zinciri dört çağrıdır ve her hata farklı bir adıma geri gönderir: plan süresi
82
+ * dolduysa analizden, anahtar benzersiz değilse plan gövdesinden, hata oranı yüksekse dosyanın
83
+ * kendisinden. Kod olmadan mesajlar "422" diye okunur ve ajan aynı çağrıyı tekrarlar.
84
+ */
85
+ function importHint(code, path) {
86
+ if (code === 'ImportPlanExpiredException') {
87
+ return ' Plan bir saat geçerlidir ve süresi doldu (ya da başka bir kullanıcıya ait). import_analyze ile dosyayı yeniden okuyup import_plan ile yeni bir plan üretin — aynı planId bir daha çalışmaz.';
88
+ }
89
+ if (code === 'ImportTooManyErrorsException') {
90
+ return " Satırların yarısından fazlası kuru koşuda düştü. Önce planın `errors` listesine bakın: çoğu zaman tek bir yanlış `locale` ya da yanlış sütun tipi bütün satırları düşürür. Dosya gerçekten böyleyse import_run'ı `force: true` ile çağırın.";
91
+ }
92
+ if (code === 'ImportKeyFieldNotUniqueException') {
93
+ return " Upsert/update anahtarı benzersiz olmalı: alanın kendisi unique/primary key olmalı, ya da tek alanlı bir unique index taşımalı, ya da 'id'/'code' olmalı. entity_get ile alanın `unique` bayrağını doğrulayın.";
94
+ }
95
+ if (code === 'ImportRelationMatchFieldNotUniqueException') {
96
+ return ' Ad → id eşlemesi yalnız hedef tablonun display alanı ya da benzersiz bir alanıyla yapılır; benzersiz olmayan bir alanda aynı ad iki satıra gidebilir ve hangisinin yazılacağı belirsiz olur.';
97
+ }
98
+ if (code === 'ImportSheetEmptyException' || code === 'ImportRowLimitExceededException') {
99
+ return ' `headerRow` doğru mu kontrol edin (0 = başlık yok); satır sınırı aşıldıysa dosyayı sayfalara bölün.';
100
+ }
101
+ if (code === 'DataJobNotUndoableException' && path.endsWith('/cancel')) {
102
+ return ' İş zaten bitmiş (completed / failed / undone / cancelled); iptal edilecek bir şey yok. Tamamlanmış işi geri almak için import_undo.';
103
+ }
104
+ if (code === 'DataJobNotUndoableException' && path.endsWith('/undo')) {
105
+ return ' Yalnız tamamlanmış ve undoAvailableUntil geçmemiş bir içe aktarma geri alınır; mevcut tabloya yalnız ekleme yapan işin penceresi hiç açılmaz. Süren işi durdurmak için import_cancel.';
106
+ }
107
+ return '';
108
+ }
109
+ function pageSchemaHint(code, path) {
50
110
  if (code === 'PageSchemaVersionConflictException') {
51
111
  return ' Sayfa siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı. page_schema_get ile yeniden okuyup planı tazeleyin, yeni `version` değerini `baseVersion` olarak gönderin.';
52
112
  }
@@ -54,10 +114,42 @@ function pageSchemaHint(code) {
54
114
  return ' Sayfa şeması başka bir aktörün kilidinde (kilit sahibi mesajda; kilit 60 sn sonra kendiliğinden düşer). Bekleyip tekrar deneyin — aynı anda iki yazar olduğunda kaybeden taraf sessizce ezilmesin diye kilit var.';
55
115
  }
56
116
  if (code === 'PageSchemaValidationException') {
57
- return ' Op listesi doğrulama duvarına takıldı ve HİÇBİR op uygulanmadı (hepsi ya da hiçbiri). Alan hatalarını düzeltip tek çağrıda tekrar gönderin; planı önce page_schema_validate ile kuru koşturabilirsiniz.';
117
+ return path.endsWith('/schema')
118
+ ? ' Op listesi doğrulama duvarına takıldı ve HİÇBİR op uygulanmadı (hepsi ya da hiçbiri). Alan hatalarını düzeltip tek çağrıda tekrar gönderin; planı önce page_schema_validate ile kuru koşturabilirsiniz.'
119
+ : ' Gövde doğrulama duvarına takıldı ve HİÇBİR ŞEY KAYDEDİLMEDİ. Yukarıdaki her satır bir düğüm/prop adresi ve kabul edilen değer listesi taşır — prop adlarını component_get ile doğrulayıp gövdeyi tek çağrıda düzeltin.';
58
120
  }
59
121
  return '';
60
122
  }
123
+ /**
124
+ * Toplu satır yazma tek transaction: platform hangi satırın düştüğünü mesajda söyler
125
+ * (`Row {n} could not be written: …`), ama "geri kalanı yazıldı mı" sorusunu söylemez. Ajan
126
+ * kısmi yazma varsayıp yalnız düşen satırı yeniden göndermesin diye all-or-nothing burada yazılı.
127
+ */
128
+ function bulkRowHint(code) {
129
+ if (code === 'BulkRowFailedException') {
130
+ return " Toplu yazma TEK TRANSACTION: bu çağrıdan HİÇBİR satır yazılmadı (yalnız id sekansı ilerledi). Mesajdaki satır numarası 0'dan başlar ve gönderdiğiniz `rows` dizisinin indeksidir. O satırı düzeltip TÜM diziyi yeniden gönderin.";
131
+ }
132
+ if (code === 'BulkRowLimitException') {
133
+ return ' Diziyi 1000 satırlık parçalara bölüp sırayla gönderin; her parça kendi transaction\'ında yazılır.';
134
+ }
135
+ return '';
136
+ }
137
+ /**
138
+ * PATCH uçlarında `baseVersion` zorunlu ve kayıt okumanızdan beri değiştiyse HİÇBİR op uygulanmaz.
139
+ * Ajanın "tekrar dene" refleksi burada yanlış: aynı ops'u yeni sürümle göndermek başkasının
140
+ * yazdığını ezer, önce yeniden okunmalı.
141
+ */
142
+ function versionConflictHint(code, path) {
143
+ if (code !== 'AggregateVersionConflictException')
144
+ return '';
145
+ const readTool = /\/entities(\/|$)/.test(path) ? 'entity_get'
146
+ : /\/workflows(\/|$)/.test(path) ? 'workflow_get'
147
+ : /\/flows(\/|$)/.test(path) ? 'flow_get'
148
+ : /\/layouts(\/|$)/.test(path) ? 'layout_get'
149
+ : /\/microservices(\/|$)/.test(path) ? 'microservice_get'
150
+ : 'ilgili *_get';
151
+ return ` Kayıt siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı (mesajdaki beklenen/mevcut sürüm). ${readTool} ile yeniden okuyup patch'i güncel hâle göre kurun, yeni \`version\` değerini \`baseVersion\` olarak gönderin — aynı ops'u eski sürümle tekrar göndermek aradaki değişikliği ezer.`;
152
+ }
61
153
  /**
62
154
  * Bilinen platform bug'larını ajanın tool yüzeyinden teşhis edebilmesi için desen eşlemesi
63
155
  * (klinik turu dersi: teşhis DB'ye/koda inmeden yapılabilmeli — görev G-09).
@@ -65,6 +157,12 @@ function pageSchemaHint(code) {
65
157
  function diagnoseKnownFailure(status, message, code, context) {
66
158
  if (status !== 500)
67
159
  return '';
160
+ // 2026-09-08'e kadar ExceptionHandler bu istisnayı eşlemiyordu ve slug çakışması 500 dönüyordu
161
+ // (3. koşu S-1 / P3-7); yeni build 409 verir, gateway de POST'tan önce project_list ile arar
162
+ // (PRJ-001). Bu dal eski build'e karşı kalır.
163
+ if (code === 'DatabaseNameAlreadyExistsException' || message.includes('already has this database name')) {
164
+ return " Sebep: proje slug'ı bu organizasyonda zaten kullanımda (veritabanı adı zeyrek_<org>_<slug> tekildir). Çalışan backend bu durumu 409 yerine 500 ile bildiren eski bir build; başka bir slug seçin ya da project_list ile mevcut projeyi bulun (PRJ-001).";
165
+ }
68
166
  if (context.path.includes('/row') && message.includes('Sequence contains no matching element')) {
69
167
  return " Muhtemel sebep: entity bir application'a bağlı değil — entity_patch ile applicationIds ekleyin (PLT-004).";
70
168
  }
@@ -72,7 +170,7 @@ function diagnoseKnownFailure(status, message, code, context) {
72
170
  return " Bilinen sınır (K-005/P-01): m2m, çoklu dosya ya da çoklu enum alanı entity_create gövdesindeyken platform junction KAYDINI açar ama fiziksel tabloyu kurmaz. Alanı entity_patch ile yeniden ekleyin; gateway'in entity_create'i bunu artık kendisi iki aşamaya bölüyor, elle kurulmuş eski kayıtlar etkilenir.";
73
171
  }
74
172
  if (message.includes('cannot be tracked') || message.includes('already being tracked')) {
75
- return " Bilinen sınır (K-006/P-02): operation:'get' tipli microservice'te PUT, izlenen Application grafiği yüzünden 500 veriyor. Geçici yol: microservice_delete + microservice_create ile yeniden kurun (gövdeyi önce microservice_get ile alın).";
173
+ return " Bilinen sınır (K-006/P-02): operation:'get' tipli microservice'te PUT, izlenen Application grafiği yüzünden 500 veriyordu. Regresyon 2026-08-16'da düzeltildi ve 2026-09-01'de canlı doğrulandı (İK koşusu M-9) — bu hatayı hâlâ görüyorsanız çalışan sürüm düzeltmeden eskidir. O sürümde geçici yol: microservice_delete + microservice_create ile yeniden kurun (gövdeyi önce microservice_get ile alın).";
76
174
  }
77
175
  if (code === 'RowValidationFailed' || message.includes('RowValidationFailed')) {
78
176
  return ' Gövdedeki alanlar doğru görünüyorsa muhtemel sebep istemcinin body alanını düşürmesidir (K-002): microservice_execute/debug çağrısında body bir NESNE ya da nesne DİZİSİ olmalı; boş gövdeyle gönderildiyse istemci şemayı desteklemiyor olabilir.';
@@ -91,7 +189,7 @@ export function mapPlatformError(status, payload, context) {
91
189
  if (validationMessages.length > 0) {
92
190
  return new PlatformApiError(`Doğrulama hatası (${location}): ${validationMessages.join(' ; ')}. Gövde şeması için ilgili ngapps://schemas kaynağına ya da mevcut bir kaydın *_get çıktısına bakın.`, status, code);
93
191
  }
94
- return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}`, status, code);
192
+ return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${importHint(code, context.path)}`, status, code);
95
193
  }
96
194
  if (status === 401) {
97
195
  const hint = context.authMode === 'local'
@@ -109,7 +207,7 @@ export function mapPlatformError(status, payload, context) {
109
207
  if (context.method !== 'GET' && /\/flows(\/|$)/.test(context.path)) {
110
208
  return new PlatformApiError(`Adımda referans verilen kayıt çözümlenemedi (${location}). Bu uçta 404 akışın kendisini DEĞİL bir adım argümanını kastediyor: flow-trigger adımının args'ında ne flowId ne triggerElementId var (ya da triggerElementId sayfadaki hiçbir elementId ile eşleşmiyor), setter adımının iteratees[].element'i bulunamıyor, ya da bir adım silinmiş bir komponente bakıyor. Adım argümanlarını page_schema_get'teki elementId'lerle karşılaştırın; flow-trigger'ın iki geçerli şekli için platform_guide('page') Kural 1c.`, status, code);
111
209
  }
112
- return new PlatformApiError(`Kayıt bulunamadı (${location}).${environmentHint} id/slug değerini ilgili *_list tool'uyla doğrulayın.`, status, code);
210
+ return new PlatformApiError(`Kayıt bulunamadı (${location}).${environmentHint} id/slug değerini ilgili *_list tool'uyla doğrulayın.${importHint(code, context.path)}`, status, code);
113
211
  }
114
212
  if (status === 409 || status === 422) {
115
213
  const projectConflictHint = status === 409 && context.method === 'POST' && /\/projects\/?$/.test(context.path)
@@ -117,7 +215,10 @@ export function mapPlatformError(status, payload, context) {
117
215
  : '';
118
216
  const validationMessages = collectValidationMessages(body);
119
217
  const detail = validationMessages.length > 0 ? ` → ${validationMessages.join(' ; ')}` : '';
120
- return new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code)}`, status, code);
218
+ const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}`, status, code);
219
+ if (typeof body.rowIndex === 'number')
220
+ rejected.rowIndex = body.rowIndex;
221
+ return rejected;
121
222
  }
122
223
  const fallbackValidationMessages = collectValidationMessages(body);
123
224
  const fallbackDetail = fallbackValidationMessages.length > 0 ? ` → ${fallbackValidationMessages.join(' ; ')}` : '';