@uipath/maestro-builder-sdk 6.18.4 → 6.18.6

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.
@@ -3,7 +3,7 @@
3
3
  * check-cli — fast static validation of <Name>.case.ts (no output file).
4
4
  *
5
5
  * Usage: node --experimental-strip-types check-cli.js <case.ts | BaseName> \
6
- * [--library <library-json dir>] [--connectors-local <overlay dir>]
6
+ * [--library <library-json dir>] [--connectors-local <overlay dir>] [--bindings <bindings.json>]
7
7
  * Exits 0 when clean (warnings allowed), 1 on any error.
8
8
  *
9
9
  * The library is opened exactly as `compile-cli` opens it (`openCliLibrary`), so
@@ -14,11 +14,13 @@ import { resolveCaseFile, loadBuiltCase } from './load.js';
14
14
  import { check } from './check.js';
15
15
  import { caseCheckOpts } from './cli-library.js';
16
16
  import { openCliLibrary } from '../core/cli-library.js';
17
- import { runWhenInvokedDirectly } from '../cli-run.js';
17
+ import { Bindings } from '../core/bindings.js';
18
+ import { defaultBindingsFile } from '../workdir.js';
19
+ import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
18
20
  import { ensureTypeScriptRuntime } from '../node-runtime.js';
19
21
  /** Flags that take a value: their argument is never the source positional. */
20
- const VALUE_FLAGS = new Set(['--library', '--connectors-local']);
21
- const USAGE = 'usage: check <case.ts | BaseName> [--library <dir>] [--connectors-local <dir>]';
22
+ const VALUE_FLAGS = new Set(['--library', '--connectors-local', '--bindings']);
23
+ const USAGE = 'usage: check <case.ts | BaseName> [--library <dir>] [--connectors-local <dir>] [--bindings <file>]';
22
24
  export async function run(argv) {
23
25
  if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
24
26
  console.error(USAGE);
@@ -32,7 +34,9 @@ export async function run(argv) {
32
34
  const file = resolveCaseFile(input);
33
35
  ensureTypeScriptRuntime();
34
36
  const built = await loadBuiltCase(file);
35
- const diags = check(built, caseCheckOpts(openCliLibrary(argv)));
37
+ // bindings.json as `case compile` reads it, for the binding-label rules (#1034).
38
+ const bindings = new Bindings(flagValue(argv, '--bindings') ?? defaultBindingsFile());
39
+ const diags = check(built, caseCheckOpts(openCliLibrary(argv), bindings));
36
40
  reportDiagnostics(diags);
37
41
  for (const d of diags)
38
42
  console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
@@ -25,19 +25,32 @@ export interface Diagnostic {
25
25
  * `compile` actually makes. Case `serialize` resolves the connector operation and
26
26
  * the event, and validates a connector task's inputs and resolves its lookups
27
27
  * through the shared `buildConnectorInputs` (`connectorInputs`, `lookups`,
28
- * `resolutions`). It does not check binding labels or BYOA, so those hooks stay
29
- * off until Case compile enforces them.
28
+ * `resolutions`), and refuses a connection/folder label bindings.json cannot
29
+ * turn into a resource key (`bindings`, `bindingLabelRefusal`). It does not
30
+ * check BYOA, so that hook stays off until Case compile enforces it.
30
31
  *
31
32
  * Every hook fails open: without a library, `check` stays a pure function of the
32
33
  * built model.
33
34
  */
34
- export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | 'connectorInputs' | 'lookups' | 'resolutions' | 'schemaReadiness'> & {
35
+ export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | 'connectorInputs' | 'lookups' | 'resolutions' | 'schemaReadiness' | 'bindings'> & {
35
36
  /**
36
37
  * What Case `compile` would refuse an event subscription with, or `undefined`
37
38
  * when it resolves: `EVENT_UNKNOWN` (the event is not in the library, or
38
39
  * refuses the `object`) or `EVENT_GENERIC_NO_OBJECT` (a GENERIC event with no
39
40
  * `object`). Built on `resolveCaseSubscription`, the call compile makes.
40
41
  */
42
+ /**
43
+ * What Case `compile` would refuse an External Agent / External Workflow
44
+ * task's resolved descriptor with (`externalDescriptorRefusal`), or
45
+ * `undefined`: also `undefined` when the descriptor does not resolve, which
46
+ * `connectorResolveError` reports.
47
+ */
48
+ externalDescriptorError?: (kind: 'external-agent' | 'external-workflow', descriptor: {
49
+ key: string;
50
+ action: string;
51
+ version?: string;
52
+ object?: string;
53
+ }) => string | undefined;
41
54
  subscriptionError?: (kind: 'trigger' | 'event', subscription: EventSubscription) => {
42
55
  code: string;
43
56
  message: string;
@@ -2,8 +2,9 @@ import { types } from '../core/expr.js';
2
2
  import { authoredCaseVersion, CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
3
3
  import { schemaVersionRefusal } from '../schema-version.js';
4
4
  import { caseJsonPreservation, caseJsonPreservationErrors } from './preserve.js';
5
- import { EQUALS_EXPR_DIALECT, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
5
+ import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
6
6
  import { checkEventResolution } from '../core/event-checks.js';
7
+ import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT } from './external.js';
7
8
  const TYPE_NAMES = new Set(Object.values(types));
8
9
  // audit-case-coverage: rule-slot table start
9
10
  // This runtime table must equal the committed matrix's rejected cells. The audit
@@ -113,6 +114,11 @@ export function check(built, opts = {}) {
113
114
  message: `Stage "${stage.label}" selectNextStage.objectName must name a Data Fabric entity.`,
114
115
  where: stage.label,
115
116
  });
117
+ // Its connector's labels resolve through the same `bindingRef` as a
118
+ // connector task's, so they get the same binding-label rules.
119
+ const connector = exit.selectNextStage.connector;
120
+ if (connector)
121
+ checkBindingLabels('select-next-stage connector', connector, opts, (code, message) => diags.push({ level: 'error', code, message, where: stage.label }), { key: connector.connectorKey });
116
122
  }
117
123
  for (const lane of stage.lanes)
118
124
  for (const t of lane) {
@@ -270,7 +276,10 @@ export function check(built, opts = {}) {
270
276
  *
271
277
  * - a connector task and an External Agent / External Workflow task resolve
272
278
  * their operation (`library.resolve`): CONNECTOR_NOT_IN_LIBRARY,
273
- * CONNECTOR_OBJECT_REQUIRED, CONNECTOR_OBJECT_NOT_GENERIC;
279
+ * CONNECTOR_OBJECT_REQUIRED, CONNECTOR_OBJECT_NOT_GENERIC; then their inputs
280
+ * (`buildConnectorInputs`): CONNECTOR_INPUT, LOOKUP_UNRESOLVED;
281
+ * - an external task's resolved descriptor (`externalDescriptorRefusal`):
282
+ * EXTERNAL_TASK_DESCRIPTOR_CONTRACT;
274
283
  * - an event trigger, a wait-for-connector task and a wait-for-connector rule
275
284
  * resolve their subscription (`resolveCaseSubscription`): EVENT_UNKNOWN,
276
285
  * EVENT_GENERIC_NO_OBJECT.
@@ -291,6 +300,7 @@ function checkLibraryConnectors(built, opts, diags) {
291
300
  diags.push({ level: 'error', code: refused.code, message: `${where}: ${refused.message}`, where: at });
292
301
  // A `where` lookup nobody recorded: Flow's LOOKUP_UNRESOLVED warning, from the
293
302
  // shared event rules. Only `resolutions` is passed, so nothing else in them fires.
303
+ checkBindingLabels(where, sub, opts, (code, message) => diags.push({ level: 'error', code, message, where: at }), { key: sub.connector, action: sub.event });
294
304
  checkEventResolution(sub, where, kind, { resolutions: opts.resolutions ?? {} }, (code, message) => diags.push({ level: 'error', code, message, where: at }), (code, message) => diags.push({ level: 'warning', code, message, where: at }));
295
305
  };
296
306
  const rules = (conditions, where) => {
@@ -321,7 +331,12 @@ function checkLibraryConnectors(built, opts, diags) {
321
331
  if ((t.kind === 'external-agent' || t.kind === 'external-workflow')
322
332
  && t.external?.connector?.key?.trim() && t.external.connector.action?.trim()) {
323
333
  const where = `${t.kind === 'external-agent' ? 'External Agent' : 'External Workflow'} task "${t.name}"`;
324
- checkConnectorContract(where, t.external.connector, opts, reporter(where, t.name));
334
+ const spec = t.external.connector;
335
+ checkConnectorLookups(where, spec, opts, EQUALS_EXPR_DIALECT, reporter(where, t.name));
336
+ checkConnectorContract(where, spec, opts, reporter(where, t.name));
337
+ const refused = opts.externalDescriptorError?.(t.kind, spec);
338
+ if (refused !== undefined)
339
+ diags.push({ level: 'error', code: EXTERNAL_TASK_DESCRIPTOR_CONTRACT, message: `${where}: ${refused}`, where: t.name });
325
340
  }
326
341
  if (t.kind === 'wait-for-connector' && isSubscription(t.waitConnector))
327
342
  subscription('event', t.waitConnector, `Wait-for-connector task "${t.name}"`, t.name);
@@ -1,4 +1,5 @@
1
1
  import type { Library } from '../core/library.js';
2
+ import type { BindingsView } from '../core/binding-messages.js';
2
3
  import type { CaseCheckOpts } from './check.js';
3
4
  /**
4
5
  * Case's check options from a library the CLI already opened, or `{}` without one.
@@ -7,10 +8,13 @@ import type { CaseCheckOpts } from './check.js';
7
8
  * the operation and event resolution (the event through `resolveCaseSubscription`,
8
9
  * compile's own call), a connector task's field contract and lookups (the
9
10
  * `=`-expression validator Case compile runs), and the schema-readiness warnings.
10
- * The binding-label and BYOA hooks of the shared bag are left out, because Case
11
- * `serialize` does not enforce them.
11
+ * With `bindings`, the binding-label rules (BINDING_SELF_NAME, BINDING_UNDECLARED),
12
+ * which Case `serialize` enforces through the same `bindingLabelRefusal`. The
13
+ * BYOA hooks of the shared bag are left out, because Case `serialize` does not
14
+ * enforce them.
12
15
  *
13
16
  * @param library - The library `openCliLibrary` returned, or `undefined`.
17
+ * @param bindings - The project's bindings.json, as compile reads it.
14
18
  * @returns The options for Case `check`.
15
19
  */
16
- export declare function caseCheckOpts(library: Library | undefined): CaseCheckOpts;
20
+ export declare function caseCheckOpts(library: Library | undefined, bindings?: BindingsView): CaseCheckOpts;
@@ -8,6 +8,7 @@
8
8
  * both `case check` and `case compile`'s check pass call.
9
9
  */
10
10
  import { connectorCheckOpts } from '../core/cli-library.js';
11
+ import { externalDescriptorRefusal } from './external.js';
11
12
  import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscription } from './subscription.js';
12
13
  /**
13
14
  * Case's check options from a library the CLI already opened, or `{}` without one.
@@ -16,22 +17,36 @@ import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscrip
16
17
  * the operation and event resolution (the event through `resolveCaseSubscription`,
17
18
  * compile's own call), a connector task's field contract and lookups (the
18
19
  * `=`-expression validator Case compile runs), and the schema-readiness warnings.
19
- * The binding-label and BYOA hooks of the shared bag are left out, because Case
20
- * `serialize` does not enforce them.
20
+ * With `bindings`, the binding-label rules (BINDING_SELF_NAME, BINDING_UNDECLARED),
21
+ * which Case `serialize` enforces through the same `bindingLabelRefusal`. The
22
+ * BYOA hooks of the shared bag are left out, because Case `serialize` does not
23
+ * enforce them.
21
24
  *
22
25
  * @param library - The library `openCliLibrary` returned, or `undefined`.
26
+ * @param bindings - The project's bindings.json, as compile reads it.
23
27
  * @returns The options for Case `check`.
24
28
  */
25
- export function caseCheckOpts(library) {
29
+ export function caseCheckOpts(library, bindings) {
26
30
  if (!library)
27
- return {};
28
- const shared = connectorCheckOpts(library);
31
+ return bindings ? { bindings } : {};
32
+ const shared = connectorCheckOpts(library, bindings);
29
33
  return {
34
+ ...(bindings ? { bindings } : {}),
30
35
  connectorResolveError: shared.connectorResolveError,
31
36
  connectorInputs: shared.connectorInputs,
32
37
  lookups: shared.lookups,
33
38
  resolutions: shared.resolutions,
34
39
  schemaReadiness: shared.schemaReadiness,
40
+ externalDescriptorError: (kind, d) => {
41
+ let rc;
42
+ try {
43
+ rc = library.resolve(d.key, d.action, d.version, d.object);
44
+ }
45
+ catch {
46
+ return undefined; // connectorResolveError's to report
47
+ }
48
+ return externalDescriptorRefusal(kind, rc);
49
+ },
35
50
  subscriptionError: (kind, sub) => {
36
51
  try {
37
52
  resolveCaseSubscription(library, kind, sub);
@@ -49,7 +49,9 @@ export async function run(argv) {
49
49
  // which is what the three copies of these lines used to each decide for itself.
50
50
  // Opened BEFORE the check pass, which reads the same library (#1014).
51
51
  const library = openCliLibrary(argv);
52
- const diags = check(built, caseCheckOpts(library));
52
+ const bindFile = flagValue(argv, '--bindings') ?? defaultBindingsFile();
53
+ const bindings = new Bindings(bindFile);
54
+ const diags = check(built, caseCheckOpts(library, bindings));
53
55
  for (const d of diags)
54
56
  console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
55
57
  // Reported before serialize, so every exit path carries what stderr printed.
@@ -59,8 +61,6 @@ export async function run(argv) {
59
61
  console.error(`compile: ${errors.length} error(s); not emitting.`);
60
62
  return 1;
61
63
  }
62
- const bindFile = flagValue(argv, '--bindings') ?? defaultBindingsFile();
63
- const bindings = new Bindings(bindFile);
64
64
  let caseplan;
65
65
  try {
66
66
  caseplan = serialize(built, { library, bindings });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * case/external — the descriptor contract of an External Agent / External
3
+ * Workflow task, shared by Case `serialize` (which throws it) and Case `check`
4
+ * (which reports it as EXTERNAL_TASK_DESCRIPTOR_CONTRACT), so the two refuse the
5
+ * same descriptors with the same text (#1035).
6
+ *
7
+ * The task's inputs are not here: they go through the shared
8
+ * `buildConnectorInputs`, which `check` already runs as `connectorInputs`.
9
+ */
10
+ import type { ResolvedConnector } from '../core/library.js';
11
+ /** Check's code for {@link externalDescriptorRefusal}. */
12
+ export declare const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = "EXTERNAL_TASK_DESCRIPTOR_CONTRACT";
13
+ /**
14
+ * Why a resolved descriptor cannot back this external task, or `undefined`.
15
+ *
16
+ * An External Agent task needs an `AgentExecution` operation and an External
17
+ * Workflow task a `ProcessExecution` one: the service type the task emits
18
+ * (`Intsvc.SyncAgentExecution`, …) is chosen by the family, and a descriptor of
19
+ * the other kind (or a plain connector action) would run the wrong contract.
20
+ * Both need the operation's `uiPathActivityTypeId`, which the task emits as its
21
+ * `activity` context.
22
+ *
23
+ * @param kind - The task kind.
24
+ * @param rc - What `library.resolve` returned for the task's descriptor.
25
+ * @returns The refusal, or `undefined`.
26
+ */
27
+ export declare function externalDescriptorRefusal(kind: 'external-agent' | 'external-workflow', rc: Pick<ResolvedConnector, 'nodeType' | 'version' | 'activityTypeId' | 'entry'>): string | undefined;
@@ -0,0 +1,25 @@
1
+ /** Check's code for {@link externalDescriptorRefusal}. */
2
+ export const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = 'EXTERNAL_TASK_DESCRIPTOR_CONTRACT';
3
+ /**
4
+ * Why a resolved descriptor cannot back this external task, or `undefined`.
5
+ *
6
+ * An External Agent task needs an `AgentExecution` operation and an External
7
+ * Workflow task a `ProcessExecution` one: the service type the task emits
8
+ * (`Intsvc.SyncAgentExecution`, …) is chosen by the family, and a descriptor of
9
+ * the other kind (or a plain connector action) would run the wrong contract.
10
+ * Both need the operation's `uiPathActivityTypeId`, which the task emits as its
11
+ * `activity` context.
12
+ *
13
+ * @param kind - The task kind.
14
+ * @param rc - What `library.resolve` returned for the task's descriptor.
15
+ * @returns The refusal, or `undefined`.
16
+ */
17
+ export function externalDescriptorRefusal(kind, rc) {
18
+ const op = rc.entry?.operation ?? {};
19
+ const expectedSubType = kind === 'external-agent' ? 'AgentExecution' : 'ProcessExecution';
20
+ if (op.subType !== expectedSubType)
21
+ return `${kind} task requires a ${expectedSubType} descriptor; resolved ${String(op.subType ?? 'no subtype')}.`;
22
+ if (!rc.activityTypeId)
23
+ return `${kind} task descriptor ${rc.nodeType}@${rc.version} has no uiPathActivityTypeId.`;
24
+ return undefined;
25
+ }
@@ -11,8 +11,10 @@
11
11
  import { IdGen } from './ids.js';
12
12
  import { buildConnectorInputs, nestConnectorBody, transportHttpMethod } from '../core/connector-inputs.js';
13
13
  import { EQUALS_EXPR_DIALECT } from '../core/connector-checks.js';
14
+ import { bindingLabelRefusal } from '../core/binding-messages.js';
14
15
  import { resolveEventWhere } from '../core/lookups.js';
15
16
  import { resolveCaseSubscription } from './subscription.js';
17
+ import { externalDescriptorRefusal } from './external.js';
16
18
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
17
19
  import { resolveSchemaVersion } from '../schema-version.js';
18
20
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
@@ -176,12 +178,13 @@ export function serialize(built, opts = {}) {
176
178
  const context = [];
177
179
  if (connector) {
178
180
  context.push(io('connectorKey', 'string', { value: connector.connectorKey }));
179
- const connection = bindingRef(resolver, connector.connection, 'Connection', 'ConnectionId', `${connector.connectorKey} connection`);
181
+ const site = { where: 'select-next-stage connector', target: { key: connector.connectorKey } };
182
+ const connection = bindingRef(resolver, connector.connection, 'Connection', 'ConnectionId', `${connector.connectorKey} connection`, { ...site, attribute: 'connection' });
180
183
  if (connection) {
181
184
  context.push(io('connection', 'string', { value: `=bindings.${connection.id}` }));
182
185
  context.push(io('resourceKey', 'string', { value: connection.resourceKey }));
183
186
  }
184
- const folder = bindingRef(resolver, connector.folder, 'Connection', 'folderKey', 'FolderKey');
187
+ const folder = bindingRef(resolver, connector.folder, 'Connection', 'folderKey', 'FolderKey', { ...site, attribute: 'folder' });
185
188
  if (folder)
186
189
  context.push(io('folderKey', 'string', { value: `=bindings.${folder.id}` }));
187
190
  }
@@ -696,7 +699,7 @@ function emitTriggers(triggers, ids, resolver) {
696
699
  // placeholder. A subscription resolves through the same library/binding
697
700
  // machinery as connector tasks and wait-for-connector bags.
698
701
  const bag = t.eventSubscription
699
- ? resolvedSubscriptionBag('trigger', t.eventSubscription, resolver, triggerId)
702
+ ? resolvedSubscriptionBag('trigger', t.eventSubscription, resolver, triggerId, `Event trigger "${label}"`)
700
703
  : { serviceType: CASE_FORMAT_PROFILE.eventServiceType };
701
704
  if (t.eventOutputs?.length) {
702
705
  const outputs = t.eventOutputs.map((o) => {
@@ -835,10 +838,10 @@ function taskData(task, resolver, taskId, stageId, ids) {
835
838
  return data;
836
839
  }
837
840
  case 'connector':
838
- return connectorTaskData(task.connector, resolver, taskId);
841
+ return connectorTaskData(task.connector, resolver, taskId, `Connector task "${task.name}"`);
839
842
  case 'external-agent':
840
843
  case 'external-workflow':
841
- return externalTaskData(task.kind, task.external, resolver);
844
+ return externalTaskData(task.kind, task.external, resolver, `${task.kind === 'external-agent' ? 'External Agent' : 'External Workflow'} task "${task.name}"`);
842
845
  case 'wait-for-timer': {
843
846
  if (task.timer?.duration)
844
847
  return { timerType: 'timeDuration', timeDuration: task.timer.duration };
@@ -851,7 +854,7 @@ function taskData(task, resolver, taskId, stageId, ids) {
851
854
  case 'wait-for-connector':
852
855
  // Floor placeholder (no connector named) = `serviceType` only; naming the
853
856
  // connector/operation emits the `context` subscription.
854
- return waitConnectorBag(task.waitConnector, { alwaysContext: false }, resolver, `${stageId}-${taskId}`);
857
+ return waitConnectorBag(task.waitConnector, { alwaysContext: false }, resolver, `${stageId}-${taskId}`, `Wait-for-connector task "${task.name}"`);
855
858
  }
856
859
  }
857
860
  /**
@@ -862,9 +865,9 @@ function taskData(task, resolver, taskId, stageId, ids) {
862
865
  * and empty `inputs`/`outputs`/`bindings`. A *rule* always carries the context —
863
866
  * `validate` rejects a bare `uipath` ("connector activity missing").
864
867
  */
865
- function waitConnectorBag(spec, opts, resolver, elementId) {
868
+ function waitConnectorBag(spec, opts, resolver, elementId, where) {
866
869
  if (isResolvedSubscription(spec))
867
- return resolvedSubscriptionBag('event', spec, resolver, elementId);
870
+ return resolvedSubscriptionBag('event', spec, resolver, elementId, where);
868
871
  const bag = { serviceType: CASE_FORMAT_PROFILE.waitConnectorServiceType };
869
872
  const named = spec?.connectorKey !== undefined || spec?.operation !== undefined;
870
873
  if (opts.alwaysContext || named) {
@@ -883,19 +886,20 @@ function isResolvedSubscription(spec) {
883
886
  }
884
887
  /** Resolve a Case event/wait subscription into the designer/runtime bag pinned
885
888
  * by the archived v1 artifacts. Placeholder bags never enter this path. */
886
- function resolvedSubscriptionBag(kind, spec, resolver, elementId) {
889
+ function resolvedSubscriptionBag(kind, spec, resolver, elementId, where) {
887
890
  if (!resolver.library)
888
891
  throw new Error(`${kind === 'trigger' ? 'event trigger' : 'wait-for-connector'} needs the connector library — `
889
892
  + `pass { library } to serialize or use uip maestro case compile --library.`);
890
893
  const rc = resolveCaseSubscription(resolver.library, kind, spec);
891
894
  const op = rc.entry.operation ?? {};
892
895
  const context = [io('connectorKey', 'string', { value: rc.connectorKey })];
893
- const connection = bindingRef(resolver, spec.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
896
+ const target = { key: spec.connector, action: spec.event };
897
+ const connection = bindingRef(resolver, spec.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`, { attribute: 'connection', where, target });
894
898
  if (connection) {
895
899
  context.push(io('connection', 'string', { value: `=bindings.${connection.id}` }));
896
900
  context.push(io('resourceKey', 'string', { value: connection.resourceKey }));
897
901
  }
898
- const folder = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey');
902
+ const folder = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey', { attribute: 'folder', where, target });
899
903
  if (folder)
900
904
  context.push(io('folderKey', 'string', { value: `=bindings.${folder.id}` }));
901
905
  context.push(io('method', 'string', {}), io('path', 'string', {}), io('objectName', 'string', { value: op.objectName ?? spec.event }), io('operation', 'string', { value: op.eventOperation ?? spec.event }), io('metadata', 'json', { body: subscriptionMetadata(rc, op, spec.filters ?? [], elementId) }));
@@ -1076,7 +1080,7 @@ function emitTaskOutput(o, elementId, ids, seed, resolver) {
1076
1080
  * the op's output schema. Connection/folder resolve through `bindings` and are
1077
1081
  * referenced as `=bindings.<id>`, with the binding entry collected at case root.
1078
1082
  */
1079
- function connectorTaskData(spec, resolver, elementId) {
1083
+ function connectorTaskData(spec, resolver, elementId, where) {
1080
1084
  if (!resolver.library)
1081
1085
  throw new Error(`connector task needs the connector library — pass { library } to serialize or use uip maestro case compile --library.`);
1082
1086
  const rc = resolver.library.resolve(spec.key, spec.action, spec.version, spec.object);
@@ -1085,12 +1089,13 @@ function connectorTaskData(spec, resolver, elementId) {
1085
1089
  io('connectorKey', 'string', { value: rc.connectorKey }),
1086
1090
  ];
1087
1091
  // connection + folder → root bindings, referenced by id.
1088
- const connRef = bindingRef(resolver, spec.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
1092
+ const target = { key: spec.key, action: spec.action };
1093
+ const connRef = bindingRef(resolver, spec.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`, { attribute: 'connection', where, target });
1089
1094
  if (connRef) {
1090
1095
  context.push(io('connection', 'string', { value: `=bindings.${connRef.id}` }));
1091
1096
  context.push(io('resourceKey', 'string', { value: connRef.resourceKey }));
1092
1097
  }
1093
- const folderRef = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey');
1098
+ const folderRef = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey', { attribute: 'folder', where, target });
1094
1099
  if (folderRef)
1095
1100
  context.push(io('folderKey', 'string', { value: `=bindings.${folderRef.id}` }));
1096
1101
  context.push(io('objectName', 'string', { value: op.objectName ?? spec.action }), io('method', 'string', { value: transportHttpMethod(rc.httpMethod) }), io('path', 'string', { value: rc.endpoint }), io('metadata', 'json', { body: connectorMetadata(rc, op) }));
@@ -1127,20 +1132,18 @@ function connectorTaskData(spec, resolver, elementId) {
1127
1132
  const outputs = connectorOutputs(rc, elementId, resolver);
1128
1133
  return { serviceType: CASE_FORMAT_PROFILE.connectorActivityServiceType, context, inputs, outputs, bindings: [] };
1129
1134
  }
1130
- function externalTaskData(kind, spec, resolver) {
1135
+ function externalTaskData(kind, spec, resolver, where) {
1131
1136
  if (!resolver.library)
1132
1137
  throw new Error(`${kind} task needs the connector library — pass { library } to serialize or use uip maestro case compile --library.`);
1133
1138
  const rc = resolver.library.resolve(spec.connector.key, spec.connector.action, spec.connector.version, spec.connector.object);
1134
- const op = rc.entry.operation ?? {};
1135
- const expectedSubType = kind === 'external-agent' ? 'AgentExecution' : 'ProcessExecution';
1136
- if (op.subType !== expectedSubType)
1137
- throw new Error(`${kind} task requires a ${expectedSubType} descriptor; resolved ${String(op.subType ?? 'no subtype')}.`);
1138
- if (!rc.activityTypeId)
1139
- throw new Error(`${kind} task descriptor ${rc.nodeType}@${rc.version} has no uiPathActivityTypeId.`);
1139
+ const refused = externalDescriptorRefusal(kind, rc);
1140
+ if (refused)
1141
+ throw new Error(refused);
1140
1142
  if (spec.mode !== 'sync' && spec.mode !== 'async')
1141
1143
  throw new Error(`${kind} task mode must be "sync" or "async".`);
1142
- const connection = bindingRef(resolver, spec.connector.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
1143
- const folder = bindingRef(resolver, spec.connector.folder, 'Connection', 'folderKey', 'FolderKey');
1144
+ const target = { key: spec.connector.key, action: spec.connector.action };
1145
+ const connection = bindingRef(resolver, spec.connector.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`, { attribute: 'connection', where, target });
1146
+ const folder = bindingRef(resolver, spec.connector.folder, 'Connection', 'folderKey', 'FolderKey', { attribute: 'folder', where, target });
1144
1147
  if (!connection || !folder)
1145
1148
  throw new Error(`${kind} task requires non-empty connection and folder binding names.`);
1146
1149
  const serviceType = kind === 'external-agent'
@@ -1150,13 +1153,18 @@ function externalTaskData(kind, spec, resolver) {
1150
1153
  : (spec.mode === 'sync'
1151
1154
  ? CASE_FORMAT_PROFILE.externalWorkflowSyncServiceType
1152
1155
  : CASE_FORMAT_PROFILE.externalWorkflowAsyncServiceType);
1156
+ // The inputs: the shared validator every connector activity runs (unknown,
1157
+ // missing required or mistyped input; a `lookup()` swapped for its recorded id,
1158
+ // or refused). Case `check` reports the same through `connectorInputs`.
1153
1159
  const fields = new Map(rc.fields.map((field) => [field.name, field]));
1154
- const unknown = Object.keys(spec.connector.inputs).find((name) => !fields.has(name));
1155
- if (unknown)
1156
- throw new Error(`${kind} task has unknown input "${unknown}" for ${rc.nodeType}@${rc.version}.`);
1157
- const missing = rc.fields.find((field) => field.required && !(field.name in spec.connector.inputs));
1158
- if (missing)
1159
- throw new Error(`${kind} task is missing required input "${missing.name}".`);
1160
+ const slots = buildConnectorInputs(rc, spec.connector.inputs, {
1161
+ dialect: EQUALS_EXPR_DIALECT,
1162
+ resolutions: resolver.library.lookupResolutions,
1163
+ lookups: resolver.library.lookupSpecs(spec.connector.key, spec.connector.action, spec.connector.version, spec.connector.object),
1164
+ });
1165
+ const resolved = {
1166
+ ...slots.bodyParameters, ...slots.pathParameters, ...slots.queryParameters,
1167
+ };
1160
1168
  const responseVar = connectorOutputVar(resolver, 'response');
1161
1169
  return {
1162
1170
  serviceType,
@@ -1172,7 +1180,7 @@ function externalTaskData(kind, spec, resolver) {
1172
1180
  id: name,
1173
1181
  name,
1174
1182
  type: jsonType(fields.get(name)?.type),
1175
- value,
1183
+ value: name in resolved ? resolved[name] : value,
1176
1184
  target: name,
1177
1185
  })),
1178
1186
  outputs: [{
@@ -1196,9 +1204,17 @@ function ioSlot(name, bodyObj, elementId) {
1196
1204
  }
1197
1205
  /** Resolve a symbolic connection/folder to a root binding entry (collected once),
1198
1206
  * returning its id + resourceKey for the `=bindings.<id>` reference. */
1199
- function bindingRef(resolver, name, resource, propertyAttribute, displayName) {
1207
+ function bindingRef(resolver, name, resource, propertyAttribute, displayName, site) {
1200
1208
  if (!name)
1201
1209
  return undefined;
1210
+ // A label compile cannot turn into a resource key: one bindings.json answers
1211
+ // with the label itself, or a populated one does not declare it. Falling back
1212
+ // to the label emitted it as the binding's resourceKey, and the run faulted
1213
+ // with "'Connection' has an invalid GUID value" (#1034). Same refusal, same
1214
+ // text, as Flow compile and every family's check (`bindingLabelRefusal`).
1215
+ const refused = bindingLabelRefusal(resolver.bindings, name, site.attribute, site.where, site.target);
1216
+ if (refused)
1217
+ throw refused;
1202
1218
  const entry = resolver.bindings?.entry(name);
1203
1219
  const id = entry?.id ?? name;
1204
1220
  const resourceKey = entry?.resourceKey ?? entry?.default ?? name;
@@ -1321,10 +1337,11 @@ function toCamel(s) {
1321
1337
  return s.replace(/_([a-z0-9])/gi, (_m, c) => String(c).toUpperCase());
1322
1338
  }
1323
1339
  // ─── Conditions & rules ───────────────────────────────────────────────────────
1324
- function emitRules(rules, seed, ownerId, ids, resolver) {
1325
- return rules.map((group, gi) => group.map((r, ri) => emitRule(r, `${seed}:${gi}:${ri}`, ownerId, ids, resolver)));
1340
+ /** `site` names the condition as Case `check` does (`stage "S" entry`), for refusals. */
1341
+ function emitRules(rules, seed, ownerId, ids, resolver, site) {
1342
+ return rules.map((group, gi) => group.map((r, ri) => emitRule(r, `${seed}:${gi}:${ri}`, ownerId, ids, resolver, site)));
1326
1343
  }
1327
- function emitRule(r, seed, ownerId, ids, resolver) {
1344
+ function emitRule(r, seed, ownerId, ids, resolver, site) {
1328
1345
  const ruleId = ids.ruleId(seed);
1329
1346
  const out = { rule: r.rule, id: ruleId };
1330
1347
  if (r.rule === 'selected-stage-completed' || r.rule === 'selected-stage-exited') {
@@ -1344,7 +1361,7 @@ function emitRule(r, seed, ownerId, ids, resolver) {
1344
1361
  }
1345
1362
  if (r.rule === 'wait-for-connector') {
1346
1363
  // A rule always carries the subscription under `uipath` (placeholder-safe).
1347
- out[CASE_FORMAT_PROFILE.waitConnectorRuleBagField] = waitConnectorBag(r.connector, { alwaysContext: true }, resolver, `${ownerId}-${ruleId}`);
1364
+ out[CASE_FORMAT_PROFILE.waitConnectorRuleBagField] = waitConnectorBag(r.connector, { alwaysContext: true }, resolver, `${ownerId}-${ruleId}`, `wait-for-connector rule in ${site}`);
1348
1365
  }
1349
1366
  if (r.rule === 'sla-status-change') {
1350
1367
  // Resolve the SLA (and, for at-risk, the escalation) by displayName → the
@@ -1368,7 +1385,7 @@ function emitEntry(c, seed, ownerId, label, ids, resolver) {
1368
1385
  const out = {
1369
1386
  id: ids.conditionId(seed),
1370
1387
  displayName: resolver.conditionName(c.displayName, `${label} entry`),
1371
- rules: emitRules(c.rules, seed, ownerId, ids, resolver),
1388
+ rules: emitRules(c.rules, seed, ownerId, ids, resolver, `stage "${label}" entry`),
1372
1389
  };
1373
1390
  if (c.isInterrupting !== undefined)
1374
1391
  out.isInterrupting = c.isInterrupting;
@@ -1378,7 +1395,7 @@ function emitExit(c, seed, ownerId, label, ids, resolver) {
1378
1395
  const out = {
1379
1396
  id: ids.conditionId(seed),
1380
1397
  displayName: resolver.conditionName(c.displayName, `${label} ${c.marksStageComplete ? 'complete' : 'exit'}`),
1381
- rules: emitRules(c.rules, seed, ownerId, ids, resolver),
1398
+ rules: emitRules(c.rules, seed, ownerId, ids, resolver, `stage "${label}" exit`),
1382
1399
  };
1383
1400
  if (c.marksStageComplete !== undefined)
1384
1401
  out.marksStageComplete = c.marksStageComplete;
@@ -1396,7 +1413,7 @@ function emitTaskEntry(c, seed, ownerId, label, ids, resolver) {
1396
1413
  return {
1397
1414
  id: ids.conditionId(seed),
1398
1415
  displayName: resolver.conditionName(c.displayName, `${label} entry`),
1399
- rules: emitRules(c.rules, seed, ownerId, ids, resolver),
1416
+ rules: emitRules(c.rules, seed, ownerId, ids, resolver, `task "${label}" entry`),
1400
1417
  };
1401
1418
  }
1402
1419
  function emitCaseExit(c, i, ids, resolver) {
@@ -1404,7 +1421,7 @@ function emitCaseExit(c, i, ids, resolver) {
1404
1421
  const out = {
1405
1422
  id: ids.conditionId(seed),
1406
1423
  displayName: resolver.conditionName(c.displayName, 'Case complete'),
1407
- rules: emitRules(c.rules, seed, 'root', ids, resolver),
1424
+ rules: emitRules(c.rules, seed, 'root', ids, resolver, 'case exit'),
1408
1425
  };
1409
1426
  if (c.marksCaseComplete !== undefined)
1410
1427
  out.marksCaseComplete = c.marksCaseComplete;
@@ -26,6 +26,7 @@ export function registerCaseCommands(program) {
26
26
  // check-cli sees argv.
27
27
  .option('--library <dir>', 'connector library directory')
28
28
  .option('--connectors-local <dir>', 'connection-resolved connector overlay')
29
+ .option('--bindings <file>', 'bindings.json, for the connection/folder label rules')
29
30
  .addOption(retiredCompiledOption())
30
31
  .action(async (input, options) => {
31
32
  let mode;
@@ -43,6 +44,7 @@ export function registerCaseCommands(program) {
43
44
  const argv = [input];
44
45
  addValue(argv, '--library', options.library);
45
46
  addValue(argv, '--connectors-local', options.connectorsLocal);
47
+ addValue(argv, '--bindings', options.bindings);
46
48
  await invoke('case check', () => loadTool(import.meta.url, '../../case/check-cli.js'), argv);
47
49
  });
48
50
  family.command('decompile')
@@ -43,6 +43,27 @@ export declare function bindingSelfNameMessage(where: string, name: string, attr
43
43
  * with nothing real to bind is meant to take.
44
44
  */
45
45
  export declare function undeclaredBinding(bindings: BindingsView | undefined, name: string): string[] | undefined;
46
+ /**
47
+ * The refusal for one connection/folder label `compile` cannot turn into a
48
+ * resource key, or `undefined` when the label is acceptable.
49
+ *
50
+ * - `BINDING_SELF_NAME`: bindings.json declares the label and answers with the
51
+ * label itself.
52
+ * - `BINDING_UNDECLARED`: a POPULATED bindings.json does not declare it, and it
53
+ * is not a GUID ({@link undeclaredBinding}).
54
+ *
55
+ * Silent without a bindings.json, or with an empty one: offline authoring. One
56
+ * function for every family's `serialize` (which throws the result) and the
57
+ * shared `check` rule (which reports it), so all print the same sentence.
58
+ *
59
+ * @param bindings - The project's bindings.json, if any.
60
+ * @param name - The authored label.
61
+ * @param attribute - Which half of the pair it is.
62
+ * @param where - The site, prefixed to the message.
63
+ * @param target - The connector and operation, for the `prepare` remedy.
64
+ * @returns The refusal, or `undefined`.
65
+ */
66
+ export declare function bindingLabelRefusal(bindings: BindingsView | undefined, name: string, attribute: 'connection' | 'folder', where: string, target?: PrepareTarget): BindingRefusal | undefined;
46
67
  /**
47
68
  * The connector and operation a label belongs to, when the call site knows them:
48
69
  * a connector step's `key` + `action`, an event's `connector` + `event`. With it
@@ -58,6 +58,39 @@ export function undeclaredBinding(bindings, name) {
58
58
  return undefined;
59
59
  return declared;
60
60
  }
61
+ /**
62
+ * The refusal for one connection/folder label `compile` cannot turn into a
63
+ * resource key, or `undefined` when the label is acceptable.
64
+ *
65
+ * - `BINDING_SELF_NAME`: bindings.json declares the label and answers with the
66
+ * label itself.
67
+ * - `BINDING_UNDECLARED`: a POPULATED bindings.json does not declare it, and it
68
+ * is not a GUID ({@link undeclaredBinding}).
69
+ *
70
+ * Silent without a bindings.json, or with an empty one: offline authoring. One
71
+ * function for every family's `serialize` (which throws the result) and the
72
+ * shared `check` rule (which reports it), so all print the same sentence.
73
+ *
74
+ * @param bindings - The project's bindings.json, if any.
75
+ * @param name - The authored label.
76
+ * @param attribute - Which half of the pair it is.
77
+ * @param where - The site, prefixed to the message.
78
+ * @param target - The connector and operation, for the `prepare` remedy.
79
+ * @returns The refusal, or `undefined`.
80
+ */
81
+ export function bindingLabelRefusal(bindings, name, attribute, where, target) {
82
+ if (!bindings)
83
+ return undefined;
84
+ const resolved = bindings.resolve(name);
85
+ if (resolved === name)
86
+ return new BindingRefusal('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
87
+ if (resolved === undefined) {
88
+ const declared = undeclaredBinding(bindings, name);
89
+ if (declared)
90
+ return new BindingRefusal('BINDING_UNDECLARED', bindingUndeclaredMessage(where, name, attribute, declared, target));
91
+ }
92
+ return undefined;
93
+ }
61
94
  function prepareFor(target) {
62
95
  return prepareCommand(target?.key ?? '<connector-key>', target?.action ?? '<action>');
63
96
  }
@@ -26,7 +26,7 @@ import { isLookupToken, resolvedValue, } from './lookups.js';
26
26
  import { Expr } from './expr.js';
27
27
  import { extractRefs } from './expr-check.js';
28
28
  import { prepareCommand, resolveFlagArg } from './cli-spelling.js';
29
- import { bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, missingBindingLabels, undeclaredBinding, } from './binding-messages.js';
29
+ import { bindingLabelRefusal, bindingMissingMessage, connectionConnectorConflictMessage, missingBindingLabels, } from './binding-messages.js';
30
30
  /**
31
31
  * Flow: an input is an {@link Expr} object. A non-literal one is runtime; a
32
32
  * literal one (`lit(…)`) carries the constant it was built from.
@@ -370,16 +370,9 @@ use) {
370
370
  const name = labels[attribute];
371
371
  if (typeof name !== 'string')
372
372
  continue;
373
- const resolved = bindings.resolve(name);
374
- if (resolved === name) {
375
- report('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
376
- }
377
- else if (resolved === undefined) {
378
- const declared = undeclaredBinding(bindings, name);
379
- if (declared) {
380
- report('BINDING_UNDECLARED', bindingUndeclaredMessage(where, name, attribute, declared, target));
381
- }
382
- }
373
+ const refused = bindingLabelRefusal(bindings, name, attribute, where, target);
374
+ if (refused)
375
+ report(refused.code, refused.message);
383
376
  }
384
377
  }
385
378
  /**
package/dist/serialize.js CHANGED
@@ -18,7 +18,7 @@ import { planContributionBindings } from './core/contribution-bindings.js';
18
18
  import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
19
19
  import { isAnswerField } from './core/hitl-answers.js';
20
20
  import { connectionConnectors } from './bindings-v2.js';
21
- import { BindingRefusal, GUID_RE, bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, missingBindingLabels, undeclaredBinding } from './core/binding-messages.js';
21
+ import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
22
22
  import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
23
23
  import { genericEventObjectRefusal } from './core/event-checks.js';
24
24
  import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
@@ -3886,16 +3886,10 @@ function requireBindingPair(where, labels, target) {
3886
3886
  function resolveBinding(opts, name, attribute, where, target) {
3887
3887
  if (name === undefined)
3888
3888
  return undefined;
3889
+ const refused = bindingLabelRefusal(opts.bindings, name, attribute, where, target);
3890
+ if (refused)
3891
+ throw refused;
3889
3892
  const resolved = opts.bindings?.resolve(name);
3890
- if (resolved === name) {
3891
- throw new BindingRefusal('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
3892
- }
3893
- if (resolved === undefined) {
3894
- const declared = undeclaredBinding(opts.bindings, name);
3895
- if (declared) {
3896
- throw new BindingRefusal('BINDING_UNDECLARED', bindingUndeclaredMessage(where, name, attribute, declared, target));
3897
- }
3898
- }
3899
3893
  const value = resolved ?? name;
3900
3894
  const reason = connectionStubReason(value);
3901
3895
  if (reason !== undefined && opts.warn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.18.4",
3
+ "version": "6.18.6",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "35ff6e86b7aecb0b5c76663b6650a0924a8b7f6e"
95
+ "gitref": "27768f00ecc6986a54deba3c8f327d01ace642f8"
96
96
  }