@uipath/maestro-builder-sdk 6.14.5 → 6.15.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 (41) hide show
  1. package/dist/api-index.md +71 -71
  2. package/dist/api-members.md +324 -323
  3. package/dist/bpmn/bpmn-sdk.d.ts +16 -0
  4. package/dist/bpmn/bpmn-sdk.js +2 -0
  5. package/dist/bpmn/check-cli.js +22 -5
  6. package/dist/bpmn/check.d.ts +15 -1
  7. package/dist/bpmn/check.js +69 -2
  8. package/dist/bpmn/compile-cli.js +24 -14
  9. package/dist/bpmn/connector.js +29 -14
  10. package/dist/bpmn/intsvc.js +5 -2
  11. package/dist/bpmn/serialize.d.ts +2 -0
  12. package/dist/bpmn/serialize.js +1 -1
  13. package/dist/case/compile-cli.js +6 -9
  14. package/dist/case/serialize.js +1 -1
  15. package/dist/check-cli.js +6 -35
  16. package/dist/check.d.ts +16 -148
  17. package/dist/check.js +33 -709
  18. package/dist/cli/commands/bpmn.js +15 -0
  19. package/dist/cli-library.d.ts +6 -25
  20. package/dist/cli-library.js +6 -73
  21. package/dist/core/cli-library.d.ts +47 -0
  22. package/dist/core/cli-library.js +150 -0
  23. package/dist/core/connector-checks.d.ts +226 -0
  24. package/dist/core/connector-checks.js +518 -0
  25. package/dist/core/connector-inputs.d.ts +80 -0
  26. package/dist/core/connector-inputs.js +453 -0
  27. package/dist/core/event-checks.d.ts +137 -0
  28. package/dist/core/event-checks.js +321 -0
  29. package/dist/decompile.js +1 -1
  30. package/dist/registry/generators/markdown/sections/example.d.ts +7 -1
  31. package/dist/registry/generators/markdown/sections/example.js +70 -5
  32. package/dist/registry/generators/markdown/sections/lookups.js +29 -8
  33. package/dist/registry/prepare/messages.js +6 -1
  34. package/dist/registry/prepare/source-import.d.ts +3 -3
  35. package/dist/registry/prepare/source-import.js +15 -4
  36. package/dist/registry/prepare/source-labels.js +31 -4
  37. package/dist/serialize.d.ts +11 -11
  38. package/dist/serialize.js +27 -420
  39. package/package.json +2 -2
  40. /package/dist/{event-filters.d.ts → core/event-filters.d.ts} +0 -0
  41. /package/dist/{event-filters.js → core/event-filters.js} +0 -0
@@ -0,0 +1,453 @@
1
+ /**
2
+ * core/connector-inputs — a connector operation's authored inputs, validated
3
+ * against its declared field list and ROUTED to the wire slots the platform reads.
4
+ *
5
+ * Shared by every family that authors a connector activity. It was Flow's, inside
6
+ * `serialize.ts`, and BPMN had a fourteen-line substitute: an input the library
7
+ * did not declare went silently to the body, a missing required field was never
8
+ * reported, and a `lookup()` token was refused outright because nothing here
9
+ * read `connectors-local/resolutions.json`. That last one is why
10
+ * `skill-bpmn-paginated-reference-lookup` could resolve a Slack channel id and
11
+ * then emit a process that did not carry it.
12
+ *
13
+ * Two things differ between families and are injected rather than assumed:
14
+ *
15
+ * - `dialect` — which values are opaque (a Flow `Expr`, a BPMN `=`-expression).
16
+ * An opaque value is not type-checked against the field's declared type,
17
+ * because nothing here can evaluate it.
18
+ * - `render` — how an authored value becomes a wire value. Flow runs `renderDeep`
19
+ * to lower `Expr` objects into `$vars.…` source; BPMN's values are already
20
+ * plain, so it passes them through.
21
+ *
22
+ * The ROUTING is the library's business either way: a field's location is a
23
+ * property of the operation, not of the family emitting it.
24
+ */
25
+ import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './lookups.js';
26
+ import { prepareCommand } from './cli-spelling.js';
27
+ /** Flow's dialect, re-exported so `serialize.ts` need not reach past this module. */
28
+ export { FLOW_EXPR_DIALECT } from './connector-checks.js';
29
+ import { FLOW_EXPR_DIALECT as FLOW_DIALECT } from './connector-checks.js';
30
+ /**
31
+ * Validate a connector's activity inputs against its declared field list and
32
+ * return them ROUTED to the wire slots the platform reads. Catches the three
33
+ * connector-input mistakes — missing required, unknown key, type mismatch — at
34
+ * compile, where the author can act on them.
35
+ *
36
+ * Generated descriptors accept natural nested objects and arrays; the legacy
37
+ * string form also accepts exact flat registry field ids. Routing remains the
38
+ * library's business because a field's location is a property of the operation.
39
+ * Each projected registry field lands in the slot its declaration names:
40
+ *
41
+ * ```jsonc
42
+ * // connector('uipath-atlassian-jira','get-issue', { issueId: …, project: 'IN', issuetype: 'Task' })
43
+ * "inputs": { "detail": {
44
+ * "endpoint": "/curated_get_issue/{issueId}",
45
+ * "pathParameters": { "issueId": "IN-28" },
46
+ * "queryParameters": { "project": "IN", "issuetype": "Task" },
47
+ * "bodyParameters": {} } }
48
+ * ```
49
+ *
50
+ * That is the platform's own encoding, measured on the deployed corpus: 1,283
51
+ * connector nodes in the nightly artifacts, 384 carrying `pathParameters` and
52
+ * 576 carrying `queryParameters`, always as a FLAT map keyed by the parameter's
53
+ * DECLARED name (`send_as`, `issuetype`, `channel_id` keep their spelling — no
54
+ * camelCasing) and always as a SIBLING of `bodyParameters` rather than as a
55
+ * location-tagged entry inside it.
56
+ */
57
+ /** For a missing required lookup field: what it sends and how to resolve it. Empty for a plain field. */
58
+ function missingLookupHint(field, lookup, spec, descriptors) {
59
+ if (!lookup)
60
+ return '';
61
+ const target = `${lookup.objectName ?? lookup.path}.${lookup.value}`;
62
+ const alias = Object.keys(lookup.aliases)[0];
63
+ const by = lookup.by[0] ?? 'id';
64
+ // `<Descriptor>` is a placeholder only a Flow author can fill, from a
65
+ // generated `connectors/<key>.ts`. BPMN imports none, so it gets the string
66
+ // form with the real key and action — which is the call that compiles there.
67
+ const call = descriptors
68
+ ? (alias
69
+ ? `lookup(<Descriptor>, '${field}').${alias}('<value>')`
70
+ : `lookup(<Descriptor>, '${field}').by('${by}', '<value>')`)
71
+ : `lookup('${spec.key ?? '<key>'}', '${spec.action ?? '<action>'}', '${field}').by('${by}', '<value>')`;
72
+ return ` — a lookup id (\`${target}\`), not a name or address: ${call}`;
73
+ }
74
+ /**
75
+ * What `compile` would refuse these connector inputs with, or `undefined` when it
76
+ * would accept them — the SAME code path `buildConnectorInputs` runs at emit time,
77
+ * so `check` and `compile` cannot disagree about a missing required field, an
78
+ * unknown or mistyped input, or a container part passed as a value. Unresolved
79
+ * lookups are the one thing skipped: `check` already reports them as
80
+ * LOOKUP_UNRESOLVED with the resolution recipe.
81
+ */
82
+ export function connectorInputErrors(rc, inputs, resolutions = {}, lookups = {}, wire = { dialect: FLOW_DIALECT }) {
83
+ try {
84
+ buildConnectorInputs(rc, inputs, { ...wire, resolutions, lookups, unresolvedLookups: 'ignore' });
85
+ return undefined;
86
+ }
87
+ catch (e) {
88
+ return e instanceof Error ? e.message : String(e);
89
+ }
90
+ }
91
+ export function buildConnectorInputs(rc, inputs, opts) {
92
+ const { dialect, resolutions = {}, lookups = {}, unresolvedLookups } = opts;
93
+ const render = opts.render ?? ((value) => value);
94
+ const policy = { unresolvedLookups };
95
+ const fields = (rc.fields ?? []);
96
+ const byName = new Map(fields.map((f) => [f.name, f]));
97
+ const errs = validateNestedConnectorKeys(fields, inputs, byName, dialect);
98
+ const routed = {
99
+ bodyParameters: {}, pathParameters: {}, queryParameters: {},
100
+ };
101
+ // The author's values for FILE parts, by part name. The part LIST itself comes
102
+ // from the library (`rc.multipartParts`): a string-typed part is a transport
103
+ // container the runtime composes from `bodyParameters`, so it is listed without
104
+ // a value and is never an author input — see `readFields` in core/library.ts.
105
+ const partValues = new Map();
106
+ for (const f of fields) {
107
+ const flat = Object.prototype.hasOwnProperty.call(inputs, f.name);
108
+ let nested = flat
109
+ ? { found: true, value: inputs[f.name], projectedArrays: 0 }
110
+ : readConnectorPath(inputs, f.name, dialect);
111
+ if (!nested.found) {
112
+ // A required LOOKUP field is missing far more often because the author does
113
+ // not know what to put in it than because they forgot it exists: name the
114
+ // id it sends and the helper that finds one, so "(string)" is not read as
115
+ // "any string" — an e-mail into `fields.reporter.id` is exactly that misreading.
116
+ if (f.required)
117
+ errs.push(`missing required input "${f.name}" (${f.type ?? 'value'})${missingLookupHint(f.name, lookups[f.name], rc, dialect.descriptors)}`);
118
+ continue;
119
+ }
120
+ // An unresolved lookup is swapped for its recorded id, or reported. It is
121
+ // NOT rendered as a placeholder: emitting something for a value nobody
122
+ // resolved would produce a flow that compiles and then talks to the wrong
123
+ // record, which is exactly the failure the lookup surface exists to remove.
124
+ //
125
+ // A token is swapped wherever the field's value can hold one: the whole
126
+ // value, or an element of an array at any depth. A multiselect is
127
+ // `users: [lookup(…), lookup(…)]` — the form `ConnectorLookupValue`'s array
128
+ // branch types and the generated `## Example` prints — and until this swapped
129
+ // only the whole-value case, so every token inside the array reached
130
+ // validation as an object ("expected string, got object") with every lookup
131
+ // recorded. An unresolved element under 'ignore' becomes `null`, which
132
+ // validation skips, so `check` still type-checks the resolved siblings.
133
+ const unresolved = [];
134
+ const swapped = swapLookupTokens(nested.value, resolutions, unresolved);
135
+ if (unresolved.length) {
136
+ if (policy.unresolvedLookups !== 'ignore') {
137
+ for (const token of unresolved)
138
+ errs.push(unresolvedLookupMessage(token));
139
+ continue;
140
+ }
141
+ if (isLookupToken(nested.value))
142
+ continue;
143
+ }
144
+ nested = { ...nested, value: swapped };
145
+ // A FLAT key whose wire name ends in `[*]` names one array: an array value
146
+ // there is checked element by element, as the nested form is. A flat scalar
147
+ // keeps depth 0 and is emitted as the scalar it is — the product wraps it
148
+ // into a one-element list (flow-v1 `convertDotNotationToNested`).
149
+ const depth = flat
150
+ ? (f.name.endsWith('[*]') && Array.isArray(nested.value) ? 1 : 0)
151
+ : nested.projectedArrays;
152
+ validateConnectorFieldValue(f, nested.value, depth, errs, dialect);
153
+ if (f.location === 'multipart') {
154
+ partValues.set(f.name, render(nested.value));
155
+ continue;
156
+ }
157
+ const slot = SLOT_FOR_LOCATION[f.location ?? 'body'] ?? 'bodyParameters';
158
+ routed[slot][f.name] = render(nested.value);
159
+ }
160
+ if (errs.length) {
161
+ // Name the location of each required field: a "missing required input" on a
162
+ // path parameter reads very differently from one on a body field, and the
163
+ // author's fix is the same either way (add it to the same flat `inputs` map).
164
+ const req = fields
165
+ .filter((f) => f.required)
166
+ .map((f) => (f.location && f.location !== 'body' ? `${f.name} (${f.location})` : f.name))
167
+ .join(', ') || '(none required)';
168
+ // Phase 1 propagates connector discovery capability to every activity. When
169
+ // one of those activities rejects an unknown field, point at the live-schema
170
+ // preparation path; fully static activities on the same connector still
171
+ // compile normally and never reach this hint.
172
+ const schemaDynamic = !!rc.entry?.runtime?.requiresConnectionForSchema;
173
+ const hasUnknown = errs.some((e) => e.startsWith('unknown input'));
174
+ let hint = '';
175
+ if (schemaDynamic && hasUnknown) {
176
+ const prefix = `uipath.connector.${rc.connectorKey}.`;
177
+ const action = String(rc.nodeType).startsWith(prefix)
178
+ ? String(rc.nodeType).slice(prefix.length)
179
+ // `.pop()` on a split is never empty at runtime, but its type admits
180
+ // `undefined`; the node type itself is the only sane fallback.
181
+ : String(rc.nodeType).split('.').pop() ?? String(rc.nodeType);
182
+ hint =
183
+ ` This connector supports connection-specific object or field discovery, and the ` +
184
+ `static library does not contain this input. Run ` +
185
+ `\`${prepareCommand(rc.connectorKey, action)}\` ` +
186
+ `to load them ` +
187
+ `(writes ./.flow-sdk/connectors-local/, which the compiler auto-detects). If that still ` +
188
+ `does not carry this input, the operation resolves its fields from the VALUES of ` +
189
+ `parent inputs: resolve every parent and re-run with ` +
190
+ `\`-f <parent-field>=<value>\` for each — too few parents is refused, and a ` +
191
+ `wrong value resolves back to just the parents. ` +
192
+ `See the connector guide's parent-field loop.`;
193
+ }
194
+ throw new Error(`connector ${rc.nodeType}: ${errs.join('; ')}. Required fields: ${req}.${hint}`);
195
+ }
196
+ // Every declared part, in declaration order, as the designer lists them: the
197
+ // container part valueless, a file part with the author's value when given. A
198
+ // test double that resolves no `multipartParts` falls back to its multipart
199
+ // fields, which are all file parts by construction.
200
+ const declaredParts = rc.multipartParts
201
+ ?? fields.filter((f) => f.location === 'multipart').map((f) => ({ name: f.name, dataType: f.type ?? 'string' }));
202
+ const multipartParameters = declaredParts.map((part) => (partValues.has(part.name)
203
+ ? { name: part.name, dataType: part.dataType, value: partValues.get(part.name) }
204
+ : { name: part.name, dataType: part.dataType }));
205
+ // A FIXED slot order, so a recompile is byte-identical no matter which order
206
+ // the author listed the inputs in.
207
+ const slots = multipartParameters.length === 0 || Object.keys(routed.bodyParameters).length > 0
208
+ ? { bodyParameters: routed.bodyParameters }
209
+ : {};
210
+ if (Object.keys(routed.pathParameters).length)
211
+ slots.pathParameters = routed.pathParameters;
212
+ if (Object.keys(routed.queryParameters).length)
213
+ slots.queryParameters = routed.queryParameters;
214
+ if (multipartParameters.length) {
215
+ slots.multipartParameters = multipartParameters;
216
+ slots.inputMetadata = { type: 'multipart', multipart: { bodyFieldName: multipartBodyFieldName(declaredParts) } };
217
+ }
218
+ return slots;
219
+ }
220
+ /**
221
+ * `value` with every lookup token swapped for its recorded id: the value itself,
222
+ * or the elements of an array at any depth. An unrecorded token is pushed to
223
+ * `unresolved` and stands as `null`, so an array keeps its length.
224
+ *
225
+ * Objects are NOT descended: a lookup's place is a field's value or an element
226
+ * of an array field (`ConnectorLookupValue`), and a nested path such as
227
+ * `contacts[*].id` reaches here already projected to its leaf array. A token
228
+ * anywhere else is refused by {@link renderDeep}.
229
+ */
230
+ function swapLookupTokens(value, resolutions, unresolved) {
231
+ if (isLookupToken(value)) {
232
+ const resolved = resolvedValue(value, resolutions);
233
+ if (resolved === undefined) {
234
+ unresolved.push(value);
235
+ return null;
236
+ }
237
+ return resolved;
238
+ }
239
+ if (Array.isArray(value))
240
+ return value.map((item) => swapLookupTokens(item, resolutions, unresolved));
241
+ return value;
242
+ }
243
+ /**
244
+ * `inputMetadata.multipart.bodyFieldName` names the CONTAINER part — the one the
245
+ * runtime composes from `bodyParameters`. Measured on the designer's own nodes in
246
+ * the eval archive: `body` ×413, `RagRequest` ×1 (GenAI `content-generation`, whose
247
+ * string part is named that), and `body` on 91 of 93 file-only operations. A
248
+ * mis-named container is fatal at dispatch — hardcoding `body` for GenAI made
249
+ * Integration Service answer `400 The request body is empty` (verified live
250
+ * 2026-09-02) — so it is the string part's own name, with `body` only as the
251
+ * default for an operation that has no string part.
252
+ */
253
+ export function multipartBodyFieldName(parts) {
254
+ const containers = parts.filter((p) => p.dataType.toLowerCase() !== 'file');
255
+ return containers.length === 1 ? containers[0].name : 'body';
256
+ }
257
+ const SLOT_FOR_LOCATION = {
258
+ body: 'bodyParameters',
259
+ query: 'queryParameters',
260
+ path: 'pathParameters',
261
+ };
262
+ function connectorPath(name) {
263
+ return name.split('.').map((raw) => ({
264
+ key: raw.endsWith('[*]') ? raw.slice(0, -3) : raw,
265
+ array: raw.endsWith('[*]'),
266
+ }));
267
+ }
268
+ /** Read a natural nested authoring value using a registry path such as
269
+ * `contacts[*].id`. Wildcards project one value per array element so the Flow
270
+ * bodyParameters map can retain the registry's exact field ids. */
271
+ function readConnectorPath(inputs, name, dialect) {
272
+ const parts = connectorPath(name);
273
+ const read = (value, index) => {
274
+ if (index >= parts.length)
275
+ return { found: true, value, projectedArrays: 0 };
276
+ if (!value || typeof value !== 'object' || dialect.isOpaque(value) || Array.isArray(value))
277
+ return { found: false, projectedArrays: 0 };
278
+ const part = parts[index];
279
+ const object = value;
280
+ if (!Object.prototype.hasOwnProperty.call(object, part.key))
281
+ return { found: false, projectedArrays: 0 };
282
+ const next = object[part.key];
283
+ if (!part.array)
284
+ return read(next, index + 1);
285
+ if (!Array.isArray(next))
286
+ return { found: false, projectedArrays: 0 };
287
+ if (index === parts.length - 1)
288
+ return { found: true, value: next, projectedArrays: 1 };
289
+ const projected = next.map((item) => read(item, index + 1));
290
+ if (!projected.some((item) => item.found))
291
+ return { found: false, projectedArrays: 0 };
292
+ return {
293
+ found: true,
294
+ value: projected.map((item) => item.found ? item.value : null),
295
+ projectedArrays: 1 + Math.max(0, ...projected.map((item) => item.projectedArrays)),
296
+ };
297
+ };
298
+ return read(inputs, 0);
299
+ }
300
+ function buildConnectorInputTrie(fields) {
301
+ const root = { children: new Map(), array: false };
302
+ for (const field of fields) {
303
+ let cursor = root;
304
+ for (const part of connectorPath(field.name)) {
305
+ let child = cursor.children.get(part.key);
306
+ if (!child) {
307
+ child = { children: new Map(), array: part.array };
308
+ cursor.children.set(part.key, child);
309
+ }
310
+ child.array ||= part.array;
311
+ cursor = child;
312
+ }
313
+ cursor.terminal = field;
314
+ }
315
+ return root;
316
+ }
317
+ /** Reject nested typos with the same unknown-key message as flat keys. */
318
+ function validateNestedConnectorKeys(fields, inputs, exact, dialect) {
319
+ const root = buildConnectorInputTrie(fields);
320
+ const errs = [];
321
+ const visit = (value, node, path) => {
322
+ if (dialect.isOpaque(value))
323
+ return;
324
+ if (node.array) {
325
+ if (!Array.isArray(value)) {
326
+ errs.push(`input "${path}" expected array, got ${typeof value}`);
327
+ return;
328
+ }
329
+ for (const item of value)
330
+ visit(item, { ...node, array: false }, `${path}[*]`);
331
+ return;
332
+ }
333
+ // An opaque object/array terminal owns its complete value; there is no
334
+ // child metadata with which to validate its internal keys.
335
+ if (node.terminal && node.children.size === 0)
336
+ return;
337
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
338
+ if (node.children.size > 0)
339
+ errs.push(`input "${path}" expected object, got ${Array.isArray(value) ? 'array' : typeof value}`);
340
+ return;
341
+ }
342
+ for (const [key, childValue] of Object.entries(value)) {
343
+ const child = node.children.get(key);
344
+ const childPath = path ? `${path}.${key}` : key;
345
+ if (!child) {
346
+ const near = nearestField(childPath, fields.map((f) => f.name));
347
+ errs.push(`unknown input "${childPath}"${near ? ` — did you mean "${near}"?` : ''}`);
348
+ }
349
+ else {
350
+ visit(childValue, child, childPath);
351
+ }
352
+ }
353
+ };
354
+ for (const [key, value] of Object.entries(inputs)) {
355
+ if (exact.has(key))
356
+ continue; // backwards-compatible flat authoring
357
+ const node = root.children.get(key);
358
+ if (!node) {
359
+ const near = nearestField(key, [...exact.keys()]);
360
+ errs.push(`unknown input "${key}"${near ? ` — did you mean "${near}"?` : ''}`);
361
+ }
362
+ else {
363
+ visit(value, node, key);
364
+ }
365
+ }
366
+ return errs;
367
+ }
368
+ function validateConnectorFieldValue(field, value, projectedArrays, errs, dialect) {
369
+ if (projectedArrays > 0) {
370
+ if (!Array.isArray(value)) {
371
+ errs.push(`input "${field.name}" expected array projection, got ${typeof value}`);
372
+ return;
373
+ }
374
+ for (const item of value) {
375
+ if (item !== null && item !== undefined)
376
+ validateConnectorFieldValue(field, item, projectedArrays - 1, errs, dialect);
377
+ }
378
+ return;
379
+ }
380
+ // An opaque value is whatever the family cannot evaluate here — a Flow `Expr`,
381
+ // a BPMN `=`-expression. Its runtime type is unknowable, so it is not checked.
382
+ if (dialect.isOpaque(value) || value === null || value === undefined)
383
+ return;
384
+ if (field.type === 'array') {
385
+ if (!Array.isArray(value))
386
+ errs.push(`input "${field.name}" expected array, got ${typeof value}`);
387
+ return;
388
+ }
389
+ if (field.type === 'object') {
390
+ if (typeof value !== 'object' || Array.isArray(value))
391
+ errs.push(`input "${field.name}" expected object, got ${Array.isArray(value) ? 'array' : typeof value}`);
392
+ return;
393
+ }
394
+ if (field.type === 'string' || field.type === 'number' || field.type === 'boolean') {
395
+ const actual = typeof value;
396
+ const ok = actual === field.type ||
397
+ (field.type === 'number' && actual === 'string' && value !== '' && !isNaN(Number(value)));
398
+ if (!ok)
399
+ errs.push(`input "${field.name}" expected ${field.type}, got ${actual}`);
400
+ }
401
+ }
402
+ /**
403
+ * The closest declared field name to `key`, for the "did you mean …?" half of an
404
+ * unknown-input error.
405
+ *
406
+ * Three rules, tried in order: an exact case-insensitive match (`pagesize` →
407
+ * `pageSize`), a substring either way (`summary` → `fields.summary`), and — the
408
+ * one added for query/path parameters — a small EDIT DISTANCE. The first two miss
409
+ * every substitution typo: `projekt` and `project` share no containment, so the
410
+ * suggestion was simply absent, and it is absent exactly where it is needed most.
411
+ * Parameter names are unfamiliar (`issuetype`, `nextPage`, `expansionLevel`), so
412
+ * the author has no memory to fall back on.
413
+ *
414
+ * The threshold scales with the name's length (⌈len/4⌉, at least 1, at most 3), so
415
+ * a short name needs a near-exact miss and a long one may be off by a few — and
416
+ * two genuinely different fields are never conflated.
417
+ */
418
+ function nearestField(key, names) {
419
+ const lk = key.toLowerCase();
420
+ const exact = names.find((n) => n.toLowerCase() === lk);
421
+ if (exact)
422
+ return exact;
423
+ const contained = names.find((n) => n.toLowerCase().includes(lk) || lk.includes(n.toLowerCase()));
424
+ if (contained)
425
+ return contained;
426
+ const budget = Math.min(3, Math.max(1, Math.ceil(lk.length / 4)));
427
+ let best;
428
+ let bestD = budget + 1;
429
+ for (const n of names) {
430
+ const d = editDistance(lk, n.toLowerCase());
431
+ if (d < bestD) {
432
+ bestD = d;
433
+ best = n;
434
+ }
435
+ }
436
+ return bestD <= budget ? best : undefined;
437
+ }
438
+ /** Levenshtein distance, two rows — enough for field-name suggestions. */
439
+ function editDistance(a, b) {
440
+ if (a === b)
441
+ return 0;
442
+ if (Math.abs(a.length - b.length) > 3)
443
+ return 99; // cheap bail: beyond any budget
444
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
445
+ for (let i = 1; i <= a.length; i++) {
446
+ const row = [i];
447
+ for (let j = 1; j <= b.length; j++) {
448
+ row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
449
+ }
450
+ prev = row;
451
+ }
452
+ return prev[b.length];
453
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * core/event-checks — the connector-EVENT rules, shared by every family that can
3
+ * subscribe to one.
4
+ *
5
+ * The activity twin is `core/connector-checks.ts`. These were Flow's, and like
6
+ * those they were never flow-shaped: both functions take the subscription as
7
+ * `unknown` and duck-type `{ connector, event, where, object, filters, version,
8
+ * connection }`, because that is all they need. BPMN's `.eventTrigger()` /
9
+ * `.waitForEvent()` node carries the same facts under its own names, and
10
+ * `bpmn/check.ts` adapts one to the other.
11
+ *
12
+ * What BPMN does NOT carry is `filters` — payload filtering is a Flow surface —
13
+ * so the `EVENT_FILTER_*` family simply does not fire there. The rules that do
14
+ * are the ones about the SUBSCRIPTION: whether it resolves at all, whether a
15
+ * generic event named its object, and whether its scope names parameters the
16
+ * operation actually declares. `skill-bpmn-outlook-waitfor-email` in
17
+ * `flow-v2-run-8` failed on exactly that last question.
18
+ */
19
+ import { type LookupResolutions } from './lookups.js';
20
+ import { type ByoaEventFacts, type ByoaLedger } from './byoa.js';
21
+ import type { BindingsView } from './binding-messages.js';
22
+ /** A code/message sink, as each family's check supplies one. */
23
+ export type ReportEvent = (code: string, message: string, suggestion?: string) => void;
24
+ /**
25
+ * What a connector operation declares as its EVENT PARAMETERS — the `where` half
26
+ * of a subscription. `known: false` means "no library was supplied", which is a
27
+ * different answer from `known: true, names: []` ("this operation takes none").
28
+ * Conflating the two is what made `EVENT_NO_SCOPE` an error for the HTTP Webhook
29
+ * connector, whose only correct `where` is empty.
30
+ */
31
+ export interface EventParamFields {
32
+ known: boolean;
33
+ names: string[];
34
+ /**
35
+ * The operation is a GENERIC event (`activityType: GenericTrigger`): its node type
36
+ * covers every object of the connection, so the subscription must name one with
37
+ * `object`. Unknown (no library) reads as `false`, which fails open.
38
+ */
39
+ generic?: boolean;
40
+ /** The entry's own object — `''` for a generic event, the built-in one for a curated event. */
41
+ object?: string;
42
+ /**
43
+ * Required parameters that SELECT the schema rather than scope the
44
+ * subscription (Jira's `project` / `issuetype`). Their values decide which
45
+ * filter and output fields exist, and no CLI surface accepts them — so when
46
+ * this is non-empty the known filter vocabulary is a BASELINE, and a leaf
47
+ * outside it may still be legal.
48
+ */
49
+ selectors?: string[];
50
+ /**
51
+ * The named object has no prepared variant on a connector whose objects come
52
+ * from the connection — the generic template answered instead.
53
+ */
54
+ objectUnprepared?: boolean;
55
+ /**
56
+ * The connector discovers custom fields per connection and nothing has
57
+ * prepared this event, so the known vocabulary is a strict subset of the
58
+ * tenant's BY DESIGN.
59
+ */
60
+ customFieldsUnprepared?: boolean;
61
+ /**
62
+ * The filter vocabulary is a subset that NO prepare can complete.
63
+ *
64
+ * An activity closes this gap: `resources describe` takes `-f name=value`
65
+ * and `--action`, so supplying the parent values runs the connector's
66
+ * ObjectAction (Jira `GenerateSchema`, Salesforce `GenerateQuerySchema`) and
67
+ * the custom fields come back. `uip is triggers describe` has neither flag,
68
+ * so for an EVENT on a custom-field-discovering connector there is no call
69
+ * that reaches the parent-scoped fields — the designer grows Jira's filter
70
+ * list once a project and issue type are picked, and nothing in this CLI
71
+ * can. Distinct from `customFieldsUnprepared`, which preparing DOES fix.
72
+ */
73
+ filtersPartial?: boolean;
74
+ }
75
+ /** The library-backed facts the event rules need, or `undefined` for "no library". */
76
+ export interface EventCheckOpts {
77
+ /** A connector event's declared event PARAMETERS — the `where` half of a subscription. */
78
+ eventParams?: (connector: string, event: string, object?: string) => EventParamFields | undefined;
79
+ /** What `compile` would refuse a subscription with, or `undefined` when it resolves. */
80
+ eventResolveError?: (kind: 'trigger' | 'event', subscription: {
81
+ connector: string;
82
+ event: string;
83
+ version?: string;
84
+ object?: string;
85
+ }) => string | undefined;
86
+ /** A connector event's FILTERABLE field names — the vocabulary a `filters` leaf may name. */
87
+ eventFilterFields?: (connector: string, event: string, object?: string) => string[] | undefined;
88
+ /** A connector event's BYOA and webhook-URL facts, or `undefined` when unknown. */
89
+ eventFacts?: (connector: string, event: string, object?: string) => ByoaEventFacts | undefined;
90
+ /** The BYOA checks `registry prepare` recorded. */
91
+ byoa?: ByoaLedger;
92
+ /** Everything `registry prepare --resolve` has recorded in the local overlay. */
93
+ resolutions?: LookupResolutions;
94
+ /** The project's `bindings.json`, for the BYOA connection check. */
95
+ bindings?: BindingsView;
96
+ }
97
+ /**
98
+ * Ask the library what event parameters a subscription's operation declares.
99
+ * Returns `undefined` when the subscription is too broken to look up (its own
100
+ * diagnostics say so) or when no resolver was supplied — both mean "unknown", and
101
+ * unknown fails open.
102
+ */
103
+ export declare function eventParamsFor(subscription: unknown, opts: EventCheckOpts): EventParamFields | undefined;
104
+ /**
105
+ * The event-side resolution rules — the trigger twin of `checkConnectorLookups`,
106
+ * plus three rules with no activity counterpart:
107
+ *
108
+ * - `EVENT_UNKNOWN` — an ERROR: a library is loaded and it cannot resolve the
109
+ * subscription, so `compile` will refuse it. The message is the resolver's
110
+ * own (`eventResolveError`), the same text compile throws. Checked first; when
111
+ * it fires, EVENT_UNPREPARED and the filter rules below are skipped for this
112
+ * subscription, since there is no entry for them to read.
113
+ * - `EVENT_UNPREPARED` — the event RESOLVES but the library knows nothing
114
+ * about its parameters (or no `eventResolveError` resolver was supplied):
115
+ * the fallback case for a connector the shipped library was not generated
116
+ * against. A warning, because compile still succeeds. Prints the one
117
+ * command that makes the event checkable.
118
+ * - `EVENT_BYOA_UNVERIFIED` — an ERROR: the event only fires on a BYOA
119
+ * (bring-your-own-app) connection, and the bound connection id has no record
120
+ * of `prepare` having checked it (`core/byoa.ts` has the rule and when it
121
+ * stays silent). A standard connection deploys and the trigger never fires;
122
+ * the usual way in is binding the connection by preparing one of the
123
+ * connector's ACTIONS, which checks nothing about BYOA. `compile` throws the
124
+ * same text.
125
+ * - `EVENT_FILTER_UNKNOWN_FIELD` — a filter leaf naming a field outside the
126
+ * prepared vocabulary is an ERROR, not a warning, because the platform's own
127
+ * configure DROPS an unmatched leaf and reports Success: the deployed
128
+ * trigger then fires on events the filter should exclude, silently. Matching
129
+ * is case-sensitive, so the near-miss hint checks case first. When the
130
+ * vocabulary is unknown or known-empty this stays quiet — fail open.
131
+ *
132
+ * Unresolved `where` lookups follow the activity rule exactly: a WARNING here
133
+ * (compile repeats it as the hard failure), with the recorded answer looked up
134
+ * under the event-form node type — the form `prepare` stores.
135
+ */
136
+ export declare function checkEventResolution(subscription: unknown, where: string, kind: 'trigger' | 'event', opts: EventCheckOpts, error: (code: string, message: string, suggestion?: string) => void, warn: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields): void;
137
+ export declare function checkEventSubscription(subscription: unknown, where: string, at: (code: string, message: string, suggestion?: string) => void, warn?: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields): void;