@uipath/maestro-builder-sdk 6.57.0 → 6.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) 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/flow-expr-check.d.ts +2 -1
  33. package/dist/flow-expr-check.js +4 -2
  34. package/dist/flow-sdk.js +17 -16
  35. package/dist/flow-step-tree.js +1 -1
  36. package/dist/flow-variables.js +3 -2
  37. package/dist/load.js +7 -6
  38. package/dist/nodes/agent/flow/inline-agent.js +7 -6
  39. package/dist/nodes/connector/flow/node-type.js +2 -1
  40. package/dist/nodes/data-fabric/flow/factories.js +2 -1
  41. package/dist/nodes/data-fabric/flow/verbs.js +2 -1
  42. package/dist/nodes/delay/flow/factories.js +2 -1
  43. package/dist/nodes/document/flow/ixp.js +5 -1
  44. package/dist/nodes/hitl/flow/check.js +4 -2
  45. package/dist/nodes/hitl/flow/node-type.js +6 -5
  46. package/dist/nodes/http/flow/factories.js +2 -1
  47. package/dist/nodes/http/flow/node-type.js +6 -5
  48. package/dist/nodes/process/flow/factories.js +2 -1
  49. package/dist/nodes/queue/flow/node-type.js +3 -2
  50. package/dist/nodes/transform/flow/node-type.js +2 -1
  51. package/dist/raw-node.js +3 -2
  52. package/dist/ref-resolve.d.ts +0 -36
  53. package/dist/ref-resolve.js +9 -8
  54. package/dist/schema-version.d.ts +0 -38
  55. package/dist/schema-version.js +3 -2
  56. package/dist/sdk-resolve.js +2 -1
  57. package/dist/serialize.d.ts +5 -2
  58. package/dist/serialize.js +115 -83
  59. package/package.json +2 -2
@@ -10,12 +10,14 @@
10
10
  */
11
11
  import type { EventSubscription } from '../core/actions.js';
12
12
  import type { Library, ResolvedConnector } from '../core/library.js';
13
+ import { CompileRefusal } from '../core/compile-refusal.js';
13
14
  import type { BindingsView } from '../core/binding-messages.js';
14
15
  /** Check's code for {@link GenericEventNoObjectError}; Flow and BPMN `check` use the same one. */
15
16
  export declare const EVENT_GENERIC_NO_OBJECT = "EVENT_GENERIC_NO_OBJECT";
16
17
  /** A GENERIC event subscription that names no object. */
17
- export declare class GenericEventNoObjectError extends Error {
18
- readonly code = "EVENT_GENERIC_NO_OBJECT";
18
+ export declare class GenericEventNoObjectError extends CompileRefusal {
19
+ readonly code: typeof EVENT_GENERIC_NO_OBJECT;
20
+ constructor(message: string);
19
21
  }
20
22
  /**
21
23
  * Resolve a Case event subscription: `library.resolveEvent` with the
@@ -1,10 +1,13 @@
1
1
  import { genericEventObjectRefusal } from '../core/event-checks.js';
2
2
  import { ByoaRefusal, byoaUnverified } from '../core/byoa.js';
3
+ import { CompileRefusal } from '../core/compile-refusal.js';
3
4
  /** Check's code for {@link GenericEventNoObjectError}; Flow and BPMN `check` use the same one. */
4
5
  export const EVENT_GENERIC_NO_OBJECT = 'EVENT_GENERIC_NO_OBJECT';
5
6
  /** A GENERIC event subscription that names no object. */
6
- export class GenericEventNoObjectError extends Error {
7
- code = EVENT_GENERIC_NO_OBJECT;
7
+ export class GenericEventNoObjectError extends CompileRefusal {
8
+ constructor(message) {
9
+ super(EVENT_GENERIC_NO_OBJECT, message);
10
+ }
8
11
  }
9
12
  /** How a Case subscription spells the `object` of a GENERIC event, for the refusal text. */
10
13
  function caseObjectOption(object) {
@@ -13,7 +13,7 @@ class BuiltinAction extends FlowAction {
13
13
  Object.assign(this, spec);
14
14
  }
15
15
  contribute() {
16
- throw new Error('built-in action specs are lowered by the serializer directly; contribute() is only called on custom FlowAction subclasses');
16
+ throw new Error('internal SDK error: built-in action specs are lowered by the serializer directly; contribute() is only called on custom FlowAction subclasses');
17
17
  }
18
18
  }
19
19
  class BuiltinTrigger extends FlowTrigger {
@@ -22,7 +22,7 @@ class BuiltinTrigger extends FlowTrigger {
22
22
  Object.assign(this, spec);
23
23
  }
24
24
  contribute() {
25
- throw new Error('built-in trigger specs are lowered by the serializer directly; contribute() is only called on custom FlowTrigger subclasses');
25
+ throw new Error('internal SDK error: built-in trigger specs are lowered by the serializer directly; contribute() is only called on custom FlowTrigger subclasses');
26
26
  }
27
27
  }
28
28
  /**
@@ -1,3 +1,4 @@
1
+ import { CompileRefusal } from './compile-refusal.js';
1
2
  /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
2
3
  export declare const GUID_RE: RegExp;
3
4
  /** The diagnostic codes a label refusal carries, in `check` and on the error `serialize` throws. */
@@ -18,7 +19,7 @@ export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED'
18
19
  * message is the sentence `check` prints; the `code` lets `compile --no-check`
19
20
  * print it in the same `✗ [CODE]` shape as the check pass instead of a bare line.
20
21
  */
21
- export declare class BindingRefusal extends Error {
22
+ export declare class BindingRefusal extends CompileRefusal {
22
23
  readonly code: BindingRefusalCode;
23
24
  constructor(code: BindingRefusalCode, message: string);
24
25
  }
@@ -6,6 +6,7 @@
6
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
+ import { CompileRefusal } from './compile-refusal.js';
9
10
  /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
10
11
  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
  /**
@@ -13,12 +14,10 @@ export const GUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f
13
14
  * message is the sentence `check` prints; the `code` lets `compile --no-check`
14
15
  * print it in the same `✗ [CODE]` shape as the check pass instead of a bare line.
15
16
  */
16
- export class BindingRefusal extends Error {
17
- code;
17
+ export class BindingRefusal extends CompileRefusal {
18
18
  constructor(code, message) {
19
- super(message);
19
+ super(code, message);
20
20
  this.name = 'BindingRefusal';
21
- this.code = code;
22
21
  }
23
22
  }
24
23
  export function bindingSelfNameMessage(where, name, attribute, target) {
@@ -1,3 +1,4 @@
1
+ import { CompileRefusal } from './compile-refusal.js';
1
2
  /** The diagnostic code, in `check` and on the error `serialize` throws. */
2
3
  export declare const EVENT_BYOA_UNVERIFIED = "EVENT_BYOA_UNVERIFIED";
3
4
  /** One verification `prepare` recorded. Only a PASSING check is ever written. */
@@ -30,8 +31,8 @@ export declare function byoaLedgerKey(connectorKey: string, event: string): stri
30
31
  /** The record for `connectionId` under `nodeType`, matching the id case-insensitively. */
31
32
  export declare function byoaRecordFor(ledger: ByoaLedger | undefined, nodeType: string, connectionId: string): ByoaRecord | undefined;
32
33
  /** What `compile` throws for the refusal; `code` lets the CLI print it as `✗ [CODE]`. */
33
- export declare class ByoaRefusal extends Error {
34
- readonly code = "EVENT_BYOA_UNVERIFIED";
34
+ export declare class ByoaRefusal extends CompileRefusal {
35
+ readonly code: typeof EVENT_BYOA_UNVERIFIED;
35
36
  constructor(message: string);
36
37
  }
37
38
  /** One subscription, as the rule needs it. */
package/dist/core/byoa.js CHANGED
@@ -22,6 +22,7 @@
22
22
  */
23
23
  import { GUID_RE } from './binding-messages.js';
24
24
  import { prepareCommand } from './cli-spelling.js';
25
+ import { CompileRefusal } from './compile-refusal.js';
25
26
  /** The diagnostic code, in `check` and on the error `serialize` throws. */
26
27
  export const EVENT_BYOA_UNVERIFIED = 'EVENT_BYOA_UNVERIFIED';
27
28
  /**
@@ -45,10 +46,9 @@ export function byoaRecordFor(ledger, nodeType, connectionId) {
45
46
  return undefined;
46
47
  }
47
48
  /** What `compile` throws for the refusal; `code` lets the CLI print it as `✗ [CODE]`. */
48
- export class ByoaRefusal extends Error {
49
- code = EVENT_BYOA_UNVERIFIED;
49
+ export class ByoaRefusal extends CompileRefusal {
50
50
  constructor(message) {
51
- super(message);
51
+ super(EVENT_BYOA_UNVERIFIED, message);
52
52
  this.name = 'ByoaRefusal';
53
53
  }
54
54
  }
@@ -1,21 +1,3 @@
1
- /**
2
- * Node definitions a flow CARRIES — the third source a pinned `{ version }`
3
- * resolves from, after the SDK's bundle (#1128).
4
- *
5
- * Every `.flow` file holds the exact definition of every node it uses, in
6
- * `definitions[]`. The SDK bundles only the versions it was released with, so
7
- * a version the registry served later (`node add` writes
8
- * `uipath.human-in-the-loop.quick-form@1.6`; the SDK ships 1.0 and 1.1) used
9
- * to be refused, and the source decompile wrote for an untouched product flow
10
- * did not compile. A carried definition closes that: decompile copies the
11
- * file's own entry into `.definitions([...])`, and compile resolves the pin
12
- * against it.
13
- *
14
- * A carried definition is an exact copy of a real one, never a way to invent a
15
- * version. So it is validated the way a `rawNode` snapshot manifest is: it
16
- * names its own `nodeType` and `version`, and it has the `display.label` every
17
- * node manifest has.
18
- */
19
1
  /** A carried definition, by its `<nodeType>@<version>` key. */
20
2
  export type CarriedDefinitions = ReadonlyMap<string, Record<string, unknown>>;
21
3
  /**
@@ -16,6 +16,7 @@
16
16
  * names its own `nodeType` and `version`, and it has the `display.label` every
17
17
  * node manifest has.
18
18
  */
19
+ import { CompileRefusal } from './compile-refusal.js';
19
20
  /**
20
21
  * Why `manifest` cannot be carried, or `undefined` when it can.
21
22
  *
@@ -44,12 +45,12 @@ export function indexCarriedDefinitions(manifests, where, into = new Map()) {
44
45
  for (const m of manifests) {
45
46
  const refusal = carriedDefinitionRefusal(m, where);
46
47
  if (refusal)
47
- throw new Error(refusal);
48
+ throw new CompileRefusal('CARRIED_DEFINITION_INVALID', refusal);
48
49
  const def = m;
49
50
  const key = `${def.nodeType}@${def.version}`;
50
51
  const prior = into.get(key);
51
52
  if (prior !== undefined && stableJson(prior) !== stableJson(def)) {
52
- throw new Error(`${where}: two different carried definitions for ${key}. Keep the one the file holds.`);
53
+ throw new CompileRefusal('CARRIED_DEFINITION_CONFLICT', `${where}: two different carried definitions for ${key}. Keep the one the file holds.`);
53
54
  }
54
55
  into.set(key, def);
55
56
  }
@@ -32,6 +32,7 @@
32
32
  * its runtime half and gets no tree — the same state as before this existed,
33
33
  * rather than a wrong one.
34
34
  */
35
+ import { CompileRefusal } from './compile-refusal.js';
35
36
  /** A leaf's comparison operator, in the canonical PascalCase the canvas stores. */
36
37
  export type CeqlOperator = 'Equals' | 'NotEquals' | 'LessThan' | 'LessThanOrEqual' | 'GreaterThan' | 'GreaterThanOrEqual' | 'Contains' | 'NotContains' | 'StartsWith' | 'NotStartsWith' | 'EndsWith' | 'NotEndsWith' | 'Like' | 'NotLike' | 'IsNull' | 'IsNotNull';
37
38
  /** One comparison — `<field> <operator> <value>`. */
@@ -55,7 +56,7 @@ export interface CeqlTree {
55
56
  uuId?: string;
56
57
  }
57
58
  /** Raised when an expression is not CEQL this parser can represent. */
58
- export declare class CeqlParseError extends Error {
59
+ export declare class CeqlParseError extends CompileRefusal {
59
60
  readonly expression: string;
60
61
  constructor(message: string, expression: string);
61
62
  }
package/dist/core/ceql.js CHANGED
@@ -34,11 +34,12 @@
34
34
  */
35
35
  import { stableId } from './stable-id.js';
36
36
  import { ownEntry } from './own-entry.js';
37
+ import { CompileRefusal } from './compile-refusal.js';
37
38
  /** Raised when an expression is not CEQL this parser can represent. */
38
- export class CeqlParseError extends Error {
39
+ export class CeqlParseError extends CompileRefusal {
39
40
  expression;
40
41
  constructor(message, expression) {
41
- super(message);
42
+ super('CEQL_UNPARSEABLE', message);
42
43
  this.expression = expression;
43
44
  this.name = 'CeqlParseError';
44
45
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The error every build-time refusal throws: a sentence for the author plus a
3
+ * stable `code` (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.1, #1440).
4
+ *
5
+ * The code is what tells a refusal from a bug without matching on message text:
6
+ * `thrownDiagnostic` (`cli-result.ts`) reports it under this code, so
7
+ * `compile --output json` prints `[DUP_STEP]` instead of `COMPILE_FAILED`, and
8
+ * `merge` can sort it by node. Every code is listed in `core/refusals.ts` with
9
+ * its class (lint, lowering, draft); FB017 keeps the two in step.
10
+ *
11
+ * Only an internal error — a broken SDK invariant the author cannot act on —
12
+ * stays a plain `Error`, and its message starts with `internal SDK error:`.
13
+ *
14
+ * For instance:
15
+ *
16
+ * throw new CompileRefusal('DUP_STEP', `two steps are named "notify". …`);
17
+ */
18
+ export declare class CompileRefusal extends Error {
19
+ readonly code: string;
20
+ constructor(code: string, message: string);
21
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The error every build-time refusal throws: a sentence for the author plus a
3
+ * stable `code` (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.1, #1440).
4
+ *
5
+ * The code is what tells a refusal from a bug without matching on message text:
6
+ * `thrownDiagnostic` (`cli-result.ts`) reports it under this code, so
7
+ * `compile --output json` prints `[DUP_STEP]` instead of `COMPILE_FAILED`, and
8
+ * `merge` can sort it by node. Every code is listed in `core/refusals.ts` with
9
+ * its class (lint, lowering, draft); FB017 keeps the two in step.
10
+ *
11
+ * Only an internal error — a broken SDK invariant the author cannot act on —
12
+ * stays a plain `Error`, and its message starts with `internal SDK error:`.
13
+ *
14
+ * For instance:
15
+ *
16
+ * throw new CompileRefusal('DUP_STEP', `two steps are named "notify". …`);
17
+ */
18
+ export class CompileRefusal extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(message);
22
+ // `name` stays `Error`, so a converted `throw new Error(…)` prints exactly as
23
+ // it did (`Error: …` in `String(e)` and stacks). A subclass names itself.
24
+ this.code = code;
25
+ }
26
+ }
@@ -30,6 +30,7 @@ export { FLOW_EXPR_DIALECT } from './connector-checks.js';
30
30
  import { FLOW_EXPR_DIALECT as FLOW_DIALECT } from './connector-checks.js';
31
31
  import { ownEntry } from './own-entry.js';
32
32
  import { isRecord } from './node-envelope.js';
33
+ import { CompileRefusal } from './compile-refusal.js';
33
34
  /** A required connector input left empty — the one draft code with no error class of its own. */
34
35
  export const CONNECTOR_INPUT_MISSING = 'CONNECTOR_INPUT_MISSING';
35
36
  /**
@@ -204,7 +205,7 @@ export function buildConnectorInputs(rc, inputs, opts) {
204
205
  `wrong value resolves back to just the parents. ` +
205
206
  `See the connector guide's parent-field loop.`;
206
207
  }
207
- throw new Error(`connector ${rc.nodeType}: ${errs.join('; ')}. Required fields: ${req}.${hint}`);
208
+ throw new CompileRefusal('CONNECTOR_INPUT', `connector ${rc.nodeType}: ${errs.join('; ')}. Required fields: ${req}.${hint}`);
208
209
  }
209
210
  // Every declared part, in declaration order, as the designer lists them: the
210
211
  // container part valueless, a file part with the author's value when given. A
@@ -3,6 +3,7 @@
3
3
  * the connector library from disk, so `serialize` can recognise the refusal
4
4
  * without loading `node:fs` (FB015). `core/library.ts` re-exports both.
5
5
  */
6
+ import { CompileRefusal } from './compile-refusal.js';
6
7
  /** Why `Library.resolve` refused a connector action. */
7
8
  export type ConnectorResolveCode =
8
9
  /** The library carries no such operation (or no such connector, or not at that version). */
@@ -29,7 +30,7 @@ export type ConnectorResolveCode =
29
30
  * text: `check` reports it under this code with this message, and a compile
30
31
  * that throws it carries the same code into its `--json` diagnostics (#948).
31
32
  */
32
- export declare class ConnectorResolveError extends Error {
33
+ export declare class ConnectorResolveError extends CompileRefusal {
33
34
  readonly code: ConnectorResolveCode;
34
35
  constructor(code: ConnectorResolveCode, message: string);
35
36
  }
@@ -3,6 +3,7 @@
3
3
  * the connector library from disk, so `serialize` can recognise the refusal
4
4
  * without loading `node:fs` (FB015). `core/library.ts` re-exports both.
5
5
  */
6
+ import { CompileRefusal } from './compile-refusal.js';
6
7
  /**
7
8
  * The refusal `Library.resolve` throws, with a `code` for each reason.
8
9
  *
@@ -10,11 +11,9 @@
10
11
  * text: `check` reports it under this code with this message, and a compile
11
12
  * that throws it carries the same code into its `--json` diagnostics (#948).
12
13
  */
13
- export class ConnectorResolveError extends Error {
14
- code;
14
+ export class ConnectorResolveError extends CompileRefusal {
15
15
  constructor(code, message) {
16
- super(message);
16
+ super(code, message);
17
17
  this.name = 'ConnectorResolveError';
18
- this.code = code;
19
18
  }
20
19
  }
@@ -21,6 +21,7 @@
21
21
  * arrays; `buildConnectorInputs` validates them and projects them back to the
22
22
  * registry's flat field ids when emitting Flow JSON.
23
23
  */
24
+ import { CompileRefusal } from './compile-refusal.js';
24
25
  /**
25
26
  * Brand a descriptor literal with its input/output types (zero runtime cost).
26
27
  * Used by codegen: `descriptor<CreateIssueInputs, CreateIssueOutputs>()({ … } as const)`.
@@ -49,7 +50,7 @@ export function splitConnectorNodeType(nodeType) {
49
50
  const rest = nodeType.replace(/^uipath\.connector\./, '');
50
51
  const dot = rest.lastIndexOf('.');
51
52
  if (dot <= 0)
52
- throw new Error(`invalid connector nodeType "${nodeType}" (expected uipath.connector.<key>.<action>).`);
53
+ throw new CompileRefusal('CONNECTOR_NODE_TYPE_INVALID', `invalid connector nodeType "${nodeType}" (expected uipath.connector.<key>.<action>).`);
53
54
  return { key: rest.slice(0, dot), action: rest.slice(dot + 1) };
54
55
  }
55
56
  /**
@@ -35,6 +35,7 @@
35
35
  * matches.
36
36
  */
37
37
  import { ownEntry } from './own-entry.js';
38
+ import { CompileRefusal } from './compile-refusal.js';
38
39
  /** The designer's operator vocabulary this SDK emits, keyed by the authored key. */
39
40
  export const EVENT_FILTER_OPERATORS = {
40
41
  contains: { designer: 'Contains', kind: 'function', jmes: 'contains' },
@@ -136,7 +137,7 @@ export function eventFilterProblem(read) {
136
137
  export function jmesString(value) {
137
138
  const problem = jmesStringProblem(value);
138
139
  if (problem)
139
- throw new Error(`jmesString: ${problem}`);
140
+ throw new CompileRefusal('EVENT_FILTER_BACKSLASH', `jmesString: ${problem}`);
140
141
  return `'${value.replace(/'/g, "\\'")}'`;
141
142
  }
142
143
  /** A backslash whose raw-string reading depends on the engine: before `\` or `'`, or last. */
@@ -1,83 +1,3 @@
1
- /**
2
- * How a human task leaves its node — the one model `serialize`, `check` and
3
- * `.stepSwitch` (`step-ports.ts`) all read, so the three cannot drift. They did
4
- * drift once, in the direction that matters least visibly: `check` keyed on the
5
- * `outcomePorts` FLAG while `serialize` was about to infer it, which would have
6
- * made `check` refuse per-outcome wiring that `compile` then emitted.
7
- *
8
- * **The product's rule, which the model follows.** On the base node
9
- * (`uipath.human-in-the-loop`, `type: 'quick'`) and on the quick-form variant,
10
- * the designer draws ONE handle per entry in `inputs.schema.outcomes`, named
11
- * `outcome-<id>`, whatever the node's `typeVersion` — and draws the manifest's
12
- * static `completed` handle only when there are no outcomes
13
- * (flow-workbench `build-handle-customization.ts:102-124`; it derives them on
14
- * every render and deliberately never saves them). A leftover `completed` edge
15
- * on such a node is a stale placeholder: since flow-workbench #3512 the load
16
- * path and the converter remap it to the PRIMARY outcome
17
- * (`hitl-edge-reconciliation.ts:52-63`), so every other outcome loses its path at
18
- * run time. So a task with outcomes never leaves on `completed` here. The two
19
- * app-backed variants are different: action-app and document-validation read
20
- * their outcomes from `inputs.app`, and `completed` really is the only handle
21
- * they render (`hitl-edge-reconciliation.ts:68-74`).
22
- *
23
- * (This replaces an older premise — "the definition VERSION selects the port
24
- * shape, and the variants have no per-outcome version" (#729). The manifests do
25
- * declare only `completed` statically; the designer just never reads that list
26
- * for these two node types once outcomes exist.)
27
- *
28
- * **Three shapes**, and what `.step()` after the task means in each — ".step()
29
- * continues from the task's tails":
30
- *
31
- * - `per-outcome`: the task ROUTES per outcome. One tail, the PRIMARY (first)
32
- * outcome's `outcome-<id>`; the author routes the rest with `.stepToList` /
33
- * `.stepToRef`, or all of them as `.stepSwitch` arms.
34
- * - `every-outcome`: the task does NOT route per outcome. One tail PER outcome,
35
- * so `.step()` continues every outcome to the next step ("fan-out") — the
36
- * decision reaches the flow as data, `out('<step>', 'Action')`, in the handles
37
- * the product draws. An `action: 'End'` outcome is the exception: the flow
38
- * runtime never reads `action` (flow-workbench `hitl-serialization.ts` does
39
- * not; only the agent storage projection does), so the graph is the only thing
40
- * that can end the run there, and `serialize` gives it an End node.
41
- * - `placeholder`: `completed` — action-app, document-validation, and a task
42
- * with no outcomes.
43
- *
44
- * **More than one outcome infers `per-outcome`** on the base node (#739): a task
45
- * that publishes a choice and then routes every button the same way was the
46
- * modelling error behind flow-builder-sdk#735, and a warning alone did not
47
- * change what agents emitted. Five things keep a base or quick-form task on
48
- * `every-outcome` instead, in this order:
49
- *
50
- * 1. `outcomePorts: false` — the explicit "decision as data" choice.
51
- * 2. `variant: 'quick-form'`. Not a product limit — quick-form draws the same
52
- * per-outcome handles — but its authors write `.step()` or a `.switch` on the
53
- * Action after it and mean "every outcome continues" (every quick-form source
54
- * in the 09-23 baseline does), so inferring primary-only there would strand
55
- * the other outcomes. It routes per outcome when asked: `outcomePorts: true`,
56
- * or `.stepSwitch`. Its definition version never moves.
57
- * 3. An explicit `{ version }` pin. `decompile` pins every node and writes
58
- * `outcomePorts: true` whenever a task's outcomes lead to different places, so
59
- * a pinned task that reaches `.step()` without the flag is a round-tripped
60
- * artifact whose outcomes all go to one step, or a legacy lone `completed`
61
- * edge, which meant "every outcome continues" before #3512. Both are this
62
- * shape. The pin also wins the version: `outcomePorts: true` on a pinned 1.0
63
- * routes per outcome on the 1.0 node, which `flow validate` accepts.
64
- * 4. A single outcome — an acknowledgement, not a choice.
65
- * 5. Every outcome carrying `action: 'End'` — there is no continuation to
66
- * distinguish, and each outcome gets its End.
67
- *
68
- * `exposeError: true` or `outcomePorts: true` select `per-outcome` on every node
69
- * type, including the two app-backed variants, so that `serialize` and `check`
70
- * refuse them there with their own message rather than silently ignoring them.
71
- *
72
- * **`legacyCompleted: true`** is the one way to keep a task with outcomes on the
73
- * `completed` handle: the `placeholder` shape, marked `legacyCompleted`. It
74
- * exists so a file saved before flow-workbench #3512, whose task leaves on one
75
- * `completed` edge, round-trips through decompile and compile unchanged
76
- * (flow-builder-sdk#1197). It is the file's shape, not one to author: the
77
- * product runs that edge as the PRIMARY outcome only, and `check` says so
78
- * (HITL_LEGACY_COMPLETED). `serialize`'s edge guard below exempts exactly the
79
- * nodes that asked for it.
80
- */
81
1
  /**
82
2
  * The fields of a human task's inputs the routing model reads — structural, so
83
3
  * this module stays in `core/` while the authoring type (`HitlInputs`) belongs
@@ -155,7 +75,9 @@ type EdgeLike = {
155
75
  sourcePort?: unknown;
156
76
  };
157
77
  /**
158
- * Why an edge on `port` cannot leave `node`, or `undefined` when it can.
78
+ * Why an edge on `port` cannot leave `node` — the code and the sentence — or
79
+ * `undefined` when it can. The code is `check`'s for the same edge, so both
80
+ * call sites throw what this returns.
159
81
  *
160
82
  * Only the two node types whose handles the product derives from the outcomes
161
83
  * are judged, and only once they HAVE outcomes: then `completed` is a handle
@@ -165,7 +87,10 @@ type EdgeLike = {
165
87
  * bare `completed` never is, unless the task asked for it with
166
88
  * `legacyCompleted: true` (`legacyCompleted` here).
167
89
  */
168
- export declare function hitlExitViolation(node: NodeLike, port: string, legacyCompleted?: boolean): string | undefined;
90
+ export declare function hitlExitViolation(node: NodeLike, port: string, legacyCompleted?: boolean): {
91
+ code: 'HITL_COMPLETED_PORT_GONE' | 'HITL_OUTCOME_PORT_UNKNOWN';
92
+ message: string;
93
+ } | undefined;
169
94
  /**
170
95
  * The post-emit half of the guard: every edge of an emitted scope, whatever
171
96
  * path produced it (a tail, a `.stepToList` seed, a parked `.stepToRef`), is held
@@ -1,3 +1,84 @@
1
+ /**
2
+ * How a human task leaves its node — the one model `serialize`, `check` and
3
+ * `.stepSwitch` (`step-ports.ts`) all read, so the three cannot drift. They did
4
+ * drift once, in the direction that matters least visibly: `check` keyed on the
5
+ * `outcomePorts` FLAG while `serialize` was about to infer it, which would have
6
+ * made `check` refuse per-outcome wiring that `compile` then emitted.
7
+ *
8
+ * **The product's rule, which the model follows.** On the base node
9
+ * (`uipath.human-in-the-loop`, `type: 'quick'`) and on the quick-form variant,
10
+ * the designer draws ONE handle per entry in `inputs.schema.outcomes`, named
11
+ * `outcome-<id>`, whatever the node's `typeVersion` — and draws the manifest's
12
+ * static `completed` handle only when there are no outcomes
13
+ * (flow-workbench `build-handle-customization.ts:102-124`; it derives them on
14
+ * every render and deliberately never saves them). A leftover `completed` edge
15
+ * on such a node is a stale placeholder: since flow-workbench #3512 the load
16
+ * path and the converter remap it to the PRIMARY outcome
17
+ * (`hitl-edge-reconciliation.ts:52-63`), so every other outcome loses its path at
18
+ * run time. So a task with outcomes never leaves on `completed` here. The two
19
+ * app-backed variants are different: action-app and document-validation read
20
+ * their outcomes from `inputs.app`, and `completed` really is the only handle
21
+ * they render (`hitl-edge-reconciliation.ts:68-74`).
22
+ *
23
+ * (This replaces an older premise — "the definition VERSION selects the port
24
+ * shape, and the variants have no per-outcome version" (#729). The manifests do
25
+ * declare only `completed` statically; the designer just never reads that list
26
+ * for these two node types once outcomes exist.)
27
+ *
28
+ * **Three shapes**, and what `.step()` after the task means in each — ".step()
29
+ * continues from the task's tails":
30
+ *
31
+ * - `per-outcome`: the task ROUTES per outcome. One tail, the PRIMARY (first)
32
+ * outcome's `outcome-<id>`; the author routes the rest with `.stepToList` /
33
+ * `.stepToRef`, or all of them as `.stepSwitch` arms.
34
+ * - `every-outcome`: the task does NOT route per outcome. One tail PER outcome,
35
+ * so `.step()` continues every outcome to the next step ("fan-out") — the
36
+ * decision reaches the flow as data, `out('<step>', 'Action')`, in the handles
37
+ * the product draws. An `action: 'End'` outcome is the exception: the flow
38
+ * runtime never reads `action` (flow-workbench `hitl-serialization.ts` does
39
+ * not; only the agent storage projection does), so the graph is the only thing
40
+ * that can end the run there, and `serialize` gives it an End node.
41
+ * - `placeholder`: `completed` — action-app, document-validation, and a task
42
+ * with no outcomes.
43
+ *
44
+ * **More than one outcome infers `per-outcome`** on the base node (#739): a task
45
+ * that publishes a choice and then routes every button the same way was the
46
+ * modelling error behind flow-builder-sdk#735, and a warning alone did not
47
+ * change what agents emitted. Five things keep a base or quick-form task on
48
+ * `every-outcome` instead, in this order:
49
+ *
50
+ * 1. `outcomePorts: false` — the explicit "decision as data" choice.
51
+ * 2. `variant: 'quick-form'`. Not a product limit — quick-form draws the same
52
+ * per-outcome handles — but its authors write `.step()` or a `.switch` on the
53
+ * Action after it and mean "every outcome continues" (every quick-form source
54
+ * in the 09-23 baseline does), so inferring primary-only there would strand
55
+ * the other outcomes. It routes per outcome when asked: `outcomePorts: true`,
56
+ * or `.stepSwitch`. Its definition version never moves.
57
+ * 3. An explicit `{ version }` pin. `decompile` pins every node and writes
58
+ * `outcomePorts: true` whenever a task's outcomes lead to different places, so
59
+ * a pinned task that reaches `.step()` without the flag is a round-tripped
60
+ * artifact whose outcomes all go to one step, or a legacy lone `completed`
61
+ * edge, which meant "every outcome continues" before #3512. Both are this
62
+ * shape. The pin also wins the version: `outcomePorts: true` on a pinned 1.0
63
+ * routes per outcome on the 1.0 node, which `flow validate` accepts.
64
+ * 4. A single outcome — an acknowledgement, not a choice.
65
+ * 5. Every outcome carrying `action: 'End'` — there is no continuation to
66
+ * distinguish, and each outcome gets its End.
67
+ *
68
+ * `exposeError: true` or `outcomePorts: true` select `per-outcome` on every node
69
+ * type, including the two app-backed variants, so that `serialize` and `check`
70
+ * refuse them there with their own message rather than silently ignoring them.
71
+ *
72
+ * **`legacyCompleted: true`** is the one way to keep a task with outcomes on the
73
+ * `completed` handle: the `placeholder` shape, marked `legacyCompleted`. It
74
+ * exists so a file saved before flow-workbench #3512, whose task leaves on one
75
+ * `completed` edge, round-trips through decompile and compile unchanged
76
+ * (flow-builder-sdk#1197). It is the file's shape, not one to author: the
77
+ * product runs that edge as the PRIMARY outcome only, and `check` says so
78
+ * (HITL_LEGACY_COMPLETED). `serialize`'s edge guard below exempts exactly the
79
+ * nodes that asked for it.
80
+ */
81
+ import { CompileRefusal } from './compile-refusal.js';
1
82
  /**
2
83
  * An outcome's stable id — the slug that names its `outcome-<id>` port.
3
84
  *
@@ -90,7 +171,9 @@ function outcomeIdsOf(node) {
90
171
  return outcomes.map((o) => o?.id).filter((id) => typeof id === 'string' && id !== '');
91
172
  }
92
173
  /**
93
- * Why an edge on `port` cannot leave `node`, or `undefined` when it can.
174
+ * Why an edge on `port` cannot leave `node` — the code and the sentence — or
175
+ * `undefined` when it can. The code is `check`'s for the same edge, so both
176
+ * call sites throw what this returns.
94
177
  *
95
178
  * Only the two node types whose handles the product derives from the outcomes
96
179
  * are judged, and only once they HAVE outcomes: then `completed` is a handle
@@ -110,12 +193,18 @@ export function hitlExitViolation(node, port, legacyCompleted = false) {
110
193
  if (port === 'completed') {
111
194
  if (legacyCompleted)
112
195
  return undefined;
113
- return `human task "${String(node.id)}" has outcomes (${declared}), so the product draws no `
114
- + `'completed' handle on it — an edge there is remapped to the primary outcome and every other `
115
- + `outcome loses its path.`;
196
+ return {
197
+ code: 'HITL_COMPLETED_PORT_GONE',
198
+ message: `human task "${String(node.id)}" has outcomes (${declared}), so the product draws no `
199
+ + `'completed' handle on it — an edge there is remapped to the primary outcome and every other `
200
+ + `outcome loses its path.`,
201
+ };
116
202
  }
117
203
  if (port.startsWith('outcome-') && !ids.includes(port.slice('outcome-'.length))) {
118
- return `human task "${String(node.id)}" has no outcome for port "${port}" — it declares ${declared}.`;
204
+ return {
205
+ code: 'HITL_OUTCOME_PORT_UNKNOWN',
206
+ message: `human task "${String(node.id)}" has no outcome for port "${port}" — it declares ${declared}.`,
207
+ };
119
208
  }
120
209
  return undefined;
121
210
  }
@@ -135,6 +224,6 @@ export function assertHitlExitInvariant(nodes, edges, legacyCompleted = new Set(
135
224
  continue;
136
225
  const why = hitlExitViolation(node, String(e.sourcePort), legacyCompleted.has(String(e.sourceNodeId)));
137
226
  if (why)
138
- throw new Error(`serialize: ${why}`);
227
+ throw new CompileRefusal(why.code, `serialize: ${why.message}`);
139
228
  }
140
229
  }
@@ -14,6 +14,7 @@ import { lookupSpecOf } from './lookups.js';
14
14
  import { loadByoaLedger, loadInvariantCollections, loadLookupResolutions } from './lookup-store.js';
15
15
  import { ownEntry } from './own-entry.js';
16
16
  import { ConnectorResolveError } from './connector-resolve-error.js';
17
+ import { CompileRefusal } from './compile-refusal.js';
17
18
  export { ConnectorResolveError } from './connector-resolve-error.js';
18
19
  export class Library {
19
20
  layers = [];
@@ -59,7 +60,7 @@ export class Library {
59
60
  const idx = libraryPath(dir, 'index.json');
60
61
  const entries = libraryEntries(read, idx);
61
62
  if (entries === undefined)
62
- throw new Error(`connector library not found at ${idx}. `
63
+ throw new CompileRefusal('LIBRARY_NOT_FOUND', `connector library not found at ${idx}. `
63
64
  + (read === readTextFromDisk
64
65
  ? `Run \`${PULL_COMMAND}\`, or point --library / $FLOW_SDK_LIBRARY_JSON at a library-json dir.`
65
66
  : 'The reader passed to Library has no such file.'));
@@ -292,7 +293,7 @@ export class Library {
292
293
  };
293
294
  }
294
295
  }
295
- throw new Error(`connector activity not found in library: ${wire.connectorKey} ${wire.httpMethod ?? '?'} ${wire.endpoint ?? '?'}`);
296
+ throw new CompileRefusal('CONNECTOR_NOT_IN_LIBRARY', `connector activity not found in library: ${wire.connectorKey} ${wire.httpMethod ?? '?'} ${wire.endpoint ?? '?'}`);
296
297
  }
297
298
  /**
298
299
  * Resolve a connector EVENT: the START trigger
@@ -393,7 +394,7 @@ export class Library {
393
394
  };
394
395
  }
395
396
  }
396
- throw new Error(`connector ${kind} event not found in library: ${wire.connectorKey} `
397
+ throw new CompileRefusal('EVENT_UNKNOWN', `connector ${kind} event not found in library: ${wire.connectorKey} `
397
398
  + `${wire.eventOperation ?? '?'} ${wire.objectName ?? '?'}`);
398
399
  }
399
400
  /**