@appaflytech/zeyrek-ai-mcp 0.0.58 → 0.0.60

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.
@@ -31,7 +31,8 @@ const stepKindMatrix = {
31
31
  response: ['workflow', 'middleware'],
32
32
  convert: ['workflow'],
33
33
  revert: ['automation'],
34
- balance: ['workflow', 'automation', 'middleware']
34
+ balance: ['workflow', 'automation', 'middleware'],
35
+ push: ['workflow', 'automation']
35
36
  };
36
37
  function asArray(value) {
37
38
  return Array.isArray(value) ? value : [];
@@ -84,6 +85,31 @@ function carriesValue(value) {
84
85
  * Etiketteki "(loop çocuğu)" bulguyu okuyan
85
86
  * ajanı adımın `rule.stepRule`'una yönlendirir.
86
87
  */
88
+ /**
89
+ * WF-036 kapısının sorusu alt akışlarla birlikte: tanım ya da `workflow` adımıyla (loop çocuğu dahil)
90
+ * çağırdığı akışlardan biri `push` taşıyor mu. Her akış bir kez okunur (döngüye karşı). Okunamayan alt
91
+ * akış hata fırlatır — çağıran kapı kapalı kalır.
92
+ */
93
+ export async function pushReachable(root, loadWorkflow) {
94
+ const pending = [root];
95
+ const visited = new Set();
96
+ while (pending.length > 0) {
97
+ const body = workflowBody(pending.pop(), 'automation');
98
+ if (stepCallSites(body, 'push').length > 0)
99
+ return true;
100
+ for (const { rule } of stepCallSites(body, 'workflow')) {
101
+ const workflowId = typeof rule.workflowId === 'string' ? rule.workflowId : '';
102
+ if (workflowId === '' || visited.has(workflowId))
103
+ continue;
104
+ visited.add(workflowId);
105
+ const child = await loadWorkflow(workflowId);
106
+ if (!child)
107
+ throw new Error(`alt workflow '${workflowId}' okunamadı`);
108
+ pending.push(child);
109
+ }
110
+ }
111
+ return false;
112
+ }
87
113
  function stepCallSites(body, type) {
88
114
  return workflowSteps(body).flatMap((step) => {
89
115
  const rule = (step.rule ?? {});
@@ -98,7 +98,7 @@ async function allowedRoots(config) {
98
98
  * reddedilir. Bu pencereyi DARALTIR, kapatmaz — iki denetim arasında geri çevrilen bir bağ yine görülmez; tam çözüm
99
99
  * (`openat2` RESOLVE_BENEATH) Node'da yoktur. Okunan bayt yine de açılan tutamaçtan gelir.
100
100
  */
101
- async function readGuardedFile(tool, label, absolute, extensions, maxBytes, roots, config) {
101
+ async function readGuardedFile(tool, label, absolute, extensions, maxBytes, roots, config, tooLarge) {
102
102
  const accepted = extensions.join(', ');
103
103
  if (!extensions.includes(extname(absolute).toLowerCase())) {
104
104
  throw new PlatformApiError(`${tool}: ${label} yalnız ${accepted} okur — verilen: ${absolute}. İstek gönderilmedi.`, 400);
@@ -145,6 +145,8 @@ async function readGuardedFile(tool, label, absolute, extensions, maxBytes, root
145
145
  bytesRead += chunk.bytesRead;
146
146
  }
147
147
  if (bytesRead > maxBytes) {
148
+ if (tooLarge)
149
+ throw new PlatformApiError(`${tool}: ${label} ${tooLarge} İstek gönderilmedi.`, 400);
148
150
  throw new PlatformApiError(maxBytes === maxBodyFileBytes
149
151
  ? `${tool}: ${label} 5 MB sınırını aşıyor. Tanımı parçalara bölün — sayfa için page_schema_patch'e birden çok batch, akış için flow_patch. İstek gönderilmedi.`
150
152
  : `${tool}: ${label} ${maxBytes / (1024 * 1024)} MB sınırını aşıyor — ${real}. Kodu birden çok dosyaya bölün. İstek gönderilmedi.`, 400);
@@ -258,3 +260,26 @@ async function includeFiles(tool, value, folder, bodyBytes, roots, config) {
258
260
  }
259
261
  return { value: replaceInclusions(value, '', texts), files };
260
262
  }
263
+ /**
264
+ * 2026-10-30, QM SAML girişi (ADR 0027): inline microfunction kodu dosyadan okunur. Paketlenmiş `saml-dogrula`
265
+ * 433 KB'tı; satır içi `code` onu bütünüyle ajan bağlamından geçirirdi. Dosya JSON değil ham kaynaktır (`.js` /
266
+ * `.py`) ve backend'in `MaxInlineCodeBytes` sınırı (`MicrofunctionOptions.cs`, 524288) burada da denetlenir.
267
+ */
268
+ export const CODE_FILE_TOOLS = new Set(['microfunction_create', 'microfunction_update']);
269
+ const maxCodeFileBytes = 524288;
270
+ export function codeFileParam() {
271
+ return z.string().min(1).optional().describe("'code' yerine okunacak yerel kaynak dosyası (.js ya da .py, en çok 512 KB — backend MaxInlineCodeBytes): kod ajan bağlamından geçmez, gateway dosyanın metnini 'code' olarak gönderir. 'code' ile birlikte verilemez. Yol gateway sürecinin çalışma dizininin ya da MCP_INPUT_DIR'in altında olmalı (sembolik bağ çözülür). Yanıtın codeFile alanı hangi dosyanın okunduğunu söyler.");
272
+ }
273
+ export async function loadCodeFile(tool, args, config) {
274
+ if (!CODE_FILE_TOOLS.has(tool) || !args || typeof args !== 'object' || Array.isArray(args))
275
+ return { args };
276
+ const { codeFile, ...rest } = args;
277
+ if (typeof codeFile !== 'string' || codeFile.length === 0)
278
+ return { args };
279
+ if (rest.code !== undefined) {
280
+ throw new PlatformApiError(`${tool}: codeFile ile 'code' birlikte verilemez — kod ya dosyadan ya satır içinden gelir. Birini kaldırın. İstek gönderilmedi.`, 400);
281
+ }
282
+ const base = config.inputDirectory ? resolve(config.inputDirectory) : process.cwd();
283
+ const code = await readGuardedFile(tool, 'codeFile', resolve(base, codeFile), ['.js', '.py'], maxCodeFileBytes, await allowedRoots(config), config, "512 KB inline sınırını (backend MaxInlineCodeBytes) aşıyor. Paketi küçültün (minify, yinelenen bağımlılık) ya da sourceKind: 'archive' ile zip'i Studio'dan yükleyin.");
284
+ return { args: { ...rest, code: code.buffer.toString('utf8') }, note: `${code.real} → code (${code.buffer.length} B)` };
285
+ }
@@ -193,7 +193,8 @@ export function toolResult(payload, config, expectFailure) {
193
193
  ...(Object.keys(tokenRefs).length > 0 ? { tokenRefs, tokenRefNote: `Maskelenen JWT'ler gateway içinde tutuldu; bu referansları microservice_execute / microservice_debug / page_runtime_get / page_screenshot 'asUser' alanına verebilirsiniz (${tokenTtlMinutes()} dk geçerli; kompleks projede referans access_session_create'ten gelir). Jetonun kendisi yanıta ve günlüğe hiç girmez.` } : {}),
194
194
  ...(truncated ? { truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' } : {}),
195
195
  ...(argumentNotes().aliasesApplied.length > 0 ? { aliasesApplied: argumentNotes().aliasesApplied } : {}),
196
- ...(argumentNotes().bodyFile ? { bodyFile: argumentNotes().bodyFile } : {})
196
+ ...(argumentNotes().bodyFile ? { bodyFile: argumentNotes().bodyFile } : {}),
197
+ ...(argumentNotes().codeFile ? { codeFile: argumentNotes().codeFile } : {})
197
198
  };
198
199
  return {
199
200
  content: [{ type: 'text', text: JSON.stringify(enriched) }],
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { diagnosticsParam, runWithDiagnostics } from './diagnostics.js';
3
3
  import { applyArgumentAliases, closestArgument, runWithArgumentNotes } from './argumentAliases.js';
4
- import { BODY_FILE_ARGUMENTS, bodyFileParam, loadBodyFile } from './bodyFile.js';
4
+ import { BODY_FILE_ARGUMENTS, CODE_FILE_TOOLS, bodyFileParam, codeFileParam, loadBodyFile, loadCodeFile } from './bodyFile.js';
5
5
  import { PlatformApiError } from './errors.js';
6
6
  import { logError } from '../observability/trace.js';
7
7
  /** Doğrulanmış argümanlara takılan çeviri notları (`ArgumentNotes`); JSON'a ve platform isteğine girmez. */
@@ -46,15 +46,17 @@ export function rejectUnknownArguments(server, platform) {
46
46
  const validate = validator.validateToolInput.bind(server);
47
47
  validator.validateToolInput = async (tool, args, name) => {
48
48
  try {
49
- const loaded = await loadBodyFile(name, args, platform.config);
49
+ const body = await loadBodyFile(name, args, platform.config);
50
+ const code = await loadCodeFile(name, body.args, platform.config);
51
+ const loaded = { args: code.args, note: body.note };
50
52
  const requiredBody = requiredBodies.get(name);
51
53
  if (requiredBody && loaded.args?.[requiredBody] === undefined) {
52
54
  throw new PlatformApiError(`${name}: '${requiredBody}' zorunlu — satır içinde ya da bodyFile ile (.json dosyası) verin. İstek gönderilmedi.`, 400);
53
55
  }
54
56
  const { args: aliased, applied } = await applyArgumentAliases(name, loaded.args, { platform, shape: tool.inputSchema?.shape ?? {} });
55
57
  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
+ const notes = { aliasesApplied: applied, ...(loaded.note ? { bodyFile: loaded.note } : {}), ...(code.note ? { codeFile: code.note } : {}) };
59
+ return (applied.length > 0 || loaded.note || code.note) && parsed && typeof parsed === 'object' ? Object.assign(parsed, { [ARGUMENT_NOTES]: notes }) : parsed;
58
60
  }
59
61
  catch (error) {
60
62
  // Doğrulamadan önceki çeviri (dosya, takma ad) `handle`'ın dışında koşar: hata burada günlüğe yazılır.
@@ -81,6 +83,7 @@ export function rejectUnknownArguments(server, platform) {
81
83
  const shape = {
82
84
  ...config.inputSchema,
83
85
  ...(bodyArgument && bodySchema ? { [bodyArgument]: bodySchema.optional(), bodyFile: bodyFileParam(bodyArgument) } : {}),
86
+ ...(CODE_FILE_TOOLS.has(name) ? { codeFile: codeFileParam() } : {}),
84
87
  ...(exposesDiagnostics ? { diagnostics: diagnosticsParam } : {})
85
88
  };
86
89
  const invoke = (args, extra) => {
@@ -279,6 +279,14 @@ Paketler zip'teki `requirements.txt` / `package.json`'a yazılır ve Built-In ya
279
279
  - **Tek parça zip yükleme ve indirme MCP'de yok:** zip'in içeriği dosya düzeyinde okunur ve yazılır
280
280
  (§Zip kaynağı). Zip'in tamamı platform ucunda okunabilir durumdadır
281
281
  (`GET /{proje}/microfunctions/{id}/archive`, `application/zip`) ve Studio'dan indirilir.
282
+ - **Büyük kod dosyadan verilir:** `microfunction_create` / `microfunction_update` `codeFile` ile `.js` / `.py` dosyasını
283
+ okur (inline sınırı **512 KB**, `MaxInlineCodeBytes`) ve metnini `code` olarak gönderir — kod ajan bağlamından geçmez; `code` ile birlikte verilemez. Yazma yanıtı kodu geri
284
+ getirmez (uzunluk + SHA-256 özeti); `microfunction_get` / `_list` 64 KB üstü kodu aynı özete indirir — yerel paketle
285
+ karşılaştırmak için özeti kullanın.
286
+ - **Ortam değişkeni başka kayıttan kopyalanır:** `environmentFrom: <microfunction id>` kaynağın değişkenlerini gateway'de
287
+ kopyalar, değerler yanıtta `***` döner; `environmentVariables` aynı anahtarda kazanır. **Güncellemede değişkenleri
288
+ korumanın yolu budur** (`environmentFrom` = kaydın kendi id'si): PUT gövdesinde `environmentVariables` yoksa platform
289
+ hepsini siler.
282
290
  - `timeoutSeconds` ve `memoryMb` komutta non-nullable int'tir: **göndermezseniz 0 gider ve platform
283
291
  422 döner.** Gateway değer uydurmaz, eksikliği çağrıdan önce söyler (MF-001, block; ürün
284
292
  varsayılanları 30 / 256). Aynı kural provider ↔ `runtimeId` eşleşmesini, inline kaynakta `code`
@@ -124,7 +124,9 @@ servis formunda görünür ve `microservice_get` outline görünümünde geri ok
124
124
  adım adım executionItems ağacı döner. Zamanlanmış (`schedule-job`) uçta sıra farklıdır:
125
125
  `workflow_preview` → `microservice_debug` → `microservice_schedule_control` `trigger` →
126
126
  `microservice_job_history`; geçmişteki `completed` başarı kanıtı DEĞİLDİR (§Zamanlanmış işin
127
- gerçek sonucunu görmek).
127
+ gerçek sonucunu görmek). **`microservice_debug` production'da kapalıdır** (adım izi giriş
128
+ istemeden okunur; platform 404 döner, gateway isteği göndermez): debug development'ta ya da
129
+ dalda yapılır, production'da `microservice_execute` + `microservice_job_history` kalır.
128
130
  Çağrı parametrelerini (sorgu parametresi, filtre değeri) üst düzey `parameters: { ad: değer }`
129
131
  ile ver; gateway GET'te query-string'e, POST/PUT'ta gövdeye koyar. Değeri NULL bağlanan query
130
132
  parametresini debug yanıtı `unboundParameters` ile söyler — ağaçta o satırın `value` anahtarı
@@ -459,6 +459,7 @@ değişiminde koşar. Bağ: `page_update` `events: [{ name: 'focus', value: <flo
459
459
  Sunucudan gönderilen bildirim yerel `notification-schedule`'dan ayrıdır. Kanal ikiliye bağlıdır (NTF-004):
460
460
  `expo` = oynatıcı (**dev build** gerekir, Expo Go almaz), `apns` / `fcm` = mağaza sürümü iOS / Android.
461
461
 
462
+ - **Olaya bağlı, kişiye özel bildirim workflow'un `push` adımıdır** (`platform_guide('workflow')` §Push adımı); giriş yapan kullanıcının telefonu ona bağlanır.
462
463
  - **Kayıt kendiliğindendir.** Uygulama açılınca cihazını anonim kaydeder (kanal, dil, platform, izin, token);
463
464
  sayfaya adım eklenmez. Mağaza sürümü yalnız production'a kaydolur.
464
465
  - **Mağaza sürümünün push kimlikleri insanın işidir** (Studio › Yayınla; ajan yükleyemez). Durum:
@@ -213,6 +213,7 @@ olan ilk adıma** odaklanmaktır; hata yoksa son adıma. Aynı okumayı sen de y
213
213
  | Middleware ve `user` | koşmaz | koşar | **koşmaz** — `user` yok |
214
214
  | `email`, `http`, `code` içi HTTP | **gerçekten koşar** (SMTP gönderilir, dış uç çağrılır) | gerçek | gerçek |
215
215
  | `microfunction` | `preview: true` bayrağıyla çağrılır | gerçek | gerçek |
216
+ | `push` | **göndermez** — yayın commit'e bağlı, rollback onu düşürür; çıktı `deviceCount` verir, `notificationId` geri alınan kaydındır (aranmaz) | gerçek | gerçek |
216
217
  | `table` / `file` yazmaları | geri alınır | yukarıdaki commit/rollback | yukarıdaki commit/rollback |
217
218
  | Adım izi | **tam** (bu bölüm) | yok — yalnız yanıt gövdesi; ağaç için `microservice_debug` | yok — geçmiş satırı `status + süre + errorMessage` (`Step 'x' failed: <sebep>` ya da `The job's workflow returned 500 (<code>): <mesaj>`) |
218
219
 
@@ -403,6 +404,28 @@ dalına** gider; `BalanceInsufficientException` kodu `errorCode` olarak okunur.
403
404
  istemci tarafı `Entitlements.<hak>` yalnız ekranı gizler, sunucu denetimi bunun yerine geçmez.
404
405
  - Önizleme (`workflow_preview`) işlemi geri alır: `grant`/`spend` gerçekten koşar ve kalıcı satır bırakmaz.
405
406
 
407
+ ## Push adımı: kullanıcılara uzak bildirim (kök ADR 0032, `StepType.Push`)
408
+
409
+ Workflow ya da otomasyon, olay anında BELİRLİ uygulama kullanıcılarına (ya da kurulumlara) push gönderir. Girdi tek
410
+ `rule.expression` nesne literalidir:
411
+ `({ application?, to: { users?, installations? }, title, body, defaultLanguage?, path?, query?, badge? })`.
412
+
413
+ - `application` (slug) ya da `applicationId`; ikisi de boşsa workflow'un kendi uygulaması (mobil değilse 404, hata dalı).
414
+ - `to.users` uygulama kullanıcı kimlikleri, `to.installations` kurulum kimlikleri; ikisi **birleşim** (VEYA) olarak
415
+ hedeflenir, toplam 1–500. Daha büyük kitle için `loop`; "herkese" gönderim adımda yok, Studio › Bildirimler'de.
416
+ - `title` / `body` metin ya da `{ dil: metin }`; varsayılan dilde (`defaultLanguage`, boşsa projenin) başlık zorunlu,
417
+ ≤ 200 / ≤ 2000. Her telefon kendi dilini alır, yoksa varsayılanı.
418
+ - `path` dokununca açılacak sayfa; `query` nesne (`{ id: 42 }` → `id=42`) ya da metin.
419
+ - Çıktı `{ notificationId, deviceCount }`. **Telefonu olmayan kullanıcı hata değildir:** `deviceCount: 0`,
420
+ `notificationId: null`, başarı dalı. Biçim hatası 400 (parametre adı iletide: `To`, `Title`…) → hata dalı.
421
+ - **Kullanıcının telefonu nasıl bilinir:** kullanıcı uygulamada giriş yapınca telefon ona bağlanır, çıkışta (aynı
422
+ oturumun) bağı kalkar. Yalnız `Complex` erişim modelli projede; `simple` projede yalnız `installations` işe yarar.
423
+ Bağı `notification_device_list` (arama: e-posta) gösterir.
424
+ - **Önizleme göndermez** (yukarıdaki tablo; `email`'den farkı). Canlı uç, otomasyon ve zamanlanmış iş gönderir.
425
+ - **WF-036:** `push` taşıyan workflow'u (alt workflow'lar dahil) AI production'da `microservice_execute` ya da
426
+ `automation_trigger` ile çalıştıramaz — istek gitmeden reddedilir. Development'ta deneyin.
427
+ - `to.users` GUID olmalı; uygulama `applicationId` ile verilirse o da mobil olmalı (değilse 404, hata dalı).
428
+
406
429
  ## Step tipi × workflow tipi izin matrisi
407
430
 
408
431
  | Step | workflow | automation | middleware |
@@ -414,6 +437,7 @@ dalına** gider; `BalanceInsufficientException` kodu `errorCode` olarak okunur.
414
437
  | revert (Fail and Rollback) | – | ✓ | – |
415
438
  | process (BPM süreci) | ✓ | ✓ | – |
416
439
  | balance (uygulama bakiyesi / hak denetimi) | ✓ | ✓ | ✓ |
440
+ | push (kullanıcılara uzak bildirim) | ✓ | ✓ | – |
417
441
 
418
442
  > **`process` adımı süreci başlatır ya da ona sinyal/vekalet yazar, BEKLEMEZ (2026-09-18).**
419
443
  > Kural alanları `processSlug`, `processOperation: start | signal | delegate` (boş = `start`) ve
@@ -1,6 +1,6 @@
1
1
  {
2
- "hash": "34361b013283a252757338ed07d8cdcd70294df1c334d35723a9b194c62d1c4b",
3
- "generatedAt": "2026-11-03T11:41:34.837Z",
2
+ "hash": "68157be9624742c7086f9e40a9c8fb5565d612d82bf0a7d7d1ae063044ac7465",
3
+ "generatedAt": "2026-11-03T19:09:26.033Z",
4
4
  "types": 377,
5
5
  "files": 196,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
@@ -9,4 +9,4 @@
9
9
  "System.Text.Json.Nodes.JsonNode",
10
10
  "System.Text.Json.Nodes.JsonNodeOptions"
11
11
  ]
12
- }
12
+ }
@@ -234,7 +234,8 @@
234
234
  "set-user",
235
235
  "microfunction",
236
236
  "process",
237
- "balance"
237
+ "balance",
238
+ "push"
238
239
  ],
239
240
  "type": "string",
240
241
  "format": ""
@@ -172,7 +172,8 @@
172
172
  "set-user",
173
173
  "microfunction",
174
174
  "process",
175
- "balance"
175
+ "balance",
176
+ "push"
176
177
  ],
177
178
  "type": "string",
178
179
  "format": ""
@@ -77,7 +77,8 @@
77
77
  "set-user",
78
78
  "microfunction",
79
79
  "process",
80
- "balance"
80
+ "balance",
81
+ "push"
81
82
  ],
82
83
  "type": "string",
83
84
  "format": ""
@@ -234,7 +234,8 @@
234
234
  "set-user",
235
235
  "microfunction",
236
236
  "process",
237
- "balance"
237
+ "balance",
238
+ "push"
238
239
  ],
239
240
  "type": "string",
240
241
  "format": ""
@@ -7,6 +7,7 @@ import { languageHeader } from '../knowledge/languageCodes.js';
7
7
  import { handle } from '../platform/responses.js';
8
8
  import { z } from 'zod';
9
9
  import { constraintSummary } from '../knowledge/backendConstraints.js';
10
+ import { assertNoPushInProduction } from './workflows.js';
10
11
  const policyOverrideParam = z.boolean().optional()
11
12
  .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
12
13
  export function registerAutomationTools(server, platform) {
@@ -106,6 +107,15 @@ export function registerAutomationTools(server, platform) {
106
107
  }, handle(config, async ({ project, environment, entityId, id, operation, recordIds }) => {
107
108
  const resolver = new RefResolver(platform, project, environment);
108
109
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
110
+ let automationApplicationId = '';
111
+ await assertNoPushInProduction('automation_trigger', environment, config, () => platform.tenant('GET', project, `/entities/${resolvedEntityId}/automation/${id}`, { environment }).then(payload => {
112
+ const record = payload;
113
+ const body = ((record?.data ?? record) ?? {});
114
+ automationApplicationId = String(body.applicationId ?? '');
115
+ return payload;
116
+ }), workflowId => automationApplicationId === ''
117
+ ? Promise.reject(new Error('automation applicationId taşımıyor'))
118
+ : platform.tenant('GET', project, `/workflows/${automationApplicationId}/${workflowId}`, { environment }));
109
119
  return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/automation/${id}/trigger`, {
110
120
  environment,
111
121
  body: { operation, recordIds: recordIds ?? [] }
@@ -4,7 +4,9 @@ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.j
4
4
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
5
5
  import { policyRules, microfunctionArchiveEntrypointFindings } from '../knowledge/rules.js';
6
6
  import { handle } from '../platform/responses.js';
7
+ import { PlatformApiError } from '../platform/errors.js';
7
8
  import { z } from 'zod';
9
+ import { createHash } from 'node:crypto';
8
10
  const policyOverrideParam = z.boolean().optional()
9
11
  .describe("true: politika block bulgularını warn'a indirir (bilinçli istisna; yanıtta işaretlenir)");
10
12
  const behaviorParam = z.enum(['primitive', 'object', 'array']);
@@ -29,6 +31,7 @@ const definitionInput = {
29
31
  timeoutSeconds: z.number().int().min(1).max(900).optional().describe("Fonksiyon bütçesi; varsayılan 30. Built-In üst sınırı 300, Lambda 900. Bütçe dolarsa çağrı MicrofunctionInvocationTimeoutException ile step'in fail dalına düşer"),
30
32
  memoryMb: z.number().int().min(128).max(3008).optional().describe("Varsayılan 256. Built-In'de yalnız kaydedilir (küme environment limiti geçerlidir); Lambda'da gerçekten uygulanır"),
31
33
  environmentVariables: environmentVariablesParam.optional(),
34
+ environmentFrom: guid.optional().describe("Ortam değişkenlerini bu microfunction'dan kopyalar: gateway kaynağı okur, değerler ajan bağlamından geçmez ve yanıtta '***' döner. environmentVariables ile birlikte verilirse aynı anahtarda environmentVariables kazanır. GÜNCELLEMEDE değişkenleri korumak için kaydın kendi id'sini verin: PUT gövdesinde environmentVariables yoksa platform hepsini siler (Microfunction.cs, environmentVariables ?? [])"),
32
35
  dependencies: z.string().optional().describe("Inline kaynağın dış paketleri, SATIR BAŞINA BİR PAKET (≤ 16 KB; boş ve '#' satırları yorum). python: requirements.txt satırı olduğu gibi — 'pillow==11.1.0', 'requests>=2.32'; pip seçeneği ('-r', '--index-url', '-e') YASAK. node: 'ad@aralık' — 'sharp@^0.33.0', '@scope/ad@1.2', aralıksız 'dayjs' → latest. Built-In'de yayın paketleri Fission builder'da kurar: yayın build süresince (5 dk'ya kadar) 'publishing' kalır, kurulum düşerse publish-failed ve publishError'da build günlüğünün son 4 KB'ı. aws-lambda'da KURULMAZ (MF-007) — paketler zip'te gelmeli. archive kaynağında yok sayılır (manifest zip'te). Built-In Python 3.13, Node 22 (Lambda'daki sürümlerle aynı); C uzantılı paketler (Pillow gibi) hazır wheel'den kurulur"),
33
36
  testArguments: z.string().optional().describe("Studio Test call'unun kayıtlı argüman metni (≤ 64 KB, MicrofunctionTestArgumentsTooLarge); olduğu gibi saklanır, JSON geçerliliği DENETLENMEZ ve koşuma girmez. update TAM GÖVDEDİR: verilmezse kayıtlı metin silinir — önce microfunction_get'ten alın. Kaydetmek revision'ı artırır (yayındaki kayıt hasUnpublishedChanges: true olur)"),
34
37
  publish: z.boolean().optional().describe("true: kayıt açıldıktan/güncellendikten sonra aynı çağrıda yayın istenir ('Kaydet ve yayınla'). Yayın ASENKRONDUR — çağrı status: 'publishing' döner; sonucu microfunction_get ile izleyin (published | publish-failed + publishError)")
@@ -48,6 +51,51 @@ const runtimeInput = {
48
51
  secretAccessKey: z.string().min(1).describe("AWS secret access key. Kayıt düz metin tutulur; platform DTO'da maskeler (**** + son 4), gateway bu tool'ların yanıtında değeri tümüyle '***' yapar"),
49
52
  roleArn: z.string().optional().describe("Lambda execution rolü: arn:aws:iam::<12 hane>:role/<ad>. Rolü müşteri oluşturur; fonksiyon bu rolle koşar")
50
53
  };
54
+ // 2026-10-30, QM SAML girişi (ADR 0027): kayıtlı bir anahtarı (OIDC_SECRET_KEY) ikinci bir microfunction'a taşımak değeri çağrıya
55
+ // düz yazmayı gerektiriyordu; güncellemede de PUT gövdesi değişkensizse platform hepsini siliyor. Kopya gateway'de yapılır ve
56
+ // yanıttaki değerler maskelenir — değer ne çağrıya ne yanıta girer.
57
+ async function withEnvironmentFrom(platform, project, environment, body, sourceId) {
58
+ if (!sourceId)
59
+ return body;
60
+ let source;
61
+ try {
62
+ source = await platform.tenant('GET', project, `/microfunctions/${sourceId}`, { environment });
63
+ }
64
+ catch (error) {
65
+ throw new PlatformApiError(`environmentFrom (${sourceId}) okunamadı: ${error instanceof Error ? error.message : String(error)}. Kaynak kayıt bu projede ve bu ortamda olmalı (microfunction_list). İstek gönderilmedi.`, 400);
66
+ }
67
+ const record = source?.data ?? source;
68
+ const copied = record?.environmentVariables;
69
+ const variables = copied && typeof copied === 'object' && !Array.isArray(copied) ? copied : {};
70
+ return { ...body, environmentVariables: { ...variables, ...body.environmentVariables } };
71
+ }
72
+ // 2026-10-30 canlı koşu: platform yazılan kaydı `code` ile birlikte döndürüyor; codeFile ile gönderilen 433 KB'lık saml-dogrula
73
+ // yanıtta 283 bin karakter olarak geri geldi. Yazmada kod hiç yansıtılmaz (çağıran onu gönderdi); okumada 64 KB üstü kod özetlenir.
74
+ const codeSummaryBytes = 64 * 1024;
75
+ function summarizeCode(value, always) {
76
+ if (!value || typeof value !== 'object')
77
+ return value;
78
+ if (Array.isArray(value))
79
+ return value.map(item => summarizeCode(item, always));
80
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
81
+ if (key !== 'code' || typeof entry !== 'string')
82
+ return [key, summarizeCode(entry, always)];
83
+ const bytes = Buffer.byteLength(entry, 'utf8');
84
+ if (!always && bytes <= codeSummaryBytes)
85
+ return [key, entry];
86
+ return [key, `[kod yanıtta gösterilmez: ${bytes} bayt, sha256 ${createHash('sha256').update(entry, 'utf8').digest('hex')}]`];
87
+ }));
88
+ }
89
+ function maskEnvironmentValues(value, active) {
90
+ if (!active || !value || typeof value !== 'object')
91
+ return value;
92
+ if (Array.isArray(value))
93
+ return value.map(item => maskEnvironmentValues(item, active));
94
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key,
95
+ key === 'environmentVariables' && entry && typeof entry === 'object' && !Array.isArray(entry)
96
+ ? Object.fromEntries(Object.keys(entry).map(name => [name, '***']))
97
+ : maskEnvironmentValues(entry, active)]));
98
+ }
51
99
  export function registerMicrofunctionTools(server, platform) {
52
100
  const config = platform.config;
53
101
  server.registerTool('microfunction_list', {
@@ -61,35 +109,37 @@ export function registerMicrofunctionTools(server, platform) {
61
109
  ...pagingParams
62
110
  },
63
111
  annotations: readOnly
64
- }, handle(config, ({ project, environment, status, searchTerm, pageIndex, pageLength, order, isDescending }) => platform.tenant('GET', project, '/microfunctions', {
112
+ }, handle(config, async ({ project, environment, status, searchTerm, pageIndex, pageLength, order, isDescending }) => summarizeCode(await platform.tenant('GET', project, '/microfunctions', {
65
113
  environment,
66
114
  query: { status, searchTerm, pageIndex: pageIndex ?? 0, pageLength: pageLength ?? 200, order, isDescending }
67
- })));
115
+ }), false)));
68
116
  server.registerTool('microfunction_get', {
69
117
  title: 'Microfunction getir',
70
- description: "Tek bir microfunction'ın tanımını ve yayın durumunu getirir: status, revision/publishedRevision, hasUnpublishedChanges (kayıt yayında ama üstüne yazılmış), publishError, parameterScheme/returnScheme. Yayın asenkron olduğu için 'publishing' durumunda birkaç saniye arayla tekrar çağırın.",
118
+ description: "Tek bir microfunction'ın tanımını ve yayın durumunu getirir: status, revision/publishedRevision, hasUnpublishedChanges (kayıt yayında ama üstüne yazılmış), publishError, parameterScheme/returnScheme. Yayın asenkron olduğu için 'publishing' durumunda birkaç saniye arayla tekrar çağırın. 64 KB üstü kod yanıtta uzunluk ve SHA-256 özetine indirilir; yerel paketle karşılaştırmak için özeti kullanın.",
71
119
  inputSchema: { project: projectParam, environment: environmentParam, id: guid },
72
120
  annotations: readOnly
73
- }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microfunctions/${id}`, { environment })));
121
+ }, handle(config, async ({ project, environment, id }) => summarizeCode(await platform.tenant('GET', project, `/microfunctions/${id}`, { environment }), false)));
74
122
  server.registerTool('microfunction_create', {
75
123
  title: 'Microfunction tanımla',
76
124
  description: "Yeni microfunction kaydeder. Kayıt DRAFT doğar; yayına almadan hiçbir workflow step'i onu koşturamaz (publish: true ile aynı çağrıda yayın isteyebilirsiniz). Yayın adımı kodu runtime'a kurar ve bir 'probe' çağrısıyla duman testi yapar: sözdizimi/import hatası yayın anında publish-failed olur, koşumda değil. Protokol ve sınırlar: platform_guide('microfunction'). " + constraintSummary('CreateMicrofunctionCommand'),
77
125
  inputSchema: { project: projectParam, environment: environmentParam, ...definitionInput, policyOverride: policyOverrideParam },
78
126
  annotations: writes
79
- }, handle(config, async ({ project, environment, policyOverride, ...body }) => {
127
+ }, handle(config, async ({ project, environment, policyOverride, environmentFrom, ...input }) => {
128
+ const body = await withEnvironmentFrom(platform, project, environment, input, environmentFrom);
80
129
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunction', body, { operation: 'create' }, policyOverride));
81
130
  const drift = unmappedMembers('CreateMicrofunctionCommand', body);
82
- return attachFindings(withUnmappedMembers(await platform.tenant('POST', project, '/microfunctions', { environment, body }), drift), findings);
131
+ return summarizeCode(maskEnvironmentValues(attachFindings(withUnmappedMembers(await platform.tenant('POST', project, '/microfunctions', { environment, body }), drift), findings), environmentFrom !== undefined), true);
83
132
  }));
84
133
  server.registerTool('microfunction_update', {
85
134
  title: 'Microfunction güncelle',
86
135
  description: "Microfunction'ın TAMAMINI değiştirir (PUT — değiştirmediğiniz alanları da gönderin; önce microfunction_get). Her güncelleme revision'ı artırır: yayındaki kayıt güncellenirse runtime ESKİ sürümü koşmaya devam eder ve kayıt hasUnpublishedChanges: true olur — yeni kodu canlıya almak için publish: true verin ya da microfunction_publish çağırın. PROVIDER YAYINDAYKEN DEĞİŞMEZ: bir kez yayınlanmış kaydın provider'ını değiştiren PUT 400 döner (MicrofunctionProviderChangeNotAllowedException) — kaydı silip yeniden oluşturun. " + constraintSummary('UpdateMicrofunctionCommand'),
87
136
  inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...definitionInput, policyOverride: policyOverrideParam },
88
137
  annotations: updates
89
- }, handle(config, async ({ project, environment, id, policyOverride, ...body }) => {
138
+ }, handle(config, async ({ project, environment, id, policyOverride, environmentFrom, ...input }) => {
139
+ const body = await withEnvironmentFrom(platform, project, environment, input, environmentFrom);
90
140
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunction', body, { operation: 'update' }, policyOverride));
91
141
  const drift = unmappedMembers('UpdateMicrofunctionCommand', body);
92
- return attachFindings(withUnmappedMembers(await platform.tenant('PUT', project, `/microfunctions/${id}`, { environment, body }), drift), findings);
142
+ return summarizeCode(maskEnvironmentValues(attachFindings(withUnmappedMembers(await platform.tenant('PUT', project, `/microfunctions/${id}`, { environment, body }), drift), findings), environmentFrom !== undefined), true);
93
143
  }));
94
144
  server.registerTool('microfunction_publish', {
95
145
  title: 'Microfunction yayınla',
@@ -16,6 +16,7 @@ import { PlatformApiError } from '../platform/errors.js';
16
16
  import { existingMatch, ifExistsParam, listAll, platformRename, withOutcome, withRename } from '../platform/ifExists.js';
17
17
  import { outlineWrite, recordWriteViewParam } from '../platform/writeView.js';
18
18
  import { handle } from '../platform/responses.js';
19
+ import { assertNoPushInProduction } from './workflows.js';
19
20
  import { headersForMode } from '../platform/http.js';
20
21
  import fastJsonPatch from 'fast-json-patch';
21
22
  import { jsonArray } from '../platform/jsonArray.js';
@@ -113,6 +114,19 @@ function hasAuthorization(headers) {
113
114
  * ucun ÖNÜNDE koşar ve ağaçta hiç görünmez — 09-04 koşusunda sessizce açık kalan şirket kapısı
114
115
  * (Y-03) bu yüzden adım ağacından teşhis edilemedi. Gateway zinciri kendisi çözüp listeler.
115
116
  */
117
+ /** Ucun workflow tanımı (WF-036 kapısı için); workflow kaynaklı değilse adımsız bir gövde. */
118
+ async function microserviceWorkflow(platform, project, environment, application, microservice) {
119
+ const applicationId = await new RefResolver(platform, project, environment).applicationId(application);
120
+ const list = await platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query: { pageIndex: 0, pageLength: 200 } });
121
+ const record = (list?.data ?? []).find(entry => String(entry.slug ?? '') === microservice);
122
+ if (!record)
123
+ throw new Error(`'${microservice}' ucu bulunamadı`);
124
+ const detail = await platform.tenant('GET', project, `/microservices/${applicationId}/${String(record.id ?? '')}`, { environment });
125
+ const definition = (detail?.data ?? detail ?? {});
126
+ if (typeof definition.workflowId !== 'string' || definition.workflowId === '')
127
+ return { steps: [] };
128
+ return platform.tenant('GET', project, `/workflows/${applicationId}/${definition.workflowId}`, { environment });
129
+ }
116
130
  async function middlewareChain(platform, project, environment, application, microservice, httpStatus) {
117
131
  try {
118
132
  const resolver = new RefResolver(platform, project, environment);
@@ -467,6 +481,7 @@ export function registerMicroserviceTools(server, platform) {
467
481
  },
468
482
  annotations: writes
469
483
  }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, parameters, headers, anonymous, asUser, asUserId, cookies, language, includeHeaders }) => {
484
+ await assertNoPushInProduction('microservice_execute', environment, config, () => microserviceWorkflow(platform, project, environment, application, microservice), async (workflowId) => platform.tenant('GET', project, `/workflows/${await new RefResolver(platform, project, environment).applicationId(application)}/${workflowId}`, { environment }));
470
485
  const notes = [];
471
486
  const eye = await impersonationHeaders(platform, 'microservice_execute', project, environment, { asUserId, asUser, anonymous, ownAuthorization: hasAuthorization(headers) }, { runtime: true });
472
487
  const member = anonymous === true ? asUser : await resolveIdentity(asUser, platform, project, environment);
@@ -478,10 +493,17 @@ export function registerMicroserviceTools(server, platform) {
478
493
  }));
479
494
  server.registerTool('microservice_debug', {
480
495
  title: 'Microservice debug',
481
- description: "Microservice'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı). executionItems YALNIZ ucun kendi adımlarını kapsar — ucun ÖNÜNDE koşan middleware'ler ayrıca 'middlewares' alanında adım özetiyle listelenir (09-04 Y-04). Değeri NULL bağlanan query parametreleri 'unboundParameters' alanında adıyla listelenir (ağaçta bu satırların value anahtarı görünmez). Preview semantiğiyle koşar: tek transaction + sonunda rollback, kalıcı satır yazmaz. httpStatus akışın kendi sonucudur (4xx/5xx'te runtimeErrors eklenir); ağaç düğümlerinde hata bayrağı YOKTUR — patlayan adımın output'u boştur, hata gövdesi hata dalındaki response adımının çıktısındadır. Zamanlanmış (schedule-job) uçlar da slug yoluyla debug edilir (method: 'POST', parametresiz): job geçmişi hatayı yalnız mesaj olarak yazar, adım adım girdi/çıktı burada görülür (platform_guide('microservice') §Zamanlanmış işin gerçek sonucunu görmek). Kompleks projede kullanıcı gözü asUserId (parolasız) ya da asUser ile verilir ve yanıta 'accessDecision' eklenir: { decision: allow | deny | not-enforced | unknown, mode, code?, permission?, reason, next } — 401/403'ün erişim kararından mı geldiğini ve sıradaki adımı söyler.",
496
+ description: "Microservice'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı). PRODUCTION'DA KAPALIDIR: istek gönderilmez, development'ta ya da dalda debug edin. executionItems YALNIZ ucun kendi adımlarını kapsar — ucun ÖNÜNDE koşan middleware'ler ayrıca 'middlewares' alanında adım özetiyle listelenir (09-04 Y-04). Değeri NULL bağlanan query parametreleri 'unboundParameters' alanında adıyla listelenir (ağaçta bu satırların value anahtarı görünmez). Preview semantiğiyle koşar: tek transaction + sonunda rollback, kalıcı satır yazmaz. httpStatus akışın kendi sonucudur (4xx/5xx'te runtimeErrors eklenir); ağaç düğümlerinde hata bayrağı YOKTUR — patlayan adımın output'u boştur, hata gövdesi hata dalındaki response adımının çıktısındadır. Zamanlanmış (schedule-job) uçlar da slug yoluyla debug edilir (method: 'POST', parametresiz): job geçmişi hatayı yalnız mesaj olarak yazar, adım adım girdi/çıktı burada görülür (platform_guide('microservice') §Zamanlanmış işin gerçek sonucunu görmek). Kompleks projede kullanıcı gözü asUserId (parolasız) ya da asUser ile verilir ve yanıta 'accessDecision' eklenir: { decision: allow | deny | not-enforced | unknown, mode, code?, permission?, reason, next } — 401/403'ün erişim kararından mı geldiğini ve sıradaki adımı söyler.",
482
497
  inputSchema: executeInput,
483
498
  annotations: writes
484
499
  }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, parameters, headers, anonymous, asUser, asUserId, cookies, language }) => {
500
+ // Debug rotası [AllowAnonymous]'tır ve adım izini (girdi/çıktı, okunan sırlar dahil) döner;
501
+ // backend production'da 404 verir (DebugMicroserviceCommand, 2026-10-28). Gateway isteği
502
+ // göndermeden durur: o backend dağıtılmadan önce de iz production'dan okunmaz ve ara katmanlar
503
+ // ile dış çağrılar (http adımı, AD bind) koşmaz.
504
+ if (platform.resolveEnvironment(environment) === 'production') {
505
+ throw new PlatformApiError("microservice_debug: debug production'da kapalı — adım izi giriş istemeden okunabildiği için platform bu rotayı production'da reddeder; istek gönderilmedi. Akışı development'ta ya da bir dalda debug edin. Production'da sonucu microservice_execute ile, zamanlanmış işin hatasını microservice_job_history ile görün.", 400);
506
+ }
485
507
  const notes = [];
486
508
  const eye = await impersonationHeaders(platform, 'microservice_debug', project, environment, { asUserId, asUser, anonymous, ownAuthorization: hasAuthorization(headers) }, { runtime: true });
487
509
  const member = anonymous === true ? asUser : await resolveIdentity(asUser, platform, project, environment);
@@ -103,7 +103,7 @@ export function registerPlatformTools(server, platform) {
103
103
  'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
104
104
  ...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
105
105
  "Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
106
- "Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). Uzun kod alanı ayrı dosyada durabilir: gövdede { \"$file\": \"./adim.js\" } o dosyanın METNİYLE değişir (çalıştırılmaz; yol bodyFile'ın klasörüne göre, GW-013). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
106
+ "Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch); microfunction kodu codeFile ile (.js/.py). Uzun kod alanı ayrı dosyada durabilir: gövdede { \"$file\": \"./adim.js\" } o dosyanın METNİYLE değişir (çalıştırılmaz; yol bodyFile'ın klasörüne göre, GW-013). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
107
107
  "Örnek gövdeler ve kural katalogları (ngapps://examples/<ad>, ngapps://rules/<ad>) istemci MCP resource okuyamıyorsa da bu araçla okunur: platform_guide({ resource: 'ngapps://rules/connect-data' }) — guide verilmez; dizin için 'ngapps://examples/index'."
108
108
  ].join('\n');
109
109
  server.registerTool('platform_whoami', {
@@ -4,7 +4,8 @@ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/poli
4
4
  import { workflowOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
5
5
  import { patchResult, baseVersionOf } from '../platform/patch.js';
6
6
  import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
7
- import { policyRules, microfunctionStepFindings, workflowStepFindings, undefinedInputFieldFindings } from '../knowledge/rules.js';
7
+ import { policyRules, microfunctionStepFindings, workflowStepFindings, undefinedInputFieldFindings, pushReachable } from '../knowledge/rules.js';
8
+ import { PolicyBlockError } from '../knowledge/policy.js';
8
9
  import { identityMiddlewareFindings } from '../knowledge/access.js';
9
10
  import { languageHeader } from '../knowledge/languageCodes.js';
10
11
  import { handle } from '../platform/responses.js';
@@ -73,6 +74,32 @@ async function enrichMiddlewareVariables(platform, project, environment, applica
73
74
  };
74
75
  });
75
76
  }
77
+ /**
78
+ * WF-036 (kök ADR 0032): `push` adımı taşıyan bir akışı AI production'da çalıştırmaz ve tetiklemez —
79
+ * bildirim kullanıcının telefonuna gider, geri alınamaz. Tanım YALNIZ production'da okunur ve `workflow`
80
+ * adımlarının çağırdığı alt akışlar da izlenir (`pushReachable`); biri okunamazsa kapı kapalı kalır.
81
+ * Önizleme kapıya girmez: push önizlemede göndermez (commit'e bağlı yayın).
82
+ */
83
+ export async function assertNoPushInProduction(tool, environment, config, load, loadWorkflow) {
84
+ if ((environment ?? config.defaultEnvironment) !== 'production')
85
+ return;
86
+ const bodyOf = (payload) => {
87
+ const record = payload;
88
+ return ((record?.data ?? record) ?? null);
89
+ };
90
+ const definition = await load().then(bodyOf, () => null);
91
+ const sends = definition === null
92
+ ? null
93
+ : await pushReachable(definition, workflowId => loadWorkflow(workflowId).then(bodyOf)).catch(() => null);
94
+ if (sends === false)
95
+ return;
96
+ throw new PolicyBlockError([{
97
+ ruleId: 'WF-036',
98
+ severity: 'block',
99
+ message: `${tool}: push adımı taşıyan akış (alt workflow'lar dahil) production'da AI tarafından çalıştırılmaz — bildirim kullanıcıların telefonuna gider ve geri alınamaz.${sends === null ? ' Tanım ya da bir alt workflow okunamadı; kapı kapalı kalır.' : ''} Development'ta deneyin; production'da çalıştırmak insanın işidir.`,
100
+ guide: 'ngapps://guides/workflow'
101
+ }], false);
102
+ }
76
103
  export function registerWorkflowTools(server, platform) {
77
104
  const config = platform.config;
78
105
  server.registerTool('workflow_list', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.58",
3
+ "version": "0.0.60",
4
4
  "type": "module",
5
5
  "description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
6
6
  "license": "UNLICENSED",