@uipath/maestro-builder-sdk 6.68.7 → 6.69.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +45 -44
- package/dist/api-members.md +105 -105
- package/dist/case/check.d.ts +25 -57
- package/dist/case/check.js +93 -249
- package/dist/case/cli-library.d.ts +4 -7
- package/dist/case/cli-library.js +5 -46
- package/dist/case/serialize.d.ts +48 -2
- package/dist/case/serialize.js +269 -67
- package/dist/case/subscription.d.ts +6 -4
- package/dist/case/subscription.js +15 -5
- package/dist/core/refusals.d.ts +1 -0
- package/dist/core/refusals.js +4 -2
- package/dist/decompile.js +39 -11
- package/dist/flow-sdk.d.ts +9 -7
- package/dist/flow-sdk.js +20 -6
- package/dist/flow-types.d.ts +72 -0
- package/dist/flow-types.js +62 -0
- package/package.json +2 -2
package/dist/case/check.d.ts
CHANGED
|
@@ -1,80 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* case/check — fast
|
|
2
|
+
* case/check — fast validation of a BuiltCase.
|
|
3
3
|
*
|
|
4
4
|
* Surfaces the common `uip maestro case validate` failures early (task without
|
|
5
5
|
* an entry rule, case/stage without a completion rule, an unsatisfiable
|
|
6
|
-
* `required-tasks-completed
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
6
|
+
* `required-tasks-completed`) so authoring gets instant feedback, and runs the
|
|
7
|
+
* serializer in collect mode (`serializerRefusals`), so it also reports every
|
|
8
|
+
* refusal `compile` would raise (unresolved stage/task references, duplicate
|
|
9
|
+
* stage labels, a connector input the operation does not declare), each from
|
|
10
|
+
* its one implementation and placed on its task, stage or trigger
|
|
11
|
+
* (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.5, phase 8). The authoritative gate
|
|
12
|
+
* is still `uip maestro case validate` / `@uipath/case-schema` on the emitted file.
|
|
9
13
|
*/
|
|
10
14
|
import type { BuiltCase } from './case-sdk.js';
|
|
11
15
|
import { type ConnectorCheckOpts } from '../core/connector-checks.js';
|
|
12
|
-
import type {
|
|
13
|
-
import { type EventCheckOpts } from '../core/event-checks.js';
|
|
16
|
+
import type { Library } from '../core/library.js';
|
|
14
17
|
export interface Diagnostic {
|
|
15
18
|
level: 'error' | 'warning';
|
|
16
19
|
code: string;
|
|
17
20
|
message: string;
|
|
21
|
+
/** What the finding is about: a task's name, a stage's label, a trigger's label. */
|
|
18
22
|
where?: string;
|
|
19
23
|
suggestion?: string;
|
|
24
|
+
/** The id of the node `where` names, for a refusal the serializer placed on one (`serializerRefusals`). */
|
|
25
|
+
nodeId?: string;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* The connector-library facts Case `check` reads, from the same `Library`
|
|
23
29
|
* instance `compile` serializes against (`caseCheckOpts` in `./cli-library.ts`).
|
|
24
30
|
*
|
|
25
|
-
*
|
|
26
|
-
* `
|
|
27
|
-
* the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* `check` runs the serializer in collect mode (`serializerRefusals`), with
|
|
32
|
+
* `library` and `bindings`, so every refusal `compile` makes (an operation or
|
|
33
|
+
* event the library cannot resolve, a connector input the operation does not
|
|
34
|
+
* declare, an unrecorded lookup, a binding label bindings.json cannot answer,
|
|
35
|
+
* an unverified BYOA connection, an external task's descriptor contract) is
|
|
36
|
+
* reported from its one implementation. The other hooks feed the warnings only
|
|
37
|
+
* `check` gives: the schema-readiness warnings and the lookup rules of
|
|
38
|
+
* `checkConnectorLookups`.
|
|
32
39
|
*
|
|
33
|
-
* Every hook fails open: without a library, `check`
|
|
34
|
-
*
|
|
40
|
+
* Every hook fails open: without a library, `check` judges a connector task up
|
|
41
|
+
* to the library, as `compile` would.
|
|
35
42
|
*/
|
|
36
|
-
export type CaseCheckOpts = Pick<ConnectorCheckOpts, '
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
* when it resolves: `EVENT_UNKNOWN` (the event is not in the library, or
|
|
40
|
-
* refuses the `object`) or `EVENT_GENERIC_NO_OBJECT` (a GENERIC event with no
|
|
41
|
-
* `object`). Built on `resolveCaseSubscription`, the call compile makes.
|
|
42
|
-
*/
|
|
43
|
-
/**
|
|
44
|
-
* What Case `compile` would refuse an External Agent / External Workflow
|
|
45
|
-
* task's resolved descriptor with (`externalDescriptorRefusal`), or
|
|
46
|
-
* `undefined`: also `undefined` when the descriptor does not resolve, which
|
|
47
|
-
* `connectorResolveError` reports.
|
|
48
|
-
*/
|
|
49
|
-
externalDescriptorError?: (kind: 'external-agent' | 'external-workflow', descriptor: {
|
|
50
|
-
key: string;
|
|
51
|
-
action: string;
|
|
52
|
-
version?: string;
|
|
53
|
-
object?: string;
|
|
54
|
-
}) => string | undefined;
|
|
55
|
-
/**
|
|
56
|
-
* What Case `compile` would refuse an External Agent / External Workflow
|
|
57
|
-
* task's mode and event with, judged against what the library records for
|
|
58
|
-
* the object (`externalExecution`): EXTERNAL_TASK_MODE (no mode from the
|
|
59
|
-
* author or the library), EXTERNAL_TASK_MODE_MISMATCH, EXTERNAL_TASK_EVENT.
|
|
60
|
-
* `undefined` when they are fine, or the descriptor does not resolve.
|
|
61
|
-
*/
|
|
62
|
-
externalExecutionError?: (owner: string, descriptor: {
|
|
63
|
-
key: string;
|
|
64
|
-
action: string;
|
|
65
|
-
version?: string;
|
|
66
|
-
object?: string;
|
|
67
|
-
}, authored: {
|
|
68
|
-
mode?: unknown;
|
|
69
|
-
event?: unknown;
|
|
70
|
-
}) => {
|
|
71
|
-
code: string;
|
|
72
|
-
message: string;
|
|
73
|
-
} | undefined;
|
|
74
|
-
subscriptionError?: (kind: 'trigger' | 'event', subscription: EventSubscription) => {
|
|
75
|
-
code: string;
|
|
76
|
-
message: string;
|
|
77
|
-
} | undefined;
|
|
43
|
+
export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'lookups' | 'resolutions' | 'schemaReadiness' | 'bindings'> & {
|
|
44
|
+
/** The library `compile` serializes against, for the serializer `check` runs. */
|
|
45
|
+
library?: Library;
|
|
78
46
|
};
|
|
79
47
|
/**
|
|
80
48
|
* Validate a built case.
|
package/dist/case/check.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import { businessEventProblems } from './business-event-shape.js';
|
|
2
|
-
import { isUnboundInArg, unboundInArgMessage } from './in-arg-binding.js';
|
|
3
|
-
import { ruleShapeProblems } from './rule-options.js';
|
|
4
1
|
import { types } from '../core/expr.js';
|
|
5
|
-
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, authoredCaseVersion
|
|
2
|
+
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, authoredCaseVersion } from './format-profile.js';
|
|
6
3
|
import { schemaVersionRefusal } from '../schema-version.js';
|
|
7
|
-
import {
|
|
8
|
-
import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
|
|
9
|
-
import { checkEventResolution } from '../core/event-checks.js';
|
|
10
|
-
import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, externalExecution } from './external.js';
|
|
11
|
-
import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
|
|
12
|
-
import { taskShapeProblems } from './task-shape.js';
|
|
4
|
+
import { EQUALS_EXPR_DIALECT, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
|
|
13
5
|
import { isConnectorPlaceholder } from './connector-placeholder.js';
|
|
6
|
+
import { serialize } from './serialize.js';
|
|
14
7
|
const TYPE_NAMES = new Set(Object.values(types));
|
|
15
8
|
// audit-case-coverage: rule-slot table start
|
|
16
9
|
// This runtime table must equal the committed matrix's rejected cells. The audit
|
|
@@ -26,17 +19,6 @@ const ILLEGAL = [];
|
|
|
26
19
|
*/
|
|
27
20
|
export function check(built, opts = {}) {
|
|
28
21
|
const diags = [];
|
|
29
|
-
// The same registry `serialize` resolves against, so the two agree on which
|
|
30
|
-
// labels a decompiled source's preserved binding rows answer.
|
|
31
|
-
const bindings = preservedBindings(opts.bindings, caseJsonPreservation(built));
|
|
32
|
-
if (bindings !== opts.bindings)
|
|
33
|
-
opts = { ...opts, bindings };
|
|
34
|
-
for (const message of caseJsonPreservationErrors(caseJsonPreservation(built)))
|
|
35
|
-
diags.push({
|
|
36
|
-
level: 'error',
|
|
37
|
-
code: 'PRESERVATION_COLLISION',
|
|
38
|
-
message: `Invalid Case JSON preservation payload: ${message}.`,
|
|
39
|
-
});
|
|
40
22
|
if (!built.id)
|
|
41
23
|
diags.push({ level: 'error', code: 'NO_ID', message: 'casePlan() requires an id.' });
|
|
42
24
|
const requestedVersion = authoredCaseVersion(built);
|
|
@@ -46,10 +28,7 @@ export function check(built, opts = {}) {
|
|
|
46
28
|
code: 'VERSION_DEPRECATED',
|
|
47
29
|
message: `.version(${JSON.stringify(requestedVersion)}) is deprecated: the Case JSON schema version is declared with .schemaVersion(), which accepts only versions this build can write. This SDK writes ${CASE_FORMAT_PROFILE.writeVersion}.`,
|
|
48
30
|
});
|
|
49
|
-
//
|
|
50
|
-
const mismatch = caseVersionMismatch(built);
|
|
51
|
-
if (mismatch)
|
|
52
|
-
diags.push({ level: 'error', code: 'VERSION_MISMATCH', message: mismatch });
|
|
31
|
+
// A version this build cannot write is the serializer's VERSION_MISMATCH.
|
|
53
32
|
}
|
|
54
33
|
// The DECLARED grammar version (`.schemaVersion()`), as opposed to the
|
|
55
34
|
// deprecated `.version()` above. It is already refused at the call, so this
|
|
@@ -97,55 +76,10 @@ export function check(built, opts = {}) {
|
|
|
97
76
|
where,
|
|
98
77
|
});
|
|
99
78
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
for (const stage of built.stages) {
|
|
79
|
+
// A repeated stage label or task name, and the select-next-stage exit rules,
|
|
80
|
+
// are the serializer's (DUP_STAGE, DUP_TASK, SELECT_NEXT_STAGE_*).
|
|
81
|
+
for (const stage of built.stages)
|
|
104
82
|
checkName('Stage label', stage.label, stage.label);
|
|
105
|
-
if (stageLabels.has(stage.label))
|
|
106
|
-
diags.push({ level: 'error', code: 'DUP_STAGE', message: `Duplicate stage label "${stage.label}".`, where: stage.label });
|
|
107
|
-
stageLabels.add(stage.label);
|
|
108
|
-
for (const exit of stage.exit) {
|
|
109
|
-
if (!exit.selectNextStage)
|
|
110
|
-
continue;
|
|
111
|
-
selectNextStageCount += 1;
|
|
112
|
-
if (exit.type !== 'wait-for-user')
|
|
113
|
-
diags.push({
|
|
114
|
-
level: 'error',
|
|
115
|
-
code: 'SELECT_NEXT_STAGE_EXIT_TYPE',
|
|
116
|
-
message: `Stage "${stage.label}" configures selectNextStage on an exit that is not type "wait-for-user".`,
|
|
117
|
-
where: stage.label,
|
|
118
|
-
});
|
|
119
|
-
if (!exit.selectNextStage.objectName.trim())
|
|
120
|
-
diags.push({
|
|
121
|
-
level: 'error',
|
|
122
|
-
code: 'SELECT_NEXT_STAGE_OBJECT_NAME',
|
|
123
|
-
message: `Stage "${stage.label}" selectNextStage.objectName must name a Data Fabric entity.`,
|
|
124
|
-
where: stage.label,
|
|
125
|
-
});
|
|
126
|
-
// Its connector's labels resolve through the same `bindingRef` as a
|
|
127
|
-
// connector task's, so they get the same binding-label rules.
|
|
128
|
-
const connector = exit.selectNextStage.connector;
|
|
129
|
-
if (connector)
|
|
130
|
-
checkBindingLabels('select-next-stage connector', connector, opts, (code, message) => diags.push({ level: 'error', code, message, where: stage.label }), { key: connector.connectorKey });
|
|
131
|
-
}
|
|
132
|
-
for (const lane of stage.lanes)
|
|
133
|
-
for (const t of lane) {
|
|
134
|
-
// Task displayNames must be unique across the case: the platform rejects
|
|
135
|
-
// duplicates (CRITICAL), and a bare selected-tasks-completed reference
|
|
136
|
-
// would silently mis-resolve to the first task of that name.
|
|
137
|
-
if (taskNames.has(t.name))
|
|
138
|
-
diags.push({
|
|
139
|
-
level: 'error',
|
|
140
|
-
code: 'DUP_TASK',
|
|
141
|
-
message: `Duplicate task displayName "${t.name}" — task names must be unique across the case.`,
|
|
142
|
-
where: `${stage.label}/${t.name}`,
|
|
143
|
-
});
|
|
144
|
-
taskNames.add(t.name);
|
|
145
|
-
// Also register the qualified `Stage/Task` key so a qualified reference resolves.
|
|
146
|
-
taskNames.add(`${stage.label}/${t.name}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
83
|
// Condition `displayName`s must be unique across the whole case — `uip maestro
|
|
150
84
|
// case validate` rejects a repeat as CASE_MGMT_RULE_NAME_DUPLICATE, at error
|
|
151
85
|
// severity. The namespace spans stage entry, stage exit and task entry
|
|
@@ -209,12 +143,6 @@ export function check(built, opts = {}) {
|
|
|
209
143
|
for (const c of task.entry)
|
|
210
144
|
checkConditionName(c.displayName, `task "${task.name}" entry`);
|
|
211
145
|
}
|
|
212
|
-
if (selectNextStageCount > 1)
|
|
213
|
-
diags.push({
|
|
214
|
-
level: 'error',
|
|
215
|
-
code: 'SELECT_NEXT_STAGE_MULTIPLE',
|
|
216
|
-
message: 'A case can configure selectNextStage on at most one exit because the wire contract has one case-level connector.',
|
|
217
|
-
});
|
|
218
146
|
if (built.stages.length === 0)
|
|
219
147
|
diags.push({ level: 'error', code: 'NO_STAGES', message: 'Case has no stages.' });
|
|
220
148
|
const hasCompletion = built.caseExit.some((c) => c.marksCaseComplete !== false);
|
|
@@ -224,35 +152,11 @@ export function check(built, opts = {}) {
|
|
|
224
152
|
code: 'NO_CASE_COMPLETION',
|
|
225
153
|
message: 'Case has no completion rule — add .completeWhen(rule(\'required-stages-completed\')).',
|
|
226
154
|
});
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
const businessEvents = new Set((built.businessEvents ?? []).map((event) => event.name));
|
|
232
|
-
for (const problem of businessEventProblems(built.businessEvents ?? []))
|
|
233
|
-
diags.push({ level: 'error', code: problem.code, message: problem.message });
|
|
234
|
-
const refEvent = (r, where) => {
|
|
235
|
-
if (r.rule !== 'api-event')
|
|
236
|
-
return;
|
|
237
|
-
if (!r.businessEvent)
|
|
238
|
-
diags.push({
|
|
239
|
-
level: 'error',
|
|
240
|
-
code: 'API_EVENT_NO_BUSINESS_EVENT',
|
|
241
|
-
message: 'Rule "api-event" names no business event, so it listens for nothing — pass { businessEvent: \'<name>\' } with a name declared by .businessEvent(\'<name>\').',
|
|
242
|
-
where,
|
|
243
|
-
});
|
|
244
|
-
else if (!businessEvents.has(r.businessEvent))
|
|
245
|
-
diags.push({
|
|
246
|
-
level: 'error',
|
|
247
|
-
code: 'UNKNOWN_BUSINESS_EVENT',
|
|
248
|
-
message: `Rule "api-event" listens for business event "${r.businessEvent}", which the case does not declare — add .businessEvent(${JSON.stringify(r.businessEvent)}) to the case.`,
|
|
249
|
-
where,
|
|
250
|
-
});
|
|
251
|
-
};
|
|
155
|
+
// The business events' shapes, a rule's shape, an `api-event` rule's event,
|
|
156
|
+
// and a rule's stage and task references are the serializer's
|
|
157
|
+
// (`businessEventProblems`, `ruleShapeProblems`, API_EVENT_NO_BUSINESS_EVENT,
|
|
158
|
+
// UNKNOWN_BUSINESS_EVENT, UNKNOWN_STAGE_REF, UNKNOWN_TASK_REF).
|
|
252
159
|
const refPlacement = (r, slot, where) => {
|
|
253
|
-
for (const problem of ruleShapeProblems(r))
|
|
254
|
-
diags.push({ level: 'error', code: problem.code, message: problem.message, where });
|
|
255
|
-
refEvent(r, where);
|
|
256
160
|
const bad = ILLEGAL.find((x) => x.rule === r.rule && x.slot === slot);
|
|
257
161
|
if (bad)
|
|
258
162
|
diags.push({
|
|
@@ -263,28 +167,16 @@ export function check(built, opts = {}) {
|
|
|
263
167
|
});
|
|
264
168
|
};
|
|
265
169
|
const refStage = (r, where) => {
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
level: 'warning',
|
|
274
|
-
code: 'RULE_TASKS_REQUIRED',
|
|
275
|
-
message: 'Rule "selected-tasks-completed" has neither selected tasks nor an expression gate — pass { tasks: [...] } or use when(expression).',
|
|
276
|
-
where,
|
|
277
|
-
});
|
|
278
|
-
for (const n of r.tasks ?? [])
|
|
279
|
-
if (!taskNames.has(n))
|
|
280
|
-
diags.push({ level: 'error', code: 'UNKNOWN_TASK_REF', message: `Rule "selected-tasks-completed" references unknown task "${n}".`, where });
|
|
281
|
-
}
|
|
170
|
+
if (r.rule === 'selected-tasks-completed' && !r.tasks?.length && !r.expression)
|
|
171
|
+
diags.push({
|
|
172
|
+
level: 'warning',
|
|
173
|
+
code: 'RULE_TASKS_REQUIRED',
|
|
174
|
+
message: 'Rule "selected-tasks-completed" has neither selected tasks nor an expression gate — pass { tasks: [...] } or use when(expression).',
|
|
175
|
+
where,
|
|
176
|
+
});
|
|
282
177
|
};
|
|
283
|
-
// An In-arg with no `{ from }`
|
|
284
|
-
//
|
|
285
|
-
for (const vd of built.vars)
|
|
286
|
-
if (isUnboundInArg(vd, built.triggers))
|
|
287
|
-
diags.push({ level: 'error', code: 'CASE_IN_ARG_UNBOUND', message: unboundInArgMessage(vd.name, built.triggers), where: vd.name });
|
|
178
|
+
// An In-arg with no `{ from }` and no primary trigger to bind to is the
|
|
179
|
+
// serializer's CASE_IN_ARG_UNBOUND (in-arg-binding.ts), reported per arg.
|
|
288
180
|
// ── `=js:vars.<name>` resolvability — REMOVED, superseded by #257 ───────────
|
|
289
181
|
// This file briefly carried a VAR_NOT_REFERENCEABLE error asserting that only a
|
|
290
182
|
// trigger-bound In-arg (`.input(shape, { from })`) could be read as `=js:vars.x`,
|
|
@@ -303,7 +195,7 @@ export function check(built, opts = {}) {
|
|
|
303
195
|
// positive that blocks emit (this gate runs before serialization), so it must be
|
|
304
196
|
// re-derived from the serializer rather than from remembered probe results.
|
|
305
197
|
for (const stage of built.stages) {
|
|
306
|
-
checkStage(stage, diags, refStage, refPlacement
|
|
198
|
+
checkStage(stage, diags, refStage, refPlacement);
|
|
307
199
|
stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
|
|
308
200
|
}
|
|
309
201
|
built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
|
|
@@ -314,98 +206,88 @@ export function check(built, opts = {}) {
|
|
|
314
206
|
refPlacement(r, 'case-exit', 'case-exit');
|
|
315
207
|
}
|
|
316
208
|
checkLibraryConnectors(built, opts, diags);
|
|
209
|
+
diags.push(...serializerRefusals(built, opts, diags));
|
|
317
210
|
return diags;
|
|
318
211
|
}
|
|
319
212
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
213
|
+
* Every refusal `compile` would raise for `built`: the serializer, run in
|
|
214
|
+
* collect mode (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.5, phase 8), so each
|
|
215
|
+
* rule it enforces is reported from its one implementation, placed on the
|
|
216
|
+
* task, stage or trigger it is about (`where`, `nodeId`).
|
|
322
217
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
218
|
+
* LIBRARY_REQUIRED is not reported: without a library `check` fails open, as
|
|
219
|
+
* its library rules always have. A throw that is no refusal comes back as
|
|
220
|
+
* COMPILE_FAILED, on its node, which is what `compile` says; on a node `check`
|
|
221
|
+
* already refuses it is that refusal's consequence and is not repeated. An
|
|
222
|
+
* `internal SDK error:` is passed on, unless `check` or the serializer already
|
|
223
|
+
* found an error, which the author has to fix first.
|
|
224
|
+
*/
|
|
225
|
+
function serializerRefusals(built, opts, found) {
|
|
226
|
+
const collected = [];
|
|
227
|
+
try {
|
|
228
|
+
serialize(built, {
|
|
229
|
+
...(opts.library ? { library: opts.library } : {}),
|
|
230
|
+
...(opts.bindings ? { bindings: opts.bindings } : {}),
|
|
231
|
+
collect: (r) => collected.push(r),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (!found.some((d) => d.level === 'error') && !collected.some((r) => r.code !== 'LIBRARY_REQUIRED'))
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
const out = [];
|
|
239
|
+
const seen = new Set();
|
|
240
|
+
for (const r of collected) {
|
|
241
|
+
if (r.code === 'LIBRARY_REQUIRED')
|
|
242
|
+
continue;
|
|
243
|
+
// Only a crash on a node is the consequence of a finding there; a scope-level
|
|
244
|
+
// one (no `where`) is reported even beside a case-level error such as NO_ID.
|
|
245
|
+
if (r.code === 'COMPILE_FAILED' && r.where !== undefined && found.some((d) => d.level === 'error' && d.where === r.where))
|
|
246
|
+
continue;
|
|
247
|
+
// One finding per node: the same refusal twice there is one rule broken once.
|
|
248
|
+
const key = `${r.code}\n${r.where ?? ''}\n${r.message}`;
|
|
249
|
+
if (seen.has(key))
|
|
250
|
+
continue;
|
|
251
|
+
seen.add(key);
|
|
252
|
+
out.push({
|
|
253
|
+
level: 'error', code: r.code, message: r.message,
|
|
254
|
+
...(r.where !== undefined ? { where: r.where } : {}),
|
|
255
|
+
...(r.suggestion !== undefined ? { suggestion: r.suggestion } : {}),
|
|
256
|
+
...(r.nodeId !== undefined ? { nodeId: r.nodeId } : {}),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The connector-library warnings only `check` gives (#1014): a connector task's
|
|
263
|
+
* schema-readiness warnings, because the `connectors-local` overlay `registry
|
|
264
|
+
* prepare` writes is what Case compile resolves against too, and the lookup
|
|
265
|
+
* rules of `checkConnectorLookups` (a pasted id, a match key the field does not
|
|
266
|
+
* declare). Everything compile refuses (the operation, its inputs, an
|
|
267
|
+
* unrecorded lookup, the binding labels, the event subscriptions, an external
|
|
268
|
+
* task's descriptor) is the serializer's (`serializerRefusals`).
|
|
338
269
|
*/
|
|
339
270
|
function checkLibraryConnectors(built, opts, diags) {
|
|
340
|
-
const reporter = (
|
|
271
|
+
const reporter = (at) => (d) => {
|
|
341
272
|
diags.push({ level: d.level, code: d.code, message: d.message, where: at, ...(d.suggestion ? { suggestion: d.suggestion } : {}) });
|
|
342
273
|
};
|
|
343
|
-
const subscription = (kind, sub, where, at) => {
|
|
344
|
-
// The call `resolvedSubscriptionBag` makes (`resolveCaseSubscription`), so
|
|
345
|
-
// check refuses exactly what compile refuses, with the same text.
|
|
346
|
-
const refused = opts.subscriptionError?.(kind, sub);
|
|
347
|
-
if (refused !== undefined)
|
|
348
|
-
diags.push({ level: 'error', code: refused.code, message: `${where}: ${refused.message}`, where: at });
|
|
349
|
-
checkBindingLabels(where, sub, opts, (code, message) => diags.push({ level: 'error', code, message, where: at }), { key: sub.connector, action: sub.event });
|
|
350
|
-
// From the shared event rules, the two Case compile enforces: a `where` lookup
|
|
351
|
-
// nobody recorded (LOOKUP_UNRESOLVED, a warning) and an unverified BYOA
|
|
352
|
-
// connection (EVENT_BYOA_UNVERIFIED). Only their hooks are passed, so nothing
|
|
353
|
-
// else in them fires.
|
|
354
|
-
checkEventResolution(sub, where, kind, {
|
|
355
|
-
resolutions: opts.resolutions ?? {},
|
|
356
|
-
...(opts.eventFacts ? { eventFacts: opts.eventFacts } : {}),
|
|
357
|
-
...(opts.byoa ? { byoa: opts.byoa } : {}),
|
|
358
|
-
...(opts.bindings ? { bindings: opts.bindings } : {}),
|
|
359
|
-
}, (code, message) => diags.push({ level: 'error', code, message, where: at }), (code, message) => diags.push({ level: 'warning', code, message, where: at }));
|
|
360
|
-
};
|
|
361
|
-
const rules = (conditions, where) => {
|
|
362
|
-
for (const c of conditions)
|
|
363
|
-
for (const g of c.rules)
|
|
364
|
-
for (const r of g)
|
|
365
|
-
if (r.rule === 'wait-for-connector' && isSubscription(r.connector))
|
|
366
|
-
subscription('event', r.connector, `wait-for-connector rule in ${where}`, where);
|
|
367
|
-
};
|
|
368
|
-
built.triggers.forEach((t, i) => {
|
|
369
|
-
if (t.kind !== 'event' || !t.eventSubscription)
|
|
370
|
-
return;
|
|
371
|
-
const name = t.name ?? `Trigger ${i + 1}`;
|
|
372
|
-
subscription('trigger', t.eventSubscription, `Event trigger "${name}"`, name);
|
|
373
|
-
});
|
|
374
274
|
for (const stage of built.stages) {
|
|
375
|
-
rules(stage.entry, `stage "${stage.label}" entry`);
|
|
376
|
-
rules(stage.exit, `stage "${stage.label}" exit`);
|
|
377
275
|
for (const t of stage.lanes.flat()) {
|
|
378
|
-
rules(t.entry, `task "${t.name}" entry`);
|
|
379
276
|
// A placeholder (`.unresolved('connector')`) names no operation to resolve.
|
|
380
277
|
if (t.kind === 'connector' && t.connector && !isConnectorPlaceholder(t)) {
|
|
381
278
|
const where = `Connector task "${t.name}"`;
|
|
382
279
|
const spec = t.connector;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
280
|
+
// An unrecorded lookup is the serializer's LOOKUP_UNRESOLVED.
|
|
281
|
+
checkConnectorLookups(where, spec, opts, EQUALS_EXPR_DIALECT, reporter(t.name), false);
|
|
282
|
+
checkConnectorSchema(where, spec, opts, EQUALS_EXPR_DIALECT, reporter(t.name));
|
|
386
283
|
}
|
|
387
284
|
if ((t.kind === 'external-agent' || t.kind === 'external-workflow')
|
|
388
285
|
&& t.external?.connector?.key?.trim() && t.external.connector.action?.trim()) {
|
|
389
286
|
const where = `${t.kind === 'external-agent' ? 'External Agent' : 'External Workflow'} task "${t.name}"`;
|
|
390
|
-
|
|
391
|
-
checkConnectorLookups(where, spec, opts, EQUALS_EXPR_DIALECT, reporter(where, t.name));
|
|
392
|
-
checkConnectorContract(where, spec, opts, reporter(where, t.name));
|
|
393
|
-
const refused = opts.externalDescriptorError?.(t.kind, spec);
|
|
394
|
-
if (refused !== undefined)
|
|
395
|
-
diags.push({ level: 'error', code: EXTERNAL_TASK_DESCRIPTOR_CONTRACT, message: `${where}: ${refused}`, where: t.name });
|
|
396
|
-
const execution = opts.externalExecutionError?.(where, spec, t.external);
|
|
397
|
-
if (execution !== undefined)
|
|
398
|
-
diags.push({ level: 'error', code: execution.code, message: execution.message, where: t.name });
|
|
287
|
+
checkConnectorLookups(where, t.external.connector, opts, EQUALS_EXPR_DIALECT, reporter(t.name), false);
|
|
399
288
|
}
|
|
400
|
-
if (t.kind === 'wait-for-connector' && isSubscription(t.waitConnector))
|
|
401
|
-
subscription('event', t.waitConnector, `Wait-for-connector task "${t.name}"`, t.name);
|
|
402
289
|
}
|
|
403
290
|
}
|
|
404
|
-
rules(built.caseExit, 'case exit');
|
|
405
|
-
}
|
|
406
|
-
/** A resolved subscription, not a placeholder; the test `serialize`'s `isResolvedSubscription` makes. */
|
|
407
|
-
function isSubscription(spec) {
|
|
408
|
-
return !!spec && typeof spec === 'object' && 'connector' in spec && 'event' in spec;
|
|
409
291
|
}
|
|
410
292
|
/**
|
|
411
293
|
* SLA guardrails surfaced early: zero at-risk percentages that the engine
|
|
@@ -446,9 +328,7 @@ function isNilOrEmpty(value) {
|
|
|
446
328
|
return Object.keys(value).length === 0;
|
|
447
329
|
return false;
|
|
448
330
|
}
|
|
449
|
-
function checkStage(stage, diags, refStage, refPlacement
|
|
450
|
-
/** A library judges external tasks' mode and event (`externalExecutionError`). */
|
|
451
|
-
externalJudge) {
|
|
331
|
+
function checkStage(stage, diags, refStage, refPlacement) {
|
|
452
332
|
const where = `stage "${stage.label}"`;
|
|
453
333
|
if (stage.exception && stage.required !== false)
|
|
454
334
|
diags.push({
|
|
@@ -518,9 +398,9 @@ externalJudge) {
|
|
|
518
398
|
});
|
|
519
399
|
for (const t of tasks) {
|
|
520
400
|
// A timer with no or several values, an action field key compile does not
|
|
521
|
-
// write
|
|
522
|
-
|
|
523
|
-
|
|
401
|
+
// write, a `=bindings.<label>` resource name bindings.json cannot answer, and
|
|
402
|
+
// an external task's mode, connection and folder are the serializer's
|
|
403
|
+
// (`taskShapeProblems`, `resolveResourceRef`, EXTERNAL_TASK_*).
|
|
524
404
|
const invalidBinding = [...(t.inputs ?? []), ...(t.outputs ?? [])]
|
|
525
405
|
.find((binding) => !TYPE_NAMES.has(binding.type));
|
|
526
406
|
if (invalidBinding)
|
|
@@ -530,16 +410,6 @@ externalJudge) {
|
|
|
530
410
|
message: `Task "${t.name}" binding "${'var' in invalidBinding ? invalidBinding.var : invalidBinding.name}" has unsupported type ${JSON.stringify(invalidBinding.type)}. Use a type name such as "string" or "jsonSchema"; jsonSchema(...) is only for declaring a structured case variable.`,
|
|
531
411
|
where: t.name,
|
|
532
412
|
});
|
|
533
|
-
// The builder binds a resource task itself: it mints the name/folderPath
|
|
534
|
-
// binding pair from the literal name and folder. A `=bindings.<label>` name
|
|
535
|
-
// or folder is read through bindings.json first, the way a connector's
|
|
536
|
-
// `connection:` label is; one bindings.json cannot answer is refused with the
|
|
537
|
-
// same text serialize throws (`resolveResourceRef`).
|
|
538
|
-
if (t.ref) {
|
|
539
|
-
const resolved = resolveResourceRef(t.ref, bindings, `Task "${t.name}"`);
|
|
540
|
-
if (isResourceLabelRefusal(resolved))
|
|
541
|
-
diags.push({ level: 'error', code: resolved.code, message: resolved.message, where: t.name });
|
|
542
|
-
}
|
|
543
413
|
// A custom row (`{ assign }`) is evaluated in array order, after the rows
|
|
544
414
|
// before it on the same task: it cannot read itself or a later row's
|
|
545
415
|
// variable (it would see the old value). `uip maestro case validate
|
|
@@ -628,39 +498,13 @@ externalJudge) {
|
|
|
628
498
|
message: `${family} task "${t.name}" has no configuration; empty data degrades to a runtime no-op placeholder.`,
|
|
629
499
|
where: t.name,
|
|
630
500
|
});
|
|
631
|
-
else
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const judged = externalExecution(`${family} task "${t.name}"`, t.external, undefined);
|
|
639
|
-
if (judged?.code !== undefined)
|
|
640
|
-
diags.push({ level: 'error', code: judged.code, message: judged.message, where: t.name });
|
|
641
|
-
}
|
|
642
|
-
if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
|
|
643
|
-
diags.push({
|
|
644
|
-
level: 'error',
|
|
645
|
-
code: 'EXTERNAL_TASK_DESCRIPTOR',
|
|
646
|
-
message: `${family} task "${t.name}" requires a generated Integration Service descriptor.`,
|
|
647
|
-
where: t.name,
|
|
648
|
-
});
|
|
649
|
-
if (!t.external.connector?.connection?.trim())
|
|
650
|
-
diags.push({
|
|
651
|
-
level: 'error',
|
|
652
|
-
code: 'EXTERNAL_TASK_CONNECTION',
|
|
653
|
-
message: `${family} task "${t.name}" requires a non-empty connection binding name.`,
|
|
654
|
-
where: t.name,
|
|
655
|
-
});
|
|
656
|
-
if (!t.external.connector?.folder?.trim())
|
|
657
|
-
diags.push({
|
|
658
|
-
level: 'error',
|
|
659
|
-
code: 'EXTERNAL_TASK_FOLDER',
|
|
660
|
-
message: `${family} task "${t.name}" requires a non-empty folder binding name.`,
|
|
661
|
-
where: t.name,
|
|
662
|
-
});
|
|
663
|
-
}
|
|
501
|
+
else if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
|
|
502
|
+
diags.push({
|
|
503
|
+
level: 'error',
|
|
504
|
+
code: 'EXTERNAL_TASK_DESCRIPTOR',
|
|
505
|
+
message: `${family} task "${t.name}" requires a generated Integration Service descriptor.`,
|
|
506
|
+
where: t.name,
|
|
507
|
+
});
|
|
664
508
|
}
|
|
665
509
|
if (stage.exception && t.runOnce !== undefined)
|
|
666
510
|
diags.push({
|
|
@@ -4,13 +4,10 @@ import type { CaseCheckOpts } from './check.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Case's check options from a library the CLI already opened, or `{}` without one.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* With `bindings`, the binding-label rules (BINDING_SELF_NAME, BINDING_UNDECLARED),
|
|
12
|
-
* which Case `serialize` enforces through the same `bindingLabelRefusal`, and the
|
|
13
|
-
* BYOA facts and ledger, for the refusal `resolveCaseSubscription` makes.
|
|
7
|
+
* The library and bindings themselves, for the serializer `check` runs in
|
|
8
|
+
* collect mode (it reports every refusal compile makes, from compile's own
|
|
9
|
+
* code), plus the hooks for the warnings only `check` gives: a connector task's
|
|
10
|
+
* lookup rules and the schema-readiness warnings.
|
|
14
11
|
*
|
|
15
12
|
* @param library - The library `openCliLibrary` returned, or `undefined`.
|
|
16
13
|
* @param bindings - The project's bindings.json, as compile reads it.
|