@appaflytech/zeyrek-ai-mcp 0.0.24 → 0.0.26

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 (81) hide show
  1. package/.env.example +0 -15
  2. package/dist/config.js +0 -11
  3. package/dist/index.js +0 -2
  4. package/dist/knowledge/componentCatalog.js +25 -13
  5. package/dist/knowledge/componentEvents.js +6 -13
  6. package/dist/knowledge/normalize.js +17 -68
  7. package/dist/knowledge/rules.js +49 -137
  8. package/dist/knowledge/styleResolve.js +4 -50
  9. package/dist/knowledge/themeResolve.js +3 -3
  10. package/dist/observability/trace.js +1 -1
  11. package/dist/platform/diagnostics.js +4 -10
  12. package/dist/platform/domSummary.js +1 -1
  13. package/dist/platform/http.js +7 -23
  14. package/dist/platform/responses.js +2 -6
  15. package/dist/platform/{capture/capture.js → screenshot.js} +514 -52
  16. package/dist/platform/secrets.js +0 -9
  17. package/dist/platform/strictArguments.js +12 -68
  18. package/dist/platform/tokenVault.js +4 -187
  19. package/dist/resources/examples/workflow-http-step.json +2 -2
  20. package/dist/resources/guides/component.md +1 -6
  21. package/dist/resources/guides/expressions.md +2 -2
  22. package/dist/resources/guides/functions.md +1 -1
  23. package/dist/resources/guides/layout.md +7 -11
  24. package/dist/resources/guides/microfunction.md +28 -0
  25. package/dist/resources/guides/microservice.md +4 -10
  26. package/dist/resources/guides/middleware.md +1 -5
  27. package/dist/resources/guides/page.md +55 -215
  28. package/dist/resources/guides/theme.md +1 -4
  29. package/dist/resources/guides/translation.md +1 -1
  30. package/dist/resources/guides/workflow.md +9 -12
  31. package/dist/resources/schema-fingerprint.json +2 -2
  32. package/dist/resources/schemas/CreateWorkflowCommand.json +3 -0
  33. package/dist/resources/schemas/PreviewWorkflowCommand.json +3 -0
  34. package/dist/resources/schemas/StepDto.json +12 -9
  35. package/dist/resources/schemas/UpdateWorkflowCommand.json +3 -0
  36. package/dist/server.js +1 -3
  37. package/dist/tools/applications.js +3 -60
  38. package/dist/tools/components.js +10 -50
  39. package/dist/tools/datasources.js +1 -1
  40. package/dist/tools/designSchemaPatch.js +6 -16
  41. package/dist/tools/externalComponents.js +4 -25
  42. package/dist/tools/flows.js +6 -79
  43. package/dist/tools/layouts.js +12 -44
  44. package/dist/tools/microservices.js +10 -26
  45. package/dist/tools/pages.js +67 -303
  46. package/dist/tools/platformInfo.js +2 -6
  47. package/dist/tools/rows.js +1 -7
  48. package/dist/tools/schemaOps.js +1 -1
  49. package/dist/tools/screenWrite.js +5 -6
  50. package/dist/tools/screens.js +3 -90
  51. package/dist/tools/workflows.js +1 -1
  52. package/package.json +1 -1
  53. package/dist/knowledge/mobileComponentCatalog.js +0 -801
  54. package/dist/knowledge/mobileComponentEvents.js +0 -225
  55. package/dist/knowledge/platform/lint.js +0 -312
  56. package/dist/knowledge/platform/mobile.js +0 -78
  57. package/dist/knowledge/platform/mobileRules.js +0 -182
  58. package/dist/knowledge/platform/mobileTabs.js +0 -108
  59. package/dist/knowledge/platform/profile.js +0 -20
  60. package/dist/knowledge/platform/resolve.js +0 -122
  61. package/dist/knowledge/platform/web.js +0 -38
  62. package/dist/knowledge/styleResolveNative.js +0 -510
  63. package/dist/platform/argumentAliases.js +0 -128
  64. package/dist/platform/bodyFile.js +0 -103
  65. package/dist/platform/capture/actions.js +0 -333
  66. package/dist/platform/capture/browser.js +0 -1
  67. package/dist/platform/capture/console-log.js +0 -80
  68. package/dist/platform/capture/index.js +0 -11
  69. package/dist/platform/capture/readiness.js +0 -103
  70. package/dist/platform/capture/summary.js +0 -77
  71. package/dist/platform/capture/target.js +0 -5
  72. package/dist/platform/capture/targets/studio-mobile.js +0 -15
  73. package/dist/platform/capture/targets/studio-web.js +0 -5
  74. package/dist/platform/schemaReadMemory.js +0 -60
  75. package/dist/resources/examples/mobile-app-shell-drawer.json +0 -209
  76. package/dist/resources/examples/mobile-app-shell-stack.json +0 -123
  77. package/dist/resources/examples/mobile-app-shell-tab.json +0 -174
  78. package/dist/resources/examples/mobile-list-detail-form.json +0 -656
  79. package/dist/resources/guides/mobile.md +0 -419
  80. package/dist/tools/mobile.js +0 -203
  81. package/dist/tools/profileWrite.js +0 -45
@@ -38,12 +38,3 @@ export function maskSecrets(payload) {
38
38
  const tokenRefs = {};
39
39
  return { payload: walk(payload, '', masked, tokenRefs, 0), masked, tokenRefs };
40
40
  }
41
- /**
42
- * Serbest METİNDEKİ kimlik parçaları (konsol satırı, hata yığını): JWT biçimli dize ve `Bearer …`.
43
- * `maskSecrets` anahtar adına bakar; tarayıcı konsolu anahtarsız düz metin taşır (g-06).
44
- */
45
- export function maskSecretText(text) {
46
- return text
47
- .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, `Bearer ${maskedValue}`)
48
- .replace(/\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, maskedValue);
49
- }
@@ -1,11 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { diagnosticsParam, runWithDiagnostics } from './diagnostics.js';
3
- import { applyArgumentAliases, closestArgument, runWithArgumentNotes } from './argumentAliases.js';
4
- import { BODY_FILE_ARGUMENTS, bodyFileParam, loadBodyFile } from './bodyFile.js';
5
- import { PlatformApiError } from './errors.js';
6
- import { logError } from '../observability/trace.js';
7
- /** Doğrulanmış argümanlara takılan çeviri notları (`ArgumentNotes`); JSON'a ve platform isteğine girmez. */
8
- const ARGUMENT_NOTES = Symbol('argumentNotes');
9
3
  /**
10
4
  * `writes`/`updates` açıklamalı ama KAYIT yazmayan tool'lar: çalıştırma, önizleme, dışa aktarma, indirme,
11
5
  * bağlantı testi. Yanıtlarında yazma tanılaması (silentlyDropped) yoktur; parametre yalnız tool listesini
@@ -31,75 +25,25 @@ function isRawShape(value) {
31
25
  * olmayan bir sorgu kusuruna yönlendirdi. Tek tek tool'lara alan eklemek sınıfı kapatmaz; sessizlik
32
26
  * burada, SDK'ya şema verilmeden önce kaldırılır.
33
27
  */
34
- export function rejectUnknownArguments(server, platform) {
28
+ export function rejectUnknownArguments(server) {
35
29
  const registerTool = server.registerTool.bind(server);
36
- // Zorunlu gövde argümanı `bodyFile` açıkken şemada isteğe bağlı yayınlanır (yoksa şemaya uyan istemci
37
- // gövdeyi satır içinde yollamak zorunda kalır ve `bodyFile` "birlikte verilemez" ile reddedilirdi);
38
- // zorunluluk dosya okunduktan sonra burada denetlenir.
39
- const requiredBodies = new Map();
40
- // g-07/g-08: `bodyFile` ve takma ad tablosu SDK'nın strict doğrulamasından ÖNCE koşmalı — sonra
41
- // koşsa yanlış ad zaten reddedilmiş, dosyadan gelen gövde doğrulanmamış olur. SDK doğrulamayı
42
- // `validateToolInput` içinde yapar; çeviri orada uygulanır ve notlar doğrulanmış argümanlara
43
- // sembol anahtarla takılır.
44
- if (platform) {
45
- const validator = server;
46
- const validate = validator.validateToolInput.bind(server);
47
- validator.validateToolInput = async (tool, args, name) => {
48
- try {
49
- const loaded = await loadBodyFile(name, args, platform.config);
50
- const requiredBody = requiredBodies.get(name);
51
- if (requiredBody && loaded.args?.[requiredBody] === undefined) {
52
- throw new PlatformApiError(`${name}: '${requiredBody}' zorunlu — satır içinde ya da bodyFile ile (.json dosyası) verin. İstek gönderilmedi.`, 400);
53
- }
54
- const { args: aliased, applied } = await applyArgumentAliases(name, loaded.args, { platform, shape: tool.inputSchema?.shape ?? {} });
55
- const parsed = await validate(tool, aliased, name);
56
- const notes = { aliasesApplied: applied, ...(loaded.note ? { bodyFile: loaded.note } : {}) };
57
- return (applied.length > 0 || loaded.note) && parsed && typeof parsed === 'object' ? Object.assign(parsed, { [ARGUMENT_NOTES]: notes }) : parsed;
58
- }
59
- catch (error) {
60
- // Doğrulamadan önceki çeviri (dosya, takma ad) `handle`'ın dışında koşar: hata burada günlüğe yazılır.
61
- if (error instanceof PlatformApiError)
62
- logError(error);
63
- throw error;
64
- }
65
- };
66
- }
67
30
  server.registerTool = ((name, config, callback) => {
68
31
  if (!isRawShape(config.inputSchema))
69
32
  return registerTool(name, config, callback);
70
- // Tanılama notları her tool'da compact döner (g-08: okuma ve çalıştırma yanıtında da 8 not +
71
- // "N not daha"). `diagnostics` parametresi yalnız kayıt yazan tool'lara, microservice_execute'a ve
72
- // *_preview'a açılır — ötekilerde tool listesini büyütmesin diye varsayılan kullanılır. Parametre
73
- // handler'a ULAŞMAZ (gövdeyi `...rest` ile platforma geçiren tool'larda sızardı), yalnız
74
- // toolResult'ın okuduğu bağlama konur.
75
- const writesRecords = config.annotations?.readOnlyHint === false && config.annotations.destructiveHint !== true && !NON_WRITING_TOOLS.has(name);
76
- const exposesDiagnostics = writesRecords || name === 'microservice_execute' || name.endsWith('_preview');
77
- const bodyArgument = BODY_FILE_ARGUMENTS[name];
78
- const bodySchema = bodyArgument ? config.inputSchema[bodyArgument] : undefined;
79
- if (bodyArgument && bodySchema && !bodySchema.isOptional())
80
- requiredBodies.set(name, bodyArgument);
81
- const shape = {
82
- ...config.inputSchema,
83
- ...(bodyArgument && bodySchema ? { [bodyArgument]: bodySchema.optional(), bodyFile: bodyFileParam(bodyArgument) } : {}),
84
- ...(exposesDiagnostics ? { diagnostics: diagnosticsParam } : {})
85
- };
86
- const invoke = (args, extra) => {
87
- const { diagnostics, ...rest } = exposesDiagnostics ? args ?? {} : { ...args, diagnostics: undefined };
88
- return runWithDiagnostics(diagnostics === 'full' ? 'full' : 'compact', () => callback(exposesDiagnostics ? rest : args, extra), exposesDiagnostics);
89
- };
90
- const run = (args, extra) => {
91
- const { [ARGUMENT_NOTES]: notes, ...rest } = args ?? {};
92
- return runWithArgumentNotes(notes ?? { aliasesApplied: [] }, () => invoke(rest, extra));
93
- };
33
+ // Yazma tool'ları tanılama kipini alır; parametre handler'a ULAŞMAZ (gövdeyi `...rest` ile
34
+ // platforma geçiren tool'larda sızardı), yalnız toolResult'ın okuduğu bağlama konur.
35
+ const writesDiagnostics = config.annotations?.readOnlyHint === false && config.annotations.destructiveHint !== true && !NON_WRITING_TOOLS.has(name);
36
+ const shape = writesDiagnostics ? { ...config.inputSchema, diagnostics: diagnosticsParam } : config.inputSchema;
37
+ const run = writesDiagnostics
38
+ ? ((args, extra) => {
39
+ const { diagnostics, ...rest } = args ?? {};
40
+ return runWithDiagnostics(diagnostics === 'full' ? 'full' : 'compact', () => callback(rest, extra));
41
+ })
42
+ : callback;
94
43
  const accepted = Object.keys(shape).join(', ');
95
- const suggestions = (keys) => keys
96
- .map(key => ({ key, near: closestArgument(key, Object.keys(shape)) }))
97
- .filter(entry => entry.near !== undefined)
98
- .map(entry => ` '${entry.key}' → '${entry.near}' mi demek istediniz?`)
99
- .join('');
100
44
  const inputSchema = z.object(shape, {
101
45
  errorMap: (issue, context) => issue.code === 'unrecognized_keys'
102
- ? { message: `${name} şu üst düzey argümanları tanımıyor: ${issue.keys.map(key => `'${key}'`).join(', ')}. Tanınmayan alan sessizce yok sayılmaz — çağrı hiç gönderilmedi. Bu tool'un okuduğu alanlar: ${accepted}.${suggestions(issue.keys)}` }
46
+ ? { message: `${name} şu üst düzey argümanları tanımıyor: ${issue.keys.map(key => `'${key}'`).join(', ')}. Tanınmayan alan sessizce yok sayılmaz — çağrı hiç gönderilmedi. Bu tool'un okuduğu alanlar: ${accepted}.` }
103
47
  : { message: context.defaultError }
104
48
  }).strict();
105
49
  return registerTool(name, { ...config, inputSchema }, run);
@@ -1,8 +1,5 @@
1
1
  import { PlatformApiError } from './errors.js';
2
2
  import { randomBytes } from 'node:crypto';
3
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
4
- import { dirname, join } from 'node:path';
5
- import { logStep } from '../observability/trace.js';
6
3
  /**
7
4
  * `page_screenshot`/`page_runtime_get` açıklaması "jetonu uygulamanın kendi giriş ucundan alın
8
5
  * (`microservice_execute`)" diyor, ama gizli alan maskesi `access_token`'ı `***` yapıyor ve o yol
@@ -12,19 +9,10 @@ import { logStep } from '../observability/trace.js';
12
9
  * Kasa bu çelişkiyi jetonu bağlama HİÇ sokmadan çözer: maskelenen JWT gateway sürecinde tutulur,
13
10
  * yanıta yalnız kısa ömürlü bir referans (`tok_…`) döner ve kimlik isteyen tool'lar referansı
14
11
  * çözer. Jeton ne yanıtta ne günlükte açık görünür.
15
- *
16
- * g-09 (`plans/mobil-plantcare-acigi`, K9): kasa süreç sınırını aşar. `.env` `MCP_STATE_DIR`
17
- * verilmişse kayıtlar `token-vault.json`'a (0600) yazılır ve süreç açılışta onu okur; süre
18
- * `MCP_TOKEN_TTL_MINUTES`'tan gelir (verilmezse eski 15 dk). Dosya YALNIZ lokal hedefte yazılır —
19
- * uzak bir org'un jetonu diske inmez. Test kullanıcısının (`asUser: 'test:<ad>'`) jetonu da burada,
20
- * etiketiyle tutulur; aynı kullanıcı TTL boyunca yeniden giriş yapmaz.
21
12
  */
22
- const legacyTtlMinutes = 15;
13
+ const ttlMs = 15 * 60 * 1000;
23
14
  const maxEntries = 64;
24
15
  const vault = new Map();
25
- let ttlMs = legacyTtlMinutes * 60 * 1000;
26
- let vaultFile;
27
- let warnedRemote = false;
28
16
  export const tokenRefPattern = /^tok_[0-9a-f]{16}$/;
29
17
  /** Üç base64url parçalı JWT — parola ya da API anahtarı bu şekle uymaz. */
30
18
  export function looksLikeJwt(value) {
@@ -42,132 +30,29 @@ function sweep(now) {
42
30
  vault.delete(oldest.value);
43
31
  }
44
32
  }
45
- function isLocalTarget(baseUrl) {
46
- try {
47
- const host = new URL(baseUrl).hostname;
48
- return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host.endsWith('.localhost');
49
- }
50
- catch {
51
- return false;
52
- }
53
- }
54
- /**
55
- * Aynı dosyayı birden çok gateway süreci paylaşır (her Claude Code oturumu kendi stdio sürecini açar).
56
- * Yazma "son yazan kazanır" olursa bir sürecin kaydı ötekinin yazmasıyla silinirdi: yazmadan önce ve
57
- * bellekte olmayan referans istenince dosya okunur, süresi dolmamış yabancı kayıtlar belleğe katılır.
58
- */
59
- function mergeFromFile() {
60
- if (!vaultFile)
61
- return;
62
- try {
63
- const stored = JSON.parse(readFileSync(vaultFile, 'utf8'));
64
- const now = Date.now();
65
- for (const [ref, entry] of Object.entries(stored.entries ?? {})) {
66
- if (!vault.has(ref) && tokenRefPattern.test(ref) && typeof entry.token === 'string' && typeof entry.expires === 'number' && entry.expires > now) {
67
- vault.set(ref, { token: entry.token, expires: entry.expires, ...(typeof entry.label === 'string' ? { label: entry.label } : {}) });
68
- }
69
- }
70
- }
71
- catch {
72
- // dosya yok ya da okunamadı: bellekteki kasa geçerli
73
- }
74
- }
75
- function persist() {
76
- if (!vaultFile)
77
- return;
78
- mergeFromFile();
79
- sweep(Date.now());
80
- try {
81
- const temporary = `${vaultFile}.${process.pid}.tmp`;
82
- mkdirSync(dirname(vaultFile), { recursive: true, mode: 0o700 });
83
- writeFileSync(temporary, JSON.stringify({ entries: Object.fromEntries(vault) }), { mode: 0o600 });
84
- renameSync(temporary, vaultFile);
85
- }
86
- catch (error) {
87
- logStep('jeton kasası ✕', `kasa dosyası yazılamadı (${vaultFile}) — jetonlar bu süreçte bellekte kalır`, [error instanceof Error ? error.message : String(error)]);
88
- }
89
- }
90
- /**
91
- * Süreç açılışında bir kez çağrılır (`index.ts`); testler yeniden başlamayı `clearTokenVault` +
92
- * bu çağrıyla benzetir. Dosyadaki süresi dolmuş kayıtlar yüklenmez ve dosya onlarsız yeniden yazılır.
93
- */
94
- export function configureTokenVault(config) {
95
- ttlMs = (config.tokenTtlMinutes ?? legacyTtlMinutes) * 60 * 1000;
96
- vaultFile = undefined;
97
- if (!config.stateDirectory)
98
- return;
99
- if (!isLocalTarget(config.orgApiBaseUrl)) {
100
- if (!warnedRemote) {
101
- warnedRemote = true;
102
- logStep('jeton kasası', `ORG_API_BASE_URL lokal değil — kasa dosyaya YAZILMAZ, jetonlar yalnız süreç belleğinde tutulur (MCP_STATE_DIR yok sayıldı)`);
103
- }
104
- return;
105
- }
106
- vaultFile = join(config.stateDirectory, 'token-vault.json');
107
- persist();
108
- }
109
- /** Kasanın geçerli ömrü (dakika) — yanıt notları sabit bir süre yazmasın. */
110
- export const tokenTtlMinutes = () => Math.round(ttlMs / 60000);
111
- export function rememberToken(token, label) {
33
+ export function rememberToken(token) {
112
34
  const now = Date.now();
113
35
  sweep(now);
114
36
  for (const [ref, entry] of vault) {
115
37
  if (entry.token === token) {
116
38
  entry.expires = now + ttlMs;
117
- if (label !== undefined)
118
- entry.label = label;
119
- persist();
120
39
  return ref;
121
40
  }
122
41
  }
123
42
  const ref = `tok_${randomBytes(8).toString('hex')}`;
124
- vault.set(ref, { token, expires: now + ttlMs, ...(label !== undefined ? { label } : {}) });
125
- persist();
43
+ vault.set(ref, { token, expires: now + ttlMs });
126
44
  return ref;
127
45
  }
128
46
  export function resolveToken(reference) {
129
- if (!vault.has(reference))
130
- mergeFromFile();
131
47
  const entry = vault.get(reference);
132
48
  if (!entry)
133
49
  return undefined;
134
50
  if (entry.expires <= Date.now()) {
135
51
  vault.delete(reference);
136
- persist();
137
52
  return undefined;
138
53
  }
139
54
  return entry.token;
140
55
  }
141
- /**
142
- * Jetonun kendi süresi (`exp`) kasanın TTL'inden kısa olabilir: uygulamanın jetonu 15 dk yaşarken kasa
143
- * 60 dk tutuyordu ve süresi dolmuş jetonla çekilen kare sessizce çıkış yapmış sayfayı gösterirdi.
144
- * 30 sn pay bırakılır; `exp` okunamayan jeton geçerli sayılır (karar uygulamanındır).
145
- */
146
- const jwtExpired = (token) => {
147
- try {
148
- const payload = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8'));
149
- return typeof payload.exp === 'number' && payload.exp * 1000 <= Date.now() + 30_000;
150
- }
151
- catch {
152
- return false;
153
- }
154
- };
155
- function labeledToken(label) {
156
- const find = () => {
157
- const now = Date.now();
158
- for (const entry of vault.values()) {
159
- if (entry.label === label && entry.expires > now && !jwtExpired(entry.token))
160
- return entry.token;
161
- }
162
- return undefined;
163
- };
164
- const found = find();
165
- if (found || !vaultFile)
166
- return found;
167
- mergeFromFile();
168
- return find();
169
- }
170
- /** Bellekteki kasayı boşaltır (süreç yeniden başlamasının benzetimi); dosyaya dokunmaz. */
171
56
  export function clearTokenVault() {
172
57
  vault.clear();
173
58
  }
@@ -181,76 +66,8 @@ export function resolveIdentityToken(value) {
181
66
  return value;
182
67
  const token = resolveToken(value);
183
68
  if (token === undefined) {
184
- throw new PlatformApiError(`'${value}' referansı geçersiz ya da süresi dolmuş (${Math.round(ttlMs / 60000)} dk). Tercih edilen yol asUser: 'test:<ad>' — gateway kök .env'deki test kullanıcısıyla kendisi giriş yapar. Yoksa uygulamanın giriş ucunu microservice_execute ile yeniden çağırıp yanıttaki tokenRefs değerini kullanın.`, 400);
185
- }
186
- return token;
187
- }
188
- /**
189
- * Giriş yanıtındaki erişim jetonu: yanıt birden çok JWT taşıyabilir (erişim + yenileme). Anahtarı
190
- * `access` içeren önce, sonra `refresh` içermeyen, en son ilk bulunan — yenileme jetonuyla istek atmak
191
- * her uçta 401 demekti.
192
- */
193
- function accessJwt(value) {
194
- const found = [];
195
- const collect = (node, key, depth) => {
196
- if (depth > 12)
197
- return;
198
- if (looksLikeJwt(node)) {
199
- found.push({ key, token: node });
200
- return;
201
- }
202
- if (Array.isArray(node))
203
- node.forEach(entry => collect(entry, key, depth + 1));
204
- else if (node && typeof node === 'object')
205
- Object.entries(node).forEach(([name, entry]) => collect(entry, name, depth + 1));
206
- };
207
- collect(value, '', 0);
208
- return (found.find(entry => /access/i.test(entry.key)) ?? found.find(entry => !/refresh/i.test(entry.key)) ?? found[0])?.token;
209
- }
210
- /**
211
- * g-09: `asUser` üç şekil alır — ham JWT, `tok_…` referansı ve `test:<ad>`. Üçüncüsünde gateway kök
212
- * `.env`'deki `MCP_TEST_USER_<AD>` satırının tarif ettiği giriş ucunu (uygulamanın kendi uç'u)
213
- * anonim çağırır, yanıttaki ilk JWT'yi kasaya etiketiyle koyar ve TTL boyunca yeniden kullanır.
214
- * Parola ne yanıta, ne günlüğe (`hideRequestBody`), ne hata metnine girer; satırın değeri hiçbir
215
- * mesajda gösterilmez. AI `.env`'i düzenlemez: tanımsız kullanıcı, insanın ekleyeceği değişkenin
216
- * adıyla reddedilir.
217
- */
218
- export async function resolveIdentity(value, platform, project, environment) {
219
- if (value === undefined || !value.startsWith('test:'))
220
- return resolveIdentityToken(value);
221
- const name = value.slice('test:'.length).trim().toLowerCase();
222
- const variable = `MCP_TEST_USER_${name.toUpperCase()}`;
223
- const label = `test:${name}@${platform.resolveProject(project)}/${environment ?? platform.config.defaultEnvironment}`;
224
- const cached = labeledToken(label);
225
- if (cached)
226
- return cached;
227
- const raw = platform.config.testUsers[name];
228
- if (!raw) {
229
- const defined = Object.keys(platform.config.testUsers);
230
- throw new PlatformApiError(`Test kullanıcısı '${name}' tanımlı değil. Kök .env'e insan ekler: ${variable}={"application":"<uygulama slug>","microservice":"<giriş ucu slug>","body":{…giriş gövdesi…}} (biçim: docs/ortam.md §Test kullanıcıları); gateway yeniden başlatılır. AI .env'i düzenlemez. Tanımlı olanlar: ${defined.length > 0 ? defined.join(', ') : '(yok)'}.`, 400);
231
- }
232
- let login;
233
- try {
234
- login = JSON.parse(raw);
235
- }
236
- catch {
237
- throw new PlatformApiError(`${variable} JSON olarak okunamadı (değer gösterilmez). Biçim: {"application":"…","microservice":"…","body":{…}} — docs/ortam.md §Test kullanıcıları.`, 400);
238
- }
239
- if (typeof login.application !== 'string' || typeof login.microservice !== 'string') {
240
- throw new PlatformApiError(`${variable} 'application' ve 'microservice' alanlarını taşımalı (değer gösterilmez) — docs/ortam.md §Test kullanıcıları.`, 400);
241
- }
242
- const endpoint = `${login.application}/${login.microservice}`;
243
- const response = await platform.tenantRaw(typeof login.method === 'string' ? login.method : 'POST', project, `/m/${endpoint}`, {
244
- environment,
245
- body: login.body,
246
- anonymous: true,
247
- hideRequestBody: true
248
- });
249
- const token = accessJwt(response.body);
250
- if (!token) {
251
- throw new PlatformApiError(`Test kullanıcısı '${name}' girişi jeton döndürmedi: ${endpoint} HTTP ${response.httpStatus}. Kök .env'deki ${variable} satırını (uç ve gövde) ve kullanıcının uygulamada var olduğunu kontrol edin; gövde ve kimlik bilgisi gösterilmez.`, 400);
69
+ throw new PlatformApiError(`'${value}' referansı geçersiz ya da süresi dolmuş (15 dk). Uygulamanın giriş ucunu microservice_execute ile yeniden çağırıp yanıttaki tokenRefs değerini kullanın.`, 400);
252
70
  }
253
- rememberToken(token, label);
254
71
  return token;
255
72
  }
256
73
  /**
@@ -7,7 +7,7 @@
7
7
  "_rules": {
8
8
  "payloadExpression": "ÜÇ ANAHTAR DA ZORUNLUDUR, boş olsalar bile: ({ body: {}, header: {}, queryString: {} }). Yürütücü üçüne de C# dynamic üye erişimiyle bakar (HttpStep.cs:32,42,59), eksik anahtar adımı RuntimeBinderException ile düşürür. Ölçülen mesaj birebir: \"'System.Dynamic.ExpandoObject' does not contain a definition for 'queryString'\". Bu bir doğrulama hatası değildir; adım fail dalına gider.",
9
9
  "urlExpression": "Bu bir İFADEDİR, düz metin değil: sabit adres tırnak içinde yazılır ('https://api.example.com/v1/rates'), dinamik adres ifadeyle kurulur ('https://api.example.com/orders/' + input.orderId). Tırnaksız yazarsan JS onu değişken sanır ve adım 'x is not defined' ile düşer.",
10
- "output": "ÖLÇÜLDÜ 2026-09-16 — ŞEMAYLA ÇELİŞİR. workflow_step_schema('http') returnScheme'i kök seviyede { httpStatusCode, data } ilan ediyor, AMA ifade bağlamında adım { data, meta: { elapsedTime, httpStatusCode, totalCount }, error } şeklindedir: gövde <adım>.data, durum kodu <adım>.meta.httpStatusCode. <adım>.httpStatusCode UNDEFINED döner. Şemaya göre yazarsan sessizce boş değer alırsın.",
10
+ "output": "ÖLÇÜLDÜ 2026-09-16: ifade bağlamında adım { data, meta: { elapsedTime, httpStatusCode, totalCount }, error } şeklindedir — gövde <adım>.data, durum kodu <adım>.meta.httpStatusCode; <adım>.httpStatusCode UNDEFINED döner. workflow_step_schema('http') returnScheme'i 2026-09-28'den beri yalnız gövdeyi anlatır: adsız, tipsiz (anonymous) tek girdi, her adımda olduğu gibi zarfı değil.",
11
11
  "responseParsing": "Yanıt gövdesi JSON'a benziyorsa ayrıştırılır, benzemiyorsa SESSİZCE boş nesne ({}) olur (HttpStep.cs:92) — XML dönen uçtan veri alamazsın ve hata da almazsın (36/W-2, açık bulgu). Ham metne erişmenin yolu yoktur.",
12
12
  "contentType": "KOD OKUMASINDAN (bu koşuda ölçülmedi): 'application/json' → JsonContent; 'application/x-www-form-urlencoded' → düz anahtar/değer; BAŞKA BİR DEĞER ya da BOŞ → gövde body.ToString() ile gider ve nesne gövdesi için bu, tip adının kendisidir (HttpStep.cs:63-85). POST/PUT'ta contentType'ı AÇIKÇA yazın.",
13
13
  "stepName": "Adım çıktısına ADIM ADIYLA erişilir, o yüzden ad geçerli bir JS tanımlayıcısı olmalıdır. Boşluklu ad ('Call Roles') ifadede çağrılamaz; ölçümde call_roles is not defined ile düştü. snake_case kullanın.",
@@ -78,7 +78,7 @@
78
78
  "usage": {
79
79
  "postGovdesi": "POST için: { stepType: 'http', urlExpression: \"'https://api.example.com/orders'\", httpMethod: 'POST', contentType: 'application/json', payloadExpression: \"({ body: { orderId: input.orderId, adet: 2 }, header: { Authorization: 'Bearer ' + system.token }, queryString: {} })\" }",
80
80
  "queryStringKullanimi": "Sorgu parametresi payloadExpression'ın queryString anahtarına konur, URL'e elle eklenmez: queryString: { from: 'USD', to: 'TRY' }.",
81
- "onlemliOkuma": "Gövdeyi <adım>.data ile, durum kodunu <adım>.meta.httpStatusCode ile okuyun; şemanın vaat ettiği <adım>.httpStatusCode undefined'dır (ölçüldü 2026-09-16).",
81
+ "onlemliOkuma": "Gövdeyi <adım>.data ile, durum kodunu <adım>.meta.httpStatusCode ile okuyun; <adım>.httpStatusCode undefined'dır (ölçüldü 2026-09-16).",
82
82
  "dogrulama": "workflow_preview ile koşturun ve yanıttaki steps[] içinde http adımının input satırlarına bakın: source:'url' ve source:'payload' iki ayrı satır olarak görünür; hata varsa runtimeErrors alanı adımı adıyla sayar."
83
83
  }
84
84
  }
@@ -3,11 +3,6 @@
3
3
  Sayfaya konan her şeyin ortak kuralları. Kuruluş sırası ve şema gövdesi için
4
4
  `platform_guide('page')`; HTML kaçış kapısının derin sözleşmesi için `platform_guide('html')`.
5
5
 
6
- > **Mobil uygulamada** katalog ayrıdır: `component_list`/`component_get` mobil satırları okur,
7
- > `component_get` `mobileKnowledge` (bileşenin mobil farkları) ve `mobileNotes` ekler; katalogda
8
- > olmayan tip `MOB-003` alır. Stil CSS değil React Native'dir — `page_runtime_get(resolveStyles)`
9
- > `nativeStyle` döner (`platform_guide('mobile')`).
10
-
11
6
  ## Keşif: iki tool, üç bilgi katmanı
12
7
 
13
8
  ```
@@ -113,7 +108,7 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
113
108
 
114
109
  | `states` | Anlamı |
115
110
  |---|---|
116
- | `isDroppable` | İçine komponent alır (container/form/repeater true). **Değer kataloğun kararıdır** (`component_list` `droppable` sütunu): Studio kanvası bırakma hedefini bu alandan çözer, bu yüzden gateway yazdığı düğümlerde alanı katalogdan doldurur ve `gatewayNormalized` ile bildirir (PAGE-036). `false` yazılmış bir kabın içine kanvasta bırakma yapılamaz — bilerek kapatmak için `resize_component`/`set_states` |
111
+ | `isDroppable` | İçine komponent alır (container/form/repeater true) |
117
112
  | `isScrollable` | İçeriği kendi alanında kaydırır (sabit yükseklikte `overflow-y: auto`) |
118
113
  | `isWrapper` | Sayfanın/layout'un/widget'ın ANA kabı — salt düzen elemanı: `height:100%` + **`min-height:80vh`**, kap başlığı ÇİZİLMEZ. **Yalnız sayfa boyu kaplaması gereken tek kap için kullan:** iç içe kutuya verirsen 720px'lik pencerede o kutu 36px içerikle bile 576px çizilir (ölçüldü, `platform_guide('page')` Kural 1f). Çerçeve/başlık gizlemek için wrapper değil `style` (`border:'none'`) kullan. Kurulmuş bir düğümün bayrağını `resize_component` micro-op'unun `states` alanı değiştirir — düğümü yeniden kurmak gerekmez |
119
114
  | `isSticky` | Kaydırmada yerinde kalır (header/sidebar; z-index'i otomatik) |
@@ -98,8 +98,8 @@ dalından devam eder.
98
98
  | `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)` |
99
99
  | **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` |
100
100
  | `state.page` / `state.app` | Kalıcı state (oku/yaz): `state.page.selectedId = row.id`. **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`) |
101
- | `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) ama **YAPIŞKANDIR**: storage sayfa başına tek `FlowMachine`'de yaşar ve `'error'` anahtarı hiç temizlenmez, yani ilk hatadan sonra o sayfadaki her akışta dolu kalır — normal akışta `Steps.error` okuyan bir ifade bayat hatayı 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 |
102
- | `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 |
101
+ | `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 |
102
+ | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak satırı günceller) |
103
103
  | `event` | `{name, data, source: {id, props}}` |
104
104
  | `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
105
105
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
@@ -273,7 +273,7 @@ toplamHesapla(row.fiyat, row.adet) > 1000
273
273
  serbesttir — sunucuda o ad alanı yok.
274
274
  - **Önekli hata:** gövde hatası `Functions.<ad>: <mesaj>` (studio) ya da `<ad>: <mesaj>` (workflow)
275
275
  olarak gelir; `Steps.<adım>.error.message` ya da `runtimeErrors`'ta bu öneki ara.
276
- - **Mobil uygulama:** mobil runtime (`@ng-apps/ui-native`) `appFunctions`/`pageFunctions`'ı `Functions`'a geçirir, çağrı web'deki gibi çalışır.
276
+ - **Mobil yok** (K11): `apps/mobile` runtime'ı `Functions`'ı boş nesne kurar.
277
277
  - **Önbellek:** `/view` yolu eski gövdeyi birkaç saniye gösterebilir; `/dev` yolu anlıktır.
278
278
  - **Parametre konumsal:** `parameters` sırasını değiştirmek mevcut çağrıların anlamını değiştirir;
279
279
  `function` adımları ada göre eşlendiği için etkilenmez.
@@ -4,10 +4,6 @@ Sayfaların paylaştığı kabuk: yan menü, üst şerit, kullanıcı menüsü v
4
4
  gireceği delik. Şema gövdesi ve koordinat modeli için `platform_guide('page')` (Kural 1,
5
5
  1d, 1f); komponent sözleşmeleri için `platform_guide('component')`.
6
6
 
7
- > **Mobil uygulamada** kabuk `app-bar` + tek `slot`'tur; sekmeler uygulama ayarı
8
- > (`properties.mobile.tabs`), çekmece `drawer` widget'ıdır. Web kabuk bileşenleri (`navigations`,
9
- > `sidebar`, `header`) `MOB-004` alır — `platform_guide('mobile')` §Gezinme.
10
-
11
7
  ## Karar kuralları
12
8
 
13
9
  **1. Layout, tek `slot` içeren SIRADAN bir komponent ağacıdır.** Ayrı bir dil yoktur:
@@ -65,7 +61,7 @@ yapılan eski numaraya artık gerek yok.
65
61
  NEREDE durduğunu değil, BİÇİMİNİ söyler (dikey kolon / yatay şerit / dolgu yüzeyi). Hem
66
62
  runtime hem Studio tuvali bölgeleri 12 kolonluk ızgarayla yerleştirir.
67
63
 
68
- **7. Dar ekranda flex YIĞILMAZ.** Runtime'ın dar ekran kuralı yalnız ızgara özelliklerini ezer.
64
+ **7. Mobilde flex YIĞILMAZ.** Runtime'ın mobil kuralı yalnız ızgara özelliklerini ezer.
69
65
  Telefonda alt alta düşmesi gereken satırı `layout:'grid'` ile kur; `layout:'flex'` +
70
66
  `direction:'row'` telefonda da yan yana kalır (375px'te ölçüldü: ızgara çocukları 375/375px
71
67
  alt alta, flex çocukları 49/310px yan yana). Not: `wrap` varsayılanı artık açık (2026-09-01) —
@@ -144,8 +140,8 @@ bağlantı nesnesi ya da tam yol).
144
140
  "direction": { "type": "static", "data": "row" } },
145
141
  "blocks": [{ "breakpoint": 1024, "size": { "w": 12, "h": 80 }, "position": { "x": 0, "y": 0 } }] }
146
142
 
147
- // KÖTÜ — telefonda 49px'lik yan menü: row flex dar ekranda yığılmaz (Kural 7)
148
- // Dar ekranda alt alta düşmesi gerekiyorsa kökü layout:'grid' kur — bedeli, fill'in
143
+ // KÖTÜ — telefonda 49px'lik yan menü: row flex mobilde yığılmaz (Kural 7)
144
+ // Mobilde alt alta düşmesi gerekiyorsa kökü layout:'grid' kur — bedeli, fill'in
149
145
  // flex anlamının devreye girmemesi.
150
146
  ```
151
147
 
@@ -183,9 +179,9 @@ bunlardan biri olabilir:
183
179
  |---|---|
184
180
  | ~~`heightType` yalnız `auto` \| `fixed`~~ | **28 Ağustos'ta kapandı:** üçüncü değer `fill` (Kural 5b) |
185
181
  | ~~Kök kap viewport'a kilitlenmez~~ | **28 Ağustos'ta kapandı:** `fill` verilmiş kök 100vh (Kural 5b) |
186
- | Flex dar ekranda yığılmaz | Kural 7 — kabuk bugün bir MASAÜSTÜ kabuğudur |
187
- | Kırılıma duyarlı prop yok | `direction`/`gap` masaüstü ve dar ekranda AYNI; yalnız `blocks` ve `appearance.hideOn` kırılım bilir |
188
- | Dar ekranda çekmece (overlay) yok | Telefon genişliğinde içeriğin üstüne binen panel kurulamaz (mobil UYGULAMADA çekmece `drawer` widget'ıdır — `platform_guide('mobile')`) |
182
+ | Flex mobilde yığılmaz | Kural 7 — kabuk bugün bir MASAÜSTÜ kabuğudur |
183
+ | Kırılıma duyarlı prop yok | `direction`/`gap` masaüstü ve mobilde AYNI; yalnız `blocks` ve `appearance.hideOn` kırılım bilir |
184
+ | Mobil çekmece (overlay) yok | Telefonda içeriğin üstüne binen panel kurulamaz |
189
185
 
190
186
  ---
191
187
 
@@ -193,7 +189,7 @@ bunlardan biri olabilir:
193
189
  (`injectPageSchemaIntoSlots`, `getResolvedLayoutViews`), `apps/studio/src/features/page/api/page.mappers.ts`
194
190
  (`mapPageLayoutToPreview`), `packages/ng-apps-ui/src/shared/render/render.functions.ts`
195
191
  (`resolveNodeBoxStyle` — slot'un `y / auto` satırı, flex çocuk genişliği),
196
- `packages/ng-apps-ui/src/shared/render/RenderComponent.svelte` (dar ekran media kuralı),
192
+ `packages/ng-apps-ui/src/shared/render/RenderComponent.svelte` (mobil media kuralı),
197
193
  `packages/ng-apps-screen-builder/src/flex-layout.ts` (`getFlexChildStyle` — `widthType`
198
194
  fill/fixed/auto dalları, `normalizeCssSize` kabul kuralı; tuval ve runtime aynı fonksiyonu okur),
199
195
  `apps/studio/src/features/page/components/appearance/appearance-size.functions.ts` (panelin
@@ -131,6 +131,34 @@ kullanılabilir. Kuralı iki alan taşır:
131
131
  - `workflow_preview` adımı GERÇEK runtime'da koşturur; preview'ın rollback garantisi fonksiyonun
132
132
  dış dünyada yaptığı işi geri ALMAZ.
133
133
 
134
+ ### Loop içinde
135
+
136
+ Loop adımı dizinin her öğesi için fonksiyonu bir kez çağırır. Çocuğun tipi loop'un kendi
137
+ `rule.stepType`'ında, kuralı `rule.stepRule`'dadır (tekil — `children` değil, WF-018):
138
+
139
+ ```jsonc
140
+ { "id": "<GUID>", "name": "her", "type": "loop",
141
+ "rule": {
142
+ "stepType": "microfunction",
143
+ "arrayExpression": "input.kalemler || []",
144
+ "stepRule": { "stepType": "microfunction", "microfunctionId": "<published id>", "expression": "({ x: loopItem.x })" }
145
+ },
146
+ "branches": [ /* success + fail */ ] }
147
+ ```
148
+
149
+ - Çıktı `[{ item, result }]` dizisidir: `result` fonksiyonun o öğedeki dönüşü. Sonraki adımda
150
+ `her.data.map(r => r.result)`.
151
+ - **Öğe fonksiyona yalnız `args` ile gider.** `expression` loop'un motorunda değerlendirilir:
152
+ `loopItem`, `input` ve loop'tan önceki adımlar orada görünür. `context.input` akışın kendi
153
+ girdisidir, iterasyonun öğesi DEĞİL.
154
+ - `context.steps` loop'tan önce koşmuş adımları taşır (2026-09-28'den önce loop içinde boş
155
+ geliyordu; backend düzeltmesi).
156
+ - WF-MF-001 loop çocuğunu da denetler; bulgu adımı `her (loop çocuğu)` diye adlandırır. Taslak bir
157
+ fonksiyonu seçen loop ilk iterasyonda fail dalına düşer ve orada durur.
158
+ - Her iterasyon önceki adımların çıktısını `context.steps` ile yeniden taşır ve bu da
159
+ `MaxPayloadBytes` (5 MB) sınırına sayılır: büyük bir sorgudan sonra koşan loop'ta sınır
160
+ iterasyon başına dolabilir.
161
+
134
162
  Tam gövde örneği: `ngapps://examples/microfunction-node-sum`.
135
163
 
136
164
  ## Built-In ve AWS Lambda farkı
@@ -226,7 +226,8 @@ DTO'dan süzülmez (WF-026 — `platform_guide('workflow')` §Hata yolu). Sözle
226
226
  - Adımda tanımlı ama değeri boş bırakılan alanlar gövdeden/expression'dan gelen veriyi
227
227
  **ezmez**; yalnız değeri dolu olan sabitler her satıra uygulanır.
228
228
  - **`count` vardır** — kayıt sayısı için tüm satırları çekip uzunluğuna bakmayın; panel/KPI
229
- sayaçlarının doğru şekli budur.
229
+ sayaçlarının doğru şekli budur. Dönüşü tek bir sayıdır: `isReturnSchemaArray` `isSingle`'dan
230
+ bağımsız `false` okunur (2026-09-28'den önce kaydedilmiş servislerde de).
230
231
  - Gövde kökünde **`entityId` zorunlu**. (Eski "kökte datasourceId zorunlu" bilgisi yanlıştı —
231
232
  komutta öyle bir alan yok, gönderilirse yok sayılır; asıl zorunluluk selectFields'taki fieldId.)
232
233
  - `selectFields[]` / `filters[]` öğelerinde platform **`fieldId`** ister; gateway'e **`fieldSlug`**
@@ -242,16 +243,9 @@ DTO'dan süzülmez (WF-026 — `platform_guide('workflow')` §Hata yolu). Sözle
242
243
  - **Operatör sözlüğü kapalıdır:** `eq · neq · lt · lte · gt · gte · contains · icontains ·
243
244
  in · notin · startswith · endswith · istartswith · iendswith · isnull · isnotnull`.
244
245
  **`like` YOKTUR** — metin
245
- araması `contains`/`icontains` iledir. Kritik tuzak: eski backend bilinmeyen operatörü 400 ile
246
+ araması `contains`/`icontains` iledir. Kritik tuzak: platform bilinmeyen operatörü 400 ile
246
247
  reddetmez, **sessizce `eq`'e düşürür** (enum çözümlemesi unknown'da default'a düşer) — uç
247
- testten geçer ama yanlış sonuç döner (koşuda `ne` böyle geçti). 2026-09-27'den sonraki backend
248
- uç çağrısının `filters` parametresinde (JSON dizisi) 400 verir; workflow tablo adımının filtresi
249
- hâlâ sessizce `eq`'e düşer. Ad büyük/küçük harfe duyarlıdır: `NEQ` geçersizdir. Gateway her iki
250
- sürümde istek gitmeden keser: uç tanımında ve
251
- workflow tablo adımında MS-012/WF-015 (blok), çalışma anında `row_list` / `row_export` filtresinde ve
252
- `microservice_execute` / `microservice_debug`'ın `filters` parametresinde hata — sık yanlışlara öneriyle
253
- (`ne`/`!=` → `neq`, `like` → `contains`, `=` → `eq`). Studio'nun filtre ARAYÜZÜNDEKİ adlar
254
- (`notContains`, `between`…) ayrı bir sözlüktür; arayüz onları bu işleçlere çevirir.
248
+ testten geçer ama arama hiçbir şey döndürmez. Gateway MS-012/WF-015 bunu istek gitmeden keser.
255
249
  - **Write operasyonunda (`create`/`update`) yanıt yalnız primary key'dir** — platform sonucu
256
250
  PK'ya indirger ve `selectFields`'ı kaydederken sessizce boşaltır. Yanıtın başka alan da
257
251
  dönmesi gerekiyorsa **`returnFields`** kullanılır (öğeler `selectFields` ile aynı şekildedir:
@@ -57,11 +57,7 @@ cevabı adımın ifadesinde gizlidir. Ayrıntı ve yazım ölçütü: `platform_
57
57
  Gateway `set-user` adımı olmadan `user` okuyan middleware'i **MW-003** ile uyarır.
58
58
  4. **`input` YOKTUR.** Middleware girdisini HTTP bağlamından okur: `cookie`, `header`,
59
59
  `queryString`, `body`, `triggerSource` (hangi microservice/sayfa tetikledi: type, name,
60
- slug, roles, impersonatedUserId). `impersonatedUserId` ve start adımının `impersonation`
61
- dalı yalnız Studio önizlemesindeki doğrulanmış platform kullanıcısı için dolar; anonim ya da
62
- yetkisiz istekte başlık platformda silinir. `impersonation` dalı bağlı değilse start
63
- `success`'e düşer — uygulamanın kendi auth yolu koşar (ZM-22, 2026-09-26). Hazır
64
- fonksiyonlar: `system.validateToken(...)`,
60
+ slug, roles, impersonatedUserId). Hazır fonksiyonlar: `system.validateToken(...)`,
65
61
  `system.generateToken(...)`, `crypto.JWT.encode/validate`, `Authorized(user.roles)`,
66
62
  `request.url.searchParams`, `GenerateGuid()`, `UtcNow()`, `system.getPageUrl(slug)` —
67
63
  **tam sözlük ve gerçek örnekler: `platform_guide('expressions')`**.