@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.
- package/dist/knowledge/backendConstraints.js +86 -2
- package/dist/knowledge/componentCatalog.js +63 -26
- package/dist/knowledge/componentEvents.js +6 -0
- package/dist/knowledge/connectDataBind.js +43 -6
- package/dist/knowledge/connectDataDefinition.js +74 -3
- package/dist/knowledge/connectDataRules.js +14 -0
- package/dist/knowledge/mobileComponentCatalog.js +66 -7
- package/dist/knowledge/mobileStore.js +61 -2
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +2 -1
- package/dist/knowledge/platform/mobileTabs.js +31 -2
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +195 -4
- package/dist/knowledge/textHooks.js +434 -0
- package/dist/platform/capture/capture.js +1 -1
- package/dist/platform/capture/readiness.js +4 -0
- package/dist/platform/errors.js +22 -2
- package/dist/prompts/mobilePublish.js +21 -0
- package/dist/resources/examples/flow-biometric-confirm.json +199 -0
- package/dist/resources/examples/microfunction-node-dependencies.json +19 -0
- package/dist/resources/guides/access.md +3 -3
- package/dist/resources/guides/component.md +20 -4
- package/dist/resources/guides/deployment.md +98 -0
- package/dist/resources/guides/entity.md +1 -1
- package/dist/resources/guides/expressions.md +5 -1
- package/dist/resources/guides/external-component.md +6 -4
- package/dist/resources/guides/merge.md +81 -4
- package/dist/resources/guides/microfunction.md +104 -9
- package/dist/resources/guides/microservice.md +34 -5
- package/dist/resources/guides/mobile-connect-data.md +70 -0
- package/dist/resources/guides/mobile-flow.md +64 -0
- package/dist/resources/guides/mobile-publish.md +161 -16
- package/dist/resources/guides/mobile-steps.md +17 -0
- package/dist/resources/guides/mobile.md +41 -23
- package/dist/resources/guides/page.md +11 -5
- package/dist/resources/guides/translation.md +95 -0
- package/dist/resources/knowledge.js +3 -1
- package/dist/resources/prompts/mobile-publish-prepare.md +89 -0
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/ApplyMicrofunctionArchiveChangesCommand.json +76 -0
- package/dist/resources/schemas/ConnectDataDescribeDto.json +44 -0
- package/dist/resources/schemas/ConnectDataResultDto.json +10 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +8 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +0 -4
- package/dist/resources/schemas/MobileStorePreflightScope.json +13 -0
- package/dist/resources/schemas/StartMobileBuildCommand.json +35 -1
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +8 -0
- package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +75 -0
- package/dist/server.js +2 -0
- package/dist/tools/common.js +6 -0
- package/dist/tools/components.js +19 -6
- package/dist/tools/connectData.js +9 -4
- package/dist/tools/deployments.js +1 -1
- package/dist/tools/designSchemaPatch.js +16 -4
- package/dist/tools/environments.js +3 -3
- package/dist/tools/files.js +1 -1
- package/dist/tools/flows.js +29 -3
- package/dist/tools/layouts.js +32 -12
- package/dist/tools/merges.js +8 -8
- package/dist/tools/microfunctions.js +53 -3
- package/dist/tools/mobileStore.js +83 -21
- package/dist/tools/pages.js +59 -24
- package/dist/tools/platformInfo.js +4 -2
- package/dist/tools/textHookGuard.js +116 -0
- 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
|
-
|
|
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
|
},
|
package/dist/knowledge/rules.js
CHANGED
|
@@ -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
|