@appaflytech/zeyrek-ai-mcp 0.0.11 → 0.0.12

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.
@@ -1,5 +1,25 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  const ERROR_STEP_NAME = 'gatewayOnError';
3
+ const ERROR_STEP_NAME_PATTERN = /^gatewayOnError\d*$/;
4
+ /**
5
+ * Global-error dalında `error` = `WorkflowError.ToObject()`: `{ code, message, detail, type,
6
+ * step: { id, name, type, data, input } }` (`ActivityContext.EnterGlobalErrorHandling`). Gövde
7
+ * `step`'i ALT KÜME olarak yazar, çünkü (1) `ResponseStep` global-error kipinde gövdedeki `step`
8
+ * `error.step` ile birebir aynıysa onu siler, (2) `step.input` adımın ifade değerlerini (parola
9
+ * dahil) taşır. Zincir önce `error.cause`'tan okunur: backend 2026-09-17'den beri her seviyeyi
10
+ * `{ code, message, detail, type, step: { name, type }, cause }` olarak orada verir — loop iterasyonu
11
+ * (`<loop>[<sıra>]`) ve istisnasız 2xx dışı dönen çocuk dahil (`WorkflowError.ToObject`). Eski backend'de
12
+ * `error.cause` yoktur; o zaman yalnız patlayan `workflow` adımının `step.data`'sı okunur: fırlatan
13
+ * çocukta `WorkflowError.ToResponseObject()`, 2xx dışı dönen çocukta çocuğun KENDİ yanıtı. İkincisi
14
+ * keyfî olabilir (elle yazılmış `({ err: error })` input taşır), bu yüzden `pick` her iki kaynağı seviye
15
+ * seviye beyaz listeye süzer: `code/message/detail/type/step{name,type}/cause`. Hiçbiri yoksa `cause`
16
+ * yazılmaz. Loop adımının `data`'sı tamamlanan öğelerdir — okunmaz. Canlı ölçüm 2026-09-16 (lokal `emrah/web`, üç seviyeli
17
+ * zincir + `/m/`). ES5: motor Jint 4 ama desteklenen yüzey belgesiz (RFQ W-4); null alan yazılmaz.
18
+ */
19
+ export const GATEWAY_ERROR_BODY_EXPRESSION = "(function () { function pick(source, depth) { if (!source || typeof source !== 'object') return null; var picked = {}; if (source.code) picked.code = source.code; if (source.message) picked.message = source.message; if (source.detail) picked.detail = source.detail; if (source.type) picked.type = source.type; if (source.step && source.step.name) { picked.step = { name: source.step.name }; if (source.step.type) picked.step.type = source.step.type; } if (depth < 8 && source.cause) { var inner = pick(source.cause, depth + 1); if (inner) picked.cause = inner; } return picked.code || picked.message || picked.step || picked.cause ? picked : null; } var body = { code: error.code, message: error.message }; if (error.detail) body.detail = error.detail; if (error.type) body.type = error.type; if (error.step) body.step = { name: error.step.name, type: error.step.type }; var cause = pick(error.cause, 0) || (error.step && error.step.type === 'workflow' ? pick(error.step.data, 0) : null); if (cause) body.cause = cause; return body; })()";
20
+ export const LEGACY_GATEWAY_ERROR_BODY_EXPRESSION = '({ code: error.code, message: error.message })';
21
+ const ERROR_STEP_DESCRIPTION = "Gateway'in eklediği hata yolu: akışın herhangi bir adımı patlarsa buraya düşer ve 500 ile { code, message, detail, type, step: { name, type } } döner; patlayan adım bir alt akışsa çocuğun hata gövdesi cause içinde iç içe gelir. Kendi hata gövdeni istiyorsan bu adımın bodyExpression'ını değiştir.";
22
+ const LEGACY_ERROR_STEP_DESCRIPTION = "Gateway'in eklediği hata yolu: akışın herhangi bir adımı patlarsa buraya düşer ve 500 ile { code, message } döner. Kendi hata gövdeni istiyorsan bu adımın bodyExpression'ını değiştir.";
3
23
  const INJECTED_KINDS = new Set(['workflow', 'middleware']);
4
24
  function asSteps(value) {
5
25
  return (Array.isArray(value) ? value : []).filter((step) => Boolean(step) && typeof step === 'object');
@@ -35,8 +55,9 @@ export function ensureWorkflowErrorPath(definition, kind) {
35
55
  if (steps.some(step => step !== startStep && (Array.isArray(step.branches) ? step.branches : []).some(branch => branch?.type === 'global-error')))
36
56
  return [];
37
57
  const branches = Array.isArray(startStep.branches) ? startStep.branches : [];
38
- if (branches.some(branch => branch?.type === 'global-error' && branch.nextStepId))
39
- return [];
58
+ const wired = branches.find(branch => branch?.type === 'global-error' && branch.nextStepId);
59
+ if (wired)
60
+ return upgradeLegacyErrorBody(steps, String(wired.nextStepId));
40
61
  const name = availableName(steps);
41
62
  const errorStepId = randomUUID();
42
63
  const unwired = branches.find(branch => branch?.type === 'global-error');
@@ -44,12 +65,12 @@ export function ensureWorkflowErrorPath(definition, kind) {
44
65
  id: errorStepId,
45
66
  name,
46
67
  type: 'response',
47
- description: "Gateway'in eklediği hata yolu: akışın herhangi bir adımı patlarsa buraya düşer ve 500 ile { code, message } döner. Kendi hata gövdeni istiyorsan bu adımın bodyExpression'ını değiştir.",
68
+ description: ERROR_STEP_DESCRIPTION,
48
69
  caution: "Bu adımı gateway ekledi; silersen akışın hata yolu kaybolur ve patlayan adım istemciye ham hata döndürür.",
49
70
  rule: {
50
71
  stepType: 'response',
51
72
  httpStatusCode: 500,
52
- bodyExpression: '({ code: error.code, message: error.message })'
73
+ bodyExpression: GATEWAY_ERROR_BODY_EXPRESSION
53
74
  },
54
75
  branches: []
55
76
  });
@@ -60,5 +81,27 @@ export function ensureWorkflowErrorPath(definition, kind) {
60
81
  branches.push({ id: randomUUID(), name: 'global-error', type: 'global-error', nextStepId: errorStepId });
61
82
  }
62
83
  startStep.branches = branches;
63
- return [`hata yolu eklendi: start adımına 'global-error' dalı + '${name}' response adımı (500, { code, message }). Hata yolu olmayan akış patladığında platform WorkflowStepFailedException fırlatır (HTTP 500 { code, message, detail, type }; zamanlanmış işte failed + "Step 'x' failed: …"); bu adım çıktıyı { code, message } ile denetim altına alır, zamanlanmış işte sonuç yine failed düşer. Kendi hata gövdenizi istiyorsanız bu adımın bodyExpression'ını değiştirin.`];
84
+ return [`hata yolu eklendi: start adımına 'global-error' dalı + '${name}' response adımı (500, { code, message, detail, type, step: { name, type }, cause }). Hata yolu olmayan akış patladığında platform WorkflowStepFailedException fırlatır (HTTP 500 { code, message, detail, type }, 2026-09-17 backend'inde step ve cause da; zamanlanmış işte failed + "Step 'x' failed: …"); bu adım aynı şekli ele alınmış 500 olarak döner — patlayan adımın adı ve tipi, alt akış ya da loop iterasyonu zinciri cause içinde — ve zamanlanmış işte sonuç yine failed düşer. Kendi hata gövdenizi istiyorsanız bu adımın bodyExpression'ını değiştirin.`];
85
+ }
86
+ /**
87
+ * Bu dosyanın 2026-09-16 öncesi sürümü hata adımına `{ code, message }` yazıyordu; o kayıtlar
88
+ * `detail`, `type` ve patlayan adımı dışarı vermez. Yalnız gateway'in KENDİ yazdığı ve kimsenin
89
+ * dokunmadığı adım yükseltilir: global-error dalının hedefi, adı `gatewayOnError[n]`, tipi
90
+ * `response`, ifadesi eski varsayılanla birebir aynı VE açıklaması eski gateway metni (ya da boş).
91
+ * Açıklama şartı, yükseltilmiş bir adımı aynı çağrıda bilerek eski gövdeye döndüren kullanıcıyı
92
+ * korur: yükseltme açıklamayı da yenilediği için geri dönüş bir daha ezilmez.
93
+ */
94
+ function upgradeLegacyErrorBody(steps, errorStepId) {
95
+ const target = steps.find(step => String(step.id ?? '') === errorStepId);
96
+ const rule = target?.rule;
97
+ if (!target || target.type !== 'response' || !ERROR_STEP_NAME_PATTERN.test(String(target.name ?? '')) || !rule)
98
+ return [];
99
+ if (typeof rule.bodyExpression !== 'string' || rule.bodyExpression.trim() !== LEGACY_GATEWAY_ERROR_BODY_EXPRESSION)
100
+ return [];
101
+ const untouchedDescription = target.description === undefined || target.description === null || String(target.description).trim() === '' || target.description === LEGACY_ERROR_STEP_DESCRIPTION;
102
+ if (!untouchedDescription)
103
+ return [];
104
+ rule.bodyExpression = GATEWAY_ERROR_BODY_EXPRESSION;
105
+ target.description = ERROR_STEP_DESCRIPTION;
106
+ return [`hata gövdesi genişletildi: '${String(target.name)}' adımının bodyExpression'ı gateway'in eski varsayılanıydı ({ code, message }); yeni varsayılan detail, type, patlayan adımın { name, type } bilgisini ve adım bir alt akışsa çocuğun hata gövdesini cause olarak da döner. Çocuk gövdesi yalnız code/message/detail/type/step{name,type}/cause alanlarına süzülür. Yanıta alan EKLENİR, mevcut code/message değişmez. Açıklaması ya da ifadesi elle değiştirilmiş hata adımına dokunulmaz; eski gövdede kalmak için bu adımın açıklamasını değiştirin.`];
64
107
  }
@@ -47,6 +47,47 @@ function hasText(value) {
47
47
  function stepLabel(step) {
48
48
  return step.name || step.type || step.id || '?';
49
49
  }
50
+ const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
51
+ /**
52
+ * `workflow` adımının (ve `loop` içindeki `stepType: 'workflow'` çocuğunun) alt akışı çağıran
53
+ * kuralı. `WorkflowStep.cs` yalnız `rule.WorkflowId` + `rule.Expression` okur; girdiyi başka
54
+ * bir alanla göndermeye çalışan gövde ya kaydedilip okunmaz (şema üyeleri) ya da hiç
55
+ * kaydedilmez (şema dışı adlar, `unmappedMembers`) — iki durumda da alt akış SESSİZCE girdisiz
56
+ * koşar (RFQ koşusu W-7, 2026-09-14). Platform her adım kuralını `fields: []`,
57
+ * `customParameters: null` gibi boş varsayılanlarla geri okuttuğu için yalnız DOLU değer sayılır;
58
+ * aksi hâlde `workflow_get` gövdesiyle yapılan her `workflow_patch`/`update`/`preview` kendi
59
+ * kendine bloklanırdı.
60
+ */
61
+ const WORKFLOW_STEP_SAVED_BUT_UNREAD_KEYS = ['payloadExpression', 'customParameters', 'queryParameters', 'bodyExpression', 'fields'];
62
+ const WORKFLOW_STEP_UNMAPPED_INPUT_KEYS = ['parameters', 'parameter', 'input', 'payload', 'arguments', 'args', 'variables'];
63
+ function carriesValue(value) {
64
+ if (value === undefined || value === null)
65
+ return false;
66
+ if (Array.isArray(value))
67
+ return value.length > 0;
68
+ if (typeof value === 'string')
69
+ return value.trim() !== '';
70
+ return true;
71
+ }
72
+ function workflowCallSites(body) {
73
+ return workflowSteps(body).flatMap((step) => {
74
+ const rule = (step.rule ?? {});
75
+ if (step.type === 'workflow')
76
+ return [{ step, rule, label: stepLabel(step) }];
77
+ if (step.type === 'loop' && rule.stepType === 'workflow' && rule.stepRule && typeof rule.stepRule === 'object') {
78
+ return [{ step, rule: rule.stepRule, label: `${stepLabel(step)} (loop çocuğu)` }];
79
+ }
80
+ return [];
81
+ });
82
+ }
83
+ function literalShapeOf(expression) {
84
+ const head = expression.trim().replace(/^[(\s]+/, '');
85
+ if (head.startsWith('{'))
86
+ return 'object';
87
+ if (head.startsWith('['))
88
+ return 'array';
89
+ return undefined;
90
+ }
50
91
  /**
51
92
  * Start'tan başlayıp `global-error` dalları HARİÇ tüm dalları izleyerek erişilen adımlar.
52
93
  * Gateway her `workflow`/`middleware` tanımına global-error dalının ucunda bir `response` adımı
@@ -698,6 +739,45 @@ export const policyRules = [
698
739
  });
699
740
  }
700
741
  },
742
+ {
743
+ id: 'WF-032',
744
+ module: 'workflow',
745
+ appliesTo: ['create', 'update', 'preview'],
746
+ sourceRef: "WorkflowStep.cs EvaluateAsync: kural yalnız rule.WorkflowId ve rule.Expression okur; girdi workflow.HasParameter iken Expression'ın DataTransferObjectExpression.Eval'iyle bağlanır, başka hiçbir alan okunmaz. Studio WorkflowTrigger.svelte de yalnız bu iki alanı yazar ve expression'ı çağrılan akışın parameterScheme'inden nesne literali olarak doldurur. RFQ koşusu W-7 (2026-09-14): payloadExpression/customParameters kaydedildi, alt akış girdisiz koştu",
747
+ check: (definition, context) => workflowCallSites(workflowBody(definition, context.workflowKind)).flatMap(({ label, rule }) => {
748
+ const workflowId = rule.workflowId;
749
+ if (!hasText(workflowId)) {
750
+ return [{
751
+ ruleId: 'WF-032',
752
+ severity: 'block',
753
+ message: `'${label}' workflow adımında rule.workflowId yok — adım hangi akışı çağıracağını bilmez. workflow_list(applicationId, type: 'workflow') ile id seçin; girdi rule.expression'a nesne literali olarak yazılır (çağrılan akışın parametre DTO'suna göre).`,
754
+ guide: 'ngapps://guides/workflow'
755
+ }];
756
+ }
757
+ if (!GUID_PATTERN.test(String(workflowId))) {
758
+ return [{
759
+ ruleId: 'WF-032',
760
+ severity: 'block',
761
+ message: `'${label}' adımının rule.workflowId'si GUID değil: '${String(workflowId)}'. Platform alanı Guid'e çeviremediği için 400 döner; slug değil id verin (workflow_list).`,
762
+ guide: 'ngapps://guides/workflow'
763
+ }];
764
+ }
765
+ const savedButUnread = WORKFLOW_STEP_SAVED_BUT_UNREAD_KEYS.filter(key => carriesValue(rule[key]));
766
+ const unmapped = WORKFLOW_STEP_UNMAPPED_INPUT_KEYS.filter(key => carriesValue(rule[key]));
767
+ if (savedButUnread.length === 0 && unmapped.length === 0)
768
+ return [];
769
+ const fate = [
770
+ savedButUnread.length > 0 ? `${savedButUnread.map(key => `rule.${key}`).join(', ')} kaydedilir ama WorkflowStep okumaz` : '',
771
+ unmapped.length > 0 ? `${unmapped.map(key => `rule.${key}`).join(', ')} StepRuleDto'da yok, platform hiç kaydetmez (unmappedMembers)` : ''
772
+ ].filter(part => part !== '').join('; ');
773
+ return [{
774
+ ruleId: 'WF-032',
775
+ severity: 'block',
776
+ message: `'${label}' adımı alt akışa girdiyi yanlış alandan gönderiyor — ${fate}. WorkflowStep yalnız rule.workflowId ve rule.expression okur: iki durumda da alt akış boş girdiyle koşar ve 200 döner (RFQ koşusu W-7). Eşlemeyi rule.expression'a nesne literali olarak yazın — her anahtar çağrılan akışın parametre DTO'sundaki bir alan, her değer bir JS ifadesi: "({ title: input.title, source: 'telemetry' })". Aynı adlı alan code adımında GÖVDE, workflow/microfunction adımında GİRDİDİR. Alan listesini workflow_step_schema (parameterScheme) ya da workflow_get(<workflowId>).parameterDtoId → dto_get verir.`,
777
+ guide: 'ngapps://guides/workflow'
778
+ }];
779
+ })
780
+ },
701
781
  {
702
782
  id: 'WF-006',
703
783
  module: 'workflow',
@@ -2873,6 +2953,89 @@ export async function microfunctionStepFindings(platform, project, environment,
2873
2953
  }
2874
2954
  return applyPolicyMode(platform.config, findings, policyOverride);
2875
2955
  }
2956
+ /**
2957
+ * WF-032'nin ikinci yarısı. `rule.expression`'ın işe yarayıp yaramayacağı gövdeden görülemez:
2958
+ * `WorkflowStep` ifadeyi YALNIZ çağrılan akışın parametre DTO'su varken değerlendirir
2959
+ * (`workflow.HasParameter`); DTO'suz alt akışa yazılan ifade hiç koşmaz ve girdi boş gider.
2960
+ * Karar çağrılan akışın kaydında yaşar — gateway `workflow_get` ucunu okur ve bulguyu aynı
2961
+ * `policyFindings` kanalına koyar; `policyOverride` / `POLICY=warn` kaçışları aynen geçerlidir.
2962
+ *
2963
+ * Okuma başarısız olursa kural SUSAR (WF-MF-001 ile aynı gerekçe). Uygulama kimliği ne
2964
+ * parametrede ne gövdede varsa (automation yolu `entityId` ile çalışır) da susar: statik yarı
2965
+ * yine koşar, canlı yarı bir kolaylıktır.
2966
+ */
2967
+ export async function workflowStepFindings(platform, project, environment, applicationId, definition, kind, policyOverride = false) {
2968
+ if (platform.config.policyMode === 'off')
2969
+ return [];
2970
+ const application = hasText(applicationId)
2971
+ ? String(applicationId)
2972
+ : hasText(definition.applicationId) ? String(definition.applicationId) : undefined;
2973
+ if (application === undefined)
2974
+ return [];
2975
+ const sites = workflowCallSites(workflowBody(definition, kind))
2976
+ .filter(site => GUID_PATTERN.test(String(site.rule.workflowId ?? '')));
2977
+ if (sites.length === 0)
2978
+ return [];
2979
+ const guide = 'ngapps://guides/workflow';
2980
+ const records = new Map();
2981
+ const findings = [];
2982
+ for (const { label, rule } of sites) {
2983
+ const workflowId = String(rule.workflowId);
2984
+ if (!records.has(workflowId)) {
2985
+ try {
2986
+ const payload = await platform.tenant('GET', project, `/workflows/${application}/${workflowId}`, { environment });
2987
+ records.set(workflowId, (payload?.data ?? payload));
2988
+ }
2989
+ catch (error) {
2990
+ records.set(workflowId, error instanceof PlatformApiError && error.status === 404 ? 'missing' : 'unreadable');
2991
+ }
2992
+ }
2993
+ const record = records.get(workflowId);
2994
+ if (record === undefined || record === 'unreadable')
2995
+ continue;
2996
+ if (record === 'missing') {
2997
+ findings.push({
2998
+ ruleId: 'WF-032',
2999
+ severity: 'block',
3000
+ message: `'${label}' adımının workflowId'si (${workflowId}) bu ortamda YOK (platform kaydı yalnız id ile arar, uygulama kimliğine bakmaz). Adım kuralı step_rules.workflow_id ile kayda FK'lıdır: gövde gitseydi platform 400 dönerdi. workflow_list(applicationId: '${application}', type: 'workflow') ile bu ortamdaki akışları görün — alt akış başka bir dalda ya da ortamda kalmış, ya da henüz yaratılmamış olabilir.`,
3001
+ guide
3002
+ });
3003
+ continue;
3004
+ }
3005
+ const calleeName = String(record.name ?? record.slug ?? workflowId);
3006
+ const parameterDtoId = hasText(record.parameterDtoId) ? String(record.parameterDtoId) : undefined;
3007
+ const expression = hasText(rule.expression) ? String(rule.expression) : '';
3008
+ if (expression !== '' && parameterDtoId === undefined) {
3009
+ findings.push({
3010
+ ruleId: 'WF-032',
3011
+ severity: 'block',
3012
+ message: `'${label}' adımı '${calleeName}' akışına rule.expression ile girdi geçiriyor ama o akışın parametre DTO'su YOK (parameterDtoId boş). WorkflowStep ifadeyi yalnız workflow.HasParameter iken değerlendirir: bu hâliyle ifade HİÇ çalışmaz, alt akış boş girdiyle koşar ve 200 döner. Önce alt akışa girdi sözleşmesi verin — dto_create ile alan listesini tanımlayın, sonra workflow_patch(id: '${workflowId}', ops: [{ op: 'add', path: '/parameterDtoId', value: '<dtoId>' }, { op: 'add', path: '/parameterBehavior', value: 'object' }]); alt akışın adımları girdiyi input.<alan> ile okur.`,
3013
+ guide
3014
+ });
3015
+ continue;
3016
+ }
3017
+ if (expression === '' && parameterDtoId !== undefined) {
3018
+ findings.push({
3019
+ ruleId: 'WF-032',
3020
+ severity: 'warn',
3021
+ message: `'${label}' adımında rule.expression boş ama '${calleeName}' akışının parametre DTO'su var (parameterDtoId: ${parameterDtoId}). Alt akış input olarak boş nesne alır, kendi input.<alan> okumaları undefined döner. Girdiyi nesne literaliyle geçirin — alan listesini workflow_step_schema çıktısındaki parameterScheme ya da dto_get('${parameterDtoId}') verir.`,
3022
+ guide
3023
+ });
3024
+ continue;
3025
+ }
3026
+ const behavior = String(record.parameterBehavior ?? '').toLowerCase();
3027
+ const literal = literalShapeOf(expression);
3028
+ if (literal !== undefined && ((behavior === 'array' && literal === 'object') || (behavior === 'object' && literal === 'array'))) {
3029
+ findings.push({
3030
+ ruleId: 'WF-032',
3031
+ severity: 'warn',
3032
+ message: `'${label}' adımının ifadesi ${literal === 'object' ? 'nesne' : 'dizi'} literaliyle başlıyor ama '${calleeName}' akışının parameterBehavior'ı '${behavior}': alt akış input olarak ${behavior === 'array' ? 'DİZİ bekler; nesne alınca öğe okumaları' : 'NESNE bekler; dizi alınca input.<alan> okumaları'} undefined döner. İfadeyi davranışa uydurun ya da alt akışın parameterBehavior'ını değiştirin.`,
3033
+ guide
3034
+ });
3035
+ }
3036
+ }
3037
+ return applyPolicyMode(platform.config, findings, policyOverride);
3038
+ }
2876
3039
  const partialWriteOperations = new Set(['create', 'update', 'upsert']);
2877
3040
  const inputReferencePattern = /\binput\.([A-Za-z_$][\w$]*)/g;
2878
3041
  function inputReferencesOf(rule) {
@@ -177,6 +177,79 @@ function diagnoseKnownFailure(status, message, code, context) {
177
177
  }
178
178
  return '';
179
179
  }
180
+ const MaxErrorCauseWalk = 32;
181
+ const MaxErrorPathShown = 6;
182
+ const MaxErrorTextLength = 400;
183
+ function nonEmptyText(value) {
184
+ if (typeof value !== 'string' || value.trim() === '')
185
+ return undefined;
186
+ return value.length > MaxErrorTextLength ? `${value.slice(0, MaxErrorTextLength)}…` : value;
187
+ }
188
+ function asRecord(value) {
189
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
190
+ }
191
+ function errorLevels(body) {
192
+ const levels = [];
193
+ let current = asRecord(body);
194
+ while (current) {
195
+ if (levels.length === MaxErrorCauseWalk)
196
+ return { levels, truncated: true };
197
+ const step = asRecord(current.step);
198
+ const name = nonEmptyText(step?.name);
199
+ const stepType = nonEmptyText(step?.type);
200
+ levels.push({
201
+ step: name ? `'${name}'${stepType ? ` (${stepType})` : ''}` : undefined,
202
+ code: nonEmptyText(current.code),
203
+ message: nonEmptyText(current.message),
204
+ detail: nonEmptyText(current.detail),
205
+ type: nonEmptyText(current.type)
206
+ });
207
+ current = asRecord(current.cause);
208
+ }
209
+ return { levels, truncated: false };
210
+ }
211
+ function seenKey(...parts) {
212
+ return JSON.stringify(parts);
213
+ }
214
+ /**
215
+ * Workflow 5xx gövdesinden, başlıkta zaten yazılmış `code`/`message` dışında kalan bilgiyi çıkarır.
216
+ * İki gövde şekli okunur: platformun `WorkflowStepErrorResponse`'u `{ code, message, detail, type }`
217
+ * (hata yolu olmayan akış, `ExceptionHandler.cs`) ve gateway hata adımının gövdesi
218
+ * `{ …, step: { name, type }, cause }` (`errorPath.ts`). `cause` zinciri alt akış seviyelerini
219
+ * taşır; aynı kod+mesaj her seviyede tekrarlandığı için mesaj EN DERİNDEN başlanarak bir kez, hatanın
220
+ * doğduğu adıma atfedilerek yazılır. Kodla aynı `type` ve mesajla aynı `detail` gürültüdür. Uzun
221
+ * zincirde yolun başı ve SONU gösterilir (hatanın doğduğu adım kaybolmasın); metinler kırpılır.
222
+ */
223
+ export function describeErrorBody(body, shown) {
224
+ const { levels, truncated } = errorLevels(body);
225
+ const fragments = [];
226
+ const path = levels.map(level => level.step).filter((step) => step !== undefined);
227
+ const shownPath = path.length > MaxErrorPathShown ? [...path.slice(0, 2), '…', ...path.slice(-3)] : path;
228
+ if (shownPath.length > 0)
229
+ fragments.push(`hata yolu: ${shownPath.join(' → ')}${truncated ? ' → …' : ''}`);
230
+ const seen = new Set();
231
+ if (shown) {
232
+ seen.add(seenKey('head', shown.code, shown.message));
233
+ if (levels[0])
234
+ seen.add(seenKey('head', levels[0].code, levels[0].message));
235
+ }
236
+ for (const level of [...levels].reverse()) {
237
+ const key = seenKey('head', level.code, level.message);
238
+ if ((level.code || level.message) && !seen.has(key)) {
239
+ seen.add(key);
240
+ fragments.push(`${level.step ? `${level.step} ` : ''}${[level.code, level.message].filter(Boolean).join(': ')}`);
241
+ }
242
+ if (level.detail && level.detail !== level.message && !seen.has(seenKey('detail', level.detail))) {
243
+ seen.add(seenKey('detail', level.detail));
244
+ fragments.push(`detail: ${level.detail}`);
245
+ }
246
+ if (level.type && level.type !== level.code && !seen.has(seenKey('type', level.type))) {
247
+ seen.add(seenKey('type', level.type));
248
+ fragments.push(`type: ${level.type}`);
249
+ }
250
+ }
251
+ return fragments;
252
+ }
180
253
  export function mapPlatformError(status, payload, context) {
181
254
  const body = payload && typeof payload === 'object' ? payload : {};
182
255
  const code = typeof body.code === 'string' ? body.code : undefined;
@@ -222,7 +295,9 @@ export function mapPlatformError(status, payload, context) {
222
295
  }
223
296
  const fallbackValidationMessages = collectValidationMessages(body);
224
297
  const fallbackDetail = fallbackValidationMessages.length > 0 ? ` → ${fallbackValidationMessages.join(' ; ')}` : '';
225
- return new PlatformApiError(`Platform hatası ${status} (${location}): ${apiMessage ?? 'beklenmeyen hata'}${code ? ` [${code}]` : ''}${fallbackDetail}${diagnoseKnownFailure(status, apiMessage ?? '', code, context)}`, status, code);
298
+ const errorBody = status >= 500 ? describeErrorBody(body, { code, message: apiMessage }) : [];
299
+ const errorBodyDetail = errorBody.length > 0 ? ` — ${errorBody.join(' · ')}` : '';
300
+ return new PlatformApiError(`Platform hatası ${status} (${location}): ${apiMessage ?? 'beklenmeyen hata'}${code ? ` [${code}]` : ''}${errorBodyDetail}${fallbackDetail}${diagnoseKnownFailure(status, apiMessage ?? '', code, context)}`, status, code);
226
301
  }
227
302
  export function mapNetworkError(error, context) {
228
303
  const cause = error instanceof Error ? error : new Error(String(error));
@@ -1,5 +1,5 @@
1
1
  import { logError, logStep } from '../observability/trace.js';
2
- import { PlatformApiError } from './errors.js';
2
+ import { PlatformApiError, describeErrorBody } from './errors.js';
3
3
  import { PolicyBlockError } from '../knowledge/policy.js';
4
4
  import { maskSecrets } from './secrets.js';
5
5
  export function prune(value) {
@@ -76,6 +76,17 @@ export function runtimeErrorSummary(payload) {
76
76
  const errors = [];
77
77
  if (typeof record.httpStatus === 'number' && record.httpStatus >= 400) {
78
78
  errors.push(`httpStatus: ${record.httpStatus} — çağrı ${record.httpStatus} ile bitti; gövde başarı çıktısı değil hata gövdesidir. Adım adım bakmak için microservice_debug kullanın.`);
79
+ // Yalnız 5xx: 4xx gövdeleri (422 doğrulama, 401) bilinçli iş yanıtıdır ve zaten gövdede okunur.
80
+ // `/debug/` ucu gövdeyi her zaman, `/m/` ve servis önizlemesi ise uiActions kurulmuşsa
81
+ // `{ data, uiActions[, executionItems] }` ile sarar (MicroservicesController.Debug / HandleResult).
82
+ const raw = record.body && typeof record.body === 'object' && !Array.isArray(record.body) ? record.body : undefined;
83
+ const wrapped = raw !== undefined && ('executionItems' in raw || 'uiActions' in raw) && raw.data && typeof raw.data === 'object' && !Array.isArray(raw.data);
84
+ const errorBody = wrapped ? raw.data : raw;
85
+ const describable = record.httpStatus >= 500 && errorBody !== undefined &&
86
+ (typeof errorBody.code === 'string' || Boolean(errorBody.step && typeof errorBody.step === 'object') || Boolean(errorBody.cause && typeof errorBody.cause === 'object'));
87
+ const fragments = describable ? describeErrorBody(errorBody) : [];
88
+ if (fragments.length > 0)
89
+ errors.push(`hata gövdesi — ${fragments.join(' · ')}`);
79
90
  }
80
91
  const stepErrors = [];
81
92
  collectStepErrors(body.steps, stepErrors);
@@ -227,19 +227,68 @@ tanımı kendi global-error dalınla gönder — dal **bağlıysa** (`nextStepId
227
227
  dokunmaz. `nextStepId: null` ile bağlanmamış gönderirsen gateway ikinci bir dal eklemez, mevcut
228
228
  dalını enjekte ettiği hata adımına bağlar.
229
229
 
230
- **Fail dallarını tek tek bağlamana gerek yok.** Bir adımın `fail` dalı bağlanmamışsa
231
- (`nextStepId: null`) ya da hiç yoksa, hata **kendiliğinden** global-error dalına gider. `fail`
232
- dalını yalnız o adıma özel bir telafi akışı (örn. "kayıt yoksa oluştur") istiyorsan bağla.
230
+ ### `gatewayOnError`'un 500 gövdesi: patlayan adım ve alt akış zinciri (2026-09-16)
231
+
232
+ ```jsonc
233
+ // POST /m/web/ticket-ac → 500 (ebeveyn → callChild → çocuk akış → childBoom patladı)
234
+ { "code": "JavaScriptException", "message": "Error: x is not defined\n at <anonymous>:1:8",
235
+ "detail": "…", "type": "JavaScriptException",
236
+ "step": { "name": "callChild", "type": "workflow" }, // bu akışta patlayan adım
237
+ "cause": { "code": "JavaScriptException", "message": "…", // alt akışın KENDİ hata gövdesi
238
+ "step": { "name": "childBoom", "type": "code" } } } // çocukta patlayan adım
239
+ ```
240
+
241
+ - **`step` yalnız `{ name, type }`'tır.** `error.step.input` ifade değerlerini (parola dahil),
242
+ `error.step.data` adımın çıktısını taşır; ikisi de dışarı yazılmaz. `error.step`'i bütün olarak
243
+ gövdeye koyarsan platform onu **siler** (`ResponseStep` global-error kipinde `error.step` ile
244
+ birebir aynı `step` alanını kaldırır) — alt küme yaz.
245
+ - **`cause` bir seviye aşağıyı taşır** ve her seviyede yalnız `code`, `message`, `detail`, `type`,
246
+ `step { name, type }` ve `cause` geçer (gateway beyaz listeye süzer, en fazla dokuz seviye). Backend
247
+ 2026-09-17'den beri zinciri `error.cause`'ta kendisi kurar; üç durum kapsanır:
248
+ - **Alt akış patladı:** `cause.step` çocukta patlayan adım, çocuk da alt akış çağırıyorsa zincir
249
+ seviye seviye iç içe iner. Çocuk Studio'da hata yolsuz kurulduysa da adım adı gelir.
250
+ - **Loop iterasyonu patladı:** `step` loop adımı, `cause.step.name` `loopRows[0]` gibi
251
+ `<loop adı>[<sıra>]`, iterasyondaki alt akışın zinciri onun altında.
252
+ - **Çocuk istisnasız 2xx dışı döndü** (`response` 422): `code` `SubWorkflowFailedException`,
253
+ `message` `Sub-workflow '<ad>' returned 422 (<gövde code>): <gövde message>`.
254
+
255
+ Eski backend'de `error.cause` yoktur; gateway o zaman patlayan `workflow` adımının `step.data`'sına
256
+ (çocuğun kendi gövdesi) düşer — loop ve istisnasız çocuk orada kaybolur. Çocuk kendi 4xx/5xx yanıtını
257
+ elle yazdıysa (`({ success: false, token })`, `({ err: error })`) o gövdenin yalnız bu alanları geçer.
258
+ Canlı ölçüldü (2026-09-16/17): torunun `token` ve `step.input`'u üst gövdeye ulaşmadı; üç seviyeli
259
+ zincir ve `loopRows` → `loopRows[0]` → `boomStep` hem hata yolsuz hem gateway hata yollu uçta döndü.
260
+ - **Gateway zinciri okur (yalnız 5xx):** `microservice_execute`/`debug`/`preview` `runtimeErrors`'a
261
+ `hata gövdesi — hata yolu: 'callChild' (workflow) → 'childBoom' (code) · 'childBoom' (code)
262
+ JavaScriptException: …` satırını ekler (debug ucunun `{ data, executionItems }` sarmalı açılır);
263
+ fırlatan çağrıların (senkron automation'lı `row_create`, `workflow_preview`…) araç hatası da
264
+ gövdede bulunan `detail`'i, koddan farklı `type`'ı ve varsa zinciri yazar. 4xx gövdeleri
265
+ süslenmez — onlar bilinçli iş yanıtıdır.
266
+ - **Eski kayıtlar:** 2026-09-16 öncesi gateway `({ code: error.code, message: error.message })`
267
+ yazıyordu. Global-error dalının hedefi olan, adı `gatewayOnError[n]`, ifadesi bu metinle AYNEN
268
+ aynı ve açıklaması gateway'in eski metni (ya da boş) olan adım bir sonraki
269
+ `workflow_create`/`update`/`patch`/`preview`'da yeni gövdeye yükseltilir (`gatewayNormalized`:
270
+ "hata gövdesi genişletildi"; önizleme kaydetmez). Yanıta alan eklenir, `code`/`message` değişmez.
271
+ Açıklaması ya da ifadesi elle değiştirilmiş adıma dokunulmaz; yükseltme açıklamayı da yenilediği
272
+ için sonradan bilerek eski gövdeye dönen `bodyExpression` bir daha ezilmez.
273
+ - **Dışarı ne verdiğine sen karar verirsin.** Adım adları iç yapıyı, `detail` iç istisna mesajını
274
+ (kısıt adı, host) gösterebilir. **Middleware'in 500 gövdesi korumalı uçların anonim çağıranına da
275
+ gider** (`ExecuteMicroserviceBehavior`). Herkese açık yüzeyde istemiyorsan hata adımının
276
+ `bodyExpression`'ını daralt, örn. `({ code: error.code, message: error.message })`.
233
277
 
234
278
  Global-error dalında `error` değişkeni tanımlıdır:
235
279
 
236
280
  ```jsonc
237
281
  { "code": "RowAlreadyExists", "message": "…", "detail": "…", "type": "…",
238
- "step": { "id": "…", "name": "writeRow", "type": "table", "data": …, "input": [ … ] } }
282
+ "step": { "id": "…", "name": "writeRow", "type": "table", "data": …, "input": [ … ] },
283
+ "cause": null } // alt akış / loop iterasyonunda { code, message, detail, type, step: { name, type }, cause }
239
284
  ```
240
285
 
241
- `error.step` hangi adımın patladığını söyler — hata yanıtına koymadan önce iki kez düşün, iç
242
- yapıyı dışarı sızdırır.
286
+ Fail dalında aynı zincir `<adım adı>.error.cause`'tadır (`<adım adı>.error` = `{ code, message, detail,
287
+ type, cause }`).
288
+
289
+ `error.step` hangi adımın patladığını söyler. `input`/`data` alanları ifade değerlerini ve adım
290
+ çıktısını taşır — gövdeye yalnız ihtiyaç duyduğun alt kümeyi yaz (gateway'in varsayılanı
291
+ `{ name, type }`'tır, yukarıdaki §`gatewayOnError`'un 500 gövdesi).
243
292
 
244
293
  **Kurallar ve sınırlar:**
245
294
 
@@ -257,9 +306,14 @@ yapıyı dışarı sızdırır.
257
306
  bağlı — §izin matrisi) ya da `email`/`table` adımına bağla. Bağlı hata yolu yoksa `WF-021` uyarır.
258
307
  - **Hata yolu hiç yoksa** (fail dalı ve global-error dalı bağlı değil) adım patladığında motor
259
308
  canlıda `WorkflowStepFailedException` fırlatır (2026-09-16): HTTP yanıt 500 +
260
- `{ code, message, detail, type }` (patlayan adımın hatası), zamanlanmış iş ve automation geçmişi
261
- `failed` + `Step 'x' failed: <sebep>`, senkron automation tetikleyen satır yazımını düşürür.
262
- Önizlemede fırlatmaz, ağaç döner. Çalışır, ama çıktı lokalize değildir.
309
+ `{ code, message, detail, type, step: { name, type }, cause }` — `step` ve `cause` 2026-09-17'den
310
+ beri, gateway hata adımıyla aynı şekil (yerel ortamda ayrıca `stackTrace`). Zamanlanmış iş ve
311
+ automation geçmişi `failed` + `Step 'x' failed: <sebep>`, senkron automation tetikleyen satır
312
+ yazımını düşürür. Önizlemede fırlatmaz, ağaç döner. Çalışır, ama çıktı lokalize değildir.
313
+ - **Adım hata yolunun DIŞINDA patlarsa** (ör. `revert`'in `revertType` ifadesi tırnaksız) kod
314
+ `EvaluateWorkflowStepFailedException`, mesaj `Step '<ad>' threw outside its error path: <sebep>`
315
+ olur: fail dalı ve global-error dalı ÇALIŞMAZ, canlı koşu düşer. 2026-09-17'den beri önizleme
316
+ ağacı kaybolmaz; adım `hasError: true` ile işaretlenir.
263
317
 
264
318
  > **Preview HTTP 200 dönerken bile akış patlamış olabilir.** `executionSuccess` yalnız SON adıma
265
319
  > bakar; ortada patlayıp hata dalından dönen akış `true` gösterir. Gateway tüm adımları (iç içe
@@ -347,6 +401,63 @@ gidilir, hiçbiri tutmazsa `else` dalı yürür:
347
401
  > `rule.isExpression` bayrağı bu iki alan için **okunmaz**.
348
402
 
349
403
 
404
+ ## Workflow adımı: başka bir akışı parametreyle çağırmak (WF-032)
405
+
406
+ Ortak mantığı (kayıt açma, bildirim, numaralandırma) **bir kez** `type:'workflow'` akışı olarak
407
+ yazıp diğer akışlardan çağırmanın yolu budur — 30 adımı kopyalamak değil. Adım tipi `workflow`;
408
+ üç akış tipinde de kullanılabilir. Kuralı **iki alan** taşır ve girdi eşlemesi ikincisidir:
409
+
410
+ ```jsonc
411
+ { "id": "<GUID>", "name": "createTicket", "type": "workflow",
412
+ "description": "Telemetri olayından create-ticket akışıyla kayıt açar.",
413
+ "rule": {
414
+ "workflowId": "<create-ticket akışının id'si>",
415
+ "expression": "({ title: input.title, source: 'telemetry', payload: parseEvent.data })"
416
+ },
417
+ "branches": [
418
+ { "id": "<GUID>", "name": "success", "type": "success", "nextStepId": "<sonraki>" },
419
+ { "id": "<GUID>", "name": "fail", "type": "fail", "nextStepId": "<hata adımı>" }
420
+ ] }
421
+ ```
422
+
423
+ - **Eşleme `rule.expression`'daki nesne literalidir: her anahtar çağrılan akışın parametre
424
+ DTO'sundaki bir alan, her değer bir JS ifadesi** (`input.x`, önceki adımın `<ad>.data.x`'i,
425
+ sabit). Studio'nun adım editörü de aynı şeyi yapar — akış seçilince ifadeyi `parameterScheme`'den
426
+ `{ "alan": <örnek> }` olarak doldurur (`WorkflowTrigger.svelte`). Table adımının `fields[]`
427
+ listesinin buradaki karşılığı budur; ayrı bir liste alanı **yoktur** ve gerekmez.
428
+ - **Çağrılan akışın `parameterDtoId` + `parameterBehavior`'ı OLMAK ZORUNDA.** `WorkflowStep`
429
+ ifadeyi yalnız `workflow.HasParameter` iken değerlendirir (`WorkflowStep.cs`); DTO'suz alt
430
+ akışa yazılan ifade hiç koşmaz, alt akış boş `input` ile çalışır ve **200 döner**. Gateway
431
+ çağrılan akışı okuyup bunu **block** eder (WF-032). Reçete: `dto_create` → `workflow_patch(id:
432
+ <alt akış>, ops: [{ op: 'add', path: '/parameterDtoId', value: <dtoId> }, { op: 'add', path:
433
+ '/parameterBehavior', value: 'object' }])`.
434
+ - **Başka hiçbir alan girdi taşımaz.** Şema üyeleri (`payloadExpression`, `customParameters`,
435
+ `queryParameters`, `bodyExpression`, `fields`) kaydedilir ama `WorkflowStep` okumaz; şema dışı
436
+ adlar (`parameters`, `input`, `payload`, `args`…) platformda hiç kaydedilmez (`unmappedMembers`).
437
+ İki durumda da alt akış boş girdi alır (RFQ koşusu W-7, 2026-09-14: beş biçim denendi, beşinde
438
+ de boş girdi). Gateway DOLU değer görürse block eder; `workflow_get`'in geri okuttuğu
439
+ `fields: []` / `customParameters: null` gibi boş varsayılanlar kuralı tetiklemez.
440
+ - **Ad tuzağı:** `rule.expression` `code` adımında GÖVDE (çalıştırılan kod), `workflow` ve
441
+ `microfunction` adımında GİRDİ (argüman nesnesi) demektir. Aynı ad, iki anlam.
442
+ - **Alt akış girdiyi `input.<alan>` ile okur ve onun `input`'u bu nesnedir** — çağıranın
443
+ `input`'unu görmez; çağıranın bir alanı gerekiyorsa eşlemeye koy. `parameterBehavior: 'array'`
444
+ ise ifade dizi üretmelidir (gateway nesne literali görürse uyarır).
445
+ - **Çıktı alt akışın yanıt gövdesidir** (`response` adımının `returnDtoId` süzgecinden geçmiş
446
+ hâli) ve sonraki adımlara `<adım adı>.data` ile akar. Alt akış 2xx dışı dönerse ya da bir
447
+ adımı düşerse çağıran adım **fail** dalına gider; `<adım adı>.data` o zaman alt akışın hata
448
+ gövdesidir.
449
+ - **Şekli tahmin etme, oku:** `workflow_step_schema(step)` çağrılan akışın `parameterScheme` ve
450
+ `returnScheme`'ini döner; ya da `workflow_get(<workflowId>)` → `parameterDtoId` → `dto_get`.
451
+ - **Loop içinde:** `rule.stepType: 'workflow'`, `rule.arrayExpression`, çocuk kural
452
+ `rule.stepRule: { workflowId, expression: "({ title: loopItem.title })" }` — WF-032 çocuğu da
453
+ denetler.
454
+ - **Önizlemede** alt akışın kendi adım izi `steps[].children[]` altında `kind: 'subWorkflow'` ile
455
+ gelir; çağıran adımın `input[]` listesinde `member: 'parameter'` satırı ifadenin ürettiği nesneyi
456
+ gösterir — **satır yoksa ya da değeri boşsa girdi bağlanmamıştır.** Alt akışı tek başına
457
+ sınamak için kendi `previewPayload`'ı ile `workflow_preview` çağır.
458
+ - Aynı alt akışı birden çok adımdan çağırmak serbesttir; özyineleme runner'ın koşum sayacına
459
+ takılır (kural envanteri WF-028 — gateway kuralı değil, backend davranışı).
460
+
350
461
  ## Loop step — dizinin her öğesi için bir adım koştur
351
462
 
352
463
  Tek uçta "başlık + N alt kayıt" yazmanın yolu budur (anket + sorular + seçenekler tek çağrıda;
@@ -2,7 +2,7 @@ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environme
2
2
  import { RefResolver } from '../platform/refs.js';
3
3
  import { normalizeWorkflowDefinition, withNormalization } from '../knowledge/normalize.js';
4
4
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
5
- import { policyRules, microfunctionStepFindings } from '../knowledge/rules.js';
5
+ import { policyRules, microfunctionStepFindings, workflowStepFindings } from '../knowledge/rules.js';
6
6
  import { languageHeader } from '../knowledge/languageCodes.js';
7
7
  import { handle } from '../platform/responses.js';
8
8
  import { z } from 'zod';
@@ -40,7 +40,8 @@ export function registerAutomationTools(server, platform) {
40
40
  const normalized = normalizeWorkflowDefinition(definition);
41
41
  const findings = enforcePolicy([
42
42
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'create', workflowKind: 'automation' }, policyOverride),
43
- ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
43
+ ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride),
44
+ ...await workflowStepFindings(platform, project, environment, undefined, definition, 'automation', policyOverride)
44
45
  ]);
45
46
  const resolver = new RefResolver(platform, project, environment);
46
47
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
@@ -56,7 +57,8 @@ export function registerAutomationTools(server, platform) {
56
57
  const normalized = normalizeWorkflowDefinition(definition);
57
58
  const findings = enforcePolicy([
58
59
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: 'automation' }, policyOverride),
59
- ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
60
+ ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride),
61
+ ...await workflowStepFindings(platform, project, environment, undefined, definition, 'automation', policyOverride)
60
62
  ]);
61
63
  const resolver = new RefResolver(platform, project, environment);
62
64
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
@@ -135,7 +137,8 @@ export function registerAutomationTools(server, platform) {
135
137
  const normalized = normalizeWorkflowDefinition(definition);
136
138
  const findings = enforcePolicy([
137
139
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'preview', workflowKind: 'automation' }, policyOverride),
138
- ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
140
+ ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride),
141
+ ...await workflowStepFindings(platform, project, environment, undefined, definition, 'automation', policyOverride)
139
142
  ]);
140
143
  const resolver = new RefResolver(platform, project, environment);
141
144
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
@@ -4,7 +4,7 @@ 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, undefinedInputFieldFindings } from '../knowledge/rules.js';
7
+ import { policyRules, microfunctionStepFindings, workflowStepFindings, undefinedInputFieldFindings } from '../knowledge/rules.js';
8
8
  import { languageHeader } from '../knowledge/languageCodes.js';
9
9
  import { handle } from '../platform/responses.js';
10
10
  import fastJsonPatch from 'fast-json-patch';
@@ -119,6 +119,7 @@ export function registerWorkflowTools(server, platform) {
119
119
  const findings = enforcePolicy([
120
120
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'create', workflowKind: definition.type }, policyOverride),
121
121
  ...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
122
+ ...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
122
123
  ...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
123
124
  ]);
124
125
  const drift = unmappedMembers('CreateWorkflowCommand', definition);
@@ -135,6 +136,7 @@ export function registerWorkflowTools(server, platform) {
135
136
  const findings = enforcePolicy([
136
137
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: definition.type }, policyOverride),
137
138
  ...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
139
+ ...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
138
140
  ...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
139
141
  ]);
140
142
  const drift = unmappedMembers('UpdateWorkflowCommand', definition);
@@ -156,6 +158,7 @@ export function registerWorkflowTools(server, platform) {
156
158
  const findings = enforcePolicy([
157
159
  ...evaluatePolicy(config, policyRules, 'workflow', patched, { operation: 'update', workflowKind: patched.type }, policyOverride),
158
160
  ...await microfunctionStepFindings(platform, project, environment, patched, patched.type, policyOverride),
161
+ ...await workflowStepFindings(platform, project, environment, applicationId, patched, patched.type, policyOverride),
159
162
  ...await undefinedInputFieldFindings(platform, project, environment, patched, patched.type, policyOverride)
160
163
  ]);
161
164
  const drift = introducedUnmappedMembers('UpdateWorkflowCommand', (current?.data ?? current), patched);
@@ -202,6 +205,7 @@ export function registerWorkflowTools(server, platform) {
202
205
  const findings = enforcePolicy([
203
206
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'preview', workflowKind: definition.type }, policyOverride),
204
207
  ...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
208
+ ...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
205
209
  ...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
206
210
  ]);
207
211
  const payload = await platform.tenant('POST', project, `/workflows/${applicationId}/preview`, { environment, body: definition, headers: languageHeader(language, normalized) });
@@ -209,7 +213,7 @@ export function registerWorkflowTools(server, platform) {
209
213
  }));
210
214
  server.registerTool('workflow_step_schema', {
211
215
  title: 'Step şeması üret',
212
- description: 'Verilen step tanımı için çıktı şemasını üretir; step yazarken definition\'ı doğrulamanın en hızlı yolu.',
216
+ description: "Verilen step tanımı için girdi/çıktı şemasını üretir (parameterScheme / returnScheme); step yazarken definition'ı doğrulamanın en hızlı yolu. type:'workflow' adımında (rule.workflowId) çağrılan akışın parametre ve dönüş DTO'sunu döner — alt akışa girdi YALNIZ rule.expression ile geçer, nesne literalinin anahtarları bu parameterScheme'in alanlarıdır (WF-032; platform_guide('workflow') §Workflow adımı).",
213
217
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, step: definitionParam },
214
218
  annotations: readOnly
215
219
  }, handle(config, ({ project, environment, applicationId, id, step }) => platform.tenant('POST', project, `/workflows/${applicationId}/${id}/steps/schema`, { environment, body: step })));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
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",
@@ -35,6 +35,7 @@
35
35
  "harvest-examples": "tsx scripts/harvest-examples.ts",
36
36
  "kabul:import-adres": "tsx scripts/import-kabul-adres.ts",
37
37
  "kabul:import-senaryolar": "tsx scripts/import-kabul-senaryolar.ts",
38
+ "kabul:workflow-hata-zinciri": "tsx scripts/kabul-workflow-hata-zinciri.ts",
38
39
  "benchmark:report": "tsx scripts/benchmark-report.ts"
39
40
  },
40
41
  "dependencies": {