@appaflytech/zeyrek-ai-mcp 0.0.43 → 0.0.45

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 (65) hide show
  1. package/dist/knowledge/backendConstraints.js +86 -2
  2. package/dist/knowledge/componentCatalog.js +63 -26
  3. package/dist/knowledge/componentEvents.js +6 -0
  4. package/dist/knowledge/connectDataBind.js +43 -6
  5. package/dist/knowledge/connectDataDefinition.js +74 -3
  6. package/dist/knowledge/connectDataRules.js +14 -0
  7. package/dist/knowledge/mobileComponentCatalog.js +66 -7
  8. package/dist/knowledge/mobileStore.js +61 -2
  9. package/dist/knowledge/platform/mobile.js +1 -1
  10. package/dist/knowledge/platform/mobileRules.js +2 -1
  11. package/dist/knowledge/platform/mobileTabs.js +31 -2
  12. package/dist/knowledge/platform/web.js +1 -1
  13. package/dist/knowledge/rules.js +195 -4
  14. package/dist/knowledge/textHooks.js +434 -0
  15. package/dist/platform/capture/capture.js +1 -1
  16. package/dist/platform/capture/readiness.js +4 -0
  17. package/dist/platform/errors.js +22 -2
  18. package/dist/prompts/mobilePublish.js +21 -0
  19. package/dist/resources/examples/flow-biometric-confirm.json +199 -0
  20. package/dist/resources/examples/microfunction-node-dependencies.json +19 -0
  21. package/dist/resources/guides/access.md +3 -3
  22. package/dist/resources/guides/component.md +20 -4
  23. package/dist/resources/guides/deployment.md +98 -0
  24. package/dist/resources/guides/entity.md +1 -1
  25. package/dist/resources/guides/expressions.md +5 -1
  26. package/dist/resources/guides/external-component.md +6 -4
  27. package/dist/resources/guides/merge.md +81 -4
  28. package/dist/resources/guides/microfunction.md +104 -9
  29. package/dist/resources/guides/microservice.md +34 -5
  30. package/dist/resources/guides/mobile-connect-data.md +70 -0
  31. package/dist/resources/guides/mobile-flow.md +64 -0
  32. package/dist/resources/guides/mobile-publish.md +161 -16
  33. package/dist/resources/guides/mobile-steps.md +17 -0
  34. package/dist/resources/guides/mobile.md +41 -23
  35. package/dist/resources/guides/page.md +11 -5
  36. package/dist/resources/guides/translation.md +95 -0
  37. package/dist/resources/knowledge.js +3 -1
  38. package/dist/resources/prompts/mobile-publish-prepare.md +89 -0
  39. package/dist/resources/schema-fingerprint.json +4 -4
  40. package/dist/resources/schemas/ApplyMicrofunctionArchiveChangesCommand.json +76 -0
  41. package/dist/resources/schemas/ConnectDataDescribeDto.json +44 -0
  42. package/dist/resources/schemas/ConnectDataResultDto.json +10 -0
  43. package/dist/resources/schemas/CreateMicrofunctionCommand.json +8 -0
  44. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +0 -4
  45. package/dist/resources/schemas/MobileStorePreflightScope.json +13 -0
  46. package/dist/resources/schemas/StartMobileBuildCommand.json +35 -1
  47. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +8 -0
  48. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +75 -0
  49. package/dist/server.js +2 -0
  50. package/dist/tools/common.js +6 -0
  51. package/dist/tools/components.js +19 -6
  52. package/dist/tools/connectData.js +9 -4
  53. package/dist/tools/deployments.js +1 -1
  54. package/dist/tools/designSchemaPatch.js +16 -4
  55. package/dist/tools/environments.js +3 -3
  56. package/dist/tools/files.js +1 -1
  57. package/dist/tools/flows.js +29 -3
  58. package/dist/tools/layouts.js +32 -12
  59. package/dist/tools/merges.js +8 -8
  60. package/dist/tools/microfunctions.js +53 -3
  61. package/dist/tools/mobileStore.js +83 -21
  62. package/dist/tools/pages.js +59 -24
  63. package/dist/tools/platformInfo.js +4 -2
  64. package/dist/tools/textHookGuard.js +116 -0
  65. package/package.json +2 -2
@@ -0,0 +1,13 @@
1
+ {
2
+ "schema": {
3
+ "enum": [
4
+ "store",
5
+ "test"
6
+ ],
7
+ "type": "string",
8
+ "format": ""
9
+ },
10
+ "components": {
11
+ "schemas": {}
12
+ }
13
+ }
@@ -18,6 +18,21 @@
18
18
  },
19
19
  "runnerScope": {
20
20
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileBuilds.MobileRunnerScope"
21
+ },
22
+ "packageType": {
23
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileBuilds.MobilePackageType"
24
+ },
25
+ "releaseTrack": {
26
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileBuilds.MobilePlayTrack"
27
+ },
28
+ "runnerId": {
29
+ "type": "string",
30
+ "format": "uuid",
31
+ "nullable": true
32
+ },
33
+ "environment": {
34
+ "type": "string",
35
+ "nullable": true
21
36
  }
22
37
  },
23
38
  "additionalProperties": false
@@ -36,7 +51,10 @@
36
51
  "enum": [
37
52
  "build",
38
53
  "testflight",
39
- "internal"
54
+ "internal",
55
+ "app-store-review",
56
+ "app-store-release",
57
+ "play-promote"
40
58
  ],
41
59
  "type": "string",
42
60
  "format": ""
@@ -48,6 +66,22 @@
48
66
  ],
49
67
  "type": "string",
50
68
  "format": ""
69
+ },
70
+ "NgApps.Domain.Contexts.Organization.MobileBuilds.MobilePackageType": {
71
+ "enum": [
72
+ "aab",
73
+ "apk"
74
+ ],
75
+ "type": "string",
76
+ "format": ""
77
+ },
78
+ "NgApps.Domain.Contexts.Organization.MobileBuilds.MobilePlayTrack": {
79
+ "enum": [
80
+ "closed",
81
+ "production"
82
+ ],
83
+ "type": "string",
84
+ "format": ""
51
85
  }
52
86
  }
53
87
  }
@@ -74,6 +74,14 @@
74
74
  },
75
75
  "nullable": true
76
76
  },
77
+ "dependencies": {
78
+ "type": "string",
79
+ "nullable": true
80
+ },
81
+ "testArguments": {
82
+ "type": "string",
83
+ "nullable": true
84
+ },
77
85
  "publish": {
78
86
  "type": "boolean"
79
87
  }
@@ -98,6 +98,13 @@
98
98
  },
99
99
  "nullable": true
100
100
  },
101
+ "notificationReminder": {
102
+ "type": "string",
103
+ "nullable": true
104
+ },
105
+ "releaseTargets": {
106
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreReleaseTargetsModel"
107
+ },
101
108
  "listing": {
102
109
  "type": "object",
103
110
  "additionalProperties": {
@@ -120,6 +127,9 @@
120
127
  "ads": {
121
128
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdsModel"
122
129
  },
130
+ "appLock": {
131
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAppLockModel"
132
+ },
123
133
  "expectedUpdatedAt": {
124
134
  "type": "string",
125
135
  "format": "date-time",
@@ -130,6 +140,18 @@
130
140
  },
131
141
  "components": {
132
142
  "schemas": {
143
+ "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreReleaseTargetsModel": {
144
+ "type": "object",
145
+ "properties": {
146
+ "ios": {
147
+ "type": "boolean"
148
+ },
149
+ "android": {
150
+ "type": "boolean"
151
+ }
152
+ },
153
+ "additionalProperties": false
154
+ },
133
155
  "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreListingModel": {
134
156
  "type": "object",
135
157
  "properties": {
@@ -210,6 +232,29 @@
210
232
  "copyright": {
211
233
  "type": "string",
212
234
  "nullable": true
235
+ },
236
+ "releaseType": {
237
+ "type": "string",
238
+ "nullable": true
239
+ },
240
+ "scheduledAt": {
241
+ "type": "string",
242
+ "format": "date-time",
243
+ "nullable": true
244
+ },
245
+ "phasedRelease": {
246
+ "type": "boolean"
247
+ },
248
+ "pricing": {
249
+ "type": "string",
250
+ "nullable": true
251
+ },
252
+ "territories": {
253
+ "type": "array",
254
+ "items": {
255
+ "type": "string"
256
+ },
257
+ "nullable": true
213
258
  }
214
259
  },
215
260
  "additionalProperties": false
@@ -228,6 +273,23 @@
228
273
  "releaseStatus": {
229
274
  "type": "string",
230
275
  "nullable": true
276
+ },
277
+ "contactPhone": {
278
+ "type": "string",
279
+ "nullable": true
280
+ },
281
+ "contactWebsite": {
282
+ "type": "string",
283
+ "nullable": true
284
+ },
285
+ "promoVideoUrl": {
286
+ "type": "string",
287
+ "nullable": true
288
+ },
289
+ "rolloutPercent": {
290
+ "type": "integer",
291
+ "format": "int32",
292
+ "nullable": true
231
293
  }
232
294
  },
233
295
  "additionalProperties": false
@@ -292,6 +354,19 @@
292
354
  },
293
355
  "additionalProperties": false
294
356
  },
357
+ "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAppLockModel": {
358
+ "type": "object",
359
+ "properties": {
360
+ "enabled": {
361
+ "type": "boolean"
362
+ },
363
+ "timeoutSeconds": {
364
+ "type": "integer",
365
+ "format": "int32"
366
+ }
367
+ },
368
+ "additionalProperties": false
369
+ },
295
370
  "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAppleCategoriesModel": {
296
371
  "type": "object",
297
372
  "properties": {
package/dist/server.js CHANGED
@@ -17,6 +17,7 @@ import { registerAccessTools } from "./tools/access.js";
17
17
  import { registerTranslationTools } from "./tools/translations.js";
18
18
  import { registerFlowTools } from "./tools/flows.js";
19
19
  import { registerKnowledgeResources } from "./resources/knowledge.js";
20
+ import { registerMobilePublishPrompt } from "./prompts/mobilePublish.js";
20
21
  import { registerSchemaResources } from "./resources/schemas.js";
21
22
  import { registerRuleResources } from "./resources/rules.js";
22
23
  import { instrumentServer } from "./observability/trace.js";
@@ -109,6 +110,7 @@ const moduleRegistrations = {
109
110
  mobile: (server, platform) => {
110
111
  registerMobileTools(server, platform);
111
112
  registerMobileStoreTools(server, platform);
113
+ registerMobilePublishPrompt(server);
112
114
  },
113
115
  };
114
116
  export function createGatewayServer(platform) {
@@ -67,6 +67,12 @@ export const pagingParams = {
67
67
  * ayrılırsa gateway kabul ettiği bir adı görüntüleyemez hâle gelir.
68
68
  */
69
69
  export const branchNamePattern = /^(?=.{3,31}$)[a-z][a-z0-9_-]*(\/[a-z0-9][a-z0-9_-]*)*$/;
70
+ /**
71
+ * Dal ve merge yazan sekiz tool'un açıklamasındaki ortak ön kontrol cümlesi (MRG-018; backend
72
+ * `CheckBranchWriteAllowedBehavior`, ADR 0022). Abonelik `project_get` yanıtında görünür, ajan
73
+ * isteği göndermeden eleyebilsin diye açıklamada durur.
74
+ */
75
+ export const templateSubscriptionLock = "Şablondan kurulup güncellemeleri alan projede (project_get → templateMetadata.installedTemplateReceiveUpdates: true) 400 döner (MRG-018): o projede değişiklik yalnız şablondan gelir.";
70
76
  const reservedEnvironmentNames = new Set(['development', 'production', 'public', 'information_schema', 'dev', 'view', 'api', 'files', 'hubs']);
71
77
  /** Sabit ortamlar, url sözcükleri ve postgres şemaları: dal adı olamazlar */
72
78
  export function isReservedEnvironmentName(name) {
@@ -12,6 +12,8 @@ import { applicationTypeField, resolveProfile } from '../knowledge/platform/reso
12
12
  import { mobileComponentNotes } from '../knowledge/platform/mobileRules.js';
13
13
  import { randomUUID } from 'node:crypto';
14
14
  import { z } from 'zod';
15
+ import { enforceTextHooks, textHookOverrideParam } from './textHookGuard.js';
16
+ import { attachFindings } from '../knowledge/policy.js';
15
17
  const typeParam = z.enum(['web', 'mobile']).optional().describe('Hedef platform (ComponentType); verilmezse uygulamanın tipi — mobil uygulamada mobile');
16
18
  const kindParam = z.enum(['component', 'widget']).optional()
17
19
  .describe("component: sayfaya konan tekil eleman. widget: kendi şeması olan kapsayıcı (container/modal/drawer)");
@@ -320,14 +322,16 @@ export function registerComponentTools(server, platform) {
320
322
  isDroppable: z.boolean().optional(),
321
323
  isSticky: z.boolean().optional(),
322
324
  isDeletable: z.boolean().optional(),
323
- isDefaultAutoHeight: z.boolean().optional()
325
+ isDefaultAutoHeight: z.boolean().optional(),
326
+ policyOverride: textHookOverrideParam
324
327
  },
325
328
  annotations: writes
326
- }, handle(config, async ({ project, environment, applicationId, ...body }) => {
329
+ }, handle(config, async ({ project, environment, applicationId, policyOverride, ...body }) => {
327
330
  if (body.kind === 'widget' && !body.widgetType) {
328
331
  throw new PlatformApiError("kind:'widget' için widgetType (container | modal | drawer) zorunludur.");
329
332
  }
330
333
  assertNoRetiredNodes(body.schema);
334
+ const textFindings = await enforceTextHooks(config, { platform, project, environment, applicationId, after: body.schema ?? [], where: `widget '${body.name}'` }, policyOverride);
331
335
  const wrapped = body.kind === 'widget' && body.widgetType && (body.schema ?? []).length > 0
332
336
  ? wrapWidgetRoot(body.schema ?? [], body.widgetType, await listComponents(platform, project, environment, applicationId).catch(() => []))
333
337
  : { schema: body.schema, notes: [] };
@@ -348,7 +352,7 @@ export function registerComponentTools(server, platform) {
348
352
  schema: [],
349
353
  ...Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined))
350
354
  }
351
- }).then(response => withNormalization(response, [...wrapped.notes, ...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes)]));
355
+ }).then(response => attachFindings(withNormalization(response, [...wrapped.notes, ...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes)]), textFindings));
352
356
  }));
353
357
  server.registerTool('component_update', {
354
358
  title: 'Komponent güncelle',
@@ -370,10 +374,11 @@ export function registerComponentTools(server, platform) {
370
374
  isDroppable: z.boolean().optional(),
371
375
  isSticky: z.boolean().optional(),
372
376
  isDeletable: z.boolean().optional(),
373
- isDefaultAutoHeight: z.boolean().optional()
377
+ isDefaultAutoHeight: z.boolean().optional(),
378
+ policyOverride: textHookOverrideParam
374
379
  },
375
380
  annotations: updates
376
- }, handle(config, async ({ project, environment, applicationId, slug, ...changes }) => {
381
+ }, handle(config, async ({ project, environment, applicationId, slug, policyOverride, ...changes }) => {
377
382
  // PAGE-037: UpdateComponentCommand de UpsertRange'ten geçer, blocks'suz widget düğümü kayıtta
378
383
  // 500 verir. component_create bu kapıdan geçmez — CreateComponentCommand revizyon kapsamı
379
384
  // açmıyor ve platform orada blocks'suz düğümü blocks: [] gibi kaydediyor (2026-10-04, kod okuması).
@@ -387,6 +392,14 @@ export function registerComponentTools(server, platform) {
387
392
  ? await fetchHost(platform, 'component', project, environment, applicationId, String(current.id))
388
393
  : current;
389
394
  const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
395
+ const textFindings = changes.schema === undefined
396
+ ? []
397
+ : await enforceTextHooks(config, {
398
+ platform, project, environment, applicationId,
399
+ after: changes.schema,
400
+ before: async () => detailed.schema,
401
+ where: `widget '${String(detailed.name ?? slug)}'`
402
+ }, policyOverride);
390
403
  const widgetType = changes.widgetType ?? detailed.widgetType;
391
404
  const wrapped = detailed.kind === 'widget' && typeof widgetType === 'string' && changes.schema !== undefined && changes.schema.length > 0
392
405
  ? wrapWidgetRoot(changes.schema, widgetType, components)
@@ -400,7 +413,7 @@ export function registerComponentTools(server, platform) {
400
413
  const sizes = overrides.schema === undefined ? [] : normalizeSizeStrings(overrides.schema);
401
414
  const body = componentUpdateBody({ ...detailed, applicationId }, overrides);
402
415
  await platform.tenant('PUT', project, `/components/${applicationId}/${slug}`, { environment, body });
403
- return withNormalization({ slug, changed: Object.keys(overrides), preserved: { schemaNodes: body.schema.length } }, [...wrapped.notes, ...componentDefaultsNote(fixed), ...blockSizeNote(clamped), ...heightTypeNote(heights), ...sizeStringNote(sizes)]);
416
+ return attachFindings(withNormalization({ slug, changed: Object.keys(overrides), preserved: { schemaNodes: body.schema.length } }, [...wrapped.notes, ...componentDefaultsNote(fixed), ...blockSizeNote(clamped), ...heightTypeNote(heights), ...sizeStringNote(sizes)]), textFindings);
404
417
  }));
405
418
  server.registerTool('component_delete', {
406
419
  title: 'Komponent sil',
@@ -8,7 +8,8 @@ import { impersonationHeaders } from '../platform/impersonation.js';
8
8
  import { handle } from '../platform/responses.js';
9
9
  import { PlatformApiError } from '../platform/errors.js';
10
10
  import { applyPolicyMode, enforcePolicy, attachFindings } from '../knowledge/policy.js';
11
- import { connectDataOpIssues, CONNECT_DATA_OPERATORS, CONNECT_DATA_SOURCE_KINDS, lookupSchema, selectedPaths } from '../knowledge/connectDataDefinition.js';
11
+ import { connectDataOpIssues, CONNECT_DATA_OPERATORS, CONNECT_DATA_SOURCE_KINDS, groupingSchema, lookupSchema, mobileBehaviorSchema, mobileCardSchema, selectedPaths } from '../knowledge/connectDataDefinition.js';
12
+ import { resolveProfile } from '../knowledge/platform/resolve.js';
12
13
  import { CONNECT_DATA_EXECUTE_OPS, CONNECT_DATA_WRITE_OPS, CONNECT_DATA_WRITE_ROW_LIMIT, enrichRowErrors, executeFindings } from '../knowledge/connectDataExecute.js';
13
14
  import { bindOps, buildDefinition, enumOptionItems, capabilityUnderRecordedNames, carryFieldDetails, anonymousWriteFindings, lookupReadabilityFindings, userEyeFindings, sameSourceFindings, editFindings, CONNECT_DATA_PREVIEW_LIMIT, fieldListItemProps, inputFromExisting, readAllPageSize, invertRenames, filterFindings, findSchemaNode, readPropValue, renameBindInput, renameColumnItems, renameDefinitionPaths, renamesFromDrift, resolveConnectable, mergeOptionItems, microserviceReferenceId, stampExpandIds, stateReferenceFindings, stateReferenceNames, unboundStateNames, previewStateFindings } from '../knowledge/connectDataBind.js';
14
15
  import { parseVariableEntries } from '../knowledge/normalize.js';
@@ -261,7 +262,7 @@ export function registerConnectDataTools(server, platform) {
261
262
  }));
262
263
  server.registerTool('connect_data_bind', {
263
264
  title: 'Prop\'u veri kaynağına bağla',
264
- description: "Tek çağrıda bağlar: bileşenin bağlanabilir prop kaydını (component_get.connectData) ve kaynağın kabiliyetini okur, tanımı kurar ve CD-001…004/006/007/008/009/010/011/013/014/015/016/017/018 bulgularını istek gitmeden üretir (CD-018: dosya alanı edit.editable'a konamaz, platform kaydı 422 connect-data-editable-file ile reddederdi; kolonların isFilterable'ı yalnız filterable listesindeki alanlarda açılır); bileşen bir layout'taysa pageId yerine layoutId, widget içindeyse componentId verilir (yazma o kaydın şema ucuna gider, sürüm koşulu yoktur, CD-014 koşmaz); block bulgusu policyOverride ile de yazılmaz: CD-004/CD-013 şekil kuralları op gövdesinde yine reddedilir; set_connect_data (+ kolon listesi, + alan adı prop'ları, + pano kolonu isteyen bileşende rolün enum değerlerinden seçenek listesi) tek atomik PATCH olarak yazılır, sonra şema yeniden okunup prop'un connectData olarak kaydedildiği doğrulanır. Roller verilmezse yalnız ZORUNLU roller önerilir (value ← birincil anahtar, label ← görünen alan) — isteğe bağlı roller (caption, color, isDisabled…) açıkça verilmedikçe eşlenmez. Liste bileşeninde (fieldsFrom) kolonlar bileşenin kendi kolon şemasından yalnız KÖK alanlardan üretilir (expand yolu satıra okunur, kolon olmaz). VAR OLAN BAĞLANTIDA aynı kaynakla çağrılırsa verilmeyen parçalar (alanlar, expand, roller, filtre, sıralama, sayfa boyu, filterable/searchable, edit) ve alanların lookup ayarları kayıtlı tanımdan taşınır; alan listesi değişmediyse ve columns verilmediyse kolon listesi yeniden yazılmaz, değiştiyse kayıtlı listeyle birleştirilir (kalan kolonların sırası ve ayarı korunur, çıkan alan düşer, yeni alan sona eklenir). Kaynakta yeniden adlanan alan id ile eşleşir: girdide kayıtlı ya da güncel ad verilebilir, tanım güncel adlarla kurulur ve sayfanın gördüğü kayıtlı adla yazılır (rebind.renamed). Expand alt alanlarına hedef alan id'si yazılır; kayıtlı lookup ayarları kimlikleriyle taşınır. Seçim bileşeninde (connectData.props[prop].lookup) entity kaynağına YENİ bağlamada kök lookup açık yazılır ({ fields: [], mode: 'modal' } — alanın yanında ↗, pencerede türetilmiş kolonlar); yeniden bağlamada kayıtlı kök lookup korunur, kök lookup'sız eski bağlantı kapalı kalır; lookup girdisi ayarlar, null kapatır. Block bulgusu varsa hiçbir şey yazılmaz; dryRun: true yalnız tanımı, op'ları ve bulguları döner.",
265
+ description: "Tek çağrıda bağlar: bileşenin bağlanabilir prop kaydını (component_get.connectData) ve kaynağın kabiliyetini okur, tanımı kurar ve CD-001…004/006/007/008/009/010/011/013/014/015/016/017/018/019/020 bulgularını istek gitmeden üretir (CD-018: dosya alanı edit.editable'a konamaz, platform kaydı 422 connect-data-editable-file ile reddederdi; CD-019/020: gruplu liste grouping ister, başka bileşen almaz; kolonların isFilterable'ı yalnız filterable listesindeki alanlarda açılır); bileşen bir layout'taysa pageId yerine layoutId, widget içindeyse componentId verilir (yazma o kaydın şema ucuna gider, sürüm koşulu yoktur, CD-014 koşmaz); block bulgusu policyOverride ile de yazılmaz: CD-004/CD-013 şekil kuralları op gövdesinde yine reddedilir; set_connect_data (+ kolon listesi, + alan adı prop'ları, + pano kolonu isteyen bileşende rolün enum değerlerinden seçenek listesi) tek atomik PATCH olarak yazılır, sonra şema yeniden okunup prop'un connectData olarak kaydedildiği doğrulanır. Roller verilmezse yalnız ZORUNLU roller önerilir (value ← birincil anahtar, label ← görünen alan) — isteğe bağlı roller (caption, color, isDisabled…) açıkça verilmedikçe eşlenmez. Liste bileşeninde (fieldsFrom) kolonlar bileşenin kendi kolon şemasından yalnız KÖK alanlardan üretilir (expand yolu satıra okunur, kolon olmaz). VAR OLAN BAĞLANTIDA aynı kaynakla çağrılırsa verilmeyen parçalar (alanlar, expand, roller, filtre, sıralama, sayfa boyu, filterable/searchable, edit) ve alanların lookup ayarları kayıtlı tanımdan taşınır; alan listesi değişmediyse ve columns verilmediyse kolon listesi yeniden yazılmaz, değiştiyse kayıtlı listeyle birleştirilir (kalan kolonların sırası ve ayarı korunur, çıkan alan düşer, yeni alan sona eklenir). Kaynakta yeniden adlanan alan id ile eşleşir: girdide kayıtlı ya da güncel ad verilebilir, tanım güncel adlarla kurulur ve sayfanın gördüğü kayıtlı adla yazılır (rebind.renamed). Expand alt alanlarına hedef alan id'si yazılır; kayıtlı lookup ayarları kimlikleriyle taşınır. Seçim bileşeninde (connectData.props[prop].lookup) entity kaynağına YENİ bağlamada kök lookup açık yazılır ({ fields: [], mode: 'modal' } — alanın yanında ↗, pencerede türetilmiş kolonlar); yeniden bağlamada kayıtlı kök lookup korunur, kök lookup'sız eski bağlantı kapalı kalır; lookup girdisi ayarlar, null kapatır. MOBİL uygulamada telefon varsayılanları yazılır: kök lookup'a mobileMode 'sheet' ve mobileRow.title ← label alanı, liste/seçim bağlantısına behavior.mobile { infiniteScroll: true }. Block bulgusu varsa hiçbir şey yazılmaz; dryRun: true yalnız tanımı, op'ları ve bulguları döner.",
265
266
  inputSchema: {
266
267
  project: projectParam,
267
268
  environment: environmentParam,
@@ -295,7 +296,10 @@ export function registerConnectDataTools(server, platform) {
295
296
  refresh: z.object({ afterWrite: z.enum(['reload', 'merge']).optional(), poll: z.number().int().min(1).nullable().optional() }).strict().optional(),
296
297
  columns: z.union([z.literal('auto'), z.array(z.record(z.unknown()))]).optional().describe("Liste bileşeninde (fieldsFrom) kolon listesi: 'auto' bileşenin kolon şemasından ve kabiliyetten üretir; dizi verilirse öğe değerleri her seviyede static Prop'a sarılarak yazılır (sarmal değerlere dokunulmaz). Var olan bağlantıda verilmezse ve alanlar değişmediyse kolonlar korunur"),
297
298
  fixDrift: z.boolean().optional().describe('true: kaynaktan silinmiş alanları tanımdan düşürür (CD-008)'),
298
- lookup: lookupSchema.nullable().optional().describe("Seçim bileşeninin KENDİ kaynağı üzerindeki lookup penceresi (tanımın kök lookup'ı; alan lookup'ıyla aynı biçim: { fields: [{ slug }], behavior: { filterable, searchable }, sort, page, mode: 'modal' | 'drawer' }; fields boşsa platform türetir). Verilmezse yeni bağlamada açık yazılır, yeniden bağlamada kayıtlı ayar korunur; null kapatır. Yalnız entity kaynağında (CD-013)."),
299
+ lookup: lookupSchema.nullable().optional().describe("Seçim bileşeninin KENDİ kaynağı üzerindeki lookup penceresi (tanımın kök lookup'ı; alan lookup'ıyla aynı biçim: { fields: [{ slug }], behavior: { filterable, searchable }, sort, page, mode: 'modal' | 'drawer' }; fields boşsa platform türetir). Verilmezse yeni bağlamada açık yazılır, yeniden bağlamada kayıtlı ayar korunur; null kapatır. Yalnız entity kaynağında (CD-013). Telefonda: mobileMode 'sheet' (alt sayfa) | 'fullscreen', mobileRow { title, subtitle?, trailing? } lookup alanlarından (dışındaysa CD-004); MOBİL uygulamada verilmezse mobileMode 'sheet' ve mobileRow.title = label alanı yazılır."),
300
+ mobile: mobileBehaviorSchema.nullable().optional().describe("Telefon davranışı (behavior.mobile): seçim bileşeninde search · infiniteScroll · pullToRefresh · recentSelections, datatable'da filterChips · swipeDelete · infiniteScroll · pullToRefresh. MOBİL uygulamada liste/seçim bağlantısında verilmezse { infiniteScroll: true } (sayfalama tanımın page.mode'uyla); yeniden bağlamada kayıtlı ayar korunur; null kaldırır."),
301
+ mobileCard: mobileCardSchema.nullable().optional().describe("Datatable kart görünümü telefonda (behavior.mobileCard): { title, fields?: [...], trailing? } seçili yollardan (dışındaysa CD-004). Verilmezse kayıtlı ayar korunur, yoksa telefonda ilk metin alan başlıktır; null kaldırır."),
302
+ grouping: groupingSchema.nullable().optional().describe("Gruplu listenin bölümlemesi (kök ADR 0019): { by: '<seçili alan>', sectionOrder: 'asc' | 'desc' | 'asis', dateBucket?: 'day' | 'week' | 'month' (yalnız date/datetime alan), otherSectionTitle?, stickyHeaders?, showCounts? }. Satırları bölümleyen bileşende (connectData.props.<prop>.wizardSteps 'grouping' — mobilde grouped-list) ZORUNLU (yoksa CD-019 block), başka bileşende yazılmaz (CD-020 block). Yalnız entity kaynağında (CD-013); sunucu sırayı gruplama alanıyla başlatır (azalanda boşlar sonda), date alanında kovayı satıra __section yazar (datetime cihazda kovalanır); gruplama alanı sıralanabilir olmalı (asis hariç). Yeniden bağlamada kayıtlı ayar korunur."),
299
303
  params: z.record(z.unknown()).optional().describe("Kaynak parametreleri (query/workflow/microservice): sabit değer ya da '$ad' sayfa değişkeni ({ yil: 2026, sube: '$secilenSube' }). Adlar connect_data_describe source.parameters'tan (CD-013); '$ad' page_state'te olmalı (CD-014). Entity/view parametre almaz — filtrede '$ad' kullanın."),
300
304
  baseVersion: z.string().optional().describe('Sayfa sürümü; değiştiyse 409 ve hiçbir op yazılmaz. Verilmezse bu çağrıda okunan sürüm'),
301
305
  dryRun: z.boolean().optional(),
@@ -342,7 +346,8 @@ export function registerConnectDataTools(server, platform) {
342
346
  const effectiveInput = readAll === undefined ? carriedInput : { ...carriedInput, pageSize: readAll };
343
347
  const sourceCapability = await describeSource(project, environment, { source: input.source });
344
348
  const capability = isSameSource ? { ...sourceCapability, drift: existingDrift } : sourceCapability;
345
- const built = buildDefinition(effectiveInput, capability, connectable, Object.keys(connectData?.props ?? {}), fieldListItemProps(componentDefinition.props, connectable?.fieldsFrom));
349
+ const designer = (await resolveProfile(platform, applicationId)).profile.platform;
350
+ const built = buildDefinition(effectiveInput, capability, connectable, Object.keys(connectData?.props ?? {}), fieldListItemProps(componentDefinition.props, connectable?.fieldsFrom), designer);
346
351
  const carried = carryFieldDetails(built.definition, isSameSource ? working : null);
347
352
  const stamped = input.source.kind === 'entity' && carried.fields.some(field => field.expand?.some(child => !child.id))
348
353
  ? stampExpandIds(carried, await describeSource(project, environment, { definition: carried }))
@@ -26,7 +26,7 @@ export function registerDeploymentTools(server, platform) {
26
26
  const config = platform.config;
27
27
  server.registerTool('deployment_start', {
28
28
  title: 'Deployment başlat',
29
- description: 'Projeyi kaynak environment\'tan hedef environment\'a deploy eder. Asenkron bir saga\'dır: sonuç bu yanıtta bitmez, deployment_status ile izleyin. Environment id\'lerini project_get çıktısından alın. BPM: süreç tanımları ve iş takvimleri taşınır — hedefte farklı aktif model yeni sürüm olarak yayınlanır (production\'da gelişte aktif), hedefin uyarlaması korunur; uyarlama, örnek, görev ve defter taşınmaz; kaynakta olmayan tanım silinir, açık örneği varsa emekliye ayrılır. KAYNAK YALNIZ DEVELOPMENT\'TIR: dal ya da production kaynak verilirse 400 (CantDeployFromBranchException) — dalı önce development\'a merge edin. ÖN DENETİM: deploy başlamadan production verisi denetlenir; veriyi bozacak ya da silecek her değişiklik 400 (DeploymentDataRiskException) ile listelenir ve deploy BAŞLAMAZ — biçim \'tablo.alan: tür (satır)\', türler not-null | unique | field-removed | table-removed | localization-move. Kayıt taşıması \'all\' olan tabloda production development\'ın AYNASIDIR (kaynakta olmayan satır ve çevirisi silinir). Microfunction\'ın yalnız YAYINLANMIŞ kodu taşınır ve hedefte yayınlanır; hiç yayınlanmamış olan hedefte taslak kalır. Zamanlanmış işin duraklatma durumu taşınır. YAYINLANMAMIŞ TASLAK TAŞINMAZ: kaynakta taslağı olan süreçler yanıtta unpublishedDrafts ({ slug, name, draftVersionNumber, activeVersionNumber }) ve note ile listelenir; hedefte aktif sürüm kalır. Taşımak için taslağı kaynakta yayınlayın (requireGreenScenarios açıksa önce senaryoları taslak üzerinde koşun), sonra yeniden deploy edin.',
29
+ description: 'Projeyi kaynak environment\'tan hedef environment\'a deploy eder. Asenkron bir saga\'dır: sonuç bu yanıtta bitmez, deployment_status ile izleyin. Environment id\'lerini project_get çıktısından alın. BPM: süreç tanımları ve iş takvimleri taşınır — hedefte farklı aktif model yeni sürüm olarak yayınlanır (production\'da gelişte aktif), hedefin uyarlaması korunur; uyarlama, örnek, görev ve defter taşınmaz; kaynakta olmayan tanım silinir, açık örneği varsa emekliye ayrılır. KAYNAK YALNIZ DEVELOPMENT\'TIR: dal ya da production kaynak verilirse 400 (CantDeployFromBranchException) — dalı önce development\'a merge edin. ÖN DENETİM: deploy başlamadan production verisi denetlenir; veriyi bozacak ya da silecek her değişiklik 400 (DeploymentDataRiskException) ile listelenir ve deploy BAŞLAMAZ — biçim \'tablo.alan: tür (satır)\', türler not-null | unique | field-removed | table-removed | localization-move. YALNIZ TANIM TAŞINIR: tablo satırları ancak tabloda recordTransferMode (\'all\' | \'specific\' + transferRowIds) açıksa gider, çeviri ve junction satırları ana tabloyla birlikte. Kayıt taşıması \'all\' olan tabloda production development\'ın AYNASIDIR (kaynakta olmayan satır ve çevirisi silinir). DOSYA yalnız id referansıyla gider (file tipli prop { type:\'file\', ref:<fileId> } ya da taşınan satırın dosya sütunu); sayfaya düz yazılan /d/ serve URL\'si /p/\'ye çevrilir ama dosya kopyalanmaz, production\'da 404 döner — platform_guide(\'deployment\'). Microfunction\'ın yalnız YAYINLANMIŞ kodu taşınır ve hedefte yayınlanır; hiç yayınlanmamış olan hedefte taslak kalır. Zamanlanmış işin duraklatma durumu taşınır. YAYINLANMAMIŞ TASLAK TAŞINMAZ: kaynakta taslağı olan süreçler yanıtta unpublishedDrafts ({ slug, name, draftVersionNumber, activeVersionNumber }) ve note ile listelenir; hedefte aktif sürüm kalır. Taşımak için taslağı kaynakta yayınlayın (requireGreenScenarios açıksa önce senaryoları taslak üzerinde koşun), sonra yeniden deploy edin.',
30
30
  inputSchema: { projectId: guid, sourceEnvironmentId: guid, targetEnvironmentId: guid },
31
31
  annotations: writes
32
32
  }, handle(config, async ({ projectId, ...body }) => {
@@ -13,6 +13,9 @@ import { slotWarning } from './layouts.js';
13
13
  import { batchEnvelopeNote, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
14
14
  import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
15
15
  import { boundFlowSteps, layoutPatchWarnings, screenProfileWarnings } from './profileWrite.js';
16
+ import { enforceTextHooks, textHookOverrideParam } from './textHookGuard.js';
17
+ import { overwrittenValues } from '../knowledge/textHooks.js';
18
+ import { attachFindings } from '../knowledge/policy.js';
16
19
  import { z } from 'zod';
17
20
  const HOSTS = {
18
21
  layout: {
@@ -54,10 +57,11 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
54
57
  ops: opsParam.optional(),
55
58
  operations: opsParam.optional().describe("'ops' ile aynı alan (eşanlamlı)"),
56
59
  allowFlowDeletion: z.boolean().optional().describe(`true: bu yama bir akışı referanssız bırakacaksa yine de uygulanır. Varsayılan false — platform ${host === 'layout' ? 'layout' : 'widget'} kaydında da referanssız akışı KALICI siler ve gateway isteği önden reddeder (G-21).`),
57
- view: writeViewParam
60
+ view: writeViewParam,
61
+ policyOverride: textHookOverrideParam
58
62
  },
59
63
  annotations: updates
60
- }, handle(config, async ({ project, environment, applicationId, id, ops, operations, allowFlowDeletion, view }) => {
64
+ }, handle(config, async ({ project, environment, applicationId, id, ops, operations, allowFlowDeletion, view, policyOverride }) => {
61
65
  const opened = openBatchEnvelopes(spec.tool, microOps(spec.tool, ops, operations).map(op => op));
62
66
  const allOps = opened.ops;
63
67
  const removeEventOps = allOps.filter(entry => entry.op === 'remove_event');
@@ -83,6 +87,14 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
83
87
  const events = host === 'layout' ? record.events : undefined;
84
88
  const normalized = normalizeSchemaOps(opRecords);
85
89
  assertOpBodies(opRecords, propTypeOps);
90
+ // TR-005 / TR-006: yamanın yükü op'ların EZDİĞİ kayıtlı değerle karşılaştırılır — orada zaten duran kanca eskidir.
91
+ const textFindings = await enforceTextHooks(config, {
92
+ platform, project, environment, applicationId,
93
+ after: [...opRecords, ...propTypeOps],
94
+ before: async () => overwrittenValues(nodes, [...opRecords, ...propTypeOps]),
95
+ hostTree: async () => nodes,
96
+ where: `${host === 'layout' ? 'layout' : 'widget'} yaması '${String(record.name ?? record.slug ?? id)}'`
97
+ }, policyOverride);
86
98
  // CD-005 layout ve widget host'unda da: bağlı prop'u (iç içe yol dahil) ezen yazma ve bağlı olmayan prop'ta
87
99
  // remove_connect_data okunan ağaca bakarak reddedilir (gövde denetimi assertOpBodies'te koştu).
88
100
  const connectedProps = connectedPropKeys(nodes);
@@ -184,7 +196,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
184
196
  opDrops = emptyWriteDiff();
185
197
  }
186
198
  }
187
- return withWriteDiff(withNormalization(response, [
199
+ return attachFindings(withWriteDiff(withNormalization(response, [
188
200
  ...batchEnvelopeNote(opened),
189
201
  ...componentNameNote(normalized.names),
190
202
  ...sectionNotes,
@@ -192,6 +204,6 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
192
204
  ...validationNote(normalized.validations),
193
205
  ...catalogNotes,
194
206
  ...replacePropNote(normalized.props)
195
- ]), opDrops);
207
+ ]), opDrops), textFindings);
196
208
  }));
197
209
  }
@@ -1,4 +1,4 @@
1
- import { branchNamePattern, destructive, guid, isReservedEnvironmentName, readOnly, writes } from './common.js';
1
+ import { branchNamePattern, destructive, guid, isReservedEnvironmentName, readOnly, templateSubscriptionLock, writes } from './common.js';
2
2
  import { PlatformApiError } from '../platform/errors.js';
3
3
  import { handle } from '../platform/responses.js';
4
4
  import { z } from 'zod';
@@ -20,7 +20,7 @@ export function registerEnvironmentTools(server, platform) {
20
20
  }, handle(config, ({ projectId }) => platform.org('GET', `/environments/${projectId}`)));
21
21
  server.registerTool('environment_create', {
22
22
  title: 'Dal environment\'ı aç',
23
- description: "Projede yeni bir dal environment'ı açar: kendi PG şeması kurulur ve development'ın tanımları oraya kopyalanır. Asenkron bir saga'dır — yanıt 202 döner, dal hemen kullanılabilir DEĞİLDİR. includeRecords true ise projenin KENDİ veritabanındaki bütün veri tablolarının satırları da kopyalanır (çok dilli companion tablolar ve junction'lar dahil; files/folders ve audit trail tabloları hariç, dış datasource'lara dokunulmaz) — entity'de kayıt taşıması ayarı açık olmasa bile. Satırlar merge'de geri taşınmaz.",
23
+ description: `Projede yeni bir dal environment'ı açar: kendi PG şeması kurulur ve development'ın tanımları oraya kopyalanır. Asenkron bir saga'dır — yanıt 202 döner, dal hemen kullanılabilir DEĞİLDİR. includeRecords true ise projenin KENDİ veritabanındaki bütün veri tablolarının satırları da kopyalanır (çok dilli companion tablolar ve junction'lar dahil; files/folders ve audit trail tabloları hariç, dış datasource'lara dokunulmaz) — entity'de kayıt taşıması ayarı açık olmasa bile. Merge'de yalnız kayıt taşıması ayarı açık tabloların satırları üç yollu geri taşınır (rehber: merge, Tablo kayıtları). ${templateSubscriptionLock}`,
24
24
  inputSchema: { projectId: guid, name: branchName, includeRecords: z.boolean().optional().describe('Projenin kendi veritabanındaki bütün tabloların satırları da kopyalansın mı (varsayılan false)') },
25
25
  annotations: writes
26
26
  }, handle(config, async ({ projectId, name, includeRecords }) => {
@@ -33,7 +33,7 @@ export function registerEnvironmentTools(server, platform) {
33
33
  }));
34
34
  server.registerTool('environment_delete', {
35
35
  title: 'Dal environment\'ını sil',
36
- description: "Bir dal environment'ını ve PG şemasını siler. Sabit environment'lar (development, production) silinemez — gateway bunu HTTP'den önce reddeder. Yalnız 'ready' ya da 'error' durumundaki dal silinebilir; silme asenkrondur.",
36
+ description: `Bir dal environment'ını ve PG şemasını siler. Sabit environment'lar (development, production) silinemez — gateway bunu HTTP'den önce reddeder. Yalnız 'ready', 'merged' ya da 'error' durumundaki dal silinebilir; 'merged' dal applied-with-drift merge'ünden artakalandır (merge rehberi §Merge bittikten sonra). Silme asenkrondur. ${templateSubscriptionLock}`,
37
37
  inputSchema: { projectId: guid, environmentId: guid },
38
38
  annotations: destructive
39
39
  }, handle(config, async ({ projectId, environmentId }) => {
@@ -104,7 +104,7 @@ export function registerFileTools(server, platform) {
104
104
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/files/${id}`, { environment })));
105
105
  server.registerTool('file_upload', {
106
106
  title: 'Dosya yükle',
107
- description: "Dosyayı projenin depolamasına yükler ve FileDto (id + serve adresleri) döner. İçerik ÜÇ yoldan verilir: `text` — düz metin içerik (SVG, CSV, Markdown, JSON…) + `filename`; base64'e çevirmeye gerek yok, bunu gateway yapar. `path` — gateway sürecinin okuyabildiği yerel yol (büyük ve ikili dosyalar için). `content` — base64 (8 MB'a kadar) + `filename`. Platform sınırı 50 MB; dosya adı \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez. Yüklenen dosya sayfada `image`/`file-viewer` komponentiyle gösterilir ya da satır alanına yol olarak yazılır.",
107
+ description: "Dosyayı projenin depolamasına yükler ve FileDto (id + serve adresleri) döner. İçerik ÜÇ yoldan verilir: `text` — düz metin içerik (SVG, CSV, Markdown, JSON…) + `filename`; base64'e çevirmeye gerek yok, bunu gateway yapar. `path` — gateway sürecinin okuyabildiği yerel yol (büyük ve ikili dosyalar için). `content` — base64 (8 MB'a kadar) + `filename`. Platform sınırı 50 MB; dosya adı \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez. Yüklenen dosya sayfada `image`/`file-viewer` komponentiyle gösterilir ya da satır alanına yol olarak yazılır. DEPLOY dosyayı yalnız id referansıyla production'a taşır (sayfada file tipli prop `{ type:'file', ref:<fileId> }` ya da kayıt taşıması açık tablonun dosya sütunu); sayfaya düz yazılan serve URL'sinin öneki /d/ → /p/ çevrilir ama dosya kopyalanmaz ve production'da 404 döner, production'da bu araç da 400 CantWriteInProductionException ile reddedilir — platform_guide('deployment') §Dosyalar.",
108
108
  inputSchema: {
109
109
  project: projectParam,
110
110
  environment: environmentParam,
@@ -15,6 +15,7 @@ import { functionCallRefs } from '../knowledge/functionGuards.js';
15
15
  import { existingMatch, ifExistsParam, platformRename, withRename } from '../platform/ifExists.js';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { flowProfileWarnings } from './profileWrite.js';
18
+ import { enforceTextHooks } from './textHookGuard.js';
18
19
  import { jsonArray } from '../platform/jsonArray.js';
19
20
  import { z } from 'zod';
20
21
  /**
@@ -448,6 +449,14 @@ export function registerFlowTools(server, platform) {
448
449
  ];
449
450
  const existing = await existingFlowMatch(platform, host, project, environment, applicationId, hostId, hostRecord, name, slug, flowId, ifExists);
450
451
  const writtenFlowId = existing === undefined ? resolvedFlowId : String(existing.id);
452
+ // TR-005 / TR-006: ifExists:'update' mevcut akışı yeniden yazar — onda zaten duran kanca eskidir.
453
+ const textFindings = await enforceTextHooks(config, {
454
+ platform, project, environment, applicationId,
455
+ after: { steps, errorSteps: errorSteps ?? [] },
456
+ before: async () => existing === undefined ? undefined : unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, String(existing.id)), { environment })),
457
+ hostTree: async () => hostNodes(hostRecord),
458
+ where: `akış '${name}'`
459
+ }, policyOverride);
451
460
  const parameterWarnings = [
452
461
  ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
453
462
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
@@ -481,7 +490,7 @@ export function registerFlowTools(server, platform) {
481
490
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
482
491
  ...(platformWarnings.length > 0 ? { platformWarnings } : {}),
483
492
  hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
484
- }, normalized), drops.renamed), drops), findings);
493
+ }, normalized), drops.renamed), drops), [...findings, ...textFindings]);
485
494
  }));
486
495
  server.registerTool('flow_update', {
487
496
  title: 'Akış güncelle',
@@ -525,6 +534,16 @@ export function registerFlowTools(server, platform) {
525
534
  ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride).filter(finding => finding.ruleId === 'FLOW-027'))
526
535
  ]
527
536
  : [];
537
+ const textFindings = steps || errorSteps
538
+ ? await enforceTextHooks(config, {
539
+ platform, project, environment, applicationId,
540
+ // Birleşik son hâl: yalnız steps gönderilip kayıtlı errorSteps'teki kanca steps'e kopyalanırsa o kopya yenidir.
541
+ after: { steps: flow.steps, errorSteps: flow.errorSteps },
542
+ before: async () => ({ steps: current.steps ?? [], errorSteps: current.errorSteps ?? [] }),
543
+ hostTree: async () => hostNodes(hostRecord),
544
+ where: `akış '${String(flow.name ?? flowId)}'`
545
+ }, policyOverride)
546
+ : [];
528
547
  const parameterWarnings = [
529
548
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
530
549
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
@@ -543,7 +562,7 @@ export function registerFlowTools(server, platform) {
543
562
  });
544
563
  const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
545
564
  return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
546
- ...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), findings);
565
+ ...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), [...findings, ...textFindings]);
547
566
  }));
548
567
  server.registerTool('flow_patch', {
549
568
  title: "Akışa patch uygula",
@@ -579,7 +598,14 @@ export function registerFlowTools(server, platform) {
579
598
  const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
580
599
  const findings = [
581
600
  ...enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'update' }, policyOverride)),
582
- ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(flow.steps, flow.errorSteps, hostNodes(hostRecord)), policyOverride))
601
+ ...enforcePolicy(config.policyMode === 'off' ? [] : applyPolicyMode(config, connectedSetterFindings(flow.steps, flow.errorSteps, hostNodes(hostRecord)), policyOverride)),
602
+ ...await enforceTextHooks(config, {
603
+ platform, project, environment, applicationId,
604
+ after: { steps: flow.steps, errorSteps: flow.errorSteps },
605
+ before: async () => ({ steps: current.steps ?? [], errorSteps: current.errorSteps ?? [] }),
606
+ hostTree: async () => hostNodes(hostRecord),
607
+ where: `akış '${String(current.name ?? flowId)}'`
608
+ }, policyOverride)
583
609
  ];
584
610
  const parameterWarnings = [
585
611
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),