@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
@@ -6,6 +6,11 @@ function record(value) {
6
6
  function text(value) {
7
7
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
8
8
  }
9
+ function badgeOf(value) {
10
+ const badge = record(value);
11
+ const ref = badge?.source === 'state' ? text(badge.ref) : undefined;
12
+ return ref ? { source: 'state', ref } : undefined;
13
+ }
9
14
  /** Öğenin sayfa id'si: `{ page }` ya da düz metin (`items: ['id']`). */
10
15
  function entryPage(entry) {
11
16
  const item = record(entry);
@@ -23,8 +28,12 @@ export function readMobileTabs(properties) {
23
28
  * - Öğe `{ page }` ya da düz sayfa id'sidir; sayfası yoksa düşer; `pages` verildiyse orada olmayan
24
29
  * sayfa düşer; tekrar eden sayfa (ilki kalır) atılır; `MAX_MOBILE_TABS`'tan sonrası kırpılır.
25
30
  * `label`/`icon` boş metinse yazılmaz.
31
+ * - `badge` yalnız `{ source: 'state', ref }` ve `ref` metinse yazılır (11a); `reselect` yalnız `keep` ise
32
+ * yazılır — `root` alan yokken de geçerli davranıştır.
26
33
  * - Görünüm alanı yoktur: çubuk temadan çizilir; eski kayıtların `appearance`'ı okunmaz.
27
34
  * - `customWidgetId` yalnız `custom` türde ve metinse yazılır.
35
+ * - `keepStackOnSwitch` yalnız `false`, `hideOnKeyboard` yalnız `true` ise yazılır — alan yokken de bugünkü
36
+ * davranış (yığın korunur, çubuk klavyede görünür) geçerlidir; tanınmayan değer yok sayılır.
28
37
  */
29
38
  export function normalizeMobileTabs(tabs, pages) {
30
39
  const source = record(tabs);
@@ -41,7 +50,8 @@ export function normalizeMobileTabs(tabs, pages) {
41
50
  seen.add(page);
42
51
  const label = text(item?.label);
43
52
  const icon = text(item?.icon);
44
- items.push({ page, ...(label ? { label } : {}), ...(icon ? { icon } : {}) });
53
+ const badge = badgeOf(item?.badge);
54
+ items.push({ page, ...(label ? { label } : {}), ...(icon ? { icon } : {}), ...(badge ? { badge } : {}), ...(item?.reselect === 'keep' ? { reselect: 'keep' } : {}) });
45
55
  if (items.length === MAX_MOBILE_TABS)
46
56
  break;
47
57
  }
@@ -54,7 +64,9 @@ export function normalizeMobileTabs(tabs, pages) {
54
64
  style,
55
65
  items,
56
66
  ...(customWidgetId ? { customWidgetId } : {}),
57
- showLabels: typeof source.showLabels === 'boolean' ? source.showLabels : true
67
+ showLabels: typeof source.showLabels === 'boolean' ? source.showLabels : true,
68
+ ...(source.keepStackOnSwitch === false ? { keepStackOnSwitch: false } : {}),
69
+ ...(source.hideOnKeyboard === true ? { hideOnKeyboard: true } : {})
58
70
  };
59
71
  }
60
72
  /**
@@ -75,6 +87,10 @@ export function mobileTabsProblems(tabs, context = {}) {
75
87
  problems.push(`style '${String(source.style)}' tanınmıyor (default | floating | custom) — 'default' çizilir`);
76
88
  if (source.items !== undefined && !rawItems)
77
89
  problems.push('items dizi olmalı — hiçbir sekme çizilmez');
90
+ for (const key of ['keepStackOnSwitch', 'hideOnKeyboard']) {
91
+ if (source[key] !== undefined && typeof source[key] !== 'boolean')
92
+ problems.push(`${key} boolean olmalı (gelen: ${JSON.stringify(source[key])}) — yok sayılır, varsayılan davranış geçerli`);
93
+ }
78
94
  const items = rawItems ?? [];
79
95
  const pageIds = items.map(entryPage);
80
96
  if (source.enabled === true && items.length === 0)
@@ -87,6 +103,19 @@ export function mobileTabsProblems(tabs, context = {}) {
87
103
  const duplicates = pageIds.filter((id, index) => id && pageIds.indexOf(id) !== index);
88
104
  if (duplicates.length > 0)
89
105
  problems.push(`aynı sayfa birden çok sekmede: ${[...new Set(duplicates)].join(', ')} — tekrarlar atılır`);
106
+ for (const entry of items) {
107
+ const item = record(entry);
108
+ const page = entryPage(entry) ?? '?';
109
+ if (item?.badge !== undefined) {
110
+ const badge = record(item.badge);
111
+ if (!badge || badge.source !== 'state')
112
+ problems.push(`${page} rozet kaynağı '${String(badge?.source ?? item.badge)}' tanınmıyor — bugün yalnız { source: 'state', ref: '<state yolu>' }; rozet çizilmez`);
113
+ else if (!text(badge.ref))
114
+ problems.push(`${page} rozetinde ref (state yolu) yok — rozet çizilmez`);
115
+ }
116
+ if (item?.reselect !== undefined && item.reselect !== 'root' && item.reselect !== 'keep')
117
+ problems.push(`${page} reselect '${String(item.reselect)}' tanınmıyor (root | keep) — ikinci dokunuş başa döner`);
118
+ }
90
119
  if (context.pages) {
91
120
  const known = new Set(context.pages.map(page => page.id));
92
121
  const unknown = pageIds.filter((id) => Boolean(id) && !known.has(id));
@@ -20,7 +20,7 @@ export const webProfile = Object.freeze({
20
20
  },
21
21
  style: { freeStyle: true, hoverStyle: true, resolver: 'css' },
22
22
  steps: {
23
- registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show', 'back', 'scan-code']),
23
+ registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show', 'back', 'biometric-auth', 'scan-code']),
24
24
  aliases: STEP_ALIASES,
25
25
  infoOnly: new Set()
26
26
  },
@@ -3,7 +3,7 @@ import { PlatformApiError } from '../platform/errors.js';
3
3
  import { reservedTenantTables } from './reservedTables.js';
4
4
  import { headersAccessNames, isHeadersAccessGuarded } from './flowGuards.js';
5
5
  import { callsItself, functionCallRefs, isValidParameterName, mentionsAwait, nameProblem, sourceOf, callPrefix } from './functionGuards.js';
6
- import { backendPolicyRules } from './backendConstraints.js';
6
+ import { backendPolicyRules, effectiveDependencyLines } from './backendConstraints.js';
7
7
  import { accessPolicyRules } from './access.js';
8
8
  import { isGuid } from '../platform/refs.js';
9
9
  import { themePolicyRules } from './themeRules.js';
@@ -172,6 +172,7 @@ const flowBranchNames = {
172
172
  permission: ['always'],
173
173
  'open-settings': ['always'],
174
174
  'ad-show': ['always', 'error'],
175
+ 'biometric-auth': ['always', 'error'],
175
176
  back: ['always'],
176
177
  'scan-code': ['always', 'error'],
177
178
  end: []
@@ -186,7 +187,7 @@ export const knownFlowStepTypes = new Set([
186
187
  'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
187
188
  'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share',
188
189
  'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'bpm',
189
- 'permission', 'open-settings', 'ad-show', 'back', 'scan-code'
190
+ 'permission', 'open-settings', 'ad-show', 'back', 'biometric-auth', 'scan-code'
190
191
  ]);
191
192
  /**
192
193
  * Cihaz yeteneği adımlarının kapalı kümeleri — `@ng-apps/flow` `tasks/device/device.functions.ts`
@@ -200,6 +201,12 @@ export const locationAccuracies = new Set(['low', 'balanced', 'high']);
200
201
  * ve `adBanner.unit` bu adla mağaza ayarındaki yerleşimi anar.
201
202
  */
202
203
  export const adPlacementNamePattern = /^[a-z][A-Za-z0-9]{0,39}$/;
204
+ /**
205
+ * `biometric-auth` adımının yedeği (`args.value`) — `@ng-apps/flow` `biometric-golden.json` `fallbacks`'ın ve backend
206
+ * `TestData/flows/biometric-fallbacks.json`'ın aynası (`test/flow-step-parity.test.ts` üçünü bağlar); backend
207
+ * `FlowDtoValidator.HasKnownDeviceChoice` küme dışını 422 ile reddeder, boş değer `device`'tır. Kök ADR 0021 madde 2.
208
+ */
209
+ export const biometricFallbacks = new Set(['device', 'none']);
203
210
  /**
204
211
  * `scan-code` adımının kod türleri — `@ng-apps/flow` `SCAN_CODE_TYPES`'ın aynası (kök ADR 0017); altın dosya
205
212
  * `scan-code-golden.json` ile kilitlidir (`test/flow-step-parity.test.ts`), backend `ScanCodeType` aynı kümeyle 422 döner.
@@ -228,7 +235,8 @@ const flowTemplateTextFields = new Map([
228
235
  ['alert', ['message', 'description']],
229
236
  ['confirm', ['message', 'title', 'confirmText', 'cancelText']],
230
237
  ['notification-schedule', ['name', 'title']],
231
- ['notification-cancel', ['name']]
238
+ ['notification-cancel', ['name']],
239
+ ['biometric-auth', ['title', 'cancelText']]
232
240
  ]);
233
241
  // Düz metin alanları: ne ifade ne şablon çözülür (`share.title` resolveShareContent'te, bildirim `path`'i K4 kapsamı dışında).
234
242
  const flowLiteralTextFields = new Map([
@@ -697,6 +705,39 @@ export const policyRules = [
697
705
  }];
698
706
  }
699
707
  },
708
+ {
709
+ id: 'MS-019',
710
+ module: 'microservice',
711
+ appliesTo: ['create', 'update', 'preview'],
712
+ sourceRef: "ZM-50 / b-24 (ADR 0018): TableMicroserviceExecutor.ValidateAsync update/upsert/delete'te tek kök ister — tek filtre ya da çocukları yalnız And bağlı tekil filtre olan bir grup; koşullardan benzersiz olanlardan biri 'eq' olmalı (FilterFieldMustUniqueEqException). AND/OR çocuğun bir öncekine bağlantısıdır (PostgreSqlEntityConnector.ApplyFilters Where/OrWhere). Alanın benzersizliği gateway'de bilinmez; FilterFieldMustUniqueException backend'de kalır",
713
+ check: definition => {
714
+ const parameters = (definition.sourceParameters ?? {});
715
+ const operation = String(parameters.operation ?? '').toLowerCase();
716
+ if (definition.source !== 'table')
717
+ return [];
718
+ if (operation !== 'update' && operation !== 'upsert' && operation !== 'delete')
719
+ return [];
720
+ const roots = asArray(parameters.filters).filter((filter) => Boolean(filter) && typeof filter === 'object');
721
+ const recipe = "Tek satırı hedefleyin: ya tek filtre { filterType: 'single', field: <benzersiz alan>, operator: 'eq' } ya da tek kök grup { filterType: 'group', children: [<benzersiz alan> eq, <sahip alanı> eq {{user.id}} …] } — çocukların hepsi conditionType: 'and' (ilki dahil), iç içe grup yok. Ek koşul yalnız daraltır; tutmazsa işlem 0 satır döner.";
722
+ const root = roots.length === 1 ? roots[0] : undefined;
723
+ const children = asArray(root?.children).map(child => (child ?? {}));
724
+ const problem = root === undefined
725
+ ? `kökte ${roots.length} filtre var; update/upsert/delete tam bir kök ister (InvalidFilterCountException)`
726
+ : root.filterType !== 'group'
727
+ ? String(root.operator ?? 'eq') === 'eq' ? '' : `tek filtrenin işleci '${String(root.operator)}' — 'eq' dışındaki işleç hedefi tek satırdan çok satıra genişletir (ör. 'id neq X' X dışındaki bütün satırları değiştirir)`
728
+ : children.some(child => child.filterType === 'group' || String(child.conditionType ?? 'and') !== 'and')
729
+ ? "grubun bir çocuğu 'or' ile bağlı ya da iç içe grup — AND/OR çocuğun bir öncekine bağlantısıdır, 'or' hedefi tek satırdan genişletir"
730
+ : children.some(child => String(child.operator ?? 'eq') === 'eq') ? '' : "grupta 'eq' işleçli koşul yok — hedefi benzersiz alan + 'eq' belirler";
731
+ return problem === ''
732
+ ? []
733
+ : [{
734
+ ruleId: 'MS-019',
735
+ severity: 'block',
736
+ message: `operation '${operation}' filtre ağacı tek satırı hedeflemiyor: ${problem}. Platform kaydı FilterFieldMustUniqueEqException ile reddeder. ${recipe} Toplu yazma bulk-update/bulk-delete ucuyla yapılır.`,
737
+ guide: 'ngapps://guides/microservice'
738
+ }];
739
+ }
740
+ },
700
741
  {
701
742
  id: 'MS-002',
702
743
  module: 'microservice',
@@ -2547,7 +2588,7 @@ export const policyRules = [
2547
2588
  id: 'FLOW-022',
2548
2589
  module: 'flow',
2549
2590
  appliesTo: ['create', 'update'],
2550
- sourceRef: "Metin şablonu (kök docs/adr/0003-cihaz-yetenegi-adimlari.md §Metin şablonu, plans/mobil-acik-isler K4, 2026-10-08): alert.ts/confirm.ts (web) ve ui-native runtime-tasks.ts + @ng-apps/flow device.functions.ts (cihaz, önizleme) `alert.message/description`, `confirm.message/title/confirmText/cancelText`, `notification-schedule.name/title`, `notification-cancel.name`'i @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan geçirir: yalnız `{{ }}` içi çözülür (çeviri `t('anahtar')`, `state`, `Steps`, `item`), dışındaki çıplak ifade ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu; 38 S-12, 37 S-11). Düz metin kalanlar: share `title` (resolveShareContent olduğu gibi verir; `message` ve `path` ifadedir) ve notification-schedule `path` (K4 kapsamı dışı) — orada `{{ }}` de çözülmez",
2591
+ sourceRef: "Metin şablonu (kök docs/adr/0003-cihaz-yetenegi-adimlari.md §Metin şablonu, plans/mobil-acik-isler K4, 2026-10-08): alert.ts/confirm.ts (web) ve ui-native runtime-tasks.ts + @ng-apps/flow device.functions.ts (cihaz, önizleme) `alert.message/description`, `confirm.message/title/confirmText/cancelText`, `notification-schedule.name/title`, `notification-cancel.name`, `biometric-auth.title/cancelText` (kök ADR 0021)'i @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan geçirir: yalnız `{{ }}` içi çözülür (çeviri `t('anahtar')`, `state`, `Steps`, `item`), dışındaki çıplak ifade ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu; 38 S-12, 37 S-11). Düz metin kalanlar: share `title` (resolveShareContent olduğu gibi verir; `message` ve `path` ifadedir) ve notification-schedule `path` (K4 kapsamı dışı) — orada `{{ }}` de çözülmez",
2551
2592
  check: definition => flowAllSteps(definition)
2552
2593
  .flatMap((step) => {
2553
2594
  const type = String(step.type ?? '');
@@ -2717,6 +2758,87 @@ export const policyRules = [
2717
2758
  return findings;
2718
2759
  })
2719
2760
  },
2761
+ {
2762
+ id: 'FLOW-042',
2763
+ module: 'flow',
2764
+ appliesTo: ['create', 'update'],
2765
+ sourceRef: "Biyometrik doğrulama adımı (kök docs/adr/0021-biyometrik-dogrulama-adimi-ve-uygulama-kilidi.md madde 2): args.title sistem penceresindeki gerekçe, args.cancelText vazgeç düğmesi (ikisi de metin şablonu), args.value yedek — DÜZ METİN, device (biyometri olmazsa cihaz parolası; boşsa bu) ya da none. Backend FlowDtoValidator küme dışını 422 ile reddeder; fallback/prompt/reason/cancelLabel gibi anahtarlar StepManagementArgument sözleşmesinde YOKTUR ve 200 ile sessizce düşer. Vazgeçme bir hatadır: adımın error dalı → akışın hata adımları → o da yoksa akış bu adımda BİTER (@ng-apps/flow device.functions.ts settle 'stop'); always'e bağlı sonraki adım koşmaz. Web uygulaması ve production /view adımı soramaz, unsupported döner",
2766
+ check: definition => {
2767
+ // flow-engine.ts hasErrorFlow(): yalnız start taşıyan errorSteps hata yolu sayılmaz (FLOW-030 ile aynı kural).
2768
+ // Hata akışının İÇİNDEKİ adım için hata akışı yoktur: motor onu `${id}__error` olarak hata adımsız kurar
2769
+ // (flow-engine.ts), `onError` false döner ve `settle` 'stop' o akışı bitirir.
2770
+ const errorSteps = flowStepList(definition.errorSteps);
2771
+ const errorFlow = errorSteps.some(step => step.type !== 'start');
2772
+ return flowAllSteps(definition)
2773
+ .filter(step => step.type === 'biometric-auth')
2774
+ .flatMap((step) => {
2775
+ const label = flowStepLabel(step);
2776
+ // Sonuç adımın ADIYLA yazılır (device.functions.ts `machine.setStorage(task.name, …)`), stepId'yle değil.
2777
+ const resultName = step.name || label;
2778
+ const args = (step.args ?? {});
2779
+ // Backend `HasKnownDeviceChoice` ve runtime `biometricFallback` değeri KIRPMADAN karşılaştırır: ' none' 422'dir.
2780
+ const value = typeof args.value === 'string' ? args.value : '';
2781
+ const unquoted = value.trim().replace(/^['"`](.*)['"`]$/, '$1');
2782
+ const findings = [];
2783
+ if (value.trim() && unquoted !== value.trim()) {
2784
+ findings.push({
2785
+ ruleId: 'FLOW-042',
2786
+ severity: 'block',
2787
+ message: `'${label}' biometric-auth adımının yedeği tırnaklı yazılmış: ${value}. Bu alan DÜZ METİNDİR, ifade değil — args.value: '${unquoted}' yazın.`,
2788
+ guide: 'ngapps://guides/mobile-steps'
2789
+ });
2790
+ }
2791
+ else if (value.trim() && !biometricFallbacks.has(value)) {
2792
+ findings.push({
2793
+ ruleId: 'FLOW-042',
2794
+ severity: 'block',
2795
+ message: `'${label}' biometric-auth adımının yedeği '${value}' kümede değil: args.value ${[...biometricFallbacks].join(', ')} olur (boş = device; ifade ve çevresinde boşluk yazılamaz). Platform 422 döner.`,
2796
+ guide: 'ngapps://guides/mobile-steps'
2797
+ });
2798
+ }
2799
+ if (args.fallback !== undefined && args.fallback !== null && args.fallback !== '') {
2800
+ const kept = args.fallback === value;
2801
+ findings.push({
2802
+ ruleId: 'FLOW-042',
2803
+ severity: kept ? 'warn' : 'block',
2804
+ message: kept
2805
+ ? `'${label}' biometric-auth adımında args.fallback var: sözleşmede YOK ve 200 ile sessizce düşer. Yedek args.value'da zaten '${value}' — args.fallback'ı kaldırın.`
2806
+ : `'${label}' biometric-auth adımında args.fallback var: yedek args.value alanındadır (${[...biometricFallbacks].join(' | ')}). args.fallback sözleşmede YOK ve 200 ile sessizce düşer — adım args.value ile ('${value || 'device'}') kaydedilir, istediğiniz yedek kaybolur.`,
2807
+ guide: 'ngapps://guides/mobile-steps'
2808
+ });
2809
+ }
2810
+ const reasonKeys = ['reason', 'prompt', 'promptMessage'].filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2811
+ if (reasonKeys.length > 0) {
2812
+ findings.push({
2813
+ ruleId: 'FLOW-042',
2814
+ severity: 'warn',
2815
+ message: `'${label}' biometric-auth adımında args.${reasonKeys.join('/args.')} var: sistem penceresindeki gerekçe args.title alanındadır. Bu anahtar sözleşmede YOK ve 200 ile sessizce düşer; pencere uygulama dilindeki varsayılan gerekçeyle açılır.`,
2816
+ guide: 'ngapps://guides/mobile-steps'
2817
+ });
2818
+ }
2819
+ const cancelKeys = ['cancelLabel', 'cancel'].filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2820
+ if (cancelKeys.length > 0) {
2821
+ findings.push({
2822
+ ruleId: 'FLOW-042',
2823
+ severity: 'warn',
2824
+ message: `'${label}' biometric-auth adımında args.${cancelKeys.join('/args.')} var: vazgeç düğmesinin metni args.cancelText alanındadır. Bu anahtar sözleşmede YOK ve 200 ile sessizce düşer.`,
2825
+ guide: 'ngapps://guides/mobile-steps'
2826
+ });
2827
+ }
2828
+ const errorBranch = flowBranches(step).some(branch => branch.name === 'error' && Boolean(branch.nextStepId));
2829
+ const insideErrorFlow = errorSteps.includes(step);
2830
+ if (!errorBranch && (insideErrorFlow || !errorFlow)) {
2831
+ findings.push({
2832
+ ruleId: 'FLOW-042',
2833
+ severity: 'warn',
2834
+ message: `'${label}' biometric-auth adımının error dalı da akışın hata adımları da bağlı değil: kullanıcı vazgeçince ya da doğrulama olmayınca akış BU ADIMDA BİTER, always'e bağlı sonraki adım koşmaz (kapı kapalı kalır). Kullanıcıya bir şey söylenecekse error dalını bağlayın${insideErrorFlow ? ' (adım hata akışının içinde: hata akışının kendi hata akışı yoktur)' : ''} — Steps.${resultName}.error.status: cancelled, not-enrolled, lockout, failed, unavailable, unsupported (web uygulaması ve production /view), invalid.`,
2835
+ guide: 'ngapps://guides/mobile-steps'
2836
+ });
2837
+ }
2838
+ return findings;
2839
+ });
2840
+ }
2841
+ },
2720
2842
  {
2721
2843
  id: 'FLOW-042',
2722
2844
  module: 'flow',
@@ -3638,6 +3760,22 @@ export const policyRules = [
3638
3760
  }];
3639
3761
  }
3640
3762
  },
3763
+ {
3764
+ id: 'MF-007',
3765
+ module: 'microfunction',
3766
+ appliesTo: ['create', 'update'],
3767
+ sourceRef: "LambdaMicrofunctionConnector paketi olduğu gibi yükler; MicrofunctionPackage.RequiresBuild yalnız Built-In'de (FissionMicrofunctionConnector) build'e dönüşür. Karar: kök docs/adr/0001-microfunction-bagimlilik-bildirimi-ve-build-yolu.md, plans/microfunction-faz-2 K15",
3768
+ check: definition => String(definition.provider ?? '') === 'aws-lambda' &&
3769
+ String(definition.sourceKind ?? 'inline') === 'inline' &&
3770
+ effectiveDependencyLines(definition.dependencies).length > 0
3771
+ ? [{
3772
+ ruleId: 'MF-007',
3773
+ severity: 'warn',
3774
+ message: "provider 'aws-lambda' kayıtta dependencies KURULMAZ: bağımlılık build'i yalnız Built-In'de (Fission builder) koşar, Lambda'ya paket olduğu gibi gider. Paketler kurulmuş hâlde zip'in içinde gelmeli (archive kaynağı; Python'da paket klasörleri, Node'da node_modules). Aksi hâlde yayının probe adımı import hatasıyla publish-failed olur.",
3775
+ guide: 'ngapps://guides/microfunction'
3776
+ }]
3777
+ : []
3778
+ },
3641
3779
  // Backend doğrulayıcılarının aynası (ENT-015…018, MS-017, WF-026, QRY/VIEW/DTO/ROW/DS/PRJ/TR/FLD/FILE/EXT):
3642
3780
  // tek liste `backendConstraints.ts`'te durur, buradan yalnız politika motoruna katılır.
3643
3781
  ...themePolicyRules,
@@ -3726,6 +3864,59 @@ export async function microfunctionStepFindings(platform, project, environment,
3726
3864
  }
3727
3865
  return applyPolicyMode(platform.config, findings, policyOverride);
3728
3866
  }
3867
+ /**
3868
+ * MF-006'nın canlı yarısı: işlem listesi kaydın entrypoint modülünü siliyor ya da taşıyorsa uyarır.
3869
+ * Block değildir — backend yazmayı kabul eder, ama yayın kapısı
3870
+ * (`MicrofunctionService.PublishAsync`) modül zip'te yoksa 400 döner; kullanıcı sonra entrypoint'i
3871
+ * `microfunction_update` ile yeni modüle çevirebilir. Modül yolu platformdaki
3872
+ * `MicrofunctionLanguageProfile.ModulePath` kuralıdır: son '.'dan öncesi + dilin uzantısı.
3873
+ * Sonraki bir işlem aynı yola dosya yazar ya da oraya taşırsa uyarı düşer.
3874
+ *
3875
+ * Okuma başarısız olursa kural susar (WF-MF-001 ile aynı gerekçe).
3876
+ */
3877
+ export async function microfunctionArchiveEntrypointFindings(platform, project, environment, id, operations) {
3878
+ if (platform.config.policyMode === 'off')
3879
+ return [];
3880
+ let record;
3881
+ try {
3882
+ const payload = await platform.tenant('GET', project, `/microfunctions/${id}`, { environment });
3883
+ record = (payload?.data ?? payload);
3884
+ }
3885
+ catch {
3886
+ return [];
3887
+ }
3888
+ const entrypoint = hasText(record?.entrypoint) ? String(record.entrypoint) : '';
3889
+ const separatorIndex = entrypoint.lastIndexOf('.');
3890
+ const extension = microfunctionModuleExtensions[String(record?.language ?? '')];
3891
+ if (separatorIndex <= 0 || extension === undefined)
3892
+ return [];
3893
+ const modulePath = `${entrypoint.slice(0, separatorIndex)}${extension}`;
3894
+ let breaking;
3895
+ const archiveOperations = (Array.isArray(operations) ? operations : [])
3896
+ .filter((operation) => operation !== null && typeof operation === 'object');
3897
+ const coversModule = (path) => path === modulePath || modulePath.startsWith(`${path}/`);
3898
+ archiveOperations.forEach((operation, index) => {
3899
+ const type = String(operation.type ?? '');
3900
+ const path = String(operation.path ?? '');
3901
+ if ((type === 'delete' || type === 'rename') && coversModule(path))
3902
+ breaking = { index, type, path };
3903
+ if (type === 'write-file' && path === modulePath)
3904
+ breaking = undefined;
3905
+ // Modülün klasörünü geri getiren taşıma da onarır (lib → src → lib); içerik bilinmez, uyarı sezgiseldir.
3906
+ if (type === 'rename' && coversModule(String(operation.newPath ?? '')))
3907
+ breaking = undefined;
3908
+ });
3909
+ if (breaking === undefined)
3910
+ return [];
3911
+ return [{
3912
+ ruleId: 'MF-006',
3913
+ severity: 'warn',
3914
+ message: `operations[${breaking.index}] (${breaking.type} ${breaking.path}) kaydın entrypoint modülünü (${modulePath}, entrypoint '${entrypoint}') ${breaking.type === 'delete' ? 'siliyor' : 'taşıyor'}. Değişiklik yazılır ama yayın 400 ile düşer (MicrofunctionArchiveInvalidException): ya aynı yola dosya yazın ya da microfunction_update ile entrypoint'i yeni modüle çevirin.`,
3915
+ guide: 'ngapps://guides/microfunction'
3916
+ }];
3917
+ }
3918
+ /** `MicrofunctionLanguageProfile.ModuleExtension` — dil adı bilgi katmanında taşınır. */
3919
+ const microfunctionModuleExtensions = { node: '.js', python: '.py' };
3729
3920
  /**
3730
3921
  * WF-032'nin ikinci yarısı. `rule.expression`'ın işe yarayıp yaramayacağı gövdeden görülemez:
3731
3922
  * `WorkflowStep` ifadeyi YALNIZ çağrılan akışın parametre DTO'su varken değerlendirir