@hyperscale0/udl 1.0.0-alpha.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.
Files changed (97) hide show
  1. package/AUTHORS +8 -0
  2. package/CHANGELOG.md +36 -0
  3. package/LICENSE +661 -0
  4. package/LICENSING.md +51 -0
  5. package/README.md +163 -0
  6. package/SECURITY.md +42 -0
  7. package/TRADEMARKS.md +35 -0
  8. package/conformance/README.md +79 -0
  9. package/conformance/invalid/blank-title.expected.json +10 -0
  10. package/conformance/invalid/blank-title.udl +50 -0
  11. package/conformance/invalid/depth-budget.expected.json +10 -0
  12. package/conformance/invalid/depth-budget.udl +49 -0
  13. package/conformance/invalid/format-version.expected.json +10 -0
  14. package/conformance/invalid/format-version.udl +50 -0
  15. package/conformance/invalid/invalid-utf8.expected.json +10 -0
  16. package/conformance/invalid/invalid-utf8.udl +1 -0
  17. package/conformance/invalid/malformed-json.expected.json +10 -0
  18. package/conformance/invalid/malformed-json.udl +1 -0
  19. package/conformance/invalid/missing-create-verb.expected.json +10 -0
  20. package/conformance/invalid/missing-create-verb.udl +45 -0
  21. package/conformance/invalid/not-an-object.expected.json +10 -0
  22. package/conformance/invalid/not-an-object.udl +1 -0
  23. package/conformance/invalid/noun-id-not-snake-case.expected.json +10 -0
  24. package/conformance/invalid/noun-id-not-snake-case.udl +50 -0
  25. package/conformance/invalid/unknown-key.expected.json +10 -0
  26. package/conformance/invalid/unknown-key.udl +51 -0
  27. package/conformance/invalid/unreachable-state.expected.json +10 -0
  28. package/conformance/invalid/unreachable-state.udl +51 -0
  29. package/conformance/invalid/verb-without-transition.expected.json +10 -0
  30. package/conformance/invalid/verb-without-transition.udl +55 -0
  31. package/conformance/valid/cards.expected.json +5 -0
  32. package/conformance/valid/cards.udl +1092 -0
  33. package/conformance/valid/commerce-escrow.expected.json +5 -0
  34. package/conformance/valid/commerce-escrow.udl +1011 -0
  35. package/conformance/valid/hand-edited.expected.json +5 -0
  36. package/conformance/valid/hand-edited.udl +1 -0
  37. package/conformance/valid/insured-car-marketplace.expected.json +5 -0
  38. package/conformance/valid/insured-car-marketplace.udl +831 -0
  39. package/conformance/valid/insured-travel.expected.json +5 -0
  40. package/conformance/valid/insured-travel.udl +2621 -0
  41. package/conformance/valid/minimal.expected.json +5 -0
  42. package/conformance/valid/minimal.udl +50 -0
  43. package/conformance/valid/protection.expected.json +5 -0
  44. package/conformance/valid/protection.udl +1136 -0
  45. package/dist/canonical.d.ts +2 -0
  46. package/dist/canonical.d.ts.map +1 -0
  47. package/dist/canonical.js +35 -0
  48. package/dist/canonical.js.map +1 -0
  49. package/dist/cli.d.ts +3 -0
  50. package/dist/cli.d.ts.map +1 -0
  51. package/dist/cli.js +138 -0
  52. package/dist/cli.js.map +1 -0
  53. package/dist/duration.d.ts +3 -0
  54. package/dist/duration.d.ts.map +1 -0
  55. package/dist/duration.js +23 -0
  56. package/dist/duration.js.map +1 -0
  57. package/dist/evolution.d.ts +63 -0
  58. package/dist/evolution.d.ts.map +1 -0
  59. package/dist/evolution.js +345 -0
  60. package/dist/evolution.js.map +1 -0
  61. package/dist/finance.d.ts +53 -0
  62. package/dist/finance.d.ts.map +1 -0
  63. package/dist/finance.js +511 -0
  64. package/dist/finance.js.map +1 -0
  65. package/dist/index.d.ts +11 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +8 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/limits.d.ts +33 -0
  70. package/dist/limits.d.ts.map +1 -0
  71. package/dist/limits.js +33 -0
  72. package/dist/limits.js.map +1 -0
  73. package/dist/parser.d.ts +4 -0
  74. package/dist/parser.d.ts.map +1 -0
  75. package/dist/parser.js +54 -0
  76. package/dist/parser.js.map +1 -0
  77. package/dist/schema.d.ts +757 -0
  78. package/dist/schema.d.ts.map +1 -0
  79. package/dist/schema.js +281 -0
  80. package/dist/schema.js.map +1 -0
  81. package/dist/validation.d.ts +64 -0
  82. package/dist/validation.d.ts.map +1 -0
  83. package/dist/validation.js +1323 -0
  84. package/dist/validation.js.map +1 -0
  85. package/package.json +84 -0
  86. package/spec/README.md +137 -0
  87. package/spec/udl.schema.json +1019 -0
  88. package/src/canonical.ts +41 -0
  89. package/src/cli.ts +148 -0
  90. package/src/duration.ts +26 -0
  91. package/src/evolution.ts +587 -0
  92. package/src/finance.ts +775 -0
  93. package/src/index.ts +59 -0
  94. package/src/limits.ts +32 -0
  95. package/src/parser.ts +61 -0
  96. package/src/schema.ts +339 -0
  97. package/src/validation.ts +2053 -0
@@ -0,0 +1,831 @@
1
+ {
2
+ "nouns": [
3
+ {
4
+ "description": "A vehicle listing advertises one car for sale at an asking price. It is reserved while a vehicle escrow holds the buyer's funds, sold when the escrow settles to the seller, returned to the market if the escrow is canceled, or delisted by the seller before sale.",
5
+ "fields": {
6
+ "askingPrice": {
7
+ "description": "Positive minor-unit integer amount serialized as a string (at most 18 digits)",
8
+ "pattern": "^[1-9][0-9]{0,17}$",
9
+ "type": "string"
10
+ },
11
+ "currency": {
12
+ "description": "ISO 4217 currency code",
13
+ "maxLength": 3,
14
+ "minLength": 3,
15
+ "pattern": "^[A-Z]{3}$",
16
+ "type": "string"
17
+ },
18
+ "description": {
19
+ "maxLength": 2000,
20
+ "minLength": 1,
21
+ "type": "string"
22
+ },
23
+ "escrowId": {
24
+ "description": "Vehicle escrow whose held funds reserve this listing",
25
+ "pattern": "^vesc_(sandbox|live)_[a-z0-9]{8,64}$",
26
+ "type": "string"
27
+ },
28
+ "headline": {
29
+ "maxLength": 120,
30
+ "minLength": 1,
31
+ "type": "string"
32
+ },
33
+ "sellerAccountId": {
34
+ "description": "Hyperscale account ID",
35
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
36
+ "type": "string",
37
+ "x-hyperscale-reference-filter": {
38
+ "column": "role",
39
+ "values": [
40
+ "customer_balance"
41
+ ]
42
+ }
43
+ }
44
+ },
45
+ "id": "vehicle_listing",
46
+ "idPrefix": "vlst",
47
+ "lifecycle": {
48
+ "initial": "listed",
49
+ "states": [
50
+ "listed",
51
+ "reserved",
52
+ "sold",
53
+ "delisted"
54
+ ],
55
+ "transitions": {
56
+ "complete": {
57
+ "from": [
58
+ "reserved"
59
+ ],
60
+ "to": "sold"
61
+ },
62
+ "delist": {
63
+ "from": [
64
+ "listed"
65
+ ],
66
+ "to": "delisted"
67
+ },
68
+ "reserve": {
69
+ "from": [
70
+ "listed"
71
+ ],
72
+ "to": "reserved"
73
+ },
74
+ "unreserve": {
75
+ "from": [
76
+ "reserved"
77
+ ],
78
+ "to": "listed"
79
+ }
80
+ }
81
+ },
82
+ "parties": {
83
+ "subjectHolder": "sellerAccountId"
84
+ },
85
+ "required": [
86
+ "sellerAccountId",
87
+ "headline",
88
+ "askingPrice",
89
+ "currency"
90
+ ],
91
+ "subject": {
92
+ "kinds": [
93
+ "vehicle"
94
+ ]
95
+ },
96
+ "summary": "Marketplace listing for an insured used car offered by a seller.",
97
+ "title": "Vehicle listing",
98
+ "update": {
99
+ "fields": [
100
+ "headline",
101
+ "description",
102
+ "askingPrice",
103
+ "escrowId"
104
+ ],
105
+ "states": [
106
+ "listed"
107
+ ]
108
+ },
109
+ "verbs": {
110
+ "complete": {
111
+ "description": "Marks the car sold after the reserving escrow settles to the seller. Pure lifecycle transition over the catalog row.",
112
+ "moves": [],
113
+ "requiresRefs": [
114
+ {
115
+ "field": "escrowId",
116
+ "statuses": [
117
+ "settled"
118
+ ]
119
+ }
120
+ ],
121
+ "steps": [],
122
+ "summary": "Complete a vehicle listing sale"
123
+ },
124
+ "create": {
125
+ "description": "Publishes the car to the marketplace at an asking price. No money moves until an escrow is opened against it.",
126
+ "moves": [],
127
+ "steps": [],
128
+ "summary": "Create a vehicle listing"
129
+ },
130
+ "delist": {
131
+ "description": "Withdraws an unsold car from the marketplace. No money has moved, so this is a pure lifecycle transition.",
132
+ "moves": [],
133
+ "steps": [],
134
+ "summary": "Delist a vehicle listing"
135
+ },
136
+ "reserve": {
137
+ "description": "Takes the listing off the market while a buyer's escrow holds funds. The referenced escrow must currently hold the buyer's funds.",
138
+ "moves": [],
139
+ "requiresRefs": [
140
+ {
141
+ "field": "escrowId",
142
+ "statuses": [
143
+ "funded"
144
+ ]
145
+ }
146
+ ],
147
+ "steps": [],
148
+ "summary": "Reserve a vehicle listing"
149
+ },
150
+ "unreserve": {
151
+ "description": "Relists the car after the reserving escrow is canceled. No money has moved on the listing itself.",
152
+ "moves": [],
153
+ "requiresRefs": [
154
+ {
155
+ "field": "escrowId",
156
+ "statuses": [
157
+ "canceled",
158
+ "penalty_collected"
159
+ ]
160
+ }
161
+ ],
162
+ "steps": [],
163
+ "summary": "Return a reserved vehicle listing to the market"
164
+ }
165
+ }
166
+ },
167
+ {
168
+ "aggregateInvariants": [
169
+ {
170
+ "childField": "price",
171
+ "childNounId": "vehicle_escrow",
172
+ "childRefField": "policyId",
173
+ "childStatuses": [
174
+ "created",
175
+ "funded",
176
+ "cancel_quoted"
177
+ ],
178
+ "parentField": "coverageAmount"
179
+ }
180
+ ],
181
+ "description": "A motor policy insures a vehicle subject: the premium settles into the insurer account on bind, and a claim holds the coverage amount as a pending ledger reservation from the insurer toward the policyholder, posted on settlement or voided on denial. Binding is underwritten by an insurance carrier under a deadline; coverage lapses on expiry.",
182
+ "fields": {
183
+ "coverageAmount": {
184
+ "description": "Positive minor-unit integer amount serialized as a string (at most 18 digits)",
185
+ "pattern": "^[1-9][0-9]{0,17}$",
186
+ "type": "string"
187
+ },
188
+ "coverageDescription": {
189
+ "maxLength": 180,
190
+ "minLength": 1,
191
+ "type": "string"
192
+ },
193
+ "currency": {
194
+ "description": "ISO 4217 currency code",
195
+ "maxLength": 3,
196
+ "minLength": 3,
197
+ "pattern": "^[A-Z]{3}$",
198
+ "type": "string"
199
+ },
200
+ "expiresAt": {
201
+ "format": "hyperscale-date-time",
202
+ "type": "string"
203
+ },
204
+ "insurerAccountId": {
205
+ "description": "Hyperscale account ID",
206
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
207
+ "type": "string",
208
+ "x-hyperscale-reference-filter": {
209
+ "column": "role",
210
+ "values": [
211
+ "customer_balance"
212
+ ]
213
+ }
214
+ },
215
+ "policyholderAccountId": {
216
+ "description": "Hyperscale account ID",
217
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
218
+ "type": "string",
219
+ "x-hyperscale-reference-filter": {
220
+ "column": "role",
221
+ "values": [
222
+ "customer_balance"
223
+ ]
224
+ }
225
+ },
226
+ "premiumAmount": {
227
+ "description": "Positive minor-unit integer amount serialized as a string (at most 18 digits)",
228
+ "pattern": "^[1-9][0-9]{0,17}$",
229
+ "type": "string"
230
+ }
231
+ },
232
+ "id": "motor_policy",
233
+ "idPrefix": "mpol",
234
+ "lifecycle": {
235
+ "initial": "quoted",
236
+ "states": [
237
+ "quoted",
238
+ "active",
239
+ "claim_open",
240
+ "settled",
241
+ "denied",
242
+ "expired",
243
+ "canceled"
244
+ ],
245
+ "transitions": {
246
+ "bind": {
247
+ "from": [
248
+ "quoted"
249
+ ],
250
+ "to": "active"
251
+ },
252
+ "cancel": {
253
+ "from": [
254
+ "quoted"
255
+ ],
256
+ "to": "canceled"
257
+ },
258
+ "deny_claim": {
259
+ "from": [
260
+ "claim_open"
261
+ ],
262
+ "to": "denied"
263
+ },
264
+ "expire": {
265
+ "from": [
266
+ "active"
267
+ ],
268
+ "to": "expired"
269
+ },
270
+ "open_claim": {
271
+ "from": [
272
+ "active"
273
+ ],
274
+ "to": "claim_open"
275
+ },
276
+ "settle_claim": {
277
+ "from": [
278
+ "claim_open"
279
+ ],
280
+ "to": "settled"
281
+ }
282
+ }
283
+ },
284
+ "parties": {
285
+ "beneficiary": "insurerAccountId",
286
+ "payer": "policyholderAccountId"
287
+ },
288
+ "required": [
289
+ "policyholderAccountId",
290
+ "insurerAccountId",
291
+ "premiumAmount",
292
+ "coverageAmount",
293
+ "currency",
294
+ "expiresAt"
295
+ ],
296
+ "subject": {
297
+ "kinds": [
298
+ "vehicle"
299
+ ]
300
+ },
301
+ "summary": "Vehicle insurance policy priced and capped on a declared car value.",
302
+ "title": "Motor policy",
303
+ "update": {
304
+ "fields": [
305
+ "coverageDescription"
306
+ ],
307
+ "states": [
308
+ "quoted"
309
+ ]
310
+ },
311
+ "verbs": {
312
+ "bind": {
313
+ "decision": {
314
+ "capability": "insurance_carrier",
315
+ "deadlineMs": 15000,
316
+ "onTimeout": "decline"
317
+ },
318
+ "description": "Underwrites the policy through an insurance carrier under a deadline, then settles the premium from the policyholder into the insurer account and puts the policy in force.",
319
+ "earnable": true,
320
+ "eventName": "motor_policy.bound",
321
+ "moves": [
322
+ {
323
+ "bind": {
324
+ "amount": {
325
+ "from": "instance",
326
+ "path": "fields.premiumAmount"
327
+ },
328
+ "currency": {
329
+ "from": "instance",
330
+ "path": "fields.currency"
331
+ },
332
+ "destinationAccountId": {
333
+ "from": "instance",
334
+ "path": "fields.insurerAccountId"
335
+ },
336
+ "sourceAccountId": {
337
+ "from": "instance",
338
+ "path": "fields.policyholderAccountId"
339
+ },
340
+ "workspaceId": {
341
+ "from": "instance",
342
+ "path": "workspaceId"
343
+ }
344
+ },
345
+ "capture": {
346
+ "premiumTransferId": "transferId"
347
+ },
348
+ "key": "transfer",
349
+ "operation": "internal_transfer.create"
350
+ }
351
+ ],
352
+ "steps": [],
353
+ "summary": "Bind a motor policy"
354
+ },
355
+ "cancel": {
356
+ "description": "Closes a quote that never bound. No money has moved, so this is a pure lifecycle transition.",
357
+ "moves": [],
358
+ "steps": [],
359
+ "summary": "Cancel a quoted motor policy"
360
+ },
361
+ "create": {
362
+ "description": "Opens the policy quote against a vehicle subject. No money moves until the policy binds.",
363
+ "moves": [],
364
+ "steps": [],
365
+ "summary": "Quote a motor policy"
366
+ },
367
+ "deny_claim": {
368
+ "description": "Denies the claim and voids the pending coverage hold, releasing the reserved amount back to the insurer account.",
369
+ "moves": [
370
+ {
371
+ "bind": {
372
+ "reason": {
373
+ "from": "const",
374
+ "value": "Motor claim denied"
375
+ },
376
+ "transferId": {
377
+ "from": "instance",
378
+ "path": "refs.claimTransferId"
379
+ }
380
+ },
381
+ "key": "transfer",
382
+ "operation": "internal_transfer.void"
383
+ }
384
+ ],
385
+ "steps": [],
386
+ "summary": "Deny a motor claim"
387
+ },
388
+ "expire": {
389
+ "description": "Ends coverage at term with no open claim. The collected premium stays with the insurer, so this is a pure lifecycle transition fired by the maintenance loop under the expiry due-condition.",
390
+ "due": {
391
+ "field": "expiresAt"
392
+ },
393
+ "moves": [],
394
+ "steps": [],
395
+ "summary": "Expire a motor policy"
396
+ },
397
+ "open_claim": {
398
+ "description": "Places a pending ledger hold for the coverage amount from the insurer account toward the policyholder. The insurer account must be capitalized to the coverage amount, or the hold is rejected for insufficient balance.",
399
+ "moves": [
400
+ {
401
+ "bind": {
402
+ "amount": {
403
+ "from": "instance",
404
+ "path": "fields.coverageAmount"
405
+ },
406
+ "currency": {
407
+ "from": "instance",
408
+ "path": "fields.currency"
409
+ },
410
+ "destinationAccountId": {
411
+ "from": "instance",
412
+ "path": "fields.policyholderAccountId"
413
+ },
414
+ "sourceAccountId": {
415
+ "from": "instance",
416
+ "path": "fields.insurerAccountId"
417
+ }
418
+ },
419
+ "capture": {
420
+ "claimTransferId": "transferId"
421
+ },
422
+ "key": "transfer",
423
+ "operation": "internal_transfer.reserve"
424
+ }
425
+ ],
426
+ "steps": [],
427
+ "summary": "Open a motor claim"
428
+ },
429
+ "settle_claim": {
430
+ "description": "Approves the claim and posts the held coverage amount, settling the payout into the policyholder account.",
431
+ "eventName": "motor_policy.claim_settled",
432
+ "moves": [
433
+ {
434
+ "bind": {
435
+ "transferId": {
436
+ "from": "instance",
437
+ "path": "refs.claimTransferId"
438
+ }
439
+ },
440
+ "key": "transfer",
441
+ "operation": "internal_transfer.post"
442
+ }
443
+ ],
444
+ "steps": [],
445
+ "summary": "Settle a motor claim"
446
+ }
447
+ }
448
+ },
449
+ {
450
+ "description": "A vehicle escrow holds the buyer's funds in a dedicated product escrow account, then releases the full price to the seller while the car's motor policy is active. Before release it can unwind through a quote then a confirm that refunds the price to the buyer under a penalty schedule.",
451
+ "fields": {
452
+ "buyerAccountId": {
453
+ "description": "Hyperscale account ID",
454
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
455
+ "type": "string",
456
+ "x-hyperscale-reference-filter": {
457
+ "column": "role",
458
+ "values": [
459
+ "customer_balance"
460
+ ]
461
+ }
462
+ },
463
+ "currency": {
464
+ "description": "ISO 4217 currency code",
465
+ "maxLength": 3,
466
+ "minLength": 3,
467
+ "pattern": "^[A-Z]{3}$",
468
+ "type": "string"
469
+ },
470
+ "memo": {
471
+ "maxLength": 180,
472
+ "minLength": 1,
473
+ "type": "string"
474
+ },
475
+ "policyId": {
476
+ "description": "Motor policy that must be active for the escrow to clear",
477
+ "pattern": "^mpol_(sandbox|live)_[a-z0-9]{8,64}$",
478
+ "type": "string"
479
+ },
480
+ "price": {
481
+ "description": "Positive minor-unit integer amount serialized as a string (at most 18 digits)",
482
+ "pattern": "^[1-9][0-9]{0,17}$",
483
+ "type": "string"
484
+ },
485
+ "sellerAccountId": {
486
+ "description": "Hyperscale account ID",
487
+ "pattern": "^acct_(sandbox|live)_[a-z0-9]{8,64}$",
488
+ "type": "string",
489
+ "x-hyperscale-reference-filter": {
490
+ "column": "role",
491
+ "values": [
492
+ "customer_balance"
493
+ ]
494
+ }
495
+ }
496
+ },
497
+ "id": "vehicle_escrow",
498
+ "idPrefix": "vesc",
499
+ "lifecycle": {
500
+ "initial": "created",
501
+ "states": [
502
+ "created",
503
+ "funded",
504
+ "settled",
505
+ "cancel_quoted",
506
+ "canceled",
507
+ "penalty_collected"
508
+ ],
509
+ "transitions": {
510
+ "collect_penalty": {
511
+ "from": [
512
+ "canceled"
513
+ ],
514
+ "to": "penalty_collected"
515
+ },
516
+ "confirm_cancel": {
517
+ "from": [
518
+ "cancel_quoted"
519
+ ],
520
+ "to": "canceled"
521
+ },
522
+ "fund": {
523
+ "from": [
524
+ "created"
525
+ ],
526
+ "to": "funded"
527
+ },
528
+ "quote_cancel": {
529
+ "from": [
530
+ "funded"
531
+ ],
532
+ "to": "cancel_quoted"
533
+ },
534
+ "release": {
535
+ "from": [
536
+ "funded"
537
+ ],
538
+ "to": "settled"
539
+ }
540
+ }
541
+ },
542
+ "parties": {
543
+ "beneficiary": "sellerAccountId",
544
+ "payer": "buyerAccountId"
545
+ },
546
+ "required": [
547
+ "buyerAccountId",
548
+ "sellerAccountId",
549
+ "policyId",
550
+ "price",
551
+ "currency"
552
+ ],
553
+ "subject": {
554
+ "kinds": [
555
+ "vehicle"
556
+ ]
557
+ },
558
+ "summary": "Insured-car escrow that clears to the seller while coverage is live.",
559
+ "title": "Vehicle escrow",
560
+ "unwind": {
561
+ "confirm": "confirm_cancel",
562
+ "penalty": [
563
+ {
564
+ "bps": 500,
565
+ "withinOffset": "P7D"
566
+ },
567
+ {
568
+ "bps": 1000
569
+ }
570
+ ],
571
+ "quote": "quote_cancel",
572
+ "refundDestinationField": "buyerAccountId",
573
+ "refundableField": "price"
574
+ },
575
+ "update": {
576
+ "fields": [
577
+ "memo"
578
+ ],
579
+ "states": [
580
+ "created"
581
+ ]
582
+ },
583
+ "verbs": {
584
+ "collect_penalty": {
585
+ "description": "Pays the computed retained cancellation penalty to the seller. Invoke only when confirm_cancel returns a positive refs.unwindPenalty.",
586
+ "eventName": "vehicle_escrow.penalty_collected",
587
+ "moves": [
588
+ {
589
+ "bind": {
590
+ "amount": {
591
+ "from": "instance",
592
+ "path": "refs.unwindPenalty"
593
+ },
594
+ "currency": {
595
+ "from": "instance",
596
+ "path": "fields.currency"
597
+ },
598
+ "destinationAccountId": {
599
+ "from": "instance",
600
+ "path": "fields.sellerAccountId"
601
+ },
602
+ "sourceAccountId": {
603
+ "from": "instance",
604
+ "path": "refs.escrowAccountId"
605
+ },
606
+ "workspaceId": {
607
+ "from": "instance",
608
+ "path": "workspaceId"
609
+ }
610
+ },
611
+ "capture": {
612
+ "penaltyTransferId": "transferId"
613
+ },
614
+ "key": "transfer",
615
+ "operation": "internal_transfer.create"
616
+ }
617
+ ],
618
+ "steps": [],
619
+ "summary": "Collect a vehicle cancellation penalty"
620
+ },
621
+ "confirm_cancel": {
622
+ "description": "Unwinds the escrow, refunding the price minus the scheduled penalty to the buyer. The quote seeded that amount into refs.unwindRefund; this step moves it from the escrow account back to the buyer.",
623
+ "moves": [
624
+ {
625
+ "bind": {
626
+ "amount": {
627
+ "from": "instance",
628
+ "path": "refs.unwindRefund"
629
+ },
630
+ "currency": {
631
+ "from": "instance",
632
+ "path": "fields.currency"
633
+ },
634
+ "destinationAccountId": {
635
+ "from": "instance",
636
+ "path": "fields.buyerAccountId"
637
+ },
638
+ "sourceAccountId": {
639
+ "from": "instance",
640
+ "path": "refs.escrowAccountId"
641
+ },
642
+ "workspaceId": {
643
+ "from": "instance",
644
+ "path": "workspaceId"
645
+ }
646
+ },
647
+ "capture": {
648
+ "refundTransferId": "transferId"
649
+ },
650
+ "key": "transfer",
651
+ "operation": "internal_transfer.create"
652
+ }
653
+ ],
654
+ "steps": [],
655
+ "summary": "Confirm a vehicle escrow cancellation"
656
+ },
657
+ "create": {
658
+ "description": "Opens the escrow and provisions its dedicated product escrow account. The price is admitted against the motor policy's coverage cap.",
659
+ "moves": [],
660
+ "steps": [
661
+ {
662
+ "bind": {
663
+ "currency": {
664
+ "from": "instance",
665
+ "path": "fields.currency"
666
+ },
667
+ "owner.id": {
668
+ "from": "instance",
669
+ "path": "workspaceId"
670
+ },
671
+ "owner.type": {
672
+ "from": "const",
673
+ "value": "workspace"
674
+ },
675
+ "role": {
676
+ "from": "const",
677
+ "value": "workspace_escrow"
678
+ },
679
+ "workspaceId": {
680
+ "from": "instance",
681
+ "path": "workspaceId"
682
+ }
683
+ },
684
+ "capture": {
685
+ "escrowAccountId": "accountId"
686
+ },
687
+ "operation": "account.escrow.provision"
688
+ }
689
+ ],
690
+ "summary": "Create a vehicle escrow"
691
+ },
692
+ "fund": {
693
+ "description": "Moves the price from the buyer account into the escrow account.",
694
+ "moves": [
695
+ {
696
+ "bind": {
697
+ "amount": {
698
+ "from": "instance",
699
+ "path": "fields.price"
700
+ },
701
+ "currency": {
702
+ "from": "instance",
703
+ "path": "fields.currency"
704
+ },
705
+ "destinationAccountId": {
706
+ "from": "instance",
707
+ "path": "refs.escrowAccountId"
708
+ },
709
+ "sourceAccountId": {
710
+ "from": "instance",
711
+ "path": "fields.buyerAccountId"
712
+ },
713
+ "workspaceId": {
714
+ "from": "instance",
715
+ "path": "workspaceId"
716
+ }
717
+ },
718
+ "capture": {
719
+ "fundTransferId": "transferId"
720
+ },
721
+ "key": "transfer",
722
+ "operation": "internal_transfer.create"
723
+ }
724
+ ],
725
+ "steps": [],
726
+ "summary": "Fund a vehicle escrow"
727
+ },
728
+ "quote_cancel": {
729
+ "description": "Parks the funded escrow for cancellation and quotes the refund under the penalty schedule. No money moves; the confirm executes the refund.",
730
+ "moves": [],
731
+ "steps": [],
732
+ "summary": "Quote a vehicle escrow cancellation"
733
+ },
734
+ "release": {
735
+ "description": "Clears the full price from the escrow account to the seller while the car's motor policy is active.",
736
+ "earnable": true,
737
+ "moves": [
738
+ {
739
+ "bind": {
740
+ "amount": {
741
+ "from": "instance",
742
+ "path": "fields.price"
743
+ },
744
+ "currency": {
745
+ "from": "instance",
746
+ "path": "fields.currency"
747
+ },
748
+ "destinationAccountId": {
749
+ "from": "instance",
750
+ "path": "fields.sellerAccountId"
751
+ },
752
+ "sourceAccountId": {
753
+ "from": "instance",
754
+ "path": "refs.escrowAccountId"
755
+ },
756
+ "workspaceId": {
757
+ "from": "instance",
758
+ "path": "workspaceId"
759
+ }
760
+ },
761
+ "capture": {
762
+ "releaseTransferId": "transferId"
763
+ },
764
+ "key": "transfer",
765
+ "operation": "internal_transfer.create"
766
+ }
767
+ ],
768
+ "requiresRefs": [
769
+ {
770
+ "field": "policyId",
771
+ "statuses": [
772
+ "active"
773
+ ]
774
+ }
775
+ ],
776
+ "steps": [],
777
+ "summary": "Release a vehicle escrow"
778
+ }
779
+ }
780
+ }
781
+ ],
782
+ "product": "insured_car_marketplace",
783
+ "subjects": [
784
+ {
785
+ "declaredValue": "required",
786
+ "kind": "vehicle",
787
+ "schema": {
788
+ "additionalProperties": false,
789
+ "properties": {
790
+ "make": {
791
+ "maxLength": 60,
792
+ "minLength": 1,
793
+ "type": "string"
794
+ },
795
+ "mileageKm": {
796
+ "maximum": 9007199254740991,
797
+ "minimum": 0,
798
+ "type": "integer"
799
+ },
800
+ "model": {
801
+ "maxLength": 60,
802
+ "minLength": 1,
803
+ "type": "string"
804
+ },
805
+ "vin": {
806
+ "maxLength": 17,
807
+ "minLength": 11,
808
+ "type": "string"
809
+ },
810
+ "year": {
811
+ "pattern": "^\\d{4}$",
812
+ "type": "string"
813
+ }
814
+ },
815
+ "required": [
816
+ "vin",
817
+ "make",
818
+ "model",
819
+ "year",
820
+ "mileageKm"
821
+ ],
822
+ "type": "object"
823
+ },
824
+ "title": "Vehicle",
825
+ "version": 1
826
+ }
827
+ ],
828
+ "title": "Insured car marketplace",
829
+ "udl": 1,
830
+ "version": 1
831
+ }