@uipath/maestro-builder-sdk 6.57.0 → 6.58.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.
Files changed (66) hide show
  1. package/dist/case/subscription.d.ts +4 -2
  2. package/dist/case/subscription.js +5 -2
  3. package/dist/core/actions.js +2 -2
  4. package/dist/core/binding-messages.d.ts +2 -1
  5. package/dist/core/binding-messages.js +3 -4
  6. package/dist/core/byoa.d.ts +3 -2
  7. package/dist/core/byoa.js +3 -3
  8. package/dist/core/carried-definitions.d.ts +0 -18
  9. package/dist/core/carried-definitions.js +3 -2
  10. package/dist/core/ceql.d.ts +2 -1
  11. package/dist/core/ceql.js +3 -2
  12. package/dist/core/compile-refusal.d.ts +21 -0
  13. package/dist/core/compile-refusal.js +26 -0
  14. package/dist/core/connector-inputs.js +2 -1
  15. package/dist/core/connector-resolve-error.d.ts +2 -1
  16. package/dist/core/connector-resolve-error.js +3 -4
  17. package/dist/core/connectors.js +2 -1
  18. package/dist/core/event-filters.js +2 -1
  19. package/dist/core/hitl-routing.d.ts +7 -82
  20. package/dist/core/hitl-routing.js +95 -6
  21. package/dist/core/library.js +4 -3
  22. package/dist/core/lookups.js +6 -5
  23. package/dist/core/nodes/registry.js +3 -2
  24. package/dist/core/nodes/versioned.js +3 -2
  25. package/dist/core/process-resource-sort-order.js +1 -1
  26. package/dist/core/read-text.d.ts +0 -28
  27. package/dist/core/read-text.js +3 -2
  28. package/dist/core/refusals.d.ts +465 -0
  29. package/dist/core/refusals.js +504 -0
  30. package/dist/core/script-function.js +2 -1
  31. package/dist/core/typescript-module.js +2 -1
  32. package/dist/edit-scope.d.ts +180 -0
  33. package/dist/edit-scope.js +251 -0
  34. package/dist/flow-expr-check.d.ts +2 -1
  35. package/dist/flow-expr-check.js +4 -2
  36. package/dist/flow-sdk.js +17 -16
  37. package/dist/flow-step-tree.js +1 -1
  38. package/dist/flow-variables.js +3 -2
  39. package/dist/load.js +7 -6
  40. package/dist/merge-cli.js +4 -2
  41. package/dist/merge-compile.d.ts +5 -88
  42. package/dist/merge-compile.js +8 -138
  43. package/dist/merge.d.ts +1 -61
  44. package/dist/merge.js +1 -112
  45. package/dist/nodes/agent/flow/inline-agent.js +7 -6
  46. package/dist/nodes/connector/flow/node-type.js +2 -1
  47. package/dist/nodes/data-fabric/flow/factories.js +2 -1
  48. package/dist/nodes/data-fabric/flow/verbs.js +2 -1
  49. package/dist/nodes/delay/flow/factories.js +2 -1
  50. package/dist/nodes/document/flow/ixp.js +5 -1
  51. package/dist/nodes/hitl/flow/check.js +4 -2
  52. package/dist/nodes/hitl/flow/node-type.js +6 -5
  53. package/dist/nodes/http/flow/factories.js +2 -1
  54. package/dist/nodes/http/flow/node-type.js +6 -5
  55. package/dist/nodes/process/flow/factories.js +2 -1
  56. package/dist/nodes/queue/flow/node-type.js +3 -2
  57. package/dist/nodes/transform/flow/node-type.js +2 -1
  58. package/dist/raw-node.js +3 -2
  59. package/dist/ref-resolve.d.ts +0 -36
  60. package/dist/ref-resolve.js +9 -8
  61. package/dist/schema-version.d.ts +0 -38
  62. package/dist/schema-version.js +3 -2
  63. package/dist/sdk-resolve.js +2 -1
  64. package/dist/serialize.d.ts +5 -2
  65. package/dist/serialize.js +115 -83
  66. package/package.json +2 -2
@@ -3,6 +3,7 @@
3
3
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
4
4
  */
5
5
  import { builtinActionSpec } from '../../../core/actions.js';
6
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
6
7
  /**
7
8
  * Declare a delay — a step that pauses the flow for a fixed length of time and
8
9
  * then carries on.
@@ -31,7 +32,7 @@ export function delay(inputs) {
31
32
  const hasDuration = 'duration' in inputs;
32
33
  const hasUntil = 'until' in inputs;
33
34
  if (hasDuration === hasUntil) {
34
- throw new Error("delay() takes exactly ONE of { duration } (an ISO-8601 duration, e.g. 'PT15M') or "
35
+ throw new CompileRefusal('DELAY_DURATION_XOR_UNTIL', "delay() takes exactly ONE of { duration } (an ISO-8601 duration, e.g. 'PT15M') or "
35
36
  + "{ until } (an ISO-8601 date-time, e.g. '2026-09-01T09:00:00Z').");
36
37
  }
37
38
  return builtinActionSpec({ kind: 'delay', inputs });
@@ -14,6 +14,7 @@ import { definitionHasErrorPort } from '../../../core/error-port.js';
14
14
  import { Expr } from '../../../core/expr.js';
15
15
  import { jsonClone } from '../../../core/json-clone.js';
16
16
  import { parseIxpProjectType } from './spec.js';
17
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
17
18
  const IXP_PREFIX = 'uipath.ixp.';
18
19
  export const IXP_DIAGNOSTICS = {
19
20
  IXP_FORBIDDEN_INPUT: { severity: 'error', summary: 'an IxP step setting an input the current node does not declare' },
@@ -65,7 +66,10 @@ const IXP_DEF = ixpDefinition;
65
66
  export function ixpExtractDef(spec) {
66
67
  const parts = parseIxpProjectType(spec.project);
67
68
  if (!parts) {
68
- throw new Error(`ixpExtract: "${spec.project}" is not a resolvable extractor node type. It must be `
69
+ // The three conditions check tells apart, under check's three codes.
70
+ const code = typeof spec.project !== 'string' || spec.project.trim() === '' ? 'IXP_NO_PROJECT'
71
+ : !spec.project.startsWith('uipath.ixp.') ? 'IXP_PROJECT_NOT_NODE_TYPE' : 'IXP_PROJECT_TAIL_UNPARSEABLE';
72
+ throw new CompileRefusal(code, `ixpExtract: "${spec.project}" is not a resolvable extractor node type. It must be `
69
73
  + `uipath.ixp.<slug>.<projectVersionId>-<folderKey> — copy it from `
70
74
  + `\`uip maestro flow registry pull --force && uip maestro flow registry search -f "type:startsWith=uipath.ixp"\`.`);
71
75
  }
@@ -7,7 +7,8 @@ export const HITL_DIAGNOSTICS = {
7
7
  HITL_VARIANT_UNKNOWN: { severity: 'error', summary: 'a variant that names no human-task node type' },
8
8
  HITL_OUTCOME_PORTS_VARIANT: { severity: 'error', summary: 'exposeError or outcomePorts on a variant that cannot honour it' },
9
9
  HITL_LEGACY_COMPLETED: { severity: 'warning', summary: "a task with outcomes that leaves on the legacy 'completed' handle" },
10
- HITL_LEGACY_COMPLETED_CONFLICT: { severity: 'error', summary: 'legacyCompleted with outcomePorts or exposeError, or where it changes nothing' },
10
+ HITL_LEGACY_COMPLETED_CONFLICT: { severity: 'error', summary: 'legacyCompleted with outcomePorts or exposeError' },
11
+ HITL_LEGACY_COMPLETED_NO_EFFECT: { severity: 'error', summary: "legacyCompleted where 'completed' is already the only exit" },
11
12
  HITL_DOCUMENT_WITHOUT_VARIANT: { severity: 'error', summary: 'a document on a task that is not a validation station' },
12
13
  HITL_DOCVAL_WITH_FIELDS: { severity: 'error', summary: 'form fields on a validation station' },
13
14
  HITL_DOCVAL_NO_DOCUMENT: { severity: 'error', summary: 'a validation station with no document' },
@@ -103,7 +104,8 @@ export function checkHitl(inputs, ctx) {
103
104
  return;
104
105
  }
105
106
  if (!hitlDerivesOutcomeHandles(variant) || outcomes.length === 0) {
106
- at('HITL_LEGACY_COMPLETED_CONFLICT', `Human task "${step}" sets legacyCompleted: true, but ${outcomes.length === 0 ? 'it has no outcomes'
107
+ // Its own code: this flow compiles, so the code is check's alone.
108
+ at('HITL_LEGACY_COMPLETED_NO_EFFECT', `Human task "${step}" sets legacyCompleted: true, but ${outcomes.length === 0 ? 'it has no outcomes'
107
109
  : `its variant is "${variant}", which reads its outcomes from its app`}, so 'completed' is already its only exit.`, 'Drop `legacyCompleted`.');
108
110
  return;
109
111
  }
@@ -20,6 +20,7 @@ import { jsonClone } from '../../../core/json-clone.js';
20
20
  import { schemaId } from '../../../core/stable-id.js';
21
21
  import { wireExprBody } from '../../../core/wire-expr.js';
22
22
  import { checkHitl, checkHitlRead } from './check.js';
23
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
23
24
  const BASE = 'uipath.human-in-the-loop';
24
25
  const QUICK_FORM = 'uipath.human-in-the-loop.quick-form';
25
26
  const ACTION_APP = 'uipath.human-in-the-loop.coded-action-app';
@@ -556,23 +557,23 @@ function hitlAt(typeName, version, factory) {
556
557
  const pinned = ctx.pinnedVersion;
557
558
  const exitModel = hitlExitModel(inputs, pinned);
558
559
  if (typeName !== BASE && inputs?.exposeError === true) {
559
- throw new Error(`"${ctx.step}": exposeError needs the base human task's 1.2 definition, which adds the `
560
+ throw new CompileRefusal('HITL_OUTCOME_PORTS_VARIANT', `"${ctx.step}": exposeError needs the base human task's 1.2 definition, which adds the `
560
561
  + `error output — the '${inputs?.variant}' variant has no such version. Drop the variant or exposeError.`);
561
562
  }
562
563
  if (inputs?.outcomePorts === true && !OUTCOME_HANDLE_NODE_TYPES.has(typeName)) {
563
- throw new Error(`"${ctx.step}": outcomePorts: true, but the '${inputs?.variant}' variant reads its outcomes `
564
+ throw new CompileRefusal('HITL_OUTCOME_PORTS_VARIANT', `"${ctx.step}": outcomePorts: true, but the '${inputs?.variant}' variant reads its outcomes `
564
565
  + `from its app and leaves on its single 'completed' exit. Route on out('${ctx.step}', 'Action') downstream.`);
565
566
  }
566
567
  if (inputs?.legacyCompleted === true && (inputs?.outcomePorts !== undefined || inputs?.exposeError === true)) {
567
- throw new Error(`"${ctx.step}": legacyCompleted: true leaves the task on its single 'completed' handle, so it cannot also `
568
+ throw new CompileRefusal('HITL_LEGACY_COMPLETED_CONFLICT', `"${ctx.step}": legacyCompleted: true leaves the task on its single 'completed' handle, so it cannot also `
568
569
  + `${inputs?.exposeError === true ? 'expose the error output (exposeError)' : 'set outcomePorts'}. Drop one.`);
569
570
  }
570
571
  if (inputs?.legacyCompleted === true && typeName === BASE && (pinned === '1.1' || pinned === '1.2')) {
571
- throw new Error(`"${ctx.step}": legacyCompleted: true, but { version: '${pinned}' } pins the task's per-outcome definition, `
572
+ throw new CompileRefusal('HITL_LEGACY_COMPLETED_VERSION', `"${ctx.step}": legacyCompleted: true, but { version: '${pinned}' } pins the task's per-outcome definition, `
572
573
  + `which has no 'completed' handle (flow validate refuses the edge). Drop the pin or legacyCompleted.`);
573
574
  }
574
575
  if (inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
575
- throw new Error(`"${ctx.step}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
576
+ throw new CompileRefusal('HITL_EXPOSE_ERROR_VERSION', `"${ctx.step}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
576
577
  + `Drop the pin or match it.`);
577
578
  }
578
579
  const { inputs: nodeInputs, outputs } = hitlInputs(inputs, typeName, ctx);
@@ -3,6 +3,7 @@
3
3
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
4
4
  */
5
5
  import { builtinActionSpec } from '../../../core/actions.js';
6
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
6
7
  /**
7
8
  * Declare an HTTP request action, explicitly choosing standalone or managed.
8
9
  *
@@ -12,7 +13,7 @@ import { builtinActionSpec } from '../../../core/actions.js';
12
13
  */
13
14
  export function http(inputs) {
14
15
  if (typeof inputs.managed !== 'boolean') {
15
- throw new Error('http() requires managed: true or managed: false because they emit different platform nodes. '
16
+ throw new CompileRefusal('HTTP_MANAGED_REQUIRED', 'http() requires managed: true or managed: false because they emit different platform nodes. '
16
17
  + 'Use true for Managed HTTP Request/core.action.http.v2 or when non-2xx statuses must reach .onError(); '
17
18
  + 'use false for the standalone core.action.http node.');
18
19
  }
@@ -12,6 +12,7 @@ import { MANAGED_HTTP_ACTIVITY_TYPE_ID, MANAGED_HTTP_ESSENTIAL_CONFIGURATION, MA
12
12
  import { isRecord } from '../../../core/node-envelope.js';
13
13
  import { wireExprBody } from '../../../core/wire-expr.js';
14
14
  import { checkHttpRead, checkHttpStep, plainHttpOnErrorMessage } from './check.js';
15
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
15
16
  const PLAIN = 'core.action.http';
16
17
  const MANAGED = 'core.action.http.v2';
17
18
  /** The definition's `inputDefaults` at the version being emitted. */
@@ -67,7 +68,7 @@ function plainHttpInputs(i, ctx) {
67
68
  if (i.connection !== undefined
68
69
  || i.folder !== undefined
69
70
  || i.targetConnector !== undefined) {
70
- throw new Error('standalone http cannot use Integration Service connection settings; set managed: true');
71
+ throw new CompileRefusal('HTTP_BINDING_REQUIRES_MANAGED', 'standalone http cannot use Integration Service connection settings; set managed: true');
71
72
  }
72
73
  return {
73
74
  ...defaults(ctx, PLAIN),
@@ -91,7 +92,7 @@ function managedHttpBinding(i, ctx) {
91
92
  return undefined;
92
93
  if (typeof i.connection !== 'string' || i.connection.trim() === ''
93
94
  || typeof i.folder !== 'string' || i.folder.trim() === '') {
94
- throw new Error('managed http connection mode requires both a non-empty connection and folder');
95
+ throw new CompileRefusal('HTTP_MANAGED_BINDING_INCOMPLETE', 'managed http connection mode requires both a non-empty connection and folder');
95
96
  }
96
97
  // The same resolution a connector step gets: a self-referential binding is an
97
98
  // error, a stub is a warning.
@@ -121,11 +122,11 @@ function managedHttpBinding(i, ctx) {
121
122
  */
122
123
  function managedHttpInputs(i, binding, ctx) {
123
124
  if (i.targetConnector !== undefined && !binding) {
124
- throw new Error('managed http targetConnector requires both a non-empty connection and folder');
125
+ throw new CompileRefusal('HTTP_TARGET_CONNECTOR_REQUIRES_BINDING', 'managed http targetConnector requires both a non-empty connection and folder');
125
126
  }
126
127
  if (i.targetConnector !== undefined
127
128
  && (typeof i.targetConnector !== 'string' || i.targetConnector.trim() === '')) {
128
- throw new Error('managed http targetConnector must be a non-empty connector key');
129
+ throw new CompileRefusal('HTTP_TARGET_CONNECTOR_EMPTY', 'managed http targetConnector must be a non-empty connector key');
129
130
  }
130
131
  const url = ctx.render(i.url);
131
132
  const bodyParameters = {
@@ -270,7 +271,7 @@ function liftHttp(node, ctx, managed) {
270
271
  const connectionValue = detail.connectionId ?? detail.connectionResourceId ?? req.connection;
271
272
  const retry = inputs.retryCount !== undefined && Number(inputs.retryCount) !== 0 ? Number(inputs.retryCount) : undefined;
272
273
  if (retry !== undefined && !Number.isFinite(retry)) {
273
- throw new Error(`decompile: expected a number, got ${JSON.stringify(inputs.retryCount)}.`);
274
+ throw new CompileRefusal('DECOMPILE_NOT_A_NUMBER', `decompile: expected a number, got ${JSON.stringify(inputs.retryCount)}.`);
274
275
  }
275
276
  const branchRows = Array.isArray(inputs.branches) ? inputs.branches : [];
276
277
  const returns = liftReturns(node) ?? returnsFromReads(ctx.reads());
@@ -4,6 +4,7 @@
4
4
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
5
5
  */
6
6
  import { builtinActionSpec } from '../../../core/actions.js';
7
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
7
8
  /**
8
9
  * Invoke a deployed RPA (robotic process automation) workflow — a classic
9
10
  * UI-automation process, published to Orchestrator, run by a robot.
@@ -166,7 +167,7 @@ export function agenticProcess(inputs) {
166
167
  // code — and silently dropping the returns would validate a read that can
167
168
  // never be satisfied.
168
169
  if (inputs.completion === 'fire-and-forget' && inputs.returns !== undefined) {
169
- throw new Error("agenticProcess(): completion 'fire-and-forget' publishes no output — remove `returns`, "
170
+ throw new CompileRefusal('FIRE_AND_FORGET_RETURNS', "agenticProcess(): completion 'fire-and-forget' publishes no output — remove `returns`, "
170
171
  + "or use completion: 'wait' to read the process result.");
171
172
  }
172
173
  return builtinActionSpec({ kind: 'agenticProcess', inputs });
@@ -3,6 +3,7 @@ import { jsonClone } from '../../../core/json-clone.js';
3
3
  import { isRecord } from '../../../core/node-envelope.js';
4
4
  import { checkQueueItem, itemDataIsString, queueItemExprMessage } from './check.js';
5
5
  import { QUEUE_ITEM_RECORD_SCHEMA } from './record.js';
6
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
6
7
  const CREATE = 'core.action.queue.create';
7
8
  const CREATE_AND_WAIT = 'core.action.queue.create-and-wait';
8
9
  /**
@@ -39,7 +40,7 @@ function queueItemDef(spec, nodeType, stepName, ids, base) {
39
40
  // The template at the version being emitted: the registry resolved it exactly
40
41
  // (an unregistered pin is NODE_VERSION_UNREGISTERED before this runs).
41
42
  if (!base)
42
- throw new Error(`no bundled definition for "${nodeType}"`);
43
+ throw new Error(`internal SDK error: no bundled definition for "${nodeType}"`);
43
44
  const def = jsonClone(base);
44
45
  def.model.bindings.resourceKey = spec.key;
45
46
  def.model.context = [
@@ -172,7 +173,7 @@ function itemDataString(item, step, typeName, version) {
172
173
  return '';
173
174
  for (const [k, v] of rows) {
174
175
  if (v instanceof Expr)
175
- throw new Error(queueItemExprMessage(step, typeName, version, k));
176
+ throw new CompileRefusal('QUEUE_ITEM_EXPR_UNSUPPORTED', queueItemExprMessage(step, typeName, version, k));
176
177
  }
177
178
  return JSON.stringify(item);
178
179
  }
@@ -11,6 +11,7 @@ import { wireExprBody } from '../../../core/wire-expr.js';
11
11
  import { jsonClone } from '../../../core/json-clone.js';
12
12
  import { VARIANT_OPERATION } from './spec.js';
13
13
  import { checkTransform } from './check.js';
14
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
14
15
  /**
15
16
  * Emit a transform's `inputs` in the canonical shape the node's runtime reads:
16
17
  * `collection` as a BARE variable path (not `=js:`-prefixed — it is a path
@@ -210,7 +211,7 @@ function transformAt(typeName, variant, version, factory) {
210
211
  // (TRANSFORM_VARIANT_UNKNOWN). A compile that skipped check refuses here
211
212
  // rather than emit the generic node, which would run and be the wrong node.
212
213
  if (variant === undefined && inputs?.variant !== undefined && inputs.variant !== null) {
213
- throw new Error(`transform step "${ctx.step}": variant ${JSON.stringify(inputs.variant)} is not a Transform node type `
214
+ throw new CompileRefusal('TRANSFORM_VARIANT_UNKNOWN', `transform step "${ctx.step}": variant ${JSON.stringify(inputs.variant)} is not a Transform node type `
214
215
  + `(${VARIANTS.join(', ')}), so there is no node to emit. Fix the variant, or omit it for the generic Transform.`);
215
216
  }
216
217
  const base = ctx.definition(typeName, version)?.outputDefinition;
package/dist/raw-node.js CHANGED
@@ -10,6 +10,7 @@ import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
10
10
  import { rawNodeInputProblem, rawNodeTypedFamilyWarning } from './raw-node-advice.js';
11
11
  import { forwardFanOut } from './core/definition-handles.js';
12
12
  import { RAW_NODE_EXITS } from './core/raw-node-exits.js';
13
+ import { CompileRefusal } from './core/compile-refusal.js';
13
14
  /**
14
15
  * Place a node this SDK has no factory for, carrying its definition verbatim
15
16
  * (`uipath.exotic.thing@2.1` and the manifest the platform served for it).
@@ -101,12 +102,12 @@ class RawNode extends FlowAction {
101
102
  // type never reads is refused by the serializer, for every contribution.)
102
103
  const refusal = connectorRawNodeRefusal(this.spec.nodeType);
103
104
  if (refusal)
104
- throw new Error(refusal);
105
+ throw new CompileRefusal('RAW_NODE_CONNECTOR', refusal);
105
106
  const version = context.requestedVersion ?? this.spec.version;
106
107
  if (context.requestedVersion !== undefined && context.requestedVersion !== this.spec.version) {
107
108
  // Versions are exact everywhere in this SDK: a contributor may not
108
109
  // substitute its own for an explicit request (design §2.6).
109
- throw new Error(`rawNode("${this.spec.nodeType}") carries version ${this.spec.version}, but the step requests `
110
+ throw new CompileRefusal('RAW_NODE_VERSION_MISMATCH', `rawNode("${this.spec.nodeType}") carries version ${this.spec.version}, but the step requests `
110
111
  + `${context.requestedVersion}. Pass the manifest for the requested version, or drop { version }.`);
111
112
  }
112
113
  return {
@@ -1,39 +1,3 @@
1
- /**
2
- * `$ref` resolution for SPLIT `.flow` files (design §5, Phase 5E).
3
- *
4
- * The format lets a `.flow` be split across sibling files with JSON Reference
5
- * (`{ "$ref": "./layout.json" }`) — used to sideline layout or definitions, or
6
- * to extract a subflow out of an oversized file
7
- * (`flow-schema/REF_RESOLUTION.md`, MST-8579). Decompile has to READ those, so
8
- * this resolves them into ONE object model before anything else looks at the
9
- * document.
10
- *
11
- * Compile keeps writing a single file, deliberately: nothing in the designer or
12
- * the skills splits files today, so write-through splitting would be a merge
13
- * concern with no producer to round-trip against.
14
- *
15
- * The semantics below are the platform resolver's, mirrored rather than
16
- * reinvented (`flow-schema/src/ref-resolver/`), because a file the platform
17
- * reads one way and the SDK another is worse than no support at all:
18
- *
19
- * - **`$ref` replaces the whole object.** JSON Reference, not JSON Schema —
20
- * sibling properties beside a `$ref` are dropped, not merged.
21
- * - **Same-document fragments (`#/…`) are left ALONE.** They are legitimate
22
- * internal pointers — a connector tool's `outputSchema` carries them — and
23
- * they are not file references. Resolving them as filenames is the exact
24
- * regression the platform's own guard exists to prevent.
25
- * - **Relative paths only.** Absolute paths, Windows drive letters, protocol
26
- * URIs and traversal above the referencing file's root are REFUSED, because
27
- * a `.flow` is untrusted input and the alternative is a file-exfiltration
28
- * primitive (`$ref: '/etc/passwd'`, `$ref: 'https://…'`).
29
- * - **Any depth**, and resolved content is walked again for its own refs.
30
- *
31
- * Where this deliberately differs: an unresolvable ref is an ERROR here, not a
32
- * warning. The platform's resolver collects non-fatal errors because a canvas
33
- * can render a partial document and let a person fix it; decompile turns the
34
- * document into SOURCE, and a silently-missing subtree becomes source that
35
- * compiles to a different flow. Failing names the file that is missing.
36
- */
37
1
  /** How this module reads a sibling file. Injected so it is testable and so the
38
2
  * SDK core stays free of `node:fs`. */
39
3
  export type RefReader = (resolvedPath: string) => string;
@@ -34,6 +34,7 @@
34
34
  * document into SOURCE, and a silently-missing subtree becomes source that
35
35
  * compiles to a different flow. Failing names the file that is missing.
36
36
  */
37
+ import { CompileRefusal } from './core/compile-refusal.js';
37
38
  /**
38
39
  * Collapse `.` and `..` segments, preserving a leading `/` for POSIX absolute
39
40
  * paths — the platform's `normalizePath`, same behavior.
@@ -60,13 +61,13 @@ function normalizePath(path) {
60
61
  */
61
62
  export function resolveRefPath(baseUri, refPath) {
62
63
  if (refPath.startsWith('#')) {
63
- throw new Error(`$ref "${refPath}" is a same-document fragment, not a file — it must not be resolved as a path.`);
64
+ throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" is a same-document fragment, not a file — it must not be resolved as a path.`);
64
65
  }
65
66
  if (refPath.startsWith('/') || /^[a-zA-Z]:/.test(refPath)) {
66
- throw new Error(`$ref "${refPath}" is an absolute path; only relative paths are allowed.`);
67
+ throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" is an absolute path; only relative paths are allowed.`);
67
68
  }
68
69
  if (refPath.includes('://')) {
69
- throw new Error(`$ref "${refPath}" is a protocol URI; only relative file paths are allowed.`);
70
+ throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" is a protocol URI; only relative file paths are allowed.`);
70
71
  }
71
72
  const normalizedBase = baseUri.replace(/\\/g, '/');
72
73
  const lastSlash = normalizedBase.lastIndexOf('/');
@@ -74,7 +75,7 @@ export function resolveRefPath(baseUri, refPath) {
74
75
  const resolved = normalizePath(`${baseDir}${refPath}`);
75
76
  const isAbsoluteBase = normalizedBase.startsWith('/') || /^[a-zA-Z]:\//.test(normalizedBase);
76
77
  if (!isAbsoluteBase && resolved.startsWith('..')) {
77
- throw new Error(`$ref "${refPath}" escapes the project root.`);
78
+ throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" escapes the project root.`);
78
79
  }
79
80
  return resolved;
80
81
  }
@@ -115,11 +116,11 @@ export function resolveFlowRefs(doc, options) {
115
116
  if (node.$ref.startsWith('#'))
116
117
  return node;
117
118
  if (depth >= maxDepth) {
118
- throw new Error(`$ref resolution exceeded ${maxDepth} levels at ${where} ("${node.$ref}").`);
119
+ throw new CompileRefusal('JSON_REF_TOO_DEEP', `$ref resolution exceeded ${maxDepth} levels at ${where} ("${node.$ref}").`);
119
120
  }
120
121
  const path = resolveRefPath(baseUri, node.$ref);
121
122
  if (visiting.has(path)) {
122
- throw new Error(`Circular $ref at ${where}: "${node.$ref}" is already being resolved.`);
123
+ throw new CompileRefusal('JSON_REF_CIRCULAR', `Circular $ref at ${where}: "${node.$ref}" is already being resolved.`);
123
124
  }
124
125
  let text;
125
126
  try {
@@ -127,7 +128,7 @@ export function resolveFlowRefs(doc, options) {
127
128
  }
128
129
  catch (err) {
129
130
  const why = err instanceof Error ? err.message : String(err);
130
- throw new Error(`$ref at ${where} points at "${node.$ref}", which could not be read (${why}).`);
131
+ throw new CompileRefusal('JSON_REF_UNREADABLE', `$ref at ${where} points at "${node.$ref}", which could not be read (${why}).`);
131
132
  }
132
133
  let parsed;
133
134
  try {
@@ -135,7 +136,7 @@ export function resolveFlowRefs(doc, options) {
135
136
  }
136
137
  catch (err) {
137
138
  const why = err instanceof Error ? err.message : String(err);
138
- throw new Error(`$ref at ${where} points at "${node.$ref}", which is not valid JSON (${why}).`);
139
+ throw new CompileRefusal('JSON_REF_NOT_JSON', `$ref at ${where} points at "${node.$ref}", which is not valid JSON (${why}).`);
139
140
  }
140
141
  visiting.add(path);
141
142
  try {
@@ -1,41 +1,3 @@
1
- /**
2
- * schema-version — the artifact's FILE-FORMAT version, as something an author
3
- * can declare and a decompile can PRESERVE.
4
- *
5
- * Every artifact family this SDK writes carries a version that describes the
6
- * DOCUMENT's grammar rather than the author's release: Flow's top-level
7
- * `version`, Case's top-level `version`, BPMN's `uipath:migrationVersion`. Until
8
- * now each family pinned exactly one value in its serializer's format profile
9
- * and overwrote whatever the input said, which is right for greenfield
10
- * authoring and wrong for the brownfield loop:
11
- *
12
- * decompile(a 1.10 flow) -> compile -> a 1.9 flow, with nothing said.
13
- *
14
- * That silent rewrite is the hole this module closes. `.schemaVersion(v)` on
15
- * each builder declares the version the emitted document targets, decompile
16
- * emits the call so a round-trip re-declares what it read, and the serializer
17
- * writes what was declared instead of the pin.
18
- *
19
- * ## A declared version is a CLAIM, not a switch
20
- *
21
- * The rule that shapes this whole module is the one CASE_PARITY §3.8 states for
22
- * `.version()`: a document's version asserts that the whole document matches
23
- * that schema. An author writing `.version('30.0.0')` against the old
24
- * V20-emitting serializer produced an INVALID document, because the claim was
25
- * false. So `.schemaVersion()` accepts only versions this SDK can actually
26
- * write — {@link SchemaVersionPolicy.writable}, each row carrying the evidence
27
- * that says so — and refuses anything else rather than stamping a claim it
28
- * cannot back. Moving a document BETWEEN versions stays the product's job
29
- * (`uip maestro flow migrate`), because a hop can carry data repairs this SDK
30
- * has no way to reproduce.
31
- *
32
- * ## Comparing versions
33
- *
34
- * `'1.10' < '1.9'` as strings, which is exactly the wrong answer, and the same
35
- * trap sits in Case's `'30.0.0'` vs `'9.0.0'`. Nothing here compares version
36
- * strings with `<`; {@link compareSchemaVersions} splits on `.` and compares
37
- * segments numerically, and every gate goes through it.
38
- */
39
1
  /** The artifact families that carry a schema version. */
40
2
  export type ArtifactFamily = 'flow' | 'case' | 'bpmn';
41
3
  /** A version this SDK build can WRITE, with the evidence that says it can. */
@@ -36,6 +36,7 @@
36
36
  * strings with `<`; {@link compareSchemaVersions} splits on `.` and compares
37
37
  * segments numerically, and every gate goes through it.
38
38
  */
39
+ import { CompileRefusal } from './core/compile-refusal.js';
39
40
  /** A version string the schema families use: dotted, numeric, e.g. `1.9`, `1.10`, `30.0.0`. */
40
41
  const SYNTAX = /^\d+(\.\d+)*$/;
41
42
  /** True when `value` is shaped like a schema version at all. */
@@ -52,7 +53,7 @@ export function isSchemaVersionSyntax(value) {
52
53
  */
53
54
  export function compareSchemaVersions(a, b) {
54
55
  if (!isSchemaVersionSyntax(a) || !isSchemaVersionSyntax(b)) {
55
- throw new Error(`compareSchemaVersions: "${a}" / "${b}" is not a dotted-numeric schema version.`);
56
+ throw new Error(`internal SDK error: compareSchemaVersions: "${a}" / "${b}" is not a dotted-numeric schema version.`);
56
57
  }
57
58
  const left = a.split('.').map(Number);
58
59
  const right = b.split('.').map(Number);
@@ -134,7 +135,7 @@ export function resolveSchemaVersion(policy, requested) {
134
135
  return policy.default;
135
136
  const refusal = schemaVersionRefusal(policy, requested);
136
137
  if (refusal)
137
- throw new Error(refusal);
138
+ throw new CompileRefusal('SCHEMA_VERSION_UNSUPPORTED', refusal);
138
139
  return requested;
139
140
  }
140
141
  export function preserveSchemaVersion(policy, declared) {
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { register } from 'node:module';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { PACKAGE_NAME } from './package-name.js';
6
+ import { CompileRefusal } from './core/compile-refusal.js';
6
7
  let registered = false;
7
8
  /**
8
9
  * Make `import … from '@uipath/maestro-builder-sdk'` in authored source resolve
@@ -25,7 +26,7 @@ export function useThisSdkForAuthoredSource() {
25
26
  registered = true;
26
27
  return;
27
28
  }
28
- throw new Error(`cannot point authored source at this copy of ${PACKAGE_NAME}: ${fileURLToPath(hook)} is missing, as in a bundle, `
29
+ throw new CompileRefusal('SDK_RESOLVE_HOOK_MISSING', `cannot point authored source at this copy of ${PACKAGE_NAME}: ${fileURLToPath(hook)} is missing, as in a bundle, `
29
30
  + 'so its imports would load a second copy and serialize wrong. Run the installed package, or load authored source yourself.');
30
31
  }
31
32
  register(hook);
@@ -188,9 +188,12 @@ export declare function assertFileWideIdsUnique(flow: any): void;
188
188
  * It holds for real definitions: of the 5,102 in the local registry index
189
189
  * (2026-09-30), 4,999 reference a `<bindings.…>` name and every one of them
190
190
  * declares that name in `model.bindings.values`, which is what the emitters
191
- * close. Agent resource definitions are the one exemption (see below).
191
+ * close. Agent resource definitions are one exemption (see below); `unbound`,
192
+ * the nodes a draft compile reported as `BINDING_MISSING`, is the other: such a
193
+ * node names no connection, so it writes no binding rows, and the draft finding
194
+ * already says so.
192
195
  *
193
196
  * @throws BindingRefusal `BINDING_PLACEHOLDER_UNRESOLVED`, naming each dangling
194
197
  * placeholder and the step that carries it.
195
198
  */
196
- export declare function assertBindingPlaceholdersResolve(flow: any): void;
199
+ export declare function assertBindingPlaceholdersResolve(flow: any, unbound?: ReadonlySet<string>): void;