bpmnlint-plugin-camunda-compat 2.60.0 → 2.60.2
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/package.json
CHANGED
|
@@ -15,10 +15,11 @@ const { annotateRule } = require('../helper');
|
|
|
15
15
|
* or a part like `toolCallResult.statusCode`), a script task or called decision
|
|
16
16
|
* `resultVariable`, or a connector `resultVariable`/`resultExpression` header.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* A wrong-casing near-miss (`toolcallresult`, `TOOLCALLRESULT`, ...) is always
|
|
19
|
+
* reported on the element that wrote it, even when a sibling write already
|
|
20
|
+
* sets `toolCallResult` correctly — case-sensitive engines never read it.
|
|
21
|
+
* Other miswrites are reported individually per row only while no valid
|
|
22
|
+
* `toolCallResult` exists. When the flow writes none at all, the rule reports
|
|
22
23
|
* once on the entry activity, since there's no single offending element to
|
|
23
24
|
* point to (the agent gets no completion signal and may retry or hallucinate
|
|
24
25
|
* an outcome). Results written from arbitrary FEEL expressions are not
|
|
@@ -70,6 +71,19 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
// Miscased near-misses are reported even when a sibling already sets
|
|
75
|
+
// toolCallResult correctly. Casing variants never satisfy
|
|
76
|
+
// isToolCallResultChannel, so the overwrite check below ignores them.
|
|
77
|
+
for (const channel of channels) {
|
|
78
|
+
if (isToolCallResultCasingMismatch(channel)) {
|
|
79
|
+
reportErrors(channel.element, reporter, {
|
|
80
|
+
message: `Wrong casing "${getCasingMismatchText(channel)}": use toolCallResult (case-sensitive).`,
|
|
81
|
+
data: { type: ERROR_TYPES.AGENT_TOOL_OUTPUT_KEY_CASING_INVALID },
|
|
82
|
+
path: getChannelPath(channel),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
// toolCallResult is set somewhere, but assigning it more than once
|
|
74
88
|
// overwrites the earlier value. Part contributions (toolCallResult.part)
|
|
75
89
|
// and the context put(toolCallResult, ...) accumulation pattern are
|
|
@@ -267,7 +281,7 @@ function isToolCallResultChannel({ kind, value }) {
|
|
|
267
281
|
// case-insensitive name matching for fromAi().
|
|
268
282
|
function isToolCallResultCasingMismatch({ kind, value }) {
|
|
269
283
|
if (kind === 'resultExpression') {
|
|
270
|
-
return
|
|
284
|
+
return !!getResultExpressionCasingMismatch(value);
|
|
271
285
|
}
|
|
272
286
|
|
|
273
287
|
const lower = value.toLowerCase();
|
|
@@ -276,13 +290,21 @@ function isToolCallResultCasingMismatch({ kind, value }) {
|
|
|
276
290
|
return isCasingVariant && !isToolCallResultChannel({ kind, value });
|
|
277
291
|
}
|
|
278
292
|
|
|
293
|
+
// The first miscased toolCallResult token in a FEEL expression, or null when
|
|
294
|
+
// every occurrence is cased correctly. Tokens are judged individually, so a
|
|
295
|
+
// correct occurrence never excuses a miscased sibling in the same expression.
|
|
296
|
+
function getResultExpressionCasingMismatch(value) {
|
|
297
|
+
const tokens = value.match(/\btoolcallresult\b/gi) || [];
|
|
298
|
+
|
|
299
|
+
return tokens.find(token => token !== 'toolCallResult') || null;
|
|
300
|
+
}
|
|
301
|
+
|
|
279
302
|
// The mismatched word itself, not the whole expression, for resultExpression
|
|
280
303
|
// channels (a connector header value is a full FEEL expression, e.g.
|
|
281
304
|
// `={toolcallresult: response.body}`).
|
|
282
305
|
function getCasingMismatchText({ kind, value }) {
|
|
283
306
|
if (kind === 'resultExpression') {
|
|
284
|
-
|
|
285
|
-
return match ? match[0] : value;
|
|
307
|
+
return getResultExpressionCasingMismatch(value) || value;
|
|
286
308
|
}
|
|
287
309
|
|
|
288
310
|
return value;
|
|
@@ -15,7 +15,13 @@ const { reportErrors } = require('../utils/reporter');
|
|
|
15
15
|
|
|
16
16
|
const { skipInNonExecutableProcess } = require('../utils/rule');
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const { greaterOrEqual } = require('../utils/version');
|
|
19
|
+
|
|
20
|
+
// `camunda.secrets.<name>` only resolves on engines with this version or newer;
|
|
21
|
+
// before that, `{{secrets.<name>}}` remains the only working format
|
|
22
|
+
const CAMUNDA_SECRETS_FORMAT_ALLOWED_VERSION = '8.10';
|
|
23
|
+
|
|
24
|
+
module.exports = skipInNonExecutableProcess(function({ version }) {
|
|
19
25
|
function check(node, reporter) {
|
|
20
26
|
const errors = [
|
|
21
27
|
validateIoMapping,
|
|
@@ -33,66 +39,85 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
});
|
|
42
|
+
function validateIoMapping(node) {
|
|
43
|
+
const ioMapping = findExtensionElement(node, 'zeebe:IoMapping');
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
if (!ioMapping) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
return ioMapping.get('inputParameters')
|
|
50
|
+
.filter(inputParameter => !isValidSecret(inputParameter.get('source')))
|
|
51
|
+
.map(inputParameter => getReport('source', inputParameter, node));
|
|
46
52
|
}
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.map(inputParameter => getReport('source', inputParameter, node));
|
|
51
|
-
}
|
|
54
|
+
function validateProperties(node) {
|
|
55
|
+
const properties = findExtensionElement(node, 'zeebe:Properties');
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
if (!properties) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
return (properties.get('properties'))
|
|
62
|
+
.filter(property => !isValidSecret(property.get('value')))
|
|
63
|
+
.map(property => getReport('value', property, node));
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.map(property => getReport('value', property, node));
|
|
63
|
-
}
|
|
66
|
+
function validateSubscription(node) {
|
|
67
|
+
let message;
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
if (is(node, 'bpmn:ReceiveTask')) {
|
|
70
|
+
message = node.get('messageRef');
|
|
71
|
+
} else {
|
|
72
|
+
const messageEventDefinition = getEventDefinition(node, 'bpmn:MessageEventDefinition');
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const messageEventDefinition = getEventDefinition(node, 'bpmn:MessageEventDefinition');
|
|
74
|
+
if (!messageEventDefinition) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
message = messageEventDefinition.get('messageRef');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!message) {
|
|
74
82
|
return [];
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
}
|
|
85
|
+
const subscription = findExtensionElement(message, 'zeebe:Subscription');
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
if (!subscription) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
const correlationKey = subscription.get('correlationKey');
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
return isValidSecret(correlationKey)
|
|
94
|
+
? []
|
|
95
|
+
: [ getReport('correlationKey', subscription, node) ];
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
function isValidSecret(value) {
|
|
99
|
+
if (!value || !isString(value) || !value.includes('secrets.')) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
103
|
+
// the current, opt-in `camunda.secrets.<name>` format is always accepted
|
|
104
|
+
if (/camunda\.secrets\.[\w-]+/.test(value)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// once the engine supports `camunda.secrets.<name>`, any remaining
|
|
109
|
+
// `secrets.<name>` reference (wrapped or not) is considered outdated
|
|
110
|
+
if (greaterOrEqual(version, CAMUNDA_SECRETS_FORMAT_ALLOWED_VERSION)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return /{{\s*secrets\.[\w-]+\s*}}/.test(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
check
|
|
119
|
+
};
|
|
120
|
+
});
|
|
96
121
|
|
|
97
122
|
function getReport(propertyName, node, parentNode) {
|
|
98
123
|
const path = getPath(node, parentNode);
|
|
@@ -104,14 +129,8 @@ function getReport(propertyName, node, parentNode) {
|
|
|
104
129
|
type: ERROR_TYPES.SECRET_EXPRESSION_FORMAT_DEPRECATED,
|
|
105
130
|
node,
|
|
106
131
|
parentNode: parentNode,
|
|
107
|
-
property: propertyName
|
|
132
|
+
property: propertyName,
|
|
133
|
+
allowedVersion: CAMUNDA_SECRETS_FORMAT_ALLOWED_VERSION
|
|
108
134
|
}
|
|
109
135
|
};
|
|
110
136
|
}
|
|
111
|
-
|
|
112
|
-
function isValidSecret(value) {
|
|
113
|
-
return !value
|
|
114
|
-
|| !isString(value)
|
|
115
|
-
|| !value.includes('secrets.')
|
|
116
|
-
|| /{{\s*secrets\.[\w-]+\s*}}/.test(value);
|
|
117
|
-
}
|