@appaflytech/zeyrek-ai-mcp 0.0.40 → 0.0.41

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.
@@ -5,6 +5,7 @@ import { headersAccessNames, isHeadersAccessGuarded } from './flowGuards.js';
5
5
  import { callsItself, functionCallRefs, isValidParameterName, mentionsAwait, nameProblem, sourceOf, callPrefix } from './functionGuards.js';
6
6
  import { backendPolicyRules } from './backendConstraints.js';
7
7
  import { accessPolicyRules } from './access.js';
8
+ import { isGuid } from '../platform/refs.js';
8
9
  import { themePolicyRules } from './themeRules.js';
9
10
  import { processPolicyRules } from './process.js';
10
11
  import { mobileStorePolicyRules } from './mobileStore.js';
@@ -153,11 +154,13 @@ export function readsBeforeGuard(source, root) {
153
154
  * yaşatılır (microservice.ts:174 `isHasSuccess`). 'error' dalı 2026-09-26'dan beri motorun
154
155
  * KENDİSİ tanır (ADR 0004, ZM-07/g-02): bağlıysa hata anında `always` hiç koşmaz, sürer
155
156
  * (microservice.ts:182 `hasBranch`) — eski motor tanımadığı dalı hiç aramadığı için bu ekleme
156
- * geriye dönük zararsızdır.
157
+ * geriye dönük zararsızdır. `validation` tüm kurallar geçince 'success', biri düşünce 'error' dalına,
158
+ * ardından her durumda 'always'e gider (validation.ts; kök ADR 0014, g-18).
157
159
  */
158
160
  const flowBranchNames = {
159
161
  start: ['always'],
160
162
  microservice: ['always', 'success', 'error'],
163
+ validation: ['always', 'success', 'error'],
161
164
  confirm: ['yes', 'no', 'always'],
162
165
  function: ['always'],
163
166
  haptic: ['always'],
@@ -169,6 +172,7 @@ const flowBranchNames = {
169
172
  permission: ['always'],
170
173
  'open-settings': ['always'],
171
174
  'ad-show': ['always', 'error'],
175
+ back: ['always'],
172
176
  end: []
173
177
  };
174
178
  /**
@@ -181,7 +185,7 @@ export const knownFlowStepTypes = new Set([
181
185
  'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
182
186
  'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share',
183
187
  'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'bpm',
184
- 'permission', 'open-settings', 'ad-show'
188
+ 'permission', 'open-settings', 'ad-show', 'back'
185
189
  ]);
186
190
  /**
187
191
  * Cihaz yeteneği adımlarının kapalı kümeleri — `@ng-apps/flow` `tasks/device/device.functions.ts`
@@ -197,6 +201,12 @@ export const locationAccuracies = new Set(['low', 'balanced', 'high']);
197
201
  export const adPlacementNamePattern = /^[a-z][A-Za-z0-9]{0,39}$/;
198
202
  /** `permission` adımının kipleri — `@ng-apps/flow` `PERMISSION_MODES`'un aynası (kök ADR 0010); tür adını gateway bilmez, backend doğrular. */
199
203
  export const permissionStepModes = new Set(['request', 'check']);
204
+ /**
205
+ * `validation` adımının kural tipleri — `@ng-apps/flow` `VALIDATION_RULE_TYPES`'ın aynası (`test/flow-step-parity.test.ts`
206
+ * bağlar). Backend kümeyi doğrulamaz (kök ADR 0014); tanınmayan tip runtime'da `validate()` default dalında GEÇER sayılır.
207
+ */
208
+ export const validationRuleTypes = new Set(['required', 'email', 'regex', 'min-length', 'max-length', 'min-value', 'max-value']);
209
+ const validationParameterlessTypes = new Set(['required', 'email']);
200
210
  const flowFreeBranchTypes = new Set(['condition']);
201
211
  /** `condition.types.ts` ConditionArgumentRuleType — motorun tanıdığı üçlü. */
202
212
  const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
@@ -204,16 +214,22 @@ const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
204
214
  // backend'de bir Guid'dir (StepManagementSchema.cs `public Guid Id { get; set; }`), yani
205
215
  // okunabilir bir ad iki uçta birden tutmaz: istek System.Guid çevrimiyle reddedilir.
206
216
  const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
207
- // alert.ts ve confirm.ts `task.argument.message`'ı doğrudan toast/dialog'a geçirir; argüman
208
- // Runtime.svelte'de HAM verilir (`argument: step.args`), yani bu alanlarda ifade değerlendirilmez.
209
- const flowLiteralTextFields = new Map([
217
+ // Metin şablonu alanları (kök ADR 0003 §Metin şablonu, K4 2026-10-08): iki barındırıcı (web alert.ts/confirm.ts,
218
+ // cihaz runtime-tasks.ts + device.functions.ts) metni @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan
219
+ // geçirir — yalnız `{{ }}` içi çözülür, dışındaki çıplak ifade ekranda harfi harfine görünür.
220
+ const flowTemplateTextFields = new Map([
210
221
  ['alert', ['message', 'description']],
211
222
  ['confirm', ['message', 'title', 'confirmText', 'cancelText']],
212
- ['share', ['title']],
213
- ['notification-schedule', ['name', 'title', 'path']],
223
+ ['notification-schedule', ['name', 'title']],
214
224
  ['notification-cancel', ['name']]
215
225
  ]);
226
+ // Düz metin alanları: ne ifade ne şablon çözülür (`share.title` resolveShareContent'te, bildirim `path`'i K4 kapsamı dışında).
227
+ const flowLiteralTextFields = new Map([
228
+ ['share', ['title']],
229
+ ['notification-schedule', ['path']]
230
+ ]);
216
231
  const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
232
+ const flowTemplateSegments = /\{\{[\s\S]*?\}\}/g;
217
233
  // Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
218
234
  // `redirect`e çeviriyor (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da
219
235
  // koşar, yani vaka uyarılmaz, ORTADAN KALKAR. Gateway uygulamanın tipini platform profilinden bilir
@@ -1843,7 +1859,7 @@ export const policyRules = [
1843
1859
  id: 'ENT-006',
1844
1860
  module: 'entity',
1845
1861
  appliesTo: ['create', 'update'],
1846
- sourceRef: 'sms oturumu: kullanıcının intprimarykey/guidprimarykey referans tabloları — PK identity + properties.generationType:0 (Always) ile otomatik üretilir; dbType↔commonType eşleşmesi zorunlu',
1862
+ sourceRef: 'sms oturumu: kullanıcının intprimarykey/guidprimarykey referans tabloları — PK identity + properties.generationType:0 (Always) ile otomatik üretilir; dbType↔commonType eşleşmesi zorunlu. 38 S-1: PostgreSqlEntityConnector.ValidateEntityAsync identity alanında GenerationType 0/1 değilse InvalidGenerationTypeException (400) atar — create ve update',
1847
1863
  check: definition => {
1848
1864
  const primaryKeys = asArray(definition.fields)
1849
1865
  .filter((field) => Boolean(field) && typeof field === 'object')
@@ -1853,7 +1869,15 @@ export const policyRules = [
1853
1869
  const properties = (key.properties ?? {});
1854
1870
  const dbType = String(key.dbType ?? '');
1855
1871
  const expected = dbType === 'UUID' ? 'guid' : ['INT2', 'INT4', 'INT8'].includes(dbType) ? 'number' : null;
1856
- if (key.identity !== true || properties.generationType === undefined) {
1872
+ if (key.identity === true && properties.generationType !== 0 && properties.generationType !== 1) {
1873
+ findings.push({
1874
+ ruleId: 'ENT-006',
1875
+ severity: 'block',
1876
+ message: `'${key.slug ?? key.name}' primary key'i identity ama properties.generationType ${properties.generationType === undefined ? 'yok' : `'${String(properties.generationType)}'`} — platform 400 "Please select a valid generation type for '${key.slug ?? key.name}'" döner. Alana properties: { generationType: 0 } (Always) ya da 1 (ByDefault) ekleyin.`,
1877
+ guide: 'ngapps://guides/entity'
1878
+ });
1879
+ }
1880
+ else if (key.identity !== true) {
1857
1881
  findings.push({
1858
1882
  ruleId: 'ENT-006',
1859
1883
  severity: 'warn',
@@ -2507,7 +2531,7 @@ export const policyRules = [
2507
2531
  : [{
2508
2532
  ruleId: 'FLOW-021',
2509
2533
  severity: 'warn',
2510
- message: "errorSteps yalnız 'start' adımı taşıyor — motor bunu hata yolu SAYMAZ (flow-engine.ts hasErrorFlow: start dışında en az bir adım arar). Hata yolu kurulmuş görünür ama uç 4xx dönünce onError false döner, akış 'always' dalından hata gövdesiyle devam eder ve kullanıcıya hiçbir mesaj ulaşmaz. start'ın ardına en az bir adım bağlayın: ucun kendi mesajını göstereceksen setter (value.type:'code' → Steps.error.message), sabit bir uyarı yetiyorsa alert — alert'in 'message' alanı DÜZ METİNDİR (alert.ts task.argument.message'ı doğrudan toast'a geçirir), oraya ifade yazarsanız kullanıcı ifadenin kendisini görür.",
2534
+ message: "errorSteps yalnız 'start' adımı taşıyor — motor bunu hata yolu SAYMAZ (flow-engine.ts hasErrorFlow: start dışında en az bir adım arar). Hata yolu kurulmuş görünür ama uç 4xx dönünce onError false döner, akış 'always' dalından hata gövdesiyle devam eder ve kullanıcıya hiçbir mesaj ulaşmaz. start'ın ardına en az bir adım bağlayın: alert yeter — 'message' alanı metin şablonudur (FLOW-022), ucun kendi mesajı '{{ Steps.error.message }}' ile gösterilir; {{ }} dışında yazılan ifadeyi kullanıcı harfi harfine görür. Mesajı bir bileşene yazacaksan setter (value.type:'code' → Steps.error.message).",
2511
2535
  guide: 'ngapps://guides/page'
2512
2536
  }];
2513
2537
  }
@@ -2516,26 +2540,32 @@ export const policyRules = [
2516
2540
  id: 'FLOW-022',
2517
2541
  module: 'flow',
2518
2542
  appliesTo: ['create', 'update'],
2519
- sourceRef: "alert.ts `const { message, alertType, description } = task.argument` → toast.error(message, { description }) / dialog.alert(message); confirm.ts `dialog.confirm(message, { title, confirmText, cancelText })` (boş çerçeve metni dialog.functions.ts resolveDialogFrame'de sabit metne düşer). Argüman Runtime.svelte'de HAM geçer (`argument: step.args`) — bu alanlarda hiçbir ifade değerlendirilmez, yazılan metin ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu: rehber hata mesajını göstermek için alert adımını öneriyordu). share adımında (2026-09-25) yalnız `title` düz metindir: @ng-apps/flow resolveShareContent onu olduğu gibi verir, `message` ve `path` ifadedir",
2543
+ 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",
2520
2544
  check: definition => flowAllSteps(definition)
2521
2545
  .flatMap((step) => {
2522
2546
  const type = String(step.type ?? '');
2523
- const fields = flowLiteralTextFields.get(type);
2524
- if (!fields)
2525
- return [];
2526
2547
  const args = (step.args ?? {});
2527
- return fields
2528
- .filter(field => typeof args[field] === 'string' && flowExpressionLooking.test(args[field]))
2548
+ const label = flowStepLabel(step);
2549
+ const text = (field) => (typeof args[field] === 'string' ? args[field] : '');
2550
+ const templateFindings = (flowTemplateTextFields.get(type) ?? [])
2551
+ .filter(field => flowExpressionLooking.test(text(field).replace(flowTemplateSegments, '')))
2552
+ .map(field => ({
2553
+ ruleId: 'FLOW-022',
2554
+ severity: 'warn',
2555
+ message: `'${label}' ${type} adımının '${field}' alanı {{ }} dışında ifade içeriyor: bu alan METİN ŞABLONUDUR (kök ADR 0003 §Metin şablonu) — yalnız {{ }} içi çözülür, dışındaki ifade kullanıcıya harfi harfine görünür. İfadeyi sarın: '{{ Steps.error.message }}', 'ilac-{{ item.id }}', '{{ t('anahtar') }}' (çeviri).`,
2556
+ guide: 'ngapps://guides/page'
2557
+ }));
2558
+ const literalFindings = (flowLiteralTextFields.get(type) ?? [])
2559
+ .filter(field => flowExpressionLooking.test(text(field)) || text(field).includes('{{'))
2529
2560
  .map(field => ({
2530
2561
  ruleId: 'FLOW-022',
2531
2562
  severity: 'warn',
2532
- message: type.startsWith('notification-')
2533
- ? `'${flowStepLabel(step)}' ${type} adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR (kök ADR 0003): ${field === 'name' ? 'bildirimin kimliği' : field === 'path' ? 'açılacak sayfanın slug\'ı' : 'bildirim başlığı'} olduğu gibi kullanılır, ifade değerlendirilmez. Değişen içeriği ifade olan 'message' (gövde) ya da 'query' (sayfanın sorgusu) alanına yazın.`
2534
- : type === 'share'
2535
- ? `'${flowStepLabel(step)}' share adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ifade değerlendirilmez. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
2536
- : `'${flowStepLabel(step)}' ${type} adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: ${type}.ts task.argument.${field}'i doğrudan ekrana geçirir ve args hiçbir yerde değerlendirilmez (Runtime.svelte \`argument: step.args\`). Kullanıcı ifadenin sonucunu değil ifadenin KENDİSİNİ görür. Ucun mesajını göstermek için bir setter (ya da code) adımı kullanın — value { type: 'code', ref: 'Steps.error?.message' }; hata metninin nerede durduğu page.md §hata yolu'nda.`,
2563
+ message: type === 'share'
2564
+ ? `'${label}' share adımının '${field}' alanı ifade ya da {{ }} içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ne ifade ne şablon çözülür. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
2565
+ : `'${label}' ${type} adımının '${field}' alanı ifade ya da {{ }} içeriyor ama bu alan DÜZ METİNDİR (kök ADR 0003): açılacak sayfanın slug'ı olduğu gibi kullanılır, ne ifade ne şablon çözülür. Değişen kısmı ifade olan 'query' (sayfanın sorgusu) alanına yazın.`,
2537
2566
  guide: 'ngapps://guides/page'
2538
2567
  }));
2568
+ return [...templateFindings, ...literalFindings];
2539
2569
  })
2540
2570
  },
2541
2571
  {
@@ -2680,6 +2710,75 @@ export const policyRules = [
2680
2710
  return findings;
2681
2711
  })
2682
2712
  },
2713
+ {
2714
+ id: 'FLOW-041',
2715
+ module: 'flow',
2716
+ appliesTo: ['create', 'update'],
2717
+ sourceRef: "validation adımının kuralları StepManagementArgument.validations listesindedir (kök docs/adr/0014-validation-kurallari-cocuk-tabloda.md §5, 37 S-13): { id?, component (tasarım bileşeninin elementId'si; boş = null, sayfada olmayan 404), property, type, errorMessage (düz metin ya da t: anahtarı), settings (tek parametre, runtime Object.values(settings)[0]'ı okur) }. Backend type kümesini doğrulamaz; @ng-apps/flow validation.ts validate() tanımadığı tipte true döner, yani yazım hatalı kural sessizce GEÇER. Liste boşsa her çalıştırma success dalına gider. Komponent doğrulamasının { message, value } şekli bu sözleşmede YOKTUR ve 200 ile sessizce düşer",
2718
+ check: definition => flowAllSteps(definition)
2719
+ .filter(step => step.type === 'validation')
2720
+ .flatMap((step) => {
2721
+ const label = flowStepLabel(step);
2722
+ const validations = (step.args ?? {}).validations;
2723
+ if (validations !== undefined && validations !== null && !Array.isArray(validations)) {
2724
+ return [{
2725
+ ruleId: 'FLOW-041',
2726
+ severity: 'block',
2727
+ message: `'${label}' validation adımının args.validations alanı bir DİZİ olmalı: [{ component, property, type, errorMessage, settings }] (kök ADR 0014). Ör. [{ component: 'email', property: 'value', type: 'email', errorMessage: 't:validation.email', settings: {} }].`,
2728
+ guide: 'ngapps://guides/page'
2729
+ }];
2730
+ }
2731
+ const rules = (Array.isArray(validations) ? validations : []);
2732
+ if (rules.length === 0) {
2733
+ return [{
2734
+ ruleId: 'FLOW-041',
2735
+ severity: 'warn',
2736
+ message: `'${label}' validation adımında kural yok: args.validations boş olduğu için adım her çalıştırmada 'success' dalına gider, hiçbir alan denetlenmez. Kuralları args.validations'a yazın (kök ADR 0014).`,
2737
+ guide: 'ngapps://guides/page'
2738
+ }];
2739
+ }
2740
+ return rules.flatMap((rule, index) => {
2741
+ const type = typeof rule?.type === 'string' ? rule.type : '';
2742
+ const settings = rule?.settings && typeof rule.settings === 'object' ? Object.values(rule.settings) : [];
2743
+ const stray = ['message', 'value', 'name', 'isActive'].filter(key => rule?.[key] !== undefined);
2744
+ const at = `args.validations[${index}]`;
2745
+ const findings = [];
2746
+ if (!validationRuleTypes.has(type)) {
2747
+ findings.push({
2748
+ ruleId: 'FLOW-041',
2749
+ severity: 'block',
2750
+ message: `'${label}' validation adımında ${at}.type '${type}' tanınmıyor — beklenen: '${[...validationRuleTypes].join("' | '")}'. Platform tipi doğrulamadan kaydeder ve runtime tanımadığı tipi GEÇTİ sayar: kural hiçbir şeyi denetlemez.`,
2751
+ guide: 'ngapps://guides/page'
2752
+ });
2753
+ }
2754
+ else if (!validationParameterlessTypes.has(type) && (settings.length === 0 || settings[0] === null || settings[0] === '')) {
2755
+ findings.push({
2756
+ ruleId: 'FLOW-041',
2757
+ severity: 'block',
2758
+ message: `'${label}' validation adımında ${at} ('${type}') parametresiz: runtime eşiği settings'in İLK değerinden okur (${type === 'regex' ? "{ pattern: '^\\\\d+$' }" : '{ value: 8 }'}); değer yoksa karşılaştırma undefined ile yapılır ve kural her çalıştırmada düşer.`,
2759
+ guide: 'ngapps://guides/page'
2760
+ });
2761
+ }
2762
+ if (typeof rule?.property !== 'string' || rule.property.trim() === '') {
2763
+ findings.push({
2764
+ ruleId: 'FLOW-041',
2765
+ severity: 'block',
2766
+ message: `'${label}' validation adımında ${at}.property yok: runtime bileşenin hangi prop'unu okuyacağını bu alandan bilir (çoğunlukla 'value'). Yoksa okunan değer undefined olur.`,
2767
+ guide: 'ngapps://guides/page'
2768
+ });
2769
+ }
2770
+ if (stray.length > 0) {
2771
+ findings.push({
2772
+ ruleId: 'FLOW-041',
2773
+ severity: 'warn',
2774
+ message: `'${label}' validation adımında ${at}.${stray.join('/')} alanı var: bu komponent doğrulamasının şeklidir, adım kuralının sözleşmesinde YOK ve platform onu 200 ile sessizce düşürür. Mesaj errorMessage'a, eşik settings'e yazılır — ör. { component: 'sifre', property: 'value', type: 'min-length', errorMessage: 'En az 8 karakter', settings: { value: 8 } }.`,
2775
+ guide: 'ngapps://guides/page'
2776
+ });
2777
+ }
2778
+ return findings;
2779
+ });
2780
+ })
2781
+ },
2683
2782
  {
2684
2783
  id: 'FLOW-038',
2685
2784
  module: 'flow',
@@ -2741,7 +2840,7 @@ export const policyRules = [
2741
2840
  id: 'FLOW-030',
2742
2841
  module: 'flow',
2743
2842
  appliesTo: ['create', 'update'],
2744
- sourceRef: "Yerel bildirim adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): notification-schedule name = kimlik (düz metin, aynı kimlik eskisinin yerini alır), title düz metin, message ve value İFADE, path sayfa; notification-cancel name = kimlik ('*' hepsi). Zamanın türü value'nun sonucunun şeklinden okunur (@ng-apps/flow notificationTrigger): 'HH:mm' her gün, { weekday: 1-7, time: 'HH:mm' } her hafta (1 = Pazartesi), ISO tarih-saat bir kez. Eksik name/value backend FlowDtoValidator'da 400 alır; id/at/daily/weekly/body gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür",
2843
+ sourceRef: "Yerel bildirim adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): notification-schedule name = kimlik ve title = başlık METİN ŞABLONU (yalnız {{ }} içi çözülür — 2026-10-08, FLOW-022; aynı kimlik eskisinin yerini alır), message ve value İFADE, path sayfa (düz metin); notification-cancel name = kimlik, metin şablonu ('*' hepsi). Zamanın türü value'nun sonucunun şeklinden okunur (@ng-apps/flow notificationTrigger): 'HH:mm' her gün, { weekday: 1-7, time: 'HH:mm' } her hafta (1 = Pazartesi), ISO tarih-saat bir kez. Eksik name/value backend FlowDtoValidator'da 400 alır; id/at/daily/weekly/body gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür. GUID değerli args.id platformun kendi StepArgument kimliğidir (okunan akıştan geri yazım) ve işaretlenmez — 37 S-21",
2745
2844
  check: definition => flowAllSteps(definition)
2746
2845
  .filter(step => step.type === 'notification-schedule' || step.type === 'notification-cancel')
2747
2846
  .flatMap((step) => {
@@ -2749,13 +2848,16 @@ export const policyRules = [
2749
2848
  const text = (key) => typeof args[key] === 'string' ? String(args[key]).trim() : '';
2750
2849
  const findings = [];
2751
2850
  const schedule = step.type === 'notification-schedule';
2851
+ // 37 S-21: StepArgument bir AggregateRoot<Guid>'dir; okunan akış args.id'yi GUID olarak
2852
+ // taşır ve geri yazımda o platformun kimliğidir. Yalnız GUID olmayan id ajanın uydurmasıdır.
2752
2853
  const stray = ['id', 'identifier', 'at', 'daily', 'weekly', 'time', 'body', 'text']
2753
- .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2854
+ .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '')
2855
+ .filter(key => key !== 'id' || !isGuid(String(args.id)));
2754
2856
  if (!text('name')) {
2755
2857
  findings.push({
2756
2858
  ruleId: 'FLOW-030',
2757
2859
  severity: 'block',
2758
- message: `'${flowStepLabel(step)}' ${step.type} adımının kimliği yok: args.name düz metin olarak yazılmalı (ör. 'sulama')${schedule ? ' — aynı kimlikle yeniden planlama eskisinin yerini alır' : " — '*' bütün planlı bildirimleri siler"}. Platform eksik kimliği 400 ile reddeder.`,
2860
+ message: `'${flowStepLabel(step)}' ${step.type} adımının kimliği yok: args.name metin olarak yazılmalı (ör. 'sulama'; kayıt başına 'ilac-{{ item.id }}')${schedule ? ' — aynı kimlikle yeniden planlama eskisinin yerini alır' : " — '*' bütün planlı bildirimleri siler"}. Platform eksik kimliği 400 ile reddeder.`,
2759
2861
  guide: 'ngapps://guides/mobile'
2760
2862
  });
2761
2863
  }
@@ -3262,6 +3364,46 @@ export const policyRules = [
3262
3364
  return findings;
3263
3365
  }
3264
3366
  },
3367
+ /**
3368
+ * WF-035 — 38 S-10: `query.fail` gateway'in `gatewayOnError` adımına bağlanınca bozuk id'li
3369
+ * istek 500 `JavaScriptException: error is not defined` döndü. Hedef adım global-error dalının
3370
+ * da hedefi olabilir; o yoldan gelince `error` kurulur, bağlı fail yolundan gelince kurulmaz —
3371
+ * bu yüzden hedefin global-error hedefi olması uyarıyı susturmaz.
3372
+ */
3373
+ {
3374
+ id: 'WF-035',
3375
+ module: 'workflow',
3376
+ appliesTo: ['create', 'update', 'preview'],
3377
+ sourceRef: "IStep.cs FailedOutput: global-error dalına yalnız fail dalı BAĞLANMAMIŞSA geçilir ve `error` değişkenini yalnız ActivityContext.EnterGlobalErrorHandling kurar. Bağlı fail dalında sonraki adımlar patlayan adımın hatasını kendi değişkeninden okur: ActivityContext.CreateJavascriptEngine her adımı adıyla { data, meta, error: { code, message, detail, type, cause } } olarak koyar. Ölçüm 38 S-10 (2026-09-30): query.fail → gatewayOnError, bozuk id → 500 'error is not defined'",
3378
+ check: (definition, context) => {
3379
+ const steps = workflowSteps(workflowBody(definition, context.workflowKind));
3380
+ const byId = new Map(steps.filter(step => step.id).map(step => [String(step.id), step]));
3381
+ const readsGlobalError = /(?<![\w$.])error\s*(?:\?\.|\.(?=[A-Za-z_$])|\[)/;
3382
+ const strings = (value) => {
3383
+ if (typeof value === 'string')
3384
+ return [value];
3385
+ if (Array.isArray(value))
3386
+ return value.flatMap(strings);
3387
+ if (value && typeof value === 'object')
3388
+ return Object.values(value).flatMap(strings);
3389
+ return [];
3390
+ };
3391
+ return steps.flatMap((step) => asArray(step.branches)
3392
+ .map(branch => branch)
3393
+ .filter(branch => branch?.type === 'fail' && Boolean(branch.nextStepId))
3394
+ .map(branch => byId.get(String(branch.nextStepId)))
3395
+ .filter((target) => Boolean(target) && strings(target.rule).some(text => readsGlobalError.test(text)))
3396
+ .map(target => {
3397
+ const name = stepLabel(step);
3398
+ return {
3399
+ ruleId: 'WF-035',
3400
+ severity: 'warn',
3401
+ message: `'${name}' adımının fail dalı '${stepLabel(target)}' adımına bağlı ve o adım 'error.*' okuyor. 'error' YALNIZ global-error dalında (fail dalı bağlanmamışken) kurulur; bağlı fail dalından gelindiğinde tanımsızdır ve adım 500 'JavaScriptException: error is not defined' ile düşer. Patlayan adımın hatasını kendi değişkeninden okuyun: ${name}.error.message · ${name}.error.code (ayrıca detail, type, cause; çıktı ${name}.data). Ya da fail dalını bağlamayın: hata global-error dalına düşer ve 'error' orada kurulur. Global-error hedefiyle aynı adımı paylaşmak yerine fail dalına ayrı bir response adımı bağlayın (ör. 404).`,
3402
+ guide: 'ngapps://guides/workflow'
3403
+ };
3404
+ }));
3405
+ }
3406
+ },
3265
3407
  {
3266
3408
  id: 'WF-021',
3267
3409
  module: 'workflow',
@@ -575,7 +575,6 @@ export const TYPED_STYLE_COMPONENTS = {
575
575
  * eski birleşik kaskadla (tema+preset+own) çözülmeye devam eder.
576
576
  */
577
577
  export const NODE_SURFACE_TYPES = new Set([
578
- 'table',
579
578
  'datatable',
580
579
  'collapsible-container',
581
580
  'popover',
@@ -24,7 +24,13 @@ export function actionScript(action, captureTarget = studioWebTarget) {
24
24
  var count = performance.getEntriesByType('resource').length;
25
25
  var node = document.querySelector(${selector});
26
26
  if (!node) return { found: false, applied: false, requests: count };
27
- var target = node.matches('button, a, [role="button"]') ? node : (node.querySelector('button, a, [role="button"]') || node);
27
+ // g-04 sırası: düğümün kendisi basılabilirse o → <id>__press → içteki ilk basılabilir öğe.
28
+ // Mobil çoklu seçim tetikleyicisi kökte role="combobox" taşır ve içindeki ilk role="button"
29
+ // çipin "kaldır" düğmesidir — eskiden ona basılıp seçim siliniyordu (37 S-18). Tek basışlı
30
+ // denetimin yüzeyi <id>__press'tir (ui-native press-role-guard), anahtarınki role="switch".
31
+ var press = document.querySelector(${JSON.stringify(`[data-element-id="${String(action.target ?? '')}${INTERNAL_ROLE_SEPARATOR}press"]`)});
32
+ var target = node.matches('button, a, [role="button"], [role="switch"], [role="combobox"]') ? node
33
+ : (press || node.querySelector('button, a, [role="button"], [role="switch"]') || node);
28
34
  // S-2: sekme grubunun KÖKÜNE click, DOM sırasındaki ilk sekmeye gider ve "uygulandı" der —
29
35
  // ajan ikinci sekmeyi hiç seçemiyordu (2026-09-16 raporu madde 6). Sekme seçmek 'tab'in işi.
30
36
  if (target.closest && target.closest('[role="tablist"]') && !node.matches('[role="tab"]')) {
@@ -354,15 +360,32 @@ export function actionScript(action, captureTarget = studioWebTarget) {
354
360
  if (!field) return finish({ found: true, applied: false, note: 'düğümde input/select/textarea yok — bu komponent native alan kullanmıyor olabilir, click ile deneyin', requests: count });
355
361
  var proto = field instanceof HTMLSelectElement ? HTMLSelectElement.prototype
356
362
  : (field instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype);
363
+ // K1 (g-04): gerçek kullanıcı alana odaklanır, yazar ve çıkar. Blur'suz yazımda odak kaybına
364
+ // bağlı akış (ui-native girişlerin 'changed'i) hiç koşmuyordu (37 S-19). focus() odağı alamazsa
365
+ // (görünmez alan, odaksız belge) olaylar elle atılır: Svelte 'blur'u, React 'focusout'u dinler.
366
+ var focusField = function () {
367
+ field.focus();
368
+ if (document.activeElement === field) return;
369
+ field.dispatchEvent(new FocusEvent('focus'));
370
+ field.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
371
+ };
372
+ var blurField = function () {
373
+ if (document.activeElement === field) { field.blur(); return; }
374
+ field.dispatchEvent(new FocusEvent('blur'));
375
+ field.dispatchEvent(new FocusEvent('focusout', { bubbles: true }));
376
+ };
377
+ focusField();
357
378
  if (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) {
358
379
  var shouldCheck = wanted === 'true' || wanted === '1' || wanted === 'on';
359
380
  if (field.checked !== shouldCheck) field.click();
381
+ blurField();
360
382
  return finish({ found: true, applied: true, requests: count });
361
383
  }
362
384
  var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
363
385
  setter.call(field, ${value});
364
386
  field.dispatchEvent(new Event('input', { bubbles: true }));
365
387
  field.dispatchEvent(new Event('change', { bubbles: true }));
388
+ blurField();
366
389
  return finish({ found: true, applied: true, requests: count });
367
390
  })()`;
368
391
  }
@@ -45,7 +45,10 @@ export function domSummaryScript(viewportHeight) {
45
45
  var field = node.querySelector('input, select, textarea');
46
46
  var chosen = node.querySelector('.selected-item');
47
47
  var holder = field || chosen;
48
- var value = !owned(holder) ? '' : (field ? (field.type === 'password' ? '***' : clean(field.value)) : clean(chosen.textContent));
48
+ // 37 S-23: OTP'nin gizli girdisi type=text'tir ve kod özet satırında açık yazıyordu; sır sinyali
49
+ // tarayıcının otomatik tamamlama ipucudur (one-time-code · current-password · new-password).
50
+ var secret = !!field && (field.type === 'password' || /(^|\\s)(one-time-code|[\\w-]*-password)(\\s|$)/i.test(String(field.getAttribute('autocomplete') || '')));
51
+ var value = !owned(holder) ? '' : (field ? (secret ? '***' : clean(field.value)) : clean(chosen.textContent));
49
52
  var media = node.querySelector('img, svg, canvas, video, iframe');
50
53
  // İç içe <li> hem grubu hem alt mesajları sayıyordu: tek grup + 2 alt hata 3 görünüyordu (B-7).
51
54
  var errorItems = Array.prototype.slice.call(node.querySelectorAll('.form-validation-errors li:not(:has(li))'))
@@ -12,6 +12,7 @@
12
12
  "catalog": "app-bar, slot ve drawer mobil kataloğun Dalga 1 bileşenleridir. component_list mobil uygulamada bunları döndürmüyorsa katalog henüz yazılmamıştır ve layout_create 422 unknown-type alır (MOB-003).",
13
13
  "verify": "Kabuğu layout'a bağlı bir sayfada page_screenshot ile doğrula (mobilde varsayılan device: 'phone').",
14
14
  "menuTarget": "app-bar.menuTarget çekmecenin elementId'sidir (nav_drawer); iç rol app_bar__menu düğmesidir — page_screenshot actions:[{ action:'click', target:'app_bar__menu' }] çekmeceyi açar.",
15
+ "drawerButtons": "Çekmecedeki düğmeler ŞABLONDUR: mobile_app_create her sayfa için bir düğme üretir — elementId menu_<slug> (slug'daki tire ve diğer işaretler alt çizgiye döner: ana-sayfa → menu_ana_sayfa), metin pageName. Buradaki iki düğme bu örneğin kendi pages listesine (gorevler, profil) karşılıktır; başka uygulamaya kopyalanmaz (37 S-9).",
15
16
  "drawerFlows": "Menü düğmelerine akış ayrı adımda bağlanır: flow_create (host: 'layout', adım redirect { path: '<sayfa slug'ı>' }) → layout_schema_patch set_event { target: 'menu_gorevler', event: 'click' }.",
16
17
  "drawerNode": "Çekmece mobil kataloğun `drawer` bileşenidir (kind 'component', çocuk alır): layout'a doğrudan yazılır, prop'ları katalogdakilerdir (`position`, `size`, `title`, `isDismissDisabled`); kapalı doğar, `app-bar` menüsü `isOpen: true` yazarak açar. Katalogda olmayan prop (ör. `width`, `overlay`) ya da seçenek dışı değer (düğmede `variant: 'ghost'` — geçerli olanlar `solid`/`text`/`outline`) layout_create'i 422 ile düşürür.",
17
18
  "layoutContract": "Mizanpaj sözleşmesi: üst güvenli alan tek yerde, kabukta (app-bar 'safe-area' edges:'top' içinde; sayfa ikinci pay vermez). Ekran kenarı boşluğu tek değer (16) ve tek yerde: slot'un yatay ve üst margin'i — içerik app-bar'ın 16 dp altından başlar, kabuksuz sayfadaki gibi. shell_root 'shrink' ve padding'siz — düz container olduğu için tip yazılmazsa runtime 10 dp padding verir ve app-bar kenardan kopar.",
@@ -85,6 +85,7 @@
85
85
  "type": "view",
86
86
  "kind": "component",
87
87
  "props": {},
88
+ "blocks": [],
88
89
  "children": [
89
90
  {
90
91
  "elementId": "row_card",
@@ -118,6 +119,7 @@
118
119
  "type": "view",
119
120
  "kind": "component",
120
121
  "props": {},
122
+ "blocks": [],
121
123
  "children": [
122
124
  {
123
125
  "elementId": "lst_tasks_bos",
@@ -130,6 +132,7 @@
130
132
  "data": "Kayıt yok"
131
133
  }
132
134
  },
135
+ "blocks": [],
133
136
  "children": []
134
137
  }
135
138
  ]
@@ -453,6 +453,8 @@ Companion'ı adından değil **ana entity'nin `localizationEntityId` alanından*
453
453
  UUIDv7) anahtar doğru kurulmuştur.
454
454
 
455
455
  Gateway bu iki kuralı ENT-006 (PK identity/tip uyumu) ve ENT-007 (enum şekli) olarak denetler.
456
+ `identity: true` olup `properties.generationType` 0/1 taşımayan anahtar ENT-006 **block**'tur: platform
457
+ create'te de update'te de 400 "Please select a valid generation type" döner (38 S-1).
456
458
 
457
459
  > **Çok dilli bir entity TEK adımda silinir.** `entity_delete` ana entity'ye uygulanır, companion
458
460
  > da onunla düşer. (Aynı gün daha erken bir ölçümdeki "önce companion'ı sil" adımı artık geçersiz;
@@ -486,6 +488,20 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
486
488
  > yolunda bu çeviriyi **platform** yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı
487
489
  > sessizce siler.**
488
490
 
491
+ Var olan entity'yi `entity_patch` ile uygulamaya bağlamanın çalışan şekli — yamalanan belge
492
+ `entity_get`'in okuma şeklidir, yol `/applications`, öğe `{ applicationId }`:
493
+
494
+ ```jsonc
495
+ // Bağ listesini yazar (var olan bağlar da listede olmalı — replace tam listedir)
496
+ { "op": "replace", "path": "/applications", "value": [{ "applicationId": "<application id>" }] }
497
+ // Tek bağ ekler
498
+ { "op": "add", "path": "/applications/-", "value": { "applicationId": "<application id>" } }
499
+ ```
500
+
501
+ `/applicationIds` yolu belgede yoktur; çıplak guid öğesi platformda 500 `JsonException`, `{ id }`
502
+ öğesi boş `applicationId` ile yanıltıcı "This record is in use. Related table: 'Applications'" verir
503
+ (37 S-1). Gateway üçünü de yukarıdaki şekle çevirir ve `gatewayNormalized` ile söyler.
504
+
489
505
  ## İlişkiler (doğru şekil — 57 alanla doğrulandı)
490
506
 
491
507
  ```jsonc
@@ -98,10 +98,10 @@ dalından devam eder.
98
98
  | Global | İçerik |
99
99
  |---|---|
100
100
  | `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)` |
101
- | **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` hem prop'u yazar hem UI'ı günceller (change event). Addaki `-` silinir: `tickets-table` → `ticketstable` |
101
+ | **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` prop'u yazar ve UI'ı günceller; `setter` adımı gibi bileşenin `change` akışını **koşturmaz** — `change` akışı yalnız kullanıcı etkileşiminde koşar (2026-10-08; öncesindeki runtime her kod yazımında `change` akışını koştururdu). Addaki `-` silinir: `tickets-table` → `ticketstable` |
102
102
  | `state.page` / `state.app` | Sayfa/uygulama state'i (oku/yaz): `state.page.selectedId = row.id`. **Bellekte yaşar** — sayfa her açıldığında tanımlı `value`'dan başlar; mobil uygulamada kalıcı olması için değişkene `storage` verilir (`platform_guide('mobile')` §Kalıcı state). **Tanımlı değişkenler de buradadır:** `variable_create` ile yazılan her `key` sayfa düzeyinde `state.page.<key>`, uygulama düzeyinde `state.app.<key>` olur; `value` nesneyse alanlarına inilir (`state.page.MyTest.isActive`) |
103
- | `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası — ama YALNIZ HTTP durum metnidir (401 için `'Unauthorized'`), ucun mesajı değil. `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ve **yalnız orada okunur**: storage sayfa başına tek `FlowMachine`'de yaşar, ama dışarıdan başlayan her akış başlarken `'error'`'u ve kendi adımlarının önceki değerlerini temizler (`flow-engine.ts` `run`, 2026-09-25) — normal akışta `Steps.error` boştur; hata akışı ve onun tetiklediği akış (`nested`) onu görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse aynı alan ucun hata gövdesini taşır; aşağıdaki kutuya bakın |
104
- | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı tekrarlayıcının `change` olayı olarak iletilir: `change`'e bağlı akış her yazımda koşar, o akış da `item`'a yazarsa döngü olur |
103
+ | `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası: `{ status, message, body }` — `message` ucun kendi mesajı (`err.reason`, boşsa HTTP durum metni), `status` HTTP kodu, `body` ucun hata gövdesi (`microservice.ts` `.catch`; tablo `platform_guide('page')`). `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ve **yalnız orada okunur**: storage sayfa başına tek `FlowMachine`'de yaşar, ama dışarıdan başlayan her akış başlarken `'error'`'u ve kendi adımlarının önceki değerlerini temizler (`flow-engine.ts` `run`, 2026-09-25) — normal akışta `Steps.error` boştur; hata akışı ve onun tetiklediği akış (`nested`) onu görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse `Steps.<adım>.data` `null`'dır, gövde `.error.body`'dedir; aşağıdaki kutuya bakın |
104
+ | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı da aynı kuraldadır: o satırı günceller, tekrarlayıcının `change` akışını koşturmaz |
105
105
  | `event` | `{name, data, source: {id, props}}` |
106
106
  | `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
107
107
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
@@ -116,9 +116,10 @@ dalından devam eder.
116
116
  > **`.map is not a function` bir dizi sözleşmesi sorunu DEĞİL, düşmüş bir adımdır.**
117
117
  > Ölçüldü 2026-09-05 (`f09-repro/probe`, yerel kurulum): başarılı bir microservice adımında
118
118
  > `Steps.<adım>.data` `[object Array]`'dir, `Array.isArray` true döner ve `.map`/`.filter`/
119
- > `.concat` çalışır. Adım hata döndüğünde (401, 500, geçersiz parametre) motor aynı alana ucun
120
- > **hata gövdesini** yazar — o bir nesnedir, dizi metodları yoktur ve
121
- > `(Steps.x.data || []).map is not a function` tam olarak burada çıkar. 2026-09-05 koşusunda
119
+ > `.concat` çalışır. Adım hata döndüğünde (401, 500, geçersiz parametre) eski runtime aynı alana ucun
120
+ > **hata gövdesini** yazıyordu — o bir nesnedir, dizi metodları yoktur ve
121
+ > `(Steps.x.data || []).map is not a function` tam olarak burada çıkıyordu. Bugünkü runtime `data`'yı
122
+ > `null` yazar ve gövdeyi `Steps.<adım>.error.body`'ye taşır (`|| []` koruması artık yeter). 2026-09-05 koşusunda
122
123
  > boş kalan şirket filtresinin sebebi buydu: adım korumalı bir uca 401 ile düşmüştü.
123
124
  > **Teşhis sırası:** önce `Steps.<adım>.error`'a bak, sonra ifadeyi suçla. Bir dizi bekliyorsan
124
125
  > `Array.isArray(Steps.<adım>.data)` ile koru; boş listeye düşmek, hata gövdesi üzerinde
@@ -25,7 +25,7 @@ söyler. Hiçbir kural zorlamaz — ucun gerçek bir tuzağı yoksa alanı hiç
25
25
  - **Sessiz boş dönüş** — "filtre eşleşmezse 404 değil boş dizi döner"
26
26
 
27
27
  ```jsonc
28
- { "name": "urun-listesi", "httpMethod": "Get",
28
+ { "name": "urun-listesi", "httpMethod": "get",
29
29
  "description": "Aktif ürünleri ada göre sıralı döner.",
30
30
  "caution": "Sayfalama yok; katalog büyüdükçe yanıt boyutu doğrusal büyür." }
31
31
  ```
@@ -297,6 +297,8 @@ değişkenlerini `Page.variables` olarak taşır; tarayıcıda ve cihazda okunur
297
297
  **`rule.isPrivate: true`** ver (Studio: "Sunucuda tut"). Aynı istekteki sonraki middleware'ler
298
298
  onu okumaya devam eder; istemciye gitmez. Sayfanın akışları istemcide koşar ve `Page.variables`'ı
299
299
  okur — işaretli değişken orada `undefined`'dır; akışın ihtiyaç duyduğu alanı işaretleme.
300
+ `IsPrivate` değişken Studio otomatik tamamlamasında önerilmez ve `workflow_middleware_variables`
301
+ çıktısında da yer almaz (sonraki middleware'in editörü dahil).
300
302
  - İstemcinin gerçekten ihtiyaç duyduğu alanları ayrı, işaretsiz bir değişkende **izin listesiyle**
301
303
  kur (yukarıdaki `settings`). Aynı ad sonra işaretsiz bir adımla yeniden yazılırsa istemciye açılır.
302
304
  - Yalnız microservice uçlarına bağlı middleware'de değişken yanıtla dönmez; işaret yine de zararsızdır.