@hyperscale0/udl 1.0.0-beta.1 → 1.0.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 (179) hide show
  1. package/CHANGELOG.md +78 -25
  2. package/README.md +37 -99
  3. package/conformance/README.md +44 -41
  4. package/conformance/evolution/action-contract.expected.json +10 -0
  5. package/conformance/evolution/action-contract.live.udl +44 -0
  6. package/conformance/evolution/action-contract.next.udl +45 -0
  7. package/conformance/evolution/product-identity.expected.json +10 -0
  8. package/conformance/evolution/product-identity.live.udl +55 -0
  9. package/conformance/evolution/product-identity.next.udl +55 -0
  10. package/conformance/evolution/version-required.expected.json +10 -0
  11. package/conformance/evolution/version-required.live.udl +55 -0
  12. package/conformance/evolution/version-required.next.udl +58 -0
  13. package/conformance/invalid/action-without-transition.expected.json +10 -0
  14. package/conformance/invalid/{verb-without-transition.udl → action-without-transition.udl} +7 -2
  15. package/conformance/invalid/agent-description-too-long.expected.json +10 -0
  16. package/conformance/invalid/agent-description-too-long.udl +57 -0
  17. package/conformance/invalid/blank-title.expected.json +1 -1
  18. package/conformance/invalid/blank-title.udl +6 -2
  19. package/conformance/invalid/composition-dial-duplicate-key.expected.json +10 -0
  20. package/conformance/invalid/composition-dial-duplicate-key.udl +73 -0
  21. package/conformance/invalid/depth-budget.expected.json +1 -1
  22. package/conformance/invalid/duplicate-subject.expected.json +10 -0
  23. package/conformance/invalid/duplicate-subject.udl +230 -0
  24. package/conformance/invalid/forged-effects.expected.json +10 -0
  25. package/conformance/invalid/forged-effects.udl +76 -0
  26. package/conformance/invalid/format-version.expected.json +1 -1
  27. package/conformance/invalid/format-version.udl +6 -2
  28. package/conformance/invalid/instrument-id-not-snake-case.expected.json +10 -0
  29. package/conformance/invalid/{noun-id-not-snake-case.udl → instrument-id-not-snake-case.udl} +6 -2
  30. package/conformance/invalid/invalid-aggregate-gate-shape.expected.json +10 -0
  31. package/conformance/invalid/invalid-aggregate-gate-shape.udl +1819 -0
  32. package/conformance/invalid/invalid-check-duration.expected.json +10 -0
  33. package/conformance/invalid/invalid-check-duration.udl +219 -0
  34. package/conformance/invalid/invalid-dial-anchor.expected.json +10 -0
  35. package/conformance/invalid/invalid-dial-anchor.udl +219 -0
  36. package/conformance/invalid/invalid-exception-parent-ref.expected.json +10 -0
  37. package/conformance/invalid/invalid-exception-parent-ref.udl +2487 -0
  38. package/conformance/invalid/invalid-exposure-shape.expected.json +10 -0
  39. package/conformance/invalid/invalid-exposure-shape.udl +1819 -0
  40. package/conformance/invalid/invalid-remainder.expected.json +10 -0
  41. package/conformance/invalid/invalid-remainder.udl +220 -0
  42. package/conformance/invalid/invalid-schema-keyword.expected.json +10 -0
  43. package/conformance/invalid/invalid-schema-keyword.udl +220 -0
  44. package/conformance/invalid/invalid-utf8.expected.json +1 -1
  45. package/conformance/invalid/malformed-json.expected.json +1 -1
  46. package/conformance/invalid/missing-create-action.expected.json +10 -0
  47. package/conformance/invalid/{missing-create-verb.udl → missing-create-action.udl} +5 -2
  48. package/conformance/invalid/missing-exception-amount-field.expected.json +10 -0
  49. package/conformance/invalid/missing-exception-amount-field.udl +2487 -0
  50. package/conformance/invalid/missing-exception-contract.expected.json +14 -0
  51. package/conformance/invalid/missing-exception-contract.udl +2523 -0
  52. package/conformance/invalid/missing-exception-reason-field.expected.json +10 -0
  53. package/conformance/invalid/missing-exception-reason-field.udl +2487 -0
  54. package/conformance/invalid/not-an-object.expected.json +1 -1
  55. package/conformance/invalid/payout-reconcile-not-a-bank-debit.expected.json +10 -0
  56. package/conformance/invalid/payout-reconcile-not-a-bank-debit.udl +259 -0
  57. package/conformance/invalid/quote-freeze-set-incomplete.expected.json +10 -0
  58. package/conformance/invalid/quote-freeze-set-incomplete.udl +261 -0
  59. package/conformance/invalid/quote-named-reference-gate.expected.json +10 -0
  60. package/conformance/invalid/quote-named-reference-gate.udl +50 -0
  61. package/conformance/invalid/reconcile-named-reference-gate.expected.json +10 -0
  62. package/conformance/invalid/reconcile-named-reference-gate.udl +50 -0
  63. package/conformance/invalid/unknown-key.expected.json +1 -1
  64. package/conformance/invalid/unknown-key.udl +6 -2
  65. package/conformance/invalid/unknown-reference-gate-field.expected.json +10 -0
  66. package/conformance/invalid/unknown-reference-gate-field.udl +1819 -0
  67. package/conformance/invalid/unknown-required-field.expected.json +10 -0
  68. package/conformance/invalid/unknown-required-field.udl +220 -0
  69. package/conformance/invalid/unreachable-state.expected.json +2 -2
  70. package/conformance/invalid/unreachable-state.udl +6 -2
  71. package/conformance/invalid/wrong-exception-amount-field.expected.json +10 -0
  72. package/conformance/invalid/wrong-exception-amount-field.udl +2487 -0
  73. package/conformance/invalid/wrong-exception-reason-field.expected.json +10 -0
  74. package/conformance/invalid/wrong-exception-reason-field.udl +2487 -0
  75. package/conformance/valid/agent-description.expected.json +6 -0
  76. package/conformance/valid/agent-description.udl +66 -0
  77. package/conformance/valid/cards.expected.json +1 -0
  78. package/conformance/valid/cards.udl +843 -356
  79. package/conformance/valid/commerce-escrow.expected.json +1 -0
  80. package/conformance/valid/commerce-escrow.udl +792 -291
  81. package/conformance/valid/compiled-crowdfunding.expected.json +6 -0
  82. package/conformance/valid/compiled-crowdfunding.udl +1843 -0
  83. package/conformance/valid/compiled-watch-club.expected.json +6 -0
  84. package/conformance/valid/compiled-watch-club.udl +2486 -0
  85. package/conformance/valid/complete-contract.expected.json +6 -0
  86. package/conformance/valid/complete-contract.udl +218 -0
  87. package/conformance/valid/effect-signatures.expected.json +6 -0
  88. package/conformance/valid/effect-signatures.udl +75 -0
  89. package/conformance/valid/hand-edited.expected.json +1 -0
  90. package/conformance/valid/hand-edited.udl +1 -1
  91. package/conformance/valid/insured-car-marketplace.expected.json +1 -0
  92. package/conformance/valid/insured-car-marketplace.udl +534 -315
  93. package/conformance/valid/insured-travel.expected.json +2 -1
  94. package/conformance/valid/insured-travel.udl +1489 -641
  95. package/conformance/valid/minimal.expected.json +1 -0
  96. package/conformance/valid/minimal.udl +18 -14
  97. package/conformance/valid/protection.expected.json +1 -0
  98. package/conformance/valid/protection.udl +719 -304
  99. package/conformance/valid/string-escaping.expected.json +6 -0
  100. package/conformance/valid/string-escaping.udl +54 -0
  101. package/dist/canonical.d.ts +2 -0
  102. package/dist/canonical.d.ts.map +1 -1
  103. package/dist/canonical.js +7 -0
  104. package/dist/canonical.js.map +1 -1
  105. package/dist/check-profiles.d.ts +57 -0
  106. package/dist/check-profiles.d.ts.map +1 -0
  107. package/dist/check-profiles.js +62 -0
  108. package/dist/check-profiles.js.map +1 -0
  109. package/dist/cli.js +48 -4
  110. package/dist/cli.js.map +1 -1
  111. package/dist/diagnostics.d.ts +168 -0
  112. package/dist/diagnostics.d.ts.map +1 -0
  113. package/dist/diagnostics.js +164 -0
  114. package/dist/diagnostics.js.map +1 -0
  115. package/dist/effects.d.ts +41 -0
  116. package/dist/effects.d.ts.map +1 -0
  117. package/dist/effects.js +110 -0
  118. package/dist/effects.js.map +1 -0
  119. package/dist/evolution.d.ts +51 -18
  120. package/dist/evolution.d.ts.map +1 -1
  121. package/dist/evolution.js +244 -102
  122. package/dist/evolution.js.map +1 -1
  123. package/dist/finance.d.ts +17 -13
  124. package/dist/finance.d.ts.map +1 -1
  125. package/dist/finance.js +165 -109
  126. package/dist/finance.js.map +1 -1
  127. package/dist/index.d.ts +12 -8
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +8 -5
  130. package/dist/index.js.map +1 -1
  131. package/dist/limits.d.ts +2 -2
  132. package/dist/limits.d.ts.map +1 -1
  133. package/dist/limits.js +4 -3
  134. package/dist/limits.js.map +1 -1
  135. package/dist/parser.d.ts.map +1 -1
  136. package/dist/parser.js +6 -16
  137. package/dist/parser.js.map +1 -1
  138. package/dist/schema.d.ts +2059 -135
  139. package/dist/schema.d.ts.map +1 -1
  140. package/dist/schema.js +759 -73
  141. package/dist/schema.js.map +1 -1
  142. package/dist/validation.d.ts +22 -9
  143. package/dist/validation.d.ts.map +1 -1
  144. package/dist/validation.js +1463 -457
  145. package/dist/validation.js.map +1 -1
  146. package/docs/README.md +14 -0
  147. package/docs/guide/01-a-document.md +37 -0
  148. package/docs/guide/02-money-steps.md +23 -0
  149. package/docs/guide/03-laws.md +18 -0
  150. package/docs/guide/04-fees-and-remainder.md +15 -0
  151. package/docs/guide/05-checks-updates-dials.md +7 -0
  152. package/docs/guide/06-effects.md +11 -0
  153. package/docs/guide/07-evolution.md +11 -0
  154. package/docs/guide/08-implementing.md +13 -0
  155. package/docs/llms-full.txt +1241 -0
  156. package/docs/llms.txt +14 -0
  157. package/docs/reference/canonical.md +16 -0
  158. package/docs/reference/clauses.md +986 -0
  159. package/docs/reference/cli.md +22 -0
  160. package/docs/reference/diagnostics.md +32 -0
  161. package/package.json +6 -2
  162. package/skills/udl/SKILL.md +28 -0
  163. package/spec/README.md +82 -51
  164. package/spec/udl.schema.json +1191 -119
  165. package/src/canonical.ts +11 -0
  166. package/src/check-profiles.ts +80 -0
  167. package/src/cli.ts +49 -3
  168. package/src/diagnostics.ts +221 -0
  169. package/src/effects.ts +163 -0
  170. package/src/evolution.ts +375 -125
  171. package/src/finance.ts +255 -160
  172. package/src/index.ts +50 -14
  173. package/src/limits.ts +4 -3
  174. package/src/parser.ts +15 -18
  175. package/src/schema.ts +855 -81
  176. package/src/validation.ts +2666 -560
  177. package/conformance/invalid/missing-create-verb.expected.json +0 -10
  178. package/conformance/invalid/noun-id-not-snake-case.expected.json +0 -10
  179. package/conformance/invalid/verb-without-transition.expected.json +0 -10
@@ -0,0 +1,1819 @@
1
+ {
2
+ "instruments": [
3
+ {
4
+ "actionOrder": [
5
+ "create",
6
+ "fund_piece_1",
7
+ "accept_commitment",
8
+ "cancel",
9
+ "abandon"
10
+ ],
11
+ "actions": {
12
+ "abandon": {
13
+ "moves": [],
14
+ "publicAction": "abandonLenderCommitment",
15
+ "requiresDrainedAccount": {
16
+ "path": "refs.escrowAccountId"
17
+ },
18
+ "steps": [],
19
+ "summary": "Abandon the settlement before any money is held"
20
+ },
21
+ "accept_commitment": {
22
+ "effects": {
23
+ "decides": [
24
+ {
25
+ "signature": "decides.tenant_port",
26
+ "source": "port"
27
+ }
28
+ ],
29
+ "moves": [
30
+ {
31
+ "signature": "moves.transfer.internal",
32
+ "source": "moves[0]"
33
+ }
34
+ ]
35
+ },
36
+ "input": {
37
+ "additionalProperties": false,
38
+ "properties": {
39
+ "acceptanceReference": {
40
+ "description": "Required decision reference retained in the operation receipt",
41
+ "maxLength": 180,
42
+ "minLength": 1,
43
+ "type": "string"
44
+ }
45
+ },
46
+ "required": [
47
+ "acceptanceReference"
48
+ ],
49
+ "type": "object"
50
+ },
51
+ "moves": [
52
+ {
53
+ "bind": {
54
+ "amount": {
55
+ "from": "instance",
56
+ "path": "fields.committedAmount"
57
+ },
58
+ "currency": {
59
+ "from": "instance",
60
+ "path": "fields.currency"
61
+ },
62
+ "destinationAccountId": {
63
+ "from": "instance",
64
+ "path": "fields.servicingPoolAccountId"
65
+ },
66
+ "metadata.instrumentId": {
67
+ "from": "const",
68
+ "value": "lender_commitment"
69
+ },
70
+ "metadata.instrumentInstanceId": {
71
+ "from": "instance",
72
+ "path": "instrumentInstanceId"
73
+ },
74
+ "metadata.phase": {
75
+ "from": "const",
76
+ "value": "accept_commitment"
77
+ },
78
+ "productId": {
79
+ "from": "instance",
80
+ "path": "productId"
81
+ },
82
+ "sourceAccountId": {
83
+ "from": "instance",
84
+ "path": "refs.escrowAccountId"
85
+ }
86
+ },
87
+ "capture": {
88
+ "acceptCommitmentTransferTransferId": "transferId"
89
+ },
90
+ "key": "transfer",
91
+ "operation": "internal_transfer.create"
92
+ }
93
+ ],
94
+ "port": {
95
+ "allowedParties": [
96
+ "beneficiary"
97
+ ]
98
+ },
99
+ "publicAction": "acceptCommitmentLenderCommitment",
100
+ "steps": [],
101
+ "summary": "Confirm through accept_commitment and start the release payout"
102
+ },
103
+ "cancel": {
104
+ "effects": {
105
+ "moves": [
106
+ {
107
+ "signature": "moves.transfer.internal",
108
+ "source": "moves[0]"
109
+ }
110
+ ]
111
+ },
112
+ "moves": [
113
+ {
114
+ "bind": {
115
+ "amount": {
116
+ "from": "instance",
117
+ "path": "fields.committedAmount"
118
+ },
119
+ "currency": {
120
+ "from": "instance",
121
+ "path": "fields.currency"
122
+ },
123
+ "destinationAccountId": {
124
+ "from": "instance",
125
+ "path": "fields.lenderAccountId"
126
+ },
127
+ "metadata.instrumentId": {
128
+ "from": "const",
129
+ "value": "lender_commitment"
130
+ },
131
+ "metadata.instrumentInstanceId": {
132
+ "from": "instance",
133
+ "path": "instrumentInstanceId"
134
+ },
135
+ "metadata.phase": {
136
+ "from": "const",
137
+ "value": "cancel"
138
+ },
139
+ "productId": {
140
+ "from": "instance",
141
+ "path": "productId"
142
+ },
143
+ "sourceAccountId": {
144
+ "from": "instance",
145
+ "path": "refs.escrowAccountId"
146
+ }
147
+ },
148
+ "capture": {
149
+ "cancelTransferTransferId": "transferId"
150
+ },
151
+ "key": "transfer",
152
+ "operation": "internal_transfer.create"
153
+ }
154
+ ],
155
+ "publicAction": "cancelLenderCommitment",
156
+ "steps": [],
157
+ "summary": "Cancel the settlement and start the unwind"
158
+ },
159
+ "create": {
160
+ "moves": [],
161
+ "publicAction": "createLenderCommitment",
162
+ "steps": [
163
+ {
164
+ "bind": {
165
+ "currency": {
166
+ "from": "instance",
167
+ "path": "fields.currency"
168
+ },
169
+ "owner.id": {
170
+ "from": "instance",
171
+ "path": "productId"
172
+ },
173
+ "owner.type": {
174
+ "from": "const",
175
+ "value": "product"
176
+ },
177
+ "productId": {
178
+ "from": "instance",
179
+ "path": "productId"
180
+ },
181
+ "role": {
182
+ "from": "const",
183
+ "value": "product_escrow"
184
+ }
185
+ },
186
+ "capture": {
187
+ "escrowAccountId": "accountId"
188
+ },
189
+ "operation": "account.escrow.provision"
190
+ }
191
+ ],
192
+ "summary": "Create a lender commitment settlement"
193
+ },
194
+ "fund_piece_1": {
195
+ "effects": {
196
+ "moves": [
197
+ {
198
+ "signature": "moves.collection.pay_in",
199
+ "source": "moves[0]"
200
+ }
201
+ ]
202
+ },
203
+ "moves": [
204
+ {
205
+ "bind": {
206
+ "amount": {
207
+ "from": "instance",
208
+ "path": "fields.committedAmount"
209
+ },
210
+ "currency": {
211
+ "from": "instance",
212
+ "path": "fields.currency"
213
+ },
214
+ "destinationAccountId": {
215
+ "from": "instance",
216
+ "path": "refs.escrowAccountId"
217
+ },
218
+ "metadata.instrumentId": {
219
+ "from": "const",
220
+ "value": "lender_commitment"
221
+ },
222
+ "metadata.instrumentInstanceId": {
223
+ "from": "instance",
224
+ "path": "instrumentInstanceId"
225
+ },
226
+ "metadata.phase": {
227
+ "from": "const",
228
+ "value": "fund_piece_1"
229
+ },
230
+ "productId": {
231
+ "from": "instance",
232
+ "path": "productId"
233
+ },
234
+ "sourceAccountId": {
235
+ "from": "instance",
236
+ "path": "fields.lenderAccountId"
237
+ }
238
+ },
239
+ "capture": {
240
+ "fundPiece1TransferTransferId": "transferId"
241
+ },
242
+ "key": "transfer",
243
+ "operation": "internal_transfer.create"
244
+ }
245
+ ],
246
+ "publicAction": "fundPiece1LenderCommitment",
247
+ "steps": [],
248
+ "summary": "Fund piece 1 of the held amount into escrow"
249
+ }
250
+ },
251
+ "description": "Held payment: the lender funds committedAmount into this settlement's own escrow; servicing pool confirms through accept_commitment to release",
252
+ "fields": {
253
+ "committedAmount": {
254
+ "description": "The held amount in SAR minor units, funded and paid out whole",
255
+ "pattern": "^[1-9][0-9]{0,17}$",
256
+ "type": "string"
257
+ },
258
+ "currency": {
259
+ "description": "ISO 4217 currency code",
260
+ "maxLength": 3,
261
+ "minLength": 3,
262
+ "pattern": "^[A-Z]{3}$",
263
+ "type": "string"
264
+ },
265
+ "lenderAccountId": {
266
+ "description": "The lender account",
267
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
268
+ "type": "string",
269
+ "x-hyperscale-reference-filter": {
270
+ "column": "role",
271
+ "values": [
272
+ "customer_balance"
273
+ ]
274
+ }
275
+ },
276
+ "platformAccountId": {
277
+ "description": "The product fee collection account",
278
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
279
+ "type": "string",
280
+ "x-hyperscale-fee-collection-port": true,
281
+ "x-hyperscale-reference-filter": {
282
+ "column": "role",
283
+ "values": [
284
+ "customer_balance",
285
+ "product_revenue"
286
+ ]
287
+ }
288
+ },
289
+ "servicingPoolAccountId": {
290
+ "description": "The servicing pool account",
291
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
292
+ "type": "string",
293
+ "x-hyperscale-reference-filter": {
294
+ "column": "role",
295
+ "values": [
296
+ "customer_balance"
297
+ ]
298
+ }
299
+ }
300
+ },
301
+ "id": "lender_commitment",
302
+ "idPrefix": "lc",
303
+ "lifecycle": {
304
+ "initial": "created",
305
+ "states": [
306
+ "created",
307
+ "funded",
308
+ "released",
309
+ "cancelled",
310
+ "abandoned"
311
+ ],
312
+ "transitions": {
313
+ "abandon": {
314
+ "from": [
315
+ "created"
316
+ ],
317
+ "to": "abandoned"
318
+ },
319
+ "accept_commitment": {
320
+ "from": [
321
+ "funded"
322
+ ],
323
+ "to": "released"
324
+ },
325
+ "cancel": {
326
+ "from": [
327
+ "funded"
328
+ ],
329
+ "to": "cancelled"
330
+ },
331
+ "fund_piece_1": {
332
+ "from": [
333
+ "created"
334
+ ],
335
+ "to": "funded"
336
+ }
337
+ }
338
+ },
339
+ "parties": {
340
+ "beneficiary": "servicingPoolAccountId",
341
+ "payer": "lenderAccountId"
342
+ },
343
+ "required": [
344
+ "lenderAccountId",
345
+ "servicingPoolAccountId",
346
+ "platformAccountId",
347
+ "committedAmount",
348
+ "currency"
349
+ ],
350
+ "summary": "Escrow-held payment from lender to servicing pool",
351
+ "title": "Lender commitment"
352
+ },
353
+ {
354
+ "actionOrder": [
355
+ "create",
356
+ "approve",
357
+ "advance",
358
+ "collect_installment_1",
359
+ "mark_installment_1_delinquent",
360
+ "collect_installment_2",
361
+ "mark_installment_2_delinquent",
362
+ "complete",
363
+ "write_off"
364
+ ],
365
+ "actions": {
366
+ "advance": {
367
+ "effects": {
368
+ "moves": [
369
+ {
370
+ "signature": "moves.transfer.internal",
371
+ "source": "moves[0]"
372
+ }
373
+ ]
374
+ },
375
+ "moves": [
376
+ {
377
+ "bind": {
378
+ "amount": {
379
+ "from": "instance",
380
+ "path": "fields.principalAmount"
381
+ },
382
+ "currency": {
383
+ "from": "instance",
384
+ "path": "fields.currency"
385
+ },
386
+ "destinationAccountId": {
387
+ "from": "instance",
388
+ "path": "fields.borrowerProceedsAccountId"
389
+ },
390
+ "metadata.instrumentId": {
391
+ "from": "const",
392
+ "value": "borrower_loan"
393
+ },
394
+ "metadata.instrumentInstanceId": {
395
+ "from": "instance",
396
+ "path": "instrumentInstanceId"
397
+ },
398
+ "metadata.phase": {
399
+ "from": "const",
400
+ "value": "advance"
401
+ },
402
+ "productId": {
403
+ "from": "instance",
404
+ "path": "productId"
405
+ },
406
+ "sourceAccountId": {
407
+ "from": "instance",
408
+ "path": "fields.servicingPoolAccountId"
409
+ }
410
+ },
411
+ "capture": {
412
+ "advanceAdvanceTransferId": "transferId"
413
+ },
414
+ "key": "advance",
415
+ "operation": "internal_transfer.create"
416
+ }
417
+ ],
418
+ "publicAction": "advanceBorrowerLoan",
419
+ "steps": [],
420
+ "summary": "Advance the principal to the borrower proceeds; the internal ledger receipt is the confirmation"
421
+ },
422
+ "approve": {
423
+ "moves": [],
424
+ "publicAction": "approveBorrowerLoan",
425
+ "steps": [],
426
+ "summary": "Approve the immutable principal partition and stored anchors"
427
+ },
428
+ "collect_installment_1": {
429
+ "due": {
430
+ "field": "firstRepaymentDueAt"
431
+ },
432
+ "effects": {
433
+ "reads": [
434
+ {
435
+ "signature": "reads.requires_aggregate",
436
+ "source": "requiresAggregate"
437
+ }
438
+ ],
439
+ "schedules": [
440
+ {
441
+ "signature": "schedules.due",
442
+ "source": "due"
443
+ }
444
+ ]
445
+ },
446
+ "moves": [],
447
+ "requiresAggregate": [
448
+ {
449
+ "check": {
450
+ "amountField": "amount",
451
+ "kind": "sum_exactly",
452
+ "targetField": "installment1Amount"
453
+ },
454
+ "instrumentId": "borrower_loan_installment_1_payment",
455
+ "over": "children",
456
+ "refField": "borrowerLoanId",
457
+ "statuses": [
458
+ "paid"
459
+ ]
460
+ }
461
+ ],
462
+ "steps": [],
463
+ "summary": "Close delinquent installment 1 after linked payments reach its stored amount"
464
+ },
465
+ "collect_installment_2": {
466
+ "due": {
467
+ "field": "firstRepaymentDueAt",
468
+ "offset": "P30D"
469
+ },
470
+ "effects": {
471
+ "reads": [
472
+ {
473
+ "signature": "reads.requires_aggregate",
474
+ "source": "requiresAggregate"
475
+ }
476
+ ],
477
+ "schedules": [
478
+ {
479
+ "signature": "schedules.due",
480
+ "source": "due"
481
+ }
482
+ ]
483
+ },
484
+ "moves": [],
485
+ "requiresAggregate": [
486
+ {
487
+ "check": {
488
+ "amountField": "amount",
489
+ "kind": "sum_exactly",
490
+ "targetField": "installment2Amount"
491
+ },
492
+ "instrumentId": "borrower_loan_installment_2_payment",
493
+ "over": "children",
494
+ "refField": "borrowerLoanId",
495
+ "statuses": [
496
+ "paid"
497
+ ]
498
+ }
499
+ ],
500
+ "steps": [],
501
+ "summary": "Close delinquent installment 2 after linked payments reach its stored amount"
502
+ },
503
+ "complete": {
504
+ "due": {
505
+ "field": "firstRepaymentDueAt",
506
+ "offset": "P30D"
507
+ },
508
+ "effects": {
509
+ "reads": [
510
+ {
511
+ "signature": "reads.requires_aggregate",
512
+ "source": "requiresAggregate"
513
+ }
514
+ ],
515
+ "schedules": [
516
+ {
517
+ "signature": "schedules.due",
518
+ "source": "due"
519
+ }
520
+ ]
521
+ },
522
+ "moves": [],
523
+ "requiresAggregate": [
524
+ {
525
+ "check": {
526
+ "amountField": "amount",
527
+ "kind": "sum_exactly",
528
+ "targetField": "installment1Amount"
529
+ },
530
+ "instrumentId": "borrower_loan_installment_1_payment",
531
+ "over": "children",
532
+ "refField": "borrowerLoanId",
533
+ "statuses": [
534
+ "paid"
535
+ ]
536
+ },
537
+ {
538
+ "check": {
539
+ "amountField": "amount",
540
+ "kind": "sum_exactly",
541
+ "targetField": "installment2Amount"
542
+ },
543
+ "instrumentId": "borrower_loan_installment_2_payment",
544
+ "over": "children",
545
+ "refField": "borrowerLoanId",
546
+ "statuses": [
547
+ "paid"
548
+ ]
549
+ }
550
+ ],
551
+ "steps": [],
552
+ "summary": "Close the obligation only after every stored anchor is paid exactly"
553
+ },
554
+ "create": {
555
+ "effects": {
556
+ "moves": [
557
+ {
558
+ "signature": "moves.transfer.internal",
559
+ "source": "moves[0]"
560
+ }
561
+ ]
562
+ },
563
+ "moves": [
564
+ {
565
+ "bind": {
566
+ "amount": {
567
+ "from": "instance",
568
+ "path": "fields.platformFee"
569
+ },
570
+ "currency": {
571
+ "from": "instance",
572
+ "path": "fields.currency"
573
+ },
574
+ "destinationAccountId": {
575
+ "from": "instance",
576
+ "path": "fields.platformAccountId"
577
+ },
578
+ "metadata.instrumentId": {
579
+ "from": "const",
580
+ "value": "borrower_loan"
581
+ },
582
+ "metadata.instrumentInstanceId": {
583
+ "from": "instance",
584
+ "path": "instrumentInstanceId"
585
+ },
586
+ "metadata.phase": {
587
+ "from": "const",
588
+ "value": "create"
589
+ },
590
+ "productId": {
591
+ "from": "instance",
592
+ "path": "productId"
593
+ },
594
+ "sourceAccountId": {
595
+ "from": "instance",
596
+ "path": "fields.borrowerAccountId"
597
+ }
598
+ },
599
+ "capture": {
600
+ "createDerivedAmountTransferId": "transferId"
601
+ },
602
+ "key": "derived_amount",
603
+ "operation": "internal_transfer.create"
604
+ }
605
+ ],
606
+ "publicAction": "createBorrowerLoan",
607
+ "steps": [],
608
+ "summary": "Create a borrower loan obligation"
609
+ },
610
+ "mark_installment_1_delinquent": {
611
+ "due": {
612
+ "field": "firstRepaymentDueAt"
613
+ },
614
+ "effects": {
615
+ "reads": [
616
+ {
617
+ "signature": "reads.requires_aggregate",
618
+ "source": "requiresAggregate"
619
+ }
620
+ ],
621
+ "schedules": [
622
+ {
623
+ "signature": "schedules.due",
624
+ "source": "due"
625
+ }
626
+ ]
627
+ },
628
+ "moves": [],
629
+ "requiresAggregate": [
630
+ {
631
+ "check": {
632
+ "amountField": "amount",
633
+ "kind": "sum_below",
634
+ "targetField": "installment1Amount"
635
+ },
636
+ "instrumentId": "borrower_loan_installment_1_payment",
637
+ "over": "children",
638
+ "refField": "borrowerLoanId",
639
+ "statuses": [
640
+ "paid"
641
+ ]
642
+ }
643
+ ],
644
+ "setsAt": {
645
+ "field": "installment1DelinquentAfter",
646
+ "marker": true,
647
+ "offset": "PT1S"
648
+ },
649
+ "steps": [],
650
+ "summary": "Mark installment 1 delinquent only when its due anchor is unmet"
651
+ },
652
+ "mark_installment_2_delinquent": {
653
+ "due": {
654
+ "field": "firstRepaymentDueAt",
655
+ "offset": "P30D"
656
+ },
657
+ "effects": {
658
+ "reads": [
659
+ {
660
+ "signature": "reads.requires_aggregate",
661
+ "source": "requiresAggregate"
662
+ }
663
+ ],
664
+ "schedules": [
665
+ {
666
+ "signature": "schedules.due",
667
+ "source": "due"
668
+ }
669
+ ]
670
+ },
671
+ "moves": [],
672
+ "requiresAggregate": [
673
+ {
674
+ "check": {
675
+ "amountField": "amount",
676
+ "kind": "sum_below",
677
+ "targetField": "installment2Amount"
678
+ },
679
+ "instrumentId": "borrower_loan_installment_2_payment",
680
+ "over": "children",
681
+ "refField": "borrowerLoanId",
682
+ "statuses": [
683
+ "paid"
684
+ ]
685
+ }
686
+ ],
687
+ "setsAt": {
688
+ "field": "installment2DelinquentAfter",
689
+ "marker": true,
690
+ "offset": "PT1S"
691
+ },
692
+ "steps": [],
693
+ "summary": "Mark installment 2 delinquent only when its due anchor is unmet"
694
+ },
695
+ "write_off": {
696
+ "moves": [],
697
+ "publicAction": "writeOffBorrowerLoan",
698
+ "steps": [],
699
+ "summary": "Write off the remaining exposure without moving money"
700
+ }
701
+ },
702
+ "aggregateInvariants": [
703
+ {
704
+ "childField": "amount",
705
+ "childInstrumentId": "borrower_loan_installment_1_payment",
706
+ "childRefField": "borrowerLoanId",
707
+ "childStatuses": [
708
+ "paid"
709
+ ],
710
+ "parentField": "installment1Amount"
711
+ },
712
+ {
713
+ "childField": "amount",
714
+ "childInstrumentId": "borrower_loan_installment_2_payment",
715
+ "childRefField": "borrowerLoanId",
716
+ "childStatuses": [
717
+ "paid"
718
+ ],
719
+ "parentField": "installment2Amount"
720
+ }
721
+ ],
722
+ "derivedAmounts": [
723
+ {
724
+ "field": "platformFee",
725
+ "rounding": "floor",
726
+ "rule": {
727
+ "bps": 200,
728
+ "kind": "percentage_of"
729
+ },
730
+ "sourceField": "principalAmount"
731
+ }
732
+ ],
733
+ "description": "Installment obligation for borrower; borrower pays servicing pool against exact stored anchors",
734
+ "fields": {
735
+ "borrowerAccountId": {
736
+ "description": "The borrower account",
737
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
738
+ "type": "string",
739
+ "x-hyperscale-reference-filter": {
740
+ "column": "role",
741
+ "values": [
742
+ "customer_balance"
743
+ ]
744
+ }
745
+ },
746
+ "borrowerProceedsAccountId": {
747
+ "description": "The borrower proceeds account",
748
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
749
+ "type": "string",
750
+ "x-hyperscale-reference-filter": {
751
+ "column": "role",
752
+ "values": [
753
+ "customer_balance"
754
+ ]
755
+ }
756
+ },
757
+ "currency": {
758
+ "description": "ISO 4217 currency code",
759
+ "maxLength": 3,
760
+ "minLength": 3,
761
+ "pattern": "^[A-Z]{3}$",
762
+ "type": "string"
763
+ },
764
+ "firstRepaymentDueAt": {
765
+ "description": "Due date of the first installment; later anchors use fixed offsets of P30D",
766
+ "format": "hyperscale-date-time",
767
+ "type": "string"
768
+ },
769
+ "installment1Amount": {
770
+ "description": "Stored amount for installment 1 of 2 (carries the integer-division remainder)",
771
+ "pattern": "^[1-9][0-9]{0,17}$",
772
+ "type": "string"
773
+ },
774
+ "installment1DelinquentAfter": {
775
+ "description": "Machine-set marker proving installment 1 reached its stored due date while unpaid",
776
+ "format": "hyperscale-date-time",
777
+ "type": "string"
778
+ },
779
+ "installment2Amount": {
780
+ "description": "Stored amount for installment 2 of 2",
781
+ "pattern": "^[1-9][0-9]{0,17}$",
782
+ "type": "string"
783
+ },
784
+ "installment2DelinquentAfter": {
785
+ "description": "Machine-set marker proving installment 2 reached its stored due date while unpaid",
786
+ "format": "hyperscale-date-time",
787
+ "type": "string"
788
+ },
789
+ "platformAccountId": {
790
+ "description": "The product fee collection account",
791
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
792
+ "type": "string",
793
+ "x-hyperscale-fee-collection-port": true,
794
+ "x-hyperscale-reference-filter": {
795
+ "column": "role",
796
+ "values": [
797
+ "customer_balance",
798
+ "product_revenue"
799
+ ]
800
+ }
801
+ },
802
+ "platformFee": {
803
+ "description": "Machine-computed 2% of principalAmount; callers never supply it",
804
+ "pattern": "^[1-9][0-9]{0,17}$",
805
+ "type": "string"
806
+ },
807
+ "principalAmount": {
808
+ "description": "The principal in SAR minor units; the stored installment anchors partition it exactly",
809
+ "pattern": "^[1-9][0-9]{0,17}$",
810
+ "type": "string"
811
+ },
812
+ "servicingPoolAccountId": {
813
+ "description": "The servicing pool account",
814
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
815
+ "type": "string",
816
+ "x-hyperscale-reference-filter": {
817
+ "column": "role",
818
+ "values": [
819
+ "customer_balance"
820
+ ]
821
+ }
822
+ }
823
+ },
824
+ "id": "borrower_loan",
825
+ "idPrefix": "bl",
826
+ "lifecycle": {
827
+ "initial": "draft",
828
+ "states": [
829
+ "draft",
830
+ "approved",
831
+ "active",
832
+ "installment_1_delinquent",
833
+ "installment_2_delinquent",
834
+ "written_off",
835
+ "repaid"
836
+ ],
837
+ "transitions": {
838
+ "advance": {
839
+ "from": [
840
+ "approved"
841
+ ],
842
+ "to": "active"
843
+ },
844
+ "approve": {
845
+ "from": [
846
+ "draft"
847
+ ],
848
+ "to": "approved"
849
+ },
850
+ "collect_installment_1": {
851
+ "from": [
852
+ "installment_1_delinquent",
853
+ "installment_2_delinquent"
854
+ ],
855
+ "to": "active"
856
+ },
857
+ "collect_installment_2": {
858
+ "from": [
859
+ "installment_1_delinquent",
860
+ "installment_2_delinquent"
861
+ ],
862
+ "to": "active"
863
+ },
864
+ "complete": {
865
+ "from": [
866
+ "active",
867
+ "installment_1_delinquent",
868
+ "installment_2_delinquent"
869
+ ],
870
+ "to": "repaid"
871
+ },
872
+ "mark_installment_1_delinquent": {
873
+ "from": [
874
+ "active",
875
+ "installment_2_delinquent"
876
+ ],
877
+ "to": "installment_1_delinquent"
878
+ },
879
+ "mark_installment_2_delinquent": {
880
+ "from": [
881
+ "active",
882
+ "installment_1_delinquent"
883
+ ],
884
+ "to": "installment_2_delinquent"
885
+ },
886
+ "write_off": {
887
+ "from": [
888
+ "draft",
889
+ "approved",
890
+ "active",
891
+ "installment_1_delinquent",
892
+ "installment_2_delinquent"
893
+ ],
894
+ "to": "written_off"
895
+ }
896
+ }
897
+ },
898
+ "parties": {
899
+ "beneficiary": "borrowerProceedsAccountId"
900
+ },
901
+ "partitions": [
902
+ {
903
+ "pieceFields": [
904
+ "installment1Amount",
905
+ "installment2Amount"
906
+ ],
907
+ "totalField": "principalAmount"
908
+ }
909
+ ],
910
+ "required": [
911
+ "borrowerProceedsAccountId",
912
+ "servicingPoolAccountId",
913
+ "borrowerAccountId",
914
+ "platformAccountId",
915
+ "currency",
916
+ "principalAmount",
917
+ "firstRepaymentDueAt",
918
+ "installment1Amount",
919
+ "installment2Amount"
920
+ ],
921
+ "summary": "2-anchor obligation for borrower",
922
+ "title": "Borrower loan"
923
+ },
924
+ {
925
+ "actionOrder": [
926
+ "create",
927
+ "repay",
928
+ "refund"
929
+ ],
930
+ "actions": {
931
+ "create": {
932
+ "effects": {
933
+ "reads": [
934
+ {
935
+ "signature": "reads.requires_refs",
936
+ "source": "requiresRefs"
937
+ }
938
+ ]
939
+ },
940
+ "moves": [],
941
+ "publicAction": "createBorrowerLoanInstallment1Payment",
942
+ "requiresRefs": [
943
+ {
944
+ "bind": {
945
+ "borrowerAccountId": "fields.borrowerAccountId",
946
+ "currency": "fields.currency",
947
+ "servicingPoolAccountId": "fields.servicingPoolAccountId"
948
+ },
949
+ "field": "borrowerLoanId",
950
+ "statuses": [
951
+ "active",
952
+ "installment_1_delinquent",
953
+ "installment_2_delinquent"
954
+ ]
955
+ }
956
+ ],
957
+ "steps": [],
958
+ "summary": "Create a payment record for installment 1"
959
+ },
960
+ "refund": {
961
+ "effects": {
962
+ "moves": [
963
+ {
964
+ "signature": "moves.transfer.internal",
965
+ "source": "moves[0]"
966
+ }
967
+ ],
968
+ "reads": [
969
+ {
970
+ "signature": "reads.requires_refs",
971
+ "source": "requiresRefs"
972
+ }
973
+ ]
974
+ },
975
+ "moves": [
976
+ {
977
+ "bind": {
978
+ "amount": {
979
+ "from": "instance",
980
+ "path": "fields.amount"
981
+ },
982
+ "currency": {
983
+ "from": "instance",
984
+ "path": "fields.currency"
985
+ },
986
+ "destinationAccountId": {
987
+ "from": "instance",
988
+ "path": "fields.borrowerAccountId"
989
+ },
990
+ "metadata.instrumentId": {
991
+ "from": "const",
992
+ "value": "borrower_loan_installment_1_payment"
993
+ },
994
+ "metadata.instrumentInstanceId": {
995
+ "from": "instance",
996
+ "path": "instrumentInstanceId"
997
+ },
998
+ "metadata.phase": {
999
+ "from": "const",
1000
+ "value": "refund"
1001
+ },
1002
+ "productId": {
1003
+ "from": "instance",
1004
+ "path": "productId"
1005
+ },
1006
+ "sourceAccountId": {
1007
+ "from": "instance",
1008
+ "path": "fields.servicingPoolAccountId"
1009
+ }
1010
+ },
1011
+ "capture": {
1012
+ "refundRefundTransferId": "transferId"
1013
+ },
1014
+ "key": "refund",
1015
+ "operation": "internal_transfer.create"
1016
+ }
1017
+ ],
1018
+ "publicAction": "refundBorrowerLoanInstallment1Payment",
1019
+ "requiresRefs": [
1020
+ {
1021
+ "field": "borrowerLoanId",
1022
+ "match": {
1023
+ "fields.borrowerAccountId": "fields.borrowerAccountId",
1024
+ "fields.currency": "fields.currency",
1025
+ "fields.servicingPoolAccountId": "fields.servicingPoolAccountId"
1026
+ },
1027
+ "statuses": [
1028
+ "active",
1029
+ "installment_1_delinquent",
1030
+ "installment_2_delinquent"
1031
+ ]
1032
+ }
1033
+ ],
1034
+ "steps": [],
1035
+ "summary": "Refund this one stored installment 1 payment whole"
1036
+ },
1037
+ "repay": {
1038
+ "effects": {
1039
+ "moves": [
1040
+ {
1041
+ "signature": "moves.transfer.internal",
1042
+ "source": "moves[0]"
1043
+ }
1044
+ ],
1045
+ "reads": [
1046
+ {
1047
+ "signature": "reads.requires_refs",
1048
+ "source": "requiresRefs"
1049
+ }
1050
+ ]
1051
+ },
1052
+ "moves": [
1053
+ {
1054
+ "bind": {
1055
+ "amount": {
1056
+ "from": "instance",
1057
+ "path": "fields.amount"
1058
+ },
1059
+ "currency": {
1060
+ "from": "instance",
1061
+ "path": "fields.currency"
1062
+ },
1063
+ "destinationAccountId": {
1064
+ "from": "instance",
1065
+ "path": "fields.servicingPoolAccountId"
1066
+ },
1067
+ "metadata.instrumentId": {
1068
+ "from": "const",
1069
+ "value": "borrower_loan_installment_1_payment"
1070
+ },
1071
+ "metadata.instrumentInstanceId": {
1072
+ "from": "instance",
1073
+ "path": "instrumentInstanceId"
1074
+ },
1075
+ "metadata.phase": {
1076
+ "from": "const",
1077
+ "value": "repay"
1078
+ },
1079
+ "productId": {
1080
+ "from": "instance",
1081
+ "path": "productId"
1082
+ },
1083
+ "sourceAccountId": {
1084
+ "from": "instance",
1085
+ "path": "fields.borrowerAccountId"
1086
+ }
1087
+ },
1088
+ "capture": {
1089
+ "repayRepaymentTransferId": "transferId"
1090
+ },
1091
+ "key": "repayment",
1092
+ "operation": "internal_transfer.create"
1093
+ }
1094
+ ],
1095
+ "publicAction": "repayBorrowerLoanInstallment1Payment",
1096
+ "requiresExposure": [
1097
+ {
1098
+ "amountField": "amount",
1099
+ "anchorField": "borrowerLoanId",
1100
+ "capField": "installment1Amount",
1101
+ "capOnAnchor": true,
1102
+ "childInstrumentId": 7,
1103
+ "statuses": [
1104
+ "paid"
1105
+ ]
1106
+ }
1107
+ ],
1108
+ "requiresRefs": [
1109
+ {
1110
+ "field": "borrowerLoanId",
1111
+ "match": {
1112
+ "fields.borrowerAccountId": "fields.borrowerAccountId",
1113
+ "fields.currency": "fields.currency",
1114
+ "fields.servicingPoolAccountId": "fields.servicingPoolAccountId"
1115
+ },
1116
+ "statuses": [
1117
+ "active",
1118
+ "installment_1_delinquent",
1119
+ "installment_2_delinquent"
1120
+ ]
1121
+ }
1122
+ ],
1123
+ "steps": [],
1124
+ "summary": "Pay a partial or full amount against installment 1"
1125
+ }
1126
+ },
1127
+ "description": "One partial or full payment bound to installment 1 of borrower_loan; the operation name fixes the anchor and borrowerLoanId fixes the obligation",
1128
+ "fields": {
1129
+ "amount": {
1130
+ "description": "Positive payment amount capped with its paid siblings at installment1Amount",
1131
+ "pattern": "^[1-9][0-9]{0,17}$",
1132
+ "type": "string"
1133
+ },
1134
+ "borrowerAccountId": {
1135
+ "description": "The borrower account",
1136
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1137
+ "type": "string",
1138
+ "x-hyperscale-reference-filter": {
1139
+ "column": "role",
1140
+ "values": [
1141
+ "customer_balance"
1142
+ ]
1143
+ }
1144
+ },
1145
+ "borrowerLoanId": {
1146
+ "description": "The exact borrower loan this payment belongs to",
1147
+ "pattern": "^bl_(sandbox|live)_[a-z0-9]{8,64}$",
1148
+ "type": "string"
1149
+ },
1150
+ "currency": {
1151
+ "description": "ISO 4217 currency code",
1152
+ "maxLength": 3,
1153
+ "minLength": 3,
1154
+ "pattern": "^[A-Z]{3}$",
1155
+ "type": "string"
1156
+ },
1157
+ "servicingPoolAccountId": {
1158
+ "description": "The servicing pool account",
1159
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1160
+ "type": "string",
1161
+ "x-hyperscale-reference-filter": {
1162
+ "column": "role",
1163
+ "values": [
1164
+ "customer_balance"
1165
+ ]
1166
+ }
1167
+ }
1168
+ },
1169
+ "id": "borrower_loan_installment_1_payment",
1170
+ "idPrefix": "zzaa",
1171
+ "lifecycle": {
1172
+ "initial": "created",
1173
+ "states": [
1174
+ "created",
1175
+ "paid",
1176
+ "refunded"
1177
+ ],
1178
+ "transitions": {
1179
+ "refund": {
1180
+ "from": [
1181
+ "paid"
1182
+ ],
1183
+ "to": "refunded"
1184
+ },
1185
+ "repay": {
1186
+ "from": [
1187
+ "created"
1188
+ ],
1189
+ "to": "paid"
1190
+ }
1191
+ }
1192
+ },
1193
+ "parties": {
1194
+ "beneficiary": "servicingPoolAccountId",
1195
+ "payer": "borrowerAccountId"
1196
+ },
1197
+ "required": [
1198
+ "servicingPoolAccountId",
1199
+ "borrowerAccountId",
1200
+ "amount",
1201
+ "currency",
1202
+ "borrowerLoanId"
1203
+ ],
1204
+ "summary": "Anchor-bound payment for installment 1",
1205
+ "title": "Borrower loan Installment 1 Payment"
1206
+ },
1207
+ {
1208
+ "actionOrder": [
1209
+ "create",
1210
+ "repay",
1211
+ "refund"
1212
+ ],
1213
+ "actions": {
1214
+ "create": {
1215
+ "effects": {
1216
+ "reads": [
1217
+ {
1218
+ "signature": "reads.requires_refs",
1219
+ "source": "requiresRefs"
1220
+ }
1221
+ ]
1222
+ },
1223
+ "moves": [],
1224
+ "publicAction": "createBorrowerLoanInstallment2Payment",
1225
+ "requiresRefs": [
1226
+ {
1227
+ "bind": {
1228
+ "borrowerAccountId": "fields.borrowerAccountId",
1229
+ "currency": "fields.currency",
1230
+ "servicingPoolAccountId": "fields.servicingPoolAccountId"
1231
+ },
1232
+ "field": "borrowerLoanId",
1233
+ "statuses": [
1234
+ "active",
1235
+ "installment_1_delinquent",
1236
+ "installment_2_delinquent"
1237
+ ]
1238
+ }
1239
+ ],
1240
+ "steps": [],
1241
+ "summary": "Create a payment record for installment 2"
1242
+ },
1243
+ "refund": {
1244
+ "effects": {
1245
+ "moves": [
1246
+ {
1247
+ "signature": "moves.transfer.internal",
1248
+ "source": "moves[0]"
1249
+ }
1250
+ ],
1251
+ "reads": [
1252
+ {
1253
+ "signature": "reads.requires_refs",
1254
+ "source": "requiresRefs"
1255
+ }
1256
+ ]
1257
+ },
1258
+ "moves": [
1259
+ {
1260
+ "bind": {
1261
+ "amount": {
1262
+ "from": "instance",
1263
+ "path": "fields.amount"
1264
+ },
1265
+ "currency": {
1266
+ "from": "instance",
1267
+ "path": "fields.currency"
1268
+ },
1269
+ "destinationAccountId": {
1270
+ "from": "instance",
1271
+ "path": "fields.borrowerAccountId"
1272
+ },
1273
+ "metadata.instrumentId": {
1274
+ "from": "const",
1275
+ "value": "borrower_loan_installment_2_payment"
1276
+ },
1277
+ "metadata.instrumentInstanceId": {
1278
+ "from": "instance",
1279
+ "path": "instrumentInstanceId"
1280
+ },
1281
+ "metadata.phase": {
1282
+ "from": "const",
1283
+ "value": "refund"
1284
+ },
1285
+ "productId": {
1286
+ "from": "instance",
1287
+ "path": "productId"
1288
+ },
1289
+ "sourceAccountId": {
1290
+ "from": "instance",
1291
+ "path": "fields.servicingPoolAccountId"
1292
+ }
1293
+ },
1294
+ "capture": {
1295
+ "refundRefundTransferId": "transferId"
1296
+ },
1297
+ "key": "refund",
1298
+ "operation": "internal_transfer.create"
1299
+ }
1300
+ ],
1301
+ "publicAction": "refundBorrowerLoanInstallment2Payment",
1302
+ "requiresRefs": [
1303
+ {
1304
+ "field": "borrowerLoanId",
1305
+ "match": {
1306
+ "fields.borrowerAccountId": "fields.borrowerAccountId",
1307
+ "fields.currency": "fields.currency",
1308
+ "fields.servicingPoolAccountId": "fields.servicingPoolAccountId"
1309
+ },
1310
+ "statuses": [
1311
+ "active",
1312
+ "installment_1_delinquent",
1313
+ "installment_2_delinquent"
1314
+ ]
1315
+ }
1316
+ ],
1317
+ "steps": [],
1318
+ "summary": "Refund this one stored installment 2 payment whole"
1319
+ },
1320
+ "repay": {
1321
+ "effects": {
1322
+ "moves": [
1323
+ {
1324
+ "signature": "moves.transfer.internal",
1325
+ "source": "moves[0]"
1326
+ }
1327
+ ],
1328
+ "reads": [
1329
+ {
1330
+ "signature": "reads.requires_refs",
1331
+ "source": "requiresRefs"
1332
+ }
1333
+ ]
1334
+ },
1335
+ "moves": [
1336
+ {
1337
+ "bind": {
1338
+ "amount": {
1339
+ "from": "instance",
1340
+ "path": "fields.amount"
1341
+ },
1342
+ "currency": {
1343
+ "from": "instance",
1344
+ "path": "fields.currency"
1345
+ },
1346
+ "destinationAccountId": {
1347
+ "from": "instance",
1348
+ "path": "fields.servicingPoolAccountId"
1349
+ },
1350
+ "metadata.instrumentId": {
1351
+ "from": "const",
1352
+ "value": "borrower_loan_installment_2_payment"
1353
+ },
1354
+ "metadata.instrumentInstanceId": {
1355
+ "from": "instance",
1356
+ "path": "instrumentInstanceId"
1357
+ },
1358
+ "metadata.phase": {
1359
+ "from": "const",
1360
+ "value": "repay"
1361
+ },
1362
+ "productId": {
1363
+ "from": "instance",
1364
+ "path": "productId"
1365
+ },
1366
+ "sourceAccountId": {
1367
+ "from": "instance",
1368
+ "path": "fields.borrowerAccountId"
1369
+ }
1370
+ },
1371
+ "capture": {
1372
+ "repayRepaymentTransferId": "transferId"
1373
+ },
1374
+ "key": "repayment",
1375
+ "operation": "internal_transfer.create"
1376
+ }
1377
+ ],
1378
+ "publicAction": "repayBorrowerLoanInstallment2Payment",
1379
+ "requiresExposure": [
1380
+ {
1381
+ "amountField": "amount",
1382
+ "anchorField": "borrowerLoanId",
1383
+ "capField": "installment2Amount",
1384
+ "capOnAnchor": true,
1385
+ "childInstrumentId": "borrower_loan_installment_2_payment",
1386
+ "statuses": [
1387
+ "paid"
1388
+ ]
1389
+ }
1390
+ ],
1391
+ "requiresRefs": [
1392
+ {
1393
+ "field": "borrowerLoanId",
1394
+ "match": {
1395
+ "fields.borrowerAccountId": "fields.borrowerAccountId",
1396
+ "fields.currency": "fields.currency",
1397
+ "fields.servicingPoolAccountId": "fields.servicingPoolAccountId"
1398
+ },
1399
+ "statuses": [
1400
+ "active",
1401
+ "installment_1_delinquent",
1402
+ "installment_2_delinquent"
1403
+ ]
1404
+ }
1405
+ ],
1406
+ "steps": [],
1407
+ "summary": "Pay a partial or full amount against installment 2"
1408
+ }
1409
+ },
1410
+ "description": "One partial or full payment bound to installment 2 of borrower_loan; the operation name fixes the anchor and borrowerLoanId fixes the obligation",
1411
+ "fields": {
1412
+ "amount": {
1413
+ "description": "Positive payment amount capped with its paid siblings at installment2Amount",
1414
+ "pattern": "^[1-9][0-9]{0,17}$",
1415
+ "type": "string"
1416
+ },
1417
+ "borrowerAccountId": {
1418
+ "description": "The borrower account",
1419
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1420
+ "type": "string",
1421
+ "x-hyperscale-reference-filter": {
1422
+ "column": "role",
1423
+ "values": [
1424
+ "customer_balance"
1425
+ ]
1426
+ }
1427
+ },
1428
+ "borrowerLoanId": {
1429
+ "description": "The exact borrower loan this payment belongs to",
1430
+ "pattern": "^bl_(sandbox|live)_[a-z0-9]{8,64}$",
1431
+ "type": "string"
1432
+ },
1433
+ "currency": {
1434
+ "description": "ISO 4217 currency code",
1435
+ "maxLength": 3,
1436
+ "minLength": 3,
1437
+ "pattern": "^[A-Z]{3}$",
1438
+ "type": "string"
1439
+ },
1440
+ "servicingPoolAccountId": {
1441
+ "description": "The servicing pool account",
1442
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1443
+ "type": "string",
1444
+ "x-hyperscale-reference-filter": {
1445
+ "column": "role",
1446
+ "values": [
1447
+ "customer_balance"
1448
+ ]
1449
+ }
1450
+ }
1451
+ },
1452
+ "id": "borrower_loan_installment_2_payment",
1453
+ "idPrefix": "zzab",
1454
+ "lifecycle": {
1455
+ "initial": "created",
1456
+ "states": [
1457
+ "created",
1458
+ "paid",
1459
+ "refunded"
1460
+ ],
1461
+ "transitions": {
1462
+ "refund": {
1463
+ "from": [
1464
+ "paid"
1465
+ ],
1466
+ "to": "refunded"
1467
+ },
1468
+ "repay": {
1469
+ "from": [
1470
+ "created"
1471
+ ],
1472
+ "to": "paid"
1473
+ }
1474
+ }
1475
+ },
1476
+ "parties": {
1477
+ "beneficiary": "servicingPoolAccountId",
1478
+ "payer": "borrowerAccountId"
1479
+ },
1480
+ "required": [
1481
+ "servicingPoolAccountId",
1482
+ "borrowerAccountId",
1483
+ "amount",
1484
+ "currency",
1485
+ "borrowerLoanId"
1486
+ ],
1487
+ "summary": "Anchor-bound payment for installment 2",
1488
+ "title": "Borrower loan Installment 2 Payment"
1489
+ },
1490
+ {
1491
+ "actionOrder": [
1492
+ "create",
1493
+ "snapshot_lender_claims"
1494
+ ],
1495
+ "actions": {
1496
+ "create": {
1497
+ "moves": [],
1498
+ "publicAction": "createRepaymentDistribution",
1499
+ "steps": [],
1500
+ "summary": "Open entitlement recording"
1501
+ },
1502
+ "snapshot_lender_claims": {
1503
+ "captureInput": {
1504
+ "snapshotEvidenceReference": "evidenceReference"
1505
+ },
1506
+ "effects": {
1507
+ "decides": [
1508
+ {
1509
+ "signature": "decides.tenant_port",
1510
+ "source": "port"
1511
+ }
1512
+ ]
1513
+ },
1514
+ "input": {
1515
+ "additionalProperties": false,
1516
+ "properties": {
1517
+ "evidenceReference": {
1518
+ "description": "Required decision reference retained in the operation receipt",
1519
+ "maxLength": 180,
1520
+ "minLength": 1,
1521
+ "type": "string"
1522
+ }
1523
+ },
1524
+ "required": [
1525
+ "evidenceReference"
1526
+ ],
1527
+ "type": "object"
1528
+ },
1529
+ "moves": [],
1530
+ "port": {
1531
+ "allowedParties": [
1532
+ "payer"
1533
+ ]
1534
+ },
1535
+ "publicAction": "snapshotLenderClaimsRepaymentDistribution",
1536
+ "steps": [],
1537
+ "summary": "Freeze the entitlement set from stored evidence"
1538
+ }
1539
+ },
1540
+ "aggregateInvariants": [
1541
+ {
1542
+ "childInstrumentId": "repayment_distribution_entitlement",
1543
+ "childRefField": "repaymentDistributionId",
1544
+ "childStatuses": [
1545
+ "recorded",
1546
+ "paid"
1547
+ ],
1548
+ "count": true,
1549
+ "parentField": "maxRecipients"
1550
+ }
1551
+ ],
1552
+ "description": "Evidence-frozen weighted distribution",
1553
+ "fields": {
1554
+ "currency": {
1555
+ "description": "ISO 4217 currency code",
1556
+ "maxLength": 3,
1557
+ "minLength": 3,
1558
+ "pattern": "^[A-Z]{3}$",
1559
+ "type": "string"
1560
+ },
1561
+ "distributableAmount": {
1562
+ "description": "Distribution pool in SAR minor units",
1563
+ "pattern": "^[1-9][0-9]{0,17}$",
1564
+ "type": "string"
1565
+ },
1566
+ "maxRecipients": {
1567
+ "const": 250,
1568
+ "description": "Exactly 250 frozen entitlement rows",
1569
+ "type": "integer"
1570
+ },
1571
+ "returnRecordAt": {
1572
+ "description": "Stored record date",
1573
+ "format": "hyperscale-date-time",
1574
+ "type": "string"
1575
+ },
1576
+ "servicingPoolAccountId": {
1577
+ "description": "The servicing pool account",
1578
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1579
+ "type": "string",
1580
+ "x-hyperscale-reference-filter": {
1581
+ "column": "role",
1582
+ "values": [
1583
+ "customer_balance"
1584
+ ]
1585
+ }
1586
+ }
1587
+ },
1588
+ "id": "repayment_distribution",
1589
+ "idPrefix": "rd",
1590
+ "lifecycle": {
1591
+ "initial": "open",
1592
+ "states": [
1593
+ "open",
1594
+ "snapshotted"
1595
+ ],
1596
+ "transitions": {
1597
+ "snapshot_lender_claims": {
1598
+ "from": [
1599
+ "open"
1600
+ ],
1601
+ "to": "snapshotted"
1602
+ }
1603
+ }
1604
+ },
1605
+ "parties": {
1606
+ "payer": "servicingPoolAccountId"
1607
+ },
1608
+ "required": [
1609
+ "servicingPoolAccountId",
1610
+ "currency",
1611
+ "distributableAmount",
1612
+ "returnRecordAt",
1613
+ "maxRecipients"
1614
+ ],
1615
+ "summary": "Frozen largest-remainder distribution",
1616
+ "title": "Repayment distribution"
1617
+ },
1618
+ {
1619
+ "actionOrder": [
1620
+ "create",
1621
+ "payout"
1622
+ ],
1623
+ "actions": {
1624
+ "create": {
1625
+ "effects": {
1626
+ "reads": [
1627
+ {
1628
+ "signature": "reads.requires_refs",
1629
+ "source": "requiresRefs"
1630
+ }
1631
+ ]
1632
+ },
1633
+ "moves": [],
1634
+ "publicAction": "createRepaymentDistributionEntitlement",
1635
+ "requiresRefs": [
1636
+ {
1637
+ "bind": {
1638
+ "currency": "fields.currency",
1639
+ "servicingPoolAccountId": "fields.servicingPoolAccountId"
1640
+ },
1641
+ "field": "repaymentDistributionId",
1642
+ "statuses": [
1643
+ "open"
1644
+ ]
1645
+ }
1646
+ ],
1647
+ "steps": [],
1648
+ "summary": "Record one entitlement before snapshot"
1649
+ },
1650
+ "payout": {
1651
+ "distribute": {
1652
+ "amountRef": "payoutShare",
1653
+ "onZero": "skip_steps",
1654
+ "pool": {
1655
+ "from": "parent",
1656
+ "path": "fields.distributableAmount"
1657
+ },
1658
+ "refField": "repaymentDistributionId",
1659
+ "statuses": [
1660
+ "recorded",
1661
+ "paid"
1662
+ ],
1663
+ "weightField": "committedWeight"
1664
+ },
1665
+ "effects": {
1666
+ "moves": [
1667
+ {
1668
+ "signature": "moves.transfer.internal",
1669
+ "source": "moves[0]"
1670
+ }
1671
+ ],
1672
+ "reads": [
1673
+ {
1674
+ "signature": "reads.requires_refs",
1675
+ "source": "requiresRefs"
1676
+ }
1677
+ ]
1678
+ },
1679
+ "moves": [
1680
+ {
1681
+ "bind": {
1682
+ "amount": {
1683
+ "from": "instance",
1684
+ "path": "refs.payoutShare"
1685
+ },
1686
+ "currency": {
1687
+ "from": "instance",
1688
+ "path": "fields.currency"
1689
+ },
1690
+ "destinationAccountId": {
1691
+ "from": "instance",
1692
+ "path": "fields.lenderAccountId"
1693
+ },
1694
+ "metadata.instrumentId": {
1695
+ "from": "const",
1696
+ "value": "repayment_distribution_entitlement"
1697
+ },
1698
+ "metadata.instrumentInstanceId": {
1699
+ "from": "instance",
1700
+ "path": "instrumentInstanceId"
1701
+ },
1702
+ "metadata.phase": {
1703
+ "from": "const",
1704
+ "value": "payout"
1705
+ },
1706
+ "productId": {
1707
+ "from": "instance",
1708
+ "path": "productId"
1709
+ },
1710
+ "sourceAccountId": {
1711
+ "from": "instance",
1712
+ "path": "fields.servicingPoolAccountId"
1713
+ }
1714
+ },
1715
+ "capture": {
1716
+ "payoutPayoutTransferId": "transferId"
1717
+ },
1718
+ "key": "payout",
1719
+ "operation": "internal_transfer.create"
1720
+ }
1721
+ ],
1722
+ "publicAction": "payoutRepaymentDistributionEntitlement",
1723
+ "requiresRefs": [
1724
+ {
1725
+ "field": "repaymentDistributionId",
1726
+ "match": {
1727
+ "fields.currency": "fields.currency",
1728
+ "fields.servicingPoolAccountId": "fields.servicingPoolAccountId"
1729
+ },
1730
+ "statuses": [
1731
+ "snapshotted"
1732
+ ]
1733
+ }
1734
+ ],
1735
+ "steps": [],
1736
+ "summary": "Pay the deterministic largest-remainder share once"
1737
+ }
1738
+ },
1739
+ "description": "One frozen weighted entitlement in repayment_distribution",
1740
+ "fields": {
1741
+ "committedWeight": {
1742
+ "description": "Stored non-negative entitlement weight",
1743
+ "pattern": "^[1-9][0-9]{0,17}$",
1744
+ "type": "string"
1745
+ },
1746
+ "currency": {
1747
+ "description": "ISO 4217 currency code",
1748
+ "maxLength": 3,
1749
+ "minLength": 3,
1750
+ "pattern": "^[A-Z]{3}$",
1751
+ "type": "string"
1752
+ },
1753
+ "lenderAccountId": {
1754
+ "description": "The lender account",
1755
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1756
+ "type": "string",
1757
+ "x-hyperscale-reference-filter": {
1758
+ "column": "role",
1759
+ "values": [
1760
+ "customer_balance"
1761
+ ]
1762
+ }
1763
+ },
1764
+ "repaymentDistributionId": {
1765
+ "description": "The exact repayment_distribution",
1766
+ "pattern": "^rd_(sandbox|live)_[a-z0-9]{8,64}$",
1767
+ "type": "string"
1768
+ },
1769
+ "servicingPoolAccountId": {
1770
+ "description": "The servicing pool account",
1771
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
1772
+ "type": "string",
1773
+ "x-hyperscale-reference-filter": {
1774
+ "column": "role",
1775
+ "values": [
1776
+ "customer_balance"
1777
+ ]
1778
+ }
1779
+ }
1780
+ },
1781
+ "id": "repayment_distribution_entitlement",
1782
+ "idPrefix": "zzac",
1783
+ "lifecycle": {
1784
+ "initial": "recorded",
1785
+ "states": [
1786
+ "recorded",
1787
+ "paid"
1788
+ ],
1789
+ "transitions": {
1790
+ "payout": {
1791
+ "from": [
1792
+ "recorded"
1793
+ ],
1794
+ "to": "paid"
1795
+ }
1796
+ }
1797
+ },
1798
+ "parties": {
1799
+ "beneficiary": "lenderAccountId",
1800
+ "payer": "servicingPoolAccountId"
1801
+ },
1802
+ "required": [
1803
+ "lenderAccountId",
1804
+ "servicingPoolAccountId",
1805
+ "currency",
1806
+ "repaymentDistributionId",
1807
+ "committedWeight"
1808
+ ],
1809
+ "summary": "Frozen entitlement in repayment_distribution",
1810
+ "title": "Repayment distribution Entitlement"
1811
+ }
1812
+ ],
1813
+ "product": "crowdfunding_p2p",
1814
+ "subjects": [],
1815
+ "title": "Crowdfunding and peer lending",
1816
+ "udl": 1,
1817
+ "version": 1
1818
+ }
1819
+