@uipath/case-tool 1.203.0-preview.212 → 1.204.0-preview.211

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.
Files changed (85) hide show
  1. package/README.md +6 -1
  2. package/dist/THIRD-PARTY-NOTICES.md +10 -9
  3. package/dist/authoring-0hr88kff.js +623 -0
  4. package/dist/{case-api-v6cjyrfw.js → case-api-gdaj9469.js} +5 -5
  5. package/dist/case-bindings-service-c2mze9vj.js +476 -0
  6. package/dist/{case-debug-studio-web-service-8th9d8sm.js → case-debug-studio-web-service-6q9ch7cq.js} +11 -10
  7. package/dist/case-described-io-service-cpe5h2ks.js +132 -0
  8. package/dist/{case-format-service-q345a2fd.js → case-format-service-qwyjyt0y.js} +2 -2
  9. package/dist/{case-pack-service-29rza2q2.js → case-pack-service-47yrvmg0.js} +11 -11
  10. package/dist/case-shape-reader-mw58avr0.js +13 -0
  11. package/dist/case-splice-service-9zvw19ca.js +312 -0
  12. package/dist/{case-validate-service-84drzwx3.js → case-validate-service-jcdf4n67.js} +415 -1449
  13. package/dist/{connector-enrichment-service-2982ykgb.js → connector-enrichment-service-60wwjarq.js} +20 -18
  14. package/dist/{connector-lookup-service-8ddh4pq2.js → connector-lookup-service-zhf8fz2j.js} +7 -7
  15. package/dist/{connector-task-spec-qs7q7vnv.js → connector-task-spec-96e3wzx7.js} +86 -26
  16. package/dist/{debug-service-8x087xxt.js → debug-service-vf5mw0nr.js} +3 -3
  17. package/dist/{embedded-file-reader-0sjt4nkj.js → embedded-file-reader-v8c2yj2a.js} +2 -2
  18. package/dist/{enrichment-service-7vbxcwge.js → enrichment-service-78b0gqck.js} +8 -7
  19. package/dist/index-hhsf00x8.js +15 -0
  20. package/dist/{index-fz4ttsde.js → index-j2r3jb6c.js} +5 -3
  21. package/dist/{index-yj8fb3q6.js → index-k7gkw7s9.js} +59 -27
  22. package/dist/{index-jxj2jz45.js → index-rgw7caht.js} +416 -87
  23. package/dist/{index-k4xz16db.js → index-wmffgzmr.js} +12522 -7316
  24. package/dist/index.js +15 -11
  25. package/dist/{input-reader-pnh948bq.js → input-reader-bb0ptk2v.js} +2 -2
  26. package/dist/migration-diagnosis-ag2sgd1e.js +71 -0
  27. package/dist/{node-strategy-gc4sg6a2.js → node-strategy-5pz40tee.js} +1 -1
  28. package/dist/{node-sync-service-t193ery3.js → node-sync-service-zwjcwgwc.js} +6 -6
  29. package/dist/out-target-1f4n9vqb.js +70 -0
  30. package/dist/{packager-tool-wefb1zw3.js → packager-tool-0p3td5zv.js} +8 -7
  31. package/dist/packager-tool-21qfsbxn.js +17 -0
  32. package/dist/{strict-sdd-audit-8e2fh9jg.js → packager-tool-4e56rmwe.js} +2771 -1746
  33. package/dist/{packager-tool-sfkyb4kz.js → packager-tool-4rqd21wc.js} +194 -2
  34. package/dist/packager-tool-57pzrd0g.js +32 -0
  35. package/dist/{packager-tool-kpcjnxd1.js → packager-tool-58kx79ta.js} +586 -113
  36. package/dist/{packager-tool-hrdan5ms.js → packager-tool-5qghkdd0.js} +2 -2
  37. package/dist/packager-tool-5qp9xza1.js +10 -0
  38. package/dist/{packager-tool-3qkp2fts.js → packager-tool-76ms0wdw.js} +4 -4
  39. package/dist/packager-tool-8a9yqfq7.js +76 -0
  40. package/dist/packager-tool-b250k6w4.js +75 -0
  41. package/dist/packager-tool-bfgdz1zh.js +73 -0
  42. package/dist/{packager-tool-p7jgrm5r.js → packager-tool-cmwets3m.js} +1 -1
  43. package/dist/{packager-tool-d4sjk46c.js → packager-tool-cv05dwac.js} +18 -21
  44. package/dist/packager-tool-d7jjng8a.js +21 -0
  45. package/dist/{packager-tool-rh07gzxw.js → packager-tool-dqnc61h1.js} +5 -5
  46. package/dist/packager-tool-dthjwajc.js +13 -0
  47. package/dist/packager-tool-f78ntj5g.js +228 -0
  48. package/dist/{packager-tool-r1g8rqcs.js → packager-tool-gjhg49r1.js} +4 -4
  49. package/dist/{packager-tool-cg2j4rma.js → packager-tool-kacqd7h7.js} +4 -4
  50. package/dist/packager-tool-mknccck5.js +24 -0
  51. package/dist/{packager-tool-sv0d6wb6.js → packager-tool-nj4seavy.js} +7 -3
  52. package/dist/{packager-tool-adcaxdhs.js → packager-tool-paepfv5d.js} +1 -1
  53. package/dist/{packager-tool-052kvydg.js → packager-tool-pm646ag4.js} +1 -1
  54. package/dist/{packager-tool-qht4xd0y.js → packager-tool-q86py4rp.js} +320 -200
  55. package/dist/{packager-tool-geaetyks.js → packager-tool-qmeaj10z.js} +669 -100
  56. package/dist/{packager-tool-dd8bq6az.js → packager-tool-rgpdaxbx.js} +548 -108
  57. package/dist/{packager-tool-8q7vzh65.js → packager-tool-s3e2kepj.js} +455 -13
  58. package/dist/{packager-tool-r84ng21s.js → packager-tool-sag9gz55.js} +3 -1
  59. package/dist/{packager-tool-0mj06yen.js → packager-tool-sq931c23.js} +5 -22
  60. package/dist/{packager-tool-7mggwye6.js → packager-tool-tmfbp50w.js} +6 -6
  61. package/dist/{packager-tool-yrsrzz1a.js → packager-tool-v99ygh9f.js} +23 -8
  62. package/dist/packager-tool-wj9d9mqe.js +267 -0
  63. package/dist/{packager-tool-hz1mz8fs.js → packager-tool-wjaya2k2.js} +2 -2
  64. package/dist/packager-tool-xcd87qyx.js +44 -0
  65. package/dist/{packager-tool-0v3s8r01.js → packager-tool-xdxr3ync.js} +4 -2
  66. package/dist/{packager-tool-61hxhgeg.js → packager-tool-xkgq5fkf.js} +3 -3
  67. package/dist/packager-tool.js +6 -6
  68. package/dist/{project-artifacts-8snev67g.js → project-artifacts-wzrhketn.js} +1 -1
  69. package/dist/resolved-check-p99hysaw.js +50 -0
  70. package/dist/{resourcesSyncService-01qr5zp7.js → resourcesSyncService-rjt0tht7.js} +25 -18
  71. package/dist/rule-group-members-zje7n3kf.js +76 -0
  72. package/dist/sdd-convert-service-gp9733qv.js +1962 -0
  73. package/dist/{sdd-lookup-x08j9g9m.js → sdd-lookup-vpjej1kt.js} +2 -2
  74. package/dist/sdd-resolve-service-4hx64x3z.js +288 -0
  75. package/dist/strict-case-rules-ksy8q7pg.js +731 -0
  76. package/dist/strict-entry-points-v14d7y50.js +124 -0
  77. package/dist/strict-sdd-audit-k6hh0mbr.js +23 -0
  78. package/dist/task-selector-8bjn35dv.js +57 -0
  79. package/dist/tool.js +15 -11
  80. package/dist/{trigger-enrichment-service-sddbphf2.js → trigger-enrichment-service-kst89jbe.js} +41 -23
  81. package/package.json +2 -2
  82. package/dist/authoring-kngaggwm.js +0 -360
  83. package/dist/case-bindings-service-1wxfghzd.js +0 -240
  84. package/dist/packager-tool-83qnybab.js +0 -1564
  85. package/dist/strict-case-rules-x6t0vb40.js +0 -409
@@ -1,20 +1,32 @@
1
1
  import {
2
2
  ACTIVITY_TYPE_ID_PATH,
3
- BINDING_REFERENCE_PREFIX,
4
- NAME_BOUND_TASK_TYPES,
5
- RESOLVED_IDENTITY_KEYS,
6
3
  activityTypeIdMessage,
7
4
  contextEntries,
8
5
  contextEntry,
9
6
  missingContextKeys,
10
7
  readActivityTypeId
11
- } from "./packager-tool-d4sjk46c.js";
8
+ } from "./packager-tool-cv05dwac.js";
9
+ import {
10
+ CONNECTOR_TASK_TYPES,
11
+ SERVICE_TYPE_BY_TASK
12
+ } from "./packager-tool-dthjwajc.js";
13
+ import {
14
+ BINDING_REFERENCE_PREFIX,
15
+ NAME_BOUND_TASK_TYPES,
16
+ RESOLVED_IDENTITY_KEYS
17
+ } from "./packager-tool-d7jjng8a.js";
18
+ import {
19
+ readRegistryLedger
20
+ } from "./packager-tool-b250k6w4.js";
12
21
  import {
13
22
  validateIntSvcNode
14
- } from "./packager-tool-3qkp2fts.js";
23
+ } from "./packager-tool-76ms0wdw.js";
15
24
  import {
16
- loadCaseSchemaAsync
17
- } from "./packager-tool-p7jgrm5r.js";
25
+ MIN_SUPPORTED_MAJOR,
26
+ compareCaseSchemaVersions,
27
+ parseCaseSchemaVersion,
28
+ resolveCaseSchemaVersion
29
+ } from "./packager-tool-57pzrd0g.js";
18
30
  import {
19
31
  VALID_CASE_COMPLETION_RULE_TYPES,
20
32
  VALID_CASE_EXIT_RULE_TYPES,
@@ -22,30 +34,55 @@ import {
22
34
  VALID_STAGE_ENTRY_RULE_TYPES,
23
35
  VALID_STAGE_EXIT_RULE_TYPES,
24
36
  VALID_TASK_ENTRY_RULE_TYPES
25
- } from "./packager-tool-sv0d6wb6.js";
26
- import"./packager-tool-yrsrzz1a.js";
37
+ } from "./packager-tool-nj4seavy.js";
38
+ import"./packager-tool-v99ygh9f.js";
27
39
  import"./packager-tool-0gctz400.js";
28
40
  import"./packager-tool-qa9gftnj.js";
29
41
  import"./packager-tool-yc4kw997.js";
42
+ import {
43
+ loadCaseSchemaAsync
44
+ } from "./packager-tool-cmwets3m.js";
30
45
  import {
31
46
  catchError,
32
47
  logger
33
- } from "./packager-tool-kpcjnxd1.js";
48
+ } from "./packager-tool-58kx79ta.js";
34
49
  import {
35
50
  getFileSystem
36
- } from "./packager-tool-0v3s8r01.js";
51
+ } from "./packager-tool-xdxr3ync.js";
37
52
  import {
38
53
  __require
39
54
  } from "./packager-tool-1yh8g550.js";
40
55
 
56
+ // src/services/case-validators/connector-activity-validator.ts
57
+ var ACTIVITY_CONTEXT_KEYS = ["metadata", "objectName"];
58
+ var connectorActivityValidator = {
59
+ id: "uipath-connector-activity",
60
+ appliesTo: (task) => CONNECTOR_TASK_TYPES.has(task.type ?? "") && Object.keys(task.data ?? {}).length > 0,
61
+ validate: (task, ctx) => {
62
+ const context = task.data?.context;
63
+ const missing = ACTIVITY_CONTEXT_KEYS.filter((key) => contextEntry(context, key) === undefined);
64
+ if (missing.length === 0)
65
+ return [];
66
+ const name = task.displayName ?? task.id ?? "task";
67
+ return [
68
+ {
69
+ code: "CASE_MGMT_CONNECTOR_ACTIVITY_UNRESOLVED",
70
+ path: `nodes[${ctx.stageId}].data.tasks[${task.id ?? "?"}].data.context`,
71
+ severity: "error",
72
+ message: `Connector task "${name}" has no ${missing.map((key) => `\`${key}\``).join(" or ")} context entry, so Studio Web shows it ` + "with no activity selected and it cannot run. Fetch the " + "activity with `uip maestro case spec` and write it into " + "the task with `uip maestro case splice`, or empty the " + "task's `data` to leave it as a placeholder."
73
+ }
74
+ ];
75
+ }
76
+ };
77
+
41
78
  // src/services/case-validators/connector-validator.ts
42
- var CONNECTOR_TASK_TYPES = new Set([
79
+ var CONNECTOR_TASK_TYPES2 = new Set([
43
80
  "execute-connector-activity",
44
81
  "wait-for-connector"
45
82
  ]);
46
83
  var connectorTaskValidator = {
47
84
  id: "uipath-connector",
48
- appliesTo: (task) => CONNECTOR_TASK_TYPES.has(task.type),
85
+ appliesTo: (task) => CONNECTOR_TASK_TYPES2.has(task.type),
49
86
  validate: (task, ctx) => {
50
87
  const data = task.data;
51
88
  if (!data)
@@ -281,14 +318,6 @@ var timerTaskValidator = {
281
318
  };
282
319
  // src/services/case-validators/strict-task-validators.ts
283
320
  var TRIGGER_TASK_TYPES = new Set(["wait-for-connector"]);
284
- var CONNECTOR_TASK_TYPES2 = new Set([
285
- "execute-connector-activity",
286
- "wait-for-connector"
287
- ]);
288
- var SERVICE_TYPE_BY_TASK = {
289
- "execute-connector-activity": "Intsvc.ActivityExecution",
290
- "wait-for-connector": "Intsvc.WaitForEvent"
291
- };
292
321
  var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
293
322
  var asArray = (value) => Array.isArray(value) ? value : [];
294
323
  var VARS_READS = [
@@ -309,17 +338,19 @@ var taskLabel = (task) => task.displayName ?? task.id ?? "task";
309
338
  var taskPath = (stageId, task) => `nodes[${stageId}].data.tasks[${task.id ?? "?"}]`;
310
339
  var strictConnectorContextValidator = {
311
340
  id: "strict-connector-context",
312
- appliesTo: (task) => CONNECTOR_TASK_TYPES2.has(task.type ?? ""),
341
+ appliesTo: (task) => CONNECTOR_TASK_TYPES.has(task.type ?? "") && Object.keys(taskData(task) ?? {}).length > 0,
313
342
  validate: (task, ctx) => {
314
343
  const issues = [];
315
- const context = taskData(task)?.context;
316
- const missing = missingContextKeys(context, TRIGGER_TASK_TYPES.has(task.type ?? ""));
344
+ const data = taskData(task);
345
+ const context = data?.context;
346
+ const reportedByDefault = new Set(Object.keys(data ?? {}).length > 0 ? ACTIVITY_CONTEXT_KEYS : []);
347
+ const missing = missingContextKeys(context, TRIGGER_TASK_TYPES.has(task.type ?? ""), ctx.schema.bindings).filter((key) => !reportedByDefault.has(key));
317
348
  if (missing.length > 0) {
318
349
  issues.push({
319
350
  code: "STRICT_CONNECTOR_CONTEXT_INCOMPLETE",
320
351
  path: `${taskPath(ctx.stageId, task)}.data.context`,
321
352
  severity: "error",
322
- message: `Connector task "${taskLabel(task)}" is missing context ` + `entries: ${missing.join(", ")}. The default validator ` + "skips a task in this state as mid-authoring, so nothing " + "else reports it — under --strict an incomplete context " + "is a defect."
353
+ message: `Connector task "${taskLabel(task)}" is missing context ` + `entries: ${missing.join(", ")}. The runtime binds the ` + "task through these, so it deploys and cannot run — " + "under --strict an incomplete context is a defect."
323
354
  });
324
355
  }
325
356
  if (contextEntry(context, "metadata") !== undefined) {
@@ -539,11 +570,43 @@ var strictTaskValidators = [
539
570
  // src/services/case-validators/index.ts
540
571
  var defaultTaskValidators = [
541
572
  connectorTaskValidator,
573
+ connectorActivityValidator,
542
574
  timerTaskValidator,
543
575
  taskElementIdValidator,
544
576
  unconfiguredTaskValidator
545
577
  ];
546
578
 
579
+ // src/services/registry-ledger-lookup.ts
580
+ var findRegistryLedger = async (fs, casePlanPath) => {
581
+ const dir = fs.path.dirname(casePlanPath);
582
+ const candidates = [];
583
+ for (const up of ["", "..", "../.."]) {
584
+ for (const leaf of ["tasks", "."]) {
585
+ candidates.push(fs.path.join(dir, up, leaf, "registry-resolved.json"));
586
+ }
587
+ }
588
+ for (const candidate of candidates) {
589
+ if (!await fs.exists(candidate))
590
+ continue;
591
+ const [readError, text] = await catchError(fs.readFile(candidate, "utf-8"));
592
+ if (readError || text === undefined || text === null)
593
+ continue;
594
+ const [parseError, document] = await catchError(Promise.resolve().then(() => JSON.parse(text)));
595
+ if (parseError) {
596
+ logger.warn(`Ignored ${candidate}: not JSON, so a placeholder it might justify still reports as unresolved work.`);
597
+ continue;
598
+ }
599
+ const ledger = readRegistryLedger(document);
600
+ if (ledger === undefined) {
601
+ logger.warn(`Found ${candidate} but no resolution entries in it — an entry needs a \`task\` name carrying \`matches\` or \`selected\`, within four levels of the root. Placeholders it might justify will report as unresolved work.`);
602
+ continue;
603
+ }
604
+ logger.info(`Read ${candidate}: ${ledger.unresolvable.size} task(s) recorded unresolvable`);
605
+ return ledger;
606
+ }
607
+ return;
608
+ };
609
+
547
610
  // src/services/case-validate-service.ts
548
611
  var mapCanvasSeverity = (severity) => {
549
612
  const normalized = (severity ?? "error").toUpperCase();
@@ -565,165 +628,53 @@ var SKELETON_V2_SKIPPED_CANVAS_CODES = new Set([
565
628
  "CASE_MGMT_ROOT_CASE_CONDITION_RULE_NO_REQUIRED_TASK"
566
629
  ]);
567
630
  var isTaskScopedCanvasIssue = (issue2) => SKELETON_V2_SKIPPED_CANVAS_CODES.has(issue2.code) || issue2.additionalErrorInfo?.taskId !== undefined;
568
- function parseSemverMajor(version) {
569
- if (!version)
570
- return null;
571
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
572
- if (!match)
573
- return null;
574
- return Number.parseInt(match[1], 10);
575
- }
576
- function slaToHours(sla) {
577
- if (!sla?.count || sla.count <= 0)
578
- return 0;
579
- const multipliers = {
580
- h: 1,
581
- d: 24,
582
- w: 168,
583
- m: 5040
584
- };
585
- return sla.count * (multipliers[sla.unit ?? "h"] ?? 1);
586
- }
587
- function rulesMatch(a, b) {
588
- if (a.length !== b.length)
589
- return false;
590
- return a.every((andGroupA, i) => {
591
- const andGroupB = b[i];
592
- if (!andGroupB || andGroupA.length !== andGroupB.length)
593
- return false;
594
- return andGroupA.every((ruleA, j) => {
595
- const ruleB = andGroupB[j];
596
- if (!ruleB || ruleA.rule !== ruleB.rule)
597
- return false;
598
- switch (ruleA.rule) {
599
- case "wait-for-connector":
600
- return ruleA.conditionExpression === ruleB.conditionExpression && JSON.stringify(ruleA.uipath) === JSON.stringify(ruleB.uipath);
601
- case "case-entered":
602
- return ruleA.conditionExpression === ruleB.conditionExpression;
603
- case "current-stage-entered":
604
- return ruleA.conditionExpression === ruleB.conditionExpression;
605
- case "adhoc":
606
- return ruleA.conditionExpression === ruleB.conditionExpression;
607
- case "selected-stage-completed":
608
- return ruleA.selectedStageId === ruleB.selectedStageId && ruleA.conditionExpression === ruleB.conditionExpression;
609
- case "selected-stage-exited":
610
- return ruleA.selectedStageId === ruleB.selectedStageId && ruleA.conditionExpression === ruleB.conditionExpression;
611
- case "selected-tasks-completed": {
612
- const aIds = ruleA.selectedTasksIds ?? [];
613
- const bIds = ruleB.selectedTasksIds ?? [];
614
- return aIds.length === bIds.length && aIds.every((id) => bIds.includes(id)) && ruleA.conditionExpression === ruleB.conditionExpression;
615
- }
616
- case "required-stages-completed":
617
- return ruleA.conditionExpression === ruleB.conditionExpression;
618
- case "required-tasks-completed":
619
- return ruleA.conditionExpression === ruleB.conditionExpression;
620
- case "user-selected-stage":
621
- return ruleA.conditionExpression === ruleB.conditionExpression;
622
- case "runs-sequentially":
623
- return ruleA.conditionExpression === ruleB.conditionExpression;
624
- default:
625
- return false;
626
- }
627
- });
628
- });
629
- }
630
- function stageLabel(node) {
631
- return node.data.label ?? node.id;
632
- }
633
- function triggerLabel(node) {
634
- return node.data.label ?? node.id;
635
- }
636
- function edgeLabel(edge) {
637
- return edge.data.label ?? edge.id;
638
- }
639
- function flatTasks(stage) {
640
- return (stage.data.tasks ?? []).flat();
641
- }
642
- function findRequiredFieldError(fields) {
643
- for (const field of fields ?? []) {
644
- if (field.required && (field.value === undefined || field.value === null || field.value === "")) {
645
- return field.name;
646
- }
631
+ function findLegacySpellings(parsed, profile) {
632
+ const issues = [];
633
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
634
+ return issues;
647
635
  }
648
- return null;
649
- }
650
- function findRequiredFieldErrorInUiPath(uipath) {
651
- return findRequiredFieldError(uipath?.context) ?? findRequiredFieldError(uipath?.inputs) ?? findRequiredFieldError(uipath?.outputs) ?? null;
652
- }
653
- function flatConditionRules(condition) {
654
- return (condition.rules ?? []).flat();
655
- }
656
- function conditionLabel(condition) {
657
- return condition.displayName ?? condition.id ?? "(unnamed)";
658
- }
659
- function hasCaseManagerStageWithTasks(schema) {
660
- return schema.nodes.some((n) => {
661
- if (n.type !== "case-management:Stage" && n.type !== "case-management:ExceptionStage") {
662
- return false;
636
+ const version = parsed.version;
637
+ if (version === undefined || typeof version === "string" && version.trim() === "") {
638
+ return issues;
639
+ }
640
+ const severity = profile === "strict" ? "error" : "warning";
641
+ const nodes = Array.isArray(parsed.nodes) ? parsed.nodes : [];
642
+ nodes.forEach((node, index) => {
643
+ if (node?.type === "case-management:Trigger") {
644
+ issues.push({
645
+ code: "CASE_MGMT_LEGACY_TRIGGER_TYPE",
646
+ path: `nodes[${index}].type`,
647
+ message: `Trigger node "${String(node.id ?? index)}" has the legacy type "case-management:Trigger". Write "uipath.case.trigger". Studio Web reads a document with the legacy type as schema v20 and migrates it on every open.`,
648
+ severity
649
+ });
663
650
  }
664
- const data = n.data;
665
- if (data.isCaseManagerStage !== true)
666
- return false;
667
- return (data.tasks ?? []).some((group) => Array.isArray(group) && group.length > 0);
668
651
  });
652
+ const walk = (value, path) => {
653
+ if (Array.isArray(value)) {
654
+ for (const [i, one] of value.entries())
655
+ walk(one, `${path}[${i}]`);
656
+ return;
657
+ }
658
+ if (value === null || typeof value !== "object")
659
+ return;
660
+ const record = value;
661
+ if (typeof record.rule === "string" && typeof record.selectedStageId === "string") {
662
+ issues.push({
663
+ code: "CASE_MGMT_LEGACY_STAGE_SELECTOR",
664
+ path: `${path}.selectedStageId`,
665
+ message: `Rule "${String(record.id ?? record.rule)}" selects its stage with the legacy key "selectedStageId". Write "selectedStageIds": ["${record.selectedStageId}"]. The schema package rejects the legacy key in a document at the current version.`,
666
+ severity
667
+ });
668
+ }
669
+ for (const [key, child] of Object.entries(record)) {
670
+ walk(child, `${path}.${key}`);
671
+ }
672
+ };
673
+ walk(parsed.nodes, "nodes");
674
+ walk(parsed.edges, "edges");
675
+ walk(parsed.metadata, "metadata");
676
+ return issues;
669
677
  }
670
- function findFirstStageAfterTrigger(schema) {
671
- const triggerIds = new Set(schema.nodes.filter((n) => n.type === "case-management:Trigger").map((n) => n.id));
672
- return schema.edges.filter((e) => triggerIds.has(e.source)).map((e) => e.target)[0];
673
- }
674
- var VALID_RULE_TYPES = new Set([
675
- "wait-for-connector",
676
- "case-entered",
677
- "selected-stage-completed",
678
- "selected-stage-exited",
679
- "selected-tasks-completed",
680
- "current-stage-entered",
681
- "adhoc",
682
- "required-stages-completed",
683
- "required-tasks-completed",
684
- "user-selected-stage",
685
- "runs-sequentially",
686
- "stage-complete",
687
- "condition",
688
- "timer"
689
- ]);
690
- var VALID_TASK_TYPES = new Set([
691
- "process",
692
- "action",
693
- "agent",
694
- "api-workflow",
695
- "rpa",
696
- "external-agent",
697
- "wait-for-timer",
698
- "wait-for-connector",
699
- "execute-connector-activity",
700
- "case-management"
701
- ]);
702
- var VALID_NODE_TYPES = new Set([
703
- "case-management:Trigger",
704
- "case-management:Stage",
705
- "case-management:ExceptionStage",
706
- "case-management:StickyNote"
707
- ]);
708
- var VALID_EDGE_TYPES = new Set([
709
- "case-management:TriggerEdge",
710
- "case-management:Edge"
711
- ]);
712
- var VALID_EXIT_CONDITION_TYPES = new Set([
713
- "exit-only",
714
- "wait-for-user",
715
- "return-to-origin"
716
- ]);
717
- var VALID_SLA_UNITS = new Set(["min", "h", "d", "w", "m"]);
718
- var VALID_ESCALATION_TRIGGER_TYPES = new Set([
719
- "at-risk",
720
- "sla-breached"
721
- ]);
722
- var VALID_TRIGGER_SERVICE_TYPES = new Set([
723
- "None",
724
- "Intsvc.EventTrigger",
725
- "Intsvc.TimerTrigger"
726
- ]);
727
678
  var AUTHORING_MARKER_LIMIT = 50;
728
679
  var XREF_MARKER = "$xref(";
729
680
  var PLANNING_NOTATION_PREFIXES = ["<-", "->"];
@@ -869,6 +820,77 @@ var findConnectorInputDefects = (nodes) => {
869
820
  }
870
821
  return found;
871
822
  };
823
+ var findDeadMetadataReferences = (parsed) => {
824
+ const found = [];
825
+ for (const { entry, path } of taskSlotEntries(asArrayValue(parsed.nodes).map(asRecordValue).filter((n) => n !== undefined), ["outputs"])) {
826
+ if (found.length >= AUTHORING_MARKER_LIMIT)
827
+ return found;
828
+ if (entry === undefined)
829
+ continue;
830
+ for (const field of ["value", "source"]) {
831
+ const value = entry[field];
832
+ if (typeof value === "string" && value.startsWith("=metadata.")) {
833
+ found.push({ path: `${path}.${field}`, value });
834
+ }
835
+ }
836
+ }
837
+ return found;
838
+ };
839
+ var VARS_PREFIX = "=vars.";
840
+ var findDanglingTriggerBridges = (parsed) => {
841
+ const found = [];
842
+ const variables = asRecordValue(parsed.variables);
843
+ const slotElements = new Map;
844
+ for (const slot of asArrayValue(variables?.inputs).map(asRecordValue)) {
845
+ const id = slot?.id;
846
+ if (typeof id !== "string" || id === "")
847
+ continue;
848
+ const element = slot?.elementId;
849
+ slotElements.set(id, typeof element === "string" && element !== "" ? element : undefined);
850
+ }
851
+ for (const node of asArrayValue(parsed.nodes).map(asRecordValue).filter((one) => one !== undefined)) {
852
+ const nodeType = String(node.type ?? "");
853
+ if (nodeType !== "case-management:Trigger" && nodeType !== "uipath.case.trigger") {
854
+ continue;
855
+ }
856
+ const nodeId = String(node.id ?? "");
857
+ const data = asRecordValue(node.data);
858
+ for (const container of ["inputs", "uipath"]) {
859
+ const outputs = asArrayValue(asRecordValue(data?.[container])?.outputs);
860
+ for (const [index, entry] of outputs.entries()) {
861
+ if (found.length >= AUTHORING_MARKER_LIMIT)
862
+ return found;
863
+ const bridge = asRecordValue(entry);
864
+ const source = bridge?.source;
865
+ if (typeof source !== "string" || source === "")
866
+ continue;
867
+ const variable = bridge?.var;
868
+ if (typeof variable !== "string" || variable === "")
869
+ continue;
870
+ if (!source.startsWith(VARS_PREFIX)) {
871
+ found.push({
872
+ path: `nodes[${nodeId}].data.${container}.outputs[${index}].source`,
873
+ source,
874
+ variable
875
+ });
876
+ continue;
877
+ }
878
+ const named = source.slice(VARS_PREFIX.length);
879
+ if (slotElements.has(named)) {
880
+ const element = slotElements.get(named);
881
+ if (element === undefined || element === nodeId)
882
+ continue;
883
+ }
884
+ found.push({
885
+ path: `nodes[${nodeId}].data.${container}.outputs[${index}].source`,
886
+ source,
887
+ variable
888
+ });
889
+ }
890
+ }
891
+ }
892
+ return found;
893
+ };
872
894
  var findReassignedOutputsWithoutOriginalVar = (parsed) => {
873
895
  const found = [];
874
896
  for (const { entry, name, path } of taskSlotEntries(asArrayValue(parsed.nodes).map(asRecordValue).filter((n) => n !== undefined), ["outputs"])) {
@@ -1244,7 +1266,7 @@ var findSchemaTypeMismatches = (parsed, migrationError) => {
1244
1266
  }
1245
1267
  return found;
1246
1268
  };
1247
- var findConnectorTasksWithoutInputs = (nodes) => {
1269
+ var findConnectorTasksWithoutInputs = (nodes, bindings) => {
1248
1270
  const found = [];
1249
1271
  for (const node of nodes) {
1250
1272
  const data = asRecordValue(node.data);
@@ -1264,7 +1286,7 @@ var findConnectorTasksWithoutInputs = (nodes) => {
1264
1286
  const context = taskData2.context;
1265
1287
  if (contextEntries(context).length === 0)
1266
1288
  continue;
1267
- if (missingContextKeys(context, type === "wait-for-connector").length > 0)
1289
+ if (missingContextKeys(context, type === "wait-for-connector", bindings).length > 0)
1268
1290
  continue;
1269
1291
  const inputs = taskData2.inputs;
1270
1292
  if (inputs !== undefined && !Array.isArray(inputs))
@@ -1326,6 +1348,43 @@ var findUnknownSlaKeys = (parsed, nodes) => {
1326
1348
  }
1327
1349
  return found;
1328
1350
  };
1351
+ var GUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1352
+ var findGroupRecipientLiterals = (schema) => {
1353
+ const found = [];
1354
+ const visit = (rules, where) => {
1355
+ for (const [index, entry] of asArrayValue(rules).entries()) {
1356
+ const rule = asRecordValue(entry);
1357
+ for (const [at, raw] of asArrayValue(rule?.escalationRule).entries()) {
1358
+ const escalation = asRecordValue(raw);
1359
+ const action = asRecordValue(escalation?.action);
1360
+ for (const [slot, person] of asArrayValue(action?.recipients).entries()) {
1361
+ const recipient = asRecordValue(person);
1362
+ if (recipient?.scope !== "UserGroup")
1363
+ continue;
1364
+ const target = recipient.target;
1365
+ if (typeof target !== "string" || target.trim() === "") {
1366
+ continue;
1367
+ }
1368
+ if (GUID_SHAPE.test(target.trim()) || target.includes("<UNRESOLVED")) {
1369
+ continue;
1370
+ }
1371
+ found.push({
1372
+ path: `${where}[${index}].escalationRule[${at}].action.recipients[${slot}].target`,
1373
+ value: target
1374
+ });
1375
+ }
1376
+ }
1377
+ }
1378
+ };
1379
+ visit(schema.metadata?.slaRules, "metadata.slaRules");
1380
+ for (const node of asArrayValue(schema.nodes)) {
1381
+ const data = asRecordValue(node.data);
1382
+ if (data === undefined)
1383
+ continue;
1384
+ visit(data.slaRules, `nodes[${labelOf(node, "?")}].data.slaRules`);
1385
+ }
1386
+ return found;
1387
+ };
1329
1388
  var findLiteralResourceBindings = (nodes) => {
1330
1389
  const found = [];
1331
1390
  for (const node of nodes) {
@@ -1459,6 +1518,45 @@ var findUnresolvedConnectorResourceKeys = (parsed) => {
1459
1518
  }, () => found.length >= AUTHORING_MARKER_LIMIT);
1460
1519
  return found;
1461
1520
  };
1521
+ var findDuplicateOutputNames = (parsed) => {
1522
+ const found = [];
1523
+ for (const node of asArrayValue(parsed.nodes)) {
1524
+ const record = asRecordValue(node);
1525
+ const data = asRecordValue(record?.data);
1526
+ if (record === undefined || data === undefined)
1527
+ continue;
1528
+ const stage = labelOf(record, "?");
1529
+ for (const lane of asArrayValue(data.tasks)) {
1530
+ for (const task of asArrayValue(lane)) {
1531
+ if (found.length >= AUTHORING_MARKER_LIMIT)
1532
+ return found;
1533
+ const taskRecord = asRecordValue(task);
1534
+ const taskData2 = asRecordValue(taskRecord?.data);
1535
+ if (taskRecord === undefined || taskData2 === undefined) {
1536
+ continue;
1537
+ }
1538
+ const label = labelOf(taskRecord, "?");
1539
+ const seen = new Map;
1540
+ for (const output of asArrayValue(taskData2.outputs)) {
1541
+ const name = asRecordValue(output)?.name;
1542
+ if (typeof name !== "string" || name === "")
1543
+ continue;
1544
+ seen.set(name, (seen.get(name) ?? 0) + 1);
1545
+ }
1546
+ for (const [name, count] of seen) {
1547
+ if (count < 2)
1548
+ continue;
1549
+ found.push({
1550
+ path: `nodes[${stage}].data.tasks[${label}].data.outputs[${name}]`,
1551
+ name,
1552
+ task: `${stage}/${label}`
1553
+ });
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1558
+ return found;
1559
+ };
1462
1560
  var findDuplicateOutputIds = (parsed) => {
1463
1561
  const found = [];
1464
1562
  const owners = new Map;
@@ -1722,92 +1820,6 @@ class CaseValidateService {
1722
1820
  ...options.extraTaskValidators ?? []
1723
1821
  ];
1724
1822
  }
1725
- async validateFile(filePath, options = {}) {
1726
- const profile = options.profile ?? "full";
1727
- const absolutePath = this.fs.path.resolve(filePath);
1728
- if (!await this.fs.exists(absolutePath)) {
1729
- throw new Error(`File not found: ${absolutePath}`);
1730
- }
1731
- const stats = await this.fs.stat(absolutePath);
1732
- if (stats?.isDirectory()) {
1733
- throw new Error(`Expected a file but got a directory: ${absolutePath}`);
1734
- }
1735
- logger.info(`
1736
- Validating: ${absolutePath}
1737
- `);
1738
- const content = await this.fs.readFile(absolutePath, "utf-8");
1739
- if (!content) {
1740
- throw new Error(`File is empty: ${absolutePath}`);
1741
- }
1742
- const [parseError, schema] = catchError(() => JSON.parse(content));
1743
- if (parseError) {
1744
- return {
1745
- success: false,
1746
- filePath: absolutePath,
1747
- issues: [
1748
- {
1749
- code: "INVALID_JSON",
1750
- path: "(root)",
1751
- message: "File is not valid JSON",
1752
- severity: "error"
1753
- }
1754
- ]
1755
- };
1756
- }
1757
- const rawSchema = schema;
1758
- if (rawSchema.root && !rawSchema.metadata) {
1759
- return {
1760
- success: false,
1761
- filePath: absolutePath,
1762
- issues: [
1763
- {
1764
- code: "INVALID_SCHEMA_VERSION",
1765
- path: "(root)",
1766
- message: "Detected a V19-shaped schema (top-level `root`). V20 flattens the top level and moves case-specific fields under `metadata` — please migrate the file to V20.",
1767
- severity: "error"
1768
- }
1769
- ]
1770
- };
1771
- }
1772
- if (!schema.nodes || !schema.edges) {
1773
- return {
1774
- success: false,
1775
- filePath: absolutePath,
1776
- issues: [
1777
- {
1778
- code: "INVALID_SCHEMA",
1779
- path: "(root)",
1780
- message: "Missing required top-level fields: nodes, edges",
1781
- severity: "error"
1782
- }
1783
- ]
1784
- };
1785
- }
1786
- const issues = schema.metadata?.allowAdhocOptionalStageTasks ? [
1787
- ...this.validateMetadata(schema, profile),
1788
- ...this.validateEnums(schema, profile),
1789
- ...this.validateTriggers(schema, profile),
1790
- ...this.validateAdhocNodes(schema, profile),
1791
- ...checksTaskContent(profile) ? this.validateOutputVariableUniqueness(schema) : [],
1792
- ...checksTaskContent(profile) ? this.runTaskValidators(schema, profile) : []
1793
- ] : [
1794
- ...this.validateMetadata(schema, profile),
1795
- ...this.validateEnums(schema, profile),
1796
- ...this.validateStructure(schema),
1797
- ...this.validateTriggers(schema, profile),
1798
- ...this.validatePrimaryStages(schema, profile),
1799
- ...this.validateSecondaryStages(schema, profile),
1800
- ...this.validateEdges(schema, profile),
1801
- ...profile !== "skeleton" ? this.validateOverallCase(schema) : [],
1802
- ...checksTaskContent(profile) ? this.validateOutputVariableUniqueness(schema) : [],
1803
- ...checksTaskContent(profile) ? this.runTaskValidators(schema, profile) : []
1804
- ];
1805
- return {
1806
- success: issues.filter((i) => i.severity === "error").length === 0,
1807
- filePath: absolutePath,
1808
- issues
1809
- };
1810
- }
1811
1823
  runSupplementalRules(parsed, profile) {
1812
1824
  const issues = [];
1813
1825
  const taskContent = checksTaskContent(profile);
@@ -1940,6 +1952,30 @@ Validating: ${absolutePath}
1940
1952
  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
1953
  });
1942
1954
  }
1955
+ for (const { path, value } of slaContent ? findGroupRecipientLiterals(parsed) : []) {
1956
+ issues.push({
1957
+ code: "CASE_MGMT_ESCALATION_RECIPIENT_LITERAL",
1958
+ path,
1959
+ severity: "error",
1960
+ message: `Escalation recipient targets the group "${value}" by ` + "name. A `UserGroup` recipient is addressed by the " + "tenant's id for that group, so a name here notifies " + "nobody — and it looks resolved, which is why nothing " + "else reports it. Put the group's id in `target` and keep " + "its name in `value`, or leave an `<UNRESOLVED: …>` " + "marker in `target` until the id is known."
1961
+ });
1962
+ }
1963
+ for (const { path, source, variable } of findDanglingTriggerBridges(parsed)) {
1964
+ issues.push({
1965
+ code: "CASE_MGMT_TRIGGER_BRIDGE_SOURCE_DANGLING",
1966
+ path,
1967
+ severity: "error",
1968
+ message: `The trigger bridge for "${variable}" reads ` + `\`${source}\`, which names no entry in ` + "`variables.inputs[]`. The bridge is the only thing that " + "copies a caller's value from the formal argument slot to " + "its companion at trigger fire, so a source that names no " + `slot copies nothing and \`=vars.${variable}\` is ` + "undefined at run time while the plan still validates. " + "Point `source` at the formal slot's own `id`."
1969
+ });
1970
+ }
1971
+ for (const { path, value } of taskContent ? findDeadMetadataReferences(parsed) : []) {
1972
+ issues.push({
1973
+ code: "CASE_MGMT_METADATA_REFERENCE_NOT_EXPRESSION",
1974
+ path,
1975
+ severity: "error",
1976
+ message: `"${value}" resolves through nothing. \`metadata\` is a ` + "namespace the runtime puts in scope inside a `=js:` " + "expression, not a path prefix that routes one — there " + "is no `=metadata.` prefix, so unlike `=vars.` or " + "`=bindings.` this reads no value and the plan still " + `validates. Write it as \`=js:${value.slice(1)}\`.`
1977
+ });
1978
+ }
1943
1979
  for (const { path, name: name2, fields } of taskContent ? findLiteralResourceBindings(nodes) : []) {
1944
1980
  issues.push({
1945
1981
  code: "CASE_MGMT_RESOURCE_TASK_LITERAL_BINDING",
@@ -1972,7 +2008,7 @@ Validating: ${absolutePath}
1972
2008
  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
2009
  });
1974
2010
  }
1975
- for (const { path, name: name2 } of taskContent ? findConnectorTasksWithoutInputs(nodes) : []) {
2011
+ for (const { path, name: name2 } of taskContent ? findConnectorTasksWithoutInputs(nodes, parsed.bindings) : []) {
1976
2012
  issues.push({
1977
2013
  code: "CASE_MGMT_CONNECTOR_INPUTS_EMPTY",
1978
2014
  path,
@@ -1980,6 +2016,14 @@ Validating: ${absolutePath}
1980
2016
  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
2017
  });
1982
2018
  }
2019
+ for (const { path, name: name2, task } of taskContent ? findDuplicateOutputNames(parsed) : []) {
2020
+ issues.push({
2021
+ code: "CASE_MGMT_OUTPUT_NAME_DUPLICATE",
2022
+ path,
2023
+ severity: "error",
2024
+ message: `Task "${task}" has two outputs named "${name2}". The ` + "parser keeps one row per name, so the other is dropped " + "and whatever it wrote is never written. A duplicate " + "`id` is reported separately and cannot see this one: " + "the dropped row often carries no id at all. Emit one " + "output for the value, carrying the id other tasks " + "reference it by."
2025
+ });
2026
+ }
1983
2027
  for (const { path, id, first, second } of taskContent ? findDuplicateOutputIds(parsed) : []) {
1984
2028
  issues.push({
1985
2029
  code: "CASE_MGMT_OUTPUT_ID_DUPLICATE",
@@ -2141,6 +2185,9 @@ Validating: ${absolutePath}
2141
2185
  };
2142
2186
  }
2143
2187
  const issues = [];
2188
+ const versionIssues = await this.checkDeclaredVersion(parsed);
2189
+ issues.push(...versionIssues);
2190
+ issues.push(...findLegacySpellings(parsed, profile));
2144
2191
  const { validateCaseCanvasJson, validateCaseDiskJsonSchema } = await loadCaseSchemaAsync();
2145
2192
  const [diskCallError, diskResultRaw] = catchError(() => validateCaseDiskJsonSchema(parsed));
2146
2193
  if (diskCallError) {
@@ -2157,22 +2204,42 @@ Validating: ${absolutePath}
2157
2204
  };
2158
2205
  }
2159
2206
  const diskResult = diskResultRaw;
2207
+ const version = parsed?.version;
2208
+ const versionMissing = (version === undefined || typeof version === "string" && version.trim() === "") && (diskResult.issues ?? []).some((i) => i.path === "version");
2160
2209
  for (const issue2 of diskResult.issues ?? []) {
2210
+ const path = issue2.path && issue2.path !== "" ? issue2.path : "(root)";
2211
+ if (versionIssues.length > 0 && path === "version")
2212
+ continue;
2213
+ if (versionMissing && path.includes("."))
2214
+ continue;
2161
2215
  issues.push({
2162
2216
  code: "CASE_MGMT_DISK_SCHEMA_INVALID",
2163
- path: issue2.path && issue2.path !== "" ? issue2.path : "(root)",
2164
- message: issue2.message,
2217
+ path,
2218
+ message: versionMissing && path === "version" ? `${issue2.message}. The version is missing, so nested fields were not checked — set "version" to the version the case plan was written at.` : issue2.message,
2165
2219
  severity: "error"
2166
2220
  });
2167
2221
  }
2168
2222
  if (diskResult.migrationError) {
2223
+ const {
2224
+ describeMisplacedRuleGroupMember,
2225
+ findMisplacedRuleGroupMembers
2226
+ } = await import("./rule-group-members-zje7n3kf.js");
2227
+ const misplaced = findMisplacedRuleGroupMembers(parsed);
2169
2228
  const ruleTypePaths = findRuleTypeKeys(parsed);
2170
- if (ruleTypePaths.length > 0) {
2229
+ if (misplaced.length > 0 || ruleTypePaths.length > 0) {
2230
+ for (const member of misplaced) {
2231
+ issues.push({
2232
+ code: "CASE_MGMT_RULE_GROUP_MEMBER_NOT_A_RULE",
2233
+ path: member.path,
2234
+ message: describeMisplacedRuleGroupMember(member),
2235
+ severity: "error"
2236
+ });
2237
+ }
2171
2238
  for (const rulePath of ruleTypePaths) {
2172
2239
  issues.push({
2173
2240
  code: "INVALID_RULE_DISCRIMINATOR",
2174
2241
  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.",
2242
+ 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
2243
  severity: "error"
2177
2244
  });
2178
2245
  }
@@ -2188,10 +2255,13 @@ Validating: ${absolutePath}
2188
2255
  });
2189
2256
  }
2190
2257
  } else {
2258
+ const { diagnoseMigrationFailure } = await import("./migration-diagnosis-ag2sgd1e.js");
2259
+ const diagnosis = diagnoseMigrationFailure(parsed, diskResult.migrationError, validateCaseDiskJsonSchema);
2260
+ const explanation = diagnosis === undefined ? "" : diagnosis.typeIsTheBlocker ? ` This document is valid with ${diagnosis.offending.map((type) => `\`${type}\``).join(", ")} written as the legacy \`case-management:*\` name, so the node type is what this schema version rejects — the migration step that rejected it accepts only those four names, and it ran because the document matched an older version.` : diagnosis.remainingError === undefined ? ` The node type is not the only blocker: ${diagnosis.offending.map((type) => `\`${type}\``).join(", ")} could not be mapped to a legacy name to test.` : ` The node type is a symptom, not the cause: with ${diagnosis.offending.map((type) => `\`${type}\``).join(", ")} written as its legacy name the document still fails, on ${diagnosis.remainingError}. Fix that first.`;
2191
2261
  issues.push({
2192
2262
  code: "CASE_MGMT_MIGRATION_FAILED",
2193
2263
  path: "(root)",
2194
- message: diskResult.migrationError,
2264
+ message: `${diskResult.migrationError}${explanation}`,
2195
2265
  severity: "error"
2196
2266
  });
2197
2267
  }
@@ -2228,15 +2298,16 @@ Validating: ${absolutePath}
2228
2298
  issues.push(...this.runSupplementalRules(parsed, profile));
2229
2299
  }
2230
2300
  if (diskResult.valid && profile === "strict") {
2231
- const { runStrictCaseRules } = await import("./strict-case-rules-x6t0vb40.js");
2301
+ const { runStrictCaseRules } = await import("./strict-case-rules-ksy8q7pg.js");
2232
2302
  issues.push(...runStrictCaseRules(parsed));
2233
2303
  issues.push(...await this.checkBindingsSidecar(absolutePath, parsed));
2304
+ issues.push(...await this.checkEntryPointsSidecar(absolutePath, parsed));
2234
2305
  if (options.sddPath !== undefined) {
2235
2306
  const sddPath = this.fs.path.resolve(options.sddPath);
2236
2307
  if (await this.fs.exists(sddPath)) {
2237
2308
  const sddText = await this.fs.readFile(sddPath, "utf-8") ?? "";
2238
- const { auditCaseplanAgainstSdd } = await import("./strict-sdd-audit-8e2fh9jg.js");
2239
- issues.push(...auditCaseplanAgainstSdd(parsed, sddText, this.fs.path.basename(sddPath)));
2309
+ const { auditCaseplanAgainstSdd } = await import("./strict-sdd-audit-k6hh0mbr.js");
2310
+ issues.push(...auditCaseplanAgainstSdd(parsed, sddText, this.fs.path.basename(sddPath), await findRegistryLedger(this.fs, absolutePath)));
2240
2311
  } else {
2241
2312
  issues.push({
2242
2313
  code: "STRICT_SDD_FILE_NOT_FOUND",
@@ -2253,9 +2324,39 @@ Validating: ${absolutePath}
2253
2324
  issues
2254
2325
  };
2255
2326
  }
2256
- async checkBindingsSidecar(casePlanPath, parsed) {
2257
- const { compareCaseBindingsV2 } = await import("./case-bindings-service-1wxfghzd.js");
2258
- const [error, comparison] = await catchError(compareCaseBindingsV2(casePlanPath, parsed));
2327
+ async checkEntryPointsSidecar(casePlanPath, parsed) {
2328
+ const dir = this.fs.path.dirname(casePlanPath);
2329
+ const candidates = [
2330
+ this.fs.path.join(dir, "entry-points.json"),
2331
+ this.fs.path.join(dir, "..", "entry-points.json")
2332
+ ];
2333
+ for (const candidate of candidates) {
2334
+ if (!await this.fs.exists(candidate))
2335
+ continue;
2336
+ const [readError, text] = await catchError(this.fs.readFile(candidate, "utf-8"));
2337
+ const unreadable = (why) => [
2338
+ {
2339
+ code: "STRICT_ENTRY_POINT_SIDECAR_UNREADABLE",
2340
+ path: this.fs.path.basename(candidate),
2341
+ severity: "error",
2342
+ message: `\`${this.fs.path.basename(candidate)}\` is next to this case plan and ${why}, so none of the entry-point checks could run. The runtime launches whichever BPMN this file's first entry resolves to, and \`case pack\` fails on a file it cannot read, so the package would not ship. Fix the file, or delete it if the project does not need one.`
2343
+ }
2344
+ ];
2345
+ if (readError || text === undefined || text === null) {
2346
+ return unreadable(readError === undefined ? "could not be found (it may have been removed while validating)" : `could not be read (${readError.message})`);
2347
+ }
2348
+ const [parseError, document] = await catchError(Promise.resolve().then(() => JSON.parse(text)));
2349
+ if (parseError) {
2350
+ return unreadable(`is not JSON (${parseError.message})`);
2351
+ }
2352
+ const { checkEntryPoints } = await import("./strict-entry-points-v14d7y50.js");
2353
+ return checkEntryPoints(document, parsed, this.fs.path.basename(candidate), this.fs.path.basename(casePlanPath));
2354
+ }
2355
+ return [];
2356
+ }
2357
+ async checkBindingsSidecar(casePlanPath, parsed) {
2358
+ const { compareCaseBindingsV2 } = await import("./case-bindings-service-c2mze9vj.js");
2359
+ const [error, comparison] = await catchError(compareCaseBindingsV2(casePlanPath, parsed));
2259
2360
  if (error || comparison === undefined)
2260
2361
  return [];
2261
2362
  const remedy = `Run 'uip maestro case bindings sync ${this.fs.path.basename(casePlanPath)}' to regenerate it.`;
@@ -2274,6 +2375,8 @@ Validating: ${absolutePath}
2274
2375
  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
2376
  case "unreadable":
2276
2377
  return issue2(`bindings_v2.json could not be read as a bindings file. ${remedy}`);
2378
+ case "entry-points-unreadable":
2379
+ return issue2(`${comparison.reason ?? `${comparison.path} could not be read`}. The trigger bindings in bindings_v2.json take their entry point ids from that file, so they cannot be checked. Fix or remove ${comparison.path}, then run \`uip maestro case bindings sync\`.`);
2277
2380
  case "differs": {
2278
2381
  const {
2279
2382
  missing = [],
@@ -2289,1185 +2392,48 @@ Validating: ${absolutePath}
2289
2392
  }
2290
2393
  }
2291
2394
  }
2292
- validateMetadata(schema, profile) {
2293
- const issues = [];
2294
- const metadata = schema.metadata ?? {};
2295
- const rawSchema = schema;
2296
- const rawMetadata = metadata;
2297
- if (!schema.name || schema.name.trim() === "") {
2298
- issues.push({
2299
- code: "CASE_MGMT_ROOT_NAME_MISSING",
2300
- path: "name",
2301
- message: "Case definition name is missing",
2302
- severity: "error"
2303
- });
2304
- }
2305
- const versionMajor = parseSemverMajor(schema.version);
2306
- if (versionMajor === null) {
2307
- issues.push({
2308
- code: "CASE_MGMT_INVALID_VERSION",
2309
- path: "version",
2310
- message: `Schema version "${String(schema.version ?? "(missing)")}" is not a valid semver — expected "<major>.<minor>.<patch>" with major >= 20`,
2311
- severity: "error"
2312
- });
2313
- } else if (versionMajor < 20) {
2314
- issues.push({
2315
- code: "CASE_MGMT_INVALID_VERSION",
2316
- path: "version",
2317
- message: `Schema version "${String(schema.version)}" predates V20 — this validator requires major version >= 20`,
2318
- severity: "error"
2319
- });
2320
- }
2321
- if (!schema.id || !/^case-[A-Za-z0-9]{10}$/.test(schema.id)) {
2322
- issues.push({
2323
- code: "CASE_MGMT_INVALID_ID",
2324
- path: "id",
2325
- message: `Case id "${String(schema.id ?? "(missing)")}" does not match the V20 format "case-<10 alphanumeric chars>"`,
2326
- severity: "error"
2327
- });
2328
- }
2329
- if (!metadata.caseIdentifier || metadata.caseIdentifier.trim() === "") {
2330
- issues.push({
2331
- code: "CASE_MGMT_ROOT_CASE_ID_MISSING",
2332
- path: "metadata.caseIdentifier",
2333
- message: "Case identifier is missing",
2334
- severity: "error"
2335
- });
2336
- }
2337
- if (rawSchema.caseExitConditions || rawSchema.caseExitRules) {
2338
- issues.push({
2339
- code: "CASE_MGMT_CASE_EXIT_CONDITIONS_MISPLACED",
2340
- path: rawSchema.caseExitRules ? "caseExitRules" : "caseExitConditions",
2341
- message: rawSchema.caseExitRules ? "caseExitRules is at the top level of the JSON file but should be inside metadata (metadata.caseExitRules)" : "caseExitConditions is at the top level and was renamed to caseExitRules in V20 — move it under metadata.caseExitRules",
2342
- severity: "error"
2343
- });
2344
- }
2345
- if (rawMetadata.caseExitConditions && !metadata.caseExitRules?.length) {
2346
- issues.push({
2347
- code: "CASE_MGMT_CASE_EXIT_CONDITIONS_MISPLACED",
2348
- path: "metadata.caseExitConditions",
2349
- message: "metadata.caseExitConditions was renamed to metadata.caseExitRules in V20",
2350
- severity: "error"
2351
- });
2352
- }
2353
- if (profile === "skeleton")
2354
- return issues;
2355
- const rootSlaRules = metadata.slaRules ?? [];
2356
- const stageHoursTotal = schema.nodes.filter((n) => n.type === "case-management:Stage").reduce((sum, s) => sum + slaToHours(s.data.sla), 0);
2357
- for (let i = 0;i < rootSlaRules.length; i++) {
2358
- const rule = rootSlaRules[i];
2359
- const hasEscalations = (rule.escalationRule?.length ?? 0) > 0;
2360
- const hasValue = rule.count != null && rule.count > 0;
2361
- if (hasEscalations && !hasValue) {
2362
- issues.push({
2363
- code: "CASE_MGMT_ROOT_NO_SLA_ESCALATIONS",
2364
- path: `metadata.slaRules[${i}]`,
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`.",
2366
- severity: "warning"
2367
- });
2368
- }
2369
- if (hasEscalations) {
2370
- const missingRecipients = (rule.escalationRule ?? []).some((e) => !e.action?.recipients || e.action.recipients.length === 0);
2371
- if (missingRecipients) {
2372
- issues.push({
2373
- code: "CASE_MGMT_ROOT_ESCALATION_RECIPIENTS_MISSING",
2374
- path: `metadata.slaRules[${i}].escalationRule`,
2375
- message: "Root SLA rule has an escalation rule with no recipients",
2376
- severity: "warning"
2377
- });
2378
- }
2379
- }
2380
- const ruleHours = slaToHours(rule);
2381
- if (ruleHours > 0 && stageHoursTotal > 0 && ruleHours < stageHoursTotal) {
2382
- issues.push({
2383
- code: "CASE_MGMT_ROOT_SLA_LESS_THAN_NODES",
2384
- path: `metadata.slaRules[${i}]`,
2385
- message: `Root SLA rule (${ruleHours}h) is less than the sum of all stage SLAs (${stageHoursTotal}h)`,
2386
- severity: "warning"
2387
- });
2388
- }
2389
- }
2390
- const caseExitRules = metadata.caseExitRules ?? [];
2391
- const completeConditions = caseExitRules.filter((c) => c.marksCaseComplete);
2392
- if (completeConditions.length === 0 && metadata.allowAdhocOptionalStageTasks !== true) {
2393
- issues.push({
2394
- code: "CASE_MGMT_ROOT_CASE_EXIT_CONDITIONS_MISSING",
2395
- path: "metadata.caseExitRules",
2396
- message: "The case has no exit condition that marks the case as complete",
2397
- severity: "error"
2398
- });
2399
- }
2400
- for (const condition of caseExitRules) {
2401
- if (!condition.rules || condition.rules.length === 0) {
2402
- issues.push({
2403
- code: "CASE_MGMT_CASE_EXIT_CONDITION_NO_RULES",
2404
- path: "metadata.caseExitRules",
2405
- message: "A case exit condition has no rules",
2406
- severity: "error"
2407
- });
2408
- }
2409
- }
2410
- issues.push(...this.validateConditionsBattery({
2411
- conditions: caseExitRules,
2412
- schema,
2413
- currentStage: undefined,
2414
- path: "metadata.caseExitRules",
2415
- elementLabel: "Case",
2416
- duplicateCode: "CASE_MGMT_ROOT_CASE_CONDITION_DUPLICATE",
2417
- profile
2418
- }));
2419
- return issues;
2420
- }
2421
- validateEnums(schema, profile) {
2422
- const issues = [];
2423
- for (const node of schema.nodes) {
2424
- if (!VALID_NODE_TYPES.has(node.type)) {
2425
- issues.push({
2426
- code: "INVALID_ENUM_VALUE",
2427
- path: `nodes[${node.id}].type`,
2428
- message: `Node "${node.id}" has invalid type "${node.type}". Expected one of: ${[...VALID_NODE_TYPES].join(", ")}`,
2429
- severity: "error"
2430
- });
2431
- }
2432
- }
2433
- for (const edge of schema.edges) {
2434
- if (!VALID_EDGE_TYPES.has(edge.type)) {
2435
- issues.push({
2436
- code: "INVALID_ENUM_VALUE",
2437
- path: `edges[${edge.id}].type`,
2438
- message: `Edge "${edge.id}" has invalid type "${edge.type}". Expected one of: ${[...VALID_EDGE_TYPES].join(", ")}`,
2439
- severity: "error"
2440
- });
2441
- }
2395
+ async checkDeclaredVersion(parsed) {
2396
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2397
+ return [];
2442
2398
  }
2443
- const triggers = schema.nodes.filter((n) => n.type === "case-management:Trigger");
2444
- for (const trigger of triggers) {
2445
- const serviceType = trigger.data.uipath?.serviceType;
2446
- if (serviceType != null && !VALID_TRIGGER_SERVICE_TYPES.has(serviceType)) {
2447
- issues.push({
2448
- code: "INVALID_ENUM_VALUE",
2449
- path: `nodes[${trigger.id}].data.uipath.serviceType`,
2450
- message: `Trigger "${triggerLabel(trigger)}" has invalid serviceType "${serviceType}". Expected one of: ${[...VALID_TRIGGER_SERVICE_TYPES].join(", ")}`,
2451
- severity: "error"
2452
- });
2453
- }
2399
+ const declared = parsed.version;
2400
+ if (declared === undefined || typeof declared === "string" && declared.trim() === "") {
2401
+ return [];
2454
2402
  }
2455
- const allStages = schema.nodes.filter((n) => n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage");
2456
- for (const stage of allStages) {
2457
- const name = stageLabel(stage);
2458
- const path = `nodes[${stage.id}]`;
2459
- for (const task of flatTasks(stage)) {
2460
- if (!VALID_TASK_TYPES.has(task.type)) {
2461
- issues.push({
2462
- code: "INVALID_ENUM_VALUE",
2463
- path,
2464
- message: `Stage "${name}" has a task with invalid type "${task.type}". Expected one of: ${[...VALID_TASK_TYPES].join(", ")}`,
2465
- severity: "error"
2466
- });
2467
- }
2468
- }
2469
- if (profile === "skeleton")
2470
- continue;
2471
- const slaUnit = stage.data.sla?.unit;
2472
- if (slaUnit != null && !VALID_SLA_UNITS.has(slaUnit)) {
2473
- issues.push({
2474
- code: "INVALID_ENUM_VALUE",
2475
- path: `${path}.data.sla.unit`,
2476
- message: `Stage "${name}" has invalid SLA unit "${slaUnit}". Expected one of: ${[...VALID_SLA_UNITS].join(", ")}`,
2403
+ const latest = await resolveCaseSchemaVersion();
2404
+ const parts = parseCaseSchemaVersion(declared);
2405
+ if (parts === null) {
2406
+ return [
2407
+ {
2408
+ code: "CASE_MGMT_INVALID_VERSION",
2409
+ path: "version",
2410
+ message: `Schema version ${JSON.stringify(declared)} is not a version — expected "<major>.<minor>.<patch>", and the current schema is "${latest}".`,
2477
2411
  severity: "error"
2478
- });
2479
- }
2480
- issues.push(...this.validateEscalationTriggerTypes(stage.data.sla?.escalationRule, path, `Stage "${name}"`));
2481
- for (const ec of stage.data.exitConditions ?? []) {
2482
- if (ec.type != null && !VALID_EXIT_CONDITION_TYPES.has(ec.type)) {
2483
- issues.push({
2484
- code: "INVALID_ENUM_VALUE",
2485
- path: `${path}.exitConditions`,
2486
- message: `Stage "${name}" has an exit condition with invalid type "${ec.type}". Expected one of: ${[...VALID_EXIT_CONDITION_TYPES].join(", ")}`,
2487
- severity: "error"
2488
- });
2489
2412
  }
2490
- }
2491
- for (const ec of stage.data.exitConditions ?? []) {
2492
- issues.push(...this.validateRuleTypes(ec.rules, `${path}.exitConditions`, `Stage "${name}" exit condition`));
2493
- }
2494
- for (const ec of stage.data.entryConditions ?? []) {
2495
- issues.push(...this.validateRuleTypes(ec.rules, `${path}.entryConditions`, `Stage "${name}" entry condition`));
2496
- }
2497
- for (const task of flatTasks(stage)) {
2498
- for (const ec of task.entryConditions ?? []) {
2499
- issues.push(...this.validateRuleTypes(ec.rules, path, `Stage "${name}" task entry condition`));
2500
- }
2501
- }
2413
+ ];
2502
2414
  }
2503
- if (profile === "skeleton")
2504
- return issues;
2505
- for (const slaRule of schema.metadata?.slaRules ?? []) {
2506
- if (slaRule.unit != null && !VALID_SLA_UNITS.has(slaRule.unit)) {
2507
- issues.push({
2508
- code: "INVALID_ENUM_VALUE",
2509
- path: "metadata.slaRules",
2510
- message: `Root SLA rule has invalid unit "${slaRule.unit}". Expected one of: ${[...VALID_SLA_UNITS].join(", ")}`,
2415
+ if (parts.major < MIN_SUPPORTED_MAJOR) {
2416
+ return [
2417
+ {
2418
+ code: "CASE_MGMT_INVALID_VERSION",
2419
+ path: "version",
2420
+ message: `Schema version "${String(declared)}" predates V${MIN_SUPPORTED_MAJOR} — this validator requires major version >= ${MIN_SUPPORTED_MAJOR}, and the current schema is "${latest}".`,
2511
2421
  severity: "error"
2512
- });
2513
- }
2514
- issues.push(...this.validateEscalationTriggerTypes(slaRule.escalationRule, "metadata.slaRules", "Root SLA rule"));
2515
- }
2516
- for (const cer of schema.metadata?.caseExitRules ?? []) {
2517
- issues.push(...this.validateRuleTypes(cer.rules, "metadata.caseExitRules", "Case exit condition"));
2518
- }
2519
- for (const edge of schema.edges) {
2520
- if (edge.type !== "case-management:Edge")
2521
- continue;
2522
- const caseEdge = edge;
2523
- issues.push(...this.validateRuleTypes(caseEdge.data.rules, `edges[${edge.id}]`, `Edge "${edgeLabel(edge)}"`));
2524
- }
2525
- return issues;
2526
- }
2527
- validateRuleTypes(rules, path, context) {
2528
- const issues = [];
2529
- for (const andGroup of rules ?? []) {
2530
- for (const rule of andGroup) {
2531
- if (!VALID_RULE_TYPES.has(rule.rule)) {
2532
- issues.push({
2533
- code: "INVALID_ENUM_VALUE",
2534
- path,
2535
- message: `${context} has a rule with invalid type "${rule.rule}". Expected one of: ${[...VALID_RULE_TYPES].join(", ")}`,
2536
- severity: "error"
2537
- });
2538
2422
  }
2539
- }
2423
+ ];
2540
2424
  }
2541
- return issues;
2542
- }
2543
- validateEscalationTriggerTypes(escalationRules, path, context) {
2544
- const issues = [];
2545
- for (const esc of escalationRules ?? []) {
2546
- const triggerType = esc.triggerInfo?.type;
2547
- if (triggerType != null && !VALID_ESCALATION_TRIGGER_TYPES.has(triggerType)) {
2548
- issues.push({
2549
- code: "INVALID_ENUM_VALUE",
2550
- path,
2551
- message: `${context} has an escalation with invalid triggerInfo.type "${triggerType}". Expected one of: ${[...VALID_ESCALATION_TRIGGER_TYPES].join(", ")}`,
2425
+ const latestParts = parseCaseSchemaVersion(latest);
2426
+ if (latestParts !== null && compareCaseSchemaVersions(parts, latestParts) > 0) {
2427
+ return [
2428
+ {
2429
+ code: "CASE_MGMT_VERSION_NEWER_THAN_SCHEMA",
2430
+ path: "version",
2431
+ message: `Schema version "${String(declared)}" is newer than "${latest}", the newest this CLI's @uipath/case-schema knows, so this CLI cannot validate it. Update the CLI; if it is already current, the @uipath/case-schema pin needs to move.`,
2552
2432
  severity: "error"
2553
- });
2554
- }
2555
- }
2556
- return issues;
2557
- }
2558
- validateRuleContents(rule, condition, currentStage, schema, path, elementLabel, profile) {
2559
- const issues = [];
2560
- const condName = conditionLabel(condition);
2561
- switch (rule.rule) {
2562
- case "selected-stage-completed":
2563
- case "selected-stage-exited": {
2564
- if (!rule.selectedStageId) {
2565
- issues.push({
2566
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_SELECTED_STAGE_ID_MISSING",
2567
- path,
2568
- message: `${elementLabel} condition "${condName}" has a "${rule.rule}" rule with no selectedStageId`,
2569
- severity: "error"
2570
- });
2571
- break;
2572
- }
2573
- const target = schema.nodes.find((n) => n.id === rule.selectedStageId);
2574
- if (!target || target.type !== "case-management:Stage" && target.type !== "case-management:ExceptionStage") {
2575
- issues.push({
2576
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_SELECTED_STAGE_MISSING",
2577
- path,
2578
- message: `${elementLabel} condition "${condName}" references a non-existent stage "${rule.selectedStageId}"`,
2579
- severity: "error"
2580
- });
2581
- break;
2582
- }
2583
- const targetStage = target;
2584
- const targetName = stageLabel(targetStage);
2585
- const expectedMarksComplete = rule.rule === "selected-stage-completed";
2586
- const matchingExit = (targetStage.data.exitConditions ?? []).find((c) => (c.marksStageComplete ?? false) === expectedMarksComplete);
2587
- if (!matchingExit) {
2588
- issues.push({
2589
- code: expectedMarksComplete ? "CASE_MGMT_ROOT_CASE_COMPLETE_CONDITION_RULE_SELECTED_STAGE_ID_NOT_FOUND" : "CASE_MGMT_ROOT_CASE_EXIT_CONDITION_RULE_SELECTED_STAGE_ID_NOT_FOUND",
2590
- path,
2591
- message: expectedMarksComplete ? `${elementLabel} condition "${condName}" references stage "${targetName}", which has no exit condition that marks the stage complete` : `${elementLabel} condition "${condName}" references stage "${targetName}", which has no non-completing exit condition`,
2592
- severity: "error"
2593
- });
2594
- }
2595
- break;
2596
- }
2597
- case "selected-tasks-completed": {
2598
- if (profile !== "full")
2599
- break;
2600
- if (!rule.selectedTasksIds || rule.selectedTasksIds.length === 0) {
2601
- issues.push({
2602
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_SELECTED_TASK_IDS_MISSING",
2603
- path,
2604
- message: `${elementLabel} condition "${condName}" has a "selected-tasks-completed" rule with no selectedTasksIds`,
2605
- severity: "error"
2606
- });
2607
- break;
2608
- }
2609
- if (currentStage) {
2610
- const allTaskIds = new Set(flatTasks(currentStage).map((t) => t.id).filter((id) => Boolean(id)));
2611
- const missing = rule.selectedTasksIds.find((id) => !allTaskIds.has(id));
2612
- if (missing) {
2613
- issues.push({
2614
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_SELECTED_TASK_MISSING",
2615
- path,
2616
- message: `${elementLabel} condition "${condName}" references task "${missing}" which is not in this stage`,
2617
- severity: "error"
2618
- });
2619
- }
2620
- }
2621
- break;
2622
- }
2623
- case "required-stages-completed": {
2624
- const allPrimaryStages = schema.nodes.filter((n) => n.type === "case-management:Stage" && n.data.isCaseManagerStage !== true);
2625
- if (allPrimaryStages.length > 0) {
2626
- const hasRequired = allPrimaryStages.some((s) => s.data.isRequired === true);
2627
- if (!hasRequired) {
2628
- issues.push({
2629
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_NO_REQUIRED_STAGE",
2630
- path,
2631
- message: `${elementLabel} condition "${condName}" has a "required-stages-completed" rule but no primary stage is marked isRequired`,
2632
- severity: "error"
2633
- });
2634
- }
2635
- }
2636
- break;
2637
- }
2638
- case "required-tasks-completed": {
2639
- if (profile !== "full")
2640
- break;
2641
- if (currentStage) {
2642
- const tasks = flatTasks(currentStage);
2643
- if (tasks.length > 0) {
2644
- const hasRequired = tasks.some((t) => t.isRequired === true);
2645
- if (!hasRequired) {
2646
- issues.push({
2647
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_NO_REQUIRED_TASK",
2648
- path,
2649
- message: `${elementLabel} condition "${condName}" has a "required-tasks-completed" rule but no task in this stage is marked isRequired`,
2650
- severity: "error"
2651
- });
2652
- }
2653
- }
2654
- }
2655
- break;
2656
- }
2657
- case "user-selected-stage": {
2658
- const otherStages = schema.nodes.filter((n) => (n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage") && n.id !== currentStage?.id);
2659
- const hasWaitForUser = otherStages.some((s) => s.data.exitConditions?.some((c) => c.type === "wait-for-user"));
2660
- if (!hasWaitForUser) {
2661
- issues.push({
2662
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_USER_SELECTED_STAGE_MISSING",
2663
- path,
2664
- message: `${elementLabel} condition "${condName}" uses "user-selected-stage" but no other stage has a wait-for-user exit condition`,
2665
- severity: "error"
2666
- });
2667
- }
2668
- break;
2669
- }
2670
- default:
2671
- break;
2672
- }
2673
- return issues;
2674
- }
2675
- validateConditionDuplicates(conditions, duplicateCode, path, duplicateMessage) {
2676
- const issues = [];
2677
- for (let i = 0;i < conditions.length; i++) {
2678
- for (let j = i + 1;j < conditions.length; j++) {
2679
- const a = conditions[i];
2680
- const b = conditions[j];
2681
- if (a.rules && b.rules && rulesMatch(a.rules, b.rules)) {
2682
- issues.push({
2683
- code: duplicateCode,
2684
- path,
2685
- message: duplicateMessage(a, b),
2686
- severity: "error"
2687
- });
2688
- break;
2689
- }
2690
- }
2691
- }
2692
- return issues;
2693
- }
2694
- validateConditionTooSimilar(conditions, currentStage, schema, path, elementLabel, profile) {
2695
- const issues = [];
2696
- const requiredStageIds = schema.nodes.filter((n) => n.type === "case-management:Stage" && n.data.isRequired === true && n.data.isCaseManagerStage !== true).map((n) => n.id);
2697
- const requiredTaskIds = currentStage ? flatTasks(currentStage).filter((t) => t.isRequired === true).map((t) => t.id ?? "").filter((id) => id !== "") : [];
2698
- const sameSet = (a, b) => a.length === b.length && a.every((id) => b.includes(id));
2699
- const checked = [];
2700
- for (const condition of conditions) {
2701
- const firstRule = condition.rules?.[0]?.[0] ?? null;
2702
- if (!firstRule) {
2703
- checked.push(condition);
2704
- continue;
2705
- }
2706
- for (const other of checked) {
2707
- const otherFirst = other.rules?.[0]?.[0] ?? null;
2708
- if (!otherFirst)
2709
- continue;
2710
- if (firstRule.conditionExpression !== otherFirst.conditionExpression)
2711
- continue;
2712
- let overlap = false;
2713
- if (firstRule.rule === "required-stages-completed" && otherFirst.rule === "selected-stage-completed" || firstRule.rule === "selected-stage-completed" && otherFirst.rule === "required-stages-completed") {
2714
- const ruleStages = firstRule.rule === "required-stages-completed" ? requiredStageIds : [firstRule.selectedStageId ?? ""];
2715
- const otherStages = otherFirst.rule === "required-stages-completed" ? requiredStageIds : [otherFirst.selectedStageId ?? ""];
2716
- overlap = sameSet(ruleStages, otherStages);
2717
- } else if (profile === "full" && (firstRule.rule === "required-tasks-completed" && otherFirst.rule === "selected-tasks-completed" || firstRule.rule === "selected-tasks-completed" && otherFirst.rule === "required-tasks-completed")) {
2718
- const ruleTasks = firstRule.rule === "required-tasks-completed" ? requiredTaskIds : firstRule.selectedTasksIds ?? [];
2719
- const otherTasks = otherFirst.rule === "required-tasks-completed" ? requiredTaskIds : otherFirst.selectedTasksIds ?? [];
2720
- overlap = sameSet(ruleTasks, otherTasks);
2721
- }
2722
- if (overlap) {
2723
- issues.push({
2724
- code: "CASE_MGMT_ROOT_CASE_CONDITION_TOO_SIMILAR",
2725
- path,
2726
- message: `${elementLabel} condition "${conditionLabel(condition)}" overlaps with condition "${conditionLabel(other)}" (different rule types but the same target set)`,
2727
- severity: "error"
2728
- });
2729
- break;
2730
- }
2731
- }
2732
- checked.push(condition);
2733
- }
2734
- return issues;
2735
- }
2736
- validateConditionsBattery(opts) {
2737
- const issues = [];
2738
- for (const condition of opts.conditions) {
2739
- const condName = conditionLabel(condition);
2740
- for (const rule of flatConditionRules(condition)) {
2741
- if (!rule.rule) {
2742
- issues.push({
2743
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_MISSING",
2744
- path: opts.path,
2745
- message: `${opts.elementLabel} condition "${condName}" has a rule object with no rule type set`,
2746
- severity: "error"
2747
- });
2748
- continue;
2749
2433
  }
2750
- issues.push(...this.validateRuleContents(rule, condition, opts.currentStage, opts.schema, opts.path, opts.elementLabel, opts.profile));
2751
- }
2752
- }
2753
- issues.push(...this.validateConditionDuplicates(opts.conditions, opts.duplicateCode, opts.path, (a, b) => `${opts.elementLabel} has duplicate conditions "${conditionLabel(a)}" and "${conditionLabel(b)}"`));
2754
- issues.push(...this.validateConditionTooSimilar(opts.conditions, opts.currentStage, opts.schema, opts.path, opts.elementLabel, opts.profile));
2755
- return issues;
2756
- }
2757
- validateTaskEntryConditions(stage, schema) {
2758
- const issues = [];
2759
- const path = `nodes[${stage.id}]`;
2760
- const stageName = stageLabel(stage);
2761
- const data = stage.data;
2762
- let isCaseManagerStage = data.isCaseManagerStage;
2763
- if (isCaseManagerStage == null) {
2764
- isCaseManagerStage = findFirstStageAfterTrigger(schema) === stage.id;
2765
- }
2766
- if (isCaseManagerStage && schema.metadata?.allowAdhocOptionalStageTasks === true) {
2767
- return issues;
2768
- }
2769
- const tasks = flatTasks(stage);
2770
- for (const task of tasks) {
2771
- const taskName = task.displayName ?? task.id ?? "(unnamed)";
2772
- if (!task.data || Object.keys(task.data).length === 0) {
2773
- continue;
2774
- }
2775
- if (!task.entryConditions?.length) {
2776
- issues.push({
2777
- code: "CASE_MGMT_STAGE_TASK_ENTRY_CONDITION_MISSING",
2778
- path,
2779
- message: `Stage "${stageName}" task "${taskName}" has no entry condition (it would never run)`,
2780
- severity: "error"
2781
- });
2782
- continue;
2783
- }
2784
- issues.push(...this.validateConditionsBattery({
2785
- conditions: task.entryConditions,
2786
- schema,
2787
- currentStage: stage,
2788
- path,
2789
- elementLabel: `Stage "${stageName}" task "${taskName}"`,
2790
- duplicateCode: "CASE_MGMT_TASK_ENTRY_CONDITION_DUPLICATE",
2791
- profile: "full"
2792
- }));
2793
- }
2794
- return issues;
2795
- }
2796
- validateStructure(schema) {
2797
- const issues = [];
2798
- const hasTrigger = schema.nodes.some((n) => n.type === "case-management:Trigger");
2799
- if (!hasTrigger) {
2800
- issues.push({
2801
- code: "CASE_MGMT_NO_TRIGGER_NODE",
2802
- path: "nodes",
2803
- message: "The case definition has no trigger node",
2804
- severity: "error"
2805
- });
2806
- }
2807
- const hasStage = schema.nodes.some((n) => n.type === "case-management:Stage");
2808
- if (!hasStage) {
2809
- issues.push({
2810
- code: "CASE_MGMT_NO_STAGE_NODE",
2811
- path: "nodes",
2812
- message: "The case definition has no stage nodes",
2813
- severity: "error"
2814
- });
2434
+ ];
2815
2435
  }
2816
- return issues;
2817
- }
2818
- validateTriggers(schema, profile) {
2819
- const issues = [];
2820
- const triggers = schema.nodes.filter((n) => n.type === "case-management:Trigger");
2821
- const triggerTargets = new Set;
2822
- for (const trigger of triggers) {
2823
- const name = triggerLabel(trigger);
2824
- const outgoing = schema.edges.filter((e) => e.source === trigger.id);
2825
- if (profile !== "skeleton") {
2826
- const missingTriggerField = findRequiredFieldErrorInUiPath(trigger.data.uipath);
2827
- if (missingTriggerField) {
2828
- issues.push({
2829
- code: "EMPTY_REQUIRED_FIELD",
2830
- path: `nodes[${trigger.id}]`,
2831
- message: `Trigger "${name}" has required field "${missingTriggerField}" that is empty`,
2832
- severity: "error"
2833
- });
2834
- }
2835
- }
2836
- if (outgoing.length === 0) {
2837
- issues.push({
2838
- code: "CASE_MGMT_TRIGGER_NO_EDGES",
2839
- path: `nodes[${trigger.id}]`,
2840
- message: `Trigger "${name}" has no outgoing edges`,
2841
- severity: "error"
2842
- });
2843
- } else {
2844
- for (const edge of outgoing) {
2845
- triggerTargets.add(edge.target);
2846
- }
2847
- }
2848
- }
2849
- if (triggerTargets.size > 1) {
2850
- const canonicalTarget = [...triggerTargets][0];
2851
- for (const trigger of triggers) {
2852
- const name = triggerLabel(trigger);
2853
- const outgoing = schema.edges.filter((e) => e.source === trigger.id);
2854
- for (const edge of outgoing) {
2855
- if (edge.target === canonicalTarget)
2856
- continue;
2857
- issues.push({
2858
- code: "CASE_MGMT_TRIGGER_EDGE_TO_WRONG_STAGE",
2859
- path: `nodes[${trigger.id}]`,
2860
- message: `Trigger "${name}" does not point to the same first stage as other triggers`,
2861
- severity: "error"
2862
- });
2863
- break;
2864
- }
2865
- }
2866
- }
2867
- return issues;
2868
- }
2869
- validateStageShared(stage, labelCounts, profile) {
2870
- const issues = [];
2871
- const path = `nodes[${stage.id}]`;
2872
- const name = stageLabel(stage);
2873
- if (!stage.data.label || stage.data.label.trim() === "") {
2874
- issues.push({
2875
- code: "CASE_MGMT_STAGE_NAME_MISSING",
2876
- path,
2877
- message: "Stage name is missing",
2878
- severity: "error"
2879
- });
2880
- } else if ((labelCounts.get(stage.data.label.trim()) ?? 0) > 1) {
2881
- issues.push({
2882
- code: "CASE_MGMT_STAGE_NAME_NOT_UNIQUE",
2883
- path,
2884
- message: `Stage name "${name}" is not unique`,
2885
- severity: "error"
2886
- });
2887
- }
2888
- if (profile === "skeleton")
2889
- return issues;
2890
- const raw = stage;
2891
- if (raw.entryConditions && !stage.data.entryConditions?.length) {
2892
- issues.push({
2893
- code: "CASE_MGMT_STAGE_ENTRY_CONDITIONS_MISPLACED",
2894
- path,
2895
- message: `Stage "${name}" has entryConditions on the node instead of inside data (should be data.entryConditions)`,
2896
- severity: "error"
2897
- });
2898
- }
2899
- if (raw.exitConditions && !stage.data.exitConditions?.length) {
2900
- issues.push({
2901
- code: "CASE_MGMT_STAGE_EXIT_CONDITIONS_MISPLACED",
2902
- path,
2903
- message: `Stage "${name}" has exitConditions on the node instead of inside data (should be data.exitConditions)`,
2904
- severity: "error"
2905
- });
2906
- }
2907
- const allTasks = profile === "full" ? flatTasks(stage) : [];
2908
- if (profile === "full" && allTasks.length === 0) {
2909
- issues.push({
2910
- code: "CASE_MGMT_STAGE_TASKS_MISSING",
2911
- path,
2912
- message: `Stage "${name}" has no tasks`,
2913
- severity: "warning"
2914
- });
2915
- } else {
2916
- for (const task of allTasks) {
2917
- if (!task.data || Object.keys(task.data).length === 0) {
2918
- issues.push({
2919
- code: "CASE_MGMT_STAGE_TASK_SELECTION_MISSING",
2920
- path,
2921
- message: `Stage "${name}" has a task with no configuration`,
2922
- severity: "warning"
2923
- });
2924
- } else {
2925
- const taskData2 = task.data;
2926
- const missingField = findRequiredFieldErrorInUiPath(taskData2);
2927
- if (missingField) {
2928
- issues.push({
2929
- code: "EMPTY_REQUIRED_FIELD",
2930
- path,
2931
- message: `Stage "${name}" has a task with required field "${missingField}" that is empty`,
2932
- severity: "error"
2933
- });
2934
- } else if (task.type === "action" && !task.data.taskTitle) {
2935
- issues.push({
2936
- code: "EMPTY_REQUIRED_FIELD",
2937
- path,
2938
- message: `Stage "${name}" has an action task with no title`,
2939
- severity: "error"
2940
- });
2941
- }
2942
- }
2943
- for (const entryCondition of task.entryConditions ?? []) {
2944
- if (!entryCondition.rules || entryCondition.rules.length === 0) {
2945
- issues.push({
2946
- code: "CASE_MGMT_ROOT_CASE_CONDITION_RULE_MISSING",
2947
- path,
2948
- message: `Stage "${name}" has a task with an entry condition with no rules`,
2949
- severity: "error"
2950
- });
2951
- }
2952
- }
2953
- const taskDataRaw = task.data;
2954
- const taskDisplayName = task.displayName ?? task.id ?? "(unnamed)";
2955
- if (taskDataRaw?.entryConditions && !task.entryConditions?.length) {
2956
- issues.push({
2957
- code: "CASE_MGMT_TASK_ENTRY_CONDITIONS_MISPLACED",
2958
- path,
2959
- message: `Stage "${name}" task "${taskDisplayName}" has entryConditions inside data (should be on the task object, not data.entryConditions)`,
2960
- severity: "error"
2961
- });
2962
- }
2963
- }
2964
- }
2965
- const sla = stage.data.sla;
2966
- if (sla) {
2967
- const hasEscalations = (sla.escalationRule?.length ?? 0) > 0;
2968
- const hasValue = sla.count != null && sla.count > 0;
2969
- if (hasEscalations && !hasValue) {
2970
- issues.push({
2971
- code: "CASE_MGMT_STAGE_NO_SLA_ESCALATIONS",
2972
- path,
2973
- message: `Stage "${name}" has SLA escalation rules but no SLA value is set`,
2974
- severity: "warning"
2975
- });
2976
- }
2977
- if (hasEscalations) {
2978
- const missingRecipients = (sla.escalationRule ?? []).some((e) => !e.action?.recipients || e.action.recipients.length === 0);
2979
- if (missingRecipients) {
2980
- issues.push({
2981
- code: "CASE_MGMT_STAGE_ESCALATION_RECIPIENTS_MISSING",
2982
- path,
2983
- message: `Stage "${name}" has an escalation rule with no recipients`,
2984
- severity: "warning"
2985
- });
2986
- }
2987
- }
2988
- }
2989
- return issues;
2990
- }
2991
- detectReEntryStages(schema) {
2992
- const stages = schema.nodes.filter((n) => n.type === "case-management:Stage");
2993
- const stageIds = new Set(stages.map((s) => s.id));
2994
- const adj = new Map;
2995
- for (const stage of stages)
2996
- adj.set(stage.id, []);
2997
- for (const edge of schema.edges) {
2998
- if (edge.type === "case-management:Edge" && stageIds.has(edge.source) && stageIds.has(edge.target)) {
2999
- adj.get(edge.source)?.push(edge.target);
3000
- }
3001
- }
3002
- const reEntryTargets = new Set;
3003
- const visited = new Set;
3004
- const inStack = new Set;
3005
- for (const start of stages) {
3006
- if (visited.has(start.id))
3007
- continue;
3008
- const stack = [[start.id, 0]];
3009
- visited.add(start.id);
3010
- inStack.add(start.id);
3011
- while (stack.length > 0) {
3012
- const frame = stack[stack.length - 1];
3013
- const neighbors = adj.get(frame[0]) ?? [];
3014
- if (frame[1] >= neighbors.length) {
3015
- stack.pop();
3016
- inStack.delete(frame[0]);
3017
- } else {
3018
- const neighbor = neighbors[frame[1]++];
3019
- if (inStack.has(neighbor)) {
3020
- reEntryTargets.add(neighbor);
3021
- } else if (!visited.has(neighbor)) {
3022
- visited.add(neighbor);
3023
- inStack.add(neighbor);
3024
- stack.push([neighbor, 0]);
3025
- }
3026
- }
3027
- }
3028
- }
3029
- return reEntryTargets;
3030
- }
3031
- buildLabelCounts(schema) {
3032
- const counts = new Map;
3033
- for (const stage of schema.nodes.filter((n) => n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage")) {
3034
- const label = stage.data.label?.trim() ?? "";
3035
- if (label)
3036
- counts.set(label, (counts.get(label) ?? 0) + 1);
3037
- }
3038
- return counts;
3039
- }
3040
- validatePrimaryStages(schema, profile) {
3041
- const issues = [];
3042
- const stages = schema.nodes.filter((n) => n.type === "case-management:Stage");
3043
- const secondaryStages = schema.nodes.filter((n) => n.type === "case-management:ExceptionStage");
3044
- const reEntryStages = profile === "full" ? this.detectReEntryStages(schema) : new Set;
3045
- const labelCounts = this.buildLabelCounts(schema);
3046
- const suppressMissingErrors = hasCaseManagerStageWithTasks(schema);
3047
- for (const stage of stages) {
3048
- const path = `nodes[${stage.id}]`;
3049
- const name = stageLabel(stage);
3050
- const entryConditions = stage.data.entryConditions ?? [];
3051
- const exitConditions = stage.data.exitConditions ?? [];
3052
- issues.push(...this.validateStageShared(stage, labelCounts, profile));
3053
- const incomingEdges = schema.edges.filter((e) => e.target === stage.id);
3054
- if (incomingEdges.length === 0) {
3055
- const hasSendToStage = secondaryStages.some((ss) => ss.data.exitConditions?.some((ec) => ec.exitToStageId === stage.id));
3056
- const hasWaitForUser = exitConditions.some((ec) => ec.type === "wait-for-user");
3057
- if (!hasSendToStage && !hasWaitForUser) {
3058
- issues.push({
3059
- code: "CASE_MGMT_STAGE_EDGES_MISSING",
3060
- path,
3061
- message: `Stage "${name}" has no incoming edges`,
3062
- severity: "error"
3063
- });
3064
- }
3065
- }
3066
- if (profile === "skeleton")
3067
- continue;
3068
- if (entryConditions.length === 0 && !suppressMissingErrors) {
3069
- issues.push({
3070
- code: "CASE_MGMT_STAGE_ENTRY_CONDITION_MISSING",
3071
- path,
3072
- message: `Stage "${name}" has no entry condition`,
3073
- severity: "error"
3074
- });
3075
- }
3076
- const hasCompleteExit = exitConditions.some((c) => c.marksStageComplete === true);
3077
- if (!hasCompleteExit && !suppressMissingErrors) {
3078
- issues.push({
3079
- code: "CASE_MGMT_STAGE_EXIT_CONDITION_MISSING",
3080
- path,
3081
- message: `Stage "${name}" has no exit condition that marks the stage as complete`,
3082
- severity: "error"
3083
- });
3084
- }
3085
- const allTasks = flatTasks(stage);
3086
- if (reEntryStages.has(stage.id)) {
3087
- const hasReEntryTask = allTasks.some((t) => t.shouldRunOnlyOnce === false || t.shouldRunOnlyOnce === undefined);
3088
- if (!hasReEntryTask) {
3089
- issues.push({
3090
- code: "CASE_MGMT_STAGE_NO_TASKS_RUNNING_ON_RE_ENTRY",
3091
- path,
3092
- message: `Stage "${name}" can be re-entered but no task is marked to run on re-entry`,
3093
- severity: "warning"
3094
- });
3095
- }
3096
- }
3097
- for (const condition of entryConditions) {
3098
- for (const rule of flatConditionRules(condition)) {
3099
- if ((rule.rule === "selected-stage-completed" || rule.rule === "selected-stage-exited") && rule.selectedStageId === stage.id) {
3100
- issues.push({
3101
- code: "CASE_MGMT_STAGE_ENTRY_CONDITION_RULE_SELF_REFERENCE_WARNING",
3102
- path,
3103
- message: `Stage "${name}" entry condition "${conditionLabel(condition)}" references its own stage`,
3104
- severity: "warning"
3105
- });
3106
- }
3107
- }
3108
- }
3109
- const hasWaitForUserExit = exitConditions.some((c) => c.type === "wait-for-user");
3110
- if (hasWaitForUserExit) {
3111
- const otherStages = schema.nodes.filter((n) => (n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage") && n.id !== stage.id);
3112
- const hasUserSelectedStageEntry = otherStages.some((s) => (s.data.entryConditions ?? []).some((c) => flatConditionRules(c).some((r) => r.rule === "user-selected-stage")));
3113
- if (!hasUserSelectedStageEntry) {
3114
- issues.push({
3115
- code: "CASE_MGMT_STAGE_EXIT_CONDITION_USER_SELECTED_STAGE_MISSING",
3116
- path,
3117
- message: `Stage "${name}" has a "wait-for-user" exit condition but no other stage has a "user-selected-stage" entry rule`,
3118
- severity: "error"
3119
- });
3120
- }
3121
- }
3122
- issues.push(...this.validateConditionsBattery({
3123
- conditions: entryConditions,
3124
- schema,
3125
- currentStage: stage,
3126
- path,
3127
- elementLabel: `Stage "${name}"`,
3128
- duplicateCode: "CASE_MGMT_STAGE_ENTRY_CONDITION_DUPLICATE",
3129
- profile
3130
- }));
3131
- issues.push(...this.validateConditionsBattery({
3132
- conditions: exitConditions,
3133
- schema,
3134
- currentStage: stage,
3135
- path,
3136
- elementLabel: `Stage "${name}"`,
3137
- duplicateCode: "CASE_MGMT_STAGE_EXIT_CONDITION_DUPLICATE",
3138
- profile
3139
- }));
3140
- const outgoingEdges = schema.edges.filter((e) => e.source === stage.id && e.type === "case-management:Edge");
3141
- for (const condition of exitConditions) {
3142
- if (!condition.rules)
3143
- continue;
3144
- for (const edge of outgoingEdges) {
3145
- const edgeRules = edge.data.rules;
3146
- if (edgeRules && rulesMatch(condition.rules, edgeRules)) {
3147
- issues.push({
3148
- code: "CASE_MGMT_STAGE_EXIT_CONDITION_DUPLICATE_EDGE",
3149
- path,
3150
- message: `Stage "${name}" has an exit condition that duplicates outgoing edge "${edgeLabel(edge)}"`,
3151
- severity: "error"
3152
- });
3153
- break;
3154
- }
3155
- }
3156
- }
3157
- for (const condition of exitConditions) {
3158
- if (!condition.rules)
3159
- continue;
3160
- for (const ss of secondaryStages) {
3161
- const ssName = stageLabel(ss);
3162
- for (const ec of ss.data.entryConditions ?? []) {
3163
- if (!ec.rules || ec.rules.length === 0)
3164
- continue;
3165
- if (rulesMatch(condition.rules, ec.rules)) {
3166
- issues.push({
3167
- code: "CASE_MGMT_STAGE_EDGE_DUPLICATE_CONDITION_STAGE",
3168
- path,
3169
- message: `Stage "${name}" has an exit condition that duplicates the entry rules of secondary stage "${ssName}"`,
3170
- severity: "error"
3171
- });
3172
- break;
3173
- }
3174
- }
3175
- }
3176
- }
3177
- for (const condition of exitConditions) {
3178
- const allRules = (condition.rules ?? []).flat();
3179
- for (const rule of allRules) {
3180
- const missingField = findRequiredFieldErrorInUiPath(rule.uipath);
3181
- if (missingField) {
3182
- issues.push({
3183
- code: "EMPTY_REQUIRED_FIELD",
3184
- path,
3185
- message: `Stage "${name}" has an exit condition rule with required field "${missingField}" that is empty`,
3186
- severity: "error"
3187
- });
3188
- }
3189
- }
3190
- }
3191
- if (profile === "full") {
3192
- issues.push(...this.validateTaskEntryConditions(stage, schema));
3193
- }
3194
- }
3195
- return issues;
3196
- }
3197
- validateSecondaryStages(schema, profile) {
3198
- const issues = [];
3199
- const secondaryStages = schema.nodes.filter((n) => n.type === "case-management:ExceptionStage");
3200
- const labelCounts = this.buildLabelCounts(schema);
3201
- const suppressMissingErrors = hasCaseManagerStageWithTasks(schema);
3202
- for (const stage of secondaryStages) {
3203
- const path = `nodes[${stage.id}]`;
3204
- const name = stageLabel(stage);
3205
- issues.push(...this.validateStageShared(stage, labelCounts, profile));
3206
- const hasDirectEdges = schema.edges.some((e) => e.source === stage.id || e.target === stage.id);
3207
- if (hasDirectEdges) {
3208
- issues.push({
3209
- code: "CASE_MGMT_SECONDARY_STAGE_EDGES",
3210
- path,
3211
- message: `Secondary stage "${name}" has direct edges (secondary stages use entry/exit conditions instead)`,
3212
- severity: "error"
3213
- });
3214
- }
3215
- if (profile === "skeleton")
3216
- continue;
3217
- const entryConditions = stage.data.entryConditions ?? [];
3218
- if (entryConditions.length === 0) {
3219
- if (!suppressMissingErrors) {
3220
- issues.push({
3221
- code: "CASE_MGMT_SECONDARY_STAGE_ENTRY_RULES_MISSING",
3222
- path,
3223
- message: `Secondary stage "${name}" has no entry conditions`,
3224
- severity: "warning"
3225
- });
3226
- }
3227
- } else {
3228
- for (const condition of entryConditions) {
3229
- if (!condition.rules || condition.rules.length === 0) {
3230
- issues.push({
3231
- code: "CASE_MGMT_SECONDARY_STAGE_EXIT_RULES_MISSING",
3232
- path,
3233
- message: `Secondary stage "${name}" has an entry condition with no rules`,
3234
- severity: "error"
3235
- });
3236
- }
3237
- }
3238
- const stageRules = entryConditions.flatMap((c) => c.rules ?? []);
3239
- for (const otherStage of secondaryStages) {
3240
- if (otherStage.id === stage.id)
3241
- continue;
3242
- const otherName = stageLabel(otherStage);
3243
- const otherRules = (otherStage.data.entryConditions ?? []).flatMap((c) => c.rules ?? []);
3244
- if (stageRules.length > 0 && otherRules.length > 0 && rulesMatch(stageRules, otherRules)) {
3245
- issues.push({
3246
- code: "CASE_MGMT_SECONDARY_STAGE_ENTRY_RULES_DUPLICATE",
3247
- path,
3248
- message: `Secondary stage "${name}" has entry rules that duplicate those of "${otherName}"`,
3249
- severity: "error"
3250
- });
3251
- break;
3252
- }
3253
- }
3254
- for (const condition of entryConditions) {
3255
- if (!condition.rules || condition.rules.length === 0)
3256
- continue;
3257
- for (const edge of schema.edges) {
3258
- if (edge.type !== "case-management:Edge")
3259
- continue;
3260
- const caseEdge = edge;
3261
- const edgeRules = caseEdge.data.rules;
3262
- if (edgeRules && rulesMatch(condition.rules, edgeRules)) {
3263
- issues.push({
3264
- code: "CASE_MGMT_SECONDARY_STAGE_ENTRY_RULES_DUPLICATE_EDGE",
3265
- path,
3266
- message: `Secondary stage "${name}" has an entry condition that duplicates edge "${edgeLabel(edge)}"`,
3267
- severity: "error"
3268
- });
3269
- break;
3270
- }
3271
- }
3272
- }
3273
- }
3274
- const exitConditions = stage.data.exitConditions ?? [];
3275
- if (exitConditions.length === 0) {
3276
- if (!suppressMissingErrors) {
3277
- issues.push({
3278
- code: "CASE_MGMT_SECONDARY_STAGE_EXIT_RULES_MISSING",
3279
- path,
3280
- message: `Secondary stage "${name}" has no exit conditions`,
3281
- severity: "error"
3282
- });
3283
- }
3284
- } else {
3285
- for (const condition of exitConditions) {
3286
- if (!condition.rules || condition.rules.length === 0) {
3287
- issues.push({
3288
- code: "CASE_MGMT_SECONDARY_STAGE_EXIT_RULES_MISSING",
3289
- path,
3290
- message: `Secondary stage "${name}" has an exit condition with no rules`,
3291
- severity: "error"
3292
- });
3293
- }
3294
- }
3295
- }
3296
- if (profile === "full") {
3297
- issues.push(...this.validateTaskEntryConditions(stage, schema));
3298
- }
3299
- }
3300
- return issues;
3301
- }
3302
- validateEdges(schema, profile) {
3303
- const issues = [];
3304
- const secondaryStages = schema.nodes.filter((n) => n.type === "case-management:ExceptionStage");
3305
- const nodeIds = new Set(schema.nodes.map((n) => n.id));
3306
- const edgesBySource = new Map;
3307
- for (const edge of schema.edges) {
3308
- if (!nodeIds.has(edge.source)) {
3309
- issues.push({
3310
- code: "EDGE_INVALID_SOURCE",
3311
- path: `edges[${edge.id}]`,
3312
- message: `Edge "${edgeLabel(edge)}" source node "${edge.source}" does not exist`,
3313
- severity: "error"
3314
- });
3315
- }
3316
- if (!nodeIds.has(edge.target)) {
3317
- issues.push({
3318
- code: "EDGE_INVALID_TARGET",
3319
- path: `edges[${edge.id}]`,
3320
- message: `Edge "${edgeLabel(edge)}" target node "${edge.target}" does not exist`,
3321
- severity: "error"
3322
- });
3323
- }
3324
- const group = edgesBySource.get(edge.source) ?? [];
3325
- group.push(edge);
3326
- edgesBySource.set(edge.source, group);
3327
- }
3328
- if (profile === "skeleton")
3329
- return issues;
3330
- for (const [, edges] of edgesBySource) {
3331
- const caseEdges = edges.filter((e) => e.type === "case-management:Edge");
3332
- for (let i = 0;i < caseEdges.length; i++) {
3333
- let hasDuplicate = false;
3334
- for (let j = i + 1;j < caseEdges.length && !hasDuplicate; j++) {
3335
- const a = caseEdges[i];
3336
- const b = caseEdges[j];
3337
- if (a.data.rules && b.data.rules && rulesMatch(a.data.rules, b.data.rules)) {
3338
- issues.push({
3339
- code: "CASE_MGMT_STAGE_EDGE_DUPLICATE_CONDITION",
3340
- path: `edges[${a.id}]`,
3341
- message: `Two or more edges from the same source node have duplicate conditions`,
3342
- severity: "error"
3343
- });
3344
- hasDuplicate = true;
3345
- }
3346
- }
3347
- }
3348
- for (const edge of caseEdges) {
3349
- if (!edge.data.rules)
3350
- continue;
3351
- for (const ss of secondaryStages) {
3352
- const ssName = stageLabel(ss);
3353
- for (const ec of ss.data.entryConditions ?? []) {
3354
- if (!ec.rules || ec.rules.length === 0)
3355
- continue;
3356
- if (rulesMatch(edge.data.rules, ec.rules)) {
3357
- issues.push({
3358
- code: "CASE_MGMT_STAGE_EDGE_DUPLICATE_CONDITION_STAGE",
3359
- path: `edges[${edge.id}]`,
3360
- message: `Edge "${edgeLabel(edge)}" has conditions that duplicate the entry rules of secondary stage "${ssName}"`,
3361
- severity: "error"
3362
- });
3363
- break;
3364
- }
3365
- }
3366
- }
3367
- const allEdgeRules = (edge.data.rules ?? []).flat();
3368
- for (const rule of allEdgeRules) {
3369
- const missingRuleField = findRequiredFieldErrorInUiPath(rule.uipath);
3370
- if (missingRuleField) {
3371
- issues.push({
3372
- code: "EMPTY_REQUIRED_FIELD",
3373
- path: `edges[${edge.id}]`,
3374
- message: `Edge "${edgeLabel(edge)}" has a rule with required field "${missingRuleField}" that is empty`,
3375
- severity: "error"
3376
- });
3377
- }
3378
- }
3379
- }
3380
- }
3381
- return issues;
3382
- }
3383
- validateOverallCase(schema) {
3384
- const issues = [];
3385
- const stages = schema.nodes.filter((n) => n.type === "case-management:Stage");
3386
- if (stages.length === 0)
3387
- return issues;
3388
- const allStageNodes = schema.nodes.filter((n) => n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage");
3389
- const hasExplicitTerminal = allStageNodes.some((s) => s.data.exitConditions?.some((ec) => ec.type === "exit-only")) || (schema.metadata?.caseExitRules ?? []).length > 0;
3390
- if (hasExplicitTerminal)
3391
- return issues;
3392
- const hasImplicitTerminal = stages.every((s) => schema.edges.some((e) => e.target === s.id));
3393
- if (!hasImplicitTerminal) {
3394
- issues.push({
3395
- code: "CASE_MGMT_VALID_TERMINAL_EXIT_MISSING",
3396
- path: "(overall)",
3397
- message: "The case definition has no valid terminal exit (not all stages have incoming edges and no explicit terminal exit condition)",
3398
- severity: "error"
3399
- });
3400
- }
3401
- return issues;
3402
- }
3403
- validateAdhocNodes(schema, profile) {
3404
- const issues = [];
3405
- const labelCounts = this.buildLabelCounts(schema);
3406
- const triggerIds = new Set(schema.nodes.filter((n) => n.type === "case-management:Trigger").map((n) => n.id));
3407
- const masterStageId = schema.edges.filter((e) => triggerIds.has(e.source)).map((e) => e.target)[0];
3408
- const masterStage = masterStageId ? schema.nodes.find((n) => n.id === masterStageId && (n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage")) : undefined;
3409
- if (!masterStage) {
3410
- issues.push({
3411
- code: "CASE_MGMT_NO_MASTER_ADHOC_STAGE",
3412
- path: "nodes",
3413
- message: "The adhoc case definition has no master orchestration stage",
3414
- severity: "error"
3415
- });
3416
- } else {
3417
- issues.push(...this.validateStageShared(masterStage, labelCounts, profile));
3418
- if (profile === "full") {
3419
- issues.push(...this.validateTaskEntryConditions(masterStage, schema));
3420
- }
3421
- }
3422
- const otherStages = schema.nodes.filter((n) => (n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage") && n.id !== masterStageId);
3423
- if (otherStages.length === 0) {
3424
- if (profile !== "skeleton") {
3425
- issues.push({
3426
- code: "CASE_MGMT_NO_ADDITIONAL_STAGE_NODES",
3427
- path: "nodes",
3428
- message: "The adhoc case definition has no additional stage nodes besides the master stage",
3429
- severity: "error"
3430
- });
3431
- }
3432
- } else {
3433
- for (const stage of otherStages) {
3434
- issues.push(...this.validateStageShared(stage, labelCounts, profile));
3435
- if (profile === "full") {
3436
- issues.push(...this.validateTaskEntryConditions(stage, schema));
3437
- }
3438
- }
3439
- }
3440
- return issues;
3441
- }
3442
- validateOutputVariableUniqueness(schema) {
3443
- const issues = [];
3444
- const allStages = schema.nodes.filter((n) => n.type === "case-management:Stage" || n.type === "case-management:ExceptionStage");
3445
- const seen = new Map;
3446
- for (const stage of allStages) {
3447
- const name = stageLabel(stage);
3448
- for (const task of flatTasks(stage)) {
3449
- const taskName = task.displayName ?? task.id ?? "(unnamed)";
3450
- const outputs = task.data?.outputs ?? [];
3451
- for (const output of outputs) {
3452
- const varId = output.var ?? output.id;
3453
- if (!varId)
3454
- continue;
3455
- const location = `"${name}"/"${taskName}"`;
3456
- const existing = seen.get(varId);
3457
- if (existing) {
3458
- issues.push({
3459
- code: "CASE_MGMT_DUPLICATE_OUTPUT_VARIABLE",
3460
- path: `nodes[${stage.id}]`,
3461
- message: `Output variable "${varId}" in ${location} duplicates the same variable in ${existing}`,
3462
- severity: "error"
3463
- });
3464
- } else {
3465
- seen.set(varId, location);
3466
- }
3467
- }
3468
- }
3469
- }
3470
- return issues;
2436
+ return [];
3471
2437
  }
3472
2438
  runTaskValidators(schema, profile = "full") {
3473
2439
  const issues = [];
@@ -3493,4 +2459,4 @@ export {
3493
2459
  CaseValidateService
3494
2460
  };
3495
2461
 
3496
- //# debugId=CF6CF2FB8A5C033D64756E2164756E21
2462
+ //# debugId=38BA7511D4E039A064756E2164756E21