@uipath/maestro-builder-sdk 6.68.5 → 6.68.7

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.
@@ -191,6 +191,16 @@ export function createExternalTask(ctx, n) {
191
191
  `${n.key}/${n.action} is "${String(op.subType ?? 'untyped')}". ` +
192
192
  `The shipped library has very few of these — ${wanted === 'AgentExecution' ? '2' : '1'} operation(s).`);
193
193
  }
194
+ // The object's execution type, when the library records it (#1369): a `mode`
195
+ // that contradicts it compiles the other contract, which validate accepts and
196
+ // the run gets wrong. Studio Web runs `hybrid` as `async`.
197
+ const recorded = op.executionType;
198
+ if (recorded === 'sync' || recorded === 'async' || recorded === 'hybrid') {
199
+ const runs = recorded === 'sync' ? 'sync' : 'async';
200
+ if (n.mode !== runs)
201
+ throw new CompileRefusal('EXTERNAL_TASK_MODE_MISMATCH', `external ${n.family} "${n.id}" has mode "${n.mode}", but the connector library records ${n.key}/${n.action}'s ` +
202
+ `executionType as "${recorded}", which runs ${runs}; the node would run the wrong contract. Use mode: "${runs}".`);
203
+ }
194
204
  const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
195
205
  const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
196
206
  if (!conn || !folder) {
@@ -430,9 +430,12 @@ export type ConnectorSpecData = Extract<ActionSpec, {
430
430
  * reports it for the connector object.
431
431
  *
432
432
  * @remarks
433
- * This is a property of the connector object, not a choice about the case: copy
434
- * it from the activity's `case spec` result
435
- * (`Data.EssentialConfiguration.essentialConfiguration.executionType`). The Case
433
+ * This is a property of the connector object, not a choice about the case. The
434
+ * connector library records it for the object when it was built or prepared
435
+ * from Integration Service metadata that reports it, and compile then takes it
436
+ * from there: leave `mode` out. Otherwise copy it from the activity's `case spec`
437
+ * result (`Data.EssentialConfiguration.essentialConfiguration.executionType`).
438
+ * A `mode` the library contradicts is refused (EXTERNAL_TASK_MODE_MISMATCH). The Case
436
439
  * Designer saves `sync` as the `Sync…Execution` service type and both `async`
437
440
  * and `hybrid` as `Async…Execution`, and writes the value itself into the task's
438
441
  * `executionType` context.
@@ -457,10 +460,10 @@ export type ExternalExecutionMode = 'sync' | 'async' | 'hybrid';
457
460
  * the Case Designer reads it from the object's metadata (its first event mode
458
461
  * and that mode's first event type) and saves it as the task's `operation` and
459
462
  * `eventMode` context rows, and as `eventType` / `eventMode` in the metadata's
460
- * `activityPropertyConfiguration`. Copy it from the same two rows of the
461
- * activity's `case spec` result (`caseShape.context`). `decompile` lifts it from
462
- * a saved task. For
463
- * example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
463
+ * `activityPropertyConfiguration`. Like `mode`, compile takes it from the
464
+ * connector library when the library records it; otherwise copy it from the same
465
+ * two rows of the activity's `case spec` result (`caseShape.context`).
466
+ * `decompile` lifts it from a saved task. For example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
464
467
  * `{ operation: 'TRIGGER_CREATED', mode: 'polling' }`.
465
468
  */
466
469
  export interface ExternalEventPair {
@@ -476,16 +479,18 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
476
479
  /** Symbolic Orchestrator folder binding name declared in `bindings.json`. */
477
480
  folder: string;
478
481
  /**
479
- * The connector object's `executionType` from its `case spec` result. Required:
480
- * the connector library does not carry it yet (#1369), and a wrong value passes
481
- * `validate` but runs the wrong contract. Not whether the stage waits: that is
482
+ * The connector object's `executionType`. Leave it out when the connector
483
+ * library records it (compile reads it from there); otherwise copy it from the
484
+ * `case spec` result. Compile refuses a task with no mode from either
485
+ * (EXTERNAL_TASK_MODE), and one the library contradicts
486
+ * (EXTERNAL_TASK_MODE_MISMATCH). Not whether the stage waits: that is
482
487
  * `.required(false)`. See {@link ExternalExecutionMode}.
483
488
  */
484
- mode: ExternalExecutionMode;
489
+ mode?: ExternalExecutionMode;
485
490
  /**
486
- * The event an `async` or `hybrid` object answers with. Required for `hybrid`;
487
- * optional for `async`; refused for `sync`, which answers inline. See
488
- * {@link ExternalEventPair}.
491
+ * The event an `async` or `hybrid` object answers with. Leave it out when the
492
+ * connector library records it. A `hybrid` task needs one from either place;
493
+ * a `sync` one refuses it, since it answers inline. See {@link ExternalEventPair}.
489
494
  */
490
495
  event?: ExternalEventPair;
491
496
  /** Inputs statically checked by the generated connector descriptor. */
@@ -493,9 +498,9 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
493
498
  }
494
499
  /** Normalized descriptor and execution mode stored on a built external task. */
495
500
  export interface ExternalTaskSpecData {
496
- /** The connector object's `executionType`. */
497
- mode: ExternalExecutionMode;
498
- /** The event an `async` or `hybrid` object answers with, when given. */
501
+ /** The connector object's `executionType`, when authored (else the library's). */
502
+ mode?: ExternalExecutionMode;
503
+ /** The event an `async` or `hybrid` object answers with, when authored (else the library's). */
499
504
  event?: ExternalEventPair;
500
505
  /** Shared connector action identity, inputs, and symbolic bindings. */
501
506
  connector: ConnectorSpecData;
@@ -713,9 +718,10 @@ export interface CaseVarDecl {
713
718
  body?: unknown;
714
719
  /**
715
720
  * In-args only: the trigger this argument is bound to (its value arrives when
716
- * that trigger fires). Set via `.input(shape, { from })`. When present, the arg
717
- * emits the full three-entry binding (formal slot + companion + trigger-output
718
- * bridge); when absent it stays a bare declaration.
721
+ * that trigger fires). Set via `.input(shape, { from })`. Every In-arg emits the
722
+ * full three-entry binding (formal slot + companion + trigger-output bridge):
723
+ * when this is absent the arg binds to the primary trigger, and a case with
724
+ * none refuses it (CASE_IN_ARG_UNBOUND). See `.input()`.
719
725
  */
720
726
  sourceTrigger?: BuiltTrigger;
721
727
  }
@@ -1461,10 +1467,28 @@ declare class CaseBuilder {
1461
1467
  layout(layout: CaseLayout): this;
1462
1468
  /**
1463
1469
  * Declare case In-args. Each value is a {@link TypeDesc}, or `{ type, default }`
1464
- * to set a default. Pass `{ from: <trigger> }` to bind the args to a trigger —
1465
- * their value arrives when it fires, readable as `=vars.<name>` — and they are
1466
- * projected into that trigger's `entry-points.json` input schema. A declared
1467
- * In-arg is readable as `=vars.<name>` (its `inputOutputs` companion resolves it).
1470
+ * to set a default. An In-arg is always bound to a trigger: its value arrives
1471
+ * when that trigger starts the case, it is readable as `=vars.<name>` (its
1472
+ * `inputOutputs` companion, keyed to the trigger, resolves it), and it is
1473
+ * projected into that trigger's `entry-points.json` input schema.
1474
+ *
1475
+ * Which trigger:
1476
+ * - `{ from: <trigger> }` binds the args to that trigger.
1477
+ * - Without `{ from }`, they bind to the primary trigger. That is the case's
1478
+ * only trigger (the one added with `.trigger()`, or the implicit manual
1479
+ * trigger when none was added), or, when there are several, the one that is
1480
+ * not a timer. `uip maestro case sdd convert` reads a blank
1481
+ * `sourceTriggers` the same way.
1482
+ * - Without `{ from }` in a case with no primary trigger (several non-timer
1483
+ * triggers, or only timers), `check` and `compile` refuse the plan with
1484
+ * `CASE_IN_ARG_UNBOUND`. Pass `{ from }`, or use `.var()` for a value no
1485
+ * trigger supplies.
1486
+ *
1487
+ * @example
1488
+ * **Declare an In-arg on the primary trigger (here the implicit manual one)**
1489
+ * ```ts
1490
+ * casePlan('x').input({ invoiceNumber: { type: 'string', default: 'INV-1042' } })
1491
+ * ```
1468
1492
  *
1469
1493
  * @example
1470
1494
  * **Bind case In-args to a trigger's payload**
@@ -1475,7 +1499,8 @@ declare class CaseBuilder {
1475
1499
  * ```
1476
1500
  *
1477
1501
  * @param shape - In-arg names to types, or `{ type, default }`.
1478
- * @param opts - `{ from: <trigger> }` binds the args to a trigger's payload.
1502
+ * @param opts - `{ from: <trigger> }` binds the args to that trigger. Required
1503
+ * when the case has no primary trigger.
1479
1504
  * @returns This builder, so calls chain.
1480
1505
  */
1481
1506
  input(shape: Record<string, TypeDesc | JsonSchemaType | {
@@ -333,7 +333,7 @@ class TaskBuilder {
333
333
  external(kind, descriptor, opts) {
334
334
  this.setKind(kind);
335
335
  this._external = {
336
- mode: opts.mode,
336
+ ...(opts.mode !== undefined ? { mode: opts.mode } : {}),
337
337
  ...(opts.event !== undefined ? { event: { ...opts.event } } : {}),
338
338
  connector: makeConnector(descriptor, opts.inputs, {
339
339
  connection: opts.connection,
@@ -956,10 +956,28 @@ class CaseBuilder {
956
956
  }
957
957
  /**
958
958
  * Declare case In-args. Each value is a {@link TypeDesc}, or `{ type, default }`
959
- * to set a default. Pass `{ from: <trigger> }` to bind the args to a trigger —
960
- * their value arrives when it fires, readable as `=vars.<name>` — and they are
961
- * projected into that trigger's `entry-points.json` input schema. A declared
962
- * In-arg is readable as `=vars.<name>` (its `inputOutputs` companion resolves it).
959
+ * to set a default. An In-arg is always bound to a trigger: its value arrives
960
+ * when that trigger starts the case, it is readable as `=vars.<name>` (its
961
+ * `inputOutputs` companion, keyed to the trigger, resolves it), and it is
962
+ * projected into that trigger's `entry-points.json` input schema.
963
+ *
964
+ * Which trigger:
965
+ * - `{ from: <trigger> }` binds the args to that trigger.
966
+ * - Without `{ from }`, they bind to the primary trigger. That is the case's
967
+ * only trigger (the one added with `.trigger()`, or the implicit manual
968
+ * trigger when none was added), or, when there are several, the one that is
969
+ * not a timer. `uip maestro case sdd convert` reads a blank
970
+ * `sourceTriggers` the same way.
971
+ * - Without `{ from }` in a case with no primary trigger (several non-timer
972
+ * triggers, or only timers), `check` and `compile` refuse the plan with
973
+ * `CASE_IN_ARG_UNBOUND`. Pass `{ from }`, or use `.var()` for a value no
974
+ * trigger supplies.
975
+ *
976
+ * @example
977
+ * **Declare an In-arg on the primary trigger (here the implicit manual one)**
978
+ * ```ts
979
+ * casePlan('x').input({ invoiceNumber: { type: 'string', default: 'INV-1042' } })
980
+ * ```
963
981
  *
964
982
  * @example
965
983
  * **Bind case In-args to a trigger's payload**
@@ -970,7 +988,8 @@ class CaseBuilder {
970
988
  * ```
971
989
  *
972
990
  * @param shape - In-arg names to types, or `{ type, default }`.
973
- * @param opts - `{ from: <trigger> }` binds the args to a trigger's payload.
991
+ * @param opts - `{ from: <trigger> }` binds the args to that trigger. Required
992
+ * when the case has no primary trigger.
974
993
  * @returns This builder, so calls chain.
975
994
  */
976
995
  input(shape, opts = {}) {
@@ -52,6 +52,25 @@ export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | '
52
52
  version?: string;
53
53
  object?: string;
54
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;
55
74
  subscriptionError?: (kind: 'trigger' | 'event', subscription: EventSubscription) => {
56
75
  code: string;
57
76
  message: string;
@@ -1,4 +1,5 @@
1
1
  import { businessEventProblems } from './business-event-shape.js';
2
+ import { isUnboundInArg, unboundInArgMessage } from './in-arg-binding.js';
2
3
  import { ruleShapeProblems } from './rule-options.js';
3
4
  import { types } from '../core/expr.js';
4
5
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, authoredCaseVersion, caseVersionMismatch } from './format-profile.js';
@@ -6,7 +7,7 @@ import { schemaVersionRefusal } from '../schema-version.js';
6
7
  import { caseJsonPreservation, caseJsonPreservationErrors, preservedBindings } from './preserve.js';
7
8
  import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
8
9
  import { checkEventResolution } from '../core/event-checks.js';
9
- import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_EVENT, externalEventRefusal, isExternalExecutionMode } from './external.js';
10
+ import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, externalExecution } from './external.js';
10
11
  import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
11
12
  import { taskShapeProblems } from './task-shape.js';
12
13
  import { isConnectorPlaceholder } from './connector-placeholder.js';
@@ -279,6 +280,11 @@ export function check(built, opts = {}) {
279
280
  diags.push({ level: 'error', code: 'UNKNOWN_TASK_REF', message: `Rule "selected-tasks-completed" references unknown task "${n}".`, where });
280
281
  }
281
282
  };
283
+ // An In-arg with no `{ from }` binds to the primary trigger; when the case has
284
+ // none, `serialize` refuses it with the same code (in-arg-binding.ts).
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 });
282
288
  // ── `=js:vars.<name>` resolvability — REMOVED, superseded by #257 ───────────
283
289
  // This file briefly carried a VAR_NOT_REFERENCEABLE error asserting that only a
284
290
  // trigger-bound In-arg (`.input(shape, { from })`) could be read as `=js:vars.x`,
@@ -297,7 +303,7 @@ export function check(built, opts = {}) {
297
303
  // positive that blocks emit (this gate runs before serialization), so it must be
298
304
  // re-derived from the serializer rather than from remembered probe results.
299
305
  for (const stage of built.stages) {
300
- checkStage(stage, diags, refStage, refPlacement, opts.bindings);
306
+ checkStage(stage, diags, refStage, refPlacement, opts.bindings, opts.externalExecutionError !== undefined);
301
307
  stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
302
308
  }
303
309
  built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
@@ -319,7 +325,9 @@ export function check(built, opts = {}) {
319
325
  * CONNECTOR_OBJECT_REQUIRED, CONNECTOR_OBJECT_NOT_GENERIC; then their inputs
320
326
  * (`buildConnectorInputs`): CONNECTOR_INPUT, LOOKUP_UNRESOLVED;
321
327
  * - an external task's resolved descriptor (`externalDescriptorRefusal`):
322
- * EXTERNAL_TASK_DESCRIPTOR_CONTRACT;
328
+ * EXTERNAL_TASK_DESCRIPTOR_CONTRACT; and its mode and event against what the
329
+ * library records for the object (`externalExecution`): EXTERNAL_TASK_MODE,
330
+ * EXTERNAL_TASK_MODE_MISMATCH, EXTERNAL_TASK_EVENT;
323
331
  * - an event trigger, a wait-for-connector task and a wait-for-connector rule
324
332
  * resolve their subscription (`resolveCaseSubscription`): EVENT_UNKNOWN,
325
333
  * EVENT_GENERIC_NO_OBJECT.
@@ -385,6 +393,9 @@ function checkLibraryConnectors(built, opts, diags) {
385
393
  const refused = opts.externalDescriptorError?.(t.kind, spec);
386
394
  if (refused !== undefined)
387
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 });
388
399
  }
389
400
  if (t.kind === 'wait-for-connector' && isSubscription(t.waitConnector))
390
401
  subscription('event', t.waitConnector, `Wait-for-connector task "${t.name}"`, t.name);
@@ -435,7 +446,9 @@ function isNilOrEmpty(value) {
435
446
  return Object.keys(value).length === 0;
436
447
  return false;
437
448
  }
438
- function checkStage(stage, diags, refStage, refPlacement, bindings) {
449
+ function checkStage(stage, diags, refStage, refPlacement, bindings,
450
+ /** A library judges external tasks' mode and event (`externalExecutionError`). */
451
+ externalJudge) {
439
452
  const where = `stage "${stage.label}"`;
440
453
  if (stage.exception && stage.required !== false)
441
454
  diags.push({
@@ -616,17 +629,15 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
616
629
  where: t.name,
617
630
  });
618
631
  else {
619
- if (!isExternalExecutionMode(t.external.mode))
620
- diags.push({
621
- level: 'error',
622
- code: 'EXTERNAL_TASK_MODE',
623
- message: `${family} task "${t.name}" mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
624
- where: t.name,
625
- });
626
- else {
627
- const refused = externalEventRefusal(`${family} task "${t.name}"`, t.external.mode, t.external.event);
628
- if (refused)
629
- diags.push({ level: 'error', code: EXTERNAL_TASK_EVENT, message: refused, where: t.name });
632
+ // The authored mode and event alone. With a library at hand, the
633
+ // library pass judges them against the object instead (and reports a
634
+ // missing mode), so they are judged here only without one.
635
+ const libraryJudges = externalJudge
636
+ && !!t.external.connector?.key?.trim() && !!t.external.connector?.action?.trim();
637
+ if (!libraryJudges) {
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 });
630
641
  }
631
642
  if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
632
643
  diags.push({
@@ -8,7 +8,7 @@
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';
11
+ import { externalDescriptorRefusal, externalExecution } from './external.js';
12
12
  import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscription } from './subscription.js';
13
13
  /**
14
14
  * Case's check options from a library the CLI already opened, or `{}` without one.
@@ -48,6 +48,17 @@ export function caseCheckOpts(library, bindings) {
48
48
  }
49
49
  return externalDescriptorRefusal(kind, rc);
50
50
  },
51
+ externalExecutionError: (owner, d, authored) => {
52
+ let rc;
53
+ try {
54
+ rc = library.resolve(d.key, d.action, d.version, d.object);
55
+ }
56
+ catch {
57
+ return undefined; // connectorResolveError's to report
58
+ }
59
+ const judged = externalExecution(owner, authored, rc.entry?.operation ?? {});
60
+ return judged?.code !== undefined ? { code: judged.code, message: judged.message } : undefined;
61
+ },
51
62
  subscriptionError: (kind, sub) => {
52
63
  try {
53
64
  resolveCaseSubscription(library, kind, sub);
@@ -1,7 +1,8 @@
1
1
  import { commentText } from '../core/comment-text.js';
2
+ import { implicitInArgTrigger } from './in-arg-binding.js';
2
3
  import { tsLiteral, tsNumber } from '../core/ts-literal.js';
3
4
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
4
- 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';
5
6
  import { preserveSchemaVersion } from '../schema-version.js';
6
7
  import { ownEntry } from '../core/own-entry.js';
7
8
  import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
@@ -675,8 +676,8 @@ function externalCall(task, refs, library) {
675
676
  const serviceType = externalServiceType(kind, mode);
676
677
  if (data.serviceType !== serviceType)
677
678
  throw new Error(`${owner} has serviceType "${String(data.serviceType ?? '')}", but its executionType "${mode}" saves as "${serviceType}".`);
678
- // The event the object answers with, as saved: the same rule compile applies,
679
- // so a lifted task always compiles back (a hybrid task without one does not).
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).
680
681
  const event = eventRows.length ? { operation: stringValue('operation'), mode: stringValue('eventMode') } : undefined;
681
682
  // A saved plan has no `event` option to drop, so a sync task with event rows
682
683
  // gets its own text naming the rows; every other case is compile's rule.
@@ -698,6 +699,19 @@ function externalCall(task, refs, library) {
698
699
  const refused = externalDescriptorRefusal(kind, resolved);
699
700
  if (refused)
700
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.`);
701
715
  if (!Array.isArray(data.inputs))
702
716
  throw new Error(`${owner} inputs must be an array.`);
703
717
  const inputs = connectorInputs(data.inputs, resolved, owner);
@@ -1088,13 +1102,25 @@ function reassignedOwnNameTargets(plan) {
1088
1102
  }
1089
1103
  return targets;
1090
1104
  }
1091
- function emitVariables(out, depth, plan, triggerNameById) {
1105
+ function emitVariables(out, depth, plan, triggerNameById, triggerIsTimer) {
1092
1106
  const variables = plan.variables ?? {};
1093
1107
  const produced = producedVariableNames(plan);
1094
1108
  for (const input of variables.inputs ?? []) {
1095
- const from = input.elementId && triggerNameById.get(input.elementId);
1109
+ let from = input.elementId && triggerNameById.get(input.elementId);
1110
+ // An In-arg with no trigger is a shape only older SDKs wrote (builder-sdk
1111
+ // #1368): no companion, so nothing binds it. A bare `.input()` now binds to
1112
+ // the primary trigger, which is what this source recompiles to. When the
1113
+ // case has no primary trigger a bare `.input()` is refused
1114
+ // (CASE_IN_ARG_UNBOUND), so the source names the first trigger and says so.
1115
+ if (!input.elementId && implicitInArgTrigger(triggerIsTimer) === undefined) {
1116
+ from = triggerNameById.values().next().value;
1117
+ out.line(depth, `// The source document left In-arg "${commentText(input.name)}" unbound and the case has no primary trigger; it is bound to the first trigger here (builder-sdk#1368).`);
1118
+ }
1096
1119
  const fromOpts = from ? `, { from: ${from} }` : '';
1097
- out.line(depth, `.input({ ${key(input.name)}: ${varSpec(input)} }${fromOpts})`);
1120
+ // Compile writes `default: ""` on every In-arg that authored none, so the
1121
+ // empty default is not authored content and the source leaves it out.
1122
+ const spec = varSpec(input.default === '' ? { ...input, default: undefined } : input);
1123
+ out.line(depth, `.input({ ${key(input.name)}: ${spec} }${fromOpts})`);
1098
1124
  }
1099
1125
  for (const output of variables.outputs ?? [])
1100
1126
  out.line(depth, `.output({ ${key(output.name)}: ${varSpec(output)} })`);
@@ -1130,6 +1156,10 @@ function bridgeRows(plan, triggerId, bag) {
1130
1156
  }
1131
1157
  return bridges;
1132
1158
  }
1159
+ function isTimerTrigger(node) {
1160
+ const serviceType = triggerData(node).bag?.serviceType;
1161
+ return serviceType === 'Intsvc.TimerTrigger' || serviceType === 'timer';
1162
+ }
1133
1163
  function triggerSource(node, index, plan, refs) {
1134
1164
  const { label, description, bag } = triggerData(node);
1135
1165
  const name = label !== `Trigger ${index + 1}` ? tsLiteral(label) : undefined;
@@ -1577,7 +1607,7 @@ export function decompileWithDiagnostics(plan, options = {}) {
1577
1607
  throw new Error('allowAdhocOptionalStageTasks must be boolean.');
1578
1608
  out.line(depth, metadata.allowAdhocOptionalStageTasks ? '.allowAdhocTasks()' : '.allowAdhocTasks(false)');
1579
1609
  }
1580
- emitVariables(out, depth, plan, triggerNameById);
1610
+ emitVariables(out, depth, plan, triggerNameById, triggers.map(isTimerTrigger));
1581
1611
  triggers.forEach((_trigger, index) => out.line(depth, `.trigger(trigger${index + 1})`));
1582
1612
  (metadata.slaRules ?? []).forEach((sla, index) => out.line(depth, `.sla(${slaSource(sla, index)})`));
1583
1613
  for (const event of metadata.businessEvents ?? [])
@@ -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
  /**
@@ -45,7 +45,8 @@ export function externalEventRefusal(owner, mode, event) {
45
45
  return undefined;
46
46
  return (`${owner} is on a "hybrid" object, which answers through an event: pass the event the object reports as ` +
47
47
  `event: { operation, mode } (for example { operation: "TRIGGER_CREATED", mode: "polling" }, ` +
48
- `the "operation" and "eventMode" rows of the case spec result's caseShape.context). Without it the task has nothing to wait on.`);
48
+ `the "operation" and "eventMode" rows of the case spec result's caseShape.context), or prepare the connector so the ` +
49
+ `library records it. Without it the task has nothing to wait on.`);
49
50
  }
50
51
  if (mode === 'sync')
51
52
  return `${owner} is on a "sync" object, which answers inline and has no event; drop event, or fix mode.`;
@@ -79,6 +80,83 @@ export function externalServiceType(kind, mode) {
79
80
  return sync ? CASE_FORMAT_PROFILE.externalAgentSyncServiceType : CASE_FORMAT_PROFILE.externalAgentAsyncServiceType;
80
81
  return sync ? CASE_FORMAT_PROFILE.externalWorkflowSyncServiceType : CASE_FORMAT_PROFILE.externalWorkflowAsyncServiceType;
81
82
  }
83
+ /** Check's and compile's code when an authored `mode` disagrees with the library. */
84
+ export const EXTERNAL_TASK_MODE_MISMATCH = 'EXTERNAL_TASK_MODE_MISMATCH';
85
+ /**
86
+ * The execution facts the connector library records for the object
87
+ * (`operation.executionType` and `operation.executionEvent`, from
88
+ * `registry build-library` / `registry prepare`, #1369). A value the SDK does
89
+ * not know is ignored, as if it were absent.
90
+ */
91
+ export function libraryExecution(operation) {
92
+ const op = (operation ?? {});
93
+ const mode = isExternalExecutionMode(op.executionType) ? op.executionType : undefined;
94
+ const raw = op.executionEvent;
95
+ const event = mode !== undefined && mode !== 'sync' && typeof raw?.operation === 'string' && raw.operation
96
+ && isExternalEventMode(raw.mode)
97
+ ? { operation: raw.operation, mode: raw.mode }
98
+ : undefined;
99
+ return { ...(mode ? { mode } : {}), ...(event ? { event } : {}) };
100
+ }
101
+ /**
102
+ * The mode and event an external task is written with: what the author passed,
103
+ * else what the connector library records for the object. `check` and
104
+ * `serialize` both call this, so they refuse the same tasks with the same text.
105
+ *
106
+ * - `mode` comes from the author or the library. With neither, the task is
107
+ * refused (EXTERNAL_TASK_MODE): a guessed default would compile an `async`
108
+ * object to the sync contract without a word (#1370 was closed for that).
109
+ * - An authored `mode` the library contradicts is refused
110
+ * (EXTERNAL_TASK_MODE_MISMATCH): the plan would run the wrong contract.
111
+ * - `event` comes from the author or, for an `async` / `hybrid` object, the
112
+ * library; an authored one the library contradicts is refused
113
+ * (EXTERNAL_TASK_EVENT), and so is any pair {@link externalEventRefusal} refuses.
114
+ *
115
+ * @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
116
+ * @param authored - The task's authored `mode` and `event`.
117
+ * @param operation - The resolved library entry's `operation`, or `undefined`
118
+ * when no library is at hand (model-only `check`, which then judges only what
119
+ * was authored and reports no missing mode).
120
+ * @returns The mode and event, or the refusal.
121
+ */
122
+ export function externalExecution(owner, authored, operation) {
123
+ if (authored.mode !== undefined && !isExternalExecutionMode(authored.mode))
124
+ return {
125
+ code: 'EXTERNAL_TASK_MODE',
126
+ message: `${owner} mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
127
+ };
128
+ const library = operation === undefined ? {} : libraryExecution(operation);
129
+ if (authored.mode !== undefined && library.mode !== undefined && authored.mode !== library.mode)
130
+ return {
131
+ code: EXTERNAL_TASK_MODE_MISMATCH,
132
+ message: `${owner} has mode "${authored.mode}", but the connector library records the object's executionType as ` +
133
+ `"${library.mode}"; the plan would run the wrong contract. Drop mode to use the library's, or fix it.`,
134
+ };
135
+ const mode = authored.mode ?? library.mode;
136
+ if (mode === undefined) {
137
+ if (operation === undefined)
138
+ return undefined; // model-only check: the library decides
139
+ return {
140
+ code: 'EXTERNAL_TASK_MODE',
141
+ message: `${owner} has no mode, and the connector library records no executionType for the object. ` +
142
+ `Pass mode: the object's executionType from its case spec result ` +
143
+ `(Data.EssentialConfiguration.essentialConfiguration.executionType), or rebuild the library with registry prepare.`,
144
+ };
145
+ }
146
+ const authoredEvent = authored.event;
147
+ if (authoredEvent !== undefined && library.event !== undefined
148
+ && (authoredEvent?.operation !== library.event.operation || authoredEvent?.mode !== library.event.mode))
149
+ return {
150
+ code: EXTERNAL_TASK_EVENT,
151
+ message: `${owner} has event ${JSON.stringify(authoredEvent)}, but the connector library records the object's event as ` +
152
+ `${JSON.stringify(library.event)}. Drop event to use the library's, or fix it.`,
153
+ };
154
+ const event = authoredEvent ?? (mode !== 'sync' ? library.event : undefined);
155
+ const refused = externalEventRefusal(owner, mode, event);
156
+ if (refused)
157
+ return { code: EXTERNAL_TASK_EVENT, message: refused };
158
+ return { mode, ...(event !== undefined ? { event } : {}) };
159
+ }
82
160
  /** Check's code for {@link externalDescriptorRefusal}. */
83
161
  export const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = 'EXTERNAL_TASK_DESCRIPTOR_CONTRACT';
84
162
  /**