@uipath/maestro-builder-sdk 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/bindings-v2.d.ts +37 -0
- package/dist/bindings-v2.js +155 -0
- package/dist/bindings.d.ts +6 -0
- package/dist/bindings.js +6 -0
- package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
- package/dist/bpmn/bpmn-expr-check.js +218 -0
- package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
- package/dist/bpmn/bpmn-sdk.js +1476 -0
- package/dist/bpmn/check-cli.d.ts +3 -0
- package/dist/bpmn/check-cli.js +32 -0
- package/dist/bpmn/check.d.ts +25 -0
- package/dist/bpmn/check.js +430 -0
- package/dist/bpmn/compile-cli.d.ts +3 -0
- package/dist/bpmn/compile-cli.js +60 -0
- package/dist/bpmn/connector.d.ts +56 -0
- package/dist/bpmn/connector.js +199 -0
- package/dist/bpmn/decompile-cli.d.ts +8 -0
- package/dist/bpmn/decompile-cli.js +54 -0
- package/dist/bpmn/decompile.d.ts +6 -0
- package/dist/bpmn/decompile.js +1108 -0
- package/dist/bpmn/format-profile.d.ts +27 -0
- package/dist/bpmn/format-profile.js +11 -0
- package/dist/bpmn/intsvc.d.ts +77 -0
- package/dist/bpmn/intsvc.js +155 -0
- package/dist/bpmn/load.d.ts +4 -0
- package/dist/bpmn/load.js +22 -0
- package/dist/bpmn/merge-cli.d.ts +3 -0
- package/dist/bpmn/merge-cli.js +45 -0
- package/dist/bpmn/merge.d.ts +19 -0
- package/dist/bpmn/merge.js +293 -0
- package/dist/bpmn/registry-types.json +1869 -0
- package/dist/bpmn/serialize.d.ts +28 -0
- package/dist/bpmn/serialize.js +757 -0
- package/dist/bpmn/tidy-cli.d.ts +2 -0
- package/dist/bpmn/tidy-cli.js +39 -0
- package/dist/bpmn/typed-node.d.ts +382 -0
- package/dist/bpmn/typed-node.js +551 -0
- package/dist/bpmn/uipath-moddle.v1.json +823 -0
- package/dist/bpmn/xml-ranges.d.ts +45 -0
- package/dist/bpmn/xml-ranges.js +151 -0
- package/dist/case/case-expr-check.d.ts +35 -0
- package/dist/case/case-expr-check.js +85 -0
- package/dist/case/case-sdk.d.ts +1282 -0
- package/dist/case/case-sdk.js +923 -0
- package/dist/case/check-cli.d.ts +3 -0
- package/dist/case/check-cli.js +31 -0
- package/dist/case/check.d.ts +18 -0
- package/dist/case/check.js +456 -0
- package/dist/case/compile-cli.d.ts +3 -0
- package/dist/case/compile-cli.js +291 -0
- package/dist/case/decompile-cli.d.ts +9 -0
- package/dist/case/decompile-cli.js +117 -0
- package/dist/case/decompile.d.ts +187 -0
- package/dist/case/decompile.js +1170 -0
- package/dist/case/entry-points.d.ts +79 -0
- package/dist/case/entry-points.js +118 -0
- package/dist/case/format-profile.d.ts +60 -0
- package/dist/case/format-profile.js +79 -0
- package/dist/case/ids.d.ts +24 -0
- package/dist/case/ids.js +76 -0
- package/dist/case/load.d.ts +12 -0
- package/dist/case/load.js +32 -0
- package/dist/case/preserve.d.ts +133 -0
- package/dist/case/preserve.js +105 -0
- package/dist/case/serialize.d.ts +10 -0
- package/dist/case/serialize.js +1414 -0
- package/dist/check-cli.d.ts +3 -0
- package/dist/check-cli.js +118 -0
- package/dist/check.d.ts +146 -0
- package/dist/check.js +4898 -0
- package/dist/cli/commands/bpmn.d.ts +2 -0
- package/dist/cli/commands/bpmn.js +73 -0
- package/dist/cli/commands/case.d.ts +2 -0
- package/dist/cli/commands/case.js +60 -0
- package/dist/cli/commands/flow.d.ts +2 -0
- package/dist/cli/commands/flow.js +99 -0
- package/dist/cli/commands/registry.d.ts +10 -0
- package/dist/cli/commands/registry.js +170 -0
- package/dist/cli/common.d.ts +30 -0
- package/dist/cli/common.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli-run.d.ts +2 -0
- package/dist/cli-run.js +42 -0
- package/dist/compile-cli.d.ts +3 -0
- package/dist/compile-cli.js +160 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +201 -0
- package/dist/core/actions.d.ts +3244 -0
- package/dist/core/actions.js +1062 -0
- package/dist/core/binding-messages.d.ts +1 -0
- package/dist/core/binding-messages.js +16 -0
- package/dist/core/bindings.d.ts +12 -0
- package/dist/core/bindings.js +67 -0
- package/dist/core/ceql.d.ts +73 -0
- package/dist/core/ceql.js +272 -0
- package/dist/core/cli-spelling.d.ts +39 -0
- package/dist/core/cli-spelling.js +62 -0
- package/dist/core/connector-raw-node.d.ts +21 -0
- package/dist/core/connector-raw-node.js +47 -0
- package/dist/core/connectors.d.ts +184 -0
- package/dist/core/connectors.js +128 -0
- package/dist/core/expr-check.d.ts +93 -0
- package/dist/core/expr-check.js +186 -0
- package/dist/core/expr.d.ts +323 -0
- package/dist/core/expr.js +400 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +10 -0
- package/dist/core/library.d.ts +440 -0
- package/dist/core/library.js +1183 -0
- package/dist/core/list-envelope.d.ts +45 -0
- package/dist/core/list-envelope.js +97 -0
- package/dist/core/lookup-store.d.ts +22 -0
- package/dist/core/lookup-store.js +85 -0
- package/dist/core/lookups.d.ts +227 -0
- package/dist/core/lookups.js +231 -0
- package/dist/core/node-classes.d.ts +234 -0
- package/dist/core/node-classes.js +158 -0
- package/dist/core/script-return.d.ts +68 -0
- package/dist/core/script-return.js +197 -0
- package/dist/core/stable-id.d.ts +28 -0
- package/dist/core/stable-id.js +37 -0
- package/dist/core-definitions.json +16249 -0
- package/dist/decompile-cli.d.ts +3 -0
- package/dist/decompile-cli.js +131 -0
- package/dist/decompile.d.ts +138 -0
- package/dist/decompile.js +2482 -0
- package/dist/emit.d.ts +10 -0
- package/dist/emit.js +60 -0
- package/dist/event-filters.d.ts +130 -0
- package/dist/event-filters.js +137 -0
- package/dist/flow-expr-check.d.ts +17 -0
- package/dist/flow-expr-check.js +193 -0
- package/dist/flow-sdk.d.ts +1341 -0
- package/dist/flow-sdk.js +1164 -0
- package/dist/flow-variables.d.ts +13 -0
- package/dist/flow-variables.js +42 -0
- package/dist/format-profile.d.ts +72 -0
- package/dist/format-profile.js +97 -0
- package/dist/generators/_connections.py +287 -0
- package/dist/generators/_events.py +355 -0
- package/dist/generators/_lookups.py +114 -0
- package/dist/generators/_naming.py +185 -0
- package/dist/generators/_objects.py +299 -0
- package/dist/generators/_resolve.py +273 -0
- package/dist/generators/convert_library_to_md.py +1441 -0
- package/dist/generators/extract_library.py +125 -0
- package/dist/generators/generate_connectors_ts.py +936 -0
- package/dist/generators/generate_library_json.py +2527 -0
- package/dist/generators/generate_triggers_ts.py +110 -0
- package/dist/generators/materialize_registry.py +155 -0
- package/dist/generators/prepare_connector.py +1688 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/ixp-definition.json +1982 -0
- package/dist/library.d.ts +6 -0
- package/dist/library.js +6 -0
- package/dist/load.d.ts +16 -0
- package/dist/load.js +95 -0
- package/dist/merge-cli.d.ts +3 -0
- package/dist/merge-cli.js +47 -0
- package/dist/merge.d.ts +125 -0
- package/dist/merge.js +0 -0
- package/dist/migrate.d.ts +15 -0
- package/dist/migrate.js +245 -0
- package/dist/node-runtime.d.ts +3 -0
- package/dist/node-runtime.js +22 -0
- package/dist/package-name.d.ts +17 -0
- package/dist/package-name.js +17 -0
- package/dist/ref-resolve.d.ts +77 -0
- package/dist/ref-resolve.js +170 -0
- package/dist/registry/cache.d.ts +55 -0
- package/dist/registry/cache.js +134 -0
- package/dist/registry/curated-overlay.d.ts +16 -0
- package/dist/registry/curated-overlay.js +62 -0
- package/dist/registry/extractor.d.ts +22 -0
- package/dist/registry/extractor.js +18 -0
- package/dist/registry/materialize-bin.d.ts +2 -0
- package/dist/registry/materialize-bin.js +7 -0
- package/dist/registry/materialize-cli.d.ts +1 -0
- package/dist/registry/materialize-cli.js +46 -0
- package/dist/registry/materializer.d.ts +23 -0
- package/dist/registry/materializer.js +34 -0
- package/dist/registry/path-cli.d.ts +1 -0
- package/dist/registry/path-cli.js +56 -0
- package/dist/registry/prepare-cli.d.ts +62 -0
- package/dist/registry/prepare-cli.js +185 -0
- package/dist/registry/pull-cli.d.ts +1 -0
- package/dist/registry/pull-cli.js +274 -0
- package/dist/registry/python.d.ts +26 -0
- package/dist/registry/python.js +46 -0
- package/dist/registry/search-cli.d.ts +52 -0
- package/dist/registry/search-cli.js +159 -0
- package/dist/schema-version.d.ts +165 -0
- package/dist/schema-version.js +148 -0
- package/dist/serialize.d.ts +64 -0
- package/dist/serialize.js +6398 -0
- package/lib/README.md +112 -0
- package/lib/library-json.samples/index.json +158 -0
- package/lib/library-json.samples/invariant-collections.json +83 -0
- package/lib/library-json.samples/list-envelopes.json +123 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
- package/package.json +85 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-cli — fast static validation of <Name>.case.ts (no output file).
|
|
4
|
+
*
|
|
5
|
+
* Usage: node --experimental-strip-types check-cli.js <case.ts | BaseName>
|
|
6
|
+
* Exits 0 when clean (warnings allowed), 1 on any error.
|
|
7
|
+
*/
|
|
8
|
+
import { resolveCaseFile, loadBuiltCase } from './load.js';
|
|
9
|
+
import { check } from './check.js';
|
|
10
|
+
import { runWhenInvokedDirectly } from '../cli-run.js';
|
|
11
|
+
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
12
|
+
export async function run(argv) {
|
|
13
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
14
|
+
console.error('usage: check <case.ts | BaseName>');
|
|
15
|
+
return 2;
|
|
16
|
+
}
|
|
17
|
+
const file = resolveCaseFile(argv[0]);
|
|
18
|
+
ensureTypeScriptRuntime();
|
|
19
|
+
const built = await loadBuiltCase(file);
|
|
20
|
+
const diags = check(built);
|
|
21
|
+
for (const d of diags)
|
|
22
|
+
console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
|
|
23
|
+
const errors = diags.filter((d) => d.level === 'error');
|
|
24
|
+
if (errors.length) {
|
|
25
|
+
console.error(`check: ${errors.length} error(s).`);
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
console.log(`check: OK${diags.length ? ` (${diags.length} warning(s))` : ''}`);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
runWhenInvokedDirectly(import.meta.url, 'check', run);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* case/check — fast static validation of a BuiltCase before serialization.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the common `uip maestro case validate` failures early (task without
|
|
5
|
+
* an entry rule, case/stage without a completion rule, an unsatisfiable
|
|
6
|
+
* `required-tasks-completed`, unresolved stage/task references, duplicate stage
|
|
7
|
+
* labels) so authoring gets instant feedback. The authoritative gate is still
|
|
8
|
+
* `uip maestro case validate` / `@uipath/case-schema` on the emitted file.
|
|
9
|
+
*/
|
|
10
|
+
import type { BuiltCase } from './case-sdk.js';
|
|
11
|
+
export interface Diagnostic {
|
|
12
|
+
level: 'error' | 'warning';
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
where?: string;
|
|
16
|
+
suggestion?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function check(built: BuiltCase): Diagnostic[];
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { types } from '../core/expr.js';
|
|
2
|
+
import { authoredCaseVersion, CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
|
|
3
|
+
import { schemaVersionRefusal } from '../schema-version.js';
|
|
4
|
+
import { caseJsonPreservation, caseJsonPreservationErrors } from './preserve.js';
|
|
5
|
+
const TYPE_NAMES = new Set(Object.values(types));
|
|
6
|
+
// audit-case-coverage: rule-slot table start
|
|
7
|
+
// This runtime table must equal the committed matrix's rejected cells. The audit
|
|
8
|
+
// parses these object literals; the product-pin matrix currently rejects none.
|
|
9
|
+
const ILLEGAL = [];
|
|
10
|
+
// audit-case-coverage: rule-slot table end
|
|
11
|
+
export function check(built) {
|
|
12
|
+
const diags = [];
|
|
13
|
+
for (const message of caseJsonPreservationErrors(caseJsonPreservation(built)))
|
|
14
|
+
diags.push({
|
|
15
|
+
level: 'error',
|
|
16
|
+
code: 'PRESERVATION_COLLISION',
|
|
17
|
+
message: `Invalid Case JSON preservation payload: ${message}.`,
|
|
18
|
+
});
|
|
19
|
+
if (!built.id)
|
|
20
|
+
diags.push({ level: 'error', code: 'NO_ID', message: 'casePlan() requires an id.' });
|
|
21
|
+
const requestedVersion = authoredCaseVersion(built);
|
|
22
|
+
if (requestedVersion !== undefined) {
|
|
23
|
+
diags.push({
|
|
24
|
+
level: 'warning',
|
|
25
|
+
code: 'VERSION_DEPRECATED',
|
|
26
|
+
message: `.version(${JSON.stringify(requestedVersion)}) is deprecated: the Case JSON schema version is declared with .schemaVersion(), which accepts only versions this build can write. This SDK writes ${CASE_FORMAT_PROFILE.writeVersion}.`,
|
|
27
|
+
});
|
|
28
|
+
if (requestedVersion !== CASE_FORMAT_PROFILE.writeVersion)
|
|
29
|
+
diags.push({
|
|
30
|
+
level: 'error',
|
|
31
|
+
code: 'VERSION_MISMATCH',
|
|
32
|
+
message: `.version(${JSON.stringify(requestedVersion)}) does not match the serializer's ${CASE_FORMAT_PROFILE.writeVersion} format profile. A document version is a grammar claim, not an author release value.`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// The DECLARED grammar version (`.schemaVersion()`), as opposed to the
|
|
36
|
+
// deprecated `.version()` above. It is already refused at the call, so this
|
|
37
|
+
// only fires for a BuiltCase assembled by hand — which `serialize()` would
|
|
38
|
+
// then throw on, so reporting it first is what makes the message actionable.
|
|
39
|
+
const schemaRefusal = schemaVersionRefusal(CASE_SCHEMA_VERSIONS, built.version);
|
|
40
|
+
if (schemaRefusal)
|
|
41
|
+
diags.push({ level: 'error', code: 'SCHEMA_VERSION_UNSUPPORTED', message: schemaRefusal });
|
|
42
|
+
const checkName = (kind, name, where) => {
|
|
43
|
+
if (/[:.]/.test(name))
|
|
44
|
+
diags.push({
|
|
45
|
+
level: 'error',
|
|
46
|
+
code: 'NAME_HYGIENE',
|
|
47
|
+
message: `${kind} "${name}" contains ":" or ".". Runtime event keys do not escape these characters, so instance state can silently corrupt; rename it without either character.`,
|
|
48
|
+
where,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
checkName('Case name', built.name, built.name);
|
|
52
|
+
for (const [index, section] of (built.caseAppConfig?.sections ?? []).entries()) {
|
|
53
|
+
const where = `Case App section "${section.title || index + 1}"`;
|
|
54
|
+
const details = section.details;
|
|
55
|
+
if (!details || typeof details !== 'object' || Array.isArray(details)) {
|
|
56
|
+
diags.push({
|
|
57
|
+
level: 'error',
|
|
58
|
+
code: 'CASE_APP_DETAILS_TYPE',
|
|
59
|
+
message: `${where} details must be a map of primitive values.`,
|
|
60
|
+
where,
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const entries = Object.entries(details);
|
|
65
|
+
if (entries.length > 6)
|
|
66
|
+
diags.push({
|
|
67
|
+
level: 'error',
|
|
68
|
+
code: 'CASE_APP_DETAILS_LIMIT',
|
|
69
|
+
message: `${where} has ${entries.length} details; Case App sections support at most 6.`,
|
|
70
|
+
where,
|
|
71
|
+
});
|
|
72
|
+
const nested = entries.find(([, value]) => value !== null && !['string', 'number', 'boolean'].includes(typeof value));
|
|
73
|
+
if (nested)
|
|
74
|
+
diags.push({
|
|
75
|
+
level: 'error',
|
|
76
|
+
code: 'CASE_APP_DETAILS_TYPE',
|
|
77
|
+
message: `${where} detail "${nested[0]}" must be a string, number, boolean, or null; nested objects and arrays are not supported.`,
|
|
78
|
+
where,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const stageLabels = new Set();
|
|
82
|
+
const taskNames = new Set();
|
|
83
|
+
let selectNextStageCount = 0;
|
|
84
|
+
for (const stage of built.stages) {
|
|
85
|
+
checkName('Stage label', stage.label, stage.label);
|
|
86
|
+
if (stageLabels.has(stage.label))
|
|
87
|
+
diags.push({ level: 'error', code: 'DUP_STAGE', message: `Duplicate stage label "${stage.label}".`, where: stage.label });
|
|
88
|
+
stageLabels.add(stage.label);
|
|
89
|
+
for (const exit of stage.exit) {
|
|
90
|
+
if (!exit.selectNextStage)
|
|
91
|
+
continue;
|
|
92
|
+
selectNextStageCount += 1;
|
|
93
|
+
if (exit.type !== 'wait-for-user')
|
|
94
|
+
diags.push({
|
|
95
|
+
level: 'error',
|
|
96
|
+
code: 'SELECT_NEXT_STAGE_EXIT_TYPE',
|
|
97
|
+
message: `Stage "${stage.label}" configures selectNextStage on an exit that is not type "wait-for-user".`,
|
|
98
|
+
where: stage.label,
|
|
99
|
+
});
|
|
100
|
+
if (!exit.selectNextStage.objectName.trim())
|
|
101
|
+
diags.push({
|
|
102
|
+
level: 'error',
|
|
103
|
+
code: 'SELECT_NEXT_STAGE_OBJECT_NAME',
|
|
104
|
+
message: `Stage "${stage.label}" selectNextStage.objectName must name a Data Fabric entity.`,
|
|
105
|
+
where: stage.label,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const lane of stage.lanes)
|
|
109
|
+
for (const t of lane) {
|
|
110
|
+
// Task displayNames must be unique across the case: the platform rejects
|
|
111
|
+
// duplicates (CRITICAL), and a bare selected-tasks-completed reference
|
|
112
|
+
// would silently mis-resolve to the first task of that name.
|
|
113
|
+
if (taskNames.has(t.name))
|
|
114
|
+
diags.push({
|
|
115
|
+
level: 'error',
|
|
116
|
+
code: 'DUP_TASK',
|
|
117
|
+
message: `Duplicate task displayName "${t.name}" — task names must be unique across the case.`,
|
|
118
|
+
where: `${stage.label}/${t.name}`,
|
|
119
|
+
});
|
|
120
|
+
taskNames.add(t.name);
|
|
121
|
+
// Also register the qualified `Stage/Task` key so a qualified reference resolves.
|
|
122
|
+
taskNames.add(`${stage.label}/${t.name}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Condition `displayName`s must be unique across the whole case — `uip maestro
|
|
126
|
+
// case validate` rejects a repeat as CASE_MGMT_RULE_NAME_DUPLICATE, at error
|
|
127
|
+
// severity. The namespace spans stage entry, stage exit and task entry
|
|
128
|
+
// conditions plus `metadata.caseExitRules`, in every combination (probed
|
|
129
|
+
// against case-schema 0.1016.2).
|
|
130
|
+
//
|
|
131
|
+
// …EXCEPT that the validator ignores the names the DESIGNER generates, so a
|
|
132
|
+
// case built in Studio Web can repeat them freely. That allowlist is why the
|
|
133
|
+
// old SDK defaults failed: `Entry rule` sat one token short of the exempt
|
|
134
|
+
// `Entry rule <n>` family, and `Complete rule` was never in it at all.
|
|
135
|
+
// Mirroring the allowlist here is what keeps this diagnostic from contradicting
|
|
136
|
+
// the validator on a decompiled brownfield file — CMGoldenExpense legitimately
|
|
137
|
+
// carries 18 conditions named "Entry rule 1" and validates clean.
|
|
138
|
+
//
|
|
139
|
+
// Over-exempting is the safe direction: a name wrongly treated as exempt is
|
|
140
|
+
// still caught downstream by `validate`, whereas a false positive here would
|
|
141
|
+
// be noise on a document the platform accepts.
|
|
142
|
+
//
|
|
143
|
+
// WARNING, not error, because an error GATES EMISSION and the duplicates that
|
|
144
|
+
// reach this code are overwhelmingly brownfield: real V27/V30 documents the
|
|
145
|
+
// platform itself produced carry them (the LinearThreeStages migration oracle
|
|
146
|
+
// repeats "Entry Rule 1" six times). Refusing to compile those would turn the
|
|
147
|
+
// decompile → edit → recompile loop into a hard stop for anyone whose existing
|
|
148
|
+
// case has duplicate names — over a defect they did not introduce and this
|
|
149
|
+
// edit is not about. `validate` remains the authoritative gate and still
|
|
150
|
+
// reports it as an error.
|
|
151
|
+
const DESIGNER_GENERATED = /^(?:(?:Entry|Exit|Completion) rule \d+|Case complete|Stage complete|Tasks completed rule)$/;
|
|
152
|
+
// `serialize` de-duplicates the defaults it generates, so anything reported
|
|
153
|
+
// here is a name the author spelled out twice; renaming it for them would be
|
|
154
|
+
// the worse surprise, and without this the collision only surfaces one step
|
|
155
|
+
// later, from `validate`, after a compile.
|
|
156
|
+
const conditionNames = new Map();
|
|
157
|
+
const checkConditionName = (displayName, where) => {
|
|
158
|
+
if (!displayName || DESIGNER_GENERATED.test(displayName))
|
|
159
|
+
return;
|
|
160
|
+
const first = conditionNames.get(displayName);
|
|
161
|
+
if (first !== undefined)
|
|
162
|
+
diags.push({
|
|
163
|
+
level: 'warning',
|
|
164
|
+
code: 'DUP_RULE_NAME',
|
|
165
|
+
message: `Rule name "${displayName}" is not unique — also used by ${first}. `
|
|
166
|
+
+ '`uip maestro case validate` rejects this as CASE_MGMT_RULE_NAME_DUPLICATE; rename one of them.',
|
|
167
|
+
where,
|
|
168
|
+
});
|
|
169
|
+
else
|
|
170
|
+
conditionNames.set(displayName, where);
|
|
171
|
+
};
|
|
172
|
+
for (const [i, c] of built.caseExit.entries())
|
|
173
|
+
checkConditionName(c.displayName, `case exit condition ${i + 1}`);
|
|
174
|
+
for (const stage of built.stages) {
|
|
175
|
+
for (const c of stage.entry)
|
|
176
|
+
checkConditionName(c.displayName, `stage "${stage.label}" entry`);
|
|
177
|
+
for (const c of stage.exit)
|
|
178
|
+
checkConditionName(c.displayName, `stage "${stage.label}" exit`);
|
|
179
|
+
for (const lane of stage.lanes)
|
|
180
|
+
for (const task of lane)
|
|
181
|
+
for (const c of task.entry)
|
|
182
|
+
checkConditionName(c.displayName, `task "${task.name}" entry`);
|
|
183
|
+
}
|
|
184
|
+
if (selectNextStageCount > 1)
|
|
185
|
+
diags.push({
|
|
186
|
+
level: 'error',
|
|
187
|
+
code: 'SELECT_NEXT_STAGE_MULTIPLE',
|
|
188
|
+
message: 'A case can configure selectNextStage on at most one exit because the wire contract has one case-level connector.',
|
|
189
|
+
});
|
|
190
|
+
if (built.stages.length === 0)
|
|
191
|
+
diags.push({ level: 'error', code: 'NO_STAGES', message: 'Case has no stages.' });
|
|
192
|
+
const hasCompletion = built.caseExit.some((c) => c.marksCaseComplete !== false);
|
|
193
|
+
if (!hasCompletion)
|
|
194
|
+
diags.push({
|
|
195
|
+
level: 'error',
|
|
196
|
+
code: 'NO_CASE_COMPLETION',
|
|
197
|
+
message: 'Case has no completion rule — add .completeWhen(rule(\'required-stages-completed\')).',
|
|
198
|
+
});
|
|
199
|
+
const refPlacement = (r, slot, where) => {
|
|
200
|
+
const bad = ILLEGAL.find((x) => x.rule === r.rule && x.slot === slot);
|
|
201
|
+
if (bad)
|
|
202
|
+
diags.push({
|
|
203
|
+
level: 'error',
|
|
204
|
+
code: 'RULE_PLACEMENT',
|
|
205
|
+
message: `Rule "${r.rule}" is not legal at ${slot} — ${bad.why}.`,
|
|
206
|
+
where,
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
const refStage = (r, where) => {
|
|
210
|
+
if ((r.rule === 'selected-stage-completed' || r.rule === 'selected-stage-exited')) {
|
|
211
|
+
if (!r.stage || !stageLabels.has(r.stage))
|
|
212
|
+
diags.push({ level: 'error', code: 'UNKNOWN_STAGE_REF', message: `Rule "${r.rule}" references unknown stage "${r.stage}".`, where });
|
|
213
|
+
}
|
|
214
|
+
if (r.rule === 'selected-tasks-completed') {
|
|
215
|
+
if (!r.tasks?.length && !r.expression)
|
|
216
|
+
diags.push({
|
|
217
|
+
level: 'warning',
|
|
218
|
+
code: 'RULE_TASKS_REQUIRED',
|
|
219
|
+
message: 'Rule "selected-tasks-completed" has neither selected tasks nor an expression gate — pass { tasks: [...] } or use when(expression).',
|
|
220
|
+
where,
|
|
221
|
+
});
|
|
222
|
+
for (const n of r.tasks ?? [])
|
|
223
|
+
if (!taskNames.has(n))
|
|
224
|
+
diags.push({ level: 'error', code: 'UNKNOWN_TASK_REF', message: `Rule "selected-tasks-completed" references unknown task "${n}".`, where });
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
// ── `=js:vars.<name>` resolvability — REMOVED, superseded by #257 ───────────
|
|
228
|
+
// This file briefly carried a VAR_NOT_REFERENCEABLE error asserting that only a
|
|
229
|
+
// trigger-bound In-arg (`.input(shape, { from })`) could be read as `=js:vars.x`,
|
|
230
|
+
// and that a bare `.var()` always failed `uip maestro case validate` with
|
|
231
|
+
// "Variable 'vars.x' does not exist". That was empirically true when written and
|
|
232
|
+
// is now FALSE: #257 fixed the serializer to emit the `inputOutputs` companion
|
|
233
|
+
// with `id: <name>` / `elementId: "root"`, which is what the platform resolves
|
|
234
|
+
// `vars.<name>` against — so a declared `.var()` is readable.
|
|
235
|
+
//
|
|
236
|
+
// Both halves of the old rule are superseded:
|
|
237
|
+
// - "declared vars are unreadable" -> no longer true (#257 serializer fix).
|
|
238
|
+
// - "undeclared vars.x slips through" -> #257 also added a strict namespace
|
|
239
|
+
// check to CaseBuilder.build(), which throws CaseBuildError with a
|
|
240
|
+
// near-miss suggestion before check() ever runs.
|
|
241
|
+
// Re-adding any variable-reachability rule here would reintroduce a false
|
|
242
|
+
// positive that blocks emit (this gate runs before serialization), so it must be
|
|
243
|
+
// re-derived from the serializer rather than from remembered probe results.
|
|
244
|
+
for (const stage of built.stages) {
|
|
245
|
+
checkStage(stage, diags, refStage, refPlacement);
|
|
246
|
+
stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
|
|
247
|
+
}
|
|
248
|
+
built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
|
|
249
|
+
for (const c of built.caseExit)
|
|
250
|
+
for (const g of c.rules)
|
|
251
|
+
for (const r of g) {
|
|
252
|
+
refStage(r, 'case-exit');
|
|
253
|
+
refPlacement(r, 'case-exit', 'case-exit');
|
|
254
|
+
}
|
|
255
|
+
return diags;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* SLA guardrails surfaced early: zero at-risk percentages that the engine
|
|
259
|
+
* coerces to 100, empty recipient lists, and deadlines with no escalations.
|
|
260
|
+
*/
|
|
261
|
+
function checkSla(sla, where, diags) {
|
|
262
|
+
if (sla.count < 0 || sla.count > 1000)
|
|
263
|
+
diags.push({
|
|
264
|
+
level: 'error',
|
|
265
|
+
code: 'SLA_COUNT_RANGE',
|
|
266
|
+
message: `${where} has count ${sla.count}; SLA count must be between 0 and 1000 inclusive (engine error 400019).`,
|
|
267
|
+
where,
|
|
268
|
+
});
|
|
269
|
+
else if (sla.count === 0)
|
|
270
|
+
diags.push({ level: 'warning', code: 'SLA_NONPOSITIVE', message: `${where} has a zero-length deadline (count 0).`, where });
|
|
271
|
+
if (sla.escalations.length === 0) {
|
|
272
|
+
diags.push({ level: 'warning', code: 'SLA_NO_ESCALATIONS', message: `${where} has no escalations — the deadline notifies no one.`, where });
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
for (const e of sla.escalations) {
|
|
276
|
+
if (e.trigger === 'at-risk' && e.atRiskPercentage === 0)
|
|
277
|
+
diags.push({
|
|
278
|
+
level: 'warning',
|
|
279
|
+
code: 'ESCALATION_ZERO_ATRISK_PCT',
|
|
280
|
+
message: `${where}: an 'at-risk' escalation sets atRiskPercentage to 0, but the engine silently treats 0 as 100.`,
|
|
281
|
+
where,
|
|
282
|
+
});
|
|
283
|
+
if (e.notify.length === 0)
|
|
284
|
+
diags.push({ level: 'warning', code: 'ESCALATION_NO_RECIPIENTS', message: `${where}: an escalation notifies no one (empty notify).`, where });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function isNilOrEmpty(value) {
|
|
288
|
+
if (value === undefined || value === null)
|
|
289
|
+
return true;
|
|
290
|
+
if (typeof value === 'string' || Array.isArray(value))
|
|
291
|
+
return value.length === 0;
|
|
292
|
+
if (typeof value === 'object')
|
|
293
|
+
return Object.keys(value).length === 0;
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
function checkStage(stage, diags, refStage, refPlacement) {
|
|
297
|
+
const where = `stage "${stage.label}"`;
|
|
298
|
+
if (stage.exception && stage.required !== false)
|
|
299
|
+
diags.push({
|
|
300
|
+
level: 'warning',
|
|
301
|
+
code: 'EXCEPTION_STAGE_REQUIRED',
|
|
302
|
+
message: `${where} is a secondary stage and should be explicitly non-required — add .required(false) so it does not gate required-stages-completed.`,
|
|
303
|
+
where: stage.label,
|
|
304
|
+
});
|
|
305
|
+
for (const c of stage.entry)
|
|
306
|
+
for (const g of c.rules)
|
|
307
|
+
for (const r of g) {
|
|
308
|
+
refStage(r, where);
|
|
309
|
+
refPlacement(r, 'stage-entry', where);
|
|
310
|
+
}
|
|
311
|
+
for (const c of stage.exit)
|
|
312
|
+
for (const g of c.rules)
|
|
313
|
+
for (const r of g) {
|
|
314
|
+
refStage(r, where);
|
|
315
|
+
refPlacement(r, 'stage-exit', where);
|
|
316
|
+
}
|
|
317
|
+
for (const lane of stage.lanes)
|
|
318
|
+
for (const task of lane)
|
|
319
|
+
for (const c of task.entry)
|
|
320
|
+
for (const g of c.rules)
|
|
321
|
+
for (const r of g)
|
|
322
|
+
refPlacement(r, 'task-entry', `task "${task.name}" in ${where}`);
|
|
323
|
+
// Two exits whose rule sets are IDENTICAL collide: `uip maestro case validate`
|
|
324
|
+
// rejects the stage with "duplicate exit rules". Verified — and verified in the
|
|
325
|
+
// other direction too, because the obvious guess is wrong: exits sharing a rule
|
|
326
|
+
// TYPE are fine as long as something distinguishes them. Both of these are Valid:
|
|
327
|
+
// .exitWhen(rule('required-tasks-completed', { expression: '=js:v < 25000' }), …)
|
|
328
|
+
// .exitWhen(rule('required-tasks-completed', { expression: '=js:v >= 25000' }), …)
|
|
329
|
+
// (3-way with distinct expressions is Valid as well). Only a literal duplicate
|
|
330
|
+
// fails. So the signature below must include the expression and the refs, not
|
|
331
|
+
// just the rule name — keying on the rule type alone would reject legal
|
|
332
|
+
// value-tiered routing, which is the natural way to express "under $25k goes here".
|
|
333
|
+
const sig = (c) => JSON.stringify(c.rules.map((g) => g.map((r) => [r.rule, r.stage ?? '', (r.tasks ?? []).join(','), r.expression ?? ''])));
|
|
334
|
+
const seenExit = new Map();
|
|
335
|
+
stage.exit.forEach((c, i) => {
|
|
336
|
+
const k = sig(c);
|
|
337
|
+
const first = seenExit.get(k);
|
|
338
|
+
if (first !== undefined)
|
|
339
|
+
diags.push({
|
|
340
|
+
level: 'error',
|
|
341
|
+
code: 'DUP_EXIT_RULE',
|
|
342
|
+
message: `${where} has two exits (#${first + 1} and #${i + 1}) with identical rules — ` +
|
|
343
|
+
`\`uip maestro case validate\` rejects this with "duplicate exit rules". ` +
|
|
344
|
+
`Distinguish them with an \`expression\` (e.g. \`rule('required-tasks-completed', { expression: '=js:vars.x < 100' })\`), ` +
|
|
345
|
+
`or give each outcome its own task and gate on \`selected-tasks-completed\`.`,
|
|
346
|
+
where: stage.label,
|
|
347
|
+
});
|
|
348
|
+
else
|
|
349
|
+
seenExit.set(k, i);
|
|
350
|
+
});
|
|
351
|
+
const marksComplete = stage.exit.some((c) => c.marksStageComplete);
|
|
352
|
+
if (!marksComplete && !stage.exception)
|
|
353
|
+
diags.push({ level: 'warning', code: 'STAGE_NO_COMPLETION', message: `${where} has no exit rule marking it complete.`, where: stage.label });
|
|
354
|
+
const tasks = stage.lanes.flat();
|
|
355
|
+
const anyRequired = tasks.some((t) => t.required);
|
|
356
|
+
const usesRequiredTasks = stage.exit.some((c) => c.rules.some((g) => g.some((r) => r.rule === 'required-tasks-completed')));
|
|
357
|
+
if (usesRequiredTasks && tasks.length > 0 && !anyRequired)
|
|
358
|
+
diags.push({
|
|
359
|
+
level: 'warning',
|
|
360
|
+
code: 'NO_REQUIRED_TASK',
|
|
361
|
+
message: `${where} exits on 'required-tasks-completed' but no task is marked .required().`,
|
|
362
|
+
where: stage.label,
|
|
363
|
+
});
|
|
364
|
+
for (const t of tasks) {
|
|
365
|
+
if (t.kind === 'wait-for-timer') {
|
|
366
|
+
const timerValues = [t.timer?.duration, t.timer?.date, t.timer?.cycle]
|
|
367
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0);
|
|
368
|
+
if (timerValues.length !== 1)
|
|
369
|
+
diags.push({
|
|
370
|
+
level: 'error',
|
|
371
|
+
code: 'TIMER_VALUE_REQUIRED',
|
|
372
|
+
message: `Timer task "${t.name}" needs exactly one non-empty duration, date, or cycle. For a validating stand-in, use .waitForTimer({ duration: 'PT1M' }).`,
|
|
373
|
+
where: t.name,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const invalidBinding = [...(t.inputs ?? []), ...(t.outputs ?? [])]
|
|
377
|
+
.find((binding) => !TYPE_NAMES.has(binding.type));
|
|
378
|
+
if (invalidBinding)
|
|
379
|
+
diags.push({
|
|
380
|
+
level: 'error',
|
|
381
|
+
code: 'TASK_IO_TYPE',
|
|
382
|
+
message: `Task "${t.name}" binding "${'var' in invalidBinding ? invalidBinding.var : invalidBinding.name}" has unsupported type ${JSON.stringify(invalidBinding.type)}. Use a type name such as "string" or "jsonSchema"; jsonSchema(...) is only for declaring a structured case variable.`,
|
|
383
|
+
where: t.name,
|
|
384
|
+
});
|
|
385
|
+
if (t.kind === 'flow-process' && (!t.ref || !t.ref.name.trim()))
|
|
386
|
+
diags.push({
|
|
387
|
+
level: 'error',
|
|
388
|
+
code: 'FLOW_PROCESS_EMPTY_DATA',
|
|
389
|
+
message: `Flow Process task "${t.name}" must name a published Flow; an empty reference degrades to a runtime no-op placeholder.`,
|
|
390
|
+
where: t.name,
|
|
391
|
+
});
|
|
392
|
+
if (t.kind === 'external-agent' || t.kind === 'external-workflow') {
|
|
393
|
+
const family = t.kind === 'external-agent' ? 'External Agent' : 'External Workflow';
|
|
394
|
+
if (!t.external)
|
|
395
|
+
diags.push({
|
|
396
|
+
level: 'error',
|
|
397
|
+
code: 'EXTERNAL_TASK_EMPTY_DATA',
|
|
398
|
+
message: `${family} task "${t.name}" has no configuration; empty data degrades to a runtime no-op placeholder.`,
|
|
399
|
+
where: t.name,
|
|
400
|
+
});
|
|
401
|
+
else {
|
|
402
|
+
if (t.external.mode !== 'sync' && t.external.mode !== 'async')
|
|
403
|
+
diags.push({
|
|
404
|
+
level: 'error',
|
|
405
|
+
code: 'EXTERNAL_TASK_MODE',
|
|
406
|
+
message: `${family} task "${t.name}" mode must be "sync" or "async"; no other service type is valid.`,
|
|
407
|
+
where: t.name,
|
|
408
|
+
});
|
|
409
|
+
if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
|
|
410
|
+
diags.push({
|
|
411
|
+
level: 'error',
|
|
412
|
+
code: 'EXTERNAL_TASK_DESCRIPTOR',
|
|
413
|
+
message: `${family} task "${t.name}" requires a generated Integration Service descriptor.`,
|
|
414
|
+
where: t.name,
|
|
415
|
+
});
|
|
416
|
+
if (!t.external.connector?.connection?.trim())
|
|
417
|
+
diags.push({
|
|
418
|
+
level: 'error',
|
|
419
|
+
code: 'EXTERNAL_TASK_CONNECTION',
|
|
420
|
+
message: `${family} task "${t.name}" requires a non-empty connection binding name.`,
|
|
421
|
+
where: t.name,
|
|
422
|
+
});
|
|
423
|
+
if (!t.external.connector?.folder?.trim())
|
|
424
|
+
diags.push({
|
|
425
|
+
level: 'error',
|
|
426
|
+
code: 'EXTERNAL_TASK_FOLDER',
|
|
427
|
+
message: `${family} task "${t.name}" requires a non-empty folder binding name.`,
|
|
428
|
+
where: t.name,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (stage.exception && t.runOnce !== undefined)
|
|
433
|
+
diags.push({
|
|
434
|
+
level: 'warning',
|
|
435
|
+
code: 'TASK_REENTRY_IGNORED',
|
|
436
|
+
message: `Task "${t.name}" in ${where} sets a run-once/re-entry option, but secondary stages ignore it. Remove .runOnce()/.runOnReEntry().`,
|
|
437
|
+
where: t.name,
|
|
438
|
+
});
|
|
439
|
+
const emptyRequiredField = [...(t.action?.inputs ?? []), ...(t.action?.outputs ?? [])]
|
|
440
|
+
.find((field) => field.required && isNilOrEmpty(field.value));
|
|
441
|
+
if (emptyRequiredField)
|
|
442
|
+
diags.push({
|
|
443
|
+
level: 'error',
|
|
444
|
+
code: 'EMPTY_REQUIRED_FIELD',
|
|
445
|
+
message: `Action field "${emptyRequiredField.name}" in task "${t.name}" is required but has no value. Set its value or remove required: true; required does not mean reviewer-must-fill.`,
|
|
446
|
+
where: t.name,
|
|
447
|
+
});
|
|
448
|
+
if (t.entry.length === 0)
|
|
449
|
+
diags.push({
|
|
450
|
+
level: 'error',
|
|
451
|
+
code: 'TASK_NO_ENTRY',
|
|
452
|
+
message: `Task "${t.name}" in ${where} has no entry rule — add .entryWhen(rule('current-stage-entered')).`,
|
|
453
|
+
where: t.name,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|