@uipath/maestro-builder-sdk 6.11.4 → 6.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +132 -132
- package/dist/api-members.md +333 -333
- package/dist/check-cli.js +2 -1
- package/dist/check.d.ts +13 -0
- package/dist/check.js +201 -60
- package/dist/cli-library.d.ts +7 -0
- package/dist/cli-library.js +19 -0
- package/dist/compile-cli.js +8 -5
- package/dist/core/actions.d.ts +18 -0
- package/dist/core/byoa.d.ts +69 -0
- package/dist/core/byoa.js +98 -0
- package/dist/core/library.d.ts +23 -0
- package/dist/core/library.js +47 -1
- package/dist/core/lookup-store.d.ts +12 -0
- package/dist/core/lookup-store.js +25 -0
- package/dist/core/queue-record.d.ts +106 -0
- package/dist/core/queue-record.js +48 -0
- package/dist/flow-sdk.d.ts +1 -1
- package/dist/flow-sdk.js +1 -1
- package/dist/registry/generators/descriptors.d.ts +1 -1
- package/dist/registry/generators/descriptors.js +20 -2
- package/dist/registry/generators/markdown/page.js +3 -1
- package/dist/registry/generators/markdown/sections/event-facts.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/event-facts.js +45 -0
- package/dist/registry/library-build/events.d.ts +32 -3
- package/dist/registry/library-build/events.js +48 -19
- package/dist/registry/prepare/byoa-ledger.d.ts +12 -0
- package/dist/registry/prepare/byoa-ledger.js +26 -0
- package/dist/registry/prepare/event.d.ts +5 -1
- package/dist/registry/prepare/event.js +22 -8
- package/dist/registry/prepare/messages.d.ts +2 -0
- package/dist/registry/prepare/messages.js +4 -0
- package/dist/serialize.js +25 -48
- package/package.json +2 -2
package/dist/check-cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { reportDiagnostics } from './cli-result.js';
|
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
10
|
import { resolveFlowFile, loadBuiltFlow } from './load.js';
|
|
11
11
|
import { check } from './check.js';
|
|
12
|
-
import { eventResolveErrorFor, openCliLibrary } from './cli-library.js';
|
|
12
|
+
import { byoaCheckOpts, eventResolveErrorFor, openCliLibrary } from './cli-library.js';
|
|
13
13
|
import { Bindings } from './bindings.js';
|
|
14
14
|
import { connectorInputErrors } from './serialize.js';
|
|
15
15
|
import { runWhenInvokedDirectly } from './cli-run.js';
|
|
@@ -41,6 +41,7 @@ function checkOpts(argv) {
|
|
|
41
41
|
return bindings ? { bindings } : {};
|
|
42
42
|
return {
|
|
43
43
|
eventResolveError: eventResolveErrorFor(library),
|
|
44
|
+
...byoaCheckOpts(library),
|
|
44
45
|
eventParams: (connector, event, object) => {
|
|
45
46
|
try {
|
|
46
47
|
return library.eventParameterFields(connector, event, object);
|
package/dist/check.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type LookupResolutions, type LookupSpec } from './core/lookups.js';
|
|
|
2
2
|
import type { SchemaReadiness } from './core/library.js';
|
|
3
3
|
import type { BuiltFlow } from './flow-sdk.js';
|
|
4
4
|
import { type BindingsView } from './core/binding-messages.js';
|
|
5
|
+
import { type ByoaEventFacts, type ByoaLedger } from './core/byoa.js';
|
|
5
6
|
export interface Diagnostic {
|
|
6
7
|
level: 'error' | 'warning';
|
|
7
8
|
code: string;
|
|
@@ -116,6 +117,18 @@ export interface CheckOpts {
|
|
|
116
117
|
lookups?: (connector: string, action: string, version?: string, object?: string) => Readonly<Record<string, LookupSpec>> | undefined;
|
|
117
118
|
/** Everything `registry prepare --resolve` has recorded in the local overlay. */
|
|
118
119
|
resolutions?: LookupResolutions;
|
|
120
|
+
/**
|
|
121
|
+
* A connector event's BYOA and webhook-URL facts, or `undefined` when unknown.
|
|
122
|
+
* Supplied by the CLIs from `library.eventFacts` — the call `serialize` makes
|
|
123
|
+
* for the same refusal — so check and compile cannot disagree; omitted (no
|
|
124
|
+
* library) → EVENT_BYOA_UNVERIFIED is not checked.
|
|
125
|
+
*/
|
|
126
|
+
eventFacts?: (connector: string, event: string, object?: string) => ByoaEventFacts | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* The BYOA checks `registry prepare` recorded — the `byoa` section of the
|
|
129
|
+
* overlay's `resolutions.json`, as `library.byoaVerifications` loads it.
|
|
130
|
+
*/
|
|
131
|
+
byoa?: ByoaLedger;
|
|
119
132
|
/**
|
|
120
133
|
* Whether a connector step's schema is materialized locally — the fact behind
|
|
121
134
|
* the OBJECT_UNPREPARED and CUSTOM_FIELDS_UNPREPARED warnings. Supplied by the
|
package/dist/check.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { Expr, SCHEDULE_PRESETS, DELAY_PRESETS, VARIANT_OPERATION, parseIxpProjectType, flatNameOf, flatNameSegmentsOf, FLAT_SEP } from './flow-sdk.js';
|
|
9
9
|
import { FlowNode } from './core/node-classes.js';
|
|
10
10
|
import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './core/lookups.js';
|
|
11
|
+
import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
|
|
11
12
|
import { EVENT_FILTER_OPERATORS, readEventFilter, eventFilterProblem } from './event-filters.js';
|
|
12
13
|
import { extractRefs } from './core/expr-check.js';
|
|
13
14
|
import { bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
|
|
@@ -17,6 +18,7 @@ import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step
|
|
|
17
18
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
18
19
|
import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js';
|
|
19
20
|
import { prepareCommand, resolveFlagArg } from './core/cli-spelling.js';
|
|
21
|
+
import { EVENT_BYOA_UNVERIFIED, byoaUnverified } from './core/byoa.js';
|
|
20
22
|
export function check(built, opts = {}) {
|
|
21
23
|
return checkFlow(built, [], opts);
|
|
22
24
|
}
|
|
@@ -145,20 +147,12 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
145
147
|
// form's ANSWER fields (plus `Action`), so a read of it is checked against the
|
|
146
148
|
// form the author wrote rather than waved through (see HITL_READ_* below).
|
|
147
149
|
const hitlForms = new Map();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Agentic-process step name → its call. Same reason as api-workflow's map: the
|
|
151
|
-
// step publishes the job's output arguments and the only offline statement of
|
|
152
|
-
// what those are is the step's own `returns`.
|
|
150
|
+
// Agentic-process step name → its call, for the form-field rules below. Its
|
|
151
|
+
// output READS go through OUTPUT_READ_RULES like every `returns` family.
|
|
153
152
|
const agenticProcesses = new Map();
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
const agents = new Map();
|
|
158
|
-
// Inline-agent step name → its call. NOT the same reason: an inline agent's
|
|
159
|
-
// contract is AUTHORED rather than looked up on a tenant, so a read outside the
|
|
160
|
-
// declared `returns` is a mistake the compiler can be certain about.
|
|
161
|
-
const inlineAgents = new Map();
|
|
153
|
+
// Step name → the read rule for its output, bound to its inputs. Filled from
|
|
154
|
+
// OUTPUT_READ_RULES for every kind whose inputs take a `returns`.
|
|
155
|
+
const outputReaders = new Map();
|
|
162
156
|
const connectorSteps = new Map();
|
|
163
157
|
// Subflow step name → the child flow it runs. A subflow's output is the
|
|
164
158
|
// OBJECT of the child's declared outputs, so a read of it is checked against
|
|
@@ -434,11 +428,9 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
434
428
|
checkRpaWorkflow(s.name, s.spec.inputs, diags);
|
|
435
429
|
if (s.spec.kind === 'apiWorkflow') {
|
|
436
430
|
checkApiWorkflow(s.name, s.spec.inputs, diags);
|
|
437
|
-
apiWorkflows.set(s.name, s.spec.inputs);
|
|
438
431
|
}
|
|
439
432
|
if (s.spec.kind === 'publishedFunction') {
|
|
440
433
|
checkPublishedFunction(s.name, s.spec.inputs, diags);
|
|
441
|
-
publishedFunctions.set(s.name, s.spec.inputs);
|
|
442
434
|
}
|
|
443
435
|
if (s.spec.kind === 'sendMessage' || s.spec.kind === 'waitForMessage'
|
|
444
436
|
|| s.spec.kind === 'conversationContext') {
|
|
@@ -456,14 +448,20 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
456
448
|
}
|
|
457
449
|
if (s.spec.kind === 'agent') {
|
|
458
450
|
checkAgent(s.name, s.spec.inputs, diags);
|
|
459
|
-
agents.set(s.name, s.spec.inputs);
|
|
460
451
|
}
|
|
461
452
|
if (s.spec.kind === 'inlineAgent') {
|
|
462
453
|
checkInlineAgent(s.name, s.spec.inputs, diags);
|
|
463
|
-
inlineAgents.set(s.name, s.spec.inputs);
|
|
464
454
|
}
|
|
465
455
|
if (s.spec.kind === 'queueItem')
|
|
466
456
|
checkQueueItem(s.name, s.spec.inputs, diags);
|
|
457
|
+
{
|
|
458
|
+
const rule = OUTPUT_READ_RULES[s.spec.kind];
|
|
459
|
+
if (typeof rule === 'function') {
|
|
460
|
+
const stepName = s.name;
|
|
461
|
+
const inputs = s.spec.inputs;
|
|
462
|
+
outputReaders.set(stepName, (field, rest, at, d) => rule(stepName, field, rest, inputs, at, d));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
467
465
|
if (s.spec.kind === 'documentClassify' || s.spec.kind === 'dynamicExtract') {
|
|
468
466
|
const fr = s.spec.inputs.fileRef;
|
|
469
467
|
const empty = fr === undefined || fr === null || (typeof fr === 'string' && fr.trim() === '');
|
|
@@ -1168,49 +1166,13 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1168
1166
|
checkHitlRead(name, field, rest, form, step, diags);
|
|
1169
1167
|
continue;
|
|
1170
1168
|
}
|
|
1171
|
-
//
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
checkApiWorkflowRead(name, field, rest, apiwf, step, diags);
|
|
1179
|
-
continue;
|
|
1180
|
-
}
|
|
1181
|
-
// A published function publishes its result the same way, and the same
|
|
1182
|
-
// three read mistakes are decidable from the same place — so the reader is
|
|
1183
|
-
// shared, with the family named in the codes.
|
|
1184
|
-
const fn = publishedFunctions.get(name);
|
|
1185
|
-
if (fn) {
|
|
1186
|
-
checkPublishedFunctionRead(name, field, rest, fn, step, diags);
|
|
1187
|
-
continue;
|
|
1188
|
-
}
|
|
1189
|
-
// An agentic process publishes its job's output arguments the same way, so
|
|
1190
|
-
// the same three read mistakes are decidable from the same place.
|
|
1191
|
-
const agentic = agenticProcesses.get(name);
|
|
1192
|
-
if (agentic) {
|
|
1193
|
-
checkAgenticProcessRead(name, field, rest, agentic, step, diags);
|
|
1194
|
-
continue;
|
|
1195
|
-
}
|
|
1196
|
-
// A published agent publishes its job's output arguments the same way its
|
|
1197
|
-
// three siblings do, so the same three read mistakes are decidable — with one
|
|
1198
|
-
// extra that only this family has: a read whose CASE differs from the declared
|
|
1199
|
-
// field. That used to be a live-only defect (the poll verb PascalCased the
|
|
1200
|
-
// answer, flow-builder-sdk #122), so the wrong case was right live and wrong
|
|
1201
|
-
// offline for a while. Now it is simply wrong, and worth naming as itself
|
|
1202
|
-
// rather than as an unknown field.
|
|
1203
|
-
const published = agents.get(name);
|
|
1204
|
-
if (published) {
|
|
1205
|
-
checkAgentRead(name, field, rest, published, step, diags);
|
|
1206
|
-
continue;
|
|
1207
|
-
}
|
|
1208
|
-
// An inline agent's readable fields are exactly its declared `returns` — the
|
|
1209
|
-
// node's `agentOutputVariables`, which is also what the engine's live path
|
|
1210
|
-
// coerces the model's completion to. Nothing on a tenant to consult.
|
|
1211
|
-
const inline = inlineAgents.get(name);
|
|
1212
|
-
if (inline) {
|
|
1213
|
-
checkInlineAgentRead(name, field, rest, inline, step, diags);
|
|
1169
|
+
// Every family that declares its output's fields — the author's `returns`, or
|
|
1170
|
+
// a shape the SDK fixes — has its read rule in OUTPUT_READ_RULES, keyed by
|
|
1171
|
+
// kind. A read outside the declared set is undefined on every rung and on
|
|
1172
|
+
// the platform, so it is refused here rather than at `flow debug`.
|
|
1173
|
+
const readOutput = outputReaders.get(name);
|
|
1174
|
+
if (readOutput) {
|
|
1175
|
+
readOutput(field, rest, step, diags);
|
|
1214
1176
|
continue;
|
|
1215
1177
|
}
|
|
1216
1178
|
// A loop step exposes a fixed set of fields, and only those resolve at run
|
|
@@ -1992,6 +1954,13 @@ function eventParamsFor(subscription, opts) {
|
|
|
1992
1954
|
* the fallback case for a connector the shipped library was not generated
|
|
1993
1955
|
* against. A warning, because compile still succeeds. Prints the one
|
|
1994
1956
|
* command that makes the event checkable.
|
|
1957
|
+
* - `EVENT_BYOA_UNVERIFIED` — an ERROR: the event only fires on a BYOA
|
|
1958
|
+
* (bring-your-own-app) connection, and the bound connection id has no record
|
|
1959
|
+
* of `prepare` having checked it (`core/byoa.ts` has the rule and when it
|
|
1960
|
+
* stays silent). A standard connection deploys and the trigger never fires;
|
|
1961
|
+
* the usual way in is binding the connection by preparing one of the
|
|
1962
|
+
* connector's ACTIONS, which checks nothing about BYOA. `compile` throws the
|
|
1963
|
+
* same text.
|
|
1995
1964
|
* - `EVENT_FILTER_UNKNOWN_FIELD` — a filter leaf naming a field outside the
|
|
1996
1965
|
* prepared vocabulary is an ERROR, not a warning, because the platform's own
|
|
1997
1966
|
* configure DROPS an unmatched leaf and reports Success: the deployed
|
|
@@ -2027,6 +1996,9 @@ function checkEventResolution(subscription, where, kind, opts, error, warn, even
|
|
|
2027
1996
|
return;
|
|
2028
1997
|
}
|
|
2029
1998
|
const object = typeof sub.object === 'string' && sub.object !== '' ? sub.object : undefined;
|
|
1999
|
+
const byoa = opts.eventFacts && byoaUnverified(where, { connector: connectorKey, event: eventName, ...(object ? { object } : {}), connection: sub.connection }, opts.eventFacts(connectorKey, eventName, object), opts.byoa, opts.bindings);
|
|
2000
|
+
if (byoa)
|
|
2001
|
+
error(EVENT_BYOA_UNVERIFIED, byoa);
|
|
2030
2002
|
if (eventParams?.objectUnprepared && object) {
|
|
2031
2003
|
// The generic template answers for every object, so resolution SUCCEEDS
|
|
2032
2004
|
// and nothing downstream notices that the object named here has no
|
|
@@ -2815,6 +2787,175 @@ function checkPublishedFunctionRead(name, field, rest, spec, step, diags) {
|
|
|
2815
2787
|
suggestion: `returns: { ${wanted}: 'string' }`,
|
|
2816
2788
|
});
|
|
2817
2789
|
}
|
|
2790
|
+
/**
|
|
2791
|
+
* The fields every HTTP response carries at the top of `output`, whatever the
|
|
2792
|
+
* API answered. `core.action.http`'s definition declares all three; the author's
|
|
2793
|
+
* `returns` describes what is INSIDE `body`, never beside it.
|
|
2794
|
+
*/
|
|
2795
|
+
const HTTP_ENVELOPE_FIELDS = ['body', 'statusCode', 'headers'];
|
|
2796
|
+
/**
|
|
2797
|
+
* A read of an `http()` step's result.
|
|
2798
|
+
*
|
|
2799
|
+
* The response is an envelope — `{ body, statusCode, headers }` — and `returns`
|
|
2800
|
+
* declares the parsed JSON body's top-level fields, so a declared field is read
|
|
2801
|
+
* at `output.body.<field>` (`out('<step>', 'body.<field>')`). That nesting is the
|
|
2802
|
+
* one thing this family does not share with the process families, and it is
|
|
2803
|
+
* exactly where flow-builder-sdk#850's run went wrong: a branch kept reading
|
|
2804
|
+
* `output.currentDayForecast…` (the Azure Maps connector's path) after the step
|
|
2805
|
+
* became an Open-Meteo `http()`, passed `check` and `validate`, and faulted at
|
|
2806
|
+
* `flow debug` as `[400302]`.
|
|
2807
|
+
*/
|
|
2808
|
+
function checkHttpRead(name, field, rest, spec, step, diags) {
|
|
2809
|
+
if (field === undefined)
|
|
2810
|
+
return; // `out('fetch')` — the whole response.
|
|
2811
|
+
if (field === 'error')
|
|
2812
|
+
return; // the failure envelope (managed http's error port).
|
|
2813
|
+
if (field !== 'output') {
|
|
2814
|
+
diags.push({
|
|
2815
|
+
level: 'error', code: 'HTTP_READ_NOT_OUTPUT', step,
|
|
2816
|
+
message: `HTTP step "${name}" publishes its response under "output", but "$vars.${name}.${field}" `
|
|
2817
|
+
+ `(read in "${step}") asks for "${field}", which is undefined at run time.`,
|
|
2818
|
+
suggestion: `out('${name}', 'body.<field>')`,
|
|
2819
|
+
});
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
const [top, inner] = (rest ?? '').replace(/^\./, '').split('.');
|
|
2823
|
+
if (!top)
|
|
2824
|
+
return; // `$vars.<name>.output` — the whole envelope.
|
|
2825
|
+
const declared = Object.keys(spec?.returns ?? {});
|
|
2826
|
+
if (!HTTP_ENVELOPE_FIELDS.includes(top)) {
|
|
2827
|
+
// The commonest form: a declared body field read one level too high.
|
|
2828
|
+
const hoisted = declared.includes(top) ? top : undefined;
|
|
2829
|
+
const nearest = hoisted ?? nearestName(top, declared);
|
|
2830
|
+
diags.push({
|
|
2831
|
+
level: 'error', code: 'HTTP_READ_UNKNOWN_FIELD', step,
|
|
2832
|
+
message: `"$vars.${name}.output.${top}" (read in "${step}") is not a field of HTTP step "${name}"'s `
|
|
2833
|
+
+ `response. An HTTP response publishes ${HTTP_ENVELOPE_FIELDS.map((f) => `"${f}"`).join(', ')}; `
|
|
2834
|
+
+ `the fields \`returns\` declares live under "body"`
|
|
2835
|
+
+ (hoisted ? `, so "${top}" is read as "$vars.${name}.output.body.${top}".` : '.')
|
|
2836
|
+
+ ` A read outside that shape resolves to undefined and faults a gateway at run time.`,
|
|
2837
|
+
...(nearest !== undefined ? { suggestion: `out('${name}', 'body.${nearest}')` } : {}),
|
|
2838
|
+
});
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
if (top !== 'body' || !inner)
|
|
2842
|
+
return; // `statusCode`, `headers`, or the whole body.
|
|
2843
|
+
if (declared.length === 0) {
|
|
2844
|
+
diags.push({
|
|
2845
|
+
level: 'error', code: 'HTTP_READ_WITHOUT_RETURNS', step,
|
|
2846
|
+
message: `"$vars.${name}.output.body.${inner}" (read in "${step}") reads a field of HTTP step "${name}"'s `
|
|
2847
|
+
+ `response body, but the step declares no \`returns\` — so the node carries no body schema and nothing `
|
|
2848
|
+
+ `can tell a real field from a typo. Only the API being called knows its body; declare the fields you read.`,
|
|
2849
|
+
suggestion: `returns: { ${inner}: 'string' }`,
|
|
2850
|
+
});
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
if (declared.includes(inner))
|
|
2854
|
+
return;
|
|
2855
|
+
const nearest = nearestName(inner, declared);
|
|
2856
|
+
diags.push({
|
|
2857
|
+
level: 'error', code: 'HTTP_READ_UNKNOWN_FIELD', step,
|
|
2858
|
+
message: `HTTP step "${name}" declares no body field "${inner}" (read as "$vars.${name}.output.body.${inner}" `
|
|
2859
|
+
+ `in "${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set resolves to undefined.`,
|
|
2860
|
+
...(nearest !== undefined ? { suggestion: `out('${name}', 'body.${nearest}')` } : {}),
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* The read rule the process-shaped families share: the result is published
|
|
2865
|
+
* under `output`, keyed by a known field list — the author's `returns`, or a
|
|
2866
|
+
* shape the SDK itself fixes. `declared: undefined` means "the author declared
|
|
2867
|
+
* nothing", which is its own mistake (`<PREFIX>_READ_WITHOUT_RETURNS`).
|
|
2868
|
+
*/
|
|
2869
|
+
function checkDeclaredOutputRead(o) {
|
|
2870
|
+
const { prefix, what, name, field, rest, step, diags } = o;
|
|
2871
|
+
if (field === undefined || field === 'error')
|
|
2872
|
+
return;
|
|
2873
|
+
if (field !== 'output') {
|
|
2874
|
+
diags.push({
|
|
2875
|
+
level: 'error', code: `${prefix}_READ_NOT_OUTPUT`, step,
|
|
2876
|
+
message: `${what} "${name}" publishes its result under "output", but "$vars.${name}.${field}" `
|
|
2877
|
+
+ `(read in "${step}") asks for "${field}", which is undefined at run time.`,
|
|
2878
|
+
suggestion: `out('${name}', '<field>')`,
|
|
2879
|
+
});
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
const wanted = (rest ?? '').replace(/^\./, '').split('.')[0];
|
|
2883
|
+
if (!wanted)
|
|
2884
|
+
return;
|
|
2885
|
+
if (o.declared === undefined) {
|
|
2886
|
+
diags.push({
|
|
2887
|
+
level: 'error', code: `${prefix}_READ_WITHOUT_RETURNS`, step,
|
|
2888
|
+
message: `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of ${what} "${name}"'s result, `
|
|
2889
|
+
+ `but the step declares no \`returns\` — so the node carries no output schema and nothing can tell a `
|
|
2890
|
+
+ `real field from a typo. ${o.withoutReturns}`,
|
|
2891
|
+
suggestion: `returns: { ${wanted}: 'string' }`,
|
|
2892
|
+
});
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
if (o.declared.includes(wanted))
|
|
2896
|
+
return;
|
|
2897
|
+
const nearest = nearestName(wanted, o.declared);
|
|
2898
|
+
diags.push({
|
|
2899
|
+
level: 'error', code: `${prefix}_READ_UNKNOWN_FIELD`, step,
|
|
2900
|
+
message: `${what} "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in `
|
|
2901
|
+
+ `"${step}"). It publishes: ${o.declared.join(', ')}. A read outside that set resolves to undefined.`,
|
|
2902
|
+
...(nearest !== undefined ? { suggestion: `out('${name}', '${nearest}')` } : {}),
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
function checkRpaWorkflowRead(name, field, rest, spec, step, diags) {
|
|
2906
|
+
const declared = Object.keys(spec?.returns ?? {});
|
|
2907
|
+
checkDeclaredOutputRead({
|
|
2908
|
+
prefix: 'RPA', what: 'RPA process', name, field, rest, step, diags,
|
|
2909
|
+
declared: declared.length ? declared : undefined,
|
|
2910
|
+
withoutReturns: `The process's output arguments live on the tenant; declare the ones you read. `
|
|
2911
|
+
+ `\`uip maestro flow registry get uipath.core.rpa-workflow.${spec?.key}\` shows what the platform `
|
|
2912
|
+
+ `synthesizes for it.`,
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* A queue step publishes one of two shapes: a plain enqueue publishes the queue
|
|
2917
|
+
* ITEM's Orchestrator record (fixed, declared by the serializer from
|
|
2918
|
+
* `QUEUE_ITEM_RECORD_SCHEMA`), a `wait: true` publishes the processed result,
|
|
2919
|
+
* whose fields only the author's `returns` states.
|
|
2920
|
+
*/
|
|
2921
|
+
function checkQueueItemRead(name, field, rest, spec, step, diags) {
|
|
2922
|
+
const waitReturns = Object.keys(spec?.returns ?? {});
|
|
2923
|
+
checkDeclaredOutputRead({
|
|
2924
|
+
prefix: 'QUEUE', what: spec?.wait ? 'Queue wait' : 'Queue item', name, field, rest, step, diags,
|
|
2925
|
+
declared: !spec?.wait
|
|
2926
|
+
? Object.keys(QUEUE_ITEM_RECORD_SCHEMA.properties)
|
|
2927
|
+
: waitReturns.length ? waitReturns : undefined,
|
|
2928
|
+
withoutReturns: `What the consumer writes into the processed item's result is that automation's business; `
|
|
2929
|
+
+ `declare the fields you read.`,
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* THE GATE (flow-builder-sdk#850). Each `returns`-bearing kind maps to the rule
|
|
2934
|
+
* that checks a read of its output, or to a stated reason it has none.
|
|
2935
|
+
*
|
|
2936
|
+
* Before this table each family's rule was wired by hand — a map filled in the
|
|
2937
|
+
* walk plus a branch in the `$vars` dispatch — and nothing tied "this family
|
|
2938
|
+
* takes `returns`" to "this family has a read rule". `http`, `rpaWorkflow` and
|
|
2939
|
+
* `queueItem` shipped without one, so a read of an undeclared field passed
|
|
2940
|
+
* `check` and `validate` and only faulted live. The type below is exhaustive
|
|
2941
|
+
* over the derived kinds: give a new family a `returns` and `tsc` refuses to
|
|
2942
|
+
* build until it lands here.
|
|
2943
|
+
*/
|
|
2944
|
+
const OUTPUT_READ_RULES = {
|
|
2945
|
+
http: checkHttpRead,
|
|
2946
|
+
rpaWorkflow: checkRpaWorkflowRead,
|
|
2947
|
+
apiWorkflow: checkApiWorkflowRead,
|
|
2948
|
+
publishedFunction: checkPublishedFunctionRead,
|
|
2949
|
+
agenticProcess: checkAgenticProcessRead,
|
|
2950
|
+
agent: checkAgentRead,
|
|
2951
|
+
inlineAgent: checkInlineAgentRead,
|
|
2952
|
+
queueItem: checkQueueItemRead,
|
|
2953
|
+
script: {
|
|
2954
|
+
exempt: 'Not yet: only the field-map form of a script\'s `returns` gives a list to check against (the '
|
|
2955
|
+
+ 'type form and the inferred form do not), and script is the most-used step, so its rule lands with '
|
|
2956
|
+
+ 'its own false-positive sweep — flow-builder-sdk#855.',
|
|
2957
|
+
},
|
|
2958
|
+
};
|
|
2818
2959
|
function checkApiWorkflowRead(name, field, rest, spec, step, diags) {
|
|
2819
2960
|
if (field === undefined)
|
|
2820
2961
|
return; // `out('call')` — the whole output object.
|
package/dist/cli-library.d.ts
CHANGED
|
@@ -19,3 +19,10 @@ export declare function openCliLibrary(argv: readonly string[]): Library | undef
|
|
|
19
19
|
* exactly when compile would throw, with the same text.
|
|
20
20
|
*/
|
|
21
21
|
export declare function eventResolveErrorFor(library: Library): NonNullable<CheckOpts['eventResolveError']>;
|
|
22
|
+
/**
|
|
23
|
+
* `check`'s EVENT_BYOA_UNVERIFIED inputs: the event facts and the recorded BYOA
|
|
24
|
+
* checks, from the same `library.eventFacts` / `library.byoaVerifications` that
|
|
25
|
+
* `serialize` reads for the same refusal. Both CLIs spread this into their
|
|
26
|
+
* check options, so the inner loop and the emit rung refuse the same flows.
|
|
27
|
+
*/
|
|
28
|
+
export declare function byoaCheckOpts(library: Library): Pick<CheckOpts, 'eventFacts' | 'byoa'>;
|
package/dist/cli-library.js
CHANGED
|
@@ -54,3 +54,22 @@ export function eventResolveErrorFor(library) {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* `check`'s EVENT_BYOA_UNVERIFIED inputs: the event facts and the recorded BYOA
|
|
59
|
+
* checks, from the same `library.eventFacts` / `library.byoaVerifications` that
|
|
60
|
+
* `serialize` reads for the same refusal. Both CLIs spread this into their
|
|
61
|
+
* check options, so the inner loop and the emit rung refuse the same flows.
|
|
62
|
+
*/
|
|
63
|
+
export function byoaCheckOpts(library) {
|
|
64
|
+
return {
|
|
65
|
+
eventFacts: (connector, event, object) => {
|
|
66
|
+
try {
|
|
67
|
+
return library.eventFacts(connector, event, object);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined; // unknown fails open, same as every library callback
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
byoa: library.byoaVerifications,
|
|
74
|
+
};
|
|
75
|
+
}
|
package/dist/compile-cli.js
CHANGED
|
@@ -23,12 +23,13 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
23
23
|
import { resolveFlowFile, loadBuiltFlow } from './load.js';
|
|
24
24
|
import { serializeWithSidecars, connectorInputErrors } from './serialize.js';
|
|
25
25
|
import { check } from './check.js';
|
|
26
|
-
import { eventResolveErrorFor, openCliLibrary } from './cli-library.js';
|
|
26
|
+
import { byoaCheckOpts, eventResolveErrorFor, openCliLibrary } from './cli-library.js';
|
|
27
27
|
import { Bindings } from './bindings.js';
|
|
28
28
|
import { runWhenInvokedDirectly } from './cli-run.js';
|
|
29
29
|
import { connectionResources, mergeBindingsV2, readBindingsV2 } from './bindings-v2.js';
|
|
30
30
|
import { ensureTypeScriptRuntime } from './node-runtime.js';
|
|
31
31
|
import { BindingRefusal } from './core/binding-messages.js';
|
|
32
|
+
import { ByoaRefusal } from './core/byoa.js';
|
|
32
33
|
function opt(argv, name) {
|
|
33
34
|
const i = argv.indexOf(name);
|
|
34
35
|
return i >= 0 ? argv[i + 1] : undefined;
|
|
@@ -96,6 +97,7 @@ export async function run(argv) {
|
|
|
96
97
|
// printed as `✗ [EVENT_UNKNOWN]` before stopping instead of hiding
|
|
97
98
|
// behind "1 error(s); not emitting".
|
|
98
99
|
eventResolveError: eventResolveErrorFor(library),
|
|
100
|
+
...byoaCheckOpts(library),
|
|
99
101
|
eventParams: (connector, event) => {
|
|
100
102
|
try {
|
|
101
103
|
return library.eventParameterFields(connector, event);
|
|
@@ -134,16 +136,17 @@ export async function run(argv) {
|
|
|
134
136
|
// check()'s. Warnings are never fatal: the .flow is still written, exit stays 0.
|
|
135
137
|
// A binding LABEL the serializer refuses (its own name as the resourceKey, or a
|
|
136
138
|
// label a populated bindings.json does not declare) is an error, not a
|
|
137
|
-
// warning
|
|
138
|
-
//
|
|
139
|
-
//
|
|
139
|
+
// warning, and so is a BYOA-only event on a connection `prepare` never checked
|
|
140
|
+
// (EVENT_BYOA_UNVERIFIED): the check pass above reports each first, and this is
|
|
141
|
+
// the same refusal in the same `✗ [CODE]` shape when that pass was skipped
|
|
142
|
+
// (`--no-check`, or an emit-only project). Nothing is written.
|
|
140
143
|
const warnings = [];
|
|
141
144
|
let emitted;
|
|
142
145
|
try {
|
|
143
146
|
emitted = serializeWithSidecars(built, { library, bindings, warn: (w) => warnings.push(w) });
|
|
144
147
|
}
|
|
145
148
|
catch (error) {
|
|
146
|
-
if (!(error instanceof BindingRefusal))
|
|
149
|
+
if (!(error instanceof BindingRefusal || error instanceof ByoaRefusal))
|
|
147
150
|
throw error;
|
|
148
151
|
for (const w of warnings)
|
|
149
152
|
console.error(` ⚠ [${w.code}] ${w.message}`);
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -82,6 +82,16 @@ interface HttpInputsBase {
|
|
|
82
82
|
* fields, and nothing downstream can tell a real field from a typo. Same rule,
|
|
83
83
|
* and the same word, as
|
|
84
84
|
* `rpaWorkflow`'s `returns`.
|
|
85
|
+
*
|
|
86
|
+
* **Where a declared field is read:** under `body`, never beside it. The
|
|
87
|
+
* response is an envelope — `{ body, statusCode, headers }` — and these are the
|
|
88
|
+
* body's fields, so `returns: { items: 'array' }` is read as
|
|
89
|
+
* `out('fetch', 'body.items')`, which is `$vars.fetch.output.body.items`.
|
|
90
|
+
* `$vars.fetch.output.items` is undefined at run time.
|
|
91
|
+
*
|
|
92
|
+
* @enforcedBy HTTP_READ_UNKNOWN_FIELD A read must be `output.body.<declared field>`,
|
|
93
|
+
* `output.statusCode` or `output.headers`.
|
|
94
|
+
* @enforcedBy HTTP_READ_WITHOUT_RETURNS Reading a body field requires declaring it here.
|
|
85
95
|
*/
|
|
86
96
|
returns?: Record<string, ReturnFieldType>;
|
|
87
97
|
/**
|
|
@@ -991,6 +1001,9 @@ export interface RpaWorkflowInputs {
|
|
|
991
1001
|
* the fields here is the same thing the platform's own definition does when it
|
|
992
1002
|
* can reach the process, and it is what lets a misspelled read be caught at
|
|
993
1003
|
* `uip maestro flow check` instead of at run time.
|
|
1004
|
+
*
|
|
1005
|
+
* @enforcedBy RPA_READ_UNKNOWN_FIELD A read of the output must name a field declared here.
|
|
1006
|
+
* @enforcedBy RPA_READ_WITHOUT_RETURNS Reading a field off the result requires declaring it here.
|
|
994
1007
|
*/
|
|
995
1008
|
returns?: Record<string, ReturnFieldType>;
|
|
996
1009
|
}
|
|
@@ -2035,6 +2048,11 @@ export interface QueueItemInputs {
|
|
|
2035
2048
|
* `wait: false` needs none: what it publishes is the queue ITEM's own
|
|
2036
2049
|
* Orchestrator record (`Key`, `Status`, `Reference`, `SpecificContent`, …),
|
|
2037
2050
|
* which is the same for every queue, so the serializer declares it for you.
|
|
2051
|
+
*
|
|
2052
|
+
* @enforcedBy QUEUE_READ_UNKNOWN_FIELD A read must name a field of the record
|
|
2053
|
+
* (`wait: false`) or of this `returns` (`wait: true`).
|
|
2054
|
+
* @enforcedBy QUEUE_READ_WITHOUT_RETURNS Reading a field off a `wait: true` result
|
|
2055
|
+
* requires declaring it here.
|
|
2038
2056
|
*/
|
|
2039
2057
|
returns?: Record<string, ReturnFieldType>;
|
|
2040
2058
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** The diagnostic code, in `check` and on the error `serialize` throws. */
|
|
2
|
+
export declare const EVENT_BYOA_UNVERIFIED = "EVENT_BYOA_UNVERIFIED";
|
|
3
|
+
/** One verification `prepare` recorded. Only a PASSING check is ever written. */
|
|
4
|
+
export interface ByoaRecord {
|
|
5
|
+
byoa: true;
|
|
6
|
+
/** The connection's `Name`, so a person reading the file can tell which one it is. */
|
|
7
|
+
connectionName?: string;
|
|
8
|
+
/** `YYYY-MM-DD`: a resolved answer is a snapshot, like every other ledger row. */
|
|
9
|
+
verifiedAt?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The ledger's `byoa` section: `byoa[<event node type>][<connection id>]`.
|
|
13
|
+
*
|
|
14
|
+
* Keyed per EVENT, not per connector — Slack `send-reply` needs no BYOA, the
|
|
15
|
+
* message trigger does — and per CONNECTION, so swapping the connection in
|
|
16
|
+
* bindings.json un-verifies the event until it is prepared again.
|
|
17
|
+
*/
|
|
18
|
+
export type ByoaLedger = Record<string, Record<string, ByoaRecord>>;
|
|
19
|
+
/** The event facts the rule reads, as `Library.eventFacts` answers them. */
|
|
20
|
+
export interface ByoaEventFacts {
|
|
21
|
+
byoaConnection: boolean;
|
|
22
|
+
isWebhookUrlVisible: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The key an event's record is stored under: the WAIT form of the node type,
|
|
26
|
+
* the form `prepare` stores an event's overlay and lookup rows under, whichever
|
|
27
|
+
* role (`onEvent` or `waitForEvent`) the flow uses it in.
|
|
28
|
+
*/
|
|
29
|
+
export declare function byoaLedgerKey(connectorKey: string, event: string): string;
|
|
30
|
+
/** The record for `connectionId` under `nodeType`, matching the id case-insensitively. */
|
|
31
|
+
export declare function byoaRecordFor(ledger: ByoaLedger | undefined, nodeType: string, connectionId: string): ByoaRecord | undefined;
|
|
32
|
+
/** 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";
|
|
35
|
+
constructor(message: string);
|
|
36
|
+
}
|
|
37
|
+
/** One subscription, as the rule needs it. */
|
|
38
|
+
export interface ByoaSubscription {
|
|
39
|
+
connector: string;
|
|
40
|
+
event: string;
|
|
41
|
+
object?: string;
|
|
42
|
+
/** The `connection:` label as authored — a bindings.json name, or a connection id. */
|
|
43
|
+
connection?: unknown;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Why a BYOA-only event cannot be trusted to fire on its bound connection, or
|
|
47
|
+
* `undefined` when there is nothing to refuse. The ONE condition behind
|
|
48
|
+
* `check`'s EVENT_BYOA_UNVERIFIED error and `compile`'s refusal, so the two
|
|
49
|
+
* cannot disagree.
|
|
50
|
+
*
|
|
51
|
+
* Refuses when the event's facts say BYOA and the connection resolves to a real
|
|
52
|
+
* connection id with no `byoa` record for this event. Silent when:
|
|
53
|
+
*
|
|
54
|
+
* - the facts are unknown or say no BYOA;
|
|
55
|
+
* - the connection does not resolve to a real id — no bindings.json, a label it
|
|
56
|
+
* does not declare, or a placeholder (`0000…`, `<…>`, the label itself). There
|
|
57
|
+
* is no id to look a record up by, and nothing is lost: such a flow cannot
|
|
58
|
+
* run its trigger on any connection, and BINDING_SELF_NAME /
|
|
59
|
+
* BINDING_UNDECLARED / CONNECTION_STUB already say so.
|
|
60
|
+
*
|
|
61
|
+
* @param where - The call site, as the other event diagnostics name it (`trigger`, `waitForEvent "x"`).
|
|
62
|
+
* @param sub - The subscription.
|
|
63
|
+
* @param facts - The event's facts from the library, or `undefined` when unknown.
|
|
64
|
+
* @param ledger - The overlay ledger's `byoa` section.
|
|
65
|
+
* @param bindings - Resolves the `connection:` label to an id.
|
|
66
|
+
*/
|
|
67
|
+
export declare function byoaUnverified(where: string, sub: ByoaSubscription, facts: ByoaEventFacts | undefined, ledger: ByoaLedger | undefined, bindings: {
|
|
68
|
+
resolve(name?: string): string | undefined;
|
|
69
|
+
} | undefined): string | undefined;
|