@uipath/maestro-builder-sdk 6.68.6 → 6.69.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.
- package/dist/api-index.md +33 -33
- package/dist/api-members.md +158 -158
- package/dist/bpmn/intsvc.js +10 -0
- package/dist/case/case-sdk.d.ts +22 -17
- package/dist/case/case-sdk.js +1 -1
- package/dist/case/check.d.ts +25 -38
- package/dist/case/check.js +93 -244
- package/dist/case/cli-library.d.ts +4 -7
- package/dist/case/cli-library.js +5 -35
- package/dist/case/decompile.js +16 -3
- package/dist/case/external.d.ts +46 -0
- package/dist/case/external.js +79 -1
- package/dist/case/serialize.d.ts +48 -2
- package/dist/case/serialize.js +282 -78
- 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/registry/library-build/enrich.d.ts +20 -0
- package/dist/registry/library-build/enrich.js +40 -0
- package/dist/registry/model/library.d.ts +10 -0
- package/package.json +2 -2
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, EXTERNAL_TASK_EVENT, externalEventRefusal, isExternalExecutionMode } 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,93 +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
|
-
|
|
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`).
|
|
336
269
|
*/
|
|
337
270
|
function checkLibraryConnectors(built, opts, diags) {
|
|
338
|
-
const reporter = (
|
|
271
|
+
const reporter = (at) => (d) => {
|
|
339
272
|
diags.push({ level: d.level, code: d.code, message: d.message, where: at, ...(d.suggestion ? { suggestion: d.suggestion } : {}) });
|
|
340
273
|
};
|
|
341
|
-
const subscription = (kind, sub, where, at) => {
|
|
342
|
-
// The call `resolvedSubscriptionBag` makes (`resolveCaseSubscription`), so
|
|
343
|
-
// check refuses exactly what compile refuses, with the same text.
|
|
344
|
-
const refused = opts.subscriptionError?.(kind, sub);
|
|
345
|
-
if (refused !== undefined)
|
|
346
|
-
diags.push({ level: 'error', code: refused.code, message: `${where}: ${refused.message}`, where: at });
|
|
347
|
-
checkBindingLabels(where, sub, opts, (code, message) => diags.push({ level: 'error', code, message, where: at }), { key: sub.connector, action: sub.event });
|
|
348
|
-
// From the shared event rules, the two Case compile enforces: a `where` lookup
|
|
349
|
-
// nobody recorded (LOOKUP_UNRESOLVED, a warning) and an unverified BYOA
|
|
350
|
-
// connection (EVENT_BYOA_UNVERIFIED). Only their hooks are passed, so nothing
|
|
351
|
-
// else in them fires.
|
|
352
|
-
checkEventResolution(sub, where, kind, {
|
|
353
|
-
resolutions: opts.resolutions ?? {},
|
|
354
|
-
...(opts.eventFacts ? { eventFacts: opts.eventFacts } : {}),
|
|
355
|
-
...(opts.byoa ? { byoa: opts.byoa } : {}),
|
|
356
|
-
...(opts.bindings ? { bindings: opts.bindings } : {}),
|
|
357
|
-
}, (code, message) => diags.push({ level: 'error', code, message, where: at }), (code, message) => diags.push({ level: 'warning', code, message, where: at }));
|
|
358
|
-
};
|
|
359
|
-
const rules = (conditions, where) => {
|
|
360
|
-
for (const c of conditions)
|
|
361
|
-
for (const g of c.rules)
|
|
362
|
-
for (const r of g)
|
|
363
|
-
if (r.rule === 'wait-for-connector' && isSubscription(r.connector))
|
|
364
|
-
subscription('event', r.connector, `wait-for-connector rule in ${where}`, where);
|
|
365
|
-
};
|
|
366
|
-
built.triggers.forEach((t, i) => {
|
|
367
|
-
if (t.kind !== 'event' || !t.eventSubscription)
|
|
368
|
-
return;
|
|
369
|
-
const name = t.name ?? `Trigger ${i + 1}`;
|
|
370
|
-
subscription('trigger', t.eventSubscription, `Event trigger "${name}"`, name);
|
|
371
|
-
});
|
|
372
274
|
for (const stage of built.stages) {
|
|
373
|
-
rules(stage.entry, `stage "${stage.label}" entry`);
|
|
374
|
-
rules(stage.exit, `stage "${stage.label}" exit`);
|
|
375
275
|
for (const t of stage.lanes.flat()) {
|
|
376
|
-
rules(t.entry, `task "${t.name}" entry`);
|
|
377
276
|
// A placeholder (`.unresolved('connector')`) names no operation to resolve.
|
|
378
277
|
if (t.kind === 'connector' && t.connector && !isConnectorPlaceholder(t)) {
|
|
379
278
|
const where = `Connector task "${t.name}"`;
|
|
380
279
|
const spec = t.connector;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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));
|
|
384
283
|
}
|
|
385
284
|
if ((t.kind === 'external-agent' || t.kind === 'external-workflow')
|
|
386
285
|
&& t.external?.connector?.key?.trim() && t.external.connector.action?.trim()) {
|
|
387
286
|
const where = `${t.kind === 'external-agent' ? 'External Agent' : 'External Workflow'} task "${t.name}"`;
|
|
388
|
-
|
|
389
|
-
checkConnectorLookups(where, spec, opts, EQUALS_EXPR_DIALECT, reporter(where, t.name));
|
|
390
|
-
checkConnectorContract(where, spec, opts, reporter(where, t.name));
|
|
391
|
-
const refused = opts.externalDescriptorError?.(t.kind, spec);
|
|
392
|
-
if (refused !== undefined)
|
|
393
|
-
diags.push({ level: 'error', code: EXTERNAL_TASK_DESCRIPTOR_CONTRACT, message: `${where}: ${refused}`, where: t.name });
|
|
287
|
+
checkConnectorLookups(where, t.external.connector, opts, EQUALS_EXPR_DIALECT, reporter(t.name), false);
|
|
394
288
|
}
|
|
395
|
-
if (t.kind === 'wait-for-connector' && isSubscription(t.waitConnector))
|
|
396
|
-
subscription('event', t.waitConnector, `Wait-for-connector task "${t.name}"`, t.name);
|
|
397
289
|
}
|
|
398
290
|
}
|
|
399
|
-
rules(built.caseExit, 'case exit');
|
|
400
|
-
}
|
|
401
|
-
/** A resolved subscription, not a placeholder; the test `serialize`'s `isResolvedSubscription` makes. */
|
|
402
|
-
function isSubscription(spec) {
|
|
403
|
-
return !!spec && typeof spec === 'object' && 'connector' in spec && 'event' in spec;
|
|
404
291
|
}
|
|
405
292
|
/**
|
|
406
293
|
* SLA guardrails surfaced early: zero at-risk percentages that the engine
|
|
@@ -441,7 +328,7 @@ function isNilOrEmpty(value) {
|
|
|
441
328
|
return Object.keys(value).length === 0;
|
|
442
329
|
return false;
|
|
443
330
|
}
|
|
444
|
-
function checkStage(stage, diags, refStage, refPlacement
|
|
331
|
+
function checkStage(stage, diags, refStage, refPlacement) {
|
|
445
332
|
const where = `stage "${stage.label}"`;
|
|
446
333
|
if (stage.exception && stage.required !== false)
|
|
447
334
|
diags.push({
|
|
@@ -511,9 +398,9 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
|
|
|
511
398
|
});
|
|
512
399
|
for (const t of tasks) {
|
|
513
400
|
// A timer with no or several values, an action field key compile does not
|
|
514
|
-
// write
|
|
515
|
-
|
|
516
|
-
|
|
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_*).
|
|
517
404
|
const invalidBinding = [...(t.inputs ?? []), ...(t.outputs ?? [])]
|
|
518
405
|
.find((binding) => !TYPE_NAMES.has(binding.type));
|
|
519
406
|
if (invalidBinding)
|
|
@@ -523,16 +410,6 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
|
|
|
523
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.`,
|
|
524
411
|
where: t.name,
|
|
525
412
|
});
|
|
526
|
-
// The builder binds a resource task itself: it mints the name/folderPath
|
|
527
|
-
// binding pair from the literal name and folder. A `=bindings.<label>` name
|
|
528
|
-
// or folder is read through bindings.json first, the way a connector's
|
|
529
|
-
// `connection:` label is; one bindings.json cannot answer is refused with the
|
|
530
|
-
// same text serialize throws (`resolveResourceRef`).
|
|
531
|
-
if (t.ref) {
|
|
532
|
-
const resolved = resolveResourceRef(t.ref, bindings, `Task "${t.name}"`);
|
|
533
|
-
if (isResourceLabelRefusal(resolved))
|
|
534
|
-
diags.push({ level: 'error', code: resolved.code, message: resolved.message, where: t.name });
|
|
535
|
-
}
|
|
536
413
|
// A custom row (`{ assign }`) is evaluated in array order, after the rows
|
|
537
414
|
// before it on the same task: it cannot read itself or a later row's
|
|
538
415
|
// variable (it would see the old value). `uip maestro case validate
|
|
@@ -621,41 +498,13 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
|
|
|
621
498
|
message: `${family} task "${t.name}" has no configuration; empty data degrades to a runtime no-op placeholder.`,
|
|
622
499
|
where: t.name,
|
|
623
500
|
});
|
|
624
|
-
else
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
});
|
|
632
|
-
else {
|
|
633
|
-
const refused = externalEventRefusal(`${family} task "${t.name}"`, t.external.mode, t.external.event);
|
|
634
|
-
if (refused)
|
|
635
|
-
diags.push({ level: 'error', code: EXTERNAL_TASK_EVENT, message: refused, where: t.name });
|
|
636
|
-
}
|
|
637
|
-
if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
|
|
638
|
-
diags.push({
|
|
639
|
-
level: 'error',
|
|
640
|
-
code: 'EXTERNAL_TASK_DESCRIPTOR',
|
|
641
|
-
message: `${family} task "${t.name}" requires a generated Integration Service descriptor.`,
|
|
642
|
-
where: t.name,
|
|
643
|
-
});
|
|
644
|
-
if (!t.external.connector?.connection?.trim())
|
|
645
|
-
diags.push({
|
|
646
|
-
level: 'error',
|
|
647
|
-
code: 'EXTERNAL_TASK_CONNECTION',
|
|
648
|
-
message: `${family} task "${t.name}" requires a non-empty connection binding name.`,
|
|
649
|
-
where: t.name,
|
|
650
|
-
});
|
|
651
|
-
if (!t.external.connector?.folder?.trim())
|
|
652
|
-
diags.push({
|
|
653
|
-
level: 'error',
|
|
654
|
-
code: 'EXTERNAL_TASK_FOLDER',
|
|
655
|
-
message: `${family} task "${t.name}" requires a non-empty folder binding name.`,
|
|
656
|
-
where: t.name,
|
|
657
|
-
});
|
|
658
|
-
}
|
|
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
|
+
});
|
|
659
508
|
}
|
|
660
509
|
if (stage.exception && t.runOnce !== undefined)
|
|
661
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.
|
package/dist/case/cli-library.js
CHANGED
|
@@ -8,18 +8,13 @@
|
|
|
8
8
|
* both `case check` and `case compile`'s check pass call.
|
|
9
9
|
*/
|
|
10
10
|
import { connectorCheckOpts } from '../core/cli-library.js';
|
|
11
|
-
import { externalDescriptorRefusal } from './external.js';
|
|
12
|
-
import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscription } from './subscription.js';
|
|
13
11
|
/**
|
|
14
12
|
* Case's check options from a library the CLI already opened, or `{}` without one.
|
|
15
13
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* With `bindings`, the binding-label rules (BINDING_SELF_NAME, BINDING_UNDECLARED),
|
|
21
|
-
* which Case `serialize` enforces through the same `bindingLabelRefusal`, and the
|
|
22
|
-
* BYOA facts and ledger, for the refusal `resolveCaseSubscription` makes.
|
|
14
|
+
* The library and bindings themselves, for the serializer `check` runs in
|
|
15
|
+
* collect mode (it reports every refusal compile makes, from compile's own
|
|
16
|
+
* code), plus the hooks for the warnings only `check` gives: a connector task's
|
|
17
|
+
* lookup rules and the schema-readiness warnings.
|
|
23
18
|
*
|
|
24
19
|
* @param library - The library `openCliLibrary` returned, or `undefined`.
|
|
25
20
|
* @param bindings - The project's bindings.json, as compile reads it.
|
|
@@ -30,35 +25,10 @@ export function caseCheckOpts(library, bindings) {
|
|
|
30
25
|
return bindings ? { bindings } : {};
|
|
31
26
|
const shared = connectorCheckOpts(library, bindings);
|
|
32
27
|
return {
|
|
28
|
+
library,
|
|
33
29
|
...(bindings ? { bindings } : {}),
|
|
34
|
-
connectorResolveError: shared.connectorResolveError,
|
|
35
|
-
connectorInputs: shared.connectorInputs,
|
|
36
30
|
lookups: shared.lookups,
|
|
37
31
|
resolutions: shared.resolutions,
|
|
38
|
-
eventFacts: shared.eventFacts,
|
|
39
|
-
byoa: shared.byoa,
|
|
40
32
|
schemaReadiness: shared.schemaReadiness,
|
|
41
|
-
externalDescriptorError: (kind, d) => {
|
|
42
|
-
let rc;
|
|
43
|
-
try {
|
|
44
|
-
rc = library.resolve(d.key, d.action, d.version, d.object);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return undefined; // connectorResolveError's to report
|
|
48
|
-
}
|
|
49
|
-
return externalDescriptorRefusal(kind, rc);
|
|
50
|
-
},
|
|
51
|
-
subscriptionError: (kind, sub) => {
|
|
52
|
-
try {
|
|
53
|
-
resolveCaseSubscription(library, kind, sub);
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
return {
|
|
58
|
-
code: error instanceof GenericEventNoObjectError ? EVENT_GENERIC_NO_OBJECT : 'EVENT_UNKNOWN',
|
|
59
|
-
message: error instanceof Error ? error.message : String(error),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
33
|
};
|
|
64
34
|
}
|
package/dist/case/decompile.js
CHANGED
|
@@ -2,7 +2,7 @@ import { commentText } from '../core/comment-text.js';
|
|
|
2
2
|
import { implicitInArgTrigger } from './in-arg-binding.js';
|
|
3
3
|
import { tsLiteral, tsNumber } from '../core/ts-literal.js';
|
|
4
4
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
|
|
5
|
-
import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode } from './external.js';
|
|
5
|
+
import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode, libraryExecution } from './external.js';
|
|
6
6
|
import { preserveSchemaVersion } from '../schema-version.js';
|
|
7
7
|
import { ownEntry } from '../core/own-entry.js';
|
|
8
8
|
import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
|
|
@@ -676,8 +676,8 @@ function externalCall(task, refs, library) {
|
|
|
676
676
|
const serviceType = externalServiceType(kind, mode);
|
|
677
677
|
if (data.serviceType !== serviceType)
|
|
678
678
|
throw new Error(`${owner} has serviceType "${String(data.serviceType ?? '')}", but its executionType "${mode}" saves as "${serviceType}".`);
|
|
679
|
-
// The event the object answers with, as saved
|
|
680
|
-
//
|
|
679
|
+
// The event the object answers with, as saved, judged by compile's event rule
|
|
680
|
+
// here, and against the library's record below (`externalExecution` in compile).
|
|
681
681
|
const event = eventRows.length ? { operation: stringValue('operation'), mode: stringValue('eventMode') } : undefined;
|
|
682
682
|
// A saved plan has no `event` option to drop, so a sync task with event rows
|
|
683
683
|
// gets its own text naming the rows; every other case is compile's rule.
|
|
@@ -699,6 +699,19 @@ function externalCall(task, refs, library) {
|
|
|
699
699
|
const refused = externalDescriptorRefusal(kind, resolved);
|
|
700
700
|
if (refused)
|
|
701
701
|
throw new Error(`${owner}: ${refused}`);
|
|
702
|
+
// Compile refuses a mode or event the library contradicts (#1369), so a task
|
|
703
|
+
// saved against a different record would lift to source that does not compile
|
|
704
|
+
// back: refuse it here, by name, before the author edits anything.
|
|
705
|
+
const recorded = libraryExecution(resolved.entry?.operation);
|
|
706
|
+
if (recorded.mode !== undefined && recorded.mode !== mode)
|
|
707
|
+
throw new Error(`${owner} is saved with executionType "${mode}", but the connector library records the object's ` +
|
|
708
|
+
`executionType as "${recorded.mode}", so the lifted source would not compile back. Re-save the task in Studio Web, ` +
|
|
709
|
+
`or rebuild the library (registry prepare) if the object changed.`);
|
|
710
|
+
if (event !== undefined && recorded.event !== undefined
|
|
711
|
+
&& (event.operation !== recorded.event.operation || event.mode !== recorded.event.mode))
|
|
712
|
+
throw new Error(`${owner} is saved with event ${JSON.stringify(event)}, but the connector library records the object's ` +
|
|
713
|
+
`event as ${JSON.stringify(recorded.event)}, so the lifted source would not compile back. Re-save the task in Studio Web, ` +
|
|
714
|
+
`or rebuild the library (registry prepare) if the object changed.`);
|
|
702
715
|
if (!Array.isArray(data.inputs))
|
|
703
716
|
throw new Error(`${owner} inputs must be an array.`);
|
|
704
717
|
const inputs = connectorInputs(data.inputs, resolved, owner);
|
package/dist/case/external.d.ts
CHANGED
|
@@ -58,6 +58,52 @@ export declare function isExternalExecutionMode(value: unknown): value is Extern
|
|
|
58
58
|
* @returns The service type.
|
|
59
59
|
*/
|
|
60
60
|
export declare function externalServiceType(kind: 'external-agent' | 'external-workflow', mode: ExternalExecutionMode): string;
|
|
61
|
+
/** Check's and compile's code when an authored `mode` disagrees with the library. */
|
|
62
|
+
export declare const EXTERNAL_TASK_MODE_MISMATCH = "EXTERNAL_TASK_MODE_MISMATCH";
|
|
63
|
+
/** A judged execution: the task's mode and event, or why it cannot be written. */
|
|
64
|
+
export type ExternalExecution = {
|
|
65
|
+
mode: ExternalExecutionMode;
|
|
66
|
+
event?: ExternalEventPair;
|
|
67
|
+
code?: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
code: string;
|
|
70
|
+
message: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The execution facts the connector library records for the object
|
|
74
|
+
* (`operation.executionType` and `operation.executionEvent`, from
|
|
75
|
+
* `registry build-library` / `registry prepare`, #1369). A value the SDK does
|
|
76
|
+
* not know is ignored, as if it were absent.
|
|
77
|
+
*/
|
|
78
|
+
export declare function libraryExecution(operation: unknown): {
|
|
79
|
+
mode?: ExternalExecutionMode;
|
|
80
|
+
event?: ExternalEventPair;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The mode and event an external task is written with: what the author passed,
|
|
84
|
+
* else what the connector library records for the object. `check` and
|
|
85
|
+
* `serialize` both call this, so they refuse the same tasks with the same text.
|
|
86
|
+
*
|
|
87
|
+
* - `mode` comes from the author or the library. With neither, the task is
|
|
88
|
+
* refused (EXTERNAL_TASK_MODE): a guessed default would compile an `async`
|
|
89
|
+
* object to the sync contract without a word (#1370 was closed for that).
|
|
90
|
+
* - An authored `mode` the library contradicts is refused
|
|
91
|
+
* (EXTERNAL_TASK_MODE_MISMATCH): the plan would run the wrong contract.
|
|
92
|
+
* - `event` comes from the author or, for an `async` / `hybrid` object, the
|
|
93
|
+
* library; an authored one the library contradicts is refused
|
|
94
|
+
* (EXTERNAL_TASK_EVENT), and so is any pair {@link externalEventRefusal} refuses.
|
|
95
|
+
*
|
|
96
|
+
* @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
|
|
97
|
+
* @param authored - The task's authored `mode` and `event`.
|
|
98
|
+
* @param operation - The resolved library entry's `operation`, or `undefined`
|
|
99
|
+
* when no library is at hand (model-only `check`, which then judges only what
|
|
100
|
+
* was authored and reports no missing mode).
|
|
101
|
+
* @returns The mode and event, or the refusal.
|
|
102
|
+
*/
|
|
103
|
+
export declare function externalExecution(owner: string, authored: {
|
|
104
|
+
mode?: unknown;
|
|
105
|
+
event?: unknown;
|
|
106
|
+
}, operation: unknown): ExternalExecution | undefined;
|
|
61
107
|
/** Check's code for {@link externalDescriptorRefusal}. */
|
|
62
108
|
export declare const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = "EXTERNAL_TASK_DESCRIPTOR_CONTRACT";
|
|
63
109
|
/**
|