@uipath/maestro-builder-sdk 6.18.5 → 6.18.7

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}`);
@@ -10,6 +10,7 @@
10
10
  import type { BuiltCase } from './case-sdk.js';
11
11
  import { type ConnectorCheckOpts } from '../core/connector-checks.js';
12
12
  import type { EventSubscription } from '../core/actions.js';
13
+ import { type EventCheckOpts } from '../core/event-checks.js';
13
14
  export interface Diagnostic {
14
15
  level: 'error' | 'warning';
15
16
  code: string;
@@ -25,13 +26,14 @@ export interface Diagnostic {
25
26
  * `compile` actually makes. Case `serialize` resolves the connector operation and
26
27
  * the event, and validates a connector task's inputs and resolves its lookups
27
28
  * 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.
29
+ * `resolutions`), and refuses a connection/folder label bindings.json cannot
30
+ * turn into a resource key (`bindings`, `bindingLabelRefusal`), and refuses an
31
+ * event on an unverified BYOA connection (`eventFacts`, `byoa`).
30
32
  *
31
33
  * Every hook fails open: without a library, `check` stays a pure function of the
32
34
  * built model.
33
35
  */
34
- export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | 'connectorInputs' | 'lookups' | 'resolutions' | 'schemaReadiness'> & {
36
+ export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | 'connectorInputs' | 'lookups' | 'resolutions' | 'schemaReadiness' | 'bindings'> & Pick<EventCheckOpts, 'eventFacts' | 'byoa'> & {
35
37
  /**
36
38
  * What Case `compile` would refuse an event subscription with, or `undefined`
37
39
  * when it resolves: `EVENT_UNKNOWN` (the event is not in the library, or
@@ -2,7 +2,7 @@ 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
7
  import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT } from './external.js';
8
8
  const TYPE_NAMES = new Set(Object.values(types));
@@ -114,6 +114,11 @@ export function check(built, opts = {}) {
114
114
  message: `Stage "${stage.label}" selectNextStage.objectName must name a Data Fabric entity.`,
115
115
  where: stage.label,
116
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 });
117
122
  }
118
123
  for (const lane of stage.lanes)
119
124
  for (const t of lane) {
@@ -293,9 +298,17 @@ function checkLibraryConnectors(built, opts, diags) {
293
298
  const refused = opts.subscriptionError?.(kind, sub);
294
299
  if (refused !== undefined)
295
300
  diags.push({ level: 'error', code: refused.code, message: `${where}: ${refused.message}`, where: at });
296
- // A `where` lookup nobody recorded: Flow's LOOKUP_UNRESOLVED warning, from the
297
- // shared event rules. Only `resolutions` is passed, so nothing else in them fires.
298
- 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 }));
301
+ checkBindingLabels(where, sub, opts, (code, message) => diags.push({ level: 'error', code, message, where: at }), { key: sub.connector, action: sub.event });
302
+ // From the shared event rules, the two Case compile enforces: a `where` lookup
303
+ // nobody recorded (LOOKUP_UNRESOLVED, a warning) and an unverified BYOA
304
+ // connection (EVENT_BYOA_UNVERIFIED). Only their hooks are passed, so nothing
305
+ // else in them fires.
306
+ checkEventResolution(sub, where, kind, {
307
+ resolutions: opts.resolutions ?? {},
308
+ ...(opts.eventFacts ? { eventFacts: opts.eventFacts } : {}),
309
+ ...(opts.byoa ? { byoa: opts.byoa } : {}),
310
+ ...(opts.bindings ? { bindings: opts.bindings } : {}),
311
+ }, (code, message) => diags.push({ level: 'error', code, message, where: at }), (code, message) => diags.push({ level: 'warning', code, message, where: at }));
299
312
  };
300
313
  const rules = (conditions, where) => {
301
314
  for (const c of conditions)
@@ -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,12 @@ 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`, and the
13
+ * BYOA facts and ledger, for the refusal `resolveCaseSubscription` makes.
12
14
  *
13
15
  * @param library - The library `openCliLibrary` returned, or `undefined`.
16
+ * @param bindings - The project's bindings.json, as compile reads it.
14
17
  * @returns The options for Case `check`.
15
18
  */
16
- export declare function caseCheckOpts(library: Library | undefined): CaseCheckOpts;
19
+ export declare function caseCheckOpts(library: Library | undefined, bindings?: BindingsView): CaseCheckOpts;
@@ -17,21 +17,26 @@ import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscrip
17
17
  * the operation and event resolution (the event through `resolveCaseSubscription`,
18
18
  * compile's own call), a connector task's field contract and lookups (the
19
19
  * `=`-expression validator Case compile runs), and the schema-readiness warnings.
20
- * The binding-label and BYOA hooks of the shared bag are left out, because Case
21
- * `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`, and the
22
+ * BYOA facts and ledger, for the refusal `resolveCaseSubscription` makes.
22
23
  *
23
24
  * @param library - The library `openCliLibrary` returned, or `undefined`.
25
+ * @param bindings - The project's bindings.json, as compile reads it.
24
26
  * @returns The options for Case `check`.
25
27
  */
26
- export function caseCheckOpts(library) {
28
+ export function caseCheckOpts(library, bindings) {
27
29
  if (!library)
28
- return {};
29
- const shared = connectorCheckOpts(library);
30
+ return bindings ? { bindings } : {};
31
+ const shared = connectorCheckOpts(library, bindings);
30
32
  return {
33
+ ...(bindings ? { bindings } : {}),
31
34
  connectorResolveError: shared.connectorResolveError,
32
35
  connectorInputs: shared.connectorInputs,
33
36
  lookups: shared.lookups,
34
37
  resolutions: shared.resolutions,
38
+ eventFacts: shared.eventFacts,
39
+ byoa: shared.byoa,
35
40
  schemaReadiness: shared.schemaReadiness,
36
41
  externalDescriptorError: (kind, d) => {
37
42
  let rc;
@@ -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 });
@@ -11,6 +11,7 @@
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';
16
17
  import { externalDescriptorRefusal } from './external.js';
@@ -177,12 +178,13 @@ export function serialize(built, opts = {}) {
177
178
  const context = [];
178
179
  if (connector) {
179
180
  context.push(io('connectorKey', 'string', { value: connector.connectorKey }));
180
- 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' });
181
183
  if (connection) {
182
184
  context.push(io('connection', 'string', { value: `=bindings.${connection.id}` }));
183
185
  context.push(io('resourceKey', 'string', { value: connection.resourceKey }));
184
186
  }
185
- const folder = bindingRef(resolver, connector.folder, 'Connection', 'folderKey', 'FolderKey');
187
+ const folder = bindingRef(resolver, connector.folder, 'Connection', 'folderKey', 'FolderKey', { ...site, attribute: 'folder' });
186
188
  if (folder)
187
189
  context.push(io('folderKey', 'string', { value: `=bindings.${folder.id}` }));
188
190
  }
@@ -697,7 +699,7 @@ function emitTriggers(triggers, ids, resolver) {
697
699
  // placeholder. A subscription resolves through the same library/binding
698
700
  // machinery as connector tasks and wait-for-connector bags.
699
701
  const bag = t.eventSubscription
700
- ? resolvedSubscriptionBag('trigger', t.eventSubscription, resolver, triggerId)
702
+ ? resolvedSubscriptionBag('trigger', t.eventSubscription, resolver, triggerId, `Event trigger "${label}"`)
701
703
  : { serviceType: CASE_FORMAT_PROFILE.eventServiceType };
702
704
  if (t.eventOutputs?.length) {
703
705
  const outputs = t.eventOutputs.map((o) => {
@@ -836,10 +838,10 @@ function taskData(task, resolver, taskId, stageId, ids) {
836
838
  return data;
837
839
  }
838
840
  case 'connector':
839
- return connectorTaskData(task.connector, resolver, taskId);
841
+ return connectorTaskData(task.connector, resolver, taskId, `Connector task "${task.name}"`);
840
842
  case 'external-agent':
841
843
  case 'external-workflow':
842
- 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}"`);
843
845
  case 'wait-for-timer': {
844
846
  if (task.timer?.duration)
845
847
  return { timerType: 'timeDuration', timeDuration: task.timer.duration };
@@ -852,7 +854,7 @@ function taskData(task, resolver, taskId, stageId, ids) {
852
854
  case 'wait-for-connector':
853
855
  // Floor placeholder (no connector named) = `serviceType` only; naming the
854
856
  // connector/operation emits the `context` subscription.
855
- 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}"`);
856
858
  }
857
859
  }
858
860
  /**
@@ -863,9 +865,9 @@ function taskData(task, resolver, taskId, stageId, ids) {
863
865
  * and empty `inputs`/`outputs`/`bindings`. A *rule* always carries the context —
864
866
  * `validate` rejects a bare `uipath` ("connector activity missing").
865
867
  */
866
- function waitConnectorBag(spec, opts, resolver, elementId) {
868
+ function waitConnectorBag(spec, opts, resolver, elementId, where) {
867
869
  if (isResolvedSubscription(spec))
868
- return resolvedSubscriptionBag('event', spec, resolver, elementId);
870
+ return resolvedSubscriptionBag('event', spec, resolver, elementId, where);
869
871
  const bag = { serviceType: CASE_FORMAT_PROFILE.waitConnectorServiceType };
870
872
  const named = spec?.connectorKey !== undefined || spec?.operation !== undefined;
871
873
  if (opts.alwaysContext || named) {
@@ -884,19 +886,20 @@ function isResolvedSubscription(spec) {
884
886
  }
885
887
  /** Resolve a Case event/wait subscription into the designer/runtime bag pinned
886
888
  * by the archived v1 artifacts. Placeholder bags never enter this path. */
887
- function resolvedSubscriptionBag(kind, spec, resolver, elementId) {
889
+ function resolvedSubscriptionBag(kind, spec, resolver, elementId, where) {
888
890
  if (!resolver.library)
889
891
  throw new Error(`${kind === 'trigger' ? 'event trigger' : 'wait-for-connector'} needs the connector library — `
890
892
  + `pass { library } to serialize or use uip maestro case compile --library.`);
891
- const rc = resolveCaseSubscription(resolver.library, kind, spec);
893
+ const rc = resolveCaseSubscription(resolver.library, kind, spec, { where, bindings: resolver.bindings });
892
894
  const op = rc.entry.operation ?? {};
893
895
  const context = [io('connectorKey', 'string', { value: rc.connectorKey })];
894
- 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 });
895
898
  if (connection) {
896
899
  context.push(io('connection', 'string', { value: `=bindings.${connection.id}` }));
897
900
  context.push(io('resourceKey', 'string', { value: connection.resourceKey }));
898
901
  }
899
- const folder = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey');
902
+ const folder = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey', { attribute: 'folder', where, target });
900
903
  if (folder)
901
904
  context.push(io('folderKey', 'string', { value: `=bindings.${folder.id}` }));
902
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) }));
@@ -1077,7 +1080,7 @@ function emitTaskOutput(o, elementId, ids, seed, resolver) {
1077
1080
  * the op's output schema. Connection/folder resolve through `bindings` and are
1078
1081
  * referenced as `=bindings.<id>`, with the binding entry collected at case root.
1079
1082
  */
1080
- function connectorTaskData(spec, resolver, elementId) {
1083
+ function connectorTaskData(spec, resolver, elementId, where) {
1081
1084
  if (!resolver.library)
1082
1085
  throw new Error(`connector task needs the connector library — pass { library } to serialize or use uip maestro case compile --library.`);
1083
1086
  const rc = resolver.library.resolve(spec.key, spec.action, spec.version, spec.object);
@@ -1086,12 +1089,13 @@ function connectorTaskData(spec, resolver, elementId) {
1086
1089
  io('connectorKey', 'string', { value: rc.connectorKey }),
1087
1090
  ];
1088
1091
  // connection + folder → root bindings, referenced by id.
1089
- 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 });
1090
1094
  if (connRef) {
1091
1095
  context.push(io('connection', 'string', { value: `=bindings.${connRef.id}` }));
1092
1096
  context.push(io('resourceKey', 'string', { value: connRef.resourceKey }));
1093
1097
  }
1094
- const folderRef = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey');
1098
+ const folderRef = bindingRef(resolver, spec.folder, 'Connection', 'folderKey', 'FolderKey', { attribute: 'folder', where, target });
1095
1099
  if (folderRef)
1096
1100
  context.push(io('folderKey', 'string', { value: `=bindings.${folderRef.id}` }));
1097
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) }));
@@ -1128,7 +1132,7 @@ function connectorTaskData(spec, resolver, elementId) {
1128
1132
  const outputs = connectorOutputs(rc, elementId, resolver);
1129
1133
  return { serviceType: CASE_FORMAT_PROFILE.connectorActivityServiceType, context, inputs, outputs, bindings: [] };
1130
1134
  }
1131
- function externalTaskData(kind, spec, resolver) {
1135
+ function externalTaskData(kind, spec, resolver, where) {
1132
1136
  if (!resolver.library)
1133
1137
  throw new Error(`${kind} task needs the connector library — pass { library } to serialize or use uip maestro case compile --library.`);
1134
1138
  const rc = resolver.library.resolve(spec.connector.key, spec.connector.action, spec.connector.version, spec.connector.object);
@@ -1137,8 +1141,9 @@ function externalTaskData(kind, spec, resolver) {
1137
1141
  throw new Error(refused);
1138
1142
  if (spec.mode !== 'sync' && spec.mode !== 'async')
1139
1143
  throw new Error(`${kind} task mode must be "sync" or "async".`);
1140
- const connection = bindingRef(resolver, spec.connector.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
1141
- 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 });
1142
1147
  if (!connection || !folder)
1143
1148
  throw new Error(`${kind} task requires non-empty connection and folder binding names.`);
1144
1149
  const serviceType = kind === 'external-agent'
@@ -1199,9 +1204,17 @@ function ioSlot(name, bodyObj, elementId) {
1199
1204
  }
1200
1205
  /** Resolve a symbolic connection/folder to a root binding entry (collected once),
1201
1206
  * returning its id + resourceKey for the `=bindings.<id>` reference. */
1202
- function bindingRef(resolver, name, resource, propertyAttribute, displayName) {
1207
+ function bindingRef(resolver, name, resource, propertyAttribute, displayName, site) {
1203
1208
  if (!name)
1204
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;
1205
1218
  const entry = resolver.bindings?.entry(name);
1206
1219
  const id = entry?.id ?? name;
1207
1220
  const resourceKey = entry?.resourceKey ?? entry?.default ?? name;
@@ -1324,10 +1337,11 @@ function toCamel(s) {
1324
1337
  return s.replace(/_([a-z0-9])/gi, (_m, c) => String(c).toUpperCase());
1325
1338
  }
1326
1339
  // ─── Conditions & rules ───────────────────────────────────────────────────────
1327
- function emitRules(rules, seed, ownerId, ids, resolver) {
1328
- 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)));
1329
1343
  }
1330
- function emitRule(r, seed, ownerId, ids, resolver) {
1344
+ function emitRule(r, seed, ownerId, ids, resolver, site) {
1331
1345
  const ruleId = ids.ruleId(seed);
1332
1346
  const out = { rule: r.rule, id: ruleId };
1333
1347
  if (r.rule === 'selected-stage-completed' || r.rule === 'selected-stage-exited') {
@@ -1347,7 +1361,7 @@ function emitRule(r, seed, ownerId, ids, resolver) {
1347
1361
  }
1348
1362
  if (r.rule === 'wait-for-connector') {
1349
1363
  // A rule always carries the subscription under `uipath` (placeholder-safe).
1350
- 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}`);
1351
1365
  }
1352
1366
  if (r.rule === 'sla-status-change') {
1353
1367
  // Resolve the SLA (and, for at-risk, the escalation) by displayName → the
@@ -1371,7 +1385,7 @@ function emitEntry(c, seed, ownerId, label, ids, resolver) {
1371
1385
  const out = {
1372
1386
  id: ids.conditionId(seed),
1373
1387
  displayName: resolver.conditionName(c.displayName, `${label} entry`),
1374
- rules: emitRules(c.rules, seed, ownerId, ids, resolver),
1388
+ rules: emitRules(c.rules, seed, ownerId, ids, resolver, `stage "${label}" entry`),
1375
1389
  };
1376
1390
  if (c.isInterrupting !== undefined)
1377
1391
  out.isInterrupting = c.isInterrupting;
@@ -1381,7 +1395,7 @@ function emitExit(c, seed, ownerId, label, ids, resolver) {
1381
1395
  const out = {
1382
1396
  id: ids.conditionId(seed),
1383
1397
  displayName: resolver.conditionName(c.displayName, `${label} ${c.marksStageComplete ? 'complete' : 'exit'}`),
1384
- rules: emitRules(c.rules, seed, ownerId, ids, resolver),
1398
+ rules: emitRules(c.rules, seed, ownerId, ids, resolver, `stage "${label}" exit`),
1385
1399
  };
1386
1400
  if (c.marksStageComplete !== undefined)
1387
1401
  out.marksStageComplete = c.marksStageComplete;
@@ -1399,7 +1413,7 @@ function emitTaskEntry(c, seed, ownerId, label, ids, resolver) {
1399
1413
  return {
1400
1414
  id: ids.conditionId(seed),
1401
1415
  displayName: resolver.conditionName(c.displayName, `${label} entry`),
1402
- rules: emitRules(c.rules, seed, ownerId, ids, resolver),
1416
+ rules: emitRules(c.rules, seed, ownerId, ids, resolver, `task "${label}" entry`),
1403
1417
  };
1404
1418
  }
1405
1419
  function emitCaseExit(c, i, ids, resolver) {
@@ -1407,7 +1421,7 @@ function emitCaseExit(c, i, ids, resolver) {
1407
1421
  const out = {
1408
1422
  id: ids.conditionId(seed),
1409
1423
  displayName: resolver.conditionName(c.displayName, 'Case complete'),
1410
- rules: emitRules(c.rules, seed, 'root', ids, resolver),
1424
+ rules: emitRules(c.rules, seed, 'root', ids, resolver, 'case exit'),
1411
1425
  };
1412
1426
  if (c.marksCaseComplete !== undefined)
1413
1427
  out.marksCaseComplete = c.marksCaseComplete;
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { EventSubscription } from '../core/actions.js';
12
12
  import type { Library, ResolvedConnector } from '../core/library.js';
13
+ import type { BindingsView } from '../core/binding-messages.js';
13
14
  /** Check's code for {@link GenericEventNoObjectError}; Flow and BPMN `check` use the same one. */
14
15
  export declare const EVENT_GENERIC_NO_OBJECT = "EVENT_GENERIC_NO_OBJECT";
15
16
  /** A GENERIC event subscription that names no object. */
@@ -24,7 +25,13 @@ export declare class GenericEventNoObjectError extends Error {
24
25
  * @param library - The connector library.
25
26
  * @param kind - `trigger` for an event trigger, `event` for a wait-for-connector task or rule.
26
27
  * @param sub - The authored subscription.
28
+ * @param byoa - Where the subscription is and the bindings, to refuse an
29
+ * unverified BYOA connection; compile passes it, `check` does not (it reports
30
+ * that rule from the shared event rules).
27
31
  * @returns The resolved event.
28
- * @throws What `resolveEvent` throws, or {@link GenericEventNoObjectError}.
32
+ * @throws What `resolveEvent` throws, {@link GenericEventNoObjectError}, or a `ByoaRefusal`.
29
33
  */
30
- export declare function resolveCaseSubscription(library: Library, kind: 'trigger' | 'event', sub: EventSubscription): ResolvedConnector;
34
+ export declare function resolveCaseSubscription(library: Library, kind: 'trigger' | 'event', sub: EventSubscription, byoa?: {
35
+ where: string;
36
+ bindings?: BindingsView;
37
+ }): ResolvedConnector;
@@ -1,4 +1,5 @@
1
1
  import { genericEventObjectRefusal } from '../core/event-checks.js';
2
+ import { ByoaRefusal, byoaUnverified } from '../core/byoa.js';
2
3
  /** Check's code for {@link GenericEventNoObjectError}; Flow and BPMN `check` use the same one. */
3
4
  export const EVENT_GENERIC_NO_OBJECT = 'EVENT_GENERIC_NO_OBJECT';
4
5
  /** A GENERIC event subscription that names no object. */
@@ -17,13 +18,25 @@ function caseObjectOption(object) {
17
18
  * @param library - The connector library.
18
19
  * @param kind - `trigger` for an event trigger, `event` for a wait-for-connector task or rule.
19
20
  * @param sub - The authored subscription.
21
+ * @param byoa - Where the subscription is and the bindings, to refuse an
22
+ * unverified BYOA connection; compile passes it, `check` does not (it reports
23
+ * that rule from the shared event rules).
20
24
  * @returns The resolved event.
21
- * @throws What `resolveEvent` throws, or {@link GenericEventNoObjectError}.
25
+ * @throws What `resolveEvent` throws, {@link GenericEventNoObjectError}, or a `ByoaRefusal`.
22
26
  */
23
- export function resolveCaseSubscription(library, kind, sub) {
27
+ export function resolveCaseSubscription(library, kind, sub, byoa) {
24
28
  const rc = library.resolveEvent(kind, sub.connector, sub.event, sub.version, sub.object);
25
29
  const noObject = genericEventObjectRefusal(rc, sub, caseObjectOption);
26
30
  if (noObject)
27
31
  throw new GenericEventNoObjectError(noObject);
32
+ // A BYOA-only event on a connection `prepare` never checked deploys and never
33
+ // fires (#848). Flow's `refuseUnverifiedByoa` rule and text (`byoaUnverified`);
34
+ // Case `check` reports it from the shared event rules (`eventFacts` / `byoa`).
35
+ if (byoa && typeof library.eventFacts === 'function') {
36
+ const object = typeof sub.object === 'string' && sub.object !== '' ? sub.object : undefined;
37
+ const message = byoaUnverified(byoa.where, { connector: sub.connector, event: sub.event, ...(object ? { object } : {}), connection: sub.connection }, library.eventFacts(sub.connector, sub.event, object), library.byoaVerifications, byoa.bindings);
38
+ if (message)
39
+ throw new ByoaRefusal(message);
40
+ }
28
41
  return rc;
29
42
  }
@@ -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.5",
3
+ "version": "6.18.7",
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": "714298df601bba1d5d278525ba354d670cec21aa"
95
+ "gitref": "7dffe3f9de6568ee2673c38faa5046eb7c86eb54"
96
96
  }