@hyperscale0/hsx 1.0.0-alpha.6 → 1.0.0-rc.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 (234) hide show
  1. package/CHANGELOG.md +48 -10
  2. package/LICENSING.md +3 -3
  3. package/README.md +37 -149
  4. package/TRADEMARKS.md +4 -4
  5. package/dist/src/ast.d.ts +129 -10
  6. package/dist/src/ast.d.ts.map +1 -1
  7. package/dist/src/cli.d.ts +2 -1
  8. package/dist/src/cli.d.ts.map +1 -1
  9. package/dist/src/cli.js +148 -12
  10. package/dist/src/cli.js.map +1 -1
  11. package/dist/src/compile.d.ts +41 -4
  12. package/dist/src/compile.d.ts.map +1 -1
  13. package/dist/src/compile.js +82 -15
  14. package/dist/src/compile.js.map +1 -1
  15. package/dist/src/cost.d.ts +78 -0
  16. package/dist/src/cost.d.ts.map +1 -0
  17. package/dist/src/cost.js +369 -0
  18. package/dist/src/cost.js.map +1 -0
  19. package/dist/src/diagnostics.d.ts +11 -0
  20. package/dist/src/diagnostics.d.ts.map +1 -0
  21. package/dist/src/diagnostics.js +420 -0
  22. package/dist/src/diagnostics.js.map +1 -0
  23. package/dist/src/emit.d.ts +35 -0
  24. package/dist/src/emit.d.ts.map +1 -0
  25. package/dist/src/emit.js +359 -0
  26. package/dist/src/emit.js.map +1 -0
  27. package/dist/src/entry-overrides.d.ts +17 -3
  28. package/dist/src/entry-overrides.d.ts.map +1 -1
  29. package/dist/src/entry-overrides.js +180 -35
  30. package/dist/src/entry-overrides.js.map +1 -1
  31. package/dist/src/format.d.ts +10 -0
  32. package/dist/src/format.d.ts.map +1 -0
  33. package/dist/src/format.js +183 -0
  34. package/dist/src/format.js.map +1 -0
  35. package/dist/src/index.d.ts +10 -4
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +8 -3
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/ir.d.ts +66 -0
  40. package/dist/src/ir.d.ts.map +1 -0
  41. package/dist/src/ir.js +15 -0
  42. package/dist/src/ir.js.map +1 -0
  43. package/dist/src/lex.d.ts +9 -1
  44. package/dist/src/lex.d.ts.map +1 -1
  45. package/dist/src/lex.js +50 -4
  46. package/dist/src/lex.js.map +1 -1
  47. package/dist/src/limits.d.ts +2 -3
  48. package/dist/src/limits.d.ts.map +1 -1
  49. package/dist/src/limits.js +2 -3
  50. package/dist/src/limits.js.map +1 -1
  51. package/dist/src/modules.d.ts +36 -0
  52. package/dist/src/modules.d.ts.map +1 -0
  53. package/dist/src/modules.js +372 -0
  54. package/dist/src/modules.js.map +1 -0
  55. package/dist/src/parse.d.ts.map +1 -1
  56. package/dist/src/parse.js +590 -17
  57. package/dist/src/parse.js.map +1 -1
  58. package/dist/src/typecheck.d.ts +8 -0
  59. package/dist/src/typecheck.d.ts.map +1 -0
  60. package/dist/src/typecheck.js +2958 -0
  61. package/dist/src/typecheck.js.map +1 -0
  62. package/dist/src/version.d.ts +4 -15
  63. package/dist/src/version.d.ts.map +1 -1
  64. package/dist/src/version.js +4 -15
  65. package/dist/src/version.js.map +1 -1
  66. package/docs/README.md +23 -0
  67. package/docs/guide/01-first-program.md +24 -0
  68. package/docs/guide/02-money.md +25 -0
  69. package/docs/guide/03-instruments.md +22 -0
  70. package/docs/guide/04-lifecycles.md +25 -0
  71. package/docs/guide/05-fees-and-splits.md +45 -0
  72. package/docs/guide/06-schedules.md +22 -0
  73. package/docs/guide/07-composition.md +46 -0
  74. package/docs/guide/08-writing-a-module.md +26 -0
  75. package/docs/guide/09-cost.md +24 -0
  76. package/docs/guide/10-diagnostics.md +27 -0
  77. package/docs/llms-full.txt +1738 -0
  78. package/docs/llms.txt +45 -0
  79. package/docs/reference/cli.md +33 -0
  80. package/docs/reference/diagnostics.md +619 -0
  81. package/docs/reference/grammar.md +39 -0
  82. package/docs/reference/std/advance.md +35 -0
  83. package/docs/reference/std/cancellable_booking.md +35 -0
  84. package/docs/reference/std/captured_payment.md +43 -0
  85. package/docs/reference/std/conditional_disbursement.md +33 -0
  86. package/docs/reference/std/credit_facility.md +35 -0
  87. package/docs/reference/std/held_payment.md +64 -0
  88. package/docs/reference/std/instant_transfer.md +30 -0
  89. package/docs/reference/std/metered.md +27 -0
  90. package/docs/reference/std/pooled_split.md +26 -0
  91. package/docs/reference/std/premium_forward.md +39 -0
  92. package/docs/reference/std/reconciled_payout.md +33 -0
  93. package/docs/reference/std/recurring_collection.md +25 -0
  94. package/docs/reference/std/rotating_pool.md +42 -0
  95. package/docs/reference/std/scheduled.md +51 -0
  96. package/docs/reference/std/security_deposit.md +46 -0
  97. package/docs/reference/std/settlement_batch.md +42 -0
  98. package/docs/reference/std/swap.md +46 -0
  99. package/docs/reference/std/threshold_pool.md +47 -0
  100. package/docs/reference/std/weighted_distribution.md +39 -0
  101. package/docs/reference/types.md +20 -0
  102. package/docs/reference/udl-output.md +14 -0
  103. package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
  104. package/docs/sessions/2026-09-02-two-instruments.md +41 -0
  105. package/examples/01-first-program/README.md +45 -0
  106. package/examples/01-first-program/tip-jar.hsx +17 -0
  107. package/examples/02-imports-and-archetypes/README.md +99 -0
  108. package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
  109. package/examples/03-diagnostics/README.md +68 -0
  110. package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
  111. package/examples/03-diagnostics/corner-shop.hsx +19 -0
  112. package/examples/04-complete-product/README.md +87 -0
  113. package/examples/04-complete-product/study-hall.hsx +67 -0
  114. package/examples/05-watch-club/README.md +9 -0
  115. package/examples/05-watch-club/watch-club.hsx +124 -0
  116. package/examples/README.md +18 -0
  117. package/examples/advance/README.md +3 -0
  118. package/examples/advance/advance.hsx +13 -0
  119. package/examples/advance/advance.udl +339 -0
  120. package/examples/cancellable_booking/README.md +3 -0
  121. package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
  122. package/examples/cancellable_booking/cancellable_booking.udl +391 -0
  123. package/examples/captured_payment/README.md +3 -0
  124. package/examples/captured_payment/captured_payment.hsx +21 -0
  125. package/examples/captured_payment/captured_payment.udl +677 -0
  126. package/examples/conditional_disbursement/README.md +3 -0
  127. package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
  128. package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
  129. package/examples/cost-table.json +386 -0
  130. package/examples/credit_facility/README.md +3 -0
  131. package/examples/credit_facility/credit_facility.hsx +27 -0
  132. package/examples/credit_facility/credit_facility.udl +1373 -0
  133. package/examples/held_payment/README.md +3 -0
  134. package/examples/held_payment/held_payment.hsx +13 -0
  135. package/examples/held_payment/held_payment.udl +507 -0
  136. package/examples/instant_transfer/README.md +3 -0
  137. package/examples/instant_transfer/instant_transfer.hsx +16 -0
  138. package/examples/instant_transfer/instant_transfer.udl +364 -0
  139. package/examples/metered/README.md +3 -0
  140. package/examples/metered/metered.hsx +13 -0
  141. package/examples/metered/metered.udl +247 -0
  142. package/examples/pooled_split/README.md +3 -0
  143. package/examples/pooled_split/pooled_split.hsx +15 -0
  144. package/examples/pooled_split/pooled_split.udl +425 -0
  145. package/examples/premium_forward/README.md +3 -0
  146. package/examples/premium_forward/premium_forward.hsx +19 -0
  147. package/examples/premium_forward/premium_forward.udl +594 -0
  148. package/examples/reconciled_payout/README.md +3 -0
  149. package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
  150. package/examples/reconciled_payout/reconciled_payout.udl +303 -0
  151. package/examples/recurring_collection/README.md +3 -0
  152. package/examples/recurring_collection/recurring_collection.hsx +21 -0
  153. package/examples/recurring_collection/recurring_collection.udl +1130 -0
  154. package/examples/rotating_pool/README.md +3 -0
  155. package/examples/rotating_pool/rotating_pool.hsx +18 -0
  156. package/examples/rotating_pool/rotating_pool.udl +4352 -0
  157. package/examples/scheduled/README.md +3 -0
  158. package/examples/scheduled/scheduled.hsx +12 -0
  159. package/examples/scheduled/scheduled.udl +335 -0
  160. package/examples/security_deposit/README.md +3 -0
  161. package/examples/security_deposit/security_deposit.hsx +21 -0
  162. package/examples/security_deposit/security_deposit.udl +293 -0
  163. package/examples/settlement_batch/README.md +3 -0
  164. package/examples/settlement_batch/settlement_batch.hsx +19 -0
  165. package/examples/settlement_batch/settlement_batch.udl +753 -0
  166. package/examples/swap/README.md +3 -0
  167. package/examples/swap/swap.hsx +19 -0
  168. package/examples/swap/swap.udl +876 -0
  169. package/examples/threshold_pool/README.md +3 -0
  170. package/examples/threshold_pool/threshold_pool.hsx +16 -0
  171. package/examples/threshold_pool/threshold_pool.udl +667 -0
  172. package/examples/weighted_distribution/README.md +3 -0
  173. package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
  174. package/examples/weighted_distribution/weighted_distribution.udl +337 -0
  175. package/package.json +19 -6
  176. package/skills/hsx/SKILL.md +483 -0
  177. package/src/ast.ts +163 -9
  178. package/src/cli.ts +164 -12
  179. package/src/compile.ts +160 -20
  180. package/src/cost.ts +579 -0
  181. package/src/diagnostics.ts +442 -0
  182. package/src/emit.ts +447 -0
  183. package/src/entry-overrides.ts +263 -48
  184. package/src/format.ts +261 -0
  185. package/src/index.ts +32 -4
  186. package/src/ir.ts +99 -0
  187. package/src/lex.ts +59 -4
  188. package/src/limits.ts +2 -3
  189. package/src/modules.ts +444 -0
  190. package/src/parse.ts +655 -21
  191. package/src/typecheck.ts +3876 -0
  192. package/src/version.ts +4 -16
  193. package/std/SEMANTICS.md +130 -0
  194. package/std/settlements/advance.hsx +177 -0
  195. package/std/settlements/cancellable_booking.hsx +87 -0
  196. package/std/settlements/captured_payment.hsx +144 -0
  197. package/std/settlements/conditional_disbursement.hsx +185 -0
  198. package/std/settlements/credit_facility.hsx +167 -0
  199. package/std/settlements/held_payment.hsx +578 -0
  200. package/std/settlements/index.hsx +3 -0
  201. package/std/settlements/instant_transfer.hsx +190 -0
  202. package/std/settlements/metered.hsx +67 -0
  203. package/std/settlements/pooled_split.hsx +68 -0
  204. package/std/settlements/premium_forward.hsx +257 -0
  205. package/std/settlements/reconciled_payout.hsx +77 -0
  206. package/std/settlements/recurring_collection.hsx +27 -0
  207. package/std/settlements/rotating_pool.hsx +252 -0
  208. package/std/settlements/scheduled.hsx +641 -0
  209. package/std/settlements/security_deposit.hsx +166 -0
  210. package/std/settlements/settlement_batch.hsx +122 -0
  211. package/std/settlements/swap.hsx +1114 -0
  212. package/std/settlements/threshold_pool.hsx +221 -0
  213. package/std/settlements/weighted_distribution.hsx +141 -0
  214. package/dist/src/archetypes.d.ts +0 -17
  215. package/dist/src/archetypes.d.ts.map +0 -1
  216. package/dist/src/archetypes.js +0 -327
  217. package/dist/src/archetypes.js.map +0 -1
  218. package/dist/src/check.d.ts +0 -12
  219. package/dist/src/check.d.ts.map +0 -1
  220. package/dist/src/check.js +0 -1910
  221. package/dist/src/check.js.map +0 -1
  222. package/dist/src/lower.d.ts +0 -146
  223. package/dist/src/lower.d.ts.map +0 -1
  224. package/dist/src/lower.js +0 -4570
  225. package/dist/src/lower.js.map +0 -1
  226. package/dist/src/model.d.ts +0 -467
  227. package/dist/src/model.d.ts.map +0 -1
  228. package/dist/src/model.js +0 -15
  229. package/dist/src/model.js.map +0 -1
  230. package/spec/hsx-ir.schema.json +0 -920
  231. package/src/archetypes.ts +0 -340
  232. package/src/check.ts +0 -2938
  233. package/src/lower.ts +0 -5416
  234. package/src/model.ts +0 -519
@@ -0,0 +1,77 @@
1
+ module std.settlements.reconciled_payout
2
+
3
+ export instrument reconciled_payout<C>(
4
+ payer: party,
5
+ beneficiary: party,
6
+ amount: money<C>,
7
+ beneficiary_ref: text,
8
+ settle_by: date,
9
+ matched_within: integer,
10
+ matched_ceiling: integer,
11
+ ) {
12
+ let(payer_account_field): camel(concat(payer, "_account_id"));
13
+ let(beneficiary_account_field): camel(concat(beneficiary, "_account_id"));
14
+ let(reference_filter_key): "x-hyperscale-reference-filter";
15
+ fields {
16
+ amount { type: money<C>; description: concat("Amount instructed to the ", words(beneficiary), " in minor units"); }
17
+ currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
18
+ settle_by { type: date; description: "Instant the expectation stops waiting and the unmatched amount becomes a break"; }
19
+ beneficiary_ref { type: text; description: "Beneficiary ID for the payout instruction"; pattern: "^ben_(sandbox|live)_[a-z0-9]{8,64}$"; }
20
+ [beneficiary_account_field] { type: account<C>; description: concat("The ", words(beneficiary), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
21
+ [payer_account_field] { type: account<C>; description: concat("The ", words(payer), " account the payout leaves"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
22
+ }
23
+ lifecycle {
24
+ states created instructed settled;
25
+ initial created;
26
+ on instruct: created -> instructed;
27
+ on settle: instructed -> settled;
28
+ }
29
+ parties { beneficiary: beneficiary_account_field; payer: payer_account_field; }
30
+ required: [amount, beneficiary_account_field, beneficiary_ref, currency, payer_account_field, settle_by];
31
+ title: sentence(instrument);
32
+ agent_description: concat("Reach for reconciled payout when a payment to the ", words(beneficiary), " is not finished until the bank confirms the debit. The instruction goes out, the expectation waits for a matching statement line, and anything still unmatched at the settle date becomes a break row instead of disappearing. Pick instant transfer when no bank confirmation is involved.");
33
+ id_prefix: prefix(instrument);
34
+ summary: concat("Payout from ", words(payer), " to ", words(beneficiary), " that is not done until the bank agrees");
35
+ description: "A payout the instrument owns end to end: one instruction, one expectation about the debit it will produce, and one break when the debit never matches";
36
+ dials: [{ key: settlement_tolerance; kind: reconcile_tolerance; maxMinorUnits: matched_ceiling; summary: "Largest difference between the instructed amount and the settled debit that still counts as matched."; title: "Settlement tolerance"; }];
37
+ action create { summary: concat("Create a ", words(instrument)); agent_description: concat("Open the payout row with the ", words(payer), " and ", words(beneficiary), " accounts, the amount, the beneficiary reference, and the settle date. No money moves and no instruction goes out yet. Call instruct next."); moves: []; steps: []; }
38
+ action instruct {
39
+ agent_description: concat("Send the payout instruction and start the expectation clock. This commits the amount to the ", words(beneficiary), " and the payout is not reversible from here. The bank debit is matched later by settle, which runs on its own at the settle date.");
40
+ summary: concat("Instruct the payout to the ", words(beneficiary));
41
+ payout { amount: concat("fields.", amount); beneficiaryField: beneficiary_ref; beneficiaryPartyField: beneficiary_account_field; capture: payoutId; currencyField: currency; sourceAccountField: payer_account_field; speed: standard; }
42
+ moves: []; steps: [];
43
+ }
44
+ action settle {
45
+ summary: "Match the bank's debit against what the instruction promised";
46
+ due { field: settle_by; }
47
+ reconcile {
48
+ amount: concat("fields.", amount); capture: settlementEvidenceId; counterpartyRef: payoutId;
49
+ currencyField: currency; direction: debit; evidence: statement_line;
50
+ exception { amountField: unmatchedAmount; childInstrumentId: concat(instrument, "_break"); maxOpen: 1; reasonField: breakReason; refField: concat(camel(instrument), "Id"); }
51
+ match { law: tolerance; dial: settlement_tolerance; minorUnits: matched_within; }
52
+ within { field: settle_by; }
53
+ }
54
+ moves: []; steps: [];
55
+ }
56
+ instruments {
57
+ instrument {
58
+ let(parent_name): instrument;
59
+ id: concat(parent_name, "_break");
60
+ id_prefix: concat(prefix(parent_name), "bk");
61
+ fields {
62
+ unmatchedAmount { type: money<C>; description: "Difference between the instructed amount and the evidence, in minor units"; }
63
+ currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
64
+ breakReason { type: text; description: "Why the expectation went unmatched"; minLength: 1; maxLength: 180; }
65
+ [parent_name]Id { type: ref<parent_name>; description: "Payout whose expectation broke"; pattern: concat("^", prefix(parent_name), "_(sandbox|live)_[a-z0-9]{8,64}$"); }
66
+ }
67
+ lifecycle { states created carried; initial created; on carry: created -> carried; }
68
+ required: [breakReason, currency, unmatchedAmount, concat(camel(parent_name), "Id")];
69
+ title: concat(sentence(parent_name), " Break");
70
+ agent_description: concat("Reach for this child row when a ", words(parent_name), " expectation reached its window with no matching bank debit. The break carries the unmatched amount and the reason so the difference is owned by someone rather than silently dropped.");
71
+ summary: "Unmatched settlement expectation carried as its own row";
72
+ description: "One break raised when a payout expectation reached its window with no matching debit; it is carried, never silently dropped";
73
+ action create { summary: "Raise a break against an instructed payout"; agent_description: "Raise a break against an instructed payout, recording the unmatched amount and why the expectation went unmatched. No money moves. The payout must still be instructed, and only one open break exists per payout."; requires refs { field: concat(camel(parent_name), "Id"); statuses: [instructed]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
74
+ action carry { summary: "Carry the break to whoever owns it"; agent_description: "Hand the break to whoever owns resolving it. No money moves. The payout must still be instructed. This is the only exit from a raised break, so a break is never closed by dropping it."; requires refs { field: concat(camel(parent_name), "Id"); statuses: [instructed]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,27 @@
1
+ module std.settlements.recurring_collection
2
+
3
+ export instrument recurring_collection(marker: optional<text>) {
4
+ agent_description: concat("Reach for ", words(instrument), " to track a bounded run of scheduled collections, one call per recorded installment. It carries no amount and no account, and every action only advances its own state. A settlement that debits an account on a schedule is a different shape.");
5
+ fields {}
6
+ lifecycle {
7
+ states active ended;
8
+ initial active;
9
+ on collect: active -> active;
10
+ on end: active -> ended;
11
+ }
12
+ action create {
13
+ agent_description: "Open the collection so installments can be recorded against it. This is the first call and the collection starts active. It moves no money.";
14
+ summary: "Create a bounded recurring collection";
15
+ steps: [];
16
+ }
17
+ action collect {
18
+ agent_description: "Record one due installment as collected. The collection must still be active, and it stays active afterwards so the next installment can follow. It moves no money.";
19
+ summary: "Collect one due installment";
20
+ steps: [];
21
+ }
22
+ action end {
23
+ agent_description: "Close the collection so no further installment can be recorded. Only an active collection can end, and ended is final. It moves no money.";
24
+ summary: "End future collections";
25
+ steps: [];
26
+ }
27
+ }
@@ -0,0 +1,252 @@
1
+ module std.settlements.rotating_pool
2
+
3
+ export instrument rotating_pool<C>(members: optional<list<party>>, contribution: money<C>, count: integer, every: optional<text>, first_due: date, payout_order: optional<list<party>>, default_policy: optional<text>, guarantee_policy: optional<text>, guarantor: optional<party>, exit_policy: optional<text>, memo: optional<text>, membership: optional<block>) {
4
+ when_not(membership) {
5
+ let(member_fields): suffix_each(members, "AccountId");
6
+ let(guarantor_field): concat(camel(guarantor), "AccountId");
7
+ let(contribution_field): camel(contribution);
8
+ let(first_due_field): camel(first_due);
9
+ let(parent_ref_field): concat(camel(instrument), "Id");
10
+ let(reference_filter): "x-hyperscale-reference-filter";
11
+ fields {
12
+ for member in members { [member]AccountId { type: account<C>; description: concat("The ", words(member), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter]: { column: role; values: [customer_balance]; }; } }
13
+ [guarantor_field] { type: account<C>; description: concat("The ", words(guarantor), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter]: { column: role; values: [customer_balance]; }; }
14
+ [contribution_field] { type: money<C>; description: "Exact contribution in {C} minor units"; }
15
+ currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
16
+ [first_due_field] { type: date; description: "Stored first contribution due date"; }
17
+ one { type: integer; description: "Exact fixed member-row count"; const: 1; }
18
+ }
19
+ lifecycle {
20
+ states forming cancelled;
21
+ for cycle in count {
22
+ let(active_state): concat("active_cycle_", cycle);
23
+ let(ready_state): concat("cycle_", cycle, "_ready");
24
+ let(states_key): "states";
25
+ [states_key]: [active_state, ready_state];
26
+ }
27
+ states completed;
28
+ initial forming;
29
+ on cancel: forming -> cancelled;
30
+ on activate: forming -> active_cycle_1;
31
+ for cycle in count {
32
+ let(active_state): concat("active_cycle_", cycle);
33
+ let(ready_state): concat("cycle_", cycle, "_ready");
34
+ on ready_cycle_[cycle]: active_state -> ready_state;
35
+ let(next_state): if_eq(cycle, count, "completed", concat("active_cycle_", add(cycle, 1)));
36
+ on advance_cycle_[cycle]: ready_state -> next_state;
37
+ }
38
+ }
39
+ parties { payer: guarantor_field; }
40
+ required: concat_lists(member_fields, [guarantor_field, "currency", contribution_field, first_due_field, "one"]);
41
+ title: sentence(instrument);
42
+ id_prefix: prefix(instrument);
43
+ agent_description: "Reach for rotating pool when a fixed group pays the same contribution every cycle and one member takes the whole pot each cycle, in a declared order. The roster freezes at creation, one contribution row per member, and the pot rotates until every member has been paid. Scheduled is the one payer, one payee shape on a calendar.";
44
+ summary: concat(count, "-member rotating pool");
45
+ description: "Fixed rotating contribution and payout order";
46
+ action create { agent_description: "Create the pool with its frozen roster, cycle count, contribution amount, and payout order. No money moves. Each member's contribution row is created separately, and the pool cannot activate until every row exists."; summary: "Create the fixed roster before activation"; moves: []; steps: []; }
47
+ action cancel { agent_description: "Cancel the pool before it activates. Only a forming pool cancels, no money has moved yet, and there is nothing to refund. Once the pool activates this exit is gone."; summary: "Cancel before activation without moving money"; moves: []; steps: []; }
48
+ action activate {
49
+ agent_description: "Start cycle one. Refused unless every member on the roster has exactly one contribution row waiting at the first cycle, so a pool can never run a member short. No money moves.";
50
+ summary: "Activate only after every fixed member row exists once";
51
+ for member in members { requires aggregate { instrumentId: concat(instrument, "_", member, "_contribution"); over: children; refField: parent_ref_field; statuses: [cycle_1_due]; check: { kind: count_equals_field; field: one; }; } }
52
+ moves: [];
53
+ steps: [];
54
+ }
55
+ for cycle in count {
56
+ action ready_cycle_[cycle] {
57
+ agent_description: concat("Lock cycle ", cycle, " once every member row is funded or covered by the guarantor. Refused while any row is still due or defaulted. No money moves here. Locking the cycle is what lets its payouts run.");
58
+ summary: concat("Lock cycle ", cycle, " only after every member row is funded or guaranteed");
59
+ let(funded_state): concat("cycle_", cycle, "_funded");
60
+ let(guaranteed_state): concat("cycle_", cycle, "_guaranteed");
61
+ for member in members { requires aggregate { instrumentId: concat(instrument, "_", member, "_contribution"); over: children; refField: parent_ref_field; statuses: [funded_state, guaranteed_state]; check: { kind: all_in; }; } }
62
+ moves: [];
63
+ steps: [];
64
+ }
65
+ action advance_cycle_[cycle] {
66
+ let(target_status): if_eq(cycle, count, "completed", concat("cycle_", add(cycle, 1), "_due"));
67
+ agent_description: concat("Close cycle ", cycle, " once every member row has paid into that cycle pot, moving the pool to the next cycle or to completed on the last one. Refused while any row is unpaid. No money moves here. The money moved on the member rows.");
68
+ summary: if_eq(cycle, count, "Complete after the final shared pot pays", concat("Advance after every cycle ", cycle, " contribution pays"));
69
+ for member in members { requires aggregate { instrumentId: concat(instrument, "_", member, "_contribution"); over: children; refField: parent_ref_field; statuses: [target_status]; check: { kind: all_in; }; } }
70
+ moves: [];
71
+ steps: [];
72
+ }
73
+ }
74
+ instruments {
75
+ for member in members {
76
+ instrument {
77
+ let(parent_name): instrument;
78
+ let(child_id): concat(parent_name, "_", member, "_contribution");
79
+ id: child_id;
80
+ generatedPrefix: true;
81
+ title: concat(sentence(parent_name), " ", sentence(member), " Contribution");
82
+ agent_description: concat("One seat in the pool. The ", words(member), " pays the same contribution every cycle and takes the whole pot on their turn. Every contribution, default, guarantee, and payout for that member happens on this row, never on the pool itself.");
83
+ summary: concat(words(member), " contribution row");
84
+ description: concat("Fixed contribution row for ", words(member));
85
+ fields {
86
+ for roster_member in payout_order { [roster_member]AccountId { type: account<C>; description: concat("The ", words(roster_member), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter]: { column: role; values: [customer_balance]; }; } }
87
+ [guarantor_field] { type: account<C>; description: concat("The ", words(guarantor), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter]: { column: role; values: [customer_balance]; }; }
88
+ [contribution_field] { type: money<C>; description: "Exact contribution amount shared by every cycle"; }
89
+ currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
90
+ [first_due_field] { type: date; description: "First due anchor derived from the pool"; }
91
+ [parent_ref_field] { type: ref<parent_name>; description: concat("The exact ", parent_name); pattern: concat("^", prefix(parent_name), "_(sandbox|live)_[a-z0-9]{8,64}$"); }
92
+ }
93
+ lifecycle {
94
+ for cycle in count {
95
+ let(states_key): "states";
96
+ let(due_state): concat("cycle_", cycle, "_due");
97
+ let(funded_state): concat("cycle_", cycle, "_funded");
98
+ let(defaulted_state): concat("cycle_", cycle, "_defaulted");
99
+ let(guaranteed_state): concat("cycle_", cycle, "_guaranteed");
100
+ [states_key]: [due_state, funded_state, defaulted_state, guaranteed_state];
101
+ }
102
+ states final_paid completed;
103
+ initial cycle_1_due;
104
+ for cycle in count {
105
+ let(due_state): concat("cycle_", cycle, "_due");
106
+ let(funded_state): concat("cycle_", cycle, "_funded");
107
+ let(defaulted_state): concat("cycle_", cycle, "_defaulted");
108
+ let(guaranteed_state): concat("cycle_", cycle, "_guaranteed");
109
+ on contribute_cycle_[cycle]: due_state -> funded_state;
110
+ on mark_default_cycle_[cycle]: due_state -> defaulted_state;
111
+ on guarantee_cycle_[cycle]: defaulted_state -> guaranteed_state;
112
+ let(next_state): if_eq(cycle, count, "final_paid", concat("cycle_", add(cycle, 1), "_due"));
113
+ on pay_cycle_[cycle]: funded_state -> next_state;
114
+ on pay_guaranteed_cycle_[cycle]: guaranteed_state -> next_state;
115
+ }
116
+ on close: final_paid -> completed;
117
+ }
118
+ parties { beneficiary: concat(camel(member), "AccountId"); payer: guarantor_field; }
119
+ action create {
120
+ agent_description: concat("Create the contribution row for the ", words(member), " and provision its escrow account. The pool must still be forming, and every member needs exactly one row before the pool can activate. No money moves.");
121
+ summary: concat("Create the fixed contribution row for ", words(member));
122
+ requires refs { field: parent_ref_field; statuses: [forming]; unique: true; bind: { [contribution_field]: concat("fields.", contribution_field); currency: "fields.currency"; [first_due_field]: concat("fields.", first_due_field); [guarantor_field]: concat("fields.", guarantor_field); for roster_member in members { let(roster_field): concat(camel(roster_member), "AccountId"); [roster_field]: concat("fields.", roster_field); } }; }
123
+ moves: [];
124
+ steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: "fields.currency"; }; "owner.id": { from: instance; path: productId; }; "owner.type": { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];
125
+ }
126
+ for cycle in count {
127
+ let(active_state): concat("active_cycle_", cycle);
128
+ let(ready_state): concat("cycle_", cycle, "_ready");
129
+ let(due_offset): scale_duration(every, sub(cycle, 1));
130
+ let(contribute_name): concat("contribute_cycle_", cycle);
131
+ let(guarantee_name): concat("guarantee_cycle_", cycle);
132
+ let(pay_name): concat("pay_cycle_", cycle);
133
+ let(pay_guaranteed_name): concat("pay_guaranteed_cycle_", cycle);
134
+ let(contribute_capture): concat("contributeCycle", cycle, "ContributionTransferId");
135
+ let(guarantee_capture): concat("guaranteeCycle", cycle, "GuaranteeTransferId");
136
+ let(pay_capture): concat("payCycle", cycle, "PayoutTransferId");
137
+ let(pay_guaranteed_capture): concat("payGuaranteedCycle", cycle, "PayoutTransferId");
138
+ let(payout_member): at(payout_order, cycle);
139
+ let(payout_field): concat(camel(payout_member), "AccountId");
140
+ action contribute_cycle_[cycle] {
141
+ summary: concat("Fund ", words(member), "'s cycle ", cycle, " contribution");
142
+ let(first): if_eq(cycle, 1, true, false);
143
+ when(first) { due: { field: first_due_field; }; }
144
+ when_not(first) { due: { field: first_due_field; offset: due_offset; }; }
145
+ requires refs { field: parent_ref_field; statuses: [active_state]; }
146
+ moves: [{ key: "contribution"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", contribution_field); }; currency: { from: instance; path: "fields.currency"; }; destinationAccountId: { from: instance; path: "refs.escrowAccountId"; }; "metadata.instrumentId": { from: const; value: child_id; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: contribute_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", camel(member), "AccountId"); }; }; capture: { [contribute_capture]: transferId; }; }];
147
+ steps: [];
148
+ }
149
+ action mark_default_cycle_[cycle] {
150
+ summary: concat("Mark the stored cycle ", cycle, " due condition");
151
+ let(first): if_eq(cycle, 1, true, false);
152
+ when(first) { due: { field: first_due_field; }; }
153
+ when_not(first) { due: { field: first_due_field; offset: due_offset; }; }
154
+ requires refs { field: parent_ref_field; statuses: [active_state]; }
155
+ moves: [];
156
+ steps: [];
157
+ }
158
+ action guarantee_cycle_[cycle] {
159
+ agent_description: concat("Have the guarantor fund cycle ", cycle, " for this member after the row defaulted. The row must be marked defaulted for that cycle. This moves the guarantor's money into the row escrow so the pot still pays in full.");
160
+ summary: concat("Fund the defaulted cycle ", cycle, " amount before payout");
161
+ requires refs { field: parent_ref_field; statuses: [active_state]; }
162
+ moves: [{ key: guarantee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", contribution_field); }; currency: { from: instance; path: "fields.currency"; }; destinationAccountId: { from: instance; path: "refs.escrowAccountId"; }; "metadata.instrumentId": { from: const; value: child_id; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: guarantee_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", guarantor_field); }; }; capture: { [guarantee_capture]: transferId; }; }];
163
+ steps: [];
164
+ }
165
+ action pay_cycle_[cycle] {
166
+ agent_description: concat("Pay this row's funded cycle ", cycle, " contribution out of escrow to whoever holds the pot that cycle. The cycle must be locked first and the row must be funded rather than guaranteed. This moves money and does not reverse.");
167
+ summary: concat("Pay this member's stored contribution into cycle ", cycle, "'s shared pot recipient");
168
+ requires refs { field: parent_ref_field; statuses: [ready_state]; }
169
+ moves: [{ key: payout; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", contribution_field); }; currency: { from: instance; path: "fields.currency"; }; destinationAccountId: { from: instance; path: concat("fields.", payout_field); }; "metadata.instrumentId": { from: const; value: child_id; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: pay_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.escrowAccountId"; }; }; capture: { [pay_capture]: transferId; }; }];
170
+ steps: [];
171
+ }
172
+ action pay_guaranteed_cycle_[cycle] {
173
+ agent_description: concat("Pay this row's guarantor-funded cycle ", cycle, " contribution out of escrow to whoever holds the pot that cycle. The cycle must be locked first and the row must be guaranteed rather than funded. This moves money and does not reverse.");
174
+ summary: concat("Pay the funded default into cycle ", cycle, "'s stored recipient");
175
+ requires refs { field: parent_ref_field; statuses: [ready_state]; }
176
+ moves: [{ key: payout; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", contribution_field); }; currency: { from: instance; path: "fields.currency"; }; destinationAccountId: { from: instance; path: concat("fields.", payout_field); }; "metadata.instrumentId": { from: const; value: child_id; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: pay_guaranteed_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.escrowAccountId"; }; }; capture: { [pay_guaranteed_capture]: transferId; }; }];
177
+ steps: [];
178
+ }
179
+ }
180
+ action close { agent_description: "Retire this member row after its final payout. Refused while the row escrow account still holds anything, so a row can never close over stranded money. No money moves."; summary: "Complete after the final payout drains this member custody"; requires drained: { path: "refs.escrowAccountId"; }; moves: []; steps: []; }
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ when(membership) {
187
+ let(membership_instrument): get(membership, "instrument");
188
+ let(membership_ref_field): get(membership, "ref_field");
189
+ let(joined_status): get(membership, "joined_status");
190
+ let(active_status): get(membership, "active_status");
191
+ let(paid_status): get(membership, "paid_status");
192
+ let(custody_ref): get(membership, "custody_ref");
193
+ let(instrument_id_prefix): get(membership, "id_prefix");
194
+ let(active_parked): get(membership, "active_parked");
195
+ let(min_members): get(membership, "min_members");
196
+ let(max_members): get(membership, "max_members");
197
+ let(contribution_description): get(membership, "contribution_description");
198
+ let(currency_description): get(membership, "currency_description");
199
+ let(create_examples): get(membership, "create_examples");
200
+ let(activate_examples): get(membership, "activate_examples");
201
+ let(disband_examples): get(membership, "disband_examples");
202
+ let(close_examples): get(membership, "close_examples");
203
+ let(contribution_field): camel(contribution);
204
+ let(count_field): camel(count);
205
+ let(first_due_field): camel(first_due);
206
+
207
+ fields {
208
+ [contribution_field] { type: money<C>; description: contribution_description; }
209
+ currency { type: text; description: currency_description; maxLength: 3; minLength: 3; pattern: "^[A-Z]{3}$"; }
210
+ [count_field] { type: integer; maximum: max_members; minimum: min_members; }
211
+ when(memo) { [memo] { type: text; optional: true; maxLength: 180; minLength: 1; } }
212
+ [first_due_field] { type: date; }
213
+ }
214
+ lifecycle {
215
+ states forming active completed disbanded;
216
+ initial forming;
217
+ on activate: forming -> active;
218
+ on close: active -> completed;
219
+ on disband: forming -> disbanded;
220
+ }
221
+ required: [count_field, contribution_field, currency, first_due_field];
222
+ aggregate_invariants { childInstrumentId: membership_instrument; childRefField: membership_ref_field; childStatuses: [joined_status, active_status, paid_status]; count: true; parentField: count_field; }
223
+ caller_parked_states { active: active_parked; }
224
+ agent_description: concat("Reach for ", words(instrument), " when a fixed group pays the same contribution every cycle and one member takes the whole pot each cycle, and the product publishes its own membership rows. This shape owns the pot account and the seat count. Every contribution and payout happens on the membership row that references it, never here.");
225
+ id_prefix: instrument_id_prefix;
226
+ action create {
227
+ public: none;
228
+ agent_description: concat("Open the ", words(instrument), " and provision the pot account every contribution lands in. No money moves. The roster is still empty, so the pool stays forming until one membership row exists per seat.");
229
+ examples: create_examples;
230
+ moves: [];
231
+ steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: "fields.currency"; }; "owner.id": { from: instance; path: productId; }; "owner.type": { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { [custody_ref]: accountId; }; }];
232
+ }
233
+ action activate {
234
+ public: none;
235
+ due { field: first_due_field; }
236
+ examples: activate_examples;
237
+ requires aggregate { instrumentId: membership_instrument; over: children; refField: membership_ref_field; statuses: [joined_status]; check: { kind: count_equals_field; field: count_field; }; }
238
+ moves: [];
239
+ steps: [];
240
+ }
241
+ action disband { public: none; agent_description: concat("Close a ", words(instrument), " that never filled its roster. Only a forming pool disbands, no money has moved, and there is nothing to refund. Once the pool activates this exit is gone."); examples: disband_examples; moves: []; steps: []; }
242
+ action close {
243
+ public: none;
244
+ agent_description: concat("Retire the ", words(instrument), " after every membership row has been paid. Refused while any row is unpaid or while the pot account still holds anything, so a pool can never close over stranded money. No money moves.");
245
+ examples: close_examples;
246
+ requires aggregate { instrumentId: membership_instrument; over: children; refField: membership_ref_field; statuses: [paid_status]; check: { kind: all_in; }; }
247
+ requires drained { path: concat("refs.", custody_ref); }
248
+ moves: [];
249
+ steps: [];
250
+ }
251
+ }
252
+ }