@hyperscale0/udl 1.0.0-alpha.4 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,6 +109,14 @@
109
109
  ]
110
110
  }
111
111
  },
112
+ "computedMoneyRefs": {
113
+ "minItems": 1,
114
+ "type": "array",
115
+ "items": {
116
+ "type": "string",
117
+ "pattern": "^[a-z][A-Za-z0-9]*$"
118
+ }
119
+ },
112
120
  "description": {
113
121
  "type": "string"
114
122
  },
@@ -116,6 +124,46 @@
116
124
  "type": "boolean",
117
125
  "const": true
118
126
  },
127
+ "derivedAmounts": {
128
+ "minItems": 1,
129
+ "maxItems": 4,
130
+ "type": "array",
131
+ "items": {
132
+ "type": "object",
133
+ "properties": {
134
+ "field": {
135
+ "type": "string",
136
+ "pattern": "^[a-z][A-Za-z0-9]*$"
137
+ },
138
+ "rounding": {
139
+ "type": "string",
140
+ "const": "floor"
141
+ },
142
+ "rule": {
143
+ "type": "object",
144
+ "properties": {
145
+ "bps": {
146
+ "type": "integer",
147
+ "minimum": 1,
148
+ "maximum": 9999
149
+ },
150
+ "kind": {
151
+ "type": "string",
152
+ "const": "percentage_of"
153
+ }
154
+ },
155
+ "required": ["bps", "kind"],
156
+ "additionalProperties": false
157
+ },
158
+ "sourceField": {
159
+ "type": "string",
160
+ "pattern": "^[a-z][A-Za-z0-9]*$"
161
+ }
162
+ },
163
+ "required": ["field", "rounding", "rule", "sourceField"],
164
+ "additionalProperties": false
165
+ }
166
+ },
119
167
  "fields": {
120
168
  "type": "object",
121
169
  "propertyNames": {
@@ -338,6 +386,17 @@
338
386
  "additionalProperties": {
339
387
  "type": "object",
340
388
  "properties": {
389
+ "captureInput": {
390
+ "type": "object",
391
+ "propertyNames": {
392
+ "type": "string",
393
+ "pattern": "^[a-z][A-Za-z0-9]*$"
394
+ },
395
+ "additionalProperties": {
396
+ "type": "string",
397
+ "pattern": "^[a-z][A-Za-z0-9]*$"
398
+ }
399
+ },
341
400
  "deadline": {
342
401
  "type": "object",
343
402
  "properties": {
@@ -372,6 +431,59 @@
372
431
  "required": ["capability", "deadlineMs", "onTimeout"],
373
432
  "additionalProperties": false
374
433
  },
434
+ "distribute": {
435
+ "type": "object",
436
+ "properties": {
437
+ "amountRef": {
438
+ "type": "string",
439
+ "pattern": "^[a-z][A-Za-z0-9]*$"
440
+ },
441
+ "onZero": {
442
+ "type": "string",
443
+ "enum": ["refuse", "skip_steps"]
444
+ },
445
+ "pool": {
446
+ "type": "object",
447
+ "properties": {
448
+ "from": {
449
+ "type": "string",
450
+ "const": "parent"
451
+ },
452
+ "path": {
453
+ "type": "string",
454
+ "pattern": "^[a-z][A-Za-z0-9]*(?:\\.[a-z][A-Za-z0-9]*)*$"
455
+ }
456
+ },
457
+ "required": ["from", "path"],
458
+ "additionalProperties": false
459
+ },
460
+ "refField": {
461
+ "type": "string",
462
+ "pattern": "^[a-z][A-Za-z0-9]*$"
463
+ },
464
+ "statuses": {
465
+ "minItems": 1,
466
+ "type": "array",
467
+ "items": {
468
+ "type": "string",
469
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
470
+ }
471
+ },
472
+ "weightField": {
473
+ "type": "string",
474
+ "pattern": "^[a-z][A-Za-z0-9]*$"
475
+ }
476
+ },
477
+ "required": [
478
+ "amountRef",
479
+ "onZero",
480
+ "pool",
481
+ "refField",
482
+ "statuses",
483
+ "weightField"
484
+ ],
485
+ "additionalProperties": false
486
+ },
375
487
  "description": {
376
488
  "type": "string"
377
489
  },
@@ -580,6 +692,49 @@
580
692
  "additionalProperties": false
581
693
  }
582
694
  },
695
+ "payout": {
696
+ "type": "object",
697
+ "properties": {
698
+ "amount": {
699
+ "type": "string",
700
+ "pattern": "^(?:fields|refs)\\.[a-z][A-Za-z0-9]*$"
701
+ },
702
+ "beneficiaryField": {
703
+ "type": "string",
704
+ "pattern": "^[a-z][A-Za-z0-9]*$"
705
+ },
706
+ "beneficiaryPartyField": {
707
+ "type": "string",
708
+ "pattern": "^[a-z][A-Za-z0-9]*$"
709
+ },
710
+ "capture": {
711
+ "type": "string",
712
+ "pattern": "^[a-z][A-Za-z0-9]*$"
713
+ },
714
+ "currencyField": {
715
+ "type": "string",
716
+ "pattern": "^[a-z][A-Za-z0-9]*$"
717
+ },
718
+ "sourceAccountField": {
719
+ "type": "string",
720
+ "pattern": "^[a-z][A-Za-z0-9]*$"
721
+ },
722
+ "speed": {
723
+ "type": "string",
724
+ "const": "standard"
725
+ }
726
+ },
727
+ "required": [
728
+ "amount",
729
+ "beneficiaryField",
730
+ "beneficiaryPartyField",
731
+ "capture",
732
+ "currencyField",
733
+ "sourceAccountField",
734
+ "speed"
735
+ ],
736
+ "additionalProperties": false
737
+ },
583
738
  "port": {
584
739
  "type": "object",
585
740
  "properties": {
@@ -595,6 +750,11 @@
595
750
  "required": ["allowedParties"],
596
751
  "additionalProperties": false
597
752
  },
753
+ "publicIntent": {
754
+ "description": "Author-approved public intent name; the containing verb key remains the lifecycle and execution identity",
755
+ "type": "string",
756
+ "pattern": "^[a-z][A-Za-z0-9]*$"
757
+ },
598
758
  "requiresAggregate": {
599
759
  "minItems": 1,
600
760
  "type": "array",
@@ -758,6 +918,51 @@
758
918
  "required": ["path"],
759
919
  "additionalProperties": false
760
920
  },
921
+ "requiresExposure": {
922
+ "minItems": 1,
923
+ "type": "array",
924
+ "items": {
925
+ "type": "object",
926
+ "properties": {
927
+ "amountField": {
928
+ "type": "string",
929
+ "pattern": "^[a-z][A-Za-z0-9]*$"
930
+ },
931
+ "anchorField": {
932
+ "type": "string",
933
+ "pattern": "^[a-z][A-Za-z0-9]*$"
934
+ },
935
+ "capField": {
936
+ "type": "string",
937
+ "pattern": "^[a-z][A-Za-z0-9]*$"
938
+ },
939
+ "capOnAnchor": {
940
+ "type": "boolean",
941
+ "const": true
942
+ },
943
+ "childNounId": {
944
+ "type": "string",
945
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
946
+ },
947
+ "statuses": {
948
+ "minItems": 1,
949
+ "type": "array",
950
+ "items": {
951
+ "type": "string",
952
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
953
+ }
954
+ }
955
+ },
956
+ "required": [
957
+ "amountField",
958
+ "anchorField",
959
+ "capField",
960
+ "childNounId",
961
+ "statuses"
962
+ ],
963
+ "additionalProperties": false
964
+ }
965
+ },
761
966
  "requiresRefs": {
762
967
  "minItems": 1,
763
968
  "type": "array",
@@ -811,6 +1016,21 @@
811
1016
  "additionalProperties": false
812
1017
  }
813
1018
  },
1019
+ "requiresSettlement": {
1020
+ "type": "object",
1021
+ "properties": {
1022
+ "capture": {
1023
+ "type": "string",
1024
+ "pattern": "^[a-z][A-Za-z0-9]*$"
1025
+ },
1026
+ "payoutRef": {
1027
+ "type": "string",
1028
+ "pattern": "^[a-z][A-Za-z0-9]*$"
1029
+ }
1030
+ },
1031
+ "required": ["capture", "payoutRef"],
1032
+ "additionalProperties": false
1033
+ },
814
1034
  "setsAt": {
815
1035
  "type": "object",
816
1036
  "properties": {
@@ -818,6 +1038,10 @@
818
1038
  "type": "string",
819
1039
  "pattern": "^[a-z][A-Za-z0-9]*$"
820
1040
  },
1041
+ "marker": {
1042
+ "type": "boolean",
1043
+ "const": true
1044
+ },
821
1045
  "offset": {
822
1046
  "type": "string"
823
1047
  }
@@ -825,6 +1049,71 @@
825
1049
  "required": ["field", "offset"],
826
1050
  "additionalProperties": false
827
1051
  },
1052
+ "signedSum": {
1053
+ "type": "object",
1054
+ "properties": {
1055
+ "amountRef": {
1056
+ "type": "string",
1057
+ "pattern": "^[a-z][A-Za-z0-9]*$"
1058
+ },
1059
+ "onNegative": {
1060
+ "type": "string",
1061
+ "const": "refuse"
1062
+ },
1063
+ "onZero": {
1064
+ "type": "string",
1065
+ "enum": ["refuse", "skip_steps"]
1066
+ },
1067
+ "sources": {
1068
+ "minItems": 1,
1069
+ "type": "array",
1070
+ "items": {
1071
+ "type": "object",
1072
+ "properties": {
1073
+ "amountField": {
1074
+ "type": "string",
1075
+ "pattern": "^[a-z][A-Za-z0-9]*$"
1076
+ },
1077
+ "nounId": {
1078
+ "type": "string",
1079
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
1080
+ },
1081
+ "refField": {
1082
+ "type": "string",
1083
+ "pattern": "^[a-z][A-Za-z0-9]*$"
1084
+ },
1085
+ "sign": {
1086
+ "type": "string",
1087
+ "enum": ["add", "subtract"]
1088
+ },
1089
+ "statuses": {
1090
+ "minItems": 1,
1091
+ "type": "array",
1092
+ "items": {
1093
+ "type": "string",
1094
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
1095
+ }
1096
+ },
1097
+ "subtotalRef": {
1098
+ "type": "string",
1099
+ "pattern": "^[a-z][A-Za-z0-9]*$"
1100
+ }
1101
+ },
1102
+ "required": [
1103
+ "amountField",
1104
+ "nounId",
1105
+ "refField",
1106
+ "sign",
1107
+ "statuses",
1108
+ "subtotalRef"
1109
+ ],
1110
+ "additionalProperties": false
1111
+ }
1112
+ }
1113
+ },
1114
+ "required": ["amountRef", "onNegative", "onZero", "sources"],
1115
+ "additionalProperties": false
1116
+ },
828
1117
  "steps": {
829
1118
  "type": "array",
830
1119
  "items": {
package/src/evolution.ts CHANGED
@@ -18,8 +18,12 @@ export interface EvolutionMoveSnapshot extends EvolutionStepSnapshot {
18
18
  }
19
19
 
20
20
  export interface EvolutionVerbSnapshot {
21
+ /** Missing on snapshots written before receipt-input capture existed. */
22
+ readonly captureInput?: unknown;
21
23
  readonly deadline: unknown;
22
24
  readonly decision: unknown;
25
+ /** Missing on snapshots written before weighted distribution existed. */
26
+ readonly distribute?: unknown;
23
27
  readonly due: unknown;
24
28
  readonly earnable: boolean;
25
29
  readonly eventName: string | null;
@@ -30,11 +34,21 @@ export interface EvolutionVerbSnapshot {
30
34
  * friends change what callers may send, so they are frozen once live.
31
35
  */
32
36
  readonly inputConstraints: unknown;
37
+ /** Missing on snapshots written before payout intents existed. */
38
+ readonly payout?: unknown;
33
39
  readonly port: unknown;
40
+ /** Missing on snapshots written before public intent metadata existed. */
41
+ readonly publicIntent?: string | null;
34
42
  readonly requiresAggregate: unknown;
35
43
  readonly requiresDrainedAccount: unknown;
44
+ /** Missing on snapshots written before exposure gates entered open UDL. */
45
+ readonly requiresExposure?: unknown;
36
46
  readonly requiresRefs: unknown;
47
+ /** Missing on snapshots written before settlement evidence gates existed. */
48
+ readonly requiresSettlement?: unknown;
37
49
  readonly setsAt: unknown;
50
+ /** Missing on snapshots written before signed child sums existed. */
51
+ readonly signedSum?: unknown;
38
52
  readonly moves: readonly EvolutionMoveSnapshot[];
39
53
  readonly steps: readonly EvolutionStepSnapshot[];
40
54
  }
@@ -47,6 +61,9 @@ export interface EvolutionTransitionSnapshot {
47
61
  /** The complete serializable algebra protected by append-only evolution. */
48
62
  export interface NounEvolutionSnapshot {
49
63
  readonly aggregateInvariants: readonly string[];
64
+ /** Missing on snapshots written before typed computed refs entered UDL. */
65
+ readonly computedMoneyRefs?: readonly string[];
66
+ readonly derivedAmounts: readonly string[];
50
67
  readonly distinctParties?: true;
51
68
  readonly fields: Readonly<Record<string, EvolutionFieldSnapshot>>;
52
69
  readonly id: string;
@@ -70,6 +87,11 @@ export function snapshotUdlNoun(noun: UdlNoun): NounEvolutionSnapshot {
70
87
  aggregateInvariants: (noun.aggregateInvariants ?? []).map(
71
88
  aggregateInvariantKey,
72
89
  ),
90
+ computedMoneyRefs: [...(noun.computedMoneyRefs ?? [])].sort(),
91
+ derivedAmounts: (noun.derivedAmounts ?? []).map(
92
+ (amount) =>
93
+ `${amount.field}=floor(${amount.sourceField}*${amount.rule.bps}/10000)`,
94
+ ),
73
95
  ...(noun.distinctParties ? { distinctParties: true as const } : {}),
74
96
  fields: Object.fromEntries(
75
97
  Object.entries(noun.fields).map(([field, schema]) => [
@@ -235,6 +257,22 @@ export function diffNounEvolution(
235
257
  violations.push(...diffVerbs(previous.verbs, next.verbs));
236
258
  violations.push(...diffParties(previous.parties, next.parties));
237
259
  violations.push(...diffAggregates(previous, next));
260
+ if (
261
+ stableStringify(previous.computedMoneyRefs ?? []) !==
262
+ stableStringify(next.computedMoneyRefs ?? [])
263
+ ) {
264
+ violations.push(
265
+ "computed money refs changed; runtime-derived money surfaces are frozen once live",
266
+ );
267
+ }
268
+ if (
269
+ stableStringify(previous.derivedAmounts ?? []) !==
270
+ stableStringify(next.derivedAmounts ?? [])
271
+ ) {
272
+ violations.push(
273
+ "derived amount rules changed; derived money arithmetic is frozen once live",
274
+ );
275
+ }
238
276
  violations.push(
239
277
  ...removedSetEntries(
240
278
  previous.partitions,
@@ -271,8 +309,10 @@ export function diffNounEvolution(
271
309
 
272
310
  function snapshotUdlVerb(definition: UdlVerb): EvolutionVerbSnapshot {
273
311
  return {
312
+ captureInput: definition.captureInput ?? null,
274
313
  deadline: definition.deadline ?? null,
275
314
  decision: definition.decision ?? null,
315
+ distribute: definition.distribute ?? null,
276
316
  due: definition.due ?? null,
277
317
  earnable: definition.earnable ?? false,
278
318
  eventName: definition.eventName ?? null,
@@ -280,8 +320,11 @@ function snapshotUdlVerb(definition: UdlVerb): EvolutionVerbSnapshot {
280
320
  inputConstraints: definition.input
281
321
  ? snapshotJsonSchemaConstraints(definition.input)
282
322
  : null,
323
+ payout: definition.payout ?? null,
283
324
  port: definition.port ?? null,
325
+ publicIntent: definition.publicIntent ?? null,
284
326
  setsAt: definition.setsAt ?? null,
327
+ signedSum: definition.signedSum ?? null,
285
328
  moves: definition.moves.map((move) => ({
286
329
  bind: move.bind,
287
330
  capture: move.capture ?? {},
@@ -290,7 +333,9 @@ function snapshotUdlVerb(definition: UdlVerb): EvolutionVerbSnapshot {
290
333
  })),
291
334
  requiresAggregate: definition.requiresAggregate ?? [],
292
335
  requiresDrainedAccount: definition.requiresDrainedAccount ?? null,
336
+ requiresExposure: definition.requiresExposure ?? [],
293
337
  requiresRefs: definition.requiresRefs ?? [],
338
+ requiresSettlement: definition.requiresSettlement ?? null,
294
339
  steps: definition.steps.map((step) => ({
295
340
  bind: step.bind,
296
341
  capture: step.capture ?? {},
@@ -431,6 +476,26 @@ function diffVerbs(
431
476
  ) {
432
477
  violations.push(`verb ${verb} changed its drained-account gate`);
433
478
  }
479
+ if (
480
+ descriptor.requiresExposure !== undefined &&
481
+ stableStringify(descriptor.requiresExposure) !==
482
+ stableStringify(current.requiresExposure)
483
+ ) {
484
+ violations.push(`verb ${verb} changed its exposure admission gates`);
485
+ }
486
+ if (
487
+ descriptor.requiresSettlement !== undefined &&
488
+ stableStringify(descriptor.requiresSettlement) !==
489
+ stableStringify(current.requiresSettlement)
490
+ ) {
491
+ violations.push(`verb ${verb} changed its settlement evidence gate`);
492
+ }
493
+ if (
494
+ stableStringify(descriptor.payout ?? null) !==
495
+ stableStringify(current.payout ?? null)
496
+ ) {
497
+ violations.push(`verb ${verb} changed its payout intent`);
498
+ }
434
499
  if (descriptor.earnable !== current.earnable) {
435
500
  violations.push(`verb ${verb} changed its earnable flag`);
436
501
  }
@@ -450,14 +515,48 @@ function diffVerbs(
450
515
  ) {
451
516
  violations.push(`verb ${verb} changed its provider decision`);
452
517
  }
518
+ if (
519
+ stableStringify(descriptor.distribute) !==
520
+ stableStringify(current.distribute)
521
+ ) {
522
+ violations.push(
523
+ `verb ${verb} changed its distribution rule; money distribution is frozen once live`,
524
+ );
525
+ }
526
+ if (
527
+ descriptor.captureInput !== undefined &&
528
+ stableStringify(descriptor.captureInput) !==
529
+ stableStringify(current.captureInput)
530
+ ) {
531
+ violations.push(`verb ${verb} changed its captured receipt input`);
532
+ }
453
533
  if (stableStringify(descriptor.port) !== stableStringify(current.port)) {
454
534
  violations.push(`verb ${verb} changed its decision port`);
455
535
  }
536
+ if (
537
+ descriptor.publicIntent != null &&
538
+ descriptor.publicIntent !== current.publicIntent
539
+ ) {
540
+ violations.push(`verb ${verb} changed its public intent`);
541
+ }
456
542
  if (
457
543
  stableStringify(descriptor.setsAt) !== stableStringify(current.setsAt)
458
544
  ) {
459
545
  violations.push(`verb ${verb} changed its computed timestamp`);
460
546
  }
547
+ if (
548
+ descriptor.signedSum !== undefined &&
549
+ stableStringify(descriptor.signedSum) !==
550
+ stableStringify(current.signedSum)
551
+ ) {
552
+ violations.push(`verb ${verb} changed its signed child sum`);
553
+ }
554
+ }
555
+ for (const [verb, descriptor] of Object.entries(next)) {
556
+ if (previous[verb] || descriptor.payout == null) continue;
557
+ violations.push(
558
+ `verb ${verb} added a payout intent; external money movement is frozen once live`,
559
+ );
461
560
  }
462
561
  return violations;
463
562
  }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export {
23
23
  UDL_FORMAT_VERSION,
24
24
  udlDocumentSchema,
25
25
  udlKernelOperationSchema,
26
+ udlPublicIntentSchema,
26
27
  } from "./schema.js";
27
28
  export type {
28
29
  UdlAggregate,
@@ -39,6 +40,8 @@ export type {
39
40
  UdlLifecycleTransition,
40
41
  UdlNoun,
41
42
  UdlNounSubject,
43
+ UdlPayout,
44
+ UdlRequiresSettlement,
42
45
  UdlStep,
43
46
  UdlSubject,
44
47
  UdlUnwind,
@@ -49,6 +52,7 @@ export {
49
52
  openReferenceShapeBudget,
50
53
  UdlError,
51
54
  validateUdl,
55
+ validateUdlJsonSchema,
52
56
  validateUdlSchemaValue,
53
57
  } from "./validation.js";
54
58
  export type {