@hyperscale0/hsx 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +28 -7
  2. package/dist/src/cli.d.ts +1 -1
  3. package/dist/src/cli.d.ts.map +1 -1
  4. package/dist/src/cli.js +39 -5
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/compile.d.ts +0 -1
  7. package/dist/src/compile.d.ts.map +1 -1
  8. package/dist/src/compile.js +15 -1
  9. package/dist/src/compile.js.map +1 -1
  10. package/dist/src/cost.d.ts +3 -3
  11. package/dist/src/cost.d.ts.map +1 -1
  12. package/dist/src/cost.js +22 -12
  13. package/dist/src/cost.js.map +1 -1
  14. package/dist/src/parse.d.ts.map +1 -1
  15. package/dist/src/parse.js +10 -14
  16. package/dist/src/parse.js.map +1 -1
  17. package/dist/src/std-bundle.d.ts.map +1 -1
  18. package/dist/src/std-bundle.js +20 -84
  19. package/dist/src/std-bundle.js.map +1 -1
  20. package/dist/src/typecheck.d.ts.map +1 -1
  21. package/dist/src/typecheck.js +171 -39
  22. package/dist/src/typecheck.js.map +1 -1
  23. package/dist/src/version.d.ts +1 -1
  24. package/dist/src/version.js +1 -1
  25. package/docs/README.md +4 -2
  26. package/docs/guide/01-first-program.md +1 -1
  27. package/docs/guide/06-schedules.md +5 -1
  28. package/docs/guide/08-writing-a-module.md +48 -0
  29. package/docs/llms-full.txt +1928 -421
  30. package/docs/llms.txt +2 -3
  31. package/docs/reference/cli.md +6 -5
  32. package/docs/reference/diagnostics.md +9 -9
  33. package/docs/reference/grammar.md +2 -3
  34. package/docs/reference/std/advance.md +94 -17
  35. package/docs/reference/std/cancellable_booking.md +138 -17
  36. package/docs/reference/std/captured_payment.md +96 -24
  37. package/docs/reference/std/conditional_disbursement.md +82 -14
  38. package/docs/reference/std/credit_facility.md +89 -16
  39. package/docs/reference/std/held_payment.md +155 -55
  40. package/docs/reference/std/instant_transfer.md +79 -12
  41. package/docs/reference/std/metered.md +71 -9
  42. package/docs/reference/std/pooled_split.md +76 -7
  43. package/docs/reference/std/premium_forward.md +100 -20
  44. package/docs/reference/std/reconciled_payout.md +84 -14
  45. package/docs/reference/std/rotating_pool.md +112 -24
  46. package/docs/reference/std/scheduled.md +117 -32
  47. package/docs/reference/std/security_deposit.md +119 -27
  48. package/docs/reference/std/settlement_batch.md +105 -24
  49. package/docs/reference/std/swap.md +113 -26
  50. package/docs/reference/std/threshold_pool.md +120 -29
  51. package/docs/reference/std/weighted_distribution.md +117 -21
  52. package/docs/reference/types.md +15 -15
  53. package/docs/reference/udl-output.md +6 -6
  54. package/examples/01-first-program/README.md +1 -1
  55. package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/README.md +1 -1
  56. package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/photo-booth.hsx +1 -1
  57. package/examples/04-complete-product/README.md +1 -1
  58. package/examples/05-authored-instrument/README.md +5 -0
  59. package/examples/05-authored-instrument/payment.hsx +37 -0
  60. package/examples/05-watch-club/watch-club.hsx +0 -1
  61. package/examples/README.md +1 -2
  62. package/examples/advance/advance.udl +29 -6
  63. package/examples/cancellable_booking/cancellable_booking.udl +19 -0
  64. package/examples/captured_payment/captured_payment.hsx +0 -4
  65. package/examples/captured_payment/captured_payment.udl +6 -0
  66. package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -2
  67. package/examples/conditional_disbursement/conditional_disbursement.udl +4 -0
  68. package/examples/cost-table.json +136 -8
  69. package/examples/credit_facility/credit_facility.hsx +0 -3
  70. package/examples/credit_facility/credit_facility.udl +17 -1
  71. package/examples/held_payment/held_payment.udl +16 -0
  72. package/examples/instant_transfer/instant_transfer.udl +6 -0
  73. package/examples/metered/metered.udl +5 -1
  74. package/examples/pooled_split/pooled_split.udl +4 -0
  75. package/examples/premium_forward/premium_forward.udl +4 -0
  76. package/examples/reconciled_payout/reconciled_payout.udl +7 -0
  77. package/examples/rotating_pool/rotating_pool.udl +8 -0
  78. package/examples/scheduled/scheduled.udl +18 -3
  79. package/examples/security_deposit/security_deposit.udl +13 -0
  80. package/examples/settlement_batch/settlement_batch.udl +4 -0
  81. package/examples/swap/swap.udl +10 -0
  82. package/examples/threshold_pool/threshold_pool.udl +11 -0
  83. package/examples/weighted_distribution/weighted_distribution.udl +9 -0
  84. package/package.json +10 -10
  85. package/skills/hsx/SKILL.md +2 -36
  86. package/src/cli.ts +41 -5
  87. package/src/compile.ts +14 -6
  88. package/src/cost.ts +13 -16
  89. package/src/parse.ts +15 -10
  90. package/src/std-bundle.ts +21 -88
  91. package/src/typecheck.ts +207 -34
  92. package/src/version.ts +1 -1
  93. package/std/SEMANTICS.md +33 -128
  94. package/std/money_flows/advance.hsx +39 -26
  95. package/std/money_flows/cancellable_booking.hsx +275 -8
  96. package/std/money_flows/captured_payment.hsx +1 -9
  97. package/std/money_flows/conditional_disbursement.hsx +0 -6
  98. package/std/money_flows/credit_facility.hsx +0 -9
  99. package/std/money_flows/held_payment.hsx +39 -1
  100. package/std/money_flows/index.hsx +2 -1
  101. package/std/money_flows/metered.hsx +1 -5
  102. package/std/money_flows/scheduled.hsx +29 -119
  103. package/std/money_flows/threshold_pool.hsx +40 -5
  104. package/std/money_flows/weighted_distribution.hsx +47 -5
  105. package/docs/reference/std/recurring_collection.md +0 -25
  106. package/examples/recurring_collection/README.md +0 -3
  107. package/examples/recurring_collection/recurring_collection.hsx +0 -21
  108. package/examples/recurring_collection/recurring_collection.udl +0 -1135
  109. package/std/money_flows/recurring_collection.hsx +0 -72
@@ -24,6 +24,9 @@ module std.money_flows.advance
24
24
  // - `count`: Optional number of scheduled installment repayments.
25
25
  // - `every`: Optional recurrence duration between installments (e.g. `"P30D"`).
26
26
  // - `first_due`: Optional date for the first installment repayment.
27
+ // - `repayment_source`: Optional party repaying when distinct from the capital recipient.
28
+ // - `profit_to`: Optional recipient of profit, separated from principal repayments.
29
+ // - `dated`: Require an explicit signed date for each repayment instead of duration offsets.
27
30
  // - `against`: Optional reference to a hold instrument whose release will be carved to repay the advance.
28
31
  //
29
32
  // ### Decision ports
@@ -54,6 +57,9 @@ export instrument advance<C>(
54
57
  every: optional<text>,
55
58
  first_due: optional<date>,
56
59
  against: optional<ref>,
60
+ repayment_source: optional<party>,
61
+ profit_to: optional<party>,
62
+ dated: optional<boolean>,
57
63
  ) {
58
64
  let(amount_path): concat("fields.", amount);
59
65
  let(currency_path): "fields.currency";
@@ -66,6 +72,8 @@ export instrument advance<C>(
66
72
  parties {
67
73
  payer: funder;
68
74
  beneficiary: to;
75
+ when(repayment_source) { repayment: repayment_source; }
76
+ when(profit_to) { profit: profit_to; }
69
77
  }
70
78
 
71
79
  when(against) {
@@ -131,6 +139,8 @@ export instrument advance<C>(
131
139
  }
132
140
 
133
141
  when_not(against) {
142
+ let(repayment_role): if_eq(repayment_source, false, "beneficiary", "repayment");
143
+ when(fee) { computes derived: { field: feeAmount; source_field: amount; rule: { kind: percentage_of; bps: basis_points(fee); }; rounding: floor; }; }
134
144
  let(repayment_fields): names("repayment", count, "Amount");
135
145
  let(first_target): if_eq(count, 1, "repaid", "repaying_1");
136
146
  let(states_key): "states";
@@ -144,6 +154,7 @@ export instrument advance<C>(
144
154
  }
145
155
  fee_amount {
146
156
  type: money<C>;
157
+ when(fee) { optional: true; }
147
158
  description: concat(fee, " of ", amount, ", the funder's discount repaid on top of the advance");
148
159
  }
149
160
  repayable_amount {
@@ -154,16 +165,13 @@ export instrument advance<C>(
154
165
  type: date;
155
166
  description: concat("Due date of the first repayment; repayment k falls ", every, " after its predecessor");
156
167
  }
157
- repayment_1_amount {
158
- type: money<C>;
159
- description: concat("Repayment 1 of ", count, " (carries the integer-division remainder): about 33.34% of repayableAmount, collected on its own stored-date anchor");
160
- }
161
- for repayment in sub(count, 1) {
162
- let(index): add(repayment, 1);
163
- repayment[index]Amount {
164
- type: money<C>;
165
- description: concat("Repayment ", index, " of ", count, ": about 33.33% of repayableAmount, collected on its own stored-date anchor");
168
+ for i in count {
169
+ repayment[i]Amount { type: money<C>; description: "Stored total for repayment {i}"; }
170
+ when(profit_to) {
171
+ repayment[i]Principal { type: money<C>; description: "Principal returned to the funder by repayment {i}"; }
172
+ repayment[i]Profit { type: money<C>; description: "Profit paid to the named recipient by repayment {i}"; }
166
173
  }
174
+ when(dated) { repayment[i]DueAt { type: date; description: "Signed calendar due date for repayment {i}"; } }
167
175
  }
168
176
  }
169
177
  lifecycle {
@@ -187,8 +195,15 @@ export instrument advance<C>(
187
195
  piece_fields: [amount, feeAmount];
188
196
  total_field: repayableAmount;
189
197
  }];
198
+ when(profit_to) {
199
+ partitions: { total_field: amount; piece_fields: names("repayment", count, "Principal"); };
200
+ partitions: { total_field: feeAmount; piece_fields: names("repayment", count, "Profit"); };
201
+ for i in count {
202
+ partitions: { total_field: concat("repayment", i, "Amount"); piece_fields: [concat("repayment", i, "Principal"), concat("repayment", i, "Profit")]; };
203
+ }
204
+ }
190
205
  action create {
191
- agent_description: "Open the advance row before any money moves. The caller supplies the {amount}, the fee amount, the repayable total, and the first repayment date. The repayment fields must partition the repayable total exactly. Nothing is disbursed here.";
206
+ agent_description: "Open the advance row before any money moves. The caller supplies the {amount}, the repayable total, and repayment dates. A declared fee derives the fee amount from principal. The repayment fields must partition the repayable total exactly. Nothing is disbursed here.";
192
207
  summary: concat("Create a ", words(instrument));
193
208
  steps: [];
194
209
  moves: [];
@@ -199,22 +214,20 @@ export instrument advance<C>(
199
214
  moves: [{ amount: amount; from: payer; to: beneficiary; key: transfer; operation: create; }];
200
215
  steps: [];
201
216
  }
202
- action collect_repayment_1 {
203
- summary: concat("Collect repayment 1 of ", count);
204
- due: { field: first_due_field; };
205
- moves: [{ amount: repayment1Amount; from: beneficiary; to: payer; key: transfer; operation: create; }];
206
- steps: [];
207
- }
208
- for repayment in sub(count, 1) {
209
- let(index): add(repayment, 1);
210
- let(repayment_field): concat("repayment", index, "Amount");
211
- action collect_repayment_[index] {
212
- summary: concat("Collect repayment ", index, " of ", count);
213
- due: {
214
- field: first_due_field;
215
- offset: scale_duration(every, sub(index, 1));
216
- };
217
- moves: [{ amount: repayment_field; from: beneficiary; to: payer; key: transfer; operation: create; }];
217
+ for i in count {
218
+ let(repayment_field): concat("repayment", i, "Amount");
219
+ action collect_repayment_[i] {
220
+ agent_description: "Collect repayment {i} once on its stored due anchor, returning principal to the funder and any separately routed profit to its recipient.";
221
+ summary: "Collect repayment {i} of {count}";
222
+ when(dated) { due: { field: concat("repayment", i, "DueAt"); }; }
223
+ when_not(dated) { due: { field: first_due_field; offset: scale_duration(every, sub(i, 1)); }; }
224
+ when_not(profit_to) { moves: [{ amount: repayment_field; from: repayment_role; to: payer; key: transfer; }]; }
225
+ when(profit_to) {
226
+ moves: [
227
+ { amount: concat("repayment", i, "Principal"); from: repayment_role; to: payer; key: principal; },
228
+ { amount: concat("repayment", i, "Profit"); from: repayment_role; to: profit; key: profit; }
229
+ ];
230
+ }
218
231
  steps: [];
219
232
  }
220
233
  }
@@ -45,16 +45,38 @@ module std.money_flows.cancellable_booking
45
45
  // offer_life: "PT30M"
46
46
  // }
47
47
  // ```
48
+ // ### Finite cancellation bands
49
+ // Supply cancel_bands to select deposit/balance custody instead of quoted cancellation.
50
+ // Each band has name, refund (full, balance or none), starts_at after the first band,
51
+ // and supplier_bps when money is retained. The unpaid_band index selects a retained
52
+ // deposit policy. Omit it to return the deposit; a full-refund band also returns it. price_value and supplier_cost fix the price; deposit_bps derives
53
+ // the deposit. balance_before and confirmation_window are positive fixed durations
54
+ // relative to departure. tax_bps applies to margin after the supplier share.
55
+ // authority_type and supplier_role bind release to a consumed confirmation decision.
56
+ // acceptance optionally supplies an aggregate gate. All actions are private until
57
+ // expose selects them. Ordered band starts and deadlines prevent a late sweep from
58
+ // extending an earlier band's cancellation rights. Full timeout refund returns price.
59
+ // Seven derivations suffice for three bands; larger finite policies use up to the
60
+ // UDL bound of 64 derived amounts. shift_date(date, duration, "before") subtracts a
61
+ // fixed duration; omitting the direction adds it. Invalid dates or durations refuse.
62
+ //
48
63
  export instrument cancellable_booking<C>(
49
- guest: party,
50
- host: party,
51
- amount: money<C>,
52
- starts_at: date,
53
- late_penalty_bps: integer,
54
- late_within: text,
55
- early_penalty_bps: integer,
56
- offer_life: text,
64
+ guest: optional<party>,
65
+ host: optional<party>,
66
+ amount: optional<money<C>>,
67
+ starts_at: optional<date>,
68
+ late_penalty_bps: optional<integer>,
69
+ late_within: optional<text>,
70
+ early_penalty_bps: optional<integer>,
71
+ offer_life: optional<text>,
72
+ cancel_bands: optional<list<block>>,
73
+ price_value: optional<text>, supplier_cost: optional<text>, departure: optional<text>,
74
+ balance_before: optional<text>, confirmation_window: optional<text>, unpaid_band: optional<integer>,
75
+ acceptance: optional<block>, deposit_bps: optional<integer>, tax_bps: optional<integer>,
76
+ authority_type: optional<text>, supplier_role: optional<text>,
77
+
57
78
  ) {
79
+ when_not(cancel_bands) {
58
80
  let(guest_account_field): camel(concat(guest, "_account_id"));
59
81
  let(host_account_field): camel(concat(host, "_account_id"));
60
82
  let(reference_filter_key): "x-hyperscale-reference-filter";
@@ -130,4 +152,249 @@ export instrument cancellable_booking<C>(
130
152
  moves: [{ key: penalty; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.cancellationPenaltyAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", host_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.bookingHoldingAccountId; }; }; capture: { penaltyTransferId: transferId; }; }];
131
153
  steps: [];
132
154
  }
155
+ }
156
+ when(cancel_bands) {
157
+ let(band_count): len(cancel_bands);
158
+ for band in cancel_bands {
159
+ let(refund): get(band, "refund");
160
+ let(not_full): if_eq(refund, "full", false, true);
161
+ let(not_balance): if_eq(refund, "balance", false, true);
162
+ let(not_none): if_eq(refund, "none", false, true);
163
+ when(not_full) { when(not_balance) { when(not_none) {
164
+ unsupported { code: HSX1407; message: "Unknown cancellation refund policy"; fix: "Choose full, balance or none"; }
165
+ } } }
166
+ }
167
+ let(deposit_states): names("deposit_", len(cancel_bands), "");
168
+ let(full_states): names("full_", len(cancel_bands), "");
169
+ fields {
170
+ bookingKey { type: text; min_length: 1; }
171
+ signedTermsReference { type: text; min_length: 1; }
172
+ currency { type: text; const: C; }
173
+ customerAccountId: account<C>;
174
+ supplierAccountId: account<C>;
175
+ operatorAccountId: account<C>;
176
+ taxAccountId: account<C>;
177
+ authorityId { type: ref<authority_type>; optional: true; }
178
+ supplierRole { type: text; const: supplier_role; }
179
+ travellerCount { type: integer; minimum: 1; maximum: 20; }
180
+ departureAt { type: date; const: departure; }
181
+ balanceDueAt { type: date; const: shift_date(departure, balance_before, "before"); }
182
+ confirmationDeadline { type: date; const: shift_date(departure, confirmation_window); }
183
+ price { type: money<C>; const: price_value; }
184
+ supplierCost { type: money<C>; const: supplier_cost; }
185
+ depositAmount { type: money<C>; optional: true; }
186
+ balanceAmount: money<C>;
187
+ margin: money<C>;
188
+ operatorNet: money<C>;
189
+ marginVat { type: money<C>; optional: true; }
190
+ for i in len(cancel_bands) {
191
+ let(band): at(cancel_bands, i);
192
+ let(refund_mode): get(band, "refund");
193
+ let(full_refund): if_eq(refund_mode, "full", true, false);
194
+ let(balance_refund): if_eq(refund_mode, "balance", true, false);
195
+ let(retains): if_eq(refund_mode, "full", false, true);
196
+ let(later): if_eq(i, 1, false, true);
197
+ when(later) { band[i]Start { type: date; const: get(band, "starts_at"); } }
198
+
199
+ when(retains) {
200
+ band[i]Supplier { type: money<C>; optional: true; }
201
+ let(portions): if_eq(refund_mode, "balance", ["deposit"], ["deposit", "full"]);
202
+ for paid in portions {
203
+ let(is_full): if_eq(paid, "full", true, false);
204
+ let(portion): if_eq(refund_mode, "balance", "deposit", paid);
205
+ let(margin_field): concat("band", i, title(portion), "Margin");
206
+ let(net_field): concat("band", i, title(portion), "Net");
207
+ let(vat_field): concat("band", i, title(portion), "Vat");
208
+ [margin_field]: money<C>;
209
+ [net_field]: money<C>;
210
+ [vat_field] { type: money<C>; optional: true; }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ parties { payer: customerAccountId; beneficiary: supplierAccountId; fee: operatorAccountId; tax: taxAccountId; }
216
+ distinct_parties: true;
217
+ computes derived: { field: depositAmount; source_field: price; rule: { kind: percentage_of; bps: deposit_bps; }; rounding: floor; };
218
+ computes derived: { field: marginVat; source_field: margin; rule: { kind: percentage_of; bps: tax_bps; }; rounding: floor; };
219
+ for i in len(cancel_bands) {
220
+ let(band): at(cancel_bands, i);
221
+ let(refund_mode): get(band, "refund");
222
+ let(balance_refund): if_eq(refund_mode, "balance", true, false);
223
+ when(balance_refund) {
224
+ partitions: { total_field: price; piece_fields: [balanceAmount, concat("band", i, "Supplier"), concat("band", i, "DepositNet"), concat("band", i, "DepositVat")]; };
225
+ }
226
+ }
227
+ partitions: { total_field: price; piece_fields: [supplierCost, operatorNet, marginVat]; };
228
+ partitions: { total_field: price; piece_fields: [depositAmount, balanceAmount]; };
229
+ partitions: { total_field: price; piece_fields: [supplierCost, margin]; };
230
+ partitions: { total_field: margin; piece_fields: [operatorNet, marginVat]; };
231
+ date order: { before_field: balanceDueAt; after_field: departureAt; operator: "<"; };
232
+ date order: { before_field: departureAt; after_field: confirmationDeadline; operator: "<"; };
233
+ for i in len(cancel_bands) {
234
+ let(band): at(cancel_bands, i);
235
+ let(refund_mode): get(band, "refund");
236
+ let(full_refund): if_eq(refund_mode, "full", true, false);
237
+ let(balance_refund): if_eq(refund_mode, "balance", true, false);
238
+ let(retains): if_eq(refund_mode, "full", false, true);
239
+ let(later): if_eq(i, 1, false, true);
240
+ let(band_start): concat("band", i, "Start");
241
+ when(later) {
242
+ date order: { before_field: band_start; after_field: departureAt; operator: "<"; };
243
+ let(after_second): if_eq(i, 2, false, true);
244
+ when(after_second) { date order: { before_field: concat("band", sub(i, 1), "Start"); after_field: band_start; operator: "<"; }; }
245
+ }
246
+
247
+ when(retains) {
248
+ let(supplier_field): concat("band", i, "Supplier");
249
+ computes derived: { field: supplier_field; source_field: supplierCost; rule: { kind: percentage_of; bps: get(band, "supplier_bps"); }; rounding: floor; };
250
+ let(portions): if_eq(refund_mode, "balance", ["deposit"], ["deposit", "full"]);
251
+ for paid in portions {
252
+ let(is_full): if_eq(paid, "full", true, false);
253
+ let(portion): if_eq(refund_mode, "balance", "deposit", paid);
254
+ let(margin_field): concat("band", i, title(portion), "Margin");
255
+ let(net_field): concat("band", i, title(portion), "Net");
256
+ let(vat_field): concat("band", i, title(portion), "Vat");
257
+ let(retained): if_eq(paid, "deposit", "depositAmount", if_eq(refund_mode, "balance", "depositAmount", "price"));
258
+ computes derived: { field: vat_field; source_field: margin_field; rule: { kind: percentage_of; bps: tax_bps; }; rounding: floor; };
259
+ partitions: { total_field: retained; piece_fields: [supplier_field, net_field, vat_field]; };
260
+ partitions: { total_field: retained; piece_fields: [supplier_field, margin_field]; };
261
+ partitions: { total_field: margin_field; piece_fields: [net_field, vat_field]; };
262
+ }
263
+ }
264
+ }
265
+ agent_description: "Hold deposit and balance in one booking account. Apply a finite dated cancellation policy and release the supplier, operator and tax partition only after the named authority confirms. Refund the entire price when confirmation times out.";
266
+ lifecycle {
267
+ states created awaiting_confirmation confirmed released refunded canceled expired;
268
+ let(states_key): "states";
269
+ [states_key]: concat_lists(deposit_states, full_states);
270
+ initial created;
271
+ on deposit: created -> deposit_1;
272
+ on expire: created -> expired;
273
+ for i in len(cancel_bands) {
274
+ let(band): at(cancel_bands, i);
275
+ let(refund_mode): get(band, "refund");
276
+ let(full_refund): if_eq(refund_mode, "full", true, false);
277
+ let(balance_refund): if_eq(refund_mode, "balance", true, false);
278
+ let(retains): if_eq(refund_mode, "full", false, true);
279
+ let(band_name): get(band, "name");
280
+ let(deposit_state): concat("deposit_", i);
281
+ let(full_state): concat("full_", i);
282
+ let(balance_action): concat("balance_", band_name);
283
+ let(cancel_deposit): concat("cancel_deposit_", band_name);
284
+ let(cancel_full): concat("cancel_full_", band_name);
285
+ on [balance_action]: deposit_state -> full_state;
286
+ on [cancel_deposit]: deposit_state -> canceled;
287
+ on [cancel_full]: full_state -> canceled;
288
+ let(later): if_eq(i, 1, false, true);
289
+ when(later) {
290
+ let(previous_deposit): names("deposit_", sub(i, 1), "");
291
+ let(previous_full): names("full_", sub(i, 1), "");
292
+ let(open_deposit): concat("open_deposit_", band_name);
293
+ let(open_full): concat("open_full_", band_name);
294
+ on [open_deposit]: previous_deposit -> deposit_state;
295
+ on [open_full]: previous_full -> full_state;
296
+ }
297
+ }
298
+ on cancel_unpaid: deposit_states -> canceled;
299
+ on await_confirmation: full_states -> awaiting_confirmation;
300
+ on confirm: awaiting_confirmation -> confirmed;
301
+ on release: confirmed -> released;
302
+ on refund_unconfirmed: full_states | awaiting_confirmation -> refunded;
303
+ }
304
+ caller_parked_states: { confirmed: "Release pays the confirmed supplier and frozen partition."; };
305
+ action create {
306
+ public: none;
307
+ agent_description: "Freeze the booking terms and provision its holding account.";
308
+ unique: { namespace: instrument; by_fields: [bookingKey]; };
309
+ deadline: { field: balanceDueAt; };
310
+ steps: { operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; "owner.id": { from: instance; path: productId; }; "owner.type": { from: const; value: product; }; role: { from: const; value: product_escrow; }; }; capture: { heldAccountId: accountId; }; };
311
+ }
312
+ action deposit {
313
+ public: none;
314
+ agent_description: "Collect the derived deposit after the acceptance requirements pass.";
315
+ when(acceptance) { requires aggregate: acceptance; }
316
+ deadline: { field: balanceDueAt; }; port: { allowedParties: [payer]; };
317
+ moves: { key: deposit; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "depositAmount"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "fields.customerAccountId"; }; destinationAccountId: { from: instance; path: "refs.heldAccountId"; }; }; };
318
+ steps: [];
319
+ }
320
+ action expire { public: none; agent_description: "Close an unfunded booking."; due: { field: balanceDueAt; }; requires_drained: { path: refs.heldAccountId; }; steps: []; }
321
+ for i in len(cancel_bands) {
322
+ let(band): at(cancel_bands, i);
323
+ let(refund_mode): get(band, "refund");
324
+ let(full_refund): if_eq(refund_mode, "full", true, false);
325
+ let(balance_refund): if_eq(refund_mode, "balance", true, false);
326
+ let(retains): if_eq(refund_mode, "full", false, true);
327
+ let(band_name): get(band, "name");
328
+ let(balance_action): concat("balance_", band_name);
329
+ let(next_index): add(i, 1);
330
+ let(next_field): concat("band", next_index, "Start");
331
+ let(band_end): if_eq(i, band_count, "departureAt", next_field);
332
+ let(later): if_eq(i, 1, false, true);
333
+ action [balance_action] {
334
+ public: none; agent_description: "Collect the remaining balance once before its deadline.";
335
+ deadline: { field: balanceDueAt; }; port: { allowedParties: [payer]; };
336
+ moves: { key: balance; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "balanceAmount"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "fields.customerAccountId"; }; destinationAccountId: { from: instance; path: "refs.heldAccountId"; }; }; };
337
+ steps: [];
338
+ }
339
+ for paid in ["deposit", "full"] {
340
+ let(is_full): if_eq(paid, "full", true, false);
341
+ when(later) {
342
+ let(open_action): concat("open_", paid, "_", band_name);
343
+ action [open_action] { public: none; agent_description: "Open the next dated cancellation band."; due: { field: concat("band", i, "Start"); }; steps: []; }
344
+ }
345
+ let(portion): if_eq(refund_mode, "balance", "deposit", paid);
346
+ let(cancel_action): concat("cancel_", paid, "_", band_name);
347
+ action [cancel_action] {
348
+ public: none; agent_description: "Settle this paid amount under the active cancellation band.";
349
+ deadline: { field: band_end; }; port: { allowedParties: [payer]; };
350
+ when(full_refund) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", if_eq(paid, "deposit", "depositAmount", "price")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; }
351
+ when(retains) {
352
+ when(is_full) { when(balance_refund) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "balanceAmount"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; } }
353
+ moves: { key: supplier; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", i, "Supplier")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.supplierAccountId"; }; }; };
354
+ moves: { key: operator; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", i, title(portion), "Net")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.operatorAccountId"; }; }; };
355
+ moves: { key: vat; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", i, title(portion), "Vat")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.taxAccountId"; }; }; };
356
+ }
357
+ steps: [];
358
+ }
359
+ }
360
+ }
361
+ action cancel_unpaid {
362
+ public: none; agent_description: "Settle the deposit under the declared unpaid-balance band and end the remaining obligation.";
363
+ due: { field: balanceDueAt; };
364
+ when_not(unpaid_band) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: "fields.depositAmount"; }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; }
365
+ when(unpaid_band) {
366
+ let(unpaid_policy): at(cancel_bands, unpaid_band);
367
+ let(unpaid_full_refund): if_eq(get(unpaid_policy, "refund"), "full", true, false);
368
+ when(unpaid_full_refund) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: "fields.depositAmount"; }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; }
369
+ when_not(unpaid_full_refund) {
370
+ moves: { key: supplier; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", unpaid_band, "Supplier")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.supplierAccountId"; }; }; };
371
+ moves: { key: operator; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", unpaid_band, "DepositNet")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.operatorAccountId"; }; }; };
372
+ moves: { key: vat; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", unpaid_band, "DepositVat")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.taxAccountId"; }; }; };
373
+ }
374
+ }
375
+ steps: [];
376
+ }
377
+ action await_confirmation { public: none; agent_description: "Open confirmation at the booked departure."; due: { field: departureAt; }; steps: []; }
378
+ action confirm {
379
+ public: none; agent_description: "Consume the named supplier's approved decision for this booking before timeout.";
380
+ deadline: { field: confirmationDeadline; };
381
+ input: { type: object; additionalProperties: false; properties: { authorityId: { type: string; pattern: concat("^", prefix(authority_type), "_(sandbox|live)_[a-z0-9]{8,64}$"); }; }; required: [authorityId]; };
382
+ updates: [authorityId];
383
+ requires refs: { field: authorityId; statuses: [approved]; match: { instrumentInstanceId: fields.requestInstanceId; "fields.supplierAccountId": fields.decidingActorAccountId; "fields.supplierRole": fields.decidingRole; }; attests: { instrument: fields.requestInstrumentId; party: decider; action: fields.requestAction; digest: fields.inputDigest; role: fields.decidingRole; expires_at: fields.expiresAt; consume: consume; }; };
384
+ steps: [];
385
+ }
386
+ action release {
387
+ public: none; agent_description: "Pay supplier cost, operator net and VAT after confirmation.";
388
+ moves: { key: supplier; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "supplierCost"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.supplierAccountId"; }; }; };
389
+ moves: { key: operator; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "operatorNet"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.operatorAccountId"; }; }; };
390
+ moves: { key: vat; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "marginVat"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.taxAccountId"; }; }; };
391
+ steps: [];
392
+ }
393
+ action refund_unconfirmed {
394
+ public: none; agent_description: "Return the entire booking price when supplier confirmation times out.";
395
+ due: { field: confirmationDeadline; };
396
+ moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "price"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; };
397
+ steps: [];
398
+ }
399
+ }
133
400
  }
@@ -21,10 +21,6 @@ module std.money_flows.captured_payment
21
21
  // - `reserve_until`: Expiration date for the authorization hold.
22
22
  // - `correction`: Condition port allowing post-settlement amount corrections.
23
23
  // - `external_reversal`: Condition port allowing external processor chargebacks/reversals.
24
- // - `capture_mode`: Text specifying capture behavior (`"slice"` or `"full"`).
25
- // - `correction_mode`: Text specifying correction behavior.
26
- // - `negative_position`: Text policy when correction creates an overdraft.
27
- // - `timeout`: Timeout duration string for pending authorizations.
28
24
  // - `derived_amount`: Optional block declaring percentage fee calculation.
29
25
  //
30
26
  // ### Decision ports
@@ -44,10 +40,6 @@ module std.money_flows.captured_payment
44
40
  // reserve_until: reserveUntil
45
41
  // correction: port correct_capture
46
42
  // external_reversal: port reverse_capture within P14D
47
- // capture_mode: partial_then_full
48
- // correction_mode: full_only
49
- // negative_position: reject
50
- // timeout: reject
51
43
  // }
52
44
  // port correct_capture { allowed: [payee] }
53
45
  // port reverse_capture {
@@ -55,7 +47,7 @@ module std.money_flows.captured_payment
55
47
  // shape: { externalReference: text }
56
48
  // }
57
49
  // ```
58
- export instrument captured_payment<C>(payer: party, payee: party, amount: money<C>, reserve_until: date, correction: condition, external_reversal: condition, capture_mode: text, correction_mode: text, negative_position: text, timeout: text, derived_amount: optional<block>) {
50
+ export instrument captured_payment<C>(payer: party, payee: party, amount: money<C>, reserve_until: date, correction: condition, external_reversal: condition, derived_amount: optional<block>) {
59
51
  let(correction_name): correction;
60
52
  let(reversal_name): external_reversal;
61
53
  let(reversal_window): external_reversal_within;
@@ -20,8 +20,6 @@ module std.money_flows.conditional_disbursement
20
20
  // - `cap`: Maximum total amount that can be disbursed across all child approvals in minor units of currency `C`.
21
21
  // - `amount`: Binding name for child approval amount values.
22
22
  // - `decision`: Port conditioning approval, requiring evidence reference.
23
- // - `reopen_policy`: Policy for reopening closed disbursements (`refuse`).
24
- // - `recovery_policy`: Policy for recovering overpayments (`separate_transfer`).
25
23
  //
26
24
  // ### Decision ports
27
25
  // - `decision`: External port providing decision evidence required to approve child disbursement amounts.
@@ -38,8 +36,6 @@ module std.money_flows.conditional_disbursement
38
36
  // cap: policyLimit: money(SAR)
39
37
  // amount: approvedAmount: money(SAR)
40
38
  // decision: port approve_claim
41
- // reopen_policy: refuse
42
- // recovery_policy: separate_transfer
43
39
  // }
44
40
  // port approve_claim {
45
41
  // allowed: [source]
@@ -52,8 +48,6 @@ export instrument conditional_disbursement<C>(
52
48
  cap: money<C>,
53
49
  amount: money<C>,
54
50
  decision: condition,
55
- reopen_policy: text,
56
- recovery_policy: text,
57
51
  ) {
58
52
  let(child_id): concat(instrument, "_approved_amount");
59
53
  let(child_amount): "amount";
@@ -20,9 +20,6 @@ module std.money_flows.credit_facility
20
20
  // - `limit`: Total revolving borrowing limit in minor units of currency `C`.
21
21
  // - `expires_at`: Expiration date after which new draws cannot be opened.
22
22
  // - `obligation`: Reference to a scheduled obligation instrument handling draw repayments.
23
- // - `availability_policy`: Facility capacity availability policy (`revolving`).
24
- // - `expiry_policy`: Policy on facility expiration (`freeze_draws`).
25
- // - `close_policy`: Policy on facility closure (`no_open_draws`).
26
23
  //
27
24
  // ### Decision ports
28
25
  // None on the facility itself. Mandates and decision ports are declared on the linked `obligation` instrument.
@@ -52,9 +49,6 @@ module std.money_flows.credit_facility
52
49
  // limit: facilityLimit: money(SAR)
53
50
  // expires_at: expiresAt
54
51
  // obligation: repayment.obligation
55
- // availability_policy: revolving
56
- // expiry_policy: freeze_draws
57
- // close_policy: no_open_draws
58
52
  // }
59
53
  // ```
60
54
  export instrument credit_facility<C>(
@@ -64,9 +58,6 @@ export instrument credit_facility<C>(
64
58
  limit: money<C>,
65
59
  expires_at: date,
66
60
  obligation: ref,
67
- availability_policy: text,
68
- expiry_policy: text,
69
- close_policy: text,
70
61
  ) {
71
62
  let(draw_id): concat(instrument, "_draw");
72
63
  let(parent_field): camel(concat(instrument, "_id"));