@appaflytech/zeyrek-ai-mcp 0.0.36 → 0.0.40

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 (210) hide show
  1. package/.env.example +15 -0
  2. package/README.md +5 -5
  3. package/dist/config.js +37 -0
  4. package/dist/index.js +2 -0
  5. package/dist/knowledge/access.js +802 -0
  6. package/dist/knowledge/backendConstraints.js +18 -1
  7. package/dist/knowledge/case.js +41 -0
  8. package/dist/knowledge/componentCatalog.js +524 -53
  9. package/dist/knowledge/componentEvents.js +132 -7
  10. package/dist/knowledge/connectDataBind.js +851 -0
  11. package/dist/knowledge/connectDataDefinition.js +344 -0
  12. package/dist/knowledge/connectDataExecute.js +93 -0
  13. package/dist/knowledge/connectDataRules.js +128 -0
  14. package/dist/knowledge/decision.js +121 -0
  15. package/dist/knowledge/expressionPrelude.js +60 -0
  16. package/dist/knowledge/layoutSummary.js +140 -2
  17. package/dist/knowledge/microserviceParameters.js +58 -0
  18. package/dist/knowledge/mining.js +94 -0
  19. package/dist/knowledge/mobileComponentCatalog.js +876 -0
  20. package/dist/knowledge/mobileComponentEvents.js +259 -0
  21. package/dist/knowledge/mobileStore.js +396 -0
  22. package/dist/knowledge/normalize.js +240 -72
  23. package/dist/knowledge/outlines.js +36 -0
  24. package/dist/knowledge/platform/lint.js +385 -0
  25. package/dist/knowledge/platform/mobile.js +142 -0
  26. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  27. package/dist/knowledge/platform/mobileRules.js +231 -0
  28. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  29. package/dist/knowledge/platform/mobileTabs.js +108 -0
  30. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  31. package/dist/knowledge/platform/profile.js +25 -0
  32. package/dist/knowledge/platform/resolve.js +122 -0
  33. package/dist/knowledge/platform/web.js +39 -0
  34. package/dist/knowledge/policy.js +18 -4
  35. package/dist/knowledge/process.js +510 -0
  36. package/dist/knowledge/reservedTables.js +79 -0
  37. package/dist/knowledge/rowFilter.js +43 -0
  38. package/dist/knowledge/rules.js +485 -22
  39. package/dist/knowledge/schemaGuard.js +11 -0
  40. package/dist/knowledge/styleResolve.js +64 -10
  41. package/dist/knowledge/styleResolveNative.js +529 -0
  42. package/dist/knowledge/themeResolve.js +3 -3
  43. package/dist/observability/trace.js +13 -5
  44. package/dist/platform/argumentAliases.js +128 -0
  45. package/dist/platform/bodyFile.js +103 -0
  46. package/dist/platform/capture/actions.js +398 -0
  47. package/dist/platform/capture/browser.js +1 -0
  48. package/dist/platform/capture/capture.js +683 -0
  49. package/dist/platform/capture/console-log.js +122 -0
  50. package/dist/platform/capture/index.js +11 -0
  51. package/dist/platform/capture/readiness.js +103 -0
  52. package/dist/platform/capture/summary.js +77 -0
  53. package/dist/platform/capture/target.js +5 -0
  54. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  55. package/dist/platform/capture/targets/studio-web.js +5 -0
  56. package/dist/platform/diagnostics.js +10 -4
  57. package/dist/platform/domSummary.js +1 -1
  58. package/dist/platform/errors.js +163 -10
  59. package/dist/platform/http.js +29 -8
  60. package/dist/platform/impersonation.js +45 -0
  61. package/dist/platform/jsonArray.js +24 -0
  62. package/dist/platform/responses.js +39 -5
  63. package/dist/platform/schemaReadMemory.js +60 -0
  64. package/dist/platform/secrets.js +68 -2
  65. package/dist/platform/strictArguments.js +68 -12
  66. package/dist/platform/tokenVault.js +187 -4
  67. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  68. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  69. package/dist/resources/examples/bpm-satinalma.json +376 -0
  70. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  71. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  72. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  73. package/dist/resources/examples/connect-data-query-params.json +50 -0
  74. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  75. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  76. package/dist/resources/examples/middleware-authenticate.json +1 -1
  77. package/dist/resources/examples/mobile-app-shell-drawer.json +244 -0
  78. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  79. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  80. package/dist/resources/examples/mobile-form-screen.json +365 -0
  81. package/dist/resources/examples/mobile-list-detail-form.json +703 -0
  82. package/dist/resources/examples/page-detail-profile.json +2 -1
  83. package/dist/resources/examples/page-dynamic-form.json +3 -2
  84. package/dist/resources/examples/page-flex-sections.json +1 -1
  85. package/dist/resources/examples/page-html-embed.json +1 -1
  86. package/dist/resources/examples/workflow-basic.json +8 -2
  87. package/dist/resources/guides/access.md +810 -0
  88. package/dist/resources/guides/bpm.md +691 -0
  89. package/dist/resources/guides/case.md +153 -0
  90. package/dist/resources/guides/component.md +92 -5
  91. package/dist/resources/guides/decision.md +185 -0
  92. package/dist/resources/guides/entity.md +50 -8
  93. package/dist/resources/guides/expressions.md +14 -5
  94. package/dist/resources/guides/functions.md +1 -1
  95. package/dist/resources/guides/getting-started.md +38 -1
  96. package/dist/resources/guides/layout.md +11 -7
  97. package/dist/resources/guides/merge.md +42 -7
  98. package/dist/resources/guides/microservice.md +88 -3
  99. package/dist/resources/guides/middleware.md +69 -5
  100. package/dist/resources/guides/mobile-layout.md +193 -0
  101. package/dist/resources/guides/mobile-publish.md +148 -0
  102. package/dist/resources/guides/mobile.md +565 -0
  103. package/dist/resources/guides/page.md +469 -82
  104. package/dist/resources/guides/theme.md +8 -2
  105. package/dist/resources/guides/translation.md +1 -1
  106. package/dist/resources/guides/workflow.md +58 -1
  107. package/dist/resources/read.js +41 -0
  108. package/dist/resources/rules.js +11 -5
  109. package/dist/resources/schema-fingerprint.json +7 -5
  110. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  111. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  112. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  113. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  114. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  115. package/dist/resources/schemas/AppSessionDto.json +53 -0
  116. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  117. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  118. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  119. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  120. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  121. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  122. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  123. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  124. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  125. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  126. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  127. package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
  128. package/dist/resources/schemas/DataJobDto.json +6 -1
  129. package/dist/resources/schemas/DecisionDto.json +302 -0
  130. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  131. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  132. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  133. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +24 -0
  134. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  135. package/dist/resources/schemas/OrganizationProjectDto.json +343 -0
  136. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  137. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  138. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  139. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  140. package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
  141. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  142. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  143. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  144. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  145. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  146. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  147. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  148. package/dist/resources/schemas/StartMobileBuildCommand.json +54 -0
  149. package/dist/resources/schemas/StepDto.json +25 -1
  150. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  151. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  152. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  153. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  154. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  155. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  156. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  157. package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
  158. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +333 -0
  159. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  160. package/dist/server.js +31 -1
  161. package/dist/tools/access.js +369 -2
  162. package/dist/tools/accessOperations.js +135 -0
  163. package/dist/tools/accessPolicies.js +170 -0
  164. package/dist/tools/accessRuntime.js +203 -0
  165. package/dist/tools/applications.js +63 -3
  166. package/dist/tools/bpm.js +343 -0
  167. package/dist/tools/bpmGovernance.js +266 -0
  168. package/dist/tools/bpmOperations.js +230 -0
  169. package/dist/tools/bpmReporting.js +154 -0
  170. package/dist/tools/bpmRouting.js +150 -0
  171. package/dist/tools/bpmRuntime.js +396 -0
  172. package/dist/tools/case.js +178 -0
  173. package/dist/tools/caseRuntime.js +89 -0
  174. package/dist/tools/common.js +19 -6
  175. package/dist/tools/components.js +118 -14
  176. package/dist/tools/connectData.js +450 -0
  177. package/dist/tools/connectors.js +193 -0
  178. package/dist/tools/datasources.js +3 -3
  179. package/dist/tools/decisions.js +206 -0
  180. package/dist/tools/dependencies.js +1 -1
  181. package/dist/tools/deployments.js +38 -3
  182. package/dist/tools/designSchemaPatch.js +34 -9
  183. package/dist/tools/dtos.js +36 -5
  184. package/dist/tools/entities.js +79 -12
  185. package/dist/tools/environments.js +1 -1
  186. package/dist/tools/externalComponents.js +26 -5
  187. package/dist/tools/flows.js +119 -13
  188. package/dist/tools/folders.js +1 -1
  189. package/dist/tools/imports.js +5 -2
  190. package/dist/tools/layouts.js +50 -17
  191. package/dist/tools/merges.js +2 -2
  192. package/dist/tools/microservices.js +77 -25
  193. package/dist/tools/mobile.js +280 -0
  194. package/dist/tools/mobileStore.js +572 -0
  195. package/dist/tools/modules.js +48 -0
  196. package/dist/tools/organizationStructure.js +137 -0
  197. package/dist/tools/pages.js +467 -100
  198. package/dist/tools/platformInfo.js +28 -8
  199. package/dist/tools/profileWrite.js +92 -0
  200. package/dist/tools/projects.js +71 -9
  201. package/dist/tools/rows.js +69 -13
  202. package/dist/tools/schemaOps.js +61 -1
  203. package/dist/tools/screenWrite.js +36 -7
  204. package/dist/tools/screens.js +95 -5
  205. package/dist/tools/templates.js +26 -1
  206. package/dist/tools/translations.js +1 -1
  207. package/dist/tools/variables.js +11 -5
  208. package/dist/tools/workflows.js +10 -5
  209. package/package.json +2 -2
  210. package/dist/platform/screenshot.js +0 -1162
@@ -4,7 +4,10 @@ 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
6
  import { backendPolicyRules } from './backendConstraints.js';
7
+ import { accessPolicyRules } from './access.js';
7
8
  import { themePolicyRules } from './themeRules.js';
9
+ import { processPolicyRules } from './process.js';
10
+ import { mobileStorePolicyRules } from './mobileStore.js';
8
11
  const terminalStepTypes = new Set(['response', 'convert', 'revert']);
9
12
  const DESCRIPTION_EXEMPT_STEPS = new Set(['start']);
10
13
  const stepKindMatrix = {
@@ -19,6 +22,7 @@ const stepKindMatrix = {
19
22
  email: ['workflow', 'automation', 'middleware'],
20
23
  'file-upload': ['workflow', 'automation', 'middleware'],
21
24
  microfunction: ['workflow', 'automation', 'middleware'],
25
+ process: ['workflow', 'automation'],
22
26
  variable: ['middleware'],
23
27
  'set-user': ['middleware'],
24
28
  setcookie: ['middleware'],
@@ -146,13 +150,25 @@ export function readsBeforeGuard(source, root) {
146
150
  * code.ts:49). Eşleşme yoksa flow-engine.ts:79 sessizce durur. Studio tarafında da zincir
147
151
  * `sourceBranch === 'always'` ile çizilir (EventActionsStart.svelte:47, EventActionsItem.svelte:298).
148
152
  * condition dalları rule id'sidir (serbest), microservice'te 'success' yalnız eski şema için
149
- * yaşatılır (microservice.ts:144).
153
+ * yaşatılır (microservice.ts:174 `isHasSuccess`). 'error' dalı 2026-09-26'dan beri motorun
154
+ * KENDİSİ tanır (ADR 0004, ZM-07/g-02): bağlıysa hata anında `always` hiç koşmaz, sürer
155
+ * (microservice.ts:182 `hasBranch`) — eski motor tanımadığı dalı hiç aramadığı için bu ekleme
156
+ * geriye dönük zararsızdır.
150
157
  */
151
158
  const flowBranchNames = {
152
159
  start: ['always'],
153
- microservice: ['always', 'success'],
160
+ microservice: ['always', 'success', 'error'],
154
161
  confirm: ['yes', 'no', 'always'],
155
162
  function: ['always'],
163
+ haptic: ['always'],
164
+ location: ['always', 'error'],
165
+ 'notification-permission': ['always'],
166
+ 'notification-schedule': ['always', 'error'],
167
+ 'notification-cancel': ['always'],
168
+ bpm: ['always', 'success', 'fail'],
169
+ permission: ['always'],
170
+ 'open-settings': ['always'],
171
+ 'ad-show': ['always', 'error'],
156
172
  end: []
157
173
  };
158
174
  /**
@@ -163,8 +179,24 @@ const flowBranchNames = {
163
179
  */
164
180
  export const knownFlowStepTypes = new Set([
165
181
  'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
166
- 'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function'
182
+ 'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share',
183
+ 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'bpm',
184
+ 'permission', 'open-settings', 'ad-show'
167
185
  ]);
186
+ /**
187
+ * Cihaz yeteneği adımlarının kapalı kümeleri — `@ng-apps/flow` `tasks/device/device.functions.ts`
188
+ * `HAPTIC_STYLES`/`LOCATION_ACCURACIES`'in aynası (`test/flow-step-parity.test.ts` bağlar); backend
189
+ * `FlowDtoValidator` aynı kümeyle 400 döner. Kök `docs/adr/0003-cihaz-yetenegi-adimlari.md`.
190
+ */
191
+ export const hapticStyles = new Set(['success', 'warning', 'error', 'light', 'medium', 'heavy', 'selection']);
192
+ export const locationAccuracies = new Set(['low', 'balanced', 'high']);
193
+ /**
194
+ * Reklam yerleşiminin adı — backend `MobileStoreSettingsValidator.AdUnitNamePattern`'ın aynası (kök ADR 0012); `ad-show.value`
195
+ * ve `adBanner.unit` bu adla mağaza ayarındaki yerleşimi anar.
196
+ */
197
+ export const adPlacementNamePattern = /^[a-z][A-Za-z0-9]{0,39}$/;
198
+ /** `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
+ export const permissionStepModes = new Set(['request', 'check']);
168
200
  const flowFreeBranchTypes = new Set(['condition']);
169
201
  /** `condition.types.ts` ConditionArgumentRuleType — motorun tanıdığı üçlü. */
170
202
  const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
@@ -176,16 +208,17 @@ const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}
176
208
  // Runtime.svelte'de HAM verilir (`argument: step.args`), yani bu alanlarda ifade değerlendirilmez.
177
209
  const flowLiteralTextFields = new Map([
178
210
  ['alert', ['message', 'description']],
179
- ['confirm', ['message', 'title', 'confirmText', 'cancelText']]
211
+ ['confirm', ['message', 'title', 'confirmText', 'cancelText']],
212
+ ['share', ['title']],
213
+ ['notification-schedule', ['name', 'title', 'path']],
214
+ ['notification-cancel', ['name']]
180
215
  ]);
181
216
  const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
182
- // Barındırıcıya özel tipler (`navigate` web'de, `confirm`/`download` mobilde register edilmez)
183
- // BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten `redirect`e çeviriyor
184
- // (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da koşar, yani vaka
185
- // uyarılmaz, ORTADAN KALKAR. Geriye kalan `confirm`/`download` tek başına kusur değildir: sayfa
186
- // tek bir uygulamaya aittir (`Page.ApplicationId`), uygulamanın tipi web ya da mobile'dır
187
- // (`ApplicationType.cs`) ve gateway hangisi olduğunu bilmez. Mobil ayağı uygulama tarafında
188
- // kapanır (`ScreenRuntime.tsx`), lint'te değil.
217
+ // Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
218
+ // `redirect`e çeviriyor (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da
219
+ // koşar, yani vaka uyarılmaz, ORTADAN KALKAR. Gateway uygulamanın tipini platform profilinden bilir
220
+ // ve barındırıcı sözlüğü `platform/*.ts`'tedir (`profile.steps`); mobil farkları orada `MOB-005`
221
+ // olarak konuşulur (`platform/lint.ts`). Yeni mobil runtime `confirm`/`download`'ı kaydeder.
189
222
  const flowUnregisteredStepTypes = new Set(['end', 'query']);
190
223
  const flowSetterIterateeTypes = new Set(['change', 'fill']);
191
224
  function flowStepList(value) {
@@ -305,25 +338,71 @@ function writesRolesWithoutSlug(entityId, parameters) {
305
338
  }
306
339
  const rolesSlugMessage = "Built-in 'roles' entity'sine slug YAZMADAN rol satırı ÜRETİLİYOR (operation create/upsert). Insert 200 döner, satır role_list'te görünür ve page.roleIds ile bağlanır — ama yetkilendirme SLUG karşılaştırır: platformun get-user sorgusu roles+user_roles join'inden slug dizisi üretir ve Authorized(user.roles) o rol için hiçbir zaman tutmaz. fields'a slug'ı ekleyin ('super-admin' slug'ını vermeyin — platform süper yöneticiyi o slug ile arar). Upsert'te slug'ı İFADEYLE üretmeyin: UPDATE yolunda ifade yeniden değerlendirilir ve var olan rolün slug'ı sessizce değişir (MS-016) — üretilen slug'ı yalnız create ucunda yazın. Rolü kullanıcıya bağlamak ayrı bir user_roles satırıdır.";
307
340
  const contractKeys = ['returnDtoId', 'parameterDtoId', 'returnBehavior', 'parameterBehavior'];
308
- const filterOperators = new Set(['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'contains', 'icontains', 'in', 'notin', 'startswith', 'endswith', 'istartswith', 'iendswith', 'isnull', 'isnotnull']);
341
+ /**
342
+ * Filtre işleci sözlüğü — backend `OperatorType` enum'u (paketlenmiş `Filter` şeması; eşitliği
343
+ * `test/filter-operators.test.ts` kilitler). Studio filtre ARAYÜZÜNÜN sözlüğü (`notContains`, `between`…,
344
+ * `componentCatalog.ts`) ayrı bir kavramdır: arayüz onları bu işleçlere çevirir.
345
+ */
346
+ export const filterOperators = new Set(['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'contains', 'icontains', 'in', 'notin', 'startswith', 'endswith', 'istartswith', 'iendswith', 'isnull', 'isnotnull']);
347
+ /** Ajanların sık yazdığı yanlış adlar → doğru işleç. Yalnız ÖNERİLİR, sessizce çevrilmez. */
348
+ const filterOperatorSuggestions = {
349
+ ne: 'neq', '!=': 'neq', '<>': 'neq', noteq: 'neq', notequal: 'neq', notequals: 'neq', not_eq: 'neq',
350
+ '=': 'eq', '==': 'eq', equal: 'eq', equals: 'eq',
351
+ '<': 'lt', '<=': 'lte', '>': 'gt', '>=': 'gte',
352
+ like: 'contains', ilike: 'icontains', nin: 'notin', 'not in': 'notin', not_in: 'notin'
353
+ };
309
354
  function flattenFilterTree(filters) {
310
355
  return filters
311
356
  .filter((filter) => Boolean(filter) && typeof filter === 'object')
312
357
  .flatMap(filter => [filter, ...flattenFilterTree(asArray(filter.children))]);
313
358
  }
359
+ /**
360
+ * Bilinmeyen işleçler, öneriyle: `status: 'ne' (neq mı demek istedin?)`. Grup çocukları da okunur. Ad
361
+ * büyük/küçük harfe DUYARLI eşlenir — backend de öyle eşler (`GetValueFromDescription` `==`); `NEQ`
362
+ * bilinmeyendir ve küçük harfli adı önerilir.
363
+ */
364
+ function unknownFilterOperators(filters) {
365
+ return flattenFilterTree(filters)
366
+ .filter(filter => filter.operator !== undefined && filter.operator !== null && !filterOperators.has(String(filter.operator)))
367
+ .map(filter => {
368
+ const operator = String(filter.operator);
369
+ const lower = operator.toLowerCase();
370
+ const suggestion = filterOperators.has(lower) ? lower : filterOperatorSuggestions[lower];
371
+ return `${String(filter.field ?? filter.fieldSlug ?? filter.fieldId ?? '?')}: '${operator}'${suggestion ? ` (${suggestion} mı demek istedin?)` : ''}`;
372
+ });
373
+ }
374
+ const unknownOperatorConsequence = "Bilinmeyen işleç yola ve backend sürümüne göre ya 400 alır ya sessizce 'eq'e düşer ve sorgu yanlış sonuç döner (2026-09-27'den sonraki backend uç çağrısının `filters` parametresini 400 ile reddeder; workflow tablo adımı hâlâ sessizce 'eq' yapar); ad büyük/küçük harfe duyarlıdır — doğru işleci yazın";
314
375
  function unknownOperatorFindings(ruleId, owner, filters) {
315
- const unknownOperators = flattenFilterTree(filters)
316
- .filter(filter => filter.operator !== undefined && filter.operator !== null && !filterOperators.has(String(filter.operator).toLowerCase()))
317
- .map(filter => `${String(filter.fieldSlug ?? filter.fieldId ?? '?')}: '${String(filter.operator)}'`);
376
+ const unknownOperators = unknownFilterOperators(filters);
318
377
  return unknownOperators.length > 0
319
378
  ? [{
320
379
  ruleId,
321
380
  severity: 'block',
322
- message: `${owner}: bilinmeyen filtre operatörü — ${unknownOperators.join(', ')}. Platform bilinmeyen operatörü 400 ile REDDETMEZ, sessizce 'eq'e düşürür (EnumExtensions.GetValueFromDescription null → default) ve uç testten geçip yanlış çalışır. Geçerli sözlük: ${[...filterOperators].join(' · ')} ('like' yok — metin araması için 'contains'/'icontains').`,
381
+ message: `${owner}: bilinmeyen filtre işleci — ${unknownOperators.join(', ')}. ${unknownOperatorConsequence}. Geçerli sözlük: ${[...filterOperators].join(' · ')} ('like' yok — metin araması için 'contains'/'icontains').`,
323
382
  guide: 'ngapps://guides/microservice'
324
383
  }]
325
384
  : [];
326
385
  }
386
+ /**
387
+ * Çalışma anında gelen filtre (`row_list.filters`, `microservice_execute` `filters` parametresi): dizi ya
388
+ * da JSON dizesi. Bilinmeyen işleç varsa hata METNİ döner — araç isteği göndermeden 400 verir
389
+ * (plans/mobil-plantcare-acigi g-03). Ayrıştırılamayan değer backend'e bırakılır.
390
+ */
391
+ export function filterOperatorError(owner, filters) {
392
+ let parsed = filters;
393
+ if (typeof filters === 'string') {
394
+ try {
395
+ parsed = JSON.parse(filters);
396
+ }
397
+ catch {
398
+ return undefined;
399
+ }
400
+ }
401
+ const unknownOperators = Array.isArray(parsed) ? unknownFilterOperators(parsed) : [];
402
+ return unknownOperators.length > 0
403
+ ? `${owner}: bilinmeyen filtre işleci — ${unknownOperators.join(', ')}. İstek GÖNDERİLMEDİ. ${unknownOperatorConsequence}. Geçerli sözlük: ${[...filterOperators].join(' · ')}.`
404
+ : undefined;
405
+ }
327
406
  function isReadOperation(parameters) {
328
407
  const operation = String(parameters?.operation ?? '').toLowerCase();
329
408
  return !writeOperations.has(operation);
@@ -433,6 +512,27 @@ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Arra
433
512
  const functionsGuide = 'ngapps://guides/functions';
434
513
  /** FN-006: bu kadar dolu satırdan uzun gövde yorumsuz kalmaz; tek satırlık `return` yorum istemez. */
435
514
  const FUNCTION_COMMENT_MIN_LINES = 5;
515
+ /**
516
+ * BPM-022: `process.*` sistem işlemi karşılığı olan `Page.bpm` üyeleri (backend
517
+ * `SystemOperationConstants`, zeyrek-backend/docs/bpm/sablon-paketleri.md §Sistem işlemleri).
518
+ * `start`, `cases`, `documents`, `analytics`, `exportLog`, `offline`, `legalHold(s)` karşılıksızdır;
519
+ * onları kullanan akışa uyarı vermek yol gösteremeyen gürültüdür.
520
+ */
521
+ const PAGE_BPM_SYSTEM_MEMBERS = new Set([
522
+ 'definitions', 'users', 'directory', 'tasks', 'task', 'instances', 'instance', 'timeline', 'signal',
523
+ 'batch', 'batchOperation', 'delegations', 'delegate', 'delegation', 'handover', 'notifications',
524
+ 'notification', 'preferences', 'override', 'report'
525
+ ]);
526
+ /**
527
+ * BPM-022: karşılığı olan üyenin sistem işlemi OLMAYAN alt çağrıları (bpm.runtime.ts). Bunlar ve
528
+ * `batch('start', …)` eski yolda kalır; taze bağlam incelemesi (2026-09-23) üye düzeyindeki bakışın
529
+ * `instance(id).exportLedger()` gibi rehberin kendi önerdiği çağrıları uyardığını buldu.
530
+ */
531
+ const PAGE_BPM_UNMAPPED_CALLS = new Set([
532
+ 'tasks.calendar', 'task.comment', 'task.workedMinutes', 'task.assistant', 'instance.exportLedger',
533
+ 'instance.watch', 'instance.unwatch', 'instance.comments', 'handover.history', 'override.conflicts',
534
+ 'batchOperation.get'
535
+ ]);
436
536
  export const policyRules = [
437
537
  {
438
538
  id: 'MS-001',
@@ -712,7 +812,7 @@ export const policyRules = [
712
812
  return [{
713
813
  ruleId: 'WF-005',
714
814
  severity: 'block',
715
- message: `'${stepLabel(step)}' step'i (${step.type}) ${kind} tipinde kullanılamaz. İzin matrisi: variable/set-user/setcookie/redirect yalnız middleware; convert yalnız workflow; revert yalnız automation; response workflow+middleware.`,
815
+ message: `'${stepLabel(step)}' step'i (${step.type}) ${kind} tipinde kullanılamaz. İzin matrisi: variable/set-user/setcookie/redirect yalnız middleware; convert yalnız workflow; revert yalnız automation; response workflow+middleware; process workflow+automation.`,
716
816
  guide: 'ngapps://guides/workflow'
717
817
  }];
718
818
  });
@@ -2251,6 +2351,48 @@ export const policyRules = [
2251
2351
  }];
2252
2352
  })
2253
2353
  },
2354
+ {
2355
+ id: 'FLOW-028',
2356
+ module: 'flow',
2357
+ appliesTo: ['create', 'update'],
2358
+ sourceRef: "Yetki şablonu (access.json 1.7.0) 33 kod adımında platformun oturum uçlarına ham fetch atıyordu ('/api/' + project + '/runtime/' + application + '/auth/me' ve '/auth/sign-in'). Ham istek ng-apps-flow/src/api/instance.ts'in yaptıklarını atlar: X-Environment çözümü (resolveRuntimeEnvironment; elle ayrıştırma /preview şeridinde index -1 verir), CSRF başlığı (çerezli POST 403 CsrfTokenMismatchException — S2-37 · S3-7), 401'de tek refresh + yeniden deneme, ve ExecuteMicroserviceBehavior'ın microservice.execute kararı ile gölge günlüğü. Düzeltme 2026-09-22: giriş ve oturum bilgisi access.session.create / access.session.get system operation'larına taşındı (backend ADR 0004); çıkış 2026-10-01'de access.session.delete ile eklendi. Dış API'ye fetch meşrudur; kural yalnız göreli platform yollarını (/api/, /m/, /runtime/) görür",
2359
+ check: definition => flowAllSteps(definition)
2360
+ .filter(step => step.type === 'code')
2361
+ .flatMap((step) => {
2362
+ const body = String((step.args ?? {}).value ?? '');
2363
+ if (!/\bfetch\s*\(|XMLHttpRequest/.test(body) || !/['"`]\/(api|m|runtime)\/|\/runtime\//.test(body))
2364
+ return [];
2365
+ return [{
2366
+ ruleId: 'FLOW-028',
2367
+ severity: 'warn',
2368
+ message: `'${flowStepLabel(step)}' code adımı platformun kendi ucuna ham istek atıyor. Bu yol runtime'ın istek katmanını atlar: X-Environment elle kurulur, CSRF başlığı yoktur (çerezli POST 403 CsrfTokenMismatchException), 401'de oturum yenilenmez ve istek microservice.execute kararını ve gölge günlüğünü hiç görmez. Platform davranışını çağırmanın yolu microservice adımıdır: karşılığı olan bir system operation varsa (microservice_system_operation_list — giriş için access.session.create, oturumdaki kullanıcı için access.session.get, çıkış için access.session.delete) source:'system' bir uç açın ve akışa type:'microservice' adımı koyun; sonuç Steps.<adım>.data'dadır, kod adımı yalnız onu biçimler.`,
2369
+ guide: 'ngapps://guides/page'
2370
+ }];
2371
+ })
2372
+ },
2373
+ {
2374
+ id: 'FLOW-037',
2375
+ module: 'flow',
2376
+ appliesTo: ['create', 'update'],
2377
+ sourceRef: "mobile-qm koşusu 2026-10-01: çıkış akışı oturumu storage adımları (ZY_App_qm/ZY_AppRefresh_qm/ZY_Csrf_qm = '') ve kod adımında cookie.remove ile kapatmaya çalıştı. Platformun oturum çerezleri (AppSessionConstants: ZY_App_ · ZY_AppRefresh_ · ZY_Csrf_) sunucunun HttpOnly Set-Cookie'sidir: web'de document.cookie onları göremez, mobilde akışın cookie/storage deposu Keychain/kv-store'dur ve iOS NSHTTPCookieStorage'a ulaşmaz. Refresh ailesi sunucuda iptal edilmediği için kapat-aç sonrası auth/refresh oturumu yeniden yazdı (kullanıcı girişli açıldı). Çıkış access.session.delete system operation'ıdır (guard 2026-10-01'den beri session: süresi dolmuş jetonda 401, runtime yenileyip tekrarlar; uç isAnonymous:true kalır): aileyi iptal eder, üç çerezi yanıtla siler",
2378
+ check: definition => flowAllSteps(definition)
2379
+ .flatMap((step) => {
2380
+ const args = (step.args ?? {});
2381
+ const name = String(args.name ?? '');
2382
+ const body = String(args.value ?? '');
2383
+ const touchesSessionCookie = (step.type === 'storage' || step.type === 'setcookie')
2384
+ ? /^ZY_(App|AppRefresh|Csrf)_/.test(name)
2385
+ : step.type === 'code' && /\bcookie\s*\.\s*(remove|set)\s*\(/.test(body) && /['"`]ZY_(App|AppRefresh|Csrf)_/.test(body);
2386
+ if (!touchesSessionCookie)
2387
+ return [];
2388
+ return [{
2389
+ ruleId: 'FLOW-037',
2390
+ severity: 'warn',
2391
+ message: `'${flowStepLabel(step)}' adımı platformun oturum çerezine (ZY_App_ · ZY_AppRefresh_ · ZY_Csrf_) akıştan dokunuyor. Bu çerezler sunucunun HttpOnly çerezleridir: akışın cookie/storage deposu onlara ulaşmaz (mobilde Keychain/kv-store, web'de document.cookie) ve refresh ailesi sunucuda açık kaldığı için kullanıcı kapat-aç sonrası yeniden girişli olur. Çıkış için source:'system', systemOperation:'access.session.delete', isAnonymous:true bir uç açın ve akışa type:'microservice' adımı koyun, ardından redirect ile giriş sayfasına gidin — uç refresh ailesini iptal eder ve üç çerezi yanıtla siler. Uygulamanın kendi değerleri (ör. 'beni hatırla' bayrağı) storage'da kalabilir.`,
2392
+ guide: 'ngapps://guides/access'
2393
+ }];
2394
+ })
2395
+ },
2254
2396
  {
2255
2397
  id: 'FLOW-020',
2256
2398
  module: 'flow',
@@ -2374,7 +2516,7 @@ export const policyRules = [
2374
2516
  id: 'FLOW-022',
2375
2517
  module: 'flow',
2376
2518
  appliesTo: ['create', 'update'],
2377
- 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)",
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",
2378
2520
  check: definition => flowAllSteps(definition)
2379
2521
  .flatMap((step) => {
2380
2522
  const type = String(step.type ?? '');
@@ -2387,16 +2529,269 @@ export const policyRules = [
2387
2529
  .map(field => ({
2388
2530
  ruleId: 'FLOW-022',
2389
2531
  severity: 'warn',
2390
- message: `'${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.`,
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.`,
2391
2537
  guide: 'ngapps://guides/page'
2392
2538
  }));
2393
2539
  })
2394
2540
  },
2541
+ {
2542
+ id: 'FLOW-035',
2543
+ module: 'flow',
2544
+ appliesTo: ['create', 'update'],
2545
+ sourceRef: "share adımının argümanı StepManagementArgument'ın Title/Message/Path alanlarında saklanır (StepType.cs `share`, kök docs/adr/0001-share-akis-adimi.md); url/link/text gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür — adım hiçbir şey paylaşmadan akış sürer. `message` ve `path` İFADEDİR (@ng-apps/flow resolveShareContent → runCode): tırnaksız 'https://…' JS'te etiket + yorumdur, undefined döner",
2546
+ check: definition => flowAllSteps(definition)
2547
+ .filter(step => step.type === 'share')
2548
+ .flatMap((step) => {
2549
+ const args = (step.args ?? {});
2550
+ const filled = (key) => typeof args[key] === 'string' && String(args[key]).trim().length > 0;
2551
+ const findings = [];
2552
+ const strayLinks = ['url', 'link', 'href'].filter(filled);
2553
+ const strayTexts = ['text', 'body', 'content'].filter(filled);
2554
+ if (strayLinks.length > 0) {
2555
+ findings.push({
2556
+ ruleId: 'FLOW-035',
2557
+ severity: filled('path') ? 'warn' : 'block',
2558
+ message: `'${flowStepLabel(step)}' share adımının bağlantısı args.${strayLinks.join('/args.')} alanında: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür ve adım bağlantısız kalır. Bağlantıyı args.path'e ifade olarak yazın — ör. "'https://ornek.app/urun/' + Page.queryParams.id".`,
2559
+ guide: 'ngapps://guides/page'
2560
+ });
2561
+ }
2562
+ if (strayTexts.length > 0) {
2563
+ findings.push({
2564
+ ruleId: 'FLOW-035',
2565
+ severity: filled('message') ? 'warn' : 'block',
2566
+ message: `'${flowStepLabel(step)}' share adımının metni args.${strayTexts.join('/args.')} alanında: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu sessizce düşürür. Metni args.message'a ifade olarak yazın — ör. "'Şuna bak: ' + Page.properties.name".`,
2567
+ guide: 'ngapps://guides/page'
2568
+ });
2569
+ }
2570
+ for (const key of ['message', 'path']) {
2571
+ const value = typeof args[key] === 'string' ? String(args[key]).trim() : '';
2572
+ if (/^[a-z][a-z\d+\-.]*:\/\//i.test(value)) {
2573
+ findings.push({
2574
+ ruleId: 'FLOW-035',
2575
+ severity: 'block',
2576
+ message: `'${flowStepLabel(step)}' share adımının '${key}' alanı tırnaksız bir adres: '${value}'. Bu alan İFADEDİR — tırnaksız adres JS'te etiket + yorumdur, undefined döner ve hiçbir şey paylaşılmaz. Dize olarak yazın: "'${value}'".`,
2577
+ guide: 'ngapps://guides/page'
2578
+ });
2579
+ }
2580
+ }
2581
+ return findings;
2582
+ })
2583
+ },
2584
+ {
2585
+ id: 'FLOW-029',
2586
+ module: 'flow',
2587
+ appliesTo: ['create', 'update'],
2588
+ sourceRef: "Cihaz yeteneği adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): haptic.value = stil, location.value = doğruluk, location.isRaw = adres çözülmesin. İkisi de DÜZ METİNDİR (@ng-apps/flow hapticStyle/locationAccuracy), kümede olmayan değer backend FlowDtoValidator'da 400 alır; style/accuracy/reverseGeocode gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür — adım varsayılanla (medium / balanced / adresli) koşar",
2589
+ check: definition => flowAllSteps(definition)
2590
+ .filter(step => step.type === 'haptic' || step.type === 'location')
2591
+ .flatMap((step) => {
2592
+ const args = (step.args ?? {});
2593
+ // Backend (FlowDtoValidator) yalnız tamamen boşluk olanı boş sayar; kümeyle karşılaştırmayı kırpmadan yapar.
2594
+ const raw = typeof args.value === 'string' ? args.value : '';
2595
+ const value = raw.trim() ? raw : '';
2596
+ const haptic = step.type === 'haptic';
2597
+ const allowed = haptic ? hapticStyles : locationAccuracies;
2598
+ const field = haptic ? 'stil' : 'doğruluk';
2599
+ const findings = [];
2600
+ const stray = (haptic ? ['style', 'type', 'feedback'] : ['accuracy', 'precision'])
2601
+ .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2602
+ if (value && !allowed.has(value)) {
2603
+ const unquoted = value.replace(/^['"`](.*)['"`]$/, '$1');
2604
+ const trimmed = value.trim();
2605
+ findings.push({
2606
+ ruleId: 'FLOW-029',
2607
+ severity: 'block',
2608
+ message: allowed.has(unquoted)
2609
+ ? `'${flowStepLabel(step)}' ${step.type} adımının ${field} değeri tırnaklı yazılmış: ${value}. Bu alan DÜZ METİNDİR, ifade değil — args.value: '${unquoted}' yazın.`
2610
+ : allowed.has(trimmed)
2611
+ ? `'${flowStepLabel(step)}' ${step.type} adımının ${field} değerinin çevresinde boşluk var: '${value}'. Platform kırpmadan karşılaştırır ve 400 döner — args.value: '${trimmed}' yazın.`
2612
+ : `'${flowStepLabel(step)}' ${step.type} adımının ${field} değeri '${value}' tanınmıyor; platform 400 döner. Kullanılabilir: ${[...allowed].join(', ')} (boş → ${haptic ? 'medium' : 'balanced'}).`,
2613
+ guide: 'ngapps://guides/mobile'
2614
+ });
2615
+ }
2616
+ if (stray.length > 0) {
2617
+ findings.push({
2618
+ ruleId: 'FLOW-029',
2619
+ severity: value ? 'warn' : 'block',
2620
+ message: `'${flowStepLabel(step)}' ${step.type} adımının ${field} değeri args.${stray.join('/args.')} alanında: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür ve adım varsayılanla (${haptic ? 'medium' : 'balanced'}) koşar. Değeri args.value'ya düz metin olarak yazın — ör. args.value: '${haptic ? 'success' : 'high'}'.`,
2621
+ guide: 'ngapps://guides/mobile'
2622
+ });
2623
+ }
2624
+ if (!haptic && ['reverseGeocode', 'address', 'geocode'].some(key => args[key] !== undefined)) {
2625
+ findings.push({
2626
+ ruleId: 'FLOW-029',
2627
+ severity: 'warn',
2628
+ message: `'${flowStepLabel(step)}' location adımında adres anahtarı sözleşmede YOK ve sessizce düşer. Adres varsayılan olarak çözülür (cihazda; web'de hep null); kapatmak için args.isRaw: true yazın.`,
2629
+ guide: 'ngapps://guides/mobile'
2630
+ });
2631
+ }
2632
+ return findings;
2633
+ })
2634
+ },
2635
+ {
2636
+ id: 'FLOW-039',
2637
+ module: 'flow',
2638
+ appliesTo: ['create', 'update'],
2639
+ sourceRef: "Tam ekran reklam adımının argümanı StepManagementArgument.value'dur (kök docs/adr/0012-reklam-yerlesimleri-magaza-ayari-ve-kosullu-sdk.md): mağaza ayarındaki reklam yerleşiminin ADI, DÜZ METİN — AdMob birim kimliği değil (kimlik derlemenin platformuna göre manifestten çözülür). Backend FlowDtoValidator HasAdPlacement adı ^[a-z][A-Za-z0-9]{0,39}$ deseniyle denetler, uymayan 422 alır; unit/placement/adUnitId gibi anahtarlar sözleşmede YOKTUR ve 200 ile sessizce düşer. Yerleşimin mağaza ayarında tanımlı olduğu burada denetlenemez: mobile_store_get",
2640
+ check: definition => flowAllSteps(definition)
2641
+ .filter(step => step.type === 'ad-show')
2642
+ .flatMap((step) => {
2643
+ const args = (step.args ?? {});
2644
+ const unit = typeof args.value === 'string' ? args.value : '';
2645
+ const unquoted = unit.replace(/^['"`](.*)['"`]$/, '$1');
2646
+ const stray = ['unit', 'placement', 'adUnitId', 'unitId', 'format'].filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2647
+ const findings = [];
2648
+ if (!unit.trim()) {
2649
+ findings.push({
2650
+ ruleId: 'FLOW-039',
2651
+ severity: 'block',
2652
+ message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi yok: args.value mağaza ayarındaki tam ekran yerleşimin ADINI ister (platform 422 döner). Yerleşimler: mobile_store_get — ads.units[] (format interstitial, rewarded ya da rewardedInterstitial).`,
2653
+ guide: 'ngapps://guides/mobile'
2654
+ });
2655
+ }
2656
+ else if (unquoted !== unit) {
2657
+ findings.push({
2658
+ ruleId: 'FLOW-039',
2659
+ severity: 'block',
2660
+ message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi tırnaklı yazılmış: ${unit}. Bu alan DÜZ METİNDİR, ifade değil — args.value: '${unquoted}' yazın.`,
2661
+ guide: 'ngapps://guides/mobile'
2662
+ });
2663
+ }
2664
+ else if (!adPlacementNamePattern.test(unit)) {
2665
+ findings.push({
2666
+ ruleId: 'FLOW-039',
2667
+ severity: 'block',
2668
+ message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi bir yerleşim adı değil: '${unit}'. Değer ifade ya da AdMob birim kimliği olamaz; mağaza ayarındaki yerleşimin adıdır (${adPlacementNamePattern.source}). Platform 422 döner. Yerleşimler: mobile_store_get — ads.units[].name.`,
2669
+ guide: 'ngapps://guides/mobile'
2670
+ });
2671
+ }
2672
+ if (stray.length > 0) {
2673
+ findings.push({
2674
+ ruleId: 'FLOW-039',
2675
+ severity: unit.trim() ? 'warn' : 'block',
2676
+ message: `'${flowStepLabel(step)}' ad-show adımında args.${stray.join('/args.')} alanı var: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür. Yerleşimin adı args.value'ya düz metin olarak yazılır — ör. args: { value: '<yerleşim adı>' }.`,
2677
+ guide: 'ngapps://guides/mobile'
2678
+ });
2679
+ }
2680
+ return findings;
2681
+ })
2682
+ },
2683
+ {
2684
+ id: 'FLOW-038',
2685
+ module: 'flow',
2686
+ appliesTo: ['create', 'update'],
2687
+ sourceRef: "İzin adımının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0010-izin-katalogu-tarama-ve-derlemede-budama.md): permission.value = izin türü (katalog anahtarı, DÜZ METİN — ifade değil: backend kullanım taramasında bu değeri yazılı okur ve mağaza derlemesinin taşıdığı izinleri ondan hesaplar), permission.name = kip (@ng-apps/flow PERMISSION_MODES; boş = request). Türü ya da kipi geçersiz adım backend FlowDtoValidator'da 422 alır; kind/mode/permission gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür. Hangi türlerin desteklendiğini gateway bilmez: mobile_store_get { detail: ['permissions'] }",
2688
+ check: definition => flowAllSteps(definition)
2689
+ .filter(step => step.type === 'permission')
2690
+ .flatMap((step) => {
2691
+ const args = (step.args ?? {});
2692
+ const kind = typeof args.value === 'string' ? args.value : '';
2693
+ const mode = typeof args.name === 'string' ? args.name : '';
2694
+ const stray = ['kind', 'mode', 'permission', 'type'].filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2695
+ const findings = [];
2696
+ const unquoted = kind.replace(/^['"`](.*)['"`]$/, '$1');
2697
+ if (!kind.trim()) {
2698
+ findings.push({
2699
+ ruleId: 'FLOW-038',
2700
+ severity: 'block',
2701
+ message: `'${flowStepLabel(step)}' permission adımının türü yok: args.value izin anahtarını ister (varsayılanı yoktur; platform 422 döner). Uygulamanın isteyebileceği izinler: mobile_store_get { detail: ['permissions'] } — support: 'supported' satırlarının key'i.`,
2702
+ guide: 'ngapps://guides/mobile'
2703
+ });
2704
+ }
2705
+ else if (unquoted !== kind) {
2706
+ findings.push({
2707
+ ruleId: 'FLOW-038',
2708
+ severity: 'block',
2709
+ message: `'${flowStepLabel(step)}' permission adımının türü tırnaklı yazılmış: ${kind}. Bu alan DÜZ METİNDİR, ifade değil — args.value: '${unquoted}' yazın.`,
2710
+ guide: 'ngapps://guides/mobile'
2711
+ });
2712
+ }
2713
+ else if (flowExpressionLooking.test(kind) || /\{\{/.test(kind) || !/^[a-z][A-Za-z0-9]*$/.test(kind)) {
2714
+ findings.push({
2715
+ ruleId: 'FLOW-038',
2716
+ severity: 'block',
2717
+ message: `'${flowStepLabel(step)}' permission adımının türü yazılı bir izin anahtarı değil: '${kind}'. Tür İFADE olamaz — mağaza derlemesinin hangi izni taşıyacağı bu değerden, akış koşmadan hesaplanır. Türe göre dallanmak için condition adımıyla her tür için ayrı bir permission adımı kurun.`,
2718
+ guide: 'ngapps://guides/mobile'
2719
+ });
2720
+ }
2721
+ if (mode.trim() && !permissionStepModes.has(mode)) {
2722
+ findings.push({
2723
+ ruleId: 'FLOW-038',
2724
+ severity: 'block',
2725
+ message: `'${flowStepLabel(step)}' permission adımının kipi '${mode}' tanınmıyor; platform 422 döner. Kullanılabilir: ${[...permissionStepModes].join(', ')} (boş → request). check yalnız okur, sistem penceresi açmaz.`,
2726
+ guide: 'ngapps://guides/mobile'
2727
+ });
2728
+ }
2729
+ if (stray.length > 0) {
2730
+ findings.push({
2731
+ ruleId: 'FLOW-038',
2732
+ severity: kind.trim() ? 'warn' : 'block',
2733
+ message: `'${flowStepLabel(step)}' permission adımında args.${stray.join('/args.')} alanı var: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür. Tür args.value'ya, kip args.name'e düz metin olarak yazılır — ör. args: { value: '<izin anahtarı>', name: 'check' }.`,
2734
+ guide: 'ngapps://guides/mobile'
2735
+ });
2736
+ }
2737
+ return findings;
2738
+ })
2739
+ },
2740
+ {
2741
+ id: 'FLOW-030',
2742
+ module: 'flow',
2743
+ 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",
2745
+ check: definition => flowAllSteps(definition)
2746
+ .filter(step => step.type === 'notification-schedule' || step.type === 'notification-cancel')
2747
+ .flatMap((step) => {
2748
+ const args = (step.args ?? {});
2749
+ const text = (key) => typeof args[key] === 'string' ? String(args[key]).trim() : '';
2750
+ const findings = [];
2751
+ const schedule = step.type === 'notification-schedule';
2752
+ const stray = ['id', 'identifier', 'at', 'daily', 'weekly', 'time', 'body', 'text']
2753
+ .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2754
+ if (!text('name')) {
2755
+ findings.push({
2756
+ ruleId: 'FLOW-030',
2757
+ 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.`,
2759
+ guide: 'ngapps://guides/mobile'
2760
+ });
2761
+ }
2762
+ if (schedule && !text('value')) {
2763
+ findings.push({
2764
+ ruleId: 'FLOW-030',
2765
+ severity: 'block',
2766
+ message: `'${flowStepLabel(step)}' notification-schedule adımının zamanı yok: args.value bir İFADEDİR ve sonucu 'HH:mm' (her gün), { weekday: 1-7, time: 'HH:mm' } (her hafta, 1 = Pazartesi) ya da ISO tarih-saattir (bir kez) — ör. "tp_saat.value" ya da "'08:30'".`,
2767
+ guide: 'ngapps://guides/mobile'
2768
+ });
2769
+ }
2770
+ const literal = /^'([^']*)'$/.exec(text('value'))?.[1];
2771
+ if (schedule && literal !== undefined && !/^([01]?\d|2[0-3]):[0-5]\d$/.test(literal) && Number.isNaN(Date.parse(literal))) {
2772
+ findings.push({
2773
+ ruleId: 'FLOW-030',
2774
+ severity: 'block',
2775
+ message: `'${flowStepLabel(step)}' notification-schedule adımının zamanı '${literal}' okunamaz: 'HH:mm' (00:00–23:59), ISO tarih-saat ya da { weekday, time } nesnesi yazın. Çalışmada adım error dalına düşer (status: 'invalid').`,
2776
+ guide: 'ngapps://guides/mobile'
2777
+ });
2778
+ }
2779
+ if (stray.length > 0) {
2780
+ findings.push({
2781
+ ruleId: 'FLOW-030',
2782
+ severity: 'warn',
2783
+ message: `'${flowStepLabel(step)}' ${step.type} adımında args.${stray.join('/args.')} sözleşmede YOK ve platform onu sessizce düşürür. Kimlik args.name, zaman args.value (ifade), metin args.message (ifade), başlık args.title.`,
2784
+ guide: 'ngapps://guides/mobile'
2785
+ });
2786
+ }
2787
+ return findings;
2788
+ })
2789
+ },
2395
2790
  {
2396
2791
  id: 'FLOW-023',
2397
2792
  module: 'flow',
2398
2793
  appliesTo: ['create', 'update'],
2399
- sourceRef: "Adım tipi sözlüğü backend'indir (StepType.cs) ama koşan tipler barındırıcıdan gelir: flow-engine.ts start·setter·code·condition·microservice·validation·flow-trigger register eder, web Runtime.svelte alert·confirm·redirect·download·storage·setcookie ekler, mobil ScreenRuntime.tsx alert·redirect·navigate·storage·setcookie ekler. navigate web'de, confirm/download mobilde YOKTUR; end ve query hiçbir barındırıcıda yoktur (Studio kataloğu da bunu visibility ile söyler: navigate 'mobile', confirm/download 'web'; End eklenebilir adımlar arasında değil — EventActionsAdd.svelte). Register edilmemiş tipe gelen akış SESSİZCE durur: flow-engine.ts process() callback bulamazsa hiçbir şey yapmaz (next yok, completed/stopped yok, hata yok, log yok), run() Promise'i çözülmez ve flowRunFinished() hiç koşmaz — __ngAppsFlowActivity.running sıfıra inmez, page_screenshot'ın hazır bekleyişi her eylemde bütçesini sonuna kadar yakar",
2794
+ sourceRef: "Adım tipi sözlüğü backend'indir (StepType.cs) ama koşan tipler barındırıcıdan gelir: flow-engine.ts start·setter·code·condition·microservice·validation·flow-trigger register eder, web Runtime.svelte alert·confirm·redirect·download·storage·setcookie ekler, mobil @ng-apps/ui-native runtime-tasks.ts alert·confirm·redirect·navigate·storage·setcookie·download ekler. navigate web'de YOKTUR; end ve query hiçbir barındırıcıda yoktur (Studio kataloğu da bunu visibility ile söyler: navigate 'mobile', End eklenebilir adımlar arasında değil — EventActionsAdd.svelte). Register edilmemiş tipe gelen akış SESSİZCE durur: flow-engine.ts process() callback bulamazsa hiçbir şey yapmaz (next yok, completed/stopped yok, hata yok, log yok), run() Promise'i çözülmez ve flowRunFinished() hiç koşmaz — __ngAppsFlowActivity.running sıfıra inmez, page_screenshot'ın hazır bekleyişi her eylemde bütçesini sonuna kadar yakar",
2400
2795
  check: definition => {
2401
2796
  const steps = flowAllSteps(definition);
2402
2797
  // Studio her akış tuvaline dekoratif bir End düğümü koyar ve onu sildirmez; zararlı olan
@@ -2722,6 +3117,29 @@ export const policyRules = [
2722
3117
  return findings;
2723
3118
  })
2724
3119
  },
3120
+ {
3121
+ id: 'BPM-022',
3122
+ module: 'flow',
3123
+ appliesTo: ['create', 'update'],
3124
+ sourceRef: "bpm-management 1.1.0 sayfaları süreç çalışma zamanına yalnız source: system mikroservislerle gider (plans/bpm-sistem-mikroservis D-1 a, ADR-0004); Page.bpm aynı uçlara mikroservissiz gider, rehberi okuyan ajanlar yine de yazdı (plans/bpm/demo koşuları)",
3125
+ check: definition => flowAllSteps(definition).flatMap((step) => {
3126
+ const calls = [...new Set(flowExpressionSites(step)
3127
+ .flatMap(site => [...site.expression.matchAll(/\bPage\s*\.\s*bpm\b(?:\s*\.\s*([A-Za-z_$][\w$]*))?(\s*\((?:[^()]|\([^()]*\))*\))?(?:\s*\.\s*([A-Za-z_$][\w$]*))?/g)])
3128
+ .filter(([, member = '', callArguments = '', subCall]) => member === ''
3129
+ || (PAGE_BPM_SYSTEM_MEMBERS.has(member)
3130
+ && !(subCall && PAGE_BPM_UNMAPPED_CALLS.has(`${member}.${subCall}`))
3131
+ && !(member === 'batch' && /^\s*\(\s*['"]start['"]/.test(callArguments))))
3132
+ .map(([, member, , subCall]) => (member ? `Page.bpm.${member}${subCall ? `(…).${subCall}` : ''}` : 'Page.bpm')))];
3133
+ return calls.length === 0
3134
+ ? []
3135
+ : [{
3136
+ ruleId: 'BPM-022',
3137
+ severity: 'warn',
3138
+ message: `'${flowStepLabel(step)}' adımı Page.bpm çağırıyor (${calls.join(', ')}). Sayfa akışı süreç çalışma zamanıyla source: system mikroservis üzerinden konuşur: microservice_system_operation_list ile process.* işlemini seçin, microservice_create { source: 'system', systemOperation } ile ucu kurun ve çağrıyı bir microservice adımına taşıyın; sonucu okuyan code adımı Steps.<adım>.data / .error'dan okur. Page.bpm eski yoldur — izin ve görünürlük kararı sistem işleminde aynı servisten gelir, uç Studio'da ve microservice_execute ile görünür.`,
3139
+ guide: 'ngapps://guides/bpm'
3140
+ }];
3141
+ })
3142
+ },
2725
3143
  {
2726
3144
  id: 'FLOW-027',
2727
3145
  module: 'flow',
@@ -2968,7 +3386,7 @@ export const policyRules = [
2968
3386
  id: 'MW-003',
2969
3387
  module: 'workflow',
2970
3388
  appliesTo: ['create', 'update', 'preview'],
2971
- sourceRef: "set-user değişkeni istek kapsamındaki WorkflowContext'e yazılır (SetUserStep → AddVariable('user')) ve sonraki middleware'in motoru onu görür (WorkflowRunner her koşuda JavascriptEngineInstance'ı _variables'tan kurar; sıra ExecuteMicroserviceBehavior:96 / ExecutePageBehavior:121 → OrderBy(m => m.Name)). Ama kuran middleware set-user'a ULAŞMADAN biterse (çerez yok, koşul dalı) değişken hiç doğmaz ve okuyan middleware ham 'user is not defined' ile 500 verir — 401 değil (crm koşumu B-3)",
3389
+ sourceRef: "set-user değişkeni istek kapsamındaki WorkflowContext'e yazılır (SetUserStep → AddVariable('user')) ve sonraki middleware'in motoru onu görür (WorkflowRunner her koşuda CreateJavascriptEngine() ile motoru _variables'tan kurar; sıra ExecuteMicroserviceBehavior:96 / ExecutePageBehavior:121 → OrderBy(m => m.Name)). Ama kuran middleware set-user'a ULAŞMADAN biterse (çerez yok, koşul dalı) değişken hiç doğmaz ve okuyan middleware ham 'user is not defined' ile 500 verir — 401 değil (crm koşumu B-3)",
2972
3390
  check: (definition, context) => {
2973
3391
  if (context.workflowKind !== 'middleware')
2974
3392
  return [];
@@ -2986,10 +3404,55 @@ export const policyRules = [
2986
3404
  }];
2987
3405
  }
2988
3406
  },
3407
+ {
3408
+ id: 'MW-006',
3409
+ module: 'workflow',
3410
+ appliesTo: ['create', 'update', 'preview'],
3411
+ sourceRef: "Kimlik devri değeri triggerSource'ta DEĞİL context'te durur: WorkflowContext.SetTriggerSource triggerSource'a { type, id, name, slug, roles, pathAndQuery }, context'e { type, name, roles, path, impersonatedUserId, language } yazar (WorkflowContext.cs:216-235). triggerSource.impersonatedUserId her zaman undefined okunur ve önizleme hep jeton yoluna düşer (ZM-22, koşu 37)",
3412
+ check: (definition) => {
3413
+ const readers = workflowSteps(definition).filter(step => /\btriggerSource\s*\.\s*impersonatedUserId\b/.test(JSON.stringify(step.rule ?? {})));
3414
+ return readers.length === 0
3415
+ ? []
3416
+ : [{
3417
+ ruleId: 'MW-006',
3418
+ severity: 'warn',
3419
+ message: `'${readers.map(stepLabel).join(', ')}' kimlik devrini triggerSource.impersonatedUserId'den okuyor; bu alan yok, değer hep undefined gelir. Doğrusu context.impersonatedUserId — ve yalnız start adımının impersonation dalında: değer yalnız doğrulanmış Studio önizlemesinde dolar, başka yerde yetki kararı için kullanılmaz (platform_guide('middleware') Tarif 1c).`,
3420
+ guide: 'ngapps://guides/middleware'
3421
+ }];
3422
+ }
3423
+ },
3424
+ {
3425
+ id: 'MW-007',
3426
+ module: 'workflow',
3427
+ appliesTo: ['create', 'update', 'preview'],
3428
+ sourceRef: "Sayfa middleware'inin variable adımı WorkflowContext değişkenine yazar ve sayfa yanıtı bu değişkenleri Page.variables olarak istemciye taşır (GetPageBySlugQuery → PageAppDto.Map(workflowContext.ClientVariables), önbellek isabetinde ExecutePageBehavior). rule.isPrivate işaretli değişken sunucuda kalır (WorkflowContext.AddPrivateVariable). qm '[SY] Settings Setter' { ...settings.data } ile sso_client_secret'ı istemciye verdi (2026-10-02)",
3429
+ check: (definition, context) => {
3430
+ if (context.workflowKind !== 'middleware')
3431
+ return [];
3432
+ const exposing = workflowSteps(definition).filter(step => {
3433
+ if (step.type !== 'variable' || step.rule?.isPrivate === true)
3434
+ return false;
3435
+ const expression = String(step.rule?.expression ?? '');
3436
+ return /\.\.\.\s*[A-Za-z_$]/.test(expression) || /secret|password|passwd|api_?key|private_?key/i.test(expression);
3437
+ });
3438
+ return exposing.length === 0
3439
+ ? []
3440
+ : [{
3441
+ ruleId: 'MW-007',
3442
+ severity: 'warn',
3443
+ message: `'${exposing.map(stepLabel).join(', ')}' değişkeni bütün bir kaydı yayıyor ya da gizli değer adı taşıyor. Bu middleware bir SAYFAYA bağlanırsa değişken Page.variables ile istemciye gider (tarayıcıda/cihazda okunur). Gizli değer taşıyan değişkene rule.isPrivate: true verin — aynı istekteki sonraki middleware'ler okumaya devam eder, istemciye gitmez (sayfanın akışları istemcide koşar ve Page.variables'ı okur: işaretli değişken orada undefined'dır); istemcinin gerçekten ihtiyaç duyduğu alanları ayrı bir değişkende izin listesiyle kurun: '({ app_name: settings.data.app_name })' (platform_guide('middleware') Tarif 2). Yalnız microservice uçlarına bağlı middleware'de değişken istemciye dönmez.`,
3444
+ guide: 'ngapps://guides/middleware'
3445
+ }];
3446
+ }
3447
+ },
2989
3448
  // 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):
2990
3449
  // tek liste `backendConstraints.ts`'te durur, buradan yalnız politika motoruna katılır.
2991
3450
  ...themePolicyRules,
2992
- ...backendPolicyRules()
3451
+ ...mobileStorePolicyRules,
3452
+ ...backendPolicyRules(),
3453
+ // Organizasyon şeması, erişim politikası ve alan koruması (ORG/ACC/PROT) — `access.ts`.
3454
+ ...accessPolicyRules(),
3455
+ ...processPolicyRules()
2993
3456
  ];
2994
3457
  /**
2995
3458
  * WF-MF-001'in ikinci yarısı. Bir adımın koşup koşmayacağı yalnız GÖVDEDEN anlaşılamaz: karar