@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.
- package/dist/knowledge/errorPath.js +48 -5
- package/dist/knowledge/rules.js +163 -0
- package/dist/platform/errors.js +76 -1
- package/dist/platform/responses.js +12 -1
- package/dist/resources/guides/workflow.md +120 -9
- package/dist/tools/automations.js +7 -4
- package/dist/tools/workflows.js +6 -2
- package/package.json +2 -1
|
@@ -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
|
-
|
|
39
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
}
|
package/dist/knowledge/rules.js
CHANGED
|
@@ -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) {
|
package/dist/platform/errors.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
package/dist/tools/workflows.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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": {
|