@uipath/case-tool 1.202.0 → 1.203.0-preview.160
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/THIRD-PARTY-NOTICES.md +172 -3
- package/dist/{authoring-e757p9dq.js → authoring-kngaggwm.js} +42 -4
- package/dist/{case-api-42s4mbpn.js → case-api-v6cjyrfw.js} +8 -8
- package/dist/case-bindings-service-1wxfghzd.js +240 -0
- package/dist/case-debug-studio-web-service-0624c07c.js +133 -0
- package/dist/case-format-service-q345a2fd.js +84 -0
- package/dist/{case-pack-service-8exkecnd.js → case-pack-service-29rza2q2.js} +61 -11
- package/dist/{case-validate-service-xvxkr026.js → case-validate-service-e06varbt.js} +1422 -40
- package/dist/{connector-enrichment-service-xq9xtz95.js → connector-enrichment-service-2pes4b1b.js} +20 -20
- package/dist/{connector-lookup-service-bk1wscyr.js → connector-lookup-service-2bkybhkf.js} +7 -7
- package/dist/{connector-task-spec-0g34na74.js → connector-task-spec-q59xmzm0.js} +24 -24
- package/dist/{de-7XQXX2DK-tqdy5n2b.js → de-FGHEJRCI-rv513mfh.js} +153 -9
- package/dist/{de-5KVP66U6-1bnf4zt5.js → de-MUJPA4EB-dpg7t2rw.js} +15 -3
- package/dist/{debug-service-f6t4ntmg.js → debug-service-8x087xxt.js} +2 -2
- package/dist/{index-j3xb87ag.js → embedded-file-reader-0sjt4nkj.js} +96 -16641
- package/dist/{en-5ZGC5VQ3-h581y30k.js → en-BWGKA5OE-1qkvb9rn.js} +140 -2
- package/dist/{en-X3S6IZ5J-nhkg6pwn.js → en-FBPVNKFN-dqt573xn.js} +29 -3
- package/dist/{enrichment-service-k1zhkzgr.js → enrichment-service-7vbxcwge.js} +7 -7
- package/dist/{es-VFPNVMP5-bx2c337j.js → es-7SE7HX2X-8yh1aj2y.js} +15 -3
- package/dist/{es-ZGCPPOME-8gazspjj.js → es-KR3ZSNMP-84zphx0x.js} +153 -9
- package/dist/{es-MX-XAVLOQW3-q00km2y7.js → es-MX-2MYFZU7X-3rek4r73.js} +15 -3
- package/dist/{es-MX-U2VN3SZW-anynsmd8.js → es-MX-XXZJGC4R-2mr8jtgm.js} +153 -9
- package/dist/first-party-service-srxaftkn.js +27 -0
- package/dist/{fr-E5MDO4EG-c3mkty2t.js → fr-MDFKNZ4C-h4a122k9.js} +15 -3
- package/dist/{fr-T3G5GZCT-y3f7xb1q.js → fr-Y6NPVODW-8vr1ja4b.js} +153 -9
- package/dist/{index-f0rf4m8p.js → index-8yv94z7r.js} +6 -4
- package/dist/{index-kwxe1vep.js → index-fz4ttsde.js} +16 -4
- package/dist/index-jxj2jz45.js +16742 -0
- package/dist/index-kmp9fmhb.js +25 -0
- package/dist/index.js +10 -10
- package/dist/{input-reader-vgsdjgst.js → input-reader-pnh948bq.js} +1 -1
- package/dist/{ja-BI5K3JDZ-77zat4z2.js → ja-Q5PXKJ2V-p4msxzjd.js} +153 -9
- package/dist/{ja-DPO5PPLO-mysfmznx.js → ja-UTQEAP34-8e0q6swp.js} +15 -3
- package/dist/{known-types-qryebm95.js → known-types-mn8xn36b.js} +4 -4
- package/dist/{ko-ITOPNIYI-hmxd2asd.js → ko-AXCHW2OK-askct7f6.js} +15 -3
- package/dist/{ko-JOBF5V7N-4bv0mjje.js → ko-AYBDSRZG-41mdmsg1.js} +153 -9
- package/dist/{node-strategy-0267x5e5.js → node-strategy-gc4sg6a2.js} +4 -4
- package/dist/{node-sync-service-ac8rzgnj.js → node-sync-service-9d0dqz4z.js} +12 -14
- package/dist/{packager-tool-5yw9xatr.js → packager-tool-052kvydg.js} +1 -1
- package/dist/{packager-tool-3tfbb641.js → packager-tool-32jxda3r.js} +213 -51
- package/dist/packager-tool-53w8skv5.js +262 -0
- package/dist/{packager-tool-w0209wf4.js → packager-tool-61hxhgeg.js} +1 -1
- package/dist/{packager-tool-58q3tvh7.js → packager-tool-7q6z6npd.js} +166 -28
- package/dist/packager-tool-83qnybab.js +1564 -0
- package/dist/{packager-tool-eh50gcdk.js → packager-tool-8q7vzh65.js} +5 -4
- package/dist/{packager-tool-8rf8qda5.js → packager-tool-adcaxdhs.js} +15 -5
- package/dist/{packager-tool-933n6t8x.js → packager-tool-af2s79jb.js} +6 -6
- package/dist/{packager-tool-sk2b33km.js → packager-tool-b6368khs.js} +10 -9
- package/dist/{packager-tool-d73376xs.js → packager-tool-cg2j4rma.js} +8 -8
- package/dist/packager-tool-d4sjk46c.js +88 -0
- package/dist/{packager-tool-a42jnpy5.js → packager-tool-d88rqwk8.js} +29 -3
- package/dist/packager-tool-dd8bq6az.js +8462 -0
- package/dist/packager-tool-gdtpsdn7.js +342 -0
- package/dist/{packager-tool-v60h8pvv.js → packager-tool-hrdan5ms.js} +1 -1
- package/dist/{packager-tool-bmn50g7v.js → packager-tool-hz1mz8fs.js} +1 -1
- package/dist/{packager-tool-v0vdet53.js → packager-tool-jf4hmztd.js} +10 -13
- package/dist/{packager-tool-c23zrhj8.js → packager-tool-jysvpt8p.js} +2 -2
- package/dist/{packager-tool-jhdf8gt1.js → packager-tool-kkxhc0fp.js} +13554 -8844
- package/dist/{packager-tool-4knkebkr.js → packager-tool-kpcjnxd1.js} +269 -23
- package/dist/{packager-tool-nx0j1294.js → packager-tool-rhkk0pta.js} +31 -30
- package/dist/{packager-tool-d128hs55.js → packager-tool-wefb1zw3.js} +1 -1
- package/dist/{packager-tool-3kbkkv2a.js → packager-tool-x4vxexvk.js} +11 -11
- package/dist/{packager-tool-dqs5qy9s.js → packager-tool-yrsrzz1a.js} +134 -19
- package/dist/packager-tool.js +5 -1501
- package/dist/{project-artifacts-43c2ynd9.js → project-artifacts-8snev67g.js} +1 -1
- package/dist/{pt-YI4VE4YQ-wdkn2dcf.js → pt-3DIO5QWG-59b3f4bc.js} +15 -3
- package/dist/{pt-BR-7DYX2YPT-fqxqzqtf.js → pt-BR-TKHNHMET-sn91xyq0.js} +153 -9
- package/dist/{pt-BR-BXURWX4W-bwfcwhk1.js → pt-BR-Y6K6XHXK-mp3hwhw1.js} +15 -3
- package/dist/{pt-HTXGGX6Y-97t21dty.js → pt-ROXU257Y-2f6bxev4.js} +153 -9
- package/dist/{resourcesSyncService-7ampdrez.js → resourcesSyncService-5bq0j9wf.js} +20 -20
- package/dist/{ro-KTNQ3MNU-y08n1x5g.js → ro-AVAWPKOO-b89pw79r.js} +153 -9
- package/dist/{ro-RKXZUG7Z-nndrrsq7.js → ro-BWR3ONRR-vzx3hy8a.js} +15 -3
- package/dist/ru-WBLQ5B2U-s4em88p0.js +10 -0
- package/dist/sdd-lookup-x08j9g9m.js +48 -0
- package/dist/{strict-case-rules-303vjvas.js → strict-case-rules-x6t0vb40.js} +141 -109
- package/dist/strict-sdd-audit-8e2fh9jg.js +2520 -0
- package/dist/tool.js +9 -9
- package/dist/{tr-AA6O2WSR-86ba4bdf.js → tr-5IIHSFW5-hw4cewew.js} +15 -3
- package/dist/{tr-XHSSPNK7-cmqyc4wd.js → tr-JMIP7ZSL-c39b6pe4.js} +153 -9
- package/dist/{trigger-enrichment-service-cg99dk1f.js → trigger-enrichment-service-kkh3197w.js} +24 -24
- package/dist/{zh-CN-2EHQT23P-mr63c4xz.js → zh-CN-IFFXSEJS-9wm9gmrv.js} +153 -9
- package/dist/{zh-CN-R2KLHXQV-agqr45ds.js → zh-CN-R2KU4U5L-tc1hat59.js} +15 -3
- package/dist/{zh-TW-RJGCQANM-w0x3qc1p.js → zh-TW-GNKMELBF-j674515r.js} +15 -3
- package/dist/{zh-TW-MGZCE5BT-c4ekmrz9.js → zh-TW-IYBNYAMG-8727c7v2.js} +153 -9
- package/package.json +3 -2
- package/dist/case-debug-studio-web-service-npcw81ay.js +0 -335
- package/dist/packager-tool-134mvmcm.js +0 -3040
- package/dist/packager-tool-ty1t2byf.js +0 -27
- package/dist/ru-WBLQ5B2U-8r3c7v6d.js +0 -10
- package/dist/strict-sdd-audit-qdmc9e1y.js +0 -1024
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ACTIVITY_TYPE_ID_PATH,
|
|
3
|
+
BINDING_REFERENCE_PREFIX,
|
|
4
|
+
NAME_BOUND_TASK_TYPES,
|
|
5
|
+
RESOLVED_IDENTITY_KEYS,
|
|
6
|
+
activityTypeIdMessage,
|
|
7
|
+
contextEntries,
|
|
2
8
|
contextEntry,
|
|
3
9
|
missingContextKeys,
|
|
4
10
|
readActivityTypeId
|
|
5
|
-
} from "./packager-tool-
|
|
11
|
+
} from "./packager-tool-d4sjk46c.js";
|
|
6
12
|
import {
|
|
7
13
|
validateIntSvcNode
|
|
8
|
-
} from "./packager-tool-
|
|
14
|
+
} from "./packager-tool-b6368khs.js";
|
|
9
15
|
import {
|
|
10
16
|
loadCaseSchemaAsync
|
|
11
17
|
} from "./packager-tool-p7jgrm5r.js";
|
|
@@ -17,17 +23,17 @@ import {
|
|
|
17
23
|
VALID_STAGE_EXIT_RULE_TYPES,
|
|
18
24
|
VALID_TASK_ENTRY_RULE_TYPES
|
|
19
25
|
} from "./packager-tool-sv0d6wb6.js";
|
|
20
|
-
import"./packager-tool-
|
|
26
|
+
import"./packager-tool-yrsrzz1a.js";
|
|
27
|
+
import"./packager-tool-0gctz400.js";
|
|
21
28
|
import"./packager-tool-qa9gftnj.js";
|
|
29
|
+
import"./packager-tool-yc4kw997.js";
|
|
22
30
|
import {
|
|
23
31
|
catchError,
|
|
24
32
|
logger
|
|
25
|
-
} from "./packager-tool-
|
|
33
|
+
} from "./packager-tool-kpcjnxd1.js";
|
|
26
34
|
import {
|
|
27
35
|
getFileSystem
|
|
28
36
|
} from "./packager-tool-0v3s8r01.js";
|
|
29
|
-
import"./packager-tool-0gctz400.js";
|
|
30
|
-
import"./packager-tool-yc4kw997.js";
|
|
31
37
|
import {
|
|
32
38
|
__require
|
|
33
39
|
} from "./packager-tool-1yh8g550.js";
|
|
@@ -86,8 +92,13 @@ var connectorTaskValidator = {
|
|
|
86
92
|
return issues;
|
|
87
93
|
}
|
|
88
94
|
};
|
|
95
|
+
function namedEntry(entries, name) {
|
|
96
|
+
if (!Array.isArray(entries))
|
|
97
|
+
return;
|
|
98
|
+
return entries.find((entry) => entry.name === name);
|
|
99
|
+
}
|
|
89
100
|
function readContextValue(context, name) {
|
|
90
|
-
const entry = context
|
|
101
|
+
const entry = namedEntry(context, name);
|
|
91
102
|
if (typeof entry?.value !== "string")
|
|
92
103
|
return;
|
|
93
104
|
return entry.value;
|
|
@@ -99,18 +110,20 @@ function resolveBindingRef(rawValue, bindings) {
|
|
|
99
110
|
const match = BINDING_REF_PATTERN.exec(rawValue);
|
|
100
111
|
if (!match)
|
|
101
112
|
return rawValue;
|
|
113
|
+
if (!Array.isArray(bindings))
|
|
114
|
+
return;
|
|
102
115
|
const binding = bindings.find((b) => b.id === match[1]);
|
|
103
116
|
return typeof binding?.default === "string" ? binding.default : undefined;
|
|
104
117
|
}
|
|
105
118
|
function readMetadataConfiguration(context) {
|
|
106
|
-
const meta = context
|
|
119
|
+
const meta = namedEntry(context, "metadata");
|
|
107
120
|
const body = meta?.body;
|
|
108
121
|
const apc = body?.activityPropertyConfiguration;
|
|
109
122
|
const config = apc?.configuration;
|
|
110
123
|
return typeof config === "string" ? config : undefined;
|
|
111
124
|
}
|
|
112
125
|
function readInputBody(inputs, name) {
|
|
113
|
-
const entry = inputs
|
|
126
|
+
const entry = namedEntry(inputs, name);
|
|
114
127
|
const body = entry?.body;
|
|
115
128
|
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
116
129
|
return body;
|
|
@@ -267,6 +280,7 @@ var timerTaskValidator = {
|
|
|
267
280
|
}
|
|
268
281
|
};
|
|
269
282
|
// src/services/case-validators/strict-task-validators.ts
|
|
283
|
+
var TRIGGER_TASK_TYPES = new Set(["wait-for-connector"]);
|
|
270
284
|
var CONNECTOR_TASK_TYPES2 = new Set([
|
|
271
285
|
"execute-connector-activity",
|
|
272
286
|
"wait-for-connector"
|
|
@@ -277,6 +291,19 @@ var SERVICE_TYPE_BY_TASK = {
|
|
|
277
291
|
};
|
|
278
292
|
var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
279
293
|
var asArray = (value) => Array.isArray(value) ? value : [];
|
|
294
|
+
var VARS_READS = [
|
|
295
|
+
/\bvars\.([A-Za-z_$][A-Za-z0-9_$]*)/g,
|
|
296
|
+
/\bvars\s*\[\s*["']([A-Za-z_$][A-Za-z0-9_$]*)["']\s*\]/g
|
|
297
|
+
];
|
|
298
|
+
var readsIdentifier = (expression, name) => {
|
|
299
|
+
for (const pattern of VARS_READS) {
|
|
300
|
+
for (const match of expression.matchAll(pattern)) {
|
|
301
|
+
if (match[1] === name)
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
};
|
|
280
307
|
var taskData = (task) => asRecord(task.data);
|
|
281
308
|
var taskLabel = (task) => task.displayName ?? task.id ?? "task";
|
|
282
309
|
var taskPath = (stageId, task) => `nodes[${stageId}].data.tasks[${task.id ?? "?"}]`;
|
|
@@ -286,7 +313,7 @@ var strictConnectorContextValidator = {
|
|
|
286
313
|
validate: (task, ctx) => {
|
|
287
314
|
const issues = [];
|
|
288
315
|
const context = taskData(task)?.context;
|
|
289
|
-
const missing = missingContextKeys(context);
|
|
316
|
+
const missing = missingContextKeys(context, TRIGGER_TASK_TYPES.has(task.type ?? ""));
|
|
290
317
|
if (missing.length > 0) {
|
|
291
318
|
issues.push({
|
|
292
319
|
code: "STRICT_CONNECTOR_CONTEXT_INCOMPLETE",
|
|
@@ -299,9 +326,9 @@ var strictConnectorContextValidator = {
|
|
|
299
326
|
if (readActivityTypeId(context) === undefined) {
|
|
300
327
|
issues.push({
|
|
301
328
|
code: "STRICT_CONNECTOR_ACTIVITY_TYPE_ID",
|
|
302
|
-
path: `${taskPath(ctx.stageId, task)}.data.context[metadata]
|
|
329
|
+
path: `${taskPath(ctx.stageId, task)}.data.context[metadata].${ACTIVITY_TYPE_ID_PATH}`,
|
|
303
330
|
severity: "error",
|
|
304
|
-
message: `
|
|
331
|
+
message: activityTypeIdMessage(`task "${taskLabel(task)}"`, context, "The connection id alone does not tell the runtime " + "which activity to run, so the task deploys and " + "never binds.")
|
|
305
332
|
});
|
|
306
333
|
}
|
|
307
334
|
}
|
|
@@ -324,15 +351,71 @@ var strictConnectorContextValidator = {
|
|
|
324
351
|
message: `Connector task "${taskLabel(task)}" declares no ` + `serviceType; a "${task.type}" task must declare ` + `"${expectedServiceType}".`
|
|
325
352
|
});
|
|
326
353
|
}
|
|
354
|
+
const bodyInput = TRIGGER_TASK_TYPES.has(task.type ?? "") ? undefined : asArray(taskData(task)?.inputs).map((entry) => asRecord(entry)).find((entry) => entry?.name === "body");
|
|
355
|
+
if (bodyInput !== undefined && !bodyInput.target) {
|
|
356
|
+
issues.push({
|
|
357
|
+
code: "STRICT_CONNECTOR_BODY_TARGET",
|
|
358
|
+
path: `${taskPath(ctx.stageId, task)}.data.inputs[body].target`,
|
|
359
|
+
severity: "warning",
|
|
360
|
+
message: `Connector task "${taskLabel(task)}" has a "body" input ` + 'with no "target". The parser keeps an activity input ' + "only when its name, type, value and target are all " + "present, so this one is dropped and the request goes " + "out with no body at all. On a multipart activity that " + "leaves no parts to parse and Integration Services " + 'answers 400 "Unable to parse multipart body". Transcribe ' + "the input from `case spec`, which always names the target."
|
|
361
|
+
});
|
|
362
|
+
}
|
|
327
363
|
return issues;
|
|
328
364
|
}
|
|
329
365
|
};
|
|
366
|
+
var selfReadExpression = (output) => {
|
|
367
|
+
const target = output.var;
|
|
368
|
+
if (typeof target !== "string" || target === "")
|
|
369
|
+
return;
|
|
370
|
+
for (const candidate of [output.value, output.source]) {
|
|
371
|
+
if (typeof candidate !== "string")
|
|
372
|
+
continue;
|
|
373
|
+
if (!candidate.startsWith("="))
|
|
374
|
+
continue;
|
|
375
|
+
if (readsIdentifier(candidate, target))
|
|
376
|
+
return candidate;
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
};
|
|
380
|
+
var forwardRead = (output, index, writtenAt) => {
|
|
381
|
+
for (const candidate of [output.value, output.source]) {
|
|
382
|
+
if (typeof candidate !== "string")
|
|
383
|
+
continue;
|
|
384
|
+
if (!candidate.startsWith("="))
|
|
385
|
+
continue;
|
|
386
|
+
for (const [name, at] of writtenAt) {
|
|
387
|
+
if (at > index && readsIdentifier(candidate, name))
|
|
388
|
+
return { name, expression: candidate };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
};
|
|
393
|
+
var customShapeProblems = (output) => {
|
|
394
|
+
const problems = [];
|
|
395
|
+
if (!("target" in output) || output.target !== "")
|
|
396
|
+
problems.push('"target" must be present and empty');
|
|
397
|
+
if (!("body" in output) || output.body !== "")
|
|
398
|
+
problems.push('"body" must be present and empty');
|
|
399
|
+
if (output.elementId !== "root")
|
|
400
|
+
problems.push('"elementId" must be "root"');
|
|
401
|
+
if ("id" in output)
|
|
402
|
+
problems.push('"id" must be absent');
|
|
403
|
+
if ("originalVar" in output)
|
|
404
|
+
problems.push('"originalVar" must be absent');
|
|
405
|
+
return problems;
|
|
406
|
+
};
|
|
330
407
|
var strictOutputShapeValidator = {
|
|
331
408
|
id: "strict-output-shape",
|
|
332
409
|
appliesTo: (task) => asArray(taskData(task)?.outputs).length > 0,
|
|
333
410
|
validate: (task, ctx) => {
|
|
334
411
|
const issues = [];
|
|
335
412
|
const base = `${taskPath(ctx.stageId, task)}.data.outputs`;
|
|
413
|
+
const writtenAt = new Map;
|
|
414
|
+
asArray(taskData(task)?.outputs).forEach((entry, index) => {
|
|
415
|
+
const name = asRecord(entry)?.var;
|
|
416
|
+
if (typeof name === "string" && name !== "" && !writtenAt.has(name))
|
|
417
|
+
writtenAt.set(name, index);
|
|
418
|
+
});
|
|
336
419
|
asArray(taskData(task)?.outputs).forEach((entry, index) => {
|
|
337
420
|
const output = asRecord(entry);
|
|
338
421
|
if (!output)
|
|
@@ -345,22 +428,24 @@ var strictOutputShapeValidator = {
|
|
|
345
428
|
severity: "error",
|
|
346
429
|
message
|
|
347
430
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
431
|
+
const type = output.type;
|
|
432
|
+
const variable = typeof output.var === "string" ? output.var : label;
|
|
433
|
+
if (typeof type !== "string" || type === "") {
|
|
434
|
+
report("STRICT_OUTPUT_TYPE_MISSING", `Output "${label}" carries no "type". The parser keeps ` + "an output only when name, type, source and var are " + `all present, so this one is dropped and "${variable}" ` + "is never written. Emit the type of the case variable " + "it writes.");
|
|
435
|
+
}
|
|
436
|
+
const selfRead = selfReadExpression(output);
|
|
437
|
+
if (selfRead !== undefined) {
|
|
438
|
+
report("STRICT_OUTPUT_SELF_REFERENCE", `Output "${label}" writes "${variable}" from an ` + `expression that reads it: ${selfRead}. Nothing on ` + "the task produces that name, so the expression sees " + "the variable's default. Point it at the output that " + "carries the value instead.");
|
|
439
|
+
}
|
|
440
|
+
const forward = forwardRead(output, index, writtenAt);
|
|
441
|
+
if (forward !== undefined) {
|
|
442
|
+
report("STRICT_OUTPUT_FORWARD_READ", `Output "${label}" reads "${forward.name}" in ` + `${forward.expression}, and an output later in this ` + "task writes that name. The engine evaluates outputs " + "in array order and writes each one before the next " + "is evaluated, so this reads the variable's default. " + `Move "${label}" after the output that writes ` + `"${forward.name}".`);
|
|
443
|
+
}
|
|
444
|
+
if (output.custom !== true)
|
|
363
445
|
return;
|
|
446
|
+
const problems = customShapeProblems(output);
|
|
447
|
+
if (problems.length > 0) {
|
|
448
|
+
report("STRICT_CUSTOM_OUTPUT_SHAPE", `Custom output "${label}" is malformed: ` + `${problems.join("; ")}. The converter reads these ` + "keys positionally, so a dropped or renamed one " + "writes the value nowhere.");
|
|
364
449
|
}
|
|
365
450
|
});
|
|
366
451
|
return issues;
|
|
@@ -406,10 +491,49 @@ var strictInputReferenceValidator = {
|
|
|
406
491
|
return issues;
|
|
407
492
|
}
|
|
408
493
|
};
|
|
494
|
+
var BINDING_PREFIX = "=bindings.";
|
|
495
|
+
var literalCatalogName = (task) => {
|
|
496
|
+
const value = taskData(task)?.actionCatalogName;
|
|
497
|
+
return typeof value === "string" && value !== "" && !value.startsWith(BINDING_PREFIX) ? value : undefined;
|
|
498
|
+
};
|
|
499
|
+
var strictActionCatalogValidator = {
|
|
500
|
+
id: "strict-action-catalog",
|
|
501
|
+
appliesTo: (task) => task.type === "action" && literalCatalogName(task) !== undefined,
|
|
502
|
+
validate: (task, ctx) => {
|
|
503
|
+
const name = literalCatalogName(task) ?? "";
|
|
504
|
+
return [
|
|
505
|
+
{
|
|
506
|
+
code: "STRICT_ACTION_CATALOG_NAMED",
|
|
507
|
+
path: `${taskPath(ctx.stageId, task)}.data.actionCatalogName`,
|
|
508
|
+
severity: "warning",
|
|
509
|
+
message: `Task "${taskLabel(task)}" names the action catalog ` + `"${name}". The name is resolved in the app's deployment ` + "folder, the one data.folderPath binds, and a name that " + "folder does not hold fails the task the moment it opens, " + `with "No task catalog exists with name ${name}". Confirm ` + "it with 'uip tasks catalogs list --folder-path " + "<app deployment folder> --output json', or omit the key " + "and the task is created with no catalog."
|
|
510
|
+
}
|
|
511
|
+
];
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
var strictTaskElementIdValidator = {
|
|
515
|
+
id: "strict-task-element-id",
|
|
516
|
+
appliesTo: (task) => typeof task.elementId === "string" && task.elementId !== "" && typeof task.id === "string" && task.id !== "",
|
|
517
|
+
validate: (task, ctx) => {
|
|
518
|
+
const expected = `${ctx.stageId}-${task.id}`;
|
|
519
|
+
if (task.elementId === expected)
|
|
520
|
+
return [];
|
|
521
|
+
return [
|
|
522
|
+
{
|
|
523
|
+
code: "STRICT_TASK_ELEMENT_ID_STAGE_MISMATCH",
|
|
524
|
+
path: `${taskPath(ctx.stageId, task)}.elementId`,
|
|
525
|
+
severity: "error",
|
|
526
|
+
message: `Task "${taskLabel(task)}" carries elementId ` + `"${task.elementId}" while it sits in stage ` + `"${ctx.stageId}". A task's elementId is ` + "<stageId>-<taskId> for the stage that holds it, and " + "every writer derives it that way, so the stored prefix " + "is never read back and the task runs in the stage whose " + "data.tasks holds it. Set elementId to " + `"${expected}" if the placement is right, or move the ` + "task into the stage its elementId names if the prefix " + "is right."
|
|
527
|
+
}
|
|
528
|
+
];
|
|
529
|
+
}
|
|
530
|
+
};
|
|
409
531
|
var strictTaskValidators = [
|
|
410
532
|
strictConnectorContextValidator,
|
|
411
533
|
strictOutputShapeValidator,
|
|
412
|
-
strictInputReferenceValidator
|
|
534
|
+
strictInputReferenceValidator,
|
|
535
|
+
strictActionCatalogValidator,
|
|
536
|
+
strictTaskElementIdValidator
|
|
413
537
|
];
|
|
414
538
|
|
|
415
539
|
// src/services/case-validators/index.ts
|
|
@@ -600,6 +724,993 @@ var VALID_TRIGGER_SERVICE_TYPES = new Set([
|
|
|
600
724
|
"Intsvc.EventTrigger",
|
|
601
725
|
"Intsvc.TimerTrigger"
|
|
602
726
|
]);
|
|
727
|
+
var AUTHORING_MARKER_LIMIT = 50;
|
|
728
|
+
var XREF_MARKER = "$xref(";
|
|
729
|
+
var PLANNING_NOTATION_PREFIXES = ["<-", "->"];
|
|
730
|
+
var findXrefMarkers = (document) => {
|
|
731
|
+
const found = [];
|
|
732
|
+
const visit = (value, path) => {
|
|
733
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
734
|
+
return;
|
|
735
|
+
if (typeof value === "string") {
|
|
736
|
+
if (value.includes(XREF_MARKER))
|
|
737
|
+
found.push(path);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (Array.isArray(value)) {
|
|
741
|
+
value.forEach((entry, index) => {
|
|
742
|
+
visit(entry, `${path}[${index}]`);
|
|
743
|
+
});
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (value === null || typeof value !== "object")
|
|
747
|
+
return;
|
|
748
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
749
|
+
visit(entry, path === "" ? key : `${path}.${key}`);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
visit(document, "");
|
|
753
|
+
return found;
|
|
754
|
+
};
|
|
755
|
+
var TASK_SLOTS = ["inputs", "outputs"];
|
|
756
|
+
var parsesAsJsonValue = (text) => catchError(() => JSON.parse(text))[0] === undefined;
|
|
757
|
+
function* taskSlotEntries(nodes, slots = TASK_SLOTS) {
|
|
758
|
+
for (const node of nodes) {
|
|
759
|
+
const nodeId = typeof node.id === "string" ? node.id : "?";
|
|
760
|
+
const data = asRecordValue(node.data);
|
|
761
|
+
for (const lane of asArrayValue(data?.tasks)) {
|
|
762
|
+
for (const task of asArrayValue(lane)) {
|
|
763
|
+
const taskRecord = asRecordValue(task);
|
|
764
|
+
if (taskRecord === undefined)
|
|
765
|
+
continue;
|
|
766
|
+
const taskId = typeof taskRecord.id === "string" ? taskRecord.id : "?";
|
|
767
|
+
const taskData2 = asRecordValue(taskRecord.data);
|
|
768
|
+
for (const slot of slots) {
|
|
769
|
+
for (const [index, value] of asArrayValue(taskData2?.[slot]).entries()) {
|
|
770
|
+
const entry = asRecordValue(value);
|
|
771
|
+
const name = typeof entry?.name === "string" ? entry.name : `#${index}`;
|
|
772
|
+
yield {
|
|
773
|
+
task: taskRecord,
|
|
774
|
+
slot,
|
|
775
|
+
entry,
|
|
776
|
+
name,
|
|
777
|
+
path: `nodes[${nodeId}].data.tasks[${taskId}].data.${slot}[${name}]`
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
var findPlanningNotation = (nodes) => {
|
|
786
|
+
const found = [];
|
|
787
|
+
for (const { slot, entry, path } of taskSlotEntries(nodes)) {
|
|
788
|
+
const value = entry?.value;
|
|
789
|
+
if (typeof value !== "string")
|
|
790
|
+
continue;
|
|
791
|
+
const trimmed = value.trimStart();
|
|
792
|
+
if (!PLANNING_NOTATION_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
found.push({ path, slot });
|
|
796
|
+
}
|
|
797
|
+
return found;
|
|
798
|
+
};
|
|
799
|
+
var findUnparseableJsonValues = (nodes) => {
|
|
800
|
+
const found = [];
|
|
801
|
+
for (const { entry, name, path } of taskSlotEntries(nodes)) {
|
|
802
|
+
const value = entry?.value;
|
|
803
|
+
if (typeof value !== "string")
|
|
804
|
+
continue;
|
|
805
|
+
const trimmed = value.trim();
|
|
806
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
|
|
807
|
+
continue;
|
|
808
|
+
if (parsesAsJsonValue(trimmed))
|
|
809
|
+
continue;
|
|
810
|
+
found.push({ path, name });
|
|
811
|
+
}
|
|
812
|
+
return found;
|
|
813
|
+
};
|
|
814
|
+
var CONNECTOR_TASK_TYPES3 = new Set([
|
|
815
|
+
"execute-connector-activity",
|
|
816
|
+
"wait-for-connector"
|
|
817
|
+
]);
|
|
818
|
+
var CONNECTOR_INPUT_ENVELOPE = new Set([
|
|
819
|
+
"pathParameters",
|
|
820
|
+
"queryParameters",
|
|
821
|
+
"body"
|
|
822
|
+
]);
|
|
823
|
+
var isFileChannel = (input) => input?.type === "file" || input?.target === "file";
|
|
824
|
+
var envelopeDefect = (input, name) => {
|
|
825
|
+
const target = input.target;
|
|
826
|
+
if (typeof target === "string" && target.trim() !== "" && target.trim().toLowerCase() !== name.toLowerCase()) {
|
|
827
|
+
return "mistargeted";
|
|
828
|
+
}
|
|
829
|
+
const value = input.value;
|
|
830
|
+
if (typeof value !== "string") {
|
|
831
|
+
const parked = asRecordValue(value);
|
|
832
|
+
const body = asRecordValue(input.body);
|
|
833
|
+
const emptyBody = body === undefined || Object.keys(body).length === 0;
|
|
834
|
+
return parked !== undefined && Object.keys(parked).length > 0 && emptyBody ? "misplaced" : undefined;
|
|
835
|
+
}
|
|
836
|
+
const trimmed = value.trim();
|
|
837
|
+
if (trimmed === "" || trimmed.startsWith("="))
|
|
838
|
+
return;
|
|
839
|
+
return "stringified";
|
|
840
|
+
};
|
|
841
|
+
var findConnectorInputDefects = (nodes) => {
|
|
842
|
+
const found = [];
|
|
843
|
+
for (const { task, entry: input, name, path } of taskSlotEntries(nodes, [
|
|
844
|
+
"inputs"
|
|
845
|
+
])) {
|
|
846
|
+
const type = task.type;
|
|
847
|
+
if (typeof type !== "string" || !CONNECTOR_TASK_TYPES3.has(type)) {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
if (typeof input?.name !== "string" || input.name === "")
|
|
851
|
+
continue;
|
|
852
|
+
if (isFileChannel(input))
|
|
853
|
+
continue;
|
|
854
|
+
if (!CONNECTOR_INPUT_ENVELOPE.has(name)) {
|
|
855
|
+
found.push({ path, name, kind: "flattened" });
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const kind = envelopeDefect(input, name);
|
|
859
|
+
if (kind !== undefined) {
|
|
860
|
+
found.push({
|
|
861
|
+
path,
|
|
862
|
+
name,
|
|
863
|
+
kind,
|
|
864
|
+
...kind === "mistargeted" && {
|
|
865
|
+
target: String(input.target)
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return found;
|
|
871
|
+
};
|
|
872
|
+
var findReassignedOutputsWithoutOriginalVar = (parsed) => {
|
|
873
|
+
const found = [];
|
|
874
|
+
for (const { entry, name, path } of taskSlotEntries(asArrayValue(parsed.nodes).map(asRecordValue).filter((n) => n !== undefined), ["outputs"])) {
|
|
875
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
876
|
+
return found;
|
|
877
|
+
if (entry === undefined)
|
|
878
|
+
continue;
|
|
879
|
+
const id = entry.id;
|
|
880
|
+
const variable = entry.var;
|
|
881
|
+
if (typeof id !== "string" || id === "" || typeof variable !== "string" || variable === "" || id === variable || id.toLowerCase() === variable.toLowerCase() || entry.custom === true || entry.renamed === true || entry.originalVar) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (entry.source === undefined && entry.target === undefined)
|
|
885
|
+
continue;
|
|
886
|
+
found.push({ path, name });
|
|
887
|
+
}
|
|
888
|
+
return found;
|
|
889
|
+
};
|
|
890
|
+
var findVarIdCaseMismatches = (parsed) => {
|
|
891
|
+
const found = [];
|
|
892
|
+
for (const { entry, name, path } of taskSlotEntries(asArrayValue(parsed.nodes).map(asRecordValue).filter((n) => n !== undefined), ["outputs"])) {
|
|
893
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
894
|
+
return found;
|
|
895
|
+
const id = entry?.id;
|
|
896
|
+
const variable = entry?.var;
|
|
897
|
+
if (typeof id !== "string" || id === "" || typeof variable !== "string" || variable === "" || id === variable || id.toLowerCase() !== variable.toLowerCase()) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
found.push({ path, name });
|
|
901
|
+
}
|
|
902
|
+
return found;
|
|
903
|
+
};
|
|
904
|
+
var findMisplacedEnvelopeBodies = (nodes) => {
|
|
905
|
+
const found = [];
|
|
906
|
+
for (const { task, entry: input, path } of taskSlotEntries(nodes, [
|
|
907
|
+
"inputs"
|
|
908
|
+
])) {
|
|
909
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
910
|
+
return found;
|
|
911
|
+
const type = task.type;
|
|
912
|
+
if (typeof type !== "string" || CONNECTOR_TASK_TYPES3.has(type)) {
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (input === undefined)
|
|
916
|
+
continue;
|
|
917
|
+
if (typeof input.name !== "string" || input.name === "")
|
|
918
|
+
continue;
|
|
919
|
+
const body = asRecordValue(input.body);
|
|
920
|
+
if (body === undefined || Object.keys(body).length === 0)
|
|
921
|
+
continue;
|
|
922
|
+
const value = input.value;
|
|
923
|
+
found.push({
|
|
924
|
+
path,
|
|
925
|
+
name: input.name,
|
|
926
|
+
hasValue: typeof value === "string" ? value.trim() !== "" : value !== undefined
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return found;
|
|
930
|
+
};
|
|
931
|
+
var findUnreachableSlaRules = (parsed) => {
|
|
932
|
+
const found = [];
|
|
933
|
+
const unconditional = (rule) => {
|
|
934
|
+
const expression = asRecordValue(rule)?.expression;
|
|
935
|
+
if (expression === undefined || expression === null)
|
|
936
|
+
return true;
|
|
937
|
+
if (typeof expression !== "string")
|
|
938
|
+
return false;
|
|
939
|
+
const trimmed = expression.trim();
|
|
940
|
+
return trimmed === "" || trimmed.replaceAll(" ", "") === "=js:true";
|
|
941
|
+
};
|
|
942
|
+
const check = (rules, path, holder) => {
|
|
943
|
+
const list = asArrayValue(rules);
|
|
944
|
+
for (let index = 0;index < list.length - 1; index += 1) {
|
|
945
|
+
if (!unconditional(list[index]))
|
|
946
|
+
continue;
|
|
947
|
+
found.push({
|
|
948
|
+
path: `${path}[${index}]`,
|
|
949
|
+
holder,
|
|
950
|
+
dead: list.length - index - 1
|
|
951
|
+
});
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
check(asRecordValue(parsed.metadata)?.slaRules, "metadata.slaRules", "the case");
|
|
956
|
+
asArrayValue(parsed.nodes).forEach((node, index) => {
|
|
957
|
+
const data = asRecordValue(asRecordValue(node)?.data);
|
|
958
|
+
if (data === undefined)
|
|
959
|
+
return;
|
|
960
|
+
const label = data.label;
|
|
961
|
+
check(data.slaRules, `nodes[${index}].data.slaRules`, typeof label === "string" && label !== "" ? `stage "${label}"` : "this stage");
|
|
962
|
+
});
|
|
963
|
+
return found;
|
|
964
|
+
};
|
|
965
|
+
var findDuplicateSlaGates = (parsed) => {
|
|
966
|
+
const found = [];
|
|
967
|
+
const scan = (rules, path, scope) => {
|
|
968
|
+
const byExpression = new Map;
|
|
969
|
+
for (const entry of asArrayValue(rules)) {
|
|
970
|
+
const expression = asRecordValue(entry)?.expression;
|
|
971
|
+
if (typeof expression !== "string" || expression.trim() === "") {
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
byExpression.set(expression, (byExpression.get(expression) ?? 0) + 1);
|
|
975
|
+
}
|
|
976
|
+
for (const [expression, count] of byExpression) {
|
|
977
|
+
if (count < 2)
|
|
978
|
+
continue;
|
|
979
|
+
found.push({ path, scope, expression, count });
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
scan(asRecordValue(parsed.metadata)?.slaRules, "metadata.slaRules", "the case");
|
|
983
|
+
for (const node of asArrayValue(parsed.nodes)) {
|
|
984
|
+
const data = asRecordValue(asRecordValue(node)?.data);
|
|
985
|
+
if (data?.slaRules === undefined)
|
|
986
|
+
continue;
|
|
987
|
+
const label = typeof data.label === "string" ? data.label : typeof asRecordValue(node)?.id === "string" ? asRecordValue(node)?.id : "?";
|
|
988
|
+
scan(data.slaRules, `nodes[${label}].data.slaRules`, `stage "${label}"`);
|
|
989
|
+
}
|
|
990
|
+
return found;
|
|
991
|
+
};
|
|
992
|
+
var findSharedBindingKeys = (parsed) => {
|
|
993
|
+
const typesById = new Map;
|
|
994
|
+
const note = (id, type) => {
|
|
995
|
+
const seen = typesById.get(id) ?? new Set;
|
|
996
|
+
seen.add(type);
|
|
997
|
+
typesById.set(id, seen);
|
|
998
|
+
};
|
|
999
|
+
const walk = (value) => {
|
|
1000
|
+
if (Array.isArray(value)) {
|
|
1001
|
+
for (const entry of value)
|
|
1002
|
+
walk(entry);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const record = asRecordValue(value);
|
|
1006
|
+
if (record === undefined)
|
|
1007
|
+
return;
|
|
1008
|
+
const type = record.type;
|
|
1009
|
+
const data = asRecordValue(record.data);
|
|
1010
|
+
if (typeof type === "string" && data !== undefined) {
|
|
1011
|
+
for (const field of Object.values(data)) {
|
|
1012
|
+
if (typeof field === "string" && field.startsWith("=bindings.")) {
|
|
1013
|
+
note(field.slice("=bindings.".length), type);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const entry of Object.values(record))
|
|
1018
|
+
walk(entry);
|
|
1019
|
+
};
|
|
1020
|
+
walk(parsed.nodes);
|
|
1021
|
+
const byKey = new Map;
|
|
1022
|
+
for (const entry of asArrayValue(parsed.bindings)) {
|
|
1023
|
+
const binding = asRecordValue(entry);
|
|
1024
|
+
if (binding?.propertyAttribute !== "name")
|
|
1025
|
+
continue;
|
|
1026
|
+
const key = binding.resourceKey;
|
|
1027
|
+
const id = binding.id;
|
|
1028
|
+
if (typeof key !== "string" || typeof id !== "string")
|
|
1029
|
+
continue;
|
|
1030
|
+
byKey.set(key, [...byKey.get(key) ?? [], id]);
|
|
1031
|
+
}
|
|
1032
|
+
const found = [];
|
|
1033
|
+
for (const [key, ids] of byKey) {
|
|
1034
|
+
if (ids.length < 2)
|
|
1035
|
+
continue;
|
|
1036
|
+
const types = new Set;
|
|
1037
|
+
for (const id of ids) {
|
|
1038
|
+
for (const type of typesById.get(id) ?? [])
|
|
1039
|
+
types.add(type);
|
|
1040
|
+
}
|
|
1041
|
+
if (types.size < 2)
|
|
1042
|
+
continue;
|
|
1043
|
+
found.push({
|
|
1044
|
+
key,
|
|
1045
|
+
types: [...types].sort((left, right) => left.localeCompare(right)),
|
|
1046
|
+
ids
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
return found;
|
|
1050
|
+
};
|
|
1051
|
+
var findOutputsWithoutId = (parsed) => {
|
|
1052
|
+
const found = [];
|
|
1053
|
+
for (const { entry, name, path } of taskSlotEntries(asArrayValue(parsed.nodes).map(asRecordValue).filter((n) => n !== undefined), ["outputs"])) {
|
|
1054
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1055
|
+
return found;
|
|
1056
|
+
if (entry === undefined)
|
|
1057
|
+
continue;
|
|
1058
|
+
if (entry.custom === true)
|
|
1059
|
+
continue;
|
|
1060
|
+
const id = entry.id;
|
|
1061
|
+
if (typeof id === "string" && id.trim() !== "")
|
|
1062
|
+
continue;
|
|
1063
|
+
found.push({ path, name });
|
|
1064
|
+
}
|
|
1065
|
+
return found;
|
|
1066
|
+
};
|
|
1067
|
+
var SOLUTION_FOLDER_KEY_PREFIX = "solution_folder.";
|
|
1068
|
+
var NAME_ATTRIBUTE = "name";
|
|
1069
|
+
var FOLDER_PATH_ATTRIBUTE = "folderPath";
|
|
1070
|
+
var findBindingKeyMismatches = (document) => {
|
|
1071
|
+
const groups = new Map;
|
|
1072
|
+
for (const entry of asArrayValue(document.bindings)) {
|
|
1073
|
+
const binding = asRecordValue(entry);
|
|
1074
|
+
const key = binding?.resourceKey;
|
|
1075
|
+
const resource = binding?.resource;
|
|
1076
|
+
const attribute = binding?.propertyAttribute;
|
|
1077
|
+
if (typeof key !== "string" || key === "" || typeof resource !== "string" || typeof attribute !== "string") {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const groupKey = `${resource}\x00${key}`;
|
|
1081
|
+
const group = groups.get(groupKey) ?? {};
|
|
1082
|
+
const value = binding?.default;
|
|
1083
|
+
if (typeof value === "string")
|
|
1084
|
+
group[attribute] = value;
|
|
1085
|
+
groups.set(groupKey, group);
|
|
1086
|
+
}
|
|
1087
|
+
const found = [];
|
|
1088
|
+
for (const [groupKey, attributes] of groups) {
|
|
1089
|
+
const [resource, key] = groupKey.split("\x00");
|
|
1090
|
+
if (key.startsWith(SOLUTION_FOLDER_KEY_PREFIX))
|
|
1091
|
+
continue;
|
|
1092
|
+
const name = attributes[NAME_ATTRIBUTE];
|
|
1093
|
+
const folderPath = attributes[FOLDER_PATH_ATTRIBUTE];
|
|
1094
|
+
if (name === undefined || folderPath === undefined)
|
|
1095
|
+
continue;
|
|
1096
|
+
if (key !== `${folderPath}.${name}`)
|
|
1097
|
+
found.push({ key, resource });
|
|
1098
|
+
}
|
|
1099
|
+
return found;
|
|
1100
|
+
};
|
|
1101
|
+
var IO_SLOT_KEYS = ["inputs", "outputs", "inputOutputs"];
|
|
1102
|
+
var PROSE_KEYS = new Set(["description", "rationale"]);
|
|
1103
|
+
var collectReferenceableIds = (document) => {
|
|
1104
|
+
const ids = new Set;
|
|
1105
|
+
const visit = (value) => {
|
|
1106
|
+
if (Array.isArray(value)) {
|
|
1107
|
+
for (const entry of value)
|
|
1108
|
+
visit(entry);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const record = asRecordValue(value);
|
|
1112
|
+
if (record === undefined)
|
|
1113
|
+
return;
|
|
1114
|
+
for (const key of IO_SLOT_KEYS) {
|
|
1115
|
+
for (const entry of asArrayValue(record[key])) {
|
|
1116
|
+
const slot = asRecordValue(entry);
|
|
1117
|
+
for (const attribute of ["id", "name", "var"]) {
|
|
1118
|
+
const value2 = slot?.[attribute];
|
|
1119
|
+
if (typeof value2 === "string" && value2 !== "") {
|
|
1120
|
+
ids.add(value2);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
for (const entry of Object.values(record))
|
|
1126
|
+
visit(entry);
|
|
1127
|
+
};
|
|
1128
|
+
visit(document);
|
|
1129
|
+
return ids;
|
|
1130
|
+
};
|
|
1131
|
+
var REFERENCE_PREFIX = "vars.";
|
|
1132
|
+
var isReferenceIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_";
|
|
1133
|
+
var referencesIn = (text) => {
|
|
1134
|
+
const found = [];
|
|
1135
|
+
let index = 0;
|
|
1136
|
+
while (index < text.length) {
|
|
1137
|
+
if (!text.startsWith(REFERENCE_PREFIX, index)) {
|
|
1138
|
+
index += 1;
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
index += REFERENCE_PREFIX.length;
|
|
1142
|
+
let id = "";
|
|
1143
|
+
while (index < text.length && isReferenceIdChar(text[index])) {
|
|
1144
|
+
id += text[index];
|
|
1145
|
+
index += 1;
|
|
1146
|
+
}
|
|
1147
|
+
if (id !== "")
|
|
1148
|
+
found.push(id);
|
|
1149
|
+
}
|
|
1150
|
+
return found;
|
|
1151
|
+
};
|
|
1152
|
+
var findUnboundReferences = (document, ids) => {
|
|
1153
|
+
const found = [];
|
|
1154
|
+
const reported = new Set;
|
|
1155
|
+
const visit = (value, path) => {
|
|
1156
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1157
|
+
return;
|
|
1158
|
+
if (typeof value === "string") {
|
|
1159
|
+
for (const id of referencesIn(value)) {
|
|
1160
|
+
if (ids.has(id) || reported.has(`${path}:${id}`))
|
|
1161
|
+
continue;
|
|
1162
|
+
reported.add(`${path}:${id}`);
|
|
1163
|
+
found.push({ path, id });
|
|
1164
|
+
}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (Array.isArray(value)) {
|
|
1168
|
+
value.forEach((entry, index) => {
|
|
1169
|
+
visit(entry, `${path}[${index}]`);
|
|
1170
|
+
});
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const record = asRecordValue(value);
|
|
1174
|
+
if (record === undefined)
|
|
1175
|
+
return;
|
|
1176
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
1177
|
+
if (PROSE_KEYS.has(key))
|
|
1178
|
+
continue;
|
|
1179
|
+
visit(entry, path === "" ? key : `${path}.${key}`);
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
visit(document, "");
|
|
1183
|
+
return found;
|
|
1184
|
+
};
|
|
1185
|
+
var asRecordValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
1186
|
+
var asArrayValue = (value) => Array.isArray(value) ? value : [];
|
|
1187
|
+
var RULE_DISCRIMINATOR_LIMIT = 50;
|
|
1188
|
+
var labelOf = (record, fallback) => {
|
|
1189
|
+
const data = asRecordValue(record?.data);
|
|
1190
|
+
for (const candidate of [
|
|
1191
|
+
record?.displayName,
|
|
1192
|
+
data?.label,
|
|
1193
|
+
data?.displayName,
|
|
1194
|
+
data?.name,
|
|
1195
|
+
record?.id
|
|
1196
|
+
]) {
|
|
1197
|
+
if (typeof candidate === "string" && candidate.trim() !== "") {
|
|
1198
|
+
return candidate;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return fallback;
|
|
1202
|
+
};
|
|
1203
|
+
var findSchemaTypeMismatches = (parsed, migrationError) => {
|
|
1204
|
+
const opens = migrationError.indexOf("[");
|
|
1205
|
+
if (opens === -1)
|
|
1206
|
+
return [];
|
|
1207
|
+
const [, frames] = catchError(() => JSON.parse(migrationError.slice(opens)));
|
|
1208
|
+
const found = [];
|
|
1209
|
+
for (const frame of asArrayValue(frames)) {
|
|
1210
|
+
const issue2 = asRecordValue(frame);
|
|
1211
|
+
if (issue2?.code !== "invalid_type")
|
|
1212
|
+
continue;
|
|
1213
|
+
const segments = asArrayValue(issue2.path);
|
|
1214
|
+
const field = segments.at(-1);
|
|
1215
|
+
if (typeof field !== "string")
|
|
1216
|
+
continue;
|
|
1217
|
+
const parts = [];
|
|
1218
|
+
let node;
|
|
1219
|
+
let task;
|
|
1220
|
+
for (const [at, segment] of segments.entries()) {
|
|
1221
|
+
if (segment === "nodes" && typeof segments[at + 1] === "number") {
|
|
1222
|
+
node = asRecordValue(asArrayValue(parsed.nodes)[segments[at + 1]]);
|
|
1223
|
+
parts.push(`nodes[${labelOf(node, String(segments[at + 1]))}]`);
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (segment === "tasks" && typeof segments[at + 1] === "number" && typeof segments[at + 2] === "number") {
|
|
1227
|
+
const lane = asArrayValue(asRecordValue(node?.data)?.tasks)[segments[at + 1]];
|
|
1228
|
+
task = asRecordValue(asArrayValue(lane)[segments[at + 2]]);
|
|
1229
|
+
parts.push(`tasks[${labelOf(task, String(segments[at + 2]))}]`);
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
if (typeof segment === "number")
|
|
1233
|
+
continue;
|
|
1234
|
+
parts.push(String(segment));
|
|
1235
|
+
}
|
|
1236
|
+
const withArticle = (noun) => /^[aeiou]/i.test(noun) ? `an ${noun}` : `a ${noun}`;
|
|
1237
|
+
const expected = withArticle(typeof issue2.expected === "string" ? issue2.expected : "another type");
|
|
1238
|
+
const received = typeof issue2.message === "string" && issue2.message.includes("received ") ? issue2.message.slice(issue2.message.indexOf("received ") + 9) : "something else";
|
|
1239
|
+
const where = task !== undefined ? ` on task "${labelOf(task, "?")}"` : node !== undefined ? ` on "${labelOf(node, "?")}"` : "";
|
|
1240
|
+
found.push({
|
|
1241
|
+
path: parts.join("."),
|
|
1242
|
+
message: `\`${field}\`${where} must be ${expected}; ` + `it is ${withArticle(received)}. The schema reports this as a V20-to-V21 ` + "migration failure with a zod dump naming array indexes, on a " + "file whose version is not the problem — which is how a " + "repair lands on the wrong task."
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
return found;
|
|
1246
|
+
};
|
|
1247
|
+
var findConnectorTasksWithoutInputs = (nodes) => {
|
|
1248
|
+
const found = [];
|
|
1249
|
+
for (const node of nodes) {
|
|
1250
|
+
const data = asRecordValue(node.data);
|
|
1251
|
+
if (data === undefined)
|
|
1252
|
+
continue;
|
|
1253
|
+
const stage = labelOf(node, "?");
|
|
1254
|
+
for (const lane of asArrayValue(data.tasks)) {
|
|
1255
|
+
for (const task of asArrayValue(lane)) {
|
|
1256
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1257
|
+
return found;
|
|
1258
|
+
const record = asRecordValue(task);
|
|
1259
|
+
const type = record?.type;
|
|
1260
|
+
const taskData2 = asRecordValue(record?.data);
|
|
1261
|
+
if (typeof type !== "string" || !CONNECTOR_TASK_TYPES3.has(type) || taskData2 === undefined) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
const context = taskData2.context;
|
|
1265
|
+
if (contextEntries(context).length === 0)
|
|
1266
|
+
continue;
|
|
1267
|
+
if (missingContextKeys(context, type === "wait-for-connector").length > 0)
|
|
1268
|
+
continue;
|
|
1269
|
+
const inputs = taskData2.inputs;
|
|
1270
|
+
if (inputs !== undefined && !Array.isArray(inputs))
|
|
1271
|
+
continue;
|
|
1272
|
+
if (Array.isArray(inputs) && inputs.length > 0)
|
|
1273
|
+
continue;
|
|
1274
|
+
const name = labelOf(record, type);
|
|
1275
|
+
found.push({
|
|
1276
|
+
path: `nodes[${stage}].data.tasks[${name}].data.inputs`,
|
|
1277
|
+
name
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return found;
|
|
1283
|
+
};
|
|
1284
|
+
var SLA_RULE_KEYS = new Set([
|
|
1285
|
+
"id",
|
|
1286
|
+
"displayName",
|
|
1287
|
+
"expression",
|
|
1288
|
+
"count",
|
|
1289
|
+
"unit",
|
|
1290
|
+
"escalationRule"
|
|
1291
|
+
]);
|
|
1292
|
+
var SLA_KEY_NEAR_MISS = new Map([
|
|
1293
|
+
["duration", "count` and `unit"],
|
|
1294
|
+
["name", "displayName"],
|
|
1295
|
+
["escalations", "escalationRule"],
|
|
1296
|
+
["slatype", "expression"]
|
|
1297
|
+
]);
|
|
1298
|
+
var findUnknownSlaKeys = (parsed, nodes) => {
|
|
1299
|
+
const found = [];
|
|
1300
|
+
const check = (rules, at) => {
|
|
1301
|
+
asArrayValue(rules).forEach((entry, index) => {
|
|
1302
|
+
const rule = asRecordValue(entry);
|
|
1303
|
+
if (rule === undefined)
|
|
1304
|
+
return;
|
|
1305
|
+
const label = typeof rule.displayName === "string" && rule.displayName !== "" ? rule.displayName : String(index);
|
|
1306
|
+
for (const key of Object.keys(rule)) {
|
|
1307
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1308
|
+
return;
|
|
1309
|
+
if (SLA_RULE_KEYS.has(key))
|
|
1310
|
+
continue;
|
|
1311
|
+
const nearMiss = SLA_KEY_NEAR_MISS.get(key.toLowerCase());
|
|
1312
|
+
found.push({
|
|
1313
|
+
path: `${at}[${label}].${key}`,
|
|
1314
|
+
key,
|
|
1315
|
+
...nearMiss !== undefined && { nearMiss }
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
check(asRecordValue(parsed.metadata)?.slaRules, "metadata.slaRules");
|
|
1321
|
+
for (const node of nodes) {
|
|
1322
|
+
const data = asRecordValue(node.data);
|
|
1323
|
+
if (data === undefined)
|
|
1324
|
+
continue;
|
|
1325
|
+
check(data.slaRules, `nodes[${labelOf(node, "?")}].data.slaRules`);
|
|
1326
|
+
}
|
|
1327
|
+
return found;
|
|
1328
|
+
};
|
|
1329
|
+
var findLiteralResourceBindings = (nodes) => {
|
|
1330
|
+
const found = [];
|
|
1331
|
+
for (const node of nodes) {
|
|
1332
|
+
const data = asRecordValue(node.data);
|
|
1333
|
+
if (data === undefined)
|
|
1334
|
+
continue;
|
|
1335
|
+
const stage = labelOf(node, "?");
|
|
1336
|
+
for (const lane of asArrayValue(data.tasks)) {
|
|
1337
|
+
for (const task of asArrayValue(lane)) {
|
|
1338
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1339
|
+
return found;
|
|
1340
|
+
const record = asRecordValue(task);
|
|
1341
|
+
const type = record?.type;
|
|
1342
|
+
const taskData2 = asRecordValue(record?.data);
|
|
1343
|
+
if (typeof type !== "string" || !NAME_BOUND_TASK_TYPES.has(type) || taskData2 === undefined) {
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (RESOLVED_IDENTITY_KEYS.some((k) => (k in taskData2)))
|
|
1347
|
+
continue;
|
|
1348
|
+
const fields = ["name", "folderPath"].filter((field) => {
|
|
1349
|
+
const value = taskData2[field];
|
|
1350
|
+
return typeof value === "string" && value !== "" && !value.startsWith(BINDING_REFERENCE_PREFIX);
|
|
1351
|
+
});
|
|
1352
|
+
if (fields.length === 0)
|
|
1353
|
+
continue;
|
|
1354
|
+
const name = labelOf(record, type);
|
|
1355
|
+
found.push({
|
|
1356
|
+
path: `nodes[${stage}].data.tasks[${name}].data`,
|
|
1357
|
+
name,
|
|
1358
|
+
fields
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return found;
|
|
1364
|
+
};
|
|
1365
|
+
var eachContext = (parsed, onContext, stop) => {
|
|
1366
|
+
const visit = (value, path) => {
|
|
1367
|
+
if (stop())
|
|
1368
|
+
return;
|
|
1369
|
+
if (Array.isArray(value)) {
|
|
1370
|
+
value.forEach((entry, index) => {
|
|
1371
|
+
visit(entry, `${path}[${index}]`);
|
|
1372
|
+
});
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const record = asRecordValue(value);
|
|
1376
|
+
if (record === undefined)
|
|
1377
|
+
return;
|
|
1378
|
+
const entries = contextEntries(record.context);
|
|
1379
|
+
if (entries.length > 0)
|
|
1380
|
+
onContext(entries, path);
|
|
1381
|
+
for (const [name, child] of Object.entries(record)) {
|
|
1382
|
+
if (name === "context")
|
|
1383
|
+
continue;
|
|
1384
|
+
visit(child, path === "" ? name : `${path}.${name}`);
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
visit(parsed.nodes, "nodes");
|
|
1388
|
+
visit(parsed.edges, "edges");
|
|
1389
|
+
visit(asRecordValue(parsed.metadata)?.caseExitRules, "metadata.caseExitRules");
|
|
1390
|
+
};
|
|
1391
|
+
var bindingsById = (parsed) => {
|
|
1392
|
+
const map = new Map;
|
|
1393
|
+
for (const entry of asArrayValue(parsed.bindings)) {
|
|
1394
|
+
const record = asRecordValue(entry);
|
|
1395
|
+
const id = record?.id;
|
|
1396
|
+
if (record !== undefined && typeof id === "string" && id !== "") {
|
|
1397
|
+
if (!map.has(id))
|
|
1398
|
+
map.set(id, record);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return map;
|
|
1402
|
+
};
|
|
1403
|
+
var findConnectorResourceKeyMismatches = (parsed) => {
|
|
1404
|
+
const bindings = bindingsById(parsed);
|
|
1405
|
+
if (bindings.size === 0)
|
|
1406
|
+
return [];
|
|
1407
|
+
const found = [];
|
|
1408
|
+
eachContext(parsed, (entries, path) => {
|
|
1409
|
+
const named = (name) => entries.find((entry) => entry.name === name)?.value;
|
|
1410
|
+
const key = named("resourceKey");
|
|
1411
|
+
const connection = named("connection");
|
|
1412
|
+
if (typeof key !== "string" || key === "" || typeof connection !== "string" || connection === "") {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (key.startsWith("=") || bindings.has(key))
|
|
1416
|
+
return;
|
|
1417
|
+
const id = connection.startsWith(BINDING_REFERENCE_PREFIX) ? connection.slice(BINDING_REFERENCE_PREFIX.length) : connection;
|
|
1418
|
+
const binding = bindings.get(id);
|
|
1419
|
+
if (binding === undefined)
|
|
1420
|
+
return;
|
|
1421
|
+
const accepted = [binding.resourceKey, binding.default].filter((candidate) => typeof candidate === "string" && candidate !== "");
|
|
1422
|
+
if (accepted.length === 0 || accepted.includes(key))
|
|
1423
|
+
return;
|
|
1424
|
+
let elsewhere;
|
|
1425
|
+
for (const [candidateId, candidate] of bindings) {
|
|
1426
|
+
if (candidateId === id)
|
|
1427
|
+
continue;
|
|
1428
|
+
const resource = candidate.resource;
|
|
1429
|
+
if (typeof resource !== "string" || resource.toLowerCase() !== "connection") {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
if (candidate.resourceKey === key || candidate.default === key) {
|
|
1433
|
+
elsewhere = candidateId;
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
found.push({
|
|
1438
|
+
path: `${path}.context[resourceKey]`,
|
|
1439
|
+
value: key,
|
|
1440
|
+
binding: id,
|
|
1441
|
+
elsewhere
|
|
1442
|
+
});
|
|
1443
|
+
}, () => found.length >= AUTHORING_MARKER_LIMIT);
|
|
1444
|
+
return found;
|
|
1445
|
+
};
|
|
1446
|
+
var findUnresolvedConnectorResourceKeys = (parsed) => {
|
|
1447
|
+
const bindingIds = new Set(bindingsById(parsed).keys());
|
|
1448
|
+
if (bindingIds.size === 0)
|
|
1449
|
+
return [];
|
|
1450
|
+
const found = [];
|
|
1451
|
+
eachContext(parsed, (entries, path) => {
|
|
1452
|
+
const key = entries.find((candidate) => candidate.name === "resourceKey")?.value;
|
|
1453
|
+
if (typeof key === "string" && key !== "" && (bindingIds.has(key) || key.startsWith(BINDING_REFERENCE_PREFIX))) {
|
|
1454
|
+
found.push({
|
|
1455
|
+
path: `${path}.context[resourceKey]`,
|
|
1456
|
+
value: key
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
}, () => found.length >= AUTHORING_MARKER_LIMIT);
|
|
1460
|
+
return found;
|
|
1461
|
+
};
|
|
1462
|
+
var findDuplicateOutputIds = (parsed) => {
|
|
1463
|
+
const found = [];
|
|
1464
|
+
const owners = new Map;
|
|
1465
|
+
const reported = new Set;
|
|
1466
|
+
for (const node of asArrayValue(parsed.nodes)) {
|
|
1467
|
+
const record = asRecordValue(node);
|
|
1468
|
+
const data = asRecordValue(record?.data);
|
|
1469
|
+
if (record === undefined || data === undefined)
|
|
1470
|
+
continue;
|
|
1471
|
+
const stage = labelOf(record, "?");
|
|
1472
|
+
for (const lane of asArrayValue(data.tasks)) {
|
|
1473
|
+
for (const task of asArrayValue(lane)) {
|
|
1474
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1475
|
+
return found;
|
|
1476
|
+
const taskRecord = asRecordValue(task);
|
|
1477
|
+
const taskData2 = asRecordValue(taskRecord?.data);
|
|
1478
|
+
if (taskRecord === undefined || taskData2 === undefined) {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
const name = labelOf(taskRecord, "?");
|
|
1482
|
+
for (const output of asArrayValue(taskData2.outputs)) {
|
|
1483
|
+
const id = asRecordValue(output)?.id;
|
|
1484
|
+
if (typeof id !== "string" || id === "")
|
|
1485
|
+
continue;
|
|
1486
|
+
const owner = `${stage}/${name}`;
|
|
1487
|
+
const previous = owners.get(id);
|
|
1488
|
+
if (previous === undefined) {
|
|
1489
|
+
owners.set(id, owner);
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
if (reported.has(id))
|
|
1493
|
+
continue;
|
|
1494
|
+
reported.add(id);
|
|
1495
|
+
found.push({
|
|
1496
|
+
path: `nodes[${stage}].data.tasks[${name}].data.outputs[${id}]`,
|
|
1497
|
+
id,
|
|
1498
|
+
first: previous,
|
|
1499
|
+
second: owner
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return found;
|
|
1506
|
+
};
|
|
1507
|
+
var findDuplicateSlaIds = (parsed, nodes) => {
|
|
1508
|
+
const holders = new Map;
|
|
1509
|
+
const note = (rules, path, where) => {
|
|
1510
|
+
asArrayValue(rules).forEach((entry, index) => {
|
|
1511
|
+
const id = asRecordValue(entry)?.id;
|
|
1512
|
+
if (typeof id !== "string" || id === "")
|
|
1513
|
+
return;
|
|
1514
|
+
holders.set(id, [
|
|
1515
|
+
...holders.get(id) ?? [],
|
|
1516
|
+
{ path: `${path}[${index}]`, where }
|
|
1517
|
+
]);
|
|
1518
|
+
});
|
|
1519
|
+
};
|
|
1520
|
+
note(asRecordValue(parsed.metadata)?.slaRules, "metadata.slaRules", "the case level");
|
|
1521
|
+
for (const node of nodes) {
|
|
1522
|
+
const data = asRecordValue(node.data);
|
|
1523
|
+
if (data === undefined)
|
|
1524
|
+
continue;
|
|
1525
|
+
const stage = labelOf(node, "?");
|
|
1526
|
+
note(data.slaRules, `nodes[${stage}].data.slaRules`, `"${stage}"`);
|
|
1527
|
+
}
|
|
1528
|
+
const found = [];
|
|
1529
|
+
for (const [id, carried] of holders) {
|
|
1530
|
+
if (carried.length < 2)
|
|
1531
|
+
continue;
|
|
1532
|
+
for (const [at, holder] of carried.entries()) {
|
|
1533
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1534
|
+
return found;
|
|
1535
|
+
const others = carried.filter((_, other) => other !== at);
|
|
1536
|
+
found.push({
|
|
1537
|
+
path: holder.path,
|
|
1538
|
+
id,
|
|
1539
|
+
sameHolder: others.filter((other) => other.where === holder.where).length,
|
|
1540
|
+
otherHolders: [
|
|
1541
|
+
...new Set(others.filter((other) => other.where !== holder.where).map((other) => other.where))
|
|
1542
|
+
],
|
|
1543
|
+
where: holder.where
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return found;
|
|
1548
|
+
};
|
|
1549
|
+
var CONNECTOR_BINDING_ATTRIBUTES = new Set([
|
|
1550
|
+
"connectionid",
|
|
1551
|
+
"folderkey",
|
|
1552
|
+
"connection"
|
|
1553
|
+
]);
|
|
1554
|
+
var CONNECTOR_BINDING_RESOURCES = new Set(["connection", "folder"]);
|
|
1555
|
+
var findIncompleteBindingPairs = (parsed) => {
|
|
1556
|
+
const attributes = new Map;
|
|
1557
|
+
for (const entry of asArrayValue(parsed.bindings)) {
|
|
1558
|
+
const binding = asRecordValue(entry);
|
|
1559
|
+
const key = binding?.resourceKey;
|
|
1560
|
+
const attribute = binding?.propertyAttribute;
|
|
1561
|
+
const resource = binding?.resource;
|
|
1562
|
+
if (typeof key !== "string" || key === "")
|
|
1563
|
+
continue;
|
|
1564
|
+
if (typeof attribute !== "string" || attribute === "")
|
|
1565
|
+
continue;
|
|
1566
|
+
if (CONNECTOR_BINDING_ATTRIBUTES.has(attribute.toLowerCase()))
|
|
1567
|
+
continue;
|
|
1568
|
+
if (typeof resource === "string" && CONNECTOR_BINDING_RESOURCES.has(resource.toLowerCase())) {
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
const seen = attributes.get(key) ?? new Set;
|
|
1572
|
+
seen.add(attribute);
|
|
1573
|
+
attributes.set(key, seen);
|
|
1574
|
+
}
|
|
1575
|
+
const found = [];
|
|
1576
|
+
for (const [key, seen] of attributes) {
|
|
1577
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1578
|
+
return found;
|
|
1579
|
+
const hasName = seen.has("name");
|
|
1580
|
+
const hasFolder = seen.has("folderPath");
|
|
1581
|
+
if (hasName && hasFolder)
|
|
1582
|
+
continue;
|
|
1583
|
+
found.push({
|
|
1584
|
+
key,
|
|
1585
|
+
present: [...seen].sort((a, b) => a.localeCompare(b)).join(", "),
|
|
1586
|
+
missing: hasName ? "folderPath" : "name"
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
return found;
|
|
1590
|
+
};
|
|
1591
|
+
var findRecipientShapeDefects = (nodes) => {
|
|
1592
|
+
const found = [];
|
|
1593
|
+
for (const node of nodes) {
|
|
1594
|
+
const data = asRecordValue(node.data);
|
|
1595
|
+
if (data === undefined)
|
|
1596
|
+
continue;
|
|
1597
|
+
const stage = labelOf(node, "?");
|
|
1598
|
+
for (const lane of asArrayValue(data.tasks)) {
|
|
1599
|
+
for (const task of asArrayValue(lane)) {
|
|
1600
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1601
|
+
return found;
|
|
1602
|
+
const record = asRecordValue(task);
|
|
1603
|
+
const taskData2 = asRecordValue(record?.data);
|
|
1604
|
+
const recipient = taskData2?.recipient;
|
|
1605
|
+
if (recipient === undefined || recipient === null)
|
|
1606
|
+
continue;
|
|
1607
|
+
if (asRecordValue(recipient) !== undefined)
|
|
1608
|
+
continue;
|
|
1609
|
+
const name = labelOf(record, typeof record?.type === "string" ? record.type : "?");
|
|
1610
|
+
found.push({
|
|
1611
|
+
path: `nodes[${stage}].data.tasks[${name}].data.recipient`,
|
|
1612
|
+
name,
|
|
1613
|
+
actual: Array.isArray(recipient) ? "an array" : `a ${typeof recipient}`
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return found;
|
|
1619
|
+
};
|
|
1620
|
+
var CONDITION_KEYS = {
|
|
1621
|
+
entry: new Set(["id", "displayName", "rules", "isInterrupting"]),
|
|
1622
|
+
exit: new Set([
|
|
1623
|
+
"id",
|
|
1624
|
+
"displayName",
|
|
1625
|
+
"rules",
|
|
1626
|
+
"type",
|
|
1627
|
+
"exitToStageId",
|
|
1628
|
+
"marksStageComplete"
|
|
1629
|
+
]),
|
|
1630
|
+
caseExit: new Set(["id", "displayName", "rules", "marksCaseComplete"])
|
|
1631
|
+
};
|
|
1632
|
+
var CONDITION_EXPRESSION_KEY = "conditionExpression";
|
|
1633
|
+
var CONDITION_RULE_KEYS = new Set(["rule", "ruleType"]);
|
|
1634
|
+
var findUnknownConditionKeys = (nodes, metadata, options) => {
|
|
1635
|
+
const found = [];
|
|
1636
|
+
const check = (condition, allowed, path) => {
|
|
1637
|
+
const record = asRecordValue(condition);
|
|
1638
|
+
if (record === undefined)
|
|
1639
|
+
return;
|
|
1640
|
+
for (const key of Object.keys(record)) {
|
|
1641
|
+
if (found.length >= AUTHORING_MARKER_LIMIT)
|
|
1642
|
+
return;
|
|
1643
|
+
if (allowed.has(key))
|
|
1644
|
+
continue;
|
|
1645
|
+
const lower = key.toLowerCase();
|
|
1646
|
+
const nearMiss = [...allowed].find((candidate) => candidate.length >= 4 && candidate.toLowerCase() !== lower && lower.includes(candidate.toLowerCase()));
|
|
1647
|
+
found.push({
|
|
1648
|
+
path: `${path}.${key}`,
|
|
1649
|
+
key,
|
|
1650
|
+
...nearMiss !== undefined && { nearMiss }
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
const named = (condition, at) => {
|
|
1655
|
+
const record = asRecordValue(condition);
|
|
1656
|
+
const label = record?.displayName ?? record?.id;
|
|
1657
|
+
return typeof label === "string" && label !== "" ? label : String(at);
|
|
1658
|
+
};
|
|
1659
|
+
for (const node of nodes) {
|
|
1660
|
+
const data = asRecordValue(node.data);
|
|
1661
|
+
if (data === undefined)
|
|
1662
|
+
continue;
|
|
1663
|
+
const stage = labelOf(node, "?");
|
|
1664
|
+
for (const [slot, allowed] of [
|
|
1665
|
+
["entryConditions", CONDITION_KEYS.entry],
|
|
1666
|
+
["exitConditions", CONDITION_KEYS.exit]
|
|
1667
|
+
]) {
|
|
1668
|
+
asArrayValue(data[slot]).forEach((condition, at) => {
|
|
1669
|
+
check(condition, allowed, `nodes[${stage}].data.${slot}[${named(condition, at)}]`);
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
for (const lane of options.includeTaskSlots ? asArrayValue(data.tasks) : []) {
|
|
1673
|
+
for (const task of asArrayValue(lane)) {
|
|
1674
|
+
const taskRecord = asRecordValue(task);
|
|
1675
|
+
if (taskRecord === undefined)
|
|
1676
|
+
continue;
|
|
1677
|
+
const name = labelOf(taskRecord, "?");
|
|
1678
|
+
asArrayValue(taskRecord.entryConditions).forEach((condition, at) => {
|
|
1679
|
+
check(condition, CONDITION_KEYS.entry, `nodes[${stage}].data.tasks[${name}].entryConditions[${named(condition, at)}]`);
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
asArrayValue(metadata.caseExitRules).forEach((condition, at) => {
|
|
1685
|
+
check(condition, CONDITION_KEYS.caseExit, `metadata.caseExitRules[${named(condition, at)}]`);
|
|
1686
|
+
});
|
|
1687
|
+
return found;
|
|
1688
|
+
};
|
|
1689
|
+
var findRuleTypeKeys = (document) => {
|
|
1690
|
+
const found = [];
|
|
1691
|
+
const visit = (value, path) => {
|
|
1692
|
+
if (found.length >= RULE_DISCRIMINATOR_LIMIT)
|
|
1693
|
+
return;
|
|
1694
|
+
if (Array.isArray(value)) {
|
|
1695
|
+
value.forEach((entry, index) => {
|
|
1696
|
+
visit(entry, `${path}[${index}]`);
|
|
1697
|
+
});
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
if (value === null || typeof value !== "object")
|
|
1701
|
+
return;
|
|
1702
|
+
const record = value;
|
|
1703
|
+
if (typeof record.ruleType === "string" && record.rule === undefined) {
|
|
1704
|
+
found.push(path);
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
1708
|
+
visit(entry, path === "" ? key : `${path}.${key}`);
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
visit(document, "");
|
|
1712
|
+
return found;
|
|
1713
|
+
};
|
|
603
1714
|
|
|
604
1715
|
class CaseValidateService {
|
|
605
1716
|
fs;
|
|
@@ -699,6 +1810,8 @@ Validating: ${absolutePath}
|
|
|
699
1810
|
}
|
|
700
1811
|
runSupplementalRules(parsed, profile) {
|
|
701
1812
|
const issues = [];
|
|
1813
|
+
const taskContent = checksTaskContent(profile);
|
|
1814
|
+
const slaContent = profile !== "skeleton";
|
|
702
1815
|
const name = parsed.name;
|
|
703
1816
|
if (typeof name !== "string" || name.trim() === "") {
|
|
704
1817
|
issues.push({
|
|
@@ -709,6 +1822,197 @@ Validating: ${absolutePath}
|
|
|
709
1822
|
});
|
|
710
1823
|
}
|
|
711
1824
|
const nodes = Array.isArray(parsed.nodes) ? parsed.nodes : [];
|
|
1825
|
+
for (const markerPath of findXrefMarkers(parsed)) {
|
|
1826
|
+
issues.push({
|
|
1827
|
+
code: "CASE_MGMT_XREF_UNRESOLVED",
|
|
1828
|
+
path: markerPath,
|
|
1829
|
+
severity: "error",
|
|
1830
|
+
message: 'This value holds an unresolved "$xref(...)" authoring ' + "marker. Nothing expands it — packed and deployed it stays " + "a literal string, so whatever reads it never sees the id " + "it was standing in for. Replace it with the id or " + "expression it refers to."
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
for (const { path, slot } of taskContent ? findPlanningNotation(nodes) : []) {
|
|
1834
|
+
issues.push({
|
|
1835
|
+
code: "CASE_MGMT_PLANNING_NOTATION",
|
|
1836
|
+
path,
|
|
1837
|
+
severity: "error",
|
|
1838
|
+
message: `This ${slot === "inputs" ? "input" : "output"} value ` + `starts with the SDD's planning notation ("<-" or "->"), ` + "which describes where a value comes from rather than " + "being a value. Replace it with the expression it " + 'describes — an "=vars.<id>" reference, an "=bindings.<id>" ' + "reference, or a literal."
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
for (const { path, name: name2 } of taskContent ? findUnparseableJsonValues(nodes) : []) {
|
|
1842
|
+
issues.push({
|
|
1843
|
+
code: "CASE_MGMT_VALUE_JSON_INVALID",
|
|
1844
|
+
path,
|
|
1845
|
+
severity: "error",
|
|
1846
|
+
message: `Value of "${name2}" opens with "{" or "[" but is not ` + "valid JSON, so the runtime cannot read it as the " + "structure it announces. A truncated copy, or a markdown " + "fence character (`) picked up with the value, is the " + "usual cause."
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
for (const { path, name: name2, kind, target } of taskContent ? findConnectorInputDefects(nodes) : []) {
|
|
1850
|
+
const CONNECTOR_INPUT_MESSAGES = {
|
|
1851
|
+
flattened: `Connector input "${name2}" is not one of the three containers an Integration Service activity declares (pathParameters, queryParameters, body). A field spliced in at the top level is not passed: put it inside the container's \`body\` object.`,
|
|
1852
|
+
stringified: `Connector input "${name2}" carries its payload as JSON text in \`value\`. The container takes a \`body\` object, so a serialised string is passed through as a string and the activity receives no fields.`,
|
|
1853
|
+
misplaced: `Connector input "${name2}" carries its fields in \`value\` and has no \`body\`. A json container is read from \`body\` — \`value\` holds a scalar, and only for a string-typed entry — so the activity runs with this container empty. Move the object into \`body\`.`,
|
|
1854
|
+
mistargeted: `Connector input "${name2}" targets \`${target ?? ""}\`. Every reader keys on the input's \`name\`, and the three containers are emitted with \`target\` equal to it, so this fills \`${name2}\` and leaves \`${target ?? ""}\` empty. Rename the entry to the container its fields belong to, or set \`target\` to match \`${name2}\`.`
|
|
1855
|
+
};
|
|
1856
|
+
const CONNECTOR_INPUT_CODES = {
|
|
1857
|
+
flattened: "CASE_MGMT_CONNECTOR_INPUT_FLATTENED",
|
|
1858
|
+
stringified: "CASE_MGMT_CONNECTOR_INPUT_STRINGIFIED",
|
|
1859
|
+
misplaced: "CASE_MGMT_CONNECTOR_INPUT_VALUE_NOT_BODY",
|
|
1860
|
+
mistargeted: "CASE_MGMT_CONNECTOR_INPUT_TARGET_MISMATCH"
|
|
1861
|
+
};
|
|
1862
|
+
issues.push({
|
|
1863
|
+
code: CONNECTOR_INPUT_CODES[kind],
|
|
1864
|
+
path,
|
|
1865
|
+
severity: "error",
|
|
1866
|
+
message: CONNECTOR_INPUT_MESSAGES[kind]
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
for (const { path, name: name2 } of taskContent ? findReassignedOutputsWithoutOriginalVar(parsed) : []) {
|
|
1870
|
+
issues.push({
|
|
1871
|
+
code: "CASE_MGMT_OUTPUT_ORIGINAL_VAR_MISSING",
|
|
1872
|
+
path,
|
|
1873
|
+
severity: "error",
|
|
1874
|
+
message: `Output "${name2}" writes to a variable other than its ` + "own id but carries no `originalVar`. That field is how " + "the designer recognises a reassigned output: without " + "it the output reads as a plain one and goes down the " + "path that allocates a fresh variable by name, so the " + "plan the designer opens is not the one written here. " + "Set `originalVar` to this output's own id."
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
for (const { path, name: name2 } of taskContent ? findVarIdCaseMismatches(parsed) : []) {
|
|
1878
|
+
issues.push({
|
|
1879
|
+
code: "CASE_MGMT_OUTPUT_VAR_ID_CASE_MISMATCH",
|
|
1880
|
+
path,
|
|
1881
|
+
severity: "error",
|
|
1882
|
+
message: `Output "${name2}" has an id and a var that differ only ` + "by casing. A renamed output keeps its own id on " + "purpose, but nothing renames a value to the same word " + "in a different case — so this is one name typed two " + "ways, and the case variable it declares carries a " + "spelling no reference written from the id will match. " + "Make the two agree."
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
for (const { path, name: name2, hasValue } of taskContent ? findMisplacedEnvelopeBodies(nodes) : []) {
|
|
1886
|
+
issues.push({
|
|
1887
|
+
code: "CASE_MGMT_INPUT_BODY_ON_PLAIN_TASK",
|
|
1888
|
+
path,
|
|
1889
|
+
severity: "error",
|
|
1890
|
+
message: `Input "${name2}" carries its fields in \`body\`, which ` + "only a connector input has. This task type reads " + `\`value\`, so ${hasValue ? "the fields under `body` are ignored and the task runs on `value` alone" : "nothing reaches the task for it at all"}. ` + "Move the object into `value` — as a native object, or " + "JSON text if that is what the task expects."
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
for (const { path, holder, dead } of slaContent ? findUnreachableSlaRules(parsed) : []) {
|
|
1894
|
+
issues.push({
|
|
1895
|
+
code: "CASE_MGMT_SLA_RULE_UNREACHABLE",
|
|
1896
|
+
path,
|
|
1897
|
+
severity: "error",
|
|
1898
|
+
message: `This SLA rule on ${holder} is unconditional, and ` + `${dead} rule${dead === 1 ? "" : "s"} follow${dead === 1 ? "s" : ""} it. ` + "The first rule whose expression holds is the one that " + "applies, so everything after an unconditional rule is " + "dead — a conditional override written below the default " + "reads as configured and enforces the default instead. " + "Move the unconditional rule last."
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
for (const { path, scope, expression, count } of slaContent ? findDuplicateSlaGates(parsed) : []) {
|
|
1902
|
+
issues.push({
|
|
1903
|
+
code: "CASE_MGMT_SLA_RULE_DUPLICATE",
|
|
1904
|
+
path,
|
|
1905
|
+
severity: "error",
|
|
1906
|
+
message: `${count} SLA rules on ${scope} share the gate ` + `\`${expression}\`. The first rule whose expression holds ` + "is the one that applies, so every rule after it on the " + "same gate is unreachable — including any escalation only " + "it carries. One SLA split across two rules like this " + "looks complete rule by rule and enforces only the first."
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
for (const { key, types, ids } of taskContent ? findSharedBindingKeys(parsed) : []) {
|
|
1910
|
+
issues.push({
|
|
1911
|
+
code: "CASE_MGMT_BINDING_KEY_SHARED",
|
|
1912
|
+
path: `bindings[${ids.join(", ")}].resourceKey`,
|
|
1913
|
+
severity: "error",
|
|
1914
|
+
message: `${types.length} kinds of resource (${types.join(", ")}) ` + `resolve to one resourceKey "${key}". A resource is ` + 'addressed by folderPath + "." + name, so one key is one ' + "resource: 'uip solution resources refresh' will resolve a " + "single resource and bind it for all of them. Each " + "binding's `name` default has to be that resource's own " + "name, not the folder's."
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
for (const { path, id, sameHolder, otherHolders, where } of slaContent ? findDuplicateSlaIds(parsed, nodes) : []) {
|
|
1918
|
+
const repeats = sameHolder > 0 ? `${sameHolder} other rule${sameHolder === 1 ? "" : "s"} in ${where}'s own \`slaRules\`` : "";
|
|
1919
|
+
const elsewhere = otherHolders.length > 0 ? `${otherHolders.join(" and ")}` : "";
|
|
1920
|
+
issues.push({
|
|
1921
|
+
code: "CASE_MGMT_SLA_ID_DUPLICATE",
|
|
1922
|
+
path,
|
|
1923
|
+
severity: "error",
|
|
1924
|
+
message: `SLA rule id "${id}" is also carried by ` + `${[repeats, elsewhere].filter((part) => part !== "").join(", and by ")}. ` + "`sla-status-change` entry " + "rules key on `slaId`, so anything referring to this id " + "is ambiguous — there is no way to say which SLA a " + "threshold or an escalation belongs to. Give each SLA " + "rule its own id."
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
for (const { path, key, nearMiss } of slaContent ? findUnknownSlaKeys(parsed, nodes) : []) {
|
|
1928
|
+
issues.push({
|
|
1929
|
+
code: "CASE_MGMT_SLA_RULE_UNKNOWN_KEY",
|
|
1930
|
+
path,
|
|
1931
|
+
severity: "error",
|
|
1932
|
+
message: `\`${key}\` is not part of an SLA rule. ` + (nearMiss === undefined ? "The shape is closed, so the runtime never reads " + "this key and whatever it holds is lost." : `The key is \`${nearMiss}\` — move the value ` + "across. The schema neither migrates nor rejects " + `\`${key}\`, so the plan validates while the ` + "field the runtime reads is absent and the SLA " + "ends up with no value at all.")
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
for (const { key, present, missing } of taskContent ? findIncompleteBindingPairs(parsed) : []) {
|
|
1936
|
+
issues.push({
|
|
1937
|
+
code: "CASE_MGMT_BINDING_PAIR_INCOMPLETE",
|
|
1938
|
+
path: `bindings[${key}]`,
|
|
1939
|
+
severity: "error",
|
|
1940
|
+
message: `resourceKey "${key}" has ${present} but no \`${missing}\` ` + 'entry. A resource is addressed by folderPath + "." + ' + "name, so one key is one resource and it needs both " + "entries sharing that key: 'uip solution resources " + "refresh' resolves the pair, and bindings_v2.json is " + "derived from it, so half a pair ships a solution that " + "resolves this resource to nothing. Give both entries the " + "same resourceKey and set each one's propertyAttribute to " + "the half it carries."
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
for (const { path, name: name2, fields } of taskContent ? findLiteralResourceBindings(nodes) : []) {
|
|
1944
|
+
issues.push({
|
|
1945
|
+
code: "CASE_MGMT_RESOURCE_TASK_LITERAL_BINDING",
|
|
1946
|
+
path,
|
|
1947
|
+
severity: "error",
|
|
1948
|
+
message: `Task "${name2}" names its resource with ` + `${fields.length === 1 ? "a literal" : "literals"}: ` + `${fields.join(" and ")}. A resource task's \`name\` and ` + `\`folderPath\` are \`${BINDING_REFERENCE_PREFIX}<id>\` ` + "references — the bindings array is what 'uip solution " + "resources refresh' resolves and what bindings_v2.json is " + "derived from, so a plain string names something the " + "solution never resolves and no binding exists for it. " + "Add the name/folderPath binding pair and point both " + "fields at it."
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
for (const { path, name: name2, actual } of taskContent ? findRecipientShapeDefects(nodes) : []) {
|
|
1952
|
+
issues.push({
|
|
1953
|
+
code: "CASE_MGMT_ACTION_RECIPIENT_SHAPE",
|
|
1954
|
+
path,
|
|
1955
|
+
severity: "error",
|
|
1956
|
+
message: `Task "${name2}" writes \`recipient\` as ${actual}. The ` + 'shape is `{ "Type": <n>, "Value": "<who>" }` — a bare ' + "value carries no type, so the assignment resolves to " + "nobody and the task is created unassigned. Wrap it, " + "keeping the value."
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
for (const { path, value } of taskContent ? findUnresolvedConnectorResourceKeys(parsed) : []) {
|
|
1960
|
+
issues.push({
|
|
1961
|
+
code: "CASE_MGMT_CONNECTOR_RESOURCE_KEY_UNRESOLVED",
|
|
1962
|
+
path,
|
|
1963
|
+
severity: "error",
|
|
1964
|
+
message: `\`resourceKey\` is "${value}", which names a binding ` + "rather than a resource. It carries the *resolved* " + "connection key, so a reader looking for the connection " + "id here finds a name that means nothing to it and the " + "connector is unresolved — while every other check " + "passes, because the entry has a value and the sibling " + "`connection` reference resolves. Put the connection key " + "the binding resolves to here, not the binding's id."
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
for (const { path, value, binding, elsewhere } of taskContent ? findConnectorResourceKeyMismatches(parsed) : []) {
|
|
1968
|
+
issues.push({
|
|
1969
|
+
code: "CASE_MGMT_CONNECTOR_RESOURCE_KEY_MISMATCH",
|
|
1970
|
+
path,
|
|
1971
|
+
severity: "error",
|
|
1972
|
+
message: `This context's \`resourceKey\` is "${value}", which is ` + `not the key of the connection \`${binding}\` that its ` + "own `connection` entry names. The runtime resolves the " + "connection through this key, so the connector never " + `fires. ${elsewhere === undefined ? "The usual cause is the objectName copied into it. Set it to that binding's resolved connection key." : `That key belongs to \`${elsewhere}\`, so the wrong field may be the \`connection\` entry: decide which connection this context is for, then make both name it. Setting the key from \`${binding}\` without checking repairs this into a plan that is consistently wrong.`}`
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
for (const { path, name: name2 } of taskContent ? findConnectorTasksWithoutInputs(nodes) : []) {
|
|
1976
|
+
issues.push({
|
|
1977
|
+
code: "CASE_MGMT_CONNECTOR_INPUTS_EMPTY",
|
|
1978
|
+
path,
|
|
1979
|
+
severity: "error",
|
|
1980
|
+
message: `Connector task "${name2}" has a spliced \`data.context\` ` + "but no inputs. The context comes from the case spec " + "response, so the spec was fetched and read; " + "`data.inputs` is the `CaseShape.Inputs` splice from that " + "same response, and an empty array means that splice was " + "skipped. Nothing else reports it — the shape is " + "internally consistent and every container check has " + "nothing to look at — while the activity runs with no " + "parameters at all. Splice `CaseShape.Inputs` from the " + "case spec response."
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
for (const { path, id, first, second } of taskContent ? findDuplicateOutputIds(parsed) : []) {
|
|
1984
|
+
issues.push({
|
|
1985
|
+
code: "CASE_MGMT_OUTPUT_ID_DUPLICATE",
|
|
1986
|
+
path,
|
|
1987
|
+
severity: "error",
|
|
1988
|
+
message: `Output id "${id}" is used by two tasks (${first} and ` + `${second}). An output id is the write target, so the ` + "second write lands on the first task's variable and one " + "of the two values is gone. Give each output its own id."
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
for (const { path, name: name2 } of taskContent ? findOutputsWithoutId(parsed) : []) {
|
|
1992
|
+
issues.push({
|
|
1993
|
+
code: "CASE_MGMT_OUTPUT_ID_MISSING",
|
|
1994
|
+
path,
|
|
1995
|
+
severity: "error",
|
|
1996
|
+
message: `Output "${name2}" carries no id. The designer generates ` + "one for every output it writes, and without it nothing " + "can reference this output — every expression that reads " + "it resolves to nothing."
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
for (const { key, resource } of findBindingKeyMismatches(parsed)) {
|
|
2000
|
+
issues.push({
|
|
2001
|
+
code: "CASE_MGMT_BINDING_KEY_MISMATCH",
|
|
2002
|
+
path: `bindings[${key}]`,
|
|
2003
|
+
severity: "error",
|
|
2004
|
+
message: `The resourceKey of this ${resource} binding does not ` + "match the folderPath and name its own entries declare. " + "The key addresses the resource in the solution, so a key " + "built from anything else resolves nothing at deploy " + "time. Rebuild it from this pair's own folderPath and " + "name defaults."
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
const referenceableIds = collectReferenceableIds(parsed);
|
|
2008
|
+
for (const { path, id } of findUnboundReferences(parsed, referenceableIds)) {
|
|
2009
|
+
issues.push({
|
|
2010
|
+
code: "CASE_MGMT_REFERENCE_UNBOUND",
|
|
2011
|
+
path,
|
|
2012
|
+
severity: "error",
|
|
2013
|
+
message: `"vars.${id}" resolves to nothing: no input or output ` + "in this case plan carries that id, name or var. " + "Whatever reads it gets no value at run time. Point it " + "at the entry that produces the data."
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
712
2016
|
const seen = new Set;
|
|
713
2017
|
const reported = new Set;
|
|
714
2018
|
for (const node of nodes) {
|
|
@@ -730,6 +2034,23 @@ Validating: ${absolutePath}
|
|
|
730
2034
|
issues.push(...this.checkEntryRuleSlots(nodes, {
|
|
731
2035
|
includeTaskSlots: checksTaskContent(profile)
|
|
732
2036
|
}));
|
|
2037
|
+
for (const { path, key, nearMiss } of findUnknownConditionKeys(nodes, asRecordValue(parsed.metadata) ?? {}, { includeTaskSlots: checksTaskContent(profile) })) {
|
|
2038
|
+
if (key === CONDITION_EXPRESSION_KEY) {
|
|
2039
|
+
issues.push({
|
|
2040
|
+
code: "CASE_MGMT_CONDITION_EXPR_HOISTED",
|
|
2041
|
+
path,
|
|
2042
|
+
severity: "error",
|
|
2043
|
+
message: "`conditionExpression` sits on the condition " + "object. The scheduler reads it from a rule " + "inside `rules[][]`, so here it is ignored and " + "the condition fires **ungated** — which is " + "worse than not firing at all. Move it onto the " + "rule it is meant to guard."
|
|
2044
|
+
});
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2047
|
+
issues.push({
|
|
2048
|
+
code: "CASE_MGMT_CONDITION_UNKNOWN_KEY",
|
|
2049
|
+
path,
|
|
2050
|
+
severity: "error",
|
|
2051
|
+
message: `\`${key}\` is not part of a condition. ` + (CONDITION_RULE_KEYS.has(key) ? "A rule lives inside `rules[][]`, not on the " + "condition: the condition carries the display " + "name and the flags, the inner array carries " + "the rules. Flattened here it is never " + "evaluated, and the condition reads as having " + "no rules at all." : nearMiss === undefined ? "The shape is closed, so the runtime never " + "reads this key and whatever it holds is lost." : `The key is \`${nearMiss}\` — rename it and ` + `keep the value. The schema neither migrates ` + `nor rejects \`${key}\`, so the plan ` + `validates while \`${nearMiss}\` reads as ` + `absent, which is how a condition ends up ` + `missing the field it was written to carry.`)
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
733
2054
|
}
|
|
734
2055
|
return issues;
|
|
735
2056
|
}
|
|
@@ -845,12 +2166,36 @@ Validating: ${absolutePath}
|
|
|
845
2166
|
});
|
|
846
2167
|
}
|
|
847
2168
|
if (diskResult.migrationError) {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
2169
|
+
const ruleTypePaths = findRuleTypeKeys(parsed);
|
|
2170
|
+
if (ruleTypePaths.length > 0) {
|
|
2171
|
+
for (const rulePath of ruleTypePaths) {
|
|
2172
|
+
issues.push({
|
|
2173
|
+
code: "INVALID_RULE_DISCRIMINATOR",
|
|
2174
|
+
path: rulePath,
|
|
2175
|
+
message: 'This rule is keyed "ruleType"; the discriminator ' + 'is "rule". Rename the key, keep its value. While ' + 'any rule uses "ruleType" no rule matches, and ' + "the schema reports a version migration failure " + "instead of this.",
|
|
2176
|
+
severity: "error"
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
} else {
|
|
2180
|
+
const typeMismatches = findSchemaTypeMismatches(parsed, diskResult.migrationError);
|
|
2181
|
+
if (typeMismatches.length > 0) {
|
|
2182
|
+
for (const mismatch of typeMismatches) {
|
|
2183
|
+
issues.push({
|
|
2184
|
+
code: "CASE_MGMT_FIELD_TYPE_MISMATCH",
|
|
2185
|
+
path: mismatch.path,
|
|
2186
|
+
message: mismatch.message,
|
|
2187
|
+
severity: "error"
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
} else {
|
|
2191
|
+
issues.push({
|
|
2192
|
+
code: "CASE_MGMT_MIGRATION_FAILED",
|
|
2193
|
+
path: "(root)",
|
|
2194
|
+
message: diskResult.migrationError,
|
|
2195
|
+
severity: "error"
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
854
2199
|
}
|
|
855
2200
|
if (diskResult.valid && profile !== "skeleton") {
|
|
856
2201
|
const [canvasCallError, canvasResultRaw] = catchError(() => validateCaseCanvasJson(parsed));
|
|
@@ -883,13 +2228,14 @@ Validating: ${absolutePath}
|
|
|
883
2228
|
issues.push(...this.runSupplementalRules(parsed, profile));
|
|
884
2229
|
}
|
|
885
2230
|
if (diskResult.valid && profile === "strict") {
|
|
886
|
-
const { runStrictCaseRules } = await import("./strict-case-rules-
|
|
2231
|
+
const { runStrictCaseRules } = await import("./strict-case-rules-x6t0vb40.js");
|
|
887
2232
|
issues.push(...runStrictCaseRules(parsed));
|
|
2233
|
+
issues.push(...await this.checkBindingsSidecar(absolutePath, parsed));
|
|
888
2234
|
if (options.sddPath !== undefined) {
|
|
889
2235
|
const sddPath = this.fs.path.resolve(options.sddPath);
|
|
890
2236
|
if (await this.fs.exists(sddPath)) {
|
|
891
2237
|
const sddText = await this.fs.readFile(sddPath, "utf-8") ?? "";
|
|
892
|
-
const { auditCaseplanAgainstSdd } = await import("./strict-sdd-audit-
|
|
2238
|
+
const { auditCaseplanAgainstSdd } = await import("./strict-sdd-audit-8e2fh9jg.js");
|
|
893
2239
|
issues.push(...auditCaseplanAgainstSdd(parsed, sddText, this.fs.path.basename(sddPath)));
|
|
894
2240
|
} else {
|
|
895
2241
|
issues.push({
|
|
@@ -907,6 +2253,42 @@ Validating: ${absolutePath}
|
|
|
907
2253
|
issues
|
|
908
2254
|
};
|
|
909
2255
|
}
|
|
2256
|
+
async checkBindingsSidecar(casePlanPath, parsed) {
|
|
2257
|
+
const { compareCaseBindingsV2 } = await import("./case-bindings-service-1wxfghzd.js");
|
|
2258
|
+
const [error, comparison] = await catchError(compareCaseBindingsV2(casePlanPath, parsed));
|
|
2259
|
+
if (error || comparison === undefined)
|
|
2260
|
+
return [];
|
|
2261
|
+
const remedy = `Run 'uip maestro case bindings sync ${this.fs.path.basename(casePlanPath)}' to regenerate it.`;
|
|
2262
|
+
const issue2 = (message) => [
|
|
2263
|
+
{
|
|
2264
|
+
code: "STRICT_BINDINGS_SIDECAR_STALE",
|
|
2265
|
+
path: this.fs.path.basename(comparison.path),
|
|
2266
|
+
severity: "warning",
|
|
2267
|
+
message
|
|
2268
|
+
}
|
|
2269
|
+
];
|
|
2270
|
+
switch (comparison.status) {
|
|
2271
|
+
case "match":
|
|
2272
|
+
return [];
|
|
2273
|
+
case "absent":
|
|
2274
|
+
return comparison.expectedCount === 0 ? [] : issue2(`No bindings_v2.json next to the case plan, but its bindings produce ${comparison.expectedCount} resource entr${comparison.expectedCount === 1 ? "y" : "ies"}. 'uip solution resources refresh' reads that file, so the solution will resolve none of them. ${remedy}`);
|
|
2275
|
+
case "unreadable":
|
|
2276
|
+
return issue2(`bindings_v2.json could not be read as a bindings file. ${remedy}`);
|
|
2277
|
+
case "differs": {
|
|
2278
|
+
const {
|
|
2279
|
+
missing = [],
|
|
2280
|
+
extra = [],
|
|
2281
|
+
changed = []
|
|
2282
|
+
} = comparison.difference ?? {};
|
|
2283
|
+
const parts = [
|
|
2284
|
+
missing.length > 0 && `missing: ${missing.join(", ")}`,
|
|
2285
|
+
changed.length > 0 && `wrong: ${changed.join(", ")}`,
|
|
2286
|
+
extra.length > 0 && `unexpected: ${extra.join(", ")}`
|
|
2287
|
+
].filter((part) => part !== false);
|
|
2288
|
+
return issue2(`bindings_v2.json disagrees with the case plan's bindings (${parts.join("; ")}). An entry the consumer cannot key is skipped without an error. ${remedy}`);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
910
2292
|
validateMetadata(schema, profile) {
|
|
911
2293
|
const issues = [];
|
|
912
2294
|
const metadata = schema.metadata ?? {};
|
|
@@ -974,13 +2356,13 @@ Validating: ${absolutePath}
|
|
|
974
2356
|
const stageHoursTotal = schema.nodes.filter((n) => n.type === "case-management:Stage").reduce((sum, s) => sum + slaToHours(s.data.sla), 0);
|
|
975
2357
|
for (let i = 0;i < rootSlaRules.length; i++) {
|
|
976
2358
|
const rule = rootSlaRules[i];
|
|
977
|
-
const hasEscalations = rule.escalationRule
|
|
2359
|
+
const hasEscalations = (rule.escalationRule?.length ?? 0) > 0;
|
|
978
2360
|
const hasValue = rule.count != null && rule.count > 0;
|
|
979
2361
|
if (hasEscalations && !hasValue) {
|
|
980
2362
|
issues.push({
|
|
981
2363
|
code: "CASE_MGMT_ROOT_NO_SLA_ESCALATIONS",
|
|
982
2364
|
path: `metadata.slaRules[${i}]`,
|
|
983
|
-
message: "Root SLA rule has escalation rules but no SLA
|
|
2365
|
+
message: "Root SLA rule has escalation rules but no SLA duration: `count` is missing or zero. Escalations are measured against the duration, so none of them can fire. Check for the value sitting in an invented key such as `duration`.",
|
|
984
2366
|
severity: "warning"
|
|
985
2367
|
});
|
|
986
2368
|
}
|
|
@@ -1582,7 +2964,7 @@ Validating: ${absolutePath}
|
|
|
1582
2964
|
}
|
|
1583
2965
|
const sla = stage.data.sla;
|
|
1584
2966
|
if (sla) {
|
|
1585
|
-
const hasEscalations = sla.escalationRule
|
|
2967
|
+
const hasEscalations = (sla.escalationRule?.length ?? 0) > 0;
|
|
1586
2968
|
const hasValue = sla.count != null && sla.count > 0;
|
|
1587
2969
|
if (hasEscalations && !hasValue) {
|
|
1588
2970
|
issues.push({
|
|
@@ -2111,4 +3493,4 @@ export {
|
|
|
2111
3493
|
CaseValidateService
|
|
2112
3494
|
};
|
|
2113
3495
|
|
|
2114
|
-
//# debugId=
|
|
3496
|
+
//# debugId=CF6CF2FB8A5C033D64756E2164756E21
|