@uipath/maestro-builder-sdk 6.9.0 → 6.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/check.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type LookupResolutions, type LookupSpec } from './core/lookups.js';
2
2
  import type { SchemaReadiness } from './core/library.js';
3
3
  import type { BuiltFlow } from './flow-sdk.js';
4
+ import { type BindingsView } from './core/binding-messages.js';
4
5
  export interface Diagnostic {
5
6
  level: 'error' | 'warning';
6
7
  code: string;
@@ -140,12 +141,13 @@ export interface CheckOpts {
140
141
  object?: string;
141
142
  }, inputs: Record<string, unknown>) => string | undefined;
142
143
  /**
143
- * The project's `bindings.json`, when there is one — enough of it to detect an
144
- * entry that answers with its own name, which `compile` refuses (BINDING_SELF_NAME).
144
+ * The project's `bindings.json`, when there is one — enough of it to detect the
145
+ * two labels `compile` refuses: an entry that answers with its own name
146
+ * (BINDING_SELF_NAME), and a label a populated file does not declare
147
+ * (BINDING_UNDECLARED). `declared` and `entry` are optional so a caller holding
148
+ * only `resolve` still gets the first rule; the `Bindings` class has all three.
145
149
  */
146
- bindings?: {
147
- resolve(name?: string): string | undefined;
148
- };
150
+ bindings?: BindingsView;
149
151
  }
150
152
  export declare function check(built: BuiltFlow, opts?: CheckOpts): Diagnostic[];
151
153
  /**
package/dist/check.js CHANGED
@@ -10,7 +10,7 @@ import { FlowNode } from './core/node-classes.js';
10
10
  import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './core/lookups.js';
11
11
  import { EVENT_FILTER_OPERATORS, readEventFilter, eventFilterProblem } from './event-filters.js';
12
12
  import { extractRefs } from './core/expr-check.js';
13
- import { bindingSelfNameMessage } from './core/binding-messages.js';
13
+ import { bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
14
14
  import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
15
15
  import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
16
16
  import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
@@ -357,6 +357,14 @@ function checkFlow(built, ancestors, opts = {}) {
357
357
  checkTransform(s.name, s.spec.inputs, diags, typeOf, triggerId);
358
358
  if (s.spec.kind === 'http') {
359
359
  checkHttp(s.name, s.spec.inputs, diags);
360
+ // Only a complete pair: an empty half is HTTP_MANAGED_BINDING_INCOMPLETE, which
361
+ // serialize refuses before it resolves either name.
362
+ const pair = s.spec.inputs;
363
+ const filled = (v) => typeof v === 'string' && v.trim() !== '';
364
+ if (pair?.managed && filled(pair.connection) && filled(pair.folder)) {
365
+ const httpStep = s.name;
366
+ checkBindingLabels(`managed http "${httpStep}"`, pair, opts, (code, message) => diags.push({ level: 'error', code, step: httpStep, message }));
367
+ }
360
368
  const seen = new Set();
361
369
  for (const b of s.spec.inputs.branches ?? []) {
362
370
  addExpr(b.condition.js, s.name);
@@ -530,6 +538,7 @@ function checkFlow(built, ancestors, opts = {}) {
530
538
  });
531
539
  checkEventSubscription(s.spec.subscription, `waitForEvent "${stepName}"`, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
532
540
  checkEventResolution(s.spec.subscription, `waitForEvent "${stepName}"`, 'event', opts, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
541
+ checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription));
533
542
  }
534
543
  if (s.spec.kind === 'subflow') {
535
544
  // Only a real child goes in the map: a missing one is reported by
@@ -1470,14 +1479,41 @@ function checkConnectorContract(step, spec, opts, diags) {
1470
1479
  if (inputErr) {
1471
1480
  diags.push({ level: 'error', code: 'CONNECTOR_INPUT', step, message: `Connector step "${step}": ${inputErr}` });
1472
1481
  }
1473
- if (opts.bindings) {
1474
- for (const attribute of ['connection', 'folder']) {
1475
- const name = spec[attribute];
1476
- if (name !== undefined && opts.bindings.resolve(name) === name) {
1477
- diags.push({
1478
- level: 'error', code: 'BINDING_SELF_NAME', step,
1479
- message: bindingSelfNameMessage(`Connector step "${step}"`, name, attribute),
1480
- });
1482
+ checkBindingLabels(`Connector step "${step}"`, spec, opts, (code, message) => diags.push({ level: 'error', code, step, message }), { key: spec.key, action: spec.action });
1483
+ }
1484
+ /** What `registry prepare` takes for an event subscription: its connector key and curated event name. */
1485
+ function eventTarget(sub) {
1486
+ const { connector, event } = (sub ?? {});
1487
+ return typeof connector === 'string' && connector !== ''
1488
+ ? { key: connector, ...(typeof event === 'string' && event !== '' ? { action: event } : {}) }
1489
+ : undefined;
1490
+ }
1491
+ /**
1492
+ * The binding-label rules `serialize`'s `resolveBinding` enforces, for one
1493
+ * `{ connection, folder }` pair — reported at every site that resolves one
1494
+ * (connector step, event trigger and `.entryPoint` onEvent, waitForEvent, managed
1495
+ * HTTP), with the sentence compile throws. Without this at the event and HTTP
1496
+ * sites `check` printed `✓ no issues` for a source `compile` then refused.
1497
+ *
1498
+ * Silent without a bindings.json, and for a label that resolves to anything
1499
+ * other than its own name (stub values are compile's CONNECTION_STUB warning).
1500
+ */
1501
+ function checkBindingLabels(where, labels, opts, report, target) {
1502
+ const bindings = opts.bindings;
1503
+ if (!bindings || !labels)
1504
+ return;
1505
+ for (const attribute of ['connection', 'folder']) {
1506
+ const name = labels[attribute];
1507
+ if (typeof name !== 'string')
1508
+ continue;
1509
+ const resolved = bindings.resolve(name);
1510
+ if (resolved === name) {
1511
+ report('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
1512
+ }
1513
+ else if (resolved === undefined) {
1514
+ const declared = undeclaredBinding(bindings, name);
1515
+ if (declared) {
1516
+ report('BINDING_UNDECLARED', bindingUndeclaredMessage(where, name, attribute, declared, target));
1481
1517
  }
1482
1518
  }
1483
1519
  }
@@ -2278,6 +2314,7 @@ function checkTrigger(spec, diags, opts = {}) {
2278
2314
  const sub = spec.subscription;
2279
2315
  checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
2280
2316
  checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
2317
+ checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub));
2281
2318
  return;
2282
2319
  }
2283
2320
  // manual() is the explicit spelling of the default — nothing to validate.
@@ -27,6 +27,7 @@ import { Bindings } from './bindings.js';
27
27
  import { runWhenInvokedDirectly } from './cli-run.js';
28
28
  import { connectionResources, mergeBindingsV2, readBindingsV2 } from './bindings-v2.js';
29
29
  import { ensureTypeScriptRuntime } from './node-runtime.js';
30
+ import { BindingRefusal } from './core/binding-messages.js';
30
31
  function opt(argv, name) {
31
32
  const i = argv.indexOf(name);
32
33
  return i >= 0 ? argv[i + 1] : undefined;
@@ -106,11 +107,27 @@ export async function run(argv) {
106
107
  }
107
108
  // The serializer's own observations — a connection or folder value that can
108
109
  // never be a tenant resource key — printed in the same `⚠ [CODE]` shape as
109
- // check()'s, and never fatal: the .flow is still written, exit stays 0.
110
+ // check()'s. Warnings are never fatal: the .flow is still written, exit stays 0.
111
+ // A binding LABEL the serializer refuses (its own name as the resourceKey, or a
112
+ // label a populated bindings.json does not declare) is an error, not a
113
+ // warning: the check pass above reports it first, and this is the same refusal
114
+ // in the same `✗ [CODE]` shape when that pass was skipped (`--no-check`, or an
115
+ // emit-only project). Nothing is written.
110
116
  const warnings = [];
111
- const { flow: flowJson, sidecars } = serializeWithSidecars(built, {
112
- library, bindings, warn: (w) => warnings.push(w),
113
- });
117
+ let emitted;
118
+ try {
119
+ emitted = serializeWithSidecars(built, { library, bindings, warn: (w) => warnings.push(w) });
120
+ }
121
+ catch (error) {
122
+ if (!(error instanceof BindingRefusal))
123
+ throw error;
124
+ for (const w of warnings)
125
+ console.error(` ⚠ [${w.code}] ${w.message}`);
126
+ console.error(` ✗ [${error.code}] ${error.message}`);
127
+ console.error('compile: 1 error(s); not emitting.');
128
+ return 1;
129
+ }
130
+ const { flow: flowJson, sidecars } = emitted;
114
131
  for (const w of warnings)
115
132
  console.error(` ⚠ [${w.code}] ${w.message}`);
116
133
  const out = opt(argv, '-o') ?? `${built.name || basename(file).replace(/\.flow\.ts$|\.ts$/, '')}.flow`;
@@ -1 +1,60 @@
1
- export declare function bindingSelfNameMessage(where: string, name: string, attribute: 'connection' | 'folder'): string;
1
+ /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
2
+ export declare const GUID_RE: RegExp;
3
+ /** The diagnostic codes a label refusal carries, in `check` and on the error `serialize` throws. */
4
+ export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED';
5
+ /**
6
+ * What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
7
+ * message is the sentence `check` prints; the `code` lets `compile --no-check`
8
+ * print it in the same `✗ [CODE]` shape as the check pass instead of a bare line.
9
+ */
10
+ export declare class BindingRefusal extends Error {
11
+ readonly code: BindingRefusalCode;
12
+ constructor(code: BindingRefusalCode, message: string);
13
+ }
14
+ /** The part of a bindings.json the label rules read — `core/bindings.ts` `Bindings` has all three. */
15
+ export interface BindingsView {
16
+ resolve(name?: string): string | undefined;
17
+ declared?(): string[];
18
+ entry?(name?: string): unknown;
19
+ }
20
+ export declare function bindingSelfNameMessage(where: string, name: string, attribute: 'connection' | 'folder', target?: PrepareTarget): string;
21
+ /**
22
+ * The names a populated bindings.json declares when it does NOT declare `name`,
23
+ * else `undefined`.
24
+ *
25
+ * `undefined` — nothing to refuse — when no bindings.json is loaded, when it
26
+ * declares no entries (offline authoring: the symbolic name is the only honest
27
+ * placeholder there), when `name` is itself a GUID (an author may write the
28
+ * resource key directly), or when any entry answers to `name`. "Answers" is the
29
+ * union of `entry()`, `resolve()` and `declared()`, so an entry that is declared
30
+ * but carries no `resourceKey` is NOT refused here — it stays the
31
+ * `CONNECTION_STUB` warning, which is the route a simulated or validate-only task
32
+ * with nothing real to bind is meant to take.
33
+ */
34
+ export declare function undeclaredBinding(bindings: BindingsView | undefined, name: string): string[] | undefined;
35
+ /**
36
+ * The connector and operation a label belongs to, when the call site knows them:
37
+ * a connector step's `key` + `action`, an event's `connector` + `event`. With it
38
+ * the remedy is a command the author can paste; without it (managed HTTP) the
39
+ * command keeps its `<connector-key> <action>` placeholders.
40
+ */
41
+ export interface PrepareTarget {
42
+ key: string;
43
+ action?: string;
44
+ }
45
+ /**
46
+ * Why compile refuses `name`, and how to bind it.
47
+ *
48
+ * It names NO declared label as the one to use. bindings.json rows carry a label,
49
+ * a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
50
+ * they were bound for, so compile cannot tell which declared label — if any — is
51
+ * this connector's. Suggesting one sent a Data Service step to `connection:
52
+ * 'slack'` (the only `ConnectionId` row), and following that compiled the Slack
53
+ * connection id into the Data Service node with no warning at all: a real GUID of
54
+ * the wrong connector, which nothing downstream detects. The partial-prepare shape
55
+ * (ipe-drive-to-slack 09-23: `slack` bound, `drive` not yet) is exactly where
56
+ * that advice was on offer. So the message lists what is declared, says that a
57
+ * row is only reusable if it was bound for THIS connector, and gives the
58
+ * `prepare` command for this step's own connector and operation.
59
+ */
60
+ export declare function bindingUndeclaredMessage(where: string, name: string, attribute: 'connection' | 'folder', declared: readonly string[], target?: PrepareTarget): string;
@@ -1,16 +1,91 @@
1
1
  /**
2
- * The refusal for a bindings.json entry that answers with its own name — shared by
3
- * `serialize` (which throws it) and `check` (BINDING_SELF_NAME), so the author reads
4
- * the same sentence from the inner loop as from compile. Before this, `check`
5
- * printed `0 error(s)` for a source `compile` then refused (teams-decision,
6
- * 2026-09-01, turn 22 → 23).
2
+ * The refusals for a connection/folder label `compile` cannot turn into a tenant
3
+ * resource key — shared by `serialize` (which throws them) and `check`
4
+ * (BINDING_SELF_NAME, BINDING_UNDECLARED), so the author reads the same sentence
5
+ * from the inner loop as from compile. Before this, `check` printed `0 error(s)`
6
+ * for a source `compile` then refused (teams-decision, 2026-09-01, turn 22 → 23).
7
7
  */
8
8
  import { prepareCommand } from './cli-spelling.js';
9
- export function bindingSelfNameMessage(where, name, attribute) {
9
+ /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
10
+ export const GUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11
+ /**
12
+ * What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
13
+ * message is the sentence `check` prints; the `code` lets `compile --no-check`
14
+ * print it in the same `✗ [CODE]` shape as the check pass instead of a bare line.
15
+ */
16
+ export class BindingRefusal extends Error {
17
+ code;
18
+ constructor(code, message) {
19
+ super(message);
20
+ this.name = 'BindingRefusal';
21
+ this.code = code;
22
+ }
23
+ }
24
+ export function bindingSelfNameMessage(where, name, attribute, target) {
10
25
  return `${where}: binding "${name}" resolves to its own name, so the ${attribute} would compile to the `
11
26
  + `literal string "${name}" and the run would fault with "'Connection' has an invalid GUID value: `
12
- + `'${name}'". Set its "resourceKey" to the real id: \`${prepareCommand('<connector-key>', '<action>')}\` `
27
+ + `'${name}'". Set its "resourceKey" to the real id: \`${prepareFor(target)}\` `
13
28
  + `discovers the connection and writes both the connection and the folder entry into bindings.json; by hand, `
14
29
  + `read Id and FolderKey from \`uip is connections list --all-folders --output json\` (the default listing `
15
30
  + `shows only your own folder, so a connection shared from another folder is invisible without --all-folders).`;
16
31
  }
32
+ /**
33
+ * The names a populated bindings.json declares when it does NOT declare `name`,
34
+ * else `undefined`.
35
+ *
36
+ * `undefined` — nothing to refuse — when no bindings.json is loaded, when it
37
+ * declares no entries (offline authoring: the symbolic name is the only honest
38
+ * placeholder there), when `name` is itself a GUID (an author may write the
39
+ * resource key directly), or when any entry answers to `name`. "Answers" is the
40
+ * union of `entry()`, `resolve()` and `declared()`, so an entry that is declared
41
+ * but carries no `resourceKey` is NOT refused here — it stays the
42
+ * `CONNECTION_STUB` warning, which is the route a simulated or validate-only task
43
+ * with nothing real to bind is meant to take.
44
+ */
45
+ export function undeclaredBinding(bindings, name) {
46
+ if (!bindings || typeof bindings.declared !== 'function')
47
+ return undefined;
48
+ const declared = bindings.declared();
49
+ if (declared.length === 0)
50
+ return undefined;
51
+ if (GUID_RE.test(name))
52
+ return undefined;
53
+ if (declared.includes(name))
54
+ return undefined;
55
+ if (typeof bindings.entry === 'function' && bindings.entry(name) !== undefined)
56
+ return undefined;
57
+ if (bindings.resolve(name) !== undefined)
58
+ return undefined;
59
+ return declared;
60
+ }
61
+ function prepareFor(target) {
62
+ return prepareCommand(target?.key ?? '<connector-key>', target?.action ?? '<action>');
63
+ }
64
+ /**
65
+ * Why compile refuses `name`, and how to bind it.
66
+ *
67
+ * It names NO declared label as the one to use. bindings.json rows carry a label,
68
+ * a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
69
+ * they were bound for, so compile cannot tell which declared label — if any — is
70
+ * this connector's. Suggesting one sent a Data Service step to `connection:
71
+ * 'slack'` (the only `ConnectionId` row), and following that compiled the Slack
72
+ * connection id into the Data Service node with no warning at all: a real GUID of
73
+ * the wrong connector, which nothing downstream detects. The partial-prepare shape
74
+ * (ipe-drive-to-slack 09-23: `slack` bound, `drive` not yet) is exactly where
75
+ * that advice was on offer. So the message lists what is declared, says that a
76
+ * row is only reusable if it was bound for THIS connector, and gives the
77
+ * `prepare` command for this step's own connector and operation.
78
+ */
79
+ export function bindingUndeclaredMessage(where, name, attribute, declared, target) {
80
+ const key = attribute === 'connection' ? 'connection id' : 'folder key';
81
+ const flag = attribute === 'connection' ? '--bind-connection' : '--bind-folder';
82
+ return `${where}: ${attribute} "${name}" is not declared in bindings.json (it declares: ${declared.join(', ')}), `
83
+ + `so the .flow would carry "${name}" as its ${key} and the run would fault with `
84
+ + `"'Connection' has an invalid GUID value: '${name}'". Bind this one: `
85
+ + `\`${prepareFor(target)} ${flag} ${name}\` (several candidates: add \`--connection-id <id>\` from the list it prints). `
86
+ + `Switch to a declared label only if that row was bound for this same connector: bindings.json does not record `
87
+ + `which connector a row belongs to, and another connector's ${key} compiles without any warning and faults only at run time. `
88
+ + `A connector with nothing real to bind yet (a simulated or validate-only task, or one not prepared yet) `
89
+ + `declares the label with a placeholder resourceKey instead, which compiles with a CONNECTION_STUB warning — `
90
+ + `never an invented GUID.`;
91
+ }
package/dist/serialize.js CHANGED
@@ -14,7 +14,7 @@ import { Expr, toExpr, SCHEDULE_PRESETS, DELAY_PRESETS, parseIxpProjectType, fla
14
14
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
15
15
  import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
16
16
  import { dataFabricUnknownInputs } from './core/typed-raw-node.js';
17
- import { bindingSelfNameMessage } from './core/binding-messages.js';
17
+ import { BindingRefusal, GUID_RE, bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
18
18
  import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
19
19
  import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
20
20
  import { buildConfiguration, transportHttpMethod } from './config.js';
@@ -3064,8 +3064,6 @@ function renderValue(val, rename = NO_RENAME) {
3064
3064
  return val.literal ? val.value : '=js:' + renameRefs(val.js, rename);
3065
3065
  return val;
3066
3066
  }
3067
- /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
3068
- const GUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3069
3067
  /**
3070
3068
  * The all-zero prefix authors reach for when they need "a GUID" and have none:
3071
3069
  * `00000000-0000-0000-0000-000000000001`. No tenant issues these. The same
@@ -3087,6 +3085,12 @@ function connectionStubReason(value) {
3087
3085
  return 'not a GUID';
3088
3086
  return undefined;
3089
3087
  }
3088
+ /** What `registry prepare` takes for an event subscription: its connector key and curated event name. */
3089
+ function eventTarget(sub) {
3090
+ return typeof sub.connector === 'string' && sub.connector !== ''
3091
+ ? { key: sub.connector, ...(typeof sub.event === 'string' && sub.event !== '' ? { action: sub.event } : {}) }
3092
+ : undefined;
3093
+ }
3090
3094
  /**
3091
3095
  * Resolve a symbolic connection/folder name to the resource key that is inlined
3092
3096
  * as `connectionId` / `connectionFolderKey`.
@@ -3100,30 +3104,55 @@ function connectionStubReason(value) {
3100
3104
  * `bindings.json` that never declares the name, and one that declares it with
3101
3105
  * its own name as the `resourceKey`.
3102
3106
  *
3103
- * Only the SELF-REFERENTIAL case is an error here. Emitting the symbolic name
3104
- * when nothing resolves it is long-established and deliberate — compiling
3105
- * against partial or absent bindings is how a flow is authored offline, and the
3106
- * name is the only honest placeholder available. A binding that declares the
3107
- * name and then answers with the name is different: someone wrote the entry, so
3108
- * the intent to bind is explicit, and the one thing it cannot mean is "the
3109
- * resource key is the string `is-sandboxes`".
3107
+ * Two cases are errors here, and they are the two where the author's intent to
3108
+ * bind is on record and the emitted value still cannot be a resource key:
3109
+ *
3110
+ * - SELF-REFERENTIAL (`BINDING_SELF_NAME`): an entry declares the name and then
3111
+ * answers with the name. Someone wrote the entry, and the one thing it cannot
3112
+ * mean is "the resource key is the string `is-sandboxes`".
3113
+ * - UNDECLARED IN A POPULATED FILE (`BINDING_UNDECLARED`): bindings.json declares
3114
+ * entries, none of them answers to this label, and the label is not itself a
3115
+ * GUID. The file is the author's statement of what is bound, so a label it
3116
+ * does not carry is a mismatch between two names for one connection, not
3117
+ * offline authoring. Measured: the 2026-09-23 orchestrator-paths run bound
3118
+ * `slack` (prepare's default label) while the source said `is-sandboxes`;
3119
+ * compile warned, exited 0, and the Slack node answered `400 'Connection' has
3120
+ * an invalid GUID value: 'is-sandboxes'`. Across the eval archive this shape
3121
+ * warned 15 times; 13 were fixed before the final compile and the 2 that were
3122
+ * not are that row's failures (09-18, 09-23) — no green run shipped it.
3110
3123
  *
3111
3124
  * Everything else that cannot be a resource key is a WARNING (`CONNECTION_STUB`),
3112
- * never an error: an unbound symbol, an all-zero GUID, an `<angle>` placeholder,
3113
- * a label. The archive shows those in green runs of simulated and validate-only
3114
- * tasks, where no connection exists to bind — refusing them would push an author
3115
- * with nothing real to bind into inventing a plausible GUID instead, which is
3116
- * worse because nothing can tell. But the 2026-09-01 orchestrator-paths run
3117
- * compiled `00000000-0000-0000-0000-000000000001` with no signal at all and the
3118
- * Slack node answered `401 Invalid Organization or User secret` at dispatch;
3119
- * the author deserves to hear that BEFORE the run.
3125
+ * never an error: a symbol when NO bindings.json is loaded or it declares no
3126
+ * entries, an entry declared without a resourceKey, an all-zero GUID, an
3127
+ * `<angle>` placeholder, a label. Compiling against absent bindings is how a flow
3128
+ * is authored offline, and the name is the only honest placeholder available;
3129
+ * the archive shows those in green runs of simulated and validate-only tasks,
3130
+ * where no connection exists to bind. Refusing them would push an author with
3131
+ * nothing real to bind into inventing a plausible GUID instead, which is worse
3132
+ * because nothing can tell. That is also the route for a connector that is not
3133
+ * bound YET in a file that binds others (a two-connector flow with one prepared,
3134
+ * or a simulated connector next to real ones): declare its label with a
3135
+ * placeholder resourceKey and it stays a warning. Mid-loop compiles took that
3136
+ * state in three archived runs (ipe-drive-to-slack 09-23 `drive`, ipe-jira-
3137
+ * lifecycle 09-04 `entra`, customer-escalation-simulated 09-03 `zendesk`), and
3138
+ * each fixed it before the final compile, so what now costs one refused compile
3139
+ * there was never shipped. The 2026-09-01 orchestrator-paths run compiled
3140
+ * `00000000-0000-0000-0000-000000000001` with no signal at all and the Slack node
3141
+ * answered `401 Invalid Organization or User secret` at dispatch; the author
3142
+ * deserves to hear that BEFORE the run.
3120
3143
  */
3121
- function resolveBinding(opts, name, attribute, where) {
3144
+ function resolveBinding(opts, name, attribute, where, target) {
3122
3145
  if (name === undefined)
3123
3146
  return undefined;
3124
3147
  const resolved = opts.bindings?.resolve(name);
3125
3148
  if (resolved === name) {
3126
- throw new Error(bindingSelfNameMessage(where, name, attribute));
3149
+ throw new BindingRefusal('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
3150
+ }
3151
+ if (resolved === undefined) {
3152
+ const declared = undeclaredBinding(opts.bindings, name);
3153
+ if (declared) {
3154
+ throw new BindingRefusal('BINDING_UNDECLARED', bindingUndeclaredMessage(where, name, attribute, declared, target));
3155
+ }
3127
3156
  }
3128
3157
  const value = resolved ?? name;
3129
3158
  const reason = connectionStubReason(value);
@@ -3136,8 +3165,10 @@ function resolveBinding(opts, name, attribute, where) {
3136
3165
  + `and writes both entries into bindings.json (several candidates: pass \`--connection-id <id>\` from the list it `
3137
3166
  + `prints); by hand, read Id and FolderKey from \`uip is connections list --all-folders --output json\`.`;
3138
3167
  const message = resolved === undefined
3139
- ? `${where}: ${attribute} "${name}" has no bindings.json entry`
3140
- + `${describeDeclared(opts.bindings)}, so the .flow carries the symbolic name as its ${key} and the run `
3168
+ ? `${where}: ${declaredWithoutKey(opts.bindings, name)
3169
+ ? `binding "${name}" is declared in bindings.json with no resourceKey`
3170
+ : `${attribute} "${name}" has no bindings.json entry${describeDeclared(opts.bindings)}`}`
3171
+ + `, so the .flow carries the symbolic name as its ${key} and the run `
3141
3172
  + `faults at dispatch with ${fault}. ${remedy}`
3142
3173
  : `${where}: binding "${name}" resolves to "${value}", which is ${reason} and so never a tenant ${key}. `
3143
3174
  + `The .flow carries it and the run faults at dispatch with ${fault}. ${remedy}`;
@@ -3145,6 +3176,10 @@ function resolveBinding(opts, name, attribute, where) {
3145
3176
  }
3146
3177
  return value;
3147
3178
  }
3179
+ /** An entry answers to `name` but carries no resourceKey or default — the placeholder route, still a warning. */
3180
+ function declaredWithoutKey(bindings, name) {
3181
+ return (bindings?.declared?.() ?? []).includes(name) || bindings?.entry?.(name) !== undefined;
3182
+ }
3148
3183
  /** " (bindings.json declares: a, b)" / " (no bindings.json was loaded)" — what the author CAN refer to. */
3149
3184
  function describeDeclared(bindings) {
3150
3185
  const names = bindings?.declared?.() ?? [];
@@ -3282,8 +3317,8 @@ function flattenWhere(where, declared) {
3282
3317
  return out;
3283
3318
  }
3284
3319
  function eventDetail(rc, sub, nodeId, opts) {
3285
- const conn = resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`);
3286
- const folder = resolveBinding(opts, sub.folder, 'folder', `event node "${nodeId}"`);
3320
+ const conn = resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`, eventTarget(sub));
3321
+ const folder = resolveBinding(opts, sub.folder, 'folder', `event node "${nodeId}"`, eventTarget(sub));
3287
3322
  const op = (rc.entry?.operation ?? {});
3288
3323
  const filters = sub.filters ?? [];
3289
3324
  // A GENERIC event (`activityType: GenericTrigger` — Data Fabric, Salesforce,
@@ -4106,8 +4141,8 @@ export function serialize(built, opts = {}) {
4106
4141
  const { entries } = connectionResourceBindings({
4107
4142
  def: eventTriggerRc.v1def,
4108
4143
  connectorKey: eventTriggerRc.connectorKey,
4109
- connectionId: resolveBinding(opts, sub.connection, 'connection', 'event trigger'),
4110
- folderKey: resolveBinding(opts, sub.folder, 'folder', 'event trigger'),
4144
+ connectionId: resolveBinding(opts, sub.connection, 'connection', 'event trigger', eventTarget(sub)),
4145
+ folderKey: resolveBinding(opts, sub.folder, 'folder', 'event trigger', eventTarget(sub)),
4111
4146
  }, resourceBindings);
4112
4147
  for (const b of entries)
4113
4148
  resourceBindings.set(b.id, b);
@@ -4231,8 +4266,8 @@ export function serialize(built, opts = {}) {
4231
4266
  const id = stepUid(scope, step.name);
4232
4267
  const node = makeNode(rc.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def: rc.v1def });
4233
4268
  declareListOutputType(node, rc);
4234
- const conn = resolveBinding(opts, spec.connection, 'connection', `connector "${step.name}"`);
4235
- const folder = resolveBinding(opts, spec.folder, 'folder', `connector "${step.name}"`);
4269
+ const conn = resolveBinding(opts, spec.connection, 'connection', `connector "${step.name}"`, { key: spec.key, action: spec.action });
4270
+ const folder = resolveBinding(opts, spec.folder, 'folder', `connector "${step.name}"`, { key: spec.key, action: spec.action });
4236
4271
  // The platform reaches the connection through the flow's `bindings[]`,
4237
4272
  // never through `inputs.detail` — without this pair the run faults with
4238
4273
  // `[102010]` (board G-19; see connectionResourceBindings).
@@ -5131,8 +5166,8 @@ export function serialize(built, opts = {}) {
5131
5166
  const { entries: waitConnBindings } = connectionResourceBindings({
5132
5167
  def: rc.v1def,
5133
5168
  connectorKey: rc.connectorKey,
5134
- connectionId: resolveBinding(opts, sub.connection, 'connection', `waitForEvent "${step.name}"`),
5135
- folderKey: resolveBinding(opts, sub.folder, 'folder', `waitForEvent "${step.name}"`),
5169
+ connectionId: resolveBinding(opts, sub.connection, 'connection', `waitForEvent "${step.name}"`, eventTarget(sub)),
5170
+ folderKey: resolveBinding(opts, sub.folder, 'folder', `waitForEvent "${step.name}"`, eventTarget(sub)),
5136
5171
  }, resourceBindings);
5137
5172
  for (const b of waitConnBindings)
5138
5173
  resourceBindings.set(b.id, b);
@@ -5986,8 +6021,8 @@ export function serialize(built, opts = {}) {
5986
6021
  const { entries } = connectionResourceBindings({
5987
6022
  def: rc.v1def,
5988
6023
  connectorKey: rc.connectorKey,
5989
- connectionId: resolveBinding(opts, sub.connection, 'connection', 'onEvent() entry point'),
5990
- folderKey: resolveBinding(opts, sub.folder, 'folder', 'onEvent() entry point'),
6024
+ connectionId: resolveBinding(opts, sub.connection, 'connection', 'onEvent() entry point', eventTarget(sub)),
6025
+ folderKey: resolveBinding(opts, sub.folder, 'folder', 'onEvent() entry point', eventTarget(sub)),
5991
6026
  }, resourceBindings);
5992
6027
  for (const b of entries)
5993
6028
  resourceBindings.set(b.id, b);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.9.0",
3
+ "version": "6.9.1",
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",
@@ -87,5 +87,5 @@
87
87
  "@types/node": "^22.7.0",
88
88
  "esbuild": "^0.28.1"
89
89
  },
90
- "gitref": "b7baf691c4126ec18eb3388d0d0a70b09301f24d"
90
+ "gitref": "f637225ed0a30014831ffab8733374625126f6fb"
91
91
  }