@appaflytech/zeyrek-ai-mcp 0.0.12 → 0.0.14
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/normalize.js +98 -11
- package/dist/knowledge/outlines.js +16 -3
- package/dist/knowledge/rules.js +70 -2
- package/dist/resources/examples/function-total.json +6 -1
- package/dist/resources/examples/workflow-call-workflow.json +115 -0
- package/dist/resources/examples/workflow-http-step.json +84 -0
- package/dist/resources/examples/workflow-loop-step.json +132 -0
- package/dist/resources/guides/entity.md +40 -3
- package/dist/resources/guides/getting-started.md +4 -0
- package/dist/resources/guides/microservice.md +40 -0
- package/dist/resources/guides/modify-existing.md +13 -2
- package/dist/resources/guides/page.md +13 -1
- package/dist/resources/guides/workflow.md +53 -3
- package/dist/resources/schema-fingerprint.json +7 -5
- package/dist/resources/schemas/MicroserviceDto.json +972 -0
- package/dist/tools/access.js +1 -1
- package/dist/tools/designSchemaPatch.js +6 -2
- package/dist/tools/entities.js +1 -1
- package/dist/tools/flows.js +1 -1
- package/dist/tools/functions.js +1 -1
- package/dist/tools/microservices.js +8 -4
- package/dist/tools/pages.js +8 -4
- package/dist/tools/platformInfo.js +1 -1
- package/dist/tools/schemaOps.js +2 -2
- package/dist/tools/workflows.js +1 -1
- package/package.json +1 -1
|
@@ -140,6 +140,49 @@ export function normalizeMicroserviceDefinition(definition) {
|
|
|
140
140
|
return [];
|
|
141
141
|
return normalizeTableParameters(definition.sourceParameters, definition.entityId);
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* S-2: `microservice_patch`in yamaladığı belge YAZMA komutu değil OKUMA şeklidir
|
|
145
|
+
* (`MicroserviceDto`) — rol listesi orada `roles: [{ id, name }]`tır ve `UpdateMicroserviceCommand.FromDto`
|
|
146
|
+
* onu `RoleIds`'e eşler. Yazma gövdesinin adı (`roleIds`) patch belgesinde YOKTUR: `replace` yolu
|
|
147
|
+
* `OPERATION_PATH_UNRESOLVABLE` ile düşer, `add` ise platformda sessizce yutulur
|
|
148
|
+
* (`StJsonOptions.Read` bilinmeyen üyeyi atar, 200 döner, rol değişmez). Ajan yazma adını öğrenmiş
|
|
149
|
+
* olarak geldiği için yolu reddetmek yerine ÇEVİRİYORUZ ve çeviriyi `gatewayNormalized` ile
|
|
150
|
+
* söylüyoruz — istek geçer, doğru ad öğrenilir.
|
|
151
|
+
*/
|
|
152
|
+
export function normalizeMicroservicePatchOps(operations, definition) {
|
|
153
|
+
const notes = [];
|
|
154
|
+
const rolesIsArray = Array.isArray(definition.roles);
|
|
155
|
+
for (const operation of operations) {
|
|
156
|
+
const path = typeof operation.path === 'string' ? operation.path : '';
|
|
157
|
+
if (path !== '/roleIds' && !path.startsWith('/roleIds/') && path !== '/roles' && !path.startsWith('/roles/'))
|
|
158
|
+
continue;
|
|
159
|
+
if (path === '/roleIds' || path.startsWith('/roleIds/')) {
|
|
160
|
+
operation.path = `/roles${path.slice('/roleIds'.length)}`;
|
|
161
|
+
notes.push(`patch yolu '${path}' → '${operation.path}': yamalanan belge microservice_get'in OKUMA şeklidir (MicroserviceDto) ve rol listesi orada 'roles' [{ id, name }] alanıdır; 'roleIds' yalnız create/update GÖVDESİNDE vardır. Çevrilmeseydi replace çözülemez, add ise platformda sessizce yutulurdu.`);
|
|
162
|
+
}
|
|
163
|
+
const targetPath = operation.path;
|
|
164
|
+
if (targetPath === '/roles' && Array.isArray(operation.value)) {
|
|
165
|
+
const bare = operation.value.filter(entry => typeof entry === 'string');
|
|
166
|
+
if (bare.length > 0) {
|
|
167
|
+
operation.value = operation.value.map(entry => typeof entry === 'string' ? { id: entry } : entry);
|
|
168
|
+
notes.push(`'${targetPath}' değerindeki ${bare.length} çıplak guid { id } nesnesine sarıldı — rol listesi id/ad çiftleri taşır, 'name' isteğe bağlıdır (platform yalnız id okur).`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (targetPath.endsWith('/-') && typeof operation.value === 'string') {
|
|
172
|
+
operation.value = { id: operation.value };
|
|
173
|
+
notes.push(`'${targetPath}' değeri { id } nesnesine sarıldı — listeye eklenen öğe rol kaydıdır, çıplak guid değil.`);
|
|
174
|
+
}
|
|
175
|
+
// `roles` null gelirse (platform boş listeyi null serileştirebiliyor) diziye ekleme yolu
|
|
176
|
+
// çözülemez: tek öğelik tam dizi yazmaya katlanır, yoksa istek gitmeden düşerdi.
|
|
177
|
+
if (!rolesIsArray && targetPath === '/roles/-') {
|
|
178
|
+
operation.op = 'add';
|
|
179
|
+
operation.path = '/roles';
|
|
180
|
+
operation.value = [operation.value];
|
|
181
|
+
notes.push("okunan kayıtta 'roles' dizi değil (null) — '/roles/-' eklemesi tek öğelik tam dizi yazmaya ('add /roles') katlandı.");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return notes;
|
|
185
|
+
}
|
|
143
186
|
export function withNormalization(payload, fixes) {
|
|
144
187
|
if (fixes.length > 0) {
|
|
145
188
|
logStep('düzeltme', `gateway ${fixes.length} otomatik düzeltme uyguladı — gövdeye istek gönderilmeden ÖNCE işlendi, yanıta gatewayNormalized olarak eklenir`, fixes);
|
|
@@ -1998,32 +2041,76 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
|
|
|
1998
2041
|
if (op.op !== 'set_event' && op.op !== 'remove_event')
|
|
1999
2042
|
continue;
|
|
2000
2043
|
const eventName = String(op.event ?? '');
|
|
2044
|
+
const eventValue = typeof op.eventValue === 'string' ? op.eventValue : undefined;
|
|
2045
|
+
const boundTo = (events) => asObjects(events)
|
|
2046
|
+
.filter(event => String(event.name ?? '') === eventName && typeof event.value === 'string' && event.value !== '');
|
|
2047
|
+
// Bir olaya birden fazla akış bağlı olabilir. Değersiz `remove_event` o durumda platformda
|
|
2048
|
+
// `ambiguous-event` ile düşer ve hiçbir bağ kalkmaz; değerli olan yalnız o akışı çözer.
|
|
2049
|
+
const removedBy = (named) => eventValue === undefined
|
|
2050
|
+
? (named.length === 1 ? named : [])
|
|
2051
|
+
: named.filter(event => event.value === eventValue);
|
|
2001
2052
|
// `remove_event target:'page'` düğümü değil barındırıcının düzey olaylarını hedefler
|
|
2002
2053
|
// (`PageSchemaMicroOpApplier.PageTarget`); düğüm aramasına düşseydi plan boş kalır ve yalnız
|
|
2003
2054
|
// oraya bağlı akış kapıdan geçip kalıcı silinirdi. Düzey olayı olmayan widget'ta applier da
|
|
2004
2055
|
// düğüm aramasına düşer.
|
|
2005
2056
|
if (op.op === 'remove_event' && target === 'page' && Array.isArray(pageEvents)) {
|
|
2006
2057
|
const hostEvents = asObjects(pageEvents);
|
|
2007
|
-
const removed =
|
|
2008
|
-
|
|
2009
|
-
continue;
|
|
2010
|
-
const surviving = new Set(hostEvents.filter(event => event !== removed && typeof event.value === 'string').map(event => String(event.value)));
|
|
2058
|
+
const removed = removedBy(boundTo(hostEvents));
|
|
2059
|
+
const surviving = new Set(hostEvents.filter(event => !removed.includes(event) && typeof event.value === 'string').map(event => String(event.value)));
|
|
2011
2060
|
collectFlowRefsExcept(nodes, '', surviving);
|
|
2012
|
-
|
|
2013
|
-
|
|
2061
|
+
const flowIds = [...new Set(removed.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
|
|
2062
|
+
if (flowIds.length > 0)
|
|
2063
|
+
plan.push({ target, op: 'remove_event', flowIds });
|
|
2014
2064
|
continue;
|
|
2015
2065
|
}
|
|
2016
2066
|
const node = findNodeByElementId(nodes, target);
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2067
|
+
const named = boundTo(node?.events);
|
|
2068
|
+
// `set_event` adın BÜTÜN bağlarını tek kayda indirir; yeni değerden farklı her akış bağını kaybeder.
|
|
2069
|
+
const losing = op.op === 'set_event' ? named.filter(event => event.value !== eventValue) : removedBy(named);
|
|
2070
|
+
if (losing.length === 0)
|
|
2020
2071
|
continue;
|
|
2021
2072
|
const surviving = survivingRefs(target, eventName);
|
|
2022
|
-
|
|
2023
|
-
|
|
2073
|
+
for (const event of named) {
|
|
2074
|
+
if (!losing.includes(event))
|
|
2075
|
+
surviving.add(String(event.value));
|
|
2076
|
+
}
|
|
2077
|
+
const flowIds = [...new Set(losing.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
|
|
2078
|
+
if (flowIds.length > 0)
|
|
2079
|
+
plan.push({ target, op: String(op.op), flowIds });
|
|
2024
2080
|
}
|
|
2025
2081
|
return plan;
|
|
2026
2082
|
}
|
|
2083
|
+
/**
|
|
2084
|
+
* PAGE-034 — dolu bir olaya farklı akışla `set_event` gelmesi. `set_event` adın bütün bağlarını
|
|
2085
|
+
* TEK kayda indirir, yani önceki akış bağını sessizce kaybeder; akış başka yerden de
|
|
2086
|
+
* referanslıysa `orphanedFlowPlan` da susar ve kayıp hiç görünmez (maddelerim 4: makale önerisi
|
|
2087
|
+
* akışı, akıllı öneri bağlanınca çalışmaz oldu). Olaya ikinci akış eklemek `add_event`'tir; bilerek
|
|
2088
|
+
* değiştirmek için op'a `replaceBoundFlows: true` verilir. Okunan ağaçta olmayan hedef denetlenmez.
|
|
2089
|
+
*/
|
|
2090
|
+
export function boundEventOps(schema, ops) {
|
|
2091
|
+
const nodes = asObjects(schema);
|
|
2092
|
+
return asObjects(ops)
|
|
2093
|
+
.filter(op => op.op === 'set_event' && op.replaceBoundFlows !== true)
|
|
2094
|
+
.flatMap(op => {
|
|
2095
|
+
const target = String(op.target ?? '');
|
|
2096
|
+
const event = String(op.event ?? '');
|
|
2097
|
+
const eventValue = typeof op.eventValue === 'string' ? op.eventValue : '';
|
|
2098
|
+
const bound = asObjects(findNodeByElementId(nodes, target)?.events)
|
|
2099
|
+
.filter(entry => String(entry.name ?? '') === event && typeof entry.value === 'string' && entry.value !== '')
|
|
2100
|
+
.map(entry => String(entry.value));
|
|
2101
|
+
const replaced = [...new Set(bound)].filter(flowId => flowId !== eventValue);
|
|
2102
|
+
if (replaced.length === 0)
|
|
2103
|
+
return [];
|
|
2104
|
+
return [`PAGE-034: '${target}' komponentinin '${event}' olayına zaten bağlı akış var (${replaced.join(', ')}). set_event olayın BÜTÜN bağlarını tek akışa indirir ve bu akışlar olay tetiklenince artık çalışmaz. `
|
|
2105
|
+
+ `Olaya bir akış daha eklemek için { op: 'add_event', target: '${target}', event: '${event}', eventValue: '${eventValue}' } kullanın — bağlı akışların hepsi paralel koşar. `
|
|
2106
|
+
+ `Mevcut bağı bilerek değiştirmek istiyorsanız aynı op'a replaceBoundFlows: true ekleyin.`];
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
/** Gateway'e ait op alanları platforma gitmez. */
|
|
2110
|
+
export function stripGatewayEventFields(ops) {
|
|
2111
|
+
for (const op of ops)
|
|
2112
|
+
delete op.replaceBoundFlows;
|
|
2113
|
+
}
|
|
2027
2114
|
export function orphanedFlowMessage(plan) {
|
|
2028
2115
|
const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
|
|
2029
2116
|
return `İstek gönderilmedi: bu yama ${plan.reduce((total, entry) => total + entry.flowIds.length, 0)} akışı sayfada referanssız bırakıyor ve platform referanssız akışı KALICI siler — ${lines.join(' · ')}. `
|
|
@@ -44,6 +44,9 @@ export function microserviceOutline(definition) {
|
|
|
44
44
|
url: definition.url,
|
|
45
45
|
workflowId: definition.workflowId ?? null,
|
|
46
46
|
queryId: definition.queryId ?? null,
|
|
47
|
+
// S-2: rol listesi okuma şeklinde `roles`, yazma gövdesinde `roleIds` — outline yazma adını
|
|
48
|
+
// basar ki patch'ten sonra "rol tuttu mu" sorusu ikinci bir microservice_get istemesin.
|
|
49
|
+
roleIds: asArray(definition.roles).map(role => role.id),
|
|
47
50
|
sourceParameters: {
|
|
48
51
|
operation: sourceParameters.operation,
|
|
49
52
|
entityId: sourceParameters.entityId,
|
|
@@ -55,9 +58,10 @@ export function microserviceOutline(definition) {
|
|
|
55
58
|
filterCount: asArray(sourceParameters.filters).length
|
|
56
59
|
},
|
|
57
60
|
view: 'outline',
|
|
58
|
-
hint: "Parça için view:'part', part:'sourceParameters.joins' gibi; değişiklik için microservice_patch."
|
|
61
|
+
hint: "Parça için view:'part', part:'sourceParameters.joins' gibi; değişiklik için microservice_patch. Rol listesi patch belgesinde '/roles' [{ id, name }] yolundadır."
|
|
59
62
|
};
|
|
60
63
|
}
|
|
64
|
+
const enumOutlineValueLimit = 30;
|
|
61
65
|
export function entityOutline(definition) {
|
|
62
66
|
return {
|
|
63
67
|
id: definition.id,
|
|
@@ -75,7 +79,16 @@ export function entityOutline(definition) {
|
|
|
75
79
|
nullable: field.nullable,
|
|
76
80
|
relationType: field.relationType ?? 'none',
|
|
77
81
|
relationId: field.relationId ?? null,
|
|
78
|
-
isLocalized: Boolean(field.isLocalized)
|
|
82
|
+
isLocalized: Boolean(field.isLocalized),
|
|
83
|
+
// S-1: enum'a tek op'la yazılan değerin kanıtı outline'da dursun — aksi hâlde `entity_patch`
|
|
84
|
+
// yanıtı "liste gitti mi" sorusuna cevap vermiyor ve ajan tam gövdeye dönüyor. Bütçe K3:
|
|
85
|
+
// ilk 30 değer; `icon`/`labels` outline'a girmez, sayı toplamı söyler.
|
|
86
|
+
...(asArray(field.valueOptions).length > 0
|
|
87
|
+
? {
|
|
88
|
+
values: asArray(field.valueOptions).slice(0, enumOutlineValueLimit).map(option => option.value),
|
|
89
|
+
valueOptionCount: asArray(field.valueOptions).length
|
|
90
|
+
}
|
|
91
|
+
: {})
|
|
79
92
|
})),
|
|
80
93
|
indexCount: asArray(definition.indexes).length,
|
|
81
94
|
// Index'ler alanlardan çok daha az sayıdadır; tam projeksiyon outline'ı şişirmez ve yazılan
|
|
@@ -91,7 +104,7 @@ export function entityOutline(definition) {
|
|
|
91
104
|
}
|
|
92
105
|
: {}),
|
|
93
106
|
view: 'outline',
|
|
94
|
-
hint: "Bir alanın tam gövdesi için view:'part', part:'fields[slug=<slug>]'; değişiklik için entity_patch. isLocalized alanlar için platform_guide('entity') §'Çoklu dilli alan'."
|
|
107
|
+
hint: "Bir alanın tam gövdesi için view:'part', part:'fields[slug=<slug>]'; değişiklik için entity_patch. Enum'a tek değer: entity_patch add '/fields[slug=<slug>]/valueOptions/-'. isLocalized alanlar için platform_guide('entity') §'Çoklu dilli alan'."
|
|
95
108
|
};
|
|
96
109
|
}
|
|
97
110
|
const identityKeys = ['id', 'slug', 'name', 'elementId', 'stepId'];
|
package/dist/knowledge/rules.js
CHANGED
|
@@ -2172,7 +2172,7 @@ export const policyRules = [
|
|
|
2172
2172
|
id: 'WF-018',
|
|
2173
2173
|
module: 'workflow',
|
|
2174
2174
|
appliesTo: ['create', 'update', 'preview'],
|
|
2175
|
-
sourceRef: "LoopStep.cs:41 çocuk kuralı rule.Children.FirstOrDefault() ile okur ama API sözleşmesinde alan TEKİLdir (StepRuleDto.StepRule, WorkflowDto.cs:498). 'children' gönderilirse Children boş kalır, çocuk adım null rule ile koşar ve loop NullReferenceException verir — hata arrayExpression'ı işaret ettiği için yanlış yere baktırır. Ölçüm: anket-admin/anket-kaydet, 2026-08-19",
|
|
2175
|
+
sourceRef: "LoopStep.cs:41 çocuk kuralı rule.Children.FirstOrDefault() ile okur ama API sözleşmesinde alan TEKİLdir (StepRuleDto.StepRule, WorkflowDto.cs:498). 'children' gönderilirse Children boş kalır, çocuk adım null rule ile koşar ve loop NullReferenceException verir — hata arrayExpression'ı işaret ettiği için yanlış yere baktırır. Ölçüm: anket-admin/anket-kaydet, 2026-08-19. loopItem kapsamı 2026-09-16'da ölçüldü: table çocuğunun filtre ifadesinde çalışıyor, code çocuğunda tanımsız (LoopStep.cs:38 loop'un kendi motoruna yazıyor, code adımı ayrı motor kuruyor)",
|
|
2176
2176
|
check: (definition, context) => {
|
|
2177
2177
|
const body = workflowBody(definition, context.workflowKind);
|
|
2178
2178
|
return workflowSteps(body)
|
|
@@ -2185,7 +2185,7 @@ export const policyRules = [
|
|
|
2185
2185
|
findings.push({
|
|
2186
2186
|
ruleId: 'WF-018',
|
|
2187
2187
|
severity: 'block',
|
|
2188
|
-
message: `'${stepLabel(step)}' loop step'inde rule.stepRule yok${hasChildren ? " — ama rule.children dolu: çocuk kural alanının adı 'children' DEĞİL 'stepRule'dur (tekil)" : ''}. Çocuksuz loop çalışma anında NullReferenceException verir ve hata mesajı arrayExpression'ı suçlar. Çocuğun kendi rule gövdesini rule.stepRule'a koyun;
|
|
2188
|
+
message: `'${stepLabel(step)}' loop step'inde rule.stepRule yok${hasChildren ? " — ama rule.children dolu: çocuk kural alanının adı 'children' DEĞİL 'stepRule'dur (tekil)" : ''}. Çocuksuz loop çalışma anında NullReferenceException verir ve hata mesajı arrayExpression'ı suçlar. Çocuğun kendi rule gövdesini rule.stepRule'a koyun; loop'un çıktısı [{ item, result }] dizisidir. Geçerli öğeye 'loopItem' ile erişilir AMA yalnız loop'un kendi değerlendirdiği ifadelerde (table çocuğunun fields[].value / filters[].value gibi) — 'code' çocuğunda loopItem TANIMSIZDIR ve adım "loopItem is not defined" ile düşer (ölçüldü 2026-09-16); her öğe için hesabı arrayExpression'da önden yapın.`,
|
|
2189
2189
|
guide: 'ngapps://guides/workflow'
|
|
2190
2190
|
});
|
|
2191
2191
|
}
|
|
@@ -2845,6 +2845,74 @@ export const policyRules = [
|
|
|
2845
2845
|
guide: 'ngapps://guides/entity'
|
|
2846
2846
|
}))
|
|
2847
2847
|
},
|
|
2848
|
+
/**
|
|
2849
|
+
* WF-033 — `http` adımının sözleşmesi. Gövde ayrı bir alan değildir: `payloadExpression` tek
|
|
2850
|
+
* ifadeyle `{ header, body, queryString }` ÜÇÜNÜ birden üretmek zorundadır, çünkü yürütücü
|
|
2851
|
+
* üçüne de `dynamic` üye erişimiyle bakar (HttpStep.cs:32,42,59) ve eksik anahtar adımı
|
|
2852
|
+
* `RuntimeBinderException` ile düşürür — ölçüldü 2026-09-16, mesaj birebir
|
|
2853
|
+
* "'System.Dynamic.ExpandoObject' does not contain a definition for 'queryString'".
|
|
2854
|
+
*
|
|
2855
|
+
* Anahtar denetimi YALNIZ nesne literalinde yapılır (`(`/`{` ile başlayan ifade): bir fonksiyon
|
|
2856
|
+
* çağrısı ya da değişken döndüren ifadenin içini statik olarak göremeyiz ve orada susmak
|
|
2857
|
+
* yanlış pozitif üretmemekten iyidir (AGENTS §10).
|
|
2858
|
+
*/
|
|
2859
|
+
{
|
|
2860
|
+
id: 'WF-033',
|
|
2861
|
+
module: 'workflow',
|
|
2862
|
+
appliesTo: ['create', 'update', 'preview'],
|
|
2863
|
+
sourceRef: "HttpStep.cs:26-32,42,59,63-85 — url rule.UrlExpression'dan (JS ifadesi → string), method rule.HttpMethod'dan (HttpMethod.Parse), gövde rule.PayloadExpression'ın body/header/queryString anahtarlarından okunur; eksik anahtar RuntimeBinderException. contentType boşsa gövde body.ToString() ile gider. Ölçüm 2026-09-16, test/development: üç anahtarlı çağrı 200, queryString'siz çağrı RuntimeBinderException",
|
|
2864
|
+
check: definition => {
|
|
2865
|
+
const httpMethods = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
|
|
2866
|
+
const bodyCarryingMethods = new Set(['post', 'put', 'patch']);
|
|
2867
|
+
return workflowSteps(workflowBody(definition))
|
|
2868
|
+
.filter(step => step.type === 'http')
|
|
2869
|
+
.flatMap((step) => {
|
|
2870
|
+
const rule = (step.rule ?? {});
|
|
2871
|
+
const findings = [];
|
|
2872
|
+
const payload = String(rule.payloadExpression ?? '').trim();
|
|
2873
|
+
if (!hasText(rule.urlExpression)) {
|
|
2874
|
+
findings.push({
|
|
2875
|
+
ruleId: 'WF-033',
|
|
2876
|
+
severity: 'block',
|
|
2877
|
+
message: `'${stepLabel(step)}' http adımında rule.urlExpression yok. Adres bir JS İFADESİDİR, düz metin değil: sabit adresi tırnak içinde yazın ("'https://api.example.com/v1/rates'"), dinamik adresi ifadeyle kurun ("'https://api.example.com/orders/' + input.orderId"). Tırnaksız yazarsanız JS onu değişken sanır ve adım "x is not defined" ile düşer.`,
|
|
2878
|
+
guide: 'ngapps://guides/workflow'
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
if (!httpMethods.has(String(rule.httpMethod ?? '').trim().toLowerCase())) {
|
|
2882
|
+
findings.push({
|
|
2883
|
+
ruleId: 'WF-033',
|
|
2884
|
+
severity: 'block',
|
|
2885
|
+
message: `'${stepLabel(step)}' http adımında rule.httpMethod ${hasText(rule.httpMethod) ? `'${String(rule.httpMethod)}' — tanınmıyor` : 'yok'}. Platform değeri HttpMethod.Parse ile çözer; geçerli değerler: ${[...httpMethods].map(method => method.toUpperCase()).join(' · ')}.`,
|
|
2886
|
+
guide: 'ngapps://guides/workflow'
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
if (payload.startsWith('(') || payload.startsWith('{')) {
|
|
2890
|
+
const missing = ['body', 'header', 'queryString']
|
|
2891
|
+
.filter(key => !new RegExp(`['"]?${key}['"]?\\s*:`).test(payload));
|
|
2892
|
+
if (missing.length > 0) {
|
|
2893
|
+
findings.push({
|
|
2894
|
+
ruleId: 'WF-033',
|
|
2895
|
+
severity: 'block',
|
|
2896
|
+
message: `'${stepLabel(step)}' http adımının payloadExpression'ında ${missing.join(', ')} anahtarı yok. Gövde AYRI BİR ALAN DEĞİLDİR: bu tek ifade body, header ve queryString ÜÇÜNÜ BİRDEN taşımak zorundadır — boş olanı da yazın: "({ body: {}, header: {}, queryString: {} })". Eksik anahtar çalışma anında RuntimeBinderException verir ("'System.Dynamic.ExpandoObject' does not contain a definition for '${missing[0]}'") ve adım fail dalına düşer. Örnek: ngapps://examples/workflow-http-step`,
|
|
2897
|
+
guide: 'ngapps://guides/workflow'
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
// Gövde taşıyan metotlarda contentType'ın boş olması sessiz bir bozulmadır. Gövdenin
|
|
2902
|
+
// DOLU olup olmadığını ifade metninden ayrıştırmaya çalışmıyoruz (K7): metot yeterli
|
|
2903
|
+
// ve kesin bir sinyal, gövde ayrıştırması yanlış pozitif üretir.
|
|
2904
|
+
if (!hasText(rule.contentType) && bodyCarryingMethods.has(String(rule.httpMethod ?? '').trim().toLowerCase())) {
|
|
2905
|
+
findings.push({
|
|
2906
|
+
ruleId: 'WF-033',
|
|
2907
|
+
severity: 'warn',
|
|
2908
|
+
message: `'${stepLabel(step)}' http adımı ${String(rule.httpMethod).toUpperCase()} ile gövde gönderiyor ama rule.contentType boş: platform gövdeyi body.ToString() ile yollar ve nesne gövdesi tip adının kendisine dönüşür (HttpStep.cs:63-85). JSON gönderiyorsanız contentType: 'application/json' yazın.`,
|
|
2909
|
+
guide: 'ngapps://guides/workflow'
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
return findings;
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
},
|
|
2848
2916
|
{
|
|
2849
2917
|
id: 'MW-003',
|
|
2850
2918
|
module: 'workflow',
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"scope": "Yalnız source:'studio'. application: her sayfada (tarayıcı). page: yalnız o sayfanın tarayıcı bağlamında, pageId zorunlu. Ad uygulama içinde kapsamlar arası benzersizdir (K3); workflow adı tenant içinde benzersizdir. Aynı ad iki kaynakta yan yana olabilir.",
|
|
11
11
|
"isAsync": "Yalnız studio'da anlamlı; false bırakın: true olsaydı {{ }} bağlaması '[object Promise]' çizerdi. await gerekmeyen gövdede isAsync:true FN-004 uyarısı alır. workflow kaynağında true FN-004 block (motor senkron).",
|
|
12
12
|
"naming": "Ad JS tanımlayıcısı; ayrılmış sözcük olamaz. studio'da 'Functions' yasak (FN-001, block); workflow'da motor global adları (system, input, Now, crypto…) yasak (FN-001, block). Uygulama fonksiyonunda sayfaya özgü ad (komponent adı, state.page), workflow fonksiyonunda adım adı kullanmayın — çağrıldığı yerde yoksa 'X is not defined'.",
|
|
13
|
-
"source": "workflow: SUNUCU, tenant geneli, applicationId/scope/pageId VERİLMEZ, isAsync olamaz, çıplak adla çağrılır. studio: TARAYICI, applicationId + scope zorunlu (application: her sayfada; page: yalnız o sayfa, pageId zorunlu), Functions.<ad>() ile çağrılır. Kaynak sonradan değişmez; sunucu ifadesinde Functions.<ad>( → WF-030 block."
|
|
13
|
+
"source": "workflow: SUNUCU, tenant geneli, applicationId/scope/pageId VERİLMEZ, isAsync olamaz, çıplak adla çağrılır. studio: TARAYICI, applicationId + scope zorunlu (application: her sayfada; page: yalnız o sayfa, pageId zorunlu), Functions.<ad>() ile çağrılır. Kaynak sonradan değişmez; sunucu ifadesinde Functions.<ad>( → WF-030 block.",
|
|
14
|
+
"update": "function_update TAM gövde PUT'lar ama gateway mevcut kaydı okuyup vermediğin alanları oradan doldurur: gövdeyi değiştirmek için { functionId, body } yeter — ayrı bir patch aracı YOKTUR, fonksiyonun tek büyük alanı gövdedir. source/applicationId/scope/pageId DEĞİŞMEZ. Yanıt dependents (kullanan flow/workflow adları) ve signatureChange (ad ya da parametre ad/sırası) taşır; ikisi de doluysa warnings çağıranların etkileneceğini söyler ve güncelleme YİNE yapılır (K12). Adı değiştirirsen platform çağıranları aynı işlemde yeni ada taşır ve renamedReferences kaç kaydın yazıldığını söyler."
|
|
14
15
|
},
|
|
15
16
|
"functionCreateArgs": {
|
|
16
17
|
"source": "studio",
|
|
@@ -55,6 +56,10 @@
|
|
|
55
56
|
],
|
|
56
57
|
"body": "return fiyat * adet;"
|
|
57
58
|
},
|
|
59
|
+
"functionUpdateArgs": {
|
|
60
|
+
"functionId": "<function_list id>",
|
|
61
|
+
"body": "return fiyat * adet * 1.2;"
|
|
62
|
+
},
|
|
58
63
|
"usage": {
|
|
59
64
|
"propBinding": {
|
|
60
65
|
"op": "set_binding",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": false,
|
|
4
|
+
"note": "workflow adımı: bir akışın içinden BAŞKA bir kayıtlı akışı çağırır (alt akış). İki şey burada tuzaktır ve ikisi de ölçüldü: hedef ADLA değil rule.workflowId ile (GUID) seçilir, ve alt akışın girdisi YALNIZ rule.expression ile bağlanır — payloadExpression / customParameters kaydedilir ama SESSİZCE yok sayılır. Bu akış lokal stack'te koşturuldu (test projesi, development, 2026-09-16): iki yol da ayrı ayrı ölçüldü.",
|
|
5
|
+
"harvestedFrom": "canlı ölçüm 2026-09-16 (workflow_preview, test/development) + kod: zeyrek-backend WorkflowStep.cs:22-49, StepRule.cs:23,79-80"
|
|
6
|
+
},
|
|
7
|
+
"_rules": {
|
|
8
|
+
"hedef": "rule.workflowId bir GUID'dir — slug ya da ad DEĞİL. Id'yi workflow_list ile alın. Alt akış aynı application içinde kayıtlı olmalıdır (preview'daki kaydedilmemiş bir akış çağrılamaz).",
|
|
9
|
+
"girdi": "GİRDİ YALNIZ rule.expression İLE BAĞLANIR ve hedefin parameter DTO'sunun şeklini üretmelidir: \"({ orderId: load_order.data.id })\". Yürütücü ifadeyi yalnız hedef akışın parameterDtoId'si VARSA değerlendirir (WorkflowStep.cs:27-31).",
|
|
10
|
+
"sessizDusme": "ÖLÇÜLDÜ 2026-09-16: aynı adıma payloadExpression yazıldığında alan kaydedilir, istek 200 döner, adım hatasız görünür — ama alt akış GİRDİSİZ koşar. Preview'da kanıtı şudur: adımın input satırı yalnız { source: 'workflow', member: 'parameter' } olur, expression ve value alanları HİÇ GELMEZ. customParameters da aynı şekilde yok sayılır (36/S-29).",
|
|
11
|
+
"adAyniAlanUcAnlam": "rule.expression alanı üç adımda ÜÇ FARKLI şey demektir ve adı yanıltır: workflow adımında GİRDİ EŞLEMESİ, code adımında GÖVDE, query adımında PARAMETRE BAĞLAMA. Bir code adımından kopyalayıp workflow adımına yapıştırmayın.",
|
|
12
|
+
"cikti": "Alt akışın response gövdesi bu adımın çıktısı olur: <adım>.data. Ayrı bir 'çıktı değişkeni' alanı yoktur. İfade bağlamında adım { data, meta, error } şeklindedir (http adımıyla aynı sarmal).",
|
|
13
|
+
"ciktiyiDtoBicer": "ÖLÇÜLDÜ: alt akışın returnDtoId'si çıktıyı BİÇER. Alt akış bodyExpression'ında DTO'da olmayan bir anahtar dönerse o anahtar yanıttan sessizce düşer — çağıranda eksik alan görürseniz önce alt akışın return DTO'suna bakın.",
|
|
14
|
+
"hata": "Alt akış patlarsa bu adım fail dalına gider ve alt akışın son hatalı adımının istisnası taşınır (WorkflowStep.cs:49).",
|
|
15
|
+
"preview": "workflow_preview alt akışı children[] içinde kind:'subWorkflow' olarak TAM adım iziyle döner — girdinin gerçekten bağlanıp bağlanmadığını oradan görürsünüz."
|
|
16
|
+
},
|
|
17
|
+
"calleeDefinition": {
|
|
18
|
+
"name": "Fatura Oluştur",
|
|
19
|
+
"slug": "fatura-olustur",
|
|
20
|
+
"description": "Sipariş id'siyle fatura kaydı açar ve fatura numarasını döner.",
|
|
21
|
+
"type": "workflow",
|
|
22
|
+
"parameterDtoId": "<girdi DTO id: { orderId: string }>",
|
|
23
|
+
"parameterBehavior": "object",
|
|
24
|
+
"returnDtoId": "<çıktı DTO id: { faturaNo: string }>",
|
|
25
|
+
"returnBehavior": "object",
|
|
26
|
+
"steps": [
|
|
27
|
+
{
|
|
28
|
+
"id": "<GUID-C1>",
|
|
29
|
+
"name": "Start",
|
|
30
|
+
"type": "start",
|
|
31
|
+
"description": "Alt akışı başlatır.",
|
|
32
|
+
"branches": [{ "id": "<GUID-C2>", "name": "start", "type": "success", "nextStepId": "<GUID-C3>" }]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "<GUID-C3>",
|
|
36
|
+
"name": "Response",
|
|
37
|
+
"type": "response",
|
|
38
|
+
"description": "Üretilen fatura numarasını döner; çağıran bunu <adım>.data ile okur.",
|
|
39
|
+
"rule": {
|
|
40
|
+
"stepType": "response",
|
|
41
|
+
"bodyExpression": "({ faturaNo: 'FTR-' + input.orderId })",
|
|
42
|
+
"httpStatusCode": 200
|
|
43
|
+
},
|
|
44
|
+
"branches": []
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"definition": {
|
|
49
|
+
"name": "Sipariş Kapat",
|
|
50
|
+
"slug": "siparis-kapat",
|
|
51
|
+
"description": "Siparişi kapatır ve fatura akışını çağırarak fatura numarasını döner.",
|
|
52
|
+
"type": "workflow",
|
|
53
|
+
"parameterDtoId": "<girdi DTO id: { orderId: string }>",
|
|
54
|
+
"parameterBehavior": "object",
|
|
55
|
+
"returnDtoId": "<çıktı DTO id: { faturaNo: string }>",
|
|
56
|
+
"returnBehavior": "object",
|
|
57
|
+
"steps": [
|
|
58
|
+
{
|
|
59
|
+
"id": "<GUID-1 üret: crypto.randomUUID()>",
|
|
60
|
+
"name": "Start",
|
|
61
|
+
"type": "start",
|
|
62
|
+
"description": "Akışı başlatır.",
|
|
63
|
+
"branches": [
|
|
64
|
+
{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" },
|
|
65
|
+
{ "id": "<GUID-9>", "name": "global-error", "type": "global-error", "nextStepId": "<GUID-8>" }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "<GUID-3>",
|
|
70
|
+
"name": "fatura_akisi",
|
|
71
|
+
"type": "workflow",
|
|
72
|
+
"description": "Fatura oluşturma akışını çağırır; sipariş id'sini girdi olarak geçer.",
|
|
73
|
+
"rule": {
|
|
74
|
+
"stepType": "workflow",
|
|
75
|
+
"workflowId": "<workflow_list'ten alınan hedef akış id'si>",
|
|
76
|
+
"expression": "({ orderId: input.orderId })"
|
|
77
|
+
},
|
|
78
|
+
"branches": [
|
|
79
|
+
{ "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-6>" },
|
|
80
|
+
{ "id": "<GUID-5>", "name": "fail", "type": "fail", "nextStepId": null }
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "<GUID-6>",
|
|
85
|
+
"name": "Response",
|
|
86
|
+
"type": "response",
|
|
87
|
+
"description": "Alt akışın ürettiği fatura numarasını 200 ile döner.",
|
|
88
|
+
"rule": {
|
|
89
|
+
"stepType": "response",
|
|
90
|
+
"bodyExpression": "({ faturaNo: fatura_akisi.data.faturaNo })",
|
|
91
|
+
"httpStatusCode": 200
|
|
92
|
+
},
|
|
93
|
+
"branches": []
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "<GUID-8>",
|
|
97
|
+
"name": "onError",
|
|
98
|
+
"type": "response",
|
|
99
|
+
"description": "Akış patlarsa hata kodunu ve mesajını 500 ile döner.",
|
|
100
|
+
"rule": {
|
|
101
|
+
"stepType": "response",
|
|
102
|
+
"bodyExpression": "({ code: error.code, message: error.message })",
|
|
103
|
+
"httpStatusCode": 500
|
|
104
|
+
},
|
|
105
|
+
"branches": []
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"usage": {
|
|
110
|
+
"kurulumSirasi": "1) Alt akışın girdi/çıktı DTO'larını aç, 2) alt akışı workflow_create ile KAYDET, 3) dönen id'yi çağıranın rule.workflowId alanına yaz. Kaydedilmemiş bir akış çağrılamaz.",
|
|
111
|
+
"girdiKontrolu": "workflow_preview çıktısında çağıran adımın input satırına bakın: expression ve value doluysa girdi bağlanmıştır. Yalnız { source: 'workflow', member: 'parameter' } görüyorsanız girdiniz YOK SAYILDI — büyük olasılıkla payloadExpression yazdınız, rule.expression'a taşıyın.",
|
|
112
|
+
"yanlisYol": "YANLIŞ: rule: { stepType: 'workflow', workflowId: '…', payloadExpression: \"({ orderId: 1 })\" } — kaydedilir, 200 döner, alt akış girdisiz koşar. DOĞRU: aynı ifadeyi rule.expression'a koyun.",
|
|
113
|
+
"dogrulama": "workflow_preview; yanıtta children[] içinde kind:'subWorkflow' girişinin input alanı beklediğiniz nesne olmalı."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": false,
|
|
4
|
+
"note": "http adımı: dış (ya da platformun kendi) HTTP ucunu çağırır. Adımın üç ayrı alanı vardır — urlExpression (JS ifadesi, STRING üretir), httpMethod, contentType — ve gövde AYRI BİR ALAN DEĞİLDİR: payloadExpression tek bir ifadeyle { header, body, queryString } ÜÇÜNÜ BİRDEN taşımak zorundadır. Bu akış lokal stack'te koşturuldu (test projesi, development, 2026-09-16): platformun kendi table ucu çağrıldı, 200 döndü ve çıktı şekli ölçüldü.",
|
|
5
|
+
"harvestedFrom": "canlı ölçüm 2026-09-16 (workflow_preview, test/development) + kod: zeyrek-backend HttpStep.cs:26-99,143-158, StepRule.cs:47-51"
|
|
6
|
+
},
|
|
7
|
+
"_rules": {
|
|
8
|
+
"payloadExpression": "ÜÇ ANAHTAR DA ZORUNLUDUR, boş olsalar bile: ({ body: {}, header: {}, queryString: {} }). Yürütücü üçüne de C# dynamic üye erişimiyle bakar (HttpStep.cs:32,42,59), eksik anahtar adımı RuntimeBinderException ile düşürür. Ölçülen mesaj birebir: \"'System.Dynamic.ExpandoObject' does not contain a definition for 'queryString'\". Bu bir doğrulama hatası değildir; adım fail dalına gider.",
|
|
9
|
+
"urlExpression": "Bu bir İFADEDİR, düz metin değil: sabit adres tırnak içinde yazılır ('https://api.example.com/v1/rates'), dinamik adres ifadeyle kurulur ('https://api.example.com/orders/' + input.orderId). Tırnaksız yazarsan JS onu değişken sanır ve adım 'x is not defined' ile düşer.",
|
|
10
|
+
"output": "ÖLÇÜLDÜ 2026-09-16 — ŞEMAYLA ÇELİŞİR. workflow_step_schema('http') returnScheme'i kök seviyede { httpStatusCode, data } ilan ediyor, AMA ifade bağlamında adım { data, meta: { elapsedTime, httpStatusCode, totalCount }, error } şeklindedir: gövde <adım>.data, durum kodu <adım>.meta.httpStatusCode. <adım>.httpStatusCode UNDEFINED döner. Şemaya göre yazarsan sessizce boş değer alırsın.",
|
|
11
|
+
"responseParsing": "Yanıt gövdesi JSON'a benziyorsa ayrıştırılır, benzemiyorsa SESSİZCE boş nesne ({}) olur (HttpStep.cs:92) — XML dönen uçtan veri alamazsın ve hata da almazsın (36/W-2, açık bulgu). Ham metne erişmenin yolu yoktur.",
|
|
12
|
+
"contentType": "KOD OKUMASINDAN (bu koşuda ölçülmedi): 'application/json' → JsonContent; 'application/x-www-form-urlencoded' → düz anahtar/değer; BAŞKA BİR DEĞER ya da BOŞ → gövde body.ToString() ile gider ve nesne gövdesi için bu, tip adının kendisidir (HttpStep.cs:63-85). POST/PUT'ta contentType'ı AÇIKÇA yazın.",
|
|
13
|
+
"stepName": "Adım çıktısına ADIM ADIYLA erişilir, o yüzden ad geçerli bir JS tanımlayıcısı olmalıdır. Boşluklu ad ('Call Roles') ifadede çağrılamaz; ölçümde call_roles is not defined ile düştü. snake_case kullanın.",
|
|
14
|
+
"authorization": "Token gerekiyorsa header anahtarına koyun: header: { Authorization: 'Bearer ' + system.token }. Adım platformun kendi kimliğini OTOMATİK taşımaz."
|
|
15
|
+
},
|
|
16
|
+
"definition": {
|
|
17
|
+
"name": "Kur Çek",
|
|
18
|
+
"slug": "kur-cek",
|
|
19
|
+
"description": "Dış servisten güncel kuru çeker ve gövdesini döner.",
|
|
20
|
+
"type": "workflow",
|
|
21
|
+
"returnDtoId": "<çıktı DTO id: { kur: number }>",
|
|
22
|
+
"returnBehavior": "object",
|
|
23
|
+
"steps": [
|
|
24
|
+
{
|
|
25
|
+
"id": "<GUID-1 üret: crypto.randomUUID()>",
|
|
26
|
+
"name": "Start",
|
|
27
|
+
"type": "start",
|
|
28
|
+
"description": "Akışı başlatır.",
|
|
29
|
+
"branches": [
|
|
30
|
+
{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" },
|
|
31
|
+
{ "id": "<GUID-9>", "name": "global-error", "type": "global-error", "nextStepId": "<GUID-8>" }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "<GUID-3>",
|
|
36
|
+
"name": "kur_servisi",
|
|
37
|
+
"type": "http",
|
|
38
|
+
"description": "Kur servisini çağırır; yanıtın gövdesi bir sonraki adıma geçer.",
|
|
39
|
+
"caution": "Dış bağımlılık: servis yavaşlarsa akış bekler, XML dönerse çıktı sessizce boş nesne olur.",
|
|
40
|
+
"rule": {
|
|
41
|
+
"stepType": "http",
|
|
42
|
+
"urlExpression": "'http://zeyrek-test.zeyrek.local/api/test/m/test/olcum-roller'",
|
|
43
|
+
"httpMethod": "GET",
|
|
44
|
+
"contentType": "application/json",
|
|
45
|
+
"payloadExpression": "({ body: {}, header: { 'X-Environment': 'development' }, queryString: {} })"
|
|
46
|
+
},
|
|
47
|
+
"branches": [
|
|
48
|
+
{ "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-6>" },
|
|
49
|
+
{ "id": "<GUID-5>", "name": "fail", "type": "fail", "nextStepId": null }
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "<GUID-6>",
|
|
54
|
+
"name": "Response",
|
|
55
|
+
"type": "response",
|
|
56
|
+
"description": "Çekilen gövdeyi ve HTTP durum kodunu 200 ile döner.",
|
|
57
|
+
"rule": {
|
|
58
|
+
"stepType": "response",
|
|
59
|
+
"bodyExpression": "({ veri: kur_servisi.data, durum: kur_servisi.meta.httpStatusCode })",
|
|
60
|
+
"httpStatusCode": 200
|
|
61
|
+
},
|
|
62
|
+
"branches": []
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "<GUID-8>",
|
|
66
|
+
"name": "onError",
|
|
67
|
+
"type": "response",
|
|
68
|
+
"description": "Akış patlarsa hata kodunu ve mesajını 500 ile döner.",
|
|
69
|
+
"rule": {
|
|
70
|
+
"stepType": "response",
|
|
71
|
+
"bodyExpression": "({ code: error.code, message: error.message })",
|
|
72
|
+
"httpStatusCode": 500
|
|
73
|
+
},
|
|
74
|
+
"branches": []
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"usage": {
|
|
79
|
+
"postGovdesi": "POST için: { stepType: 'http', urlExpression: \"'https://api.example.com/orders'\", httpMethod: 'POST', contentType: 'application/json', payloadExpression: \"({ body: { orderId: input.orderId, adet: 2 }, header: { Authorization: 'Bearer ' + system.token }, queryString: {} })\" }",
|
|
80
|
+
"queryStringKullanimi": "Sorgu parametresi payloadExpression'ın queryString anahtarına konur, URL'e elle eklenmez: queryString: { from: 'USD', to: 'TRY' }.",
|
|
81
|
+
"onlemliOkuma": "Gövdeyi <adım>.data ile, durum kodunu <adım>.meta.httpStatusCode ile okuyun; şemanın vaat ettiği <adım>.httpStatusCode undefined'dır (ölçüldü 2026-09-16).",
|
|
82
|
+
"dogrulama": "workflow_preview ile koşturun ve yanıttaki steps[] içinde http adımının input satırlarına bakın: source:'url' ve source:'payload' iki ayrı satır olarak görünür; hata varsa runtimeErrors alanı adımı adıyla sayar."
|
|
83
|
+
}
|
|
84
|
+
}
|