@uipath/maestro-builder-sdk 6.31.2 → 6.31.4

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.
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { cp, mkdir, readdir, readFile } from "node:fs/promises";
2
+ import { access, cp, mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { sortedStrings } from "./compat/compare.js";
6
+ import { jsonClone } from "../core/json-clone.js";
6
7
  import { writeJsonFile } from "../core/json-file.js";
7
8
  import { consoleReporter } from "./reporter.js";
8
9
  /** The corrections and definitions that the raw registry dump does not carry. */
@@ -33,39 +34,182 @@ export async function curatedOverlayIdentity(overlayPath = bundledOverlayPath())
33
34
  function entryIdentity(entry) {
34
35
  return `${String(entry.nodeType)}\0${String(entry.version)}`;
35
36
  }
37
+ /** The ledger of deliberate patches; read here, never copied into a library. */
38
+ export const CORRECTIONS_FILE = "overlay-corrections.json";
39
+ /**
40
+ * What the overlay did to one library, written beside it as a dotfile so a
41
+ * second application (`unpack-library.mjs` applies it twice, once per verb)
42
+ * does not mistake its own patch for "the published library caught up".
43
+ */
44
+ const RECORD_FILE = ".curated-overlay.json";
45
+ /** SDK-owned side-tables the published library does not carry; always copied. */
46
+ const SIDE_TABLES = new Set(["list-envelopes.json", "invariant-collections.json"]);
36
47
  /** The sidecar that travels with an entry file, and has no index row of its own. */
37
48
  function sidecarOf(rel) {
38
49
  return `${rel.slice(0, -".json".length)}.v1def.json`;
39
50
  }
51
+ function isDict(value) {
52
+ return typeof value === "object" && value !== null && !Array.isArray(value);
53
+ }
54
+ /** A sidecar is either the definition itself or a one-element array of it. */
55
+ function sidecarBody(sidecar) {
56
+ const body = Array.isArray(sidecar) ? sidecar[0] : sidecar;
57
+ return isDict(body) ? body : undefined;
58
+ }
59
+ /** `holder[key].fields`, created when absent, so a patch has an array to add to. */
60
+ function fieldList(holder, key) {
61
+ const section = isDict(holder[key]) ? holder[key] : (holder[key] = {});
62
+ if (!Array.isArray(section.fields))
63
+ section.fields = [];
64
+ return section.fields;
65
+ }
66
+ function fieldNames(fields) {
67
+ return new Set((Array.isArray(fields) ? fields : [])
68
+ .filter(isDict)
69
+ .map((field) => String(field.name)));
70
+ }
71
+ async function readJson(path) {
72
+ return JSON.parse(await readFile(path, "utf8"));
73
+ }
74
+ async function exists(path) {
75
+ try {
76
+ await access(path);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * The bundled corrections, checked against the overlay they ship in. A row
85
+ * that names no indexed overlay entry, or a field that entry does not define,
86
+ * is a fault in the SDK's own data, so it throws rather than patching nothing.
87
+ */
88
+ export async function readOverlayCorrections(overlayPath) {
89
+ const file = join(overlayPath, CORRECTIONS_FILE);
90
+ if (!(await exists(file)))
91
+ return [];
92
+ const ledger = (await readJson(file));
93
+ const overlayIndex = (await readJson(join(overlayPath, "index.json")));
94
+ const indexed = new Set((overlayIndex.entries ?? []).map((row) => row.path));
95
+ const corrections = ledger.corrections ?? [];
96
+ for (const correction of corrections) {
97
+ const where = `${CORRECTIONS_FILE}: correction ${JSON.stringify(correction.path)}`;
98
+ if (!indexed.has(correction.path)) {
99
+ throw new Error(`${where} names no entry in the overlay's index.json.`);
100
+ }
101
+ if (!correction.inputFields?.length && !correction.sidecarKeys?.length) {
102
+ throw new Error(`${where} patches nothing: give it inputFields or sidecarKeys.`);
103
+ }
104
+ const entry = (await readJson(join(overlayPath, correction.path)));
105
+ const sidecar = sidecarBody(await readJson(join(overlayPath, sidecarOf(correction.path)))) ?? {};
106
+ const entryFields = fieldNames(entry.inputSchema?.fields);
107
+ const sidecarFields = fieldNames(sidecar.inputDefinition?.fields);
108
+ for (const name of correction.inputFields ?? []) {
109
+ if (!entryFields.has(name) || !sidecarFields.has(name)) {
110
+ throw new Error(`${where}: the overlay's copy does not define input field ${JSON.stringify(name)} in both files.`);
111
+ }
112
+ }
113
+ for (const key of correction.sidecarKeys ?? []) {
114
+ if (!(key in sidecar)) {
115
+ throw new Error(`${where}: the overlay's sidecar has no ${JSON.stringify(key)}.`);
116
+ }
117
+ }
118
+ }
119
+ return corrections;
120
+ }
121
+ /**
122
+ * sha256 of a correction's entry file and sidecar as they are on disk now (a
123
+ * missing sidecar hashes as absent). The record keeps this for each patch it
124
+ * made, so a later application trusts its own patch only while both files are
125
+ * still the ones it wrote.
126
+ */
127
+ async function correctionDigest(libraryPath, path) {
128
+ const hash = createHash("sha256");
129
+ hash.update(await readFile(join(libraryPath, path)));
130
+ const sidecarPath = join(libraryPath, sidecarOf(path));
131
+ hash.update("\0");
132
+ hash.update((await exists(sidecarPath)) ? await readFile(sidecarPath) : "<no sidecar>");
133
+ return hash.digest("hex");
134
+ }
40
135
  /**
41
- * Apply the SDK's curated corrections after extracting the raw registry dump.
136
+ * Add what `correction` names to the library's own entry and sidecar, from the
137
+ * overlay's copy. Returns the names it added (empty means the published
138
+ * definition already carries every one of them) and how many files it wrote.
139
+ * A library with no sidecar for the entry gets none: only the entry is patched.
140
+ */
141
+ async function patchCorrection(libraryPath, overlayPath, correction) {
142
+ const sourceEntry = (await readJson(join(overlayPath, correction.path)));
143
+ const sourceSidecar = sidecarBody(await readJson(join(overlayPath, sidecarOf(correction.path)))) ?? {};
144
+ const entryPath = join(libraryPath, correction.path);
145
+ const sidecarPath = join(libraryPath, sidecarOf(correction.path));
146
+ const entry = (await readJson(entryPath));
147
+ const sidecarRaw = (await exists(sidecarPath)) ? await readJson(sidecarPath) : undefined;
148
+ const sidecar = sidecarRaw === undefined ? undefined : sidecarBody(sidecarRaw);
149
+ const added = [];
150
+ const addFields = (target, source, label) => {
151
+ const have = fieldNames(target);
152
+ const byName = new Map((Array.isArray(source) ? source : []).filter(isDict).map((field) => [String(field.name), field]));
153
+ for (const name of correction.inputFields ?? []) {
154
+ if (have.has(name))
155
+ continue;
156
+ target.push(jsonClone(byName.get(name)));
157
+ added.push(`${label}:${name}`);
158
+ }
159
+ };
160
+ addFields(fieldList(entry, "inputSchema"), sourceEntry.inputSchema?.fields, "inputSchema");
161
+ if (sidecar) {
162
+ addFields(fieldList(sidecar, "inputDefinition"), sourceSidecar.inputDefinition?.fields, "inputDefinition");
163
+ for (const key of correction.sidecarKeys ?? []) {
164
+ if (key in sidecar)
165
+ continue;
166
+ sidecar[key] = jsonClone(sourceSidecar[key]);
167
+ added.push(`sidecar:${key}`);
168
+ }
169
+ }
170
+ if (added.length === 0)
171
+ return { added, files: 0 };
172
+ writeJsonFile(entryPath, entry);
173
+ if (sidecar)
174
+ writeJsonFile(sidecarPath, sidecarRaw);
175
+ return { added, files: sidecar ? 2 : 1 };
176
+ }
177
+ /**
178
+ * Apply the SDK's curated overlay after extracting the raw registry dump.
42
179
  *
43
- * Files replace the dump's copy, while index rows are a union where the dump
44
- * wins collisions. This is the same contract as the former unpack script: a
45
- * correction changes the referenced file, and a definition absent from the
46
- * dump (notably connector triggers) is made resolvable by its added index row.
180
+ * THE RULE. The overlay ADDS what the library lacks and never replaces what it
181
+ * carries, apart from the deliberate patches listed in {@link CORRECTIONS_FILE}:
47
182
  *
48
- * WHEN THE LIBRARY ALREADY RESOLVES THE IDENTITY ELSEWHERE, the file is not
49
- * copied at all and the skip is reported. That case is not hypothetical: four
50
- * of the bundled `event-*@1.0.0.json` files carry a (nodeType, version) the
51
- * raw dump lacks TODAY but a refreshed catalog serves at its own path — the
52
- * per-object event variants land under `<connector>/events/<object>/`, keeping
53
- * the template's nodeType. Since the union keeps the library's row, copying
54
- * anyway left a file no index row pointed at, which `--check-orphans` (the
55
- * image build passes it) rejects. The three ways out are not equal:
183
+ * - an overlay entry whose (nodeType, version) the library does not index is
184
+ * copied with its sidecar, and its index row is added. This is what makes
185
+ * the connector start triggers resolvable, which the raw dump lacks;
186
+ * - an entry the library indexes at the SAME path is kept as published. The
187
+ * overlay's copy is a snapshot, and the published library moves on: until
188
+ * #1148 every such file was copied over the download while the download's
189
+ * index row stayed, so nine 2026-07 snapshots replaced newer definitions and
190
+ * Outlook `send-email` read `send-mail` (no `saveAsDraft`, no multipart
191
+ * parts) under an index row that said `send-mail-v2`. The entry files carry
192
+ * no date or build number to compare, so "is the published one newer?" is
193
+ * not answerable per file; the answer is that the overlay never decides it;
194
+ * - a listed correction is PATCHED into the library's own definition: the
195
+ * named input fields and sidecar keys are added when absent, and nothing
196
+ * else is touched. It cannot change the operation's object or endpoint, so
197
+ * index row and entry file cannot be made to disagree. When the published
198
+ * definition already has everything, the correction is reported as no
199
+ * longer needed so its row can be removed;
200
+ * - an entry the library indexes at a DIFFERENT path is not copied: the copy
201
+ * would be a file no index row points at, which `--check-orphans` (the
202
+ * image build passes it) rejects, and stamping the flat curated file over
203
+ * each per-object variant the library serves would destroy their schemas.
204
+ * A correction in that position is warned about, since it no longer takes
205
+ * effect;
206
+ * - the SDK's side-tables (`list-envelopes.json`, `invariant-collections.json`)
207
+ * are the SDK's own data and are always copied.
56
208
  *
57
- * - copying and teaching the orphan check to tolerate the file would keep
58
- * the build green and hide the real loss, which is that the hand-verified
59
- * CORRECTION never takes effect once the index points somewhere else;
60
- * - redirecting the correction onto the indexed path cannot be done safely
61
- * here, because ONE event identity maps to MANY per-object paths in a
62
- * refreshed library, and stamping the flat curated definition over each
63
- * per-object variant would destroy the per-object schema the library just
64
- * served — a worse regression than the one being fixed;
65
- * - so the copy is skipped and said out loud. The library keeps the
66
- * definition it resolves, the tree gains no unreachable file, and the
67
- * operator is told which overlay row has been overtaken and needs
68
- * re-verifying. The image build is then green before AND after a refresh.
209
+ * What was done is reported in one line, and recorded in `.curated-overlay.json`
210
+ * beside the index, so a second application recognises its own patches: the
211
+ * record keeps a sha256 of each patched entry and sidecar, and a correction
212
+ * whose files no longer match it is patched again.
69
213
  */
70
214
  export async function applyCuratedOverlay(libraryPath, overlayPath = bundledOverlayPath(),
71
215
  // The public materializer API does not forward one, and a warning nobody
@@ -74,6 +218,14 @@ reporter = consoleReporter) {
74
218
  const indexPath = join(libraryPath, "index.json");
75
219
  const destination = JSON.parse(await readFile(indexPath, "utf8"));
76
220
  const overlay = JSON.parse(await readFile(join(overlayPath, "index.json"), "utf8"));
221
+ const corrections = new Map((await readOverlayCorrections(overlayPath)).map((correction) => [correction.path, correction]));
222
+ const recordPath = join(libraryPath, RECORD_FILE);
223
+ const previous = (await exists(recordPath))
224
+ ? (await readJson(recordPath))
225
+ : {};
226
+ const previouslyAdded = new Set(previous.added ?? []);
227
+ const previousDigests = previous.correctedSha256 ?? {};
228
+ const digests = {};
77
229
  const entries = destination.entries ?? [];
78
230
  // Every path the library resolves each identity to. A `GenericExpanded` or
79
231
  // per-object entry shares its nodeType with its siblings, so an identity
@@ -91,46 +243,110 @@ reporter = consoleReporter) {
91
243
  }
92
244
  const additions = [];
93
245
  const skipped = new Set();
246
+ const added = [];
247
+ const corrected = [];
248
+ const kept = [];
249
+ const toCopy = new Set();
250
+ // Every entry file and sidecar the overlay's index accounts for; anything
251
+ // else in the directory is a side-table or the corrections ledger.
252
+ const rowFiles = new Set();
253
+ let written = 0;
94
254
  for (const row of overlay.entries ?? []) {
255
+ if (typeof row.path !== "string")
256
+ continue;
257
+ rowFiles.add(row.path);
258
+ rowFiles.add(sidecarOf(row.path));
95
259
  const identity = entryIdentity(row);
96
- if (!resolvedPaths.has(identity)) {
260
+ const correction = corrections.get(row.path);
261
+ const paths = resolvedPaths.get(identity);
262
+ if (paths === undefined) {
97
263
  additions.push(row);
264
+ added.push(row.path);
265
+ toCopy.add(row.path);
266
+ toCopy.add(sidecarOf(row.path));
98
267
  continue;
99
268
  }
100
- const paths = resolvedPaths.get(identity);
101
- if (typeof row.path !== "string" || paths.includes(row.path))
269
+ if (!paths.includes(row.path)) {
270
+ skipped.add(row.path);
271
+ if (correction) {
272
+ reporter.warn([
273
+ ` WARN: curated overlay correction NOT applied: ${row.path} (${correction.since})`,
274
+ ` ${String(row.nodeType)}@${String(row.version)} is in the library's index at`,
275
+ ...paths.map((path) => ` ${path}`),
276
+ " instead, so the library's own definition stands without this correction.",
277
+ ` Re-verify it there and update ${CORRECTIONS_FILE}.`,
278
+ ].join("\n"));
279
+ }
102
280
  continue;
103
- skipped.add(row.path);
104
- reporter.warn([
105
- ` WARN: curated overlay SKIPPED ${row.path}`,
106
- ` ${String(row.nodeType)}@${String(row.version)} is already in the library's index, at`,
107
- ...paths.map((path) => ` ${path}`),
108
- " so the copied file would resolve for nobody. The library's own definition stands and",
109
- " this correction does NOT take effect. Re-verify it against the current library and drop",
110
- " its row from typescript/sdk/lib/library-json.samples/index.json once the library carries it.",
111
- ].join("\n"));
112
- }
113
- // A skipped entry's sidecar goes with it: it is only ever read beside the
114
- // entry file, so leaving it behind would state a correction nothing reads.
115
- const notCopied = new Set(["index.json"]);
116
- for (const rel of skipped) {
117
- notCopied.add(rel);
118
- notCopied.add(sidecarOf(rel));
281
+ }
282
+ if (!correction) {
283
+ // A row this overlay added on an earlier application is still its own.
284
+ (previouslyAdded.has(row.path) ? added : kept).push(row.path);
285
+ continue;
286
+ }
287
+ // Our own earlier patch, as long as neither file changed since: a refreshed
288
+ // entry (an `unzip -o` over the same directory, say) is patched again.
289
+ const digest = await correctionDigest(libraryPath, row.path);
290
+ if (previousDigests[row.path] === digest) {
291
+ corrected.push(row.path);
292
+ digests[row.path] = digest;
293
+ continue;
294
+ }
295
+ const patched = await patchCorrection(libraryPath, overlayPath, correction);
296
+ if (patched.added.length === 0) {
297
+ kept.push(row.path);
298
+ reporter.warn([
299
+ ` WARN: curated overlay correction no longer needed: ${row.path} (${correction.since})`,
300
+ " The published definition already carries every field and key it adds.",
301
+ ` Remove its row from typescript/sdk/lib/library-json.samples/${CORRECTIONS_FILE}.`,
302
+ ].join("\n"));
303
+ continue;
304
+ }
305
+ corrected.push(row.path);
306
+ digests[row.path] = await correctionDigest(libraryPath, row.path);
307
+ written += patched.files;
119
308
  }
120
309
  const overlayFiles = await jsonFiles(overlayPath);
121
- let copied = 0;
122
310
  for (const source of overlayFiles) {
123
311
  const rel = relative(overlayPath, source).split(sep).join("/");
124
- if (notCopied.has(rel))
312
+ if (rel === "index.json" || rel === CORRECTIONS_FILE)
313
+ continue;
314
+ const accounted = rowFiles.has(rel);
315
+ // An entry or sidecar goes only where its row decided; a side-table always;
316
+ // any other file only into a library that lacks it, so a stray file here
317
+ // can never replace a published one either.
318
+ if (accounted && !toCopy.has(rel))
319
+ continue;
320
+ if (!accounted && !SIDE_TABLES.has(rel) && (await exists(join(libraryPath, rel))))
125
321
  continue;
126
322
  const target = join(libraryPath, rel);
127
323
  await mkdir(dirname(target), { recursive: true });
128
324
  await cp(source, target);
129
- copied += 1;
325
+ written += 1;
130
326
  }
131
327
  if (additions.length > 0) {
132
328
  destination.entries = [...entries, ...additions];
133
329
  writeJsonFile(indexPath, destination);
134
330
  }
135
- return { files: copied, entries: additions.length, skipped: sortedStrings([...skipped]) };
331
+ const result = {
332
+ files: written,
333
+ entries: additions.length,
334
+ skipped: sortedStrings([...skipped]),
335
+ added: sortedStrings(added),
336
+ corrected: sortedStrings(corrected),
337
+ kept: sortedStrings(kept),
338
+ };
339
+ writeJsonFile(recordPath, {
340
+ $comment: "What the SDK's curated overlay did to this library (registry pull). See curated-overlay.ts.",
341
+ added: result.added,
342
+ corrected: result.corrected,
343
+ correctedSha256: digests,
344
+ kept: result.kept,
345
+ skipped: result.skipped,
346
+ });
347
+ reporter.info(`curated overlay: added ${result.added.length} definition(s) the library lacks, ` +
348
+ `corrected ${result.corrected.length}` +
349
+ (result.corrected.length ? ` (${result.corrected.join(", ")})` : "") +
350
+ `, kept ${result.kept.length + result.skipped.length} published definition(s) unchanged.`);
351
+ return result;
136
352
  }
@@ -35,10 +35,12 @@ import { sortedStrings } from "./compat/compare.js";
35
35
  * `list-envelopes.json` is the LIST-ENVELOPE LEDGER (a side-table keyed by
36
36
  * nodeType, flow-builder-sdk#156); `invariant-collections.json` is the second
37
37
  * such side-table, keyed by a reference's `objectName` instead
38
- * (CONNECTOR_LOOKUP_DESIGN.md §5.3). Asking either for an index row is the
39
- * wrong question. Dotfiles are excluded separately — see {@link isDotfile}.
38
+ * (CONNECTOR_LOOKUP_DESIGN.md §5.3). `overlay-corrections.json` is the
39
+ * curated overlay's ledger of deliberate patches (#1148); the overlay never
40
+ * copies it, but a test library copied from the samples carries it. Asking any
41
+ * of them for an index row is the wrong question. Dotfiles are excluded separately — see {@link isDotfile}.
40
42
  */
41
- const SIDE_TABLES = new Set(["index.json", "list-envelopes.json", "invariant-collections.json"]);
43
+ const SIDE_TABLES = new Set(["index.json", "list-envelopes.json", "invariant-collections.json", "overlay-corrections.json"]);
42
44
  /**
43
45
  * A dotfile is never an operation entry, so it can never have an index row.
44
46
  *
package/dist/serialize.js CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import ixpDefinition from './ixp-definition.json' with { type: 'json' };
10
10
  import { BUNDLED_DEFINITIONS, bundledVersions } from './flow-definitions.js';
11
+ import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopHasInnerHandles, loopVersion, loopVersionRefusal } from './loop-version.js';
11
12
  import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameOf, flatNameSegmentsOf } from './flow-sdk.js';
12
13
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
13
14
  import { CONTEXT_INDEX_DEFAULTS, CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, contextIndexType } from './core/context-index.js';
@@ -75,15 +76,6 @@ const defByType = new Map();
75
76
  for (const d of Object.values(DEFS))
76
77
  if (!defByType.has(d.nodeType))
77
78
  defByType.set(d.nodeType, d);
78
- /** True when a container body (or an arm/handler inside it, but not a NESTED container's body) breaks. */
79
- function bodyUsesBreak(steps) {
80
- return steps.some((s) => s.kind === 'break'
81
- || (s.kind === 'branch' && (bodyUsesBreak(s.then) || bodyUsesBreak(s.otherwise)))
82
- || (s.kind === 'switch' && (s.cases.some((c) => bodyUsesBreak(c.body)) || (s.default ? bodyUsesBreak(s.default) : false)))
83
- || (s.kind === 'stepSwitch' && s.cases.some((c) => bodyUsesBreak(c.body)))
84
- || (s.kind === 'parallel' && s.arms.some(bodyUsesBreak))
85
- || (s.kind === 'stepToList' && bodyUsesBreak(s.body)));
86
- }
87
79
  const NODE_TYPE = {
88
80
  trigger: 'core.trigger.manual',
89
81
  scheduledTrigger: 'core.trigger.scheduled',
@@ -3448,8 +3440,6 @@ export function actionVersion(spec, explicit) {
3448
3440
  export function bundledDefinition(nodeType, requestedVersion) {
3449
3441
  return requestedVersion !== undefined ? DEFS[`${nodeType}@${requestedVersion}`] : defByType.get(nodeType);
3450
3442
  }
3451
- /** The `core.logic.loop` version `.loop()` emits unless `{ version }` pins another. */
3452
- const LOOP_DEFAULT_VERSION = '2.4';
3453
3443
  /**
3454
3444
  * The bundled definition a `.loop()` or `.doWhile()` container emits, at the
3455
3445
  * version serialize writes (`.loop()` defaults to `@2.4`, `.doWhile()` to the
@@ -6342,15 +6332,14 @@ export function serialize(built, opts = {}) {
6342
6332
  // designer authors (inner start/continue/break handles, `currentIteration`
6343
6333
  // output). 1.0.0 is the legacy shape the canvas shows in its old form; it
6344
6334
  // is emitted only when { version: '1.0.0' } is pinned explicitly (#868).
6345
- // An explicit 1.0.0 plus a rich option is a contradiction the author must
6346
- // resolve, not a silent drop.
6347
- const rich = o.parallel !== undefined || o.completionCondition !== undefined || o.breakEnabled !== undefined || usesBreak;
6348
- const version = o.version ?? LOOP_DEFAULT_VERSION;
6349
- if (rich && version === '1.0.0') {
6350
- throw new Error(`loop "${step.name}": parallel/completionCondition/breakEnabled/break() need the loop's 2.4 `
6351
- + `definition, but { version: '1.0.0' } was requested. Drop the version or request 2.4.`);
6352
- }
6353
- const v24 = version !== '1.0.0';
6335
+ // An option or a .break() the pinned version's definition cannot express
6336
+ // is a contradiction the author must resolve, not a silent drop; `check`
6337
+ // reports the same refusal from the same helper (#1186).
6338
+ const refusal = loopVersionRefusal(step.name, o, usesBreak);
6339
+ if (refusal)
6340
+ throw new Error(refusal);
6341
+ const version = loopVersion(o);
6342
+ const v24 = loopHasInnerHandles(version);
6354
6343
  const id = stepUid(scope, step.name);
6355
6344
  const node = makeNode(NODE_TYPE.loop, id, step.options?.label ?? step.label ?? step.name, { parentId, requestedVersion: version });
6356
6345
  node.inputs = v24
@@ -6362,7 +6351,7 @@ export function serialize(built, opts = {}) {
6362
6351
  : {}),
6363
6352
  breakEnabled: (o.breakEnabled ?? false) || usesBreak,
6364
6353
  }
6365
- : { collection: renderValue(step.collection, scope.rename), parallel: false };
6354
+ : { collection: renderValue(step.collection, scope.rename), parallel: o.parallel ?? false };
6366
6355
  scope.nodes.push(node);
6367
6356
  for (const t of tails)
6368
6357
  addEdge(scope, t, id);
@@ -133,7 +133,7 @@
133
133
  "connectorKey": "uipath-microsoft-outlook365",
134
134
  "label": "Send Email",
135
135
  "activityType": "Curated",
136
- "objectName": "send-mail",
136
+ "objectName": "send-mail-v2",
137
137
  "path": "uipath-microsoft-outlook365/send-email@1.0.0.json"
138
138
  },
139
139
  {
@@ -0,0 +1,43 @@
1
+ {
2
+ "schemaVersion": "1",
3
+ "$comment": [
4
+ "THE CORRECTIONS LEDGER: which files in this directory deliberately patch a published definition.",
5
+ "",
6
+ "This directory has two jobs. It is the small connector library the tests and the examples read,",
7
+ "and it is the curated overlay `registry pull` applies to a freshly downloaded library. Only the",
8
+ "second job can damage a user's library, and only through the rows listed here.",
9
+ "",
10
+ "THE RULE (`src/registry/curated-overlay.ts`). An overlay entry whose (nodeType, version) the",
11
+ "downloaded library does NOT carry is added, with its index row. An entry the library DOES carry is",
12
+ "left alone: the published definition wins, because a copy here is a snapshot that goes stale the",
13
+ "day the library is republished (builder-sdk#1148: nine 2026-07 snapshots were replacing newer",
14
+ "published definitions, Outlook `send-email` among them). The one exception is a row in",
15
+ "`corrections` below, and a correction is a PATCH, never a replacement: it adds the named input",
16
+ "fields and sidecar keys the published definition lacks, taking them from this directory's copy,",
17
+ "and touches nothing else. It cannot change `operation.objectName` or the endpoint, so the index",
18
+ "row and the entry file keep agreeing.",
19
+ "",
20
+ "A correction the published library has caught up with (it already has every field and key) is",
21
+ "reported as no longer needed. Remove its row here once a published build carries it.",
22
+ "",
23
+ "FIELDS. `path`: the entry file, as in index.json. `since`: the change that made it. `reason`:",
24
+ "why the published definition is wrong. `inputFields`: names added to `inputSchema.fields` (entry)",
25
+ "and `inputDefinition.fields` (sidecar) when absent. `sidecarKeys`: top-level sidecar keys added",
26
+ "when absent."
27
+ ],
28
+ "corrections": [
29
+ {
30
+ "path": "uipath-atlassian-jira/create-issue@1.0.0.json",
31
+ "since": "builder-sdk#108",
32
+ "reason": "Jira refuses a create without fields.summary (400). The published definition leaves the body fields to design-time schema generation, so a flow authored without `registry prepare` could not set summary or description, and nothing typed the created issue's key.",
33
+ "inputFields": [
34
+ "fields.summary",
35
+ "fields.description",
36
+ "key"
37
+ ],
38
+ "sidecarKeys": [
39
+ "outputResponseDefinition"
40
+ ]
41
+ }
42
+ ]
43
+ }
@@ -10,23 +10,66 @@
10
10
  ],
11
11
  "connector": {
12
12
  "key": "uipath-microsoft-outlook365",
13
- "name": "Microsoft Outlook 365"
13
+ "name": "Microsoft Outlook 365",
14
+ "tier": "1",
15
+ "discovery": {
16
+ "type": "static",
17
+ "hasObjectDiscovery": false,
18
+ "hasFieldDiscovery": false,
19
+ "hasCustomObjectDiscovery": false,
20
+ "hasCustomFieldDiscovery": false
21
+ }
14
22
  },
15
23
  "operation": {
16
24
  "name": "Create",
17
- "objectName": "send-mail",
25
+ "objectName": "send-mail-v2",
18
26
  "objectDisplayName": "Send Email",
19
27
  "httpMethod": "POST",
20
- "path": "/hubs/productivity/send-mail",
28
+ "path": "/hubs/productivity/send-mail-v2",
21
29
  "subType": "standard",
22
30
  "supportsStreaming": false,
23
31
  "activityType": "Curated",
24
- "pathTemplate": "/hubs/productivity/send-mail",
25
- "parameters": []
32
+ "pathTemplate": "/hubs/productivity/send-mail-v2",
33
+ "parameters": [
34
+ {
35
+ "name": "saveAsDraft",
36
+ "type": "query",
37
+ "dataType": "boolean",
38
+ "required": false,
39
+ "displayName": "Save as draft",
40
+ "description": "Send an email message. By default, the email will be saved as draft.",
41
+ "defaultValue": true
42
+ },
43
+ {
44
+ "name": "includeMessageDetails",
45
+ "type": "query",
46
+ "dataType": "boolean",
47
+ "required": false,
48
+ "displayName": "Include message details",
49
+ "description": "Returns the sent message's identifiers (message ID, conversation ID, internet message ID, status) when set to true. If set to false, response only contains status.",
50
+ "defaultValue": false
51
+ },
52
+ {
53
+ "name": "file",
54
+ "type": "multipart",
55
+ "dataType": "file",
56
+ "required": false,
57
+ "displayName": "Attachment",
58
+ "description": "The file to attach to the email"
59
+ },
60
+ {
61
+ "name": "body",
62
+ "type": "multipart",
63
+ "dataType": "string",
64
+ "required": false,
65
+ "displayName": "Body",
66
+ "description": "The object body"
67
+ }
68
+ ]
26
69
  },
27
70
  "display": {
28
71
  "label": "Send Email",
29
- "description": "(Microsoft Outlook 365) Send an email message based on the input parameters",
72
+ "description": "Send an email message based on the input parameters",
30
73
  "icon": "https://alpha.uipath.com/311c6049-4966-4ef2-a6f3-8ceccaf38d54/studio_/typecache/icons/160499b52f7cea190b530fe6f40da7ee03455a8d3fb72dd7f4f68d61e7865a3a.svg",
31
74
  "iconBackground": "",
32
75
  "iconBackgroundDark": "",