@hyperscale0/udl 2.0.4 → 2.2.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +15 -1
  3. package/conformance/invalid/call-binds-results.expected.json +10 -0
  4. package/conformance/invalid/call-binds-results.udl +314 -0
  5. package/conformance/invalid/call-unknown-action.expected.json +10 -0
  6. package/conformance/invalid/call-unknown-action.udl +314 -0
  7. package/conformance/invalid/leaf-effect-mismatch.expected.json +10 -0
  8. package/conformance/invalid/leaf-effect-mismatch.udl +314 -0
  9. package/conformance/invalid/piece-plan-without-partition.expected.json +10 -0
  10. package/conformance/invalid/piece-plan-without-partition.udl +305 -0
  11. package/conformance/invalid/private-action-independent-approval.expected.json +10 -0
  12. package/conformance/invalid/private-action-independent-approval.udl +314 -0
  13. package/conformance/invalid/unfund-order-not-reversed.expected.json +10 -0
  14. package/conformance/invalid/unfund-order-not-reversed.udl +314 -0
  15. package/conformance/valid/piece-plan-calls.expected.json +6 -0
  16. package/conformance/valid/piece-plan-calls.udl +314 -0
  17. package/dist/diagnostics.d.ts +36 -0
  18. package/dist/diagnostics.d.ts.map +1 -1
  19. package/dist/diagnostics.js +36 -0
  20. package/dist/diagnostics.js.map +1 -1
  21. package/dist/effects.d.ts +26 -3
  22. package/dist/effects.d.ts.map +1 -1
  23. package/dist/effects.js +962 -8
  24. package/dist/effects.js.map +1 -1
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/limits.d.ts +2 -0
  30. package/dist/limits.d.ts.map +1 -1
  31. package/dist/limits.js +2 -0
  32. package/dist/limits.js.map +1 -1
  33. package/dist/schema.d.ts +594 -11
  34. package/dist/schema.d.ts.map +1 -1
  35. package/dist/schema.js +111 -1
  36. package/dist/schema.js.map +1 -1
  37. package/dist/validation.d.ts.map +1 -1
  38. package/dist/validation.js +289 -4
  39. package/dist/validation.js.map +1 -1
  40. package/docs/assets/brand/manifest.json +30 -0
  41. package/docs/assets/brand/udl-horizontal-white.svg +1 -0
  42. package/docs/assets/brand/udl-horizontal.svg +1 -0
  43. package/docs/assets/brand/udl-stacked-white.svg +1 -0
  44. package/docs/assets/brand/udl-stacked.svg +1 -0
  45. package/docs/assets/udl.svg +7 -14
  46. package/docs/llms-full.txt +199 -32
  47. package/docs/llms.txt +1 -1
  48. package/docs/reference/clauses.md +162 -1
  49. package/docs/reference/cli.md +1 -1
  50. package/docs/reference/diagnostics.md +38 -32
  51. package/package.json +2 -2
  52. package/spec/udl.schema.json +321 -1
  53. package/src/diagnostics.ts +36 -0
  54. package/src/effects.ts +1672 -9
  55. package/src/index.ts +15 -0
  56. package/src/limits.ts +2 -0
  57. package/src/schema.ts +149 -2
  58. package/src/validation.ts +470 -5
package/dist/effects.js CHANGED
@@ -1,11 +1,7 @@
1
- export const udlEffectKinds = [
2
- "decides",
3
- "holds",
4
- "moves",
5
- "notifies",
6
- "reads",
7
- "schedules",
8
- ];
1
+ import { issue } from "./diagnostics.js";
2
+ import { UDL_LIMITS } from "./limits.js";
3
+ import { udlClauseVocabulary, udlEffectKinds, } from "./schema.js";
4
+ export { udlEffectKinds } from "./schema.js";
9
5
  function boundPath(move, endpoint) {
10
6
  const binding = move.bind?.[endpoint];
11
7
  if (binding === null || typeof binding !== "object")
@@ -107,4 +103,962 @@ function recordValue(value) {
107
103
  ? value
108
104
  : undefined;
109
105
  }
106
+ export function deriveActionEffectsFromPlan(leaves) {
107
+ const effects = {};
108
+ for (const leaf of leaves) {
109
+ for (const eff of leaf.effects) {
110
+ const kind = eff.kind;
111
+ (effects[kind] ??= []).push({
112
+ signature: eff.signature,
113
+ source: leaf.originPath.join("."),
114
+ });
115
+ }
116
+ }
117
+ return effects;
118
+ }
119
+ function combineDerivedEffects(direct, leaves) {
120
+ const result = {};
121
+ for (const kind of udlEffectKinds) {
122
+ const directRows = direct[kind] ?? [];
123
+ const leafRows = leaves[kind] ?? [];
124
+ if (directRows.length > 0 || leafRows.length > 0) {
125
+ result[kind] = [...directRows, ...leafRows];
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+ function encodeOriginPathKey(originPath) {
131
+ const parts = originPath.map((seg) => `${seg.length}_${seg}`);
132
+ return `k_${parts.join("_")}`;
133
+ }
134
+ function expectedLeafEffects(step) {
135
+ if (step.operation === "internal_transfer.reserve") {
136
+ const moveClass = movementClass(step);
137
+ return [
138
+ { kind: "moves", signature: `moves.${moveClass}` },
139
+ { kind: "holds", signature: "holds.reserve" },
140
+ ];
141
+ }
142
+ if (step.operation.startsWith("internal_transfer.")) {
143
+ const moveClass = movementClass(step);
144
+ return [{ kind: "moves", signature: `moves.${moveClass}` }];
145
+ }
146
+ if (step.operation === "account.escrow.provision") {
147
+ return [{ kind: "holds", signature: "holds.escrow" }];
148
+ }
149
+ if (step.operation === "account.freeze") {
150
+ return [{ kind: "holds", signature: "holds.freeze" }];
151
+ }
152
+ if (step.operation === "account.unfreeze") {
153
+ return [{ kind: "holds", signature: "holds.unfreeze" }];
154
+ }
155
+ return [];
156
+ }
157
+ const accountPattern = "^acct_(sandbox|live)_[a-z0-9]{8,64}$";
158
+ const positiveMoneyPattern = "^[1-9][0-9]{0,17}$";
159
+ const nonNegativeMoneyPattern = "^(0|[1-9][0-9]{0,17})$";
160
+ const currencyPattern = "^[A-Z]{3}$";
161
+ function isMoneySchema(schema) {
162
+ if (!schema || typeof schema !== "object")
163
+ return false;
164
+ const s = schema;
165
+ return (s.type === "string" &&
166
+ (s.pattern === positiveMoneyPattern ||
167
+ s.pattern === nonNegativeMoneyPattern));
168
+ }
169
+ function isAccountSchema(schema) {
170
+ if (!schema || typeof schema !== "object")
171
+ return false;
172
+ const s = schema;
173
+ return s.type === "string" && s.pattern === accountPattern;
174
+ }
175
+ function isStringSchema(schema) {
176
+ if (!schema || typeof schema !== "object")
177
+ return false;
178
+ const s = schema;
179
+ return s.type === "string";
180
+ }
181
+ function isDeniedCrossTenantSegment(segment) {
182
+ return (segment === "tenant" ||
183
+ segment === "tenantId" ||
184
+ segment === "org" ||
185
+ segment === "environment");
186
+ }
187
+ function checkCrossTenant(rawBind) {
188
+ if (rawBind.startsWith("/"))
189
+ return true;
190
+ const segments = rawBind.split(/[./]/);
191
+ return segments.some(isDeniedCrossTenantSegment);
192
+ }
193
+ function instrumentConcreteCurrency(instrument) {
194
+ const mutableFields = new Set(instrument.update?.fields ?? []);
195
+ const allUpdatedFields = new Set(Object.values(instrument.actions).flatMap((a) => a.updates ?? []));
196
+ const currencyFields = Object.entries(instrument.fields).filter(([, schema]) => typeof schema === "object" &&
197
+ schema !== null &&
198
+ schema.pattern === currencyPattern);
199
+ if (currencyFields.length === 1) {
200
+ const [fieldName, fieldDefObj] = currencyFields[0];
201
+ if (mutableFields.has(fieldName) || allUpdatedFields.has(fieldName)) {
202
+ return undefined;
203
+ }
204
+ const fieldDef = fieldDefObj;
205
+ if (typeof fieldDef.const === "string" &&
206
+ /^[A-Z]{3}$/.test(fieldDef.const)) {
207
+ return fieldDef.const;
208
+ }
209
+ if (Array.isArray(fieldDef.enum) &&
210
+ fieldDef.enum.length === 1 &&
211
+ typeof fieldDef.enum[0] === "string" &&
212
+ /^[A-Z]{3}$/.test(fieldDef.enum[0])) {
213
+ return fieldDef.enum[0];
214
+ }
215
+ }
216
+ return undefined;
217
+ }
218
+ function pieceCurrency(piece, instrument) {
219
+ const amountSchema = instrument.fields[piece.amount];
220
+ if (amountSchema &&
221
+ typeof amountSchema["x-hyperscale-currency"] === "string" &&
222
+ /^[A-Z]{3}$/.test(amountSchema["x-hyperscale-currency"])) {
223
+ return amountSchema["x-hyperscale-currency"];
224
+ }
225
+ return instrumentConcreteCurrency(instrument);
226
+ }
227
+ export function resolveUdlActionPlans(instrument) {
228
+ const issues = [];
229
+ const plans = [];
230
+ if (instrument.piecePlan) {
231
+ for (const piece of instrument.piecePlan.pieces) {
232
+ if (pieceCurrency(piece, instrument) === undefined) {
233
+ issues.push(issue("UDL4002", `$.piecePlan.pieces.${piece.id}`, `piece ${piece.id} requires a valid concrete ISO currency from amount tag or immutable instrument currency declaration`));
234
+ }
235
+ }
236
+ }
237
+ const libraryMap = instrument.actionLibrary ?? {};
238
+ function hasLibrary(lib) {
239
+ if (!instrument.actionLibrary)
240
+ return false;
241
+ return Object.hasOwn(instrument.actionLibrary, lib);
242
+ }
243
+ function getAction(lib, act) {
244
+ if (!instrument.actionLibrary)
245
+ return undefined;
246
+ if (!Object.hasOwn(instrument.actionLibrary, lib))
247
+ return undefined;
248
+ const mod = instrument.actionLibrary[lib];
249
+ if (!mod ||
250
+ typeof mod !== "object" ||
251
+ !mod.actions ||
252
+ typeof mod.actions !== "object")
253
+ return undefined;
254
+ if (!Object.hasOwn(mod.actions, act))
255
+ return undefined;
256
+ return mod.actions[act];
257
+ }
258
+ // Check all private actions for structure, complete order, collisions, and authority
259
+ for (const [libKey, libModule] of Object.entries(libraryMap)) {
260
+ const libPath = `$.actionLibrary.${libKey}`;
261
+ const actionKeys = Object.keys(libModule.actions);
262
+ const orderSet = new Set(libModule.actionOrder);
263
+ if (new Set(libModule.actionOrder).size !== libModule.actionOrder.length) {
264
+ issues.push(issue("UDL2010", `${libPath}.actionOrder`, `actionOrder in library ${libKey} contains duplicate action names`));
265
+ }
266
+ for (const actionKey of actionKeys) {
267
+ if (!orderSet.has(actionKey)) {
268
+ issues.push(issue("UDL2010", `${libPath}.actionOrder`, `action ${actionKey} is declared in library ${libKey} but missing from actionOrder`));
269
+ }
270
+ }
271
+ for (const orderKey of libModule.actionOrder) {
272
+ if (!Object.hasOwn(libModule.actions, orderKey)) {
273
+ issues.push(issue("UDL2010", `${libPath}.actionOrder`, `actionOrder in library ${libKey} references unknown action ${orderKey}`));
274
+ }
275
+ }
276
+ for (const [actKey, privAction] of Object.entries(libModule.actions)) {
277
+ const actPath = `${libPath}.actions.${actKey}`;
278
+ if (privAction.approval === "independent") {
279
+ issues.push(issue("UDL2012", `${actPath}.approval`, `private action ${libKey}.${actKey} cannot declare independent approval`));
280
+ }
281
+ if (privAction.recovery === "external") {
282
+ issues.push(issue("UDL2012", `${actPath}.recovery`, `private action ${libKey}.${actKey} cannot declare external recovery`));
283
+ }
284
+ const leafIds = privAction.leaves.map((l) => l.id);
285
+ const callIds = privAction.calls.map((c) => c.id);
286
+ const leafIdSet = new Set(leafIds);
287
+ const callIdSet = new Set(callIds);
288
+ if (leafIdSet.size !== leafIds.length) {
289
+ issues.push(issue("UDL2010", `${actPath}.leaves`, `duplicate leaf id in private action ${libKey}.${actKey}`));
290
+ }
291
+ if (callIdSet.size !== callIds.length) {
292
+ issues.push(issue("UDL2010", `${actPath}.calls`, `duplicate call id in private action ${libKey}.${actKey}`));
293
+ }
294
+ for (const id of leafIds) {
295
+ if (callIdSet.has(id)) {
296
+ issues.push(issue("UDL2010", `${actPath}.order`, `collision between leaf id and call id ${id} in private action ${libKey}.${actKey}`));
297
+ }
298
+ }
299
+ const allIds = new Set([...leafIds, ...callIds]);
300
+ const orderIds = privAction.order;
301
+ if (orderIds.length !== allIds.size ||
302
+ !orderIds.every((id) => allIds.has(id)) ||
303
+ new Set(orderIds).size !== orderIds.length) {
304
+ issues.push(issue("UDL2010", `${actPath}.order`, `order in private action ${libKey}.${actKey} must be an exact permutation of leaf and call ids`));
305
+ }
306
+ for (const call of privAction.calls) {
307
+ const [targetLib, targetAct] = call.action.split(".");
308
+ if (!targetLib || !targetAct || !hasLibrary(targetLib)) {
309
+ issues.push(issue("UDL2010", `${actPath}.calls.${call.id}.action`, `call ${call.id} references unknown library ${targetLib ?? ""}`));
310
+ }
311
+ else {
312
+ const targetActionDef = getAction(targetLib, targetAct);
313
+ if (!targetActionDef) {
314
+ issues.push(issue("UDL2010", `${actPath}.calls.${call.id}.action`, `call ${call.id} references unknown action ${targetAct} in library ${targetLib}`));
315
+ }
316
+ else {
317
+ for (const key of Object.keys(call.bind)) {
318
+ if (!Object.hasOwn(targetActionDef.parameters, key)) {
319
+ issues.push(issue("UDL2011", `${actPath}.calls.${call.id}.bind.${key}`, `unexpected parameter ${key} in call to ${call.action}`));
320
+ }
321
+ }
322
+ for (const paramName of Object.keys(targetActionDef.parameters)) {
323
+ if (!Object.hasOwn(call.bind, paramName)) {
324
+ issues.push(issue("UDL2011", `${actPath}.calls.${call.id}.bind.${paramName}`, `missing binding for parameter ${paramName} in call to ${call.action}`));
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ }
332
+ // Cycle detection in private actions
333
+ const actionVisited = new Set();
334
+ const recursionStack = new Set();
335
+ function checkCycle(currentActionRef, path) {
336
+ const [lib, act] = currentActionRef.split(".");
337
+ if (path.length > UDL_LIMITS.maxDepth) {
338
+ issues.push(issue("UDL2010", `$.actionLibrary.${lib ?? ""}.actions.${act ?? ""}`, `action call depth exceeds maximum depth of ${UDL_LIMITS.maxDepth}`));
339
+ return true;
340
+ }
341
+ actionVisited.add(currentActionRef);
342
+ recursionStack.add(currentActionRef);
343
+ const actionDef = lib && act ? getAction(lib, act) : undefined;
344
+ if (actionDef) {
345
+ for (const call of actionDef.calls) {
346
+ const [targetLib, targetAct] = call.action.split(".");
347
+ if (!targetLib || !targetAct || !getAction(targetLib, targetAct)) {
348
+ continue;
349
+ }
350
+ if (recursionStack.has(call.action)) {
351
+ issues.push(issue("UDL2010", `$.actionLibrary.${lib}.actions.${act}`, `action call cycle detected: ${[...path, call.action].join(" -> ")}`));
352
+ return true;
353
+ }
354
+ if (!actionVisited.has(call.action)) {
355
+ if (checkCycle(call.action, [...path, call.action]))
356
+ return true;
357
+ }
358
+ }
359
+ }
360
+ recursionStack.delete(currentActionRef);
361
+ return false;
362
+ }
363
+ for (const [libKey, libModule] of Object.entries(libraryMap)) {
364
+ for (const actKey of Object.keys(libModule.actions)) {
365
+ const ref = `${libKey}.${actKey}`;
366
+ if (!actionVisited.has(ref)) {
367
+ checkCycle(ref, [ref]);
368
+ }
369
+ }
370
+ }
371
+ function resolveBinding(rawBind, scope, leafPath) {
372
+ if (rawBind.includes("$results")) {
373
+ issues.push(issue("UDL2011", leafPath, `results is not an implicit binding scope: ${rawBind}`));
374
+ return undefined;
375
+ }
376
+ if (checkCrossTenant(rawBind)) {
377
+ issues.push(issue("UDL2012", leafPath, `cross-tenant field paths are not allowed: ${rawBind}`));
378
+ return undefined;
379
+ }
380
+ if (rawBind === "$instance") {
381
+ return {
382
+ binding: { from: "instance", path: "instrumentInstanceId" },
383
+ kind: "instance",
384
+ };
385
+ }
386
+ if (rawBind === "$piece") {
387
+ const pieceVal = scope.get("piece");
388
+ if (!pieceVal || pieceVal.kind !== "piece") {
389
+ issues.push(issue("UDL2011", leafPath, `$piece reference is not available in caller scope`));
390
+ return undefined;
391
+ }
392
+ return {
393
+ binding: { from: "const", value: pieceVal.piece.id },
394
+ kind: "piece",
395
+ piece: pieceVal.piece,
396
+ };
397
+ }
398
+ if (rawBind.startsWith("$fields.")) {
399
+ const parts = rawBind.slice("$fields.".length).split(".");
400
+ if (parts.length !== 1) {
401
+ issues.push(issue("UDL2011", leafPath, `invalid member access on field: ${rawBind}`));
402
+ return undefined;
403
+ }
404
+ const fieldName = parts[0];
405
+ const fieldSchema = instrument.fields[fieldName];
406
+ if (!fieldSchema) {
407
+ issues.push(issue("UDL2011", leafPath, `referenced field ${fieldName} is not declared on instrument`));
408
+ return undefined;
409
+ }
410
+ if (isMoneySchema(fieldSchema)) {
411
+ const cur = fieldSchema["x-hyperscale-currency"] ?? instrumentConcreteCurrency(instrument);
412
+ return {
413
+ binding: { from: "instance", path: `fields.${fieldName}` },
414
+ currency: cur,
415
+ kind: "money",
416
+ path: `fields.${fieldName}`,
417
+ };
418
+ }
419
+ if (isAccountSchema(fieldSchema)) {
420
+ return {
421
+ binding: { from: "instance", path: `fields.${fieldName}` },
422
+ kind: "account",
423
+ path: `fields.${fieldName}`,
424
+ };
425
+ }
426
+ if (isStringSchema(fieldSchema)) {
427
+ return {
428
+ binding: { from: "instance", path: `fields.${fieldName}` },
429
+ kind: "text",
430
+ path: `fields.${fieldName}`,
431
+ };
432
+ }
433
+ issues.push(issue("UDL2011", leafPath, `field ${fieldName} is not an account, money, or text field`));
434
+ return undefined;
435
+ }
436
+ if (rawBind.startsWith("$")) {
437
+ const parts = rawBind.slice(1).split(".");
438
+ const paramName = parts[0];
439
+ const val = scope.get(paramName);
440
+ if (!val) {
441
+ issues.push(issue("UDL2011", leafPath, `unbound parameter $${paramName} in binding ${rawBind}`));
442
+ return undefined;
443
+ }
444
+ if (val.kind === "piece") {
445
+ if (parts.length === 1) {
446
+ return {
447
+ binding: { from: "const", value: val.piece.id },
448
+ kind: "piece",
449
+ piece: val.piece,
450
+ };
451
+ }
452
+ if (parts.length !== 2) {
453
+ issues.push(issue("UDL2011", leafPath, `invalid trailing member access on piece reference: ${rawBind}`));
454
+ return undefined;
455
+ }
456
+ const member = parts[1];
457
+ if (member === "id") {
458
+ return {
459
+ binding: { from: "const", value: val.piece.id },
460
+ kind: "text",
461
+ };
462
+ }
463
+ if (member === "amount") {
464
+ const cur = pieceCurrency(val.piece, instrument);
465
+ if (cur === undefined) {
466
+ issues.push(issue("UDL4002", leafPath, `piece ${val.piece.id} amount field lacks a concrete currency`));
467
+ }
468
+ return {
469
+ binding: { from: "instance", path: `fields.${val.piece.amount}` },
470
+ currency: cur ?? "UNKNOWN",
471
+ kind: "money",
472
+ path: `fields.${val.piece.amount}`,
473
+ };
474
+ }
475
+ if (member === "release_to") {
476
+ return {
477
+ binding: {
478
+ from: "instance",
479
+ path: `fields.${val.piece.release_to}`,
480
+ },
481
+ kind: "account",
482
+ path: `fields.${val.piece.release_to}`,
483
+ };
484
+ }
485
+ if (member === "refund_to") {
486
+ return {
487
+ binding: {
488
+ from: "instance",
489
+ path: `fields.${val.piece.refund_to}`,
490
+ },
491
+ kind: "account",
492
+ path: `fields.${val.piece.refund_to}`,
493
+ };
494
+ }
495
+ if (member === "currency") {
496
+ const cur = pieceCurrency(val.piece, instrument);
497
+ if (cur === undefined) {
498
+ issues.push(issue("UDL4002", leafPath, `piece ${val.piece.id} currency cannot be resolved to a concrete currency`));
499
+ }
500
+ return {
501
+ binding: { from: "const", value: cur ?? "UNKNOWN" },
502
+ kind: "text",
503
+ };
504
+ }
505
+ issues.push(issue("UDL2011", leafPath, `unknown piece member ${member} in ${rawBind}`));
506
+ return undefined;
507
+ }
508
+ if (val.kind === "instance") {
509
+ if (parts.length === 1) {
510
+ return {
511
+ binding: { from: "instance", path: "instrumentInstanceId" },
512
+ kind: "instance",
513
+ };
514
+ }
515
+ if (parts[1] === "fields") {
516
+ if (parts.length !== 3) {
517
+ issues.push(issue("UDL2011", leafPath, `invalid trailing member access on instance field: ${rawBind}`));
518
+ return undefined;
519
+ }
520
+ const fieldName = parts[2];
521
+ const fieldSchema = instrument.fields[fieldName];
522
+ if (!fieldSchema) {
523
+ issues.push(issue("UDL2011", leafPath, `referenced field ${fieldName} is not declared on instrument`));
524
+ return undefined;
525
+ }
526
+ if (isMoneySchema(fieldSchema)) {
527
+ const cur = fieldSchema["x-hyperscale-currency"] ??
528
+ instrumentConcreteCurrency(instrument);
529
+ return {
530
+ binding: { from: "instance", path: `fields.${fieldName}` },
531
+ currency: cur,
532
+ kind: "money",
533
+ path: `fields.${fieldName}`,
534
+ };
535
+ }
536
+ if (isAccountSchema(fieldSchema)) {
537
+ return {
538
+ binding: { from: "instance", path: `fields.${fieldName}` },
539
+ kind: "account",
540
+ path: `fields.${fieldName}`,
541
+ };
542
+ }
543
+ if (isStringSchema(fieldSchema)) {
544
+ return {
545
+ binding: { from: "instance", path: `fields.${fieldName}` },
546
+ kind: "text",
547
+ path: `fields.${fieldName}`,
548
+ };
549
+ }
550
+ issues.push(issue("UDL2011", leafPath, `field ${fieldName} is not an account, money, or text field`));
551
+ return undefined;
552
+ }
553
+ if (parts.length === 2 &&
554
+ (parts[1] === "instrumentInstanceId" || parts[1] === "productId")) {
555
+ return {
556
+ binding: { from: "instance", path: parts[1] },
557
+ kind: "text",
558
+ path: parts[1],
559
+ };
560
+ }
561
+ issues.push(issue("UDL2011", leafPath, `invalid or trailing member access on instance reference: ${rawBind}`));
562
+ return undefined;
563
+ }
564
+ // Money, Account, Text parameter references do not allow member access
565
+ if (parts.length > 1) {
566
+ issues.push(issue("UDL2011", leafPath, `member access is not allowed on ${val.kind} parameter: ${rawBind}`));
567
+ return undefined;
568
+ }
569
+ if (val.kind === "account") {
570
+ return {
571
+ binding: { from: "instance", path: val.path },
572
+ kind: "account",
573
+ path: val.path,
574
+ };
575
+ }
576
+ if (val.kind === "money") {
577
+ return {
578
+ binding: { from: "instance", path: val.path },
579
+ currency: val.currency,
580
+ kind: "money",
581
+ path: val.path,
582
+ };
583
+ }
584
+ if (val.kind === "text") {
585
+ return {
586
+ binding: val.path
587
+ ? { from: "instance", path: val.path }
588
+ : { from: "const", value: val.value ?? "" },
589
+ kind: "text",
590
+ path: val.path,
591
+ value: val.value,
592
+ };
593
+ }
594
+ }
595
+ issues.push(issue("UDL2011", leafPath, `invalid or unsupported binding reference: ${rawBind}`));
596
+ return undefined;
597
+ }
598
+ function resolveCallParameters(call, targetActionDef, scope, callPath, issuesList, piece) {
599
+ const callScope = new Map();
600
+ // Check extra keys
601
+ for (const key of Object.keys(call.bind)) {
602
+ if (!Object.hasOwn(targetActionDef.parameters, key)) {
603
+ issuesList.push(issue("UDL2011", `${callPath}.bind.${key}`, `unexpected parameter ${key} in call to ${call.action}`));
604
+ }
605
+ }
606
+ // Check missing keys & resolve bindings
607
+ for (const [pName, pDef] of Object.entries(targetActionDef.parameters)) {
608
+ if (!Object.hasOwn(call.bind, pName)) {
609
+ issuesList.push(issue("UDL2011", `${callPath}.bind.${pName}`, `missing binding for parameter ${pName} in call to ${call.action}`));
610
+ continue;
611
+ }
612
+ const raw = call.bind[pName];
613
+ const resolved = resolveBinding(raw, scope, `${callPath}.bind.${pName}`);
614
+ if (resolved) {
615
+ if (pDef.kind !== resolved.kind) {
616
+ issuesList.push(issue("UDL2011", `${callPath}.bind.${pName}`, `parameter ${pName} expected kind ${pDef.kind} but received ${resolved.kind}`));
617
+ }
618
+ if (pDef.kind === "money" &&
619
+ pDef.currency &&
620
+ resolved.currency &&
621
+ pDef.currency !== resolved.currency) {
622
+ issuesList.push(issue("UDL2011", `${callPath}.bind.${pName}`, `currency mismatch for money parameter ${pName}: expected ${pDef.currency} but received ${resolved.currency}`));
623
+ }
624
+ if (resolved.kind === "piece") {
625
+ callScope.set(pName, {
626
+ kind: "piece",
627
+ piece: resolved.piece ?? piece,
628
+ });
629
+ }
630
+ else if (resolved.kind === "instance") {
631
+ callScope.set(pName, { kind: "instance" });
632
+ }
633
+ else if (resolved.kind === "account") {
634
+ callScope.set(pName, { kind: "account", path: resolved.path ?? "" });
635
+ }
636
+ else if (resolved.kind === "money") {
637
+ callScope.set(pName, {
638
+ currency: resolved.currency ?? pDef.currency,
639
+ kind: "money",
640
+ path: resolved.path ?? "",
641
+ });
642
+ }
643
+ else {
644
+ callScope.set(pName, {
645
+ kind: "text",
646
+ path: resolved.path,
647
+ value: resolved.value,
648
+ });
649
+ }
650
+ }
651
+ }
652
+ return callScope;
653
+ }
654
+ // Counts private-action expansions for one public action plan. Reset before
655
+ // each plan so the bound is per plan, not per document.
656
+ let totalCumulativeCalls = 0;
657
+ function expandPrivateAction(targetLibKey, targetActionKey, callArgs, originPrefix, callChain, callerPrincipal, capturedDestinations, consumedSources, expandedLeaves) {
658
+ totalCumulativeCalls += 1;
659
+ if (totalCumulativeCalls > UDL_LIMITS.maxActionExpansion) {
660
+ issues.push(issue("UDL2010", `$.actions.${originPrefix[0]}`, `action exceeds cumulative call bound of ${UDL_LIMITS.maxActionExpansion}`));
661
+ return;
662
+ }
663
+ if (callChain.length > UDL_LIMITS.maxDepth) {
664
+ issues.push(issue("UDL2010", `$.actions.${originPrefix[0]}`, `action recursion depth exceeded ${UDL_LIMITS.maxDepth}`));
665
+ return;
666
+ }
667
+ const actionRef = `${targetLibKey}.${targetActionKey}`;
668
+ if (callChain.includes(actionRef)) {
669
+ issues.push(issue("UDL2010", `$.actions.${originPrefix[0]}`, `action graph cycle detected in call to ${actionRef}`));
670
+ return;
671
+ }
672
+ const privAction = getAction(targetLibKey, targetActionKey);
673
+ if (!privAction) {
674
+ issues.push(issue("UDL2010", `$.actions.${originPrefix[0]}`, `call references unknown action ${targetActionKey} in library ${targetLibKey}`));
675
+ return;
676
+ }
677
+ if (privAction.principal !== callerPrincipal) {
678
+ issues.push(issue("UDL2012", `$.actionLibrary.${targetLibKey}.actions.${targetActionKey}.principal`, `principal mismatch: caller requires ${callerPrincipal} but ${actionRef} requires ${privAction.principal}`));
679
+ }
680
+ const leavesMap = new Map(privAction.leaves.map((l) => [l.id, l]));
681
+ const callsMap = new Map(privAction.calls.map((c) => [c.id, c]));
682
+ for (const itemId of privAction.order) {
683
+ if (expandedLeaves.length >= UDL_LIMITS.maxActionLeaves) {
684
+ issues.push(issue("UDL2010", `$.actions.${originPrefix[0]}`, `action exceeds cumulative expanded leaf bound of ${UDL_LIMITS.maxActionLeaves}`));
685
+ return;
686
+ }
687
+ if (leavesMap.has(itemId)) {
688
+ const leaf = leavesMap.get(itemId);
689
+ const leafPath = `$.actionLibrary.${targetLibKey}.actions.${targetActionKey}.leaves.${leaf.id}`;
690
+ const resolvedBinds = {};
691
+ for (const [key, bindStr] of Object.entries(leaf.bind)) {
692
+ const resolved = resolveBinding(bindStr, callArgs, `${leafPath}.bind.${key}`);
693
+ if (resolved) {
694
+ resolvedBinds[key] = resolved.binding;
695
+ if (key === "amount" && resolved.kind !== "money") {
696
+ issues.push(issue("UDL2011", `${leafPath}.bind.${key}`, `amount operand must resolve to money`));
697
+ }
698
+ if ((key === "sourceAccountId" ||
699
+ key === "destinationAccountId" ||
700
+ key === "accountId") &&
701
+ resolved.kind !== "account") {
702
+ issues.push(issue("UDL2011", `${leafPath}.bind.${key}`, `${key} operand must resolve to account`));
703
+ }
704
+ }
705
+ }
706
+ // Check leaf evidence
707
+ if (!leaf.evidence || leaf.evidence.trim().length === 0) {
708
+ issues.push(issue("UDL2013", `${leafPath}.evidence`, `leaf evidence is mandatory and cannot be blank`));
709
+ }
710
+ // Check leaf captures: destination ref is the map key
711
+ if (leaf.capture) {
712
+ for (const capKey of Object.keys(leaf.capture)) {
713
+ if (capturedDestinations.has(capKey)) {
714
+ issues.push(issue("UDL2011", `${leafPath}.capture.${capKey}`, `duplicate capture destination ${capKey} across expanded leaves`));
715
+ }
716
+ capturedDestinations.add(capKey);
717
+ }
718
+ }
719
+ const originPath = [...originPrefix, leaf.id];
720
+ let step;
721
+ if (leaf.operation.startsWith("internal_transfer.")) {
722
+ step = {
723
+ bind: resolvedBinds,
724
+ ...(leaf.capture ? { capture: leaf.capture } : {}),
725
+ key: encodeOriginPathKey(originPath),
726
+ operation: leaf.operation,
727
+ };
728
+ }
729
+ else {
730
+ step = {
731
+ bind: resolvedBinds,
732
+ ...(leaf.capture ? { capture: leaf.capture } : {}),
733
+ operation: leaf.operation,
734
+ };
735
+ }
736
+ // Check repeated consumption of money amount or hold identity
737
+ const amountSrc = boundPath(step, "amount");
738
+ if (amountSrc) {
739
+ if (consumedSources.has(amountSrc)) {
740
+ issues.push(issue("UDL2011", `${leafPath}.bind.amount`, `repeated consumption of money source ${amountSrc} in one expanded action`));
741
+ }
742
+ consumedSources.add(amountSrc);
743
+ }
744
+ const holdSrc = boundPath(step, "holdId");
745
+ if (holdSrc) {
746
+ const holdKey = `hold:${holdSrc}`;
747
+ if (consumedSources.has(holdKey)) {
748
+ issues.push(issue("UDL2011", `${leafPath}.bind.holdId`, `repeated consumption of hold ${holdSrc} in one expanded action`));
749
+ }
750
+ consumedSources.add(holdKey);
751
+ }
752
+ const expectedEffects = expectedLeafEffects(step);
753
+ const actualCounts = new Map();
754
+ for (const eff of leaf.effects) {
755
+ const key = `${eff.kind}:${eff.signature}`;
756
+ actualCounts.set(key, (actualCounts.get(key) ?? 0) + 1);
757
+ }
758
+ const expectedCounts = new Map();
759
+ for (const eff of expectedEffects) {
760
+ const key = `${eff.kind}:${eff.signature}`;
761
+ expectedCounts.set(key, (expectedCounts.get(key) ?? 0) + 1);
762
+ }
763
+ let effectsMatch = actualCounts.size === expectedCounts.size;
764
+ if (effectsMatch) {
765
+ for (const [k, count] of actualCounts.entries()) {
766
+ if (expectedCounts.get(k) !== count) {
767
+ effectsMatch = false;
768
+ break;
769
+ }
770
+ }
771
+ }
772
+ if (!effectsMatch) {
773
+ issues.push(issue("UDL2013", `${leafPath}.effects`, `declared leaf effects do not match expected effects for operation ${leaf.operation}`));
774
+ }
775
+ expandedLeaves.push({
776
+ effects: leaf.effects,
777
+ evidence: leaf.evidence,
778
+ originPath,
779
+ step,
780
+ });
781
+ }
782
+ else if (callsMap.has(itemId)) {
783
+ const nextCall = callsMap.get(itemId);
784
+ const [nextLib, nextAct] = nextCall.action.split(".");
785
+ if (!nextLib || !nextAct || !hasLibrary(nextLib)) {
786
+ issues.push(issue("UDL2010", `$.actionLibrary.${targetLibKey}.actions.${targetActionKey}.calls.${nextCall.id}.action`, `call ${nextCall.id} references unknown library ${nextLib ?? ""}`));
787
+ continue;
788
+ }
789
+ const nextActionDef = getAction(nextLib, nextAct);
790
+ if (!nextActionDef) {
791
+ issues.push(issue("UDL2010", `$.actionLibrary.${targetLibKey}.actions.${targetActionKey}.calls.${nextCall.id}.action`, `call ${nextCall.id} references unknown action ${nextAct} in library ${nextLib}`));
792
+ continue;
793
+ }
794
+ const nextScope = resolveCallParameters(nextCall, nextActionDef, callArgs, `$.actionLibrary.${targetLibKey}.actions.${targetActionKey}.calls.${nextCall.id}`, issues);
795
+ expandPrivateAction(nextLib, nextAct, nextScope, [...originPrefix, nextCall.id], [...callChain, actionRef], privAction.principal, capturedDestinations, consumedSources, expandedLeaves);
796
+ }
797
+ }
798
+ }
799
+ // Iterate over public actions in actionOrder
800
+ for (const actionKey of instrument.actionOrder) {
801
+ const action = instrument.actions[actionKey];
802
+ if (!action)
803
+ continue;
804
+ const actionPath = `$.actions.${actionKey}`;
805
+ const publicPrincipal = action.principal ?? "api_key";
806
+ // Reject mixed direct steps/moves and calls
807
+ if (action.calls &&
808
+ action.calls.length > 0 &&
809
+ (action.steps.length > 0 || action.moves.length > 0)) {
810
+ issues.push(issue("UDL2010", `${actionPath}.calls`, `action ${actionKey} cannot mix calls with direct steps or moves`));
811
+ }
812
+ if (action.calls) {
813
+ const publicCallIds = action.calls.map((c) => c.id);
814
+ if (new Set(publicCallIds).size !== publicCallIds.length) {
815
+ issues.push(issue("UDL2010", `${actionPath}.calls`, `duplicate call id in action ${actionKey}`));
816
+ }
817
+ }
818
+ if (action.pieceStage) {
819
+ if (!action.calls || action.calls.length === 0) {
820
+ issues.push(issue("UDL5013", `${actionPath}.calls`, `pieceStage action ${actionKey} must move money through calls, never through direct moves or steps`));
821
+ }
822
+ if (actionKey === "create") {
823
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage`, `create action cannot declare pieceStage`));
824
+ }
825
+ if (!instrument.piecePlan ||
826
+ instrument.piecePlan.id !== action.pieceStage.plan) {
827
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage.plan`, `pieceStage references unknown plan ${action.pieceStage.plan}`));
828
+ }
829
+ const stage = action.pieceStage.stage;
830
+ const stageOrder = stage === "fund"
831
+ ? (instrument.piecePlan?.fund_order ?? [])
832
+ : stage === "release"
833
+ ? (instrument.piecePlan?.release_order ?? [])
834
+ : stage === "refund"
835
+ ? (instrument.piecePlan?.refund_order ?? [])
836
+ : (instrument.piecePlan?.unfund_order ?? []);
837
+ if (stageOrder.length === 0) {
838
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage.stage`, `piece stage ${stage} is empty`));
839
+ }
840
+ // Check input must be present and exact
841
+ if (!action.input) {
842
+ issues.push(issue("UDL5013", `${actionPath}.input`, `pieceStage action input is required and must declare exact pieceId enum matching stage order`));
843
+ }
844
+ else {
845
+ const inp = action.input;
846
+ const props = (inp.properties ?? {});
847
+ const pieceIdProp = (props.pieceId ?? {});
848
+ const enumVals = Array.isArray(pieceIdProp.enum)
849
+ ? pieceIdProp.enum
850
+ : [];
851
+ if (inp.type !== "object" ||
852
+ inp.additionalProperties !== false ||
853
+ !Array.isArray(inp.required) ||
854
+ inp.required.length !== 1 ||
855
+ inp.required[0] !== "pieceId" ||
856
+ Object.keys(props).length !== 1 ||
857
+ pieceIdProp.type !== "string" ||
858
+ enumVals.length !== stageOrder.length ||
859
+ !enumVals.every((id, idx) => id === stageOrder[idx])) {
860
+ issues.push(issue("UDL5013", `${actionPath}.input`, `pieceStage action input must declare exact pieceId enum matching stage order`));
861
+ }
862
+ }
863
+ const variantMultiplicities = [];
864
+ for (const pieceId of stageOrder) {
865
+ const piece = instrument.piecePlan?.pieces.find((p) => p.id === pieceId);
866
+ if (!piece) {
867
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage`, `piece id ${pieceId} in stage order is not declared in piecePlan`));
868
+ continue;
869
+ }
870
+ const expandedLeaves = [];
871
+ const capturedDestinations = new Set();
872
+ const consumedSources = new Set();
873
+ totalCumulativeCalls = 0;
874
+ const scope = new Map([
875
+ ["piece", { kind: "piece", piece }],
876
+ ["instance", { kind: "instance" }],
877
+ ]);
878
+ for (const call of action.calls ?? []) {
879
+ const [lib, act] = call.action.split(".");
880
+ if (!lib || !act || !hasLibrary(lib)) {
881
+ issues.push(issue("UDL2010", `${actionPath}.calls.${call.id}.action`, `call ${call.id} references unknown library ${lib ?? ""}`));
882
+ continue;
883
+ }
884
+ const targetActionDef = getAction(lib, act);
885
+ if (!targetActionDef) {
886
+ issues.push(issue("UDL2010", `${actionPath}.calls.${call.id}.action`, `call ${call.id} references unknown action ${act} in library ${lib}`));
887
+ continue;
888
+ }
889
+ const callScope = resolveCallParameters(call, targetActionDef, scope, `${actionPath}.calls.${call.id}`, issues, piece);
890
+ expandPrivateAction(lib, act, callScope, [actionKey, call.id], [], publicPrincipal, capturedDestinations, consumedSources, expandedLeaves);
891
+ }
892
+ // Refuse input amount/destination binding in leaves and validate piece bindings
893
+ for (const leaf of expandedLeaves) {
894
+ const binds = leaf.step.bind ?? {};
895
+ for (const [k, b] of Object.entries(binds)) {
896
+ if ((k === "amount" ||
897
+ k === "sourceAccountId" ||
898
+ k === "destinationAccountId") &&
899
+ typeof b === "object" &&
900
+ b !== null &&
901
+ b.from === "input") {
902
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage`, `pieceStage leaves cannot bind amount or destination to input: ${k}`));
903
+ }
904
+ }
905
+ if (leaf.step.operation.startsWith("internal_transfer.")) {
906
+ const amt = binds.amount;
907
+ if (!amt ||
908
+ typeof amt !== "object" ||
909
+ amt.from !== "instance" ||
910
+ amt.path !== `fields.${piece.amount}`) {
911
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage`, `pieceStage transfer amount must resolve to selected piece.amount (${piece.amount})`));
912
+ }
913
+ if (stage === "release") {
914
+ const dest = binds.destinationAccountId;
915
+ if (!dest ||
916
+ typeof dest !== "object" ||
917
+ dest.from !== "instance" ||
918
+ dest.path !==
919
+ `fields.${piece.release_to}`) {
920
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage`, `pieceStage release destination must resolve to selected piece.release_to (${piece.release_to})`));
921
+ }
922
+ }
923
+ else if (stage === "refund") {
924
+ const dest = binds.destinationAccountId;
925
+ if (!dest ||
926
+ typeof dest !== "object" ||
927
+ dest.from !== "instance" ||
928
+ dest.path !== `fields.${piece.refund_to}`) {
929
+ issues.push(issue("UDL5013", `${actionPath}.pieceStage`, `pieceStage refund destination must resolve to selected piece.refund_to (${piece.refund_to})`));
930
+ }
931
+ }
932
+ }
933
+ }
934
+ const counts = new Map();
935
+ for (const leaf of expandedLeaves) {
936
+ for (const eff of leaf.effects) {
937
+ counts.set(eff.signature, (counts.get(eff.signature) ?? 0) + 1);
938
+ }
939
+ }
940
+ variantMultiplicities.push(counts);
941
+ const directEffects = deriveUdlActionEffects(action, udlClauseVocabulary);
942
+ const leafEffects = action.calls && action.calls.length > 0
943
+ ? deriveActionEffectsFromPlan(expandedLeaves)
944
+ : {};
945
+ const effects = combineDerivedEffects(directEffects, leafEffects);
946
+ plans.push({
947
+ action: actionKey,
948
+ effects,
949
+ leaves: expandedLeaves,
950
+ pieceId,
951
+ });
952
+ }
953
+ // Verify identical effect signature multiplicities across piece variants
954
+ if (variantMultiplicities.length > 1) {
955
+ const baseCounts = variantMultiplicities[0];
956
+ for (let i = 1; i < variantMultiplicities.length; i++) {
957
+ const comp = variantMultiplicities[i];
958
+ let identical = baseCounts.size === comp.size;
959
+ if (identical) {
960
+ for (const [sig, count] of baseCounts.entries()) {
961
+ if (comp.get(sig) !== count) {
962
+ identical = false;
963
+ break;
964
+ }
965
+ }
966
+ }
967
+ if (!identical) {
968
+ issues.push(issue("UDL2013", `${actionPath}.pieceStage`, `piece variants of action ${actionKey} have differing effect signature multiplicities`));
969
+ break;
970
+ }
971
+ }
972
+ }
973
+ }
974
+ else if (action.calls && action.calls.length > 0) {
975
+ const expandedLeaves = [];
976
+ const capturedDestinations = new Set();
977
+ const consumedSources = new Set();
978
+ totalCumulativeCalls = 0;
979
+ const scope = new Map([
980
+ ["instance", { kind: "instance" }],
981
+ ]);
982
+ for (const call of action.calls) {
983
+ const [lib, act] = call.action.split(".");
984
+ if (!lib || !act || !hasLibrary(lib)) {
985
+ issues.push(issue("UDL2010", `${actionPath}.calls.${call.id}.action`, `call ${call.id} references unknown library ${lib ?? ""}`));
986
+ continue;
987
+ }
988
+ const targetActionDef = getAction(lib, act);
989
+ if (!targetActionDef) {
990
+ issues.push(issue("UDL2010", `${actionPath}.calls.${call.id}.action`, `call ${call.id} references unknown action ${act} in library ${lib}`));
991
+ continue;
992
+ }
993
+ const callScope = resolveCallParameters(call, targetActionDef, scope, `${actionPath}.calls.${call.id}`, issues);
994
+ expandPrivateAction(lib, act, callScope, [actionKey, call.id], [], publicPrincipal, capturedDestinations, consumedSources, expandedLeaves);
995
+ }
996
+ const directEffects = deriveUdlActionEffects(action, udlClauseVocabulary);
997
+ const leafEffects = deriveActionEffectsFromPlan(expandedLeaves);
998
+ const effects = combineDerivedEffects(directEffects, leafEffects);
999
+ plans.push({
1000
+ action: actionKey,
1001
+ effects,
1002
+ leaves: expandedLeaves,
1003
+ });
1004
+ }
1005
+ else {
1006
+ // Ordinary actions already are flat. Preserve the executor's order and
1007
+ // authored step bytes; origin paths belong to the plan, not the ABI.
1008
+ const effects = deriveUdlActionEffects(action, udlClauseVocabulary);
1009
+ const leafEffects = (source) => Object.entries(effects).flatMap(([kind, rows]) => rows
1010
+ .filter((row) => row.source === source)
1011
+ .map((row) => ({
1012
+ kind: kind,
1013
+ signature: row.signature,
1014
+ })));
1015
+ const leaves = action.steps.map((step, index) => ({
1016
+ step,
1017
+ originPath: [actionKey, "steps", String(index)],
1018
+ effects: leafEffects(`steps[${index}]`),
1019
+ evidence: "parent_receipt",
1020
+ }));
1021
+ if (action.payout) {
1022
+ const intent = action.payout;
1023
+ leaves.push({
1024
+ step: {
1025
+ operation: "payout.create",
1026
+ bind: {
1027
+ amount: { from: "instance", path: intent.amount },
1028
+ beneficiaryId: {
1029
+ from: "instance",
1030
+ path: `fields.${intent.beneficiaryField}`,
1031
+ },
1032
+ currency: {
1033
+ from: "instance",
1034
+ path: `fields.${intent.currencyField}`,
1035
+ },
1036
+ sourceAccountId: {
1037
+ from: "instance",
1038
+ path: `fields.${intent.sourceAccountField}`,
1039
+ },
1040
+ speed: { from: "const", value: intent.speed },
1041
+ },
1042
+ capture: { [intent.capture]: "payoutId" },
1043
+ },
1044
+ originPath: [actionKey, "payout"],
1045
+ effects: leafEffects("payout"),
1046
+ evidence: "parent_receipt",
1047
+ });
1048
+ }
1049
+ leaves.push(...action.moves.map((step, index) => ({
1050
+ step,
1051
+ originPath: [actionKey, "moves", String(index)],
1052
+ effects: leafEffects(`moves[${index}]`),
1053
+ evidence: "parent_receipt",
1054
+ })));
1055
+ plans.push({
1056
+ action: actionKey,
1057
+ leaves,
1058
+ effects,
1059
+ });
1060
+ }
1061
+ }
1062
+ return { issues, plans };
1063
+ }
110
1064
  //# sourceMappingURL=effects.js.map