@hyperscale0/hsx 1.0.0 → 1.0.124

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 (316) hide show
  1. package/CHANGELOG.md +5 -256
  2. package/LICENSE.md +356 -0
  3. package/NOTICE.md +8 -0
  4. package/README.md +44 -44
  5. package/bin/hsx.ts +0 -7
  6. package/dist/bin/hsx.d.ts +0 -4
  7. package/dist/bin/hsx.d.ts.map +1 -1
  8. package/dist/bin/hsx.js +0 -6
  9. package/dist/bin/hsx.js.map +1 -1
  10. package/dist/src/ast.d.ts +113 -308
  11. package/dist/src/ast.d.ts.map +1 -1
  12. package/dist/src/ast.js +7 -71
  13. package/dist/src/ast.js.map +1 -1
  14. package/dist/src/attachment-economics.d.ts +5 -0
  15. package/dist/src/attachment-economics.d.ts.map +1 -0
  16. package/dist/src/attachment-economics.js +55 -0
  17. package/dist/src/attachment-economics.js.map +1 -0
  18. package/dist/src/binding-contract.d.ts +22 -0
  19. package/dist/src/binding-contract.d.ts.map +1 -0
  20. package/dist/src/binding-contract.js +74 -0
  21. package/dist/src/binding-contract.js.map +1 -0
  22. package/dist/src/cli.d.ts +4 -19
  23. package/dist/src/cli.d.ts.map +1 -1
  24. package/dist/src/cli.js +89 -276
  25. package/dist/src/cli.js.map +1 -1
  26. package/dist/src/compile.d.ts +34 -66
  27. package/dist/src/compile.d.ts.map +1 -1
  28. package/dist/src/compile.js +2949 -117
  29. package/dist/src/compile.js.map +1 -1
  30. package/dist/src/cost.d.ts +8 -75
  31. package/dist/src/cost.d.ts.map +1 -1
  32. package/dist/src/cost.js +54 -364
  33. package/dist/src/cost.js.map +1 -1
  34. package/dist/src/diagnostics.d.ts +14 -9
  35. package/dist/src/diagnostics.d.ts.map +1 -1
  36. package/dist/src/diagnostics.js +39 -416
  37. package/dist/src/diagnostics.js.map +1 -1
  38. package/dist/src/examples-bundle.d.ts +3 -0
  39. package/dist/src/examples-bundle.d.ts.map +1 -0
  40. package/dist/src/examples-bundle.js +18 -0
  41. package/dist/src/examples-bundle.js.map +1 -0
  42. package/dist/src/examples.d.ts +10 -0
  43. package/dist/src/examples.d.ts.map +1 -0
  44. package/dist/src/examples.js +2 -0
  45. package/dist/src/examples.js.map +1 -0
  46. package/dist/src/format.d.ts +8 -7
  47. package/dist/src/format.d.ts.map +1 -1
  48. package/dist/src/format.js +9 -175
  49. package/dist/src/format.js.map +1 -1
  50. package/dist/src/header-source.d.ts +3 -0
  51. package/dist/src/header-source.d.ts.map +1 -0
  52. package/dist/src/header-source.js +41 -0
  53. package/dist/src/header-source.js.map +1 -0
  54. package/dist/src/headers.d.ts +64 -0
  55. package/dist/src/headers.d.ts.map +1 -0
  56. package/dist/src/headers.js +167 -0
  57. package/dist/src/headers.js.map +1 -0
  58. package/dist/src/index.d.ts +7 -11
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/index.js +4 -9
  61. package/dist/src/index.js.map +1 -1
  62. package/dist/src/keywords.d.ts +56 -0
  63. package/dist/src/keywords.d.ts.map +1 -0
  64. package/dist/src/keywords.js +133 -0
  65. package/dist/src/keywords.js.map +1 -0
  66. package/dist/src/language.d.ts +26 -0
  67. package/dist/src/language.d.ts.map +1 -0
  68. package/dist/src/language.js +440 -0
  69. package/dist/src/language.js.map +1 -0
  70. package/dist/src/lex.d.ts +13 -28
  71. package/dist/src/lex.d.ts.map +1 -1
  72. package/dist/src/lex.js +119 -145
  73. package/dist/src/lex.js.map +1 -1
  74. package/dist/src/parse.d.ts +4 -17
  75. package/dist/src/parse.d.ts.map +1 -1
  76. package/dist/src/parse.js +484 -1036
  77. package/dist/src/parse.js.map +1 -1
  78. package/dist/src/std-bundle.d.ts.map +1 -1
  79. package/dist/src/std-bundle.js +11 -84
  80. package/dist/src/std-bundle.js.map +1 -1
  81. package/dist/src/std-library.d.ts +1 -1
  82. package/dist/src/std-library.d.ts.map +1 -1
  83. package/dist/src/std-library.js +1 -5
  84. package/dist/src/std-library.js.map +1 -1
  85. package/dist/src/tunables.d.ts +7 -0
  86. package/dist/src/tunables.d.ts.map +1 -0
  87. package/dist/src/tunables.js +27 -0
  88. package/dist/src/tunables.js.map +1 -0
  89. package/docs/README.md +528 -26
  90. package/docs/assets/brand/hsx-horizontal-white.svg +1 -0
  91. package/docs/assets/brand/hsx-horizontal.svg +1 -0
  92. package/docs/assets/brand/hsx-stacked-white.svg +1 -0
  93. package/docs/assets/brand/hsx-stacked.svg +1 -0
  94. package/docs/assets/brand/manifest.json +30 -0
  95. package/docs/assets/hsx.svg +11 -0
  96. package/examples/cards.hsx +34 -0
  97. package/examples/collections.hsx +37 -0
  98. package/examples/cost-table.json +633 -385
  99. package/examples/escrow.hsx +24 -0
  100. package/examples/financing.hsx +32 -0
  101. package/examples/insurance.hsx +30 -0
  102. package/examples/lending.hsx +57 -0
  103. package/examples/library.hsx +15 -0
  104. package/examples/marketplace.hsx +32 -0
  105. package/examples/money.hsx +19 -0
  106. package/examples/rental-deposit.hsx +60 -0
  107. package/examples/repair-approval.hsx +44 -0
  108. package/examples/reporting.hsx +28 -0
  109. package/examples/savings.hsx +21 -0
  110. package/examples/serviced.hsx +25 -0
  111. package/examples/wallet.hsx +24 -0
  112. package/package.json +25 -23
  113. package/src/ast.ts +83 -440
  114. package/src/attachment-economics.ts +103 -0
  115. package/src/binding-contract.ts +104 -0
  116. package/src/cli.ts +95 -325
  117. package/src/compile.ts +4063 -209
  118. package/src/cost.ts +71 -574
  119. package/src/diagnostics.ts +57 -437
  120. package/src/examples-bundle.ts +19 -0
  121. package/src/examples.ts +9 -0
  122. package/src/format.ts +12 -256
  123. package/src/header-source.ts +54 -0
  124. package/src/headers.ts +195 -0
  125. package/src/index.ts +18 -33
  126. package/src/keywords.ts +134 -0
  127. package/src/language.ts +629 -0
  128. package/src/lex.ts +131 -192
  129. package/src/parse.ts +489 -1195
  130. package/src/std-bundle.ts +12 -88
  131. package/src/std-library.ts +2 -7
  132. package/src/tunables.ts +36 -0
  133. package/std/cards.hsx +85 -0
  134. package/std/collections.hsx +90 -0
  135. package/std/escrow.hsx +73 -0
  136. package/std/financing.hsx +683 -0
  137. package/std/insurance.hsx +89 -0
  138. package/std/lending.hsx +138 -0
  139. package/std/marketplace.hsx +123 -0
  140. package/std/money.hsx +173 -0
  141. package/std/reporting.hsx +753 -0
  142. package/std/savings.hsx +68 -0
  143. package/std/wallet.hsx +57 -0
  144. package/LICENSE +0 -661
  145. package/LICENSING.md +0 -52
  146. package/TRADEMARKS.md +0 -35
  147. package/dist/src/emit.d.ts +0 -34
  148. package/dist/src/emit.d.ts.map +0 -1
  149. package/dist/src/emit.js +0 -137
  150. package/dist/src/emit.js.map +0 -1
  151. package/dist/src/entry-overrides.d.ts +0 -58
  152. package/dist/src/entry-overrides.d.ts.map +0 -1
  153. package/dist/src/entry-overrides.js +0 -284
  154. package/dist/src/entry-overrides.js.map +0 -1
  155. package/dist/src/ir.d.ts +0 -66
  156. package/dist/src/ir.d.ts.map +0 -1
  157. package/dist/src/ir.js +0 -15
  158. package/dist/src/ir.js.map +0 -1
  159. package/dist/src/limits.d.ts +0 -21
  160. package/dist/src/limits.d.ts.map +0 -1
  161. package/dist/src/limits.js +0 -21
  162. package/dist/src/limits.js.map +0 -1
  163. package/dist/src/lsp/server.d.ts +0 -32
  164. package/dist/src/lsp/server.d.ts.map +0 -1
  165. package/dist/src/lsp/server.js +0 -391
  166. package/dist/src/lsp/server.js.map +0 -1
  167. package/dist/src/modules.d.ts +0 -38
  168. package/dist/src/modules.d.ts.map +0 -1
  169. package/dist/src/modules.js +0 -368
  170. package/dist/src/modules.js.map +0 -1
  171. package/dist/src/typecheck.d.ts +0 -8
  172. package/dist/src/typecheck.d.ts.map +0 -1
  173. package/dist/src/typecheck.js +0 -2958
  174. package/dist/src/typecheck.js.map +0 -1
  175. package/dist/src/version.d.ts +0 -5
  176. package/dist/src/version.d.ts.map +0 -1
  177. package/dist/src/version.js +0 -5
  178. package/dist/src/version.js.map +0 -1
  179. package/docs/guide/01-first-program.md +0 -24
  180. package/docs/guide/02-money.md +0 -25
  181. package/docs/guide/03-instruments.md +0 -22
  182. package/docs/guide/04-lifecycles.md +0 -25
  183. package/docs/guide/05-fees-and-splits.md +0 -45
  184. package/docs/guide/06-schedules.md +0 -22
  185. package/docs/guide/07-composition.md +0 -46
  186. package/docs/guide/08-writing-a-module.md +0 -26
  187. package/docs/guide/09-cost.md +0 -24
  188. package/docs/guide/10-diagnostics.md +0 -27
  189. package/docs/llms-full.txt +0 -1739
  190. package/docs/llms.txt +0 -45
  191. package/docs/playground.md +0 -54
  192. package/docs/reference/cli.md +0 -35
  193. package/docs/reference/diagnostics.md +0 -619
  194. package/docs/reference/grammar.md +0 -39
  195. package/docs/reference/std/advance.md +0 -35
  196. package/docs/reference/std/cancellable_booking.md +0 -35
  197. package/docs/reference/std/captured_payment.md +0 -43
  198. package/docs/reference/std/conditional_disbursement.md +0 -33
  199. package/docs/reference/std/credit_facility.md +0 -35
  200. package/docs/reference/std/held_payment.md +0 -64
  201. package/docs/reference/std/instant_transfer.md +0 -30
  202. package/docs/reference/std/metered.md +0 -27
  203. package/docs/reference/std/pooled_split.md +0 -26
  204. package/docs/reference/std/premium_forward.md +0 -39
  205. package/docs/reference/std/reconciled_payout.md +0 -33
  206. package/docs/reference/std/recurring_collection.md +0 -25
  207. package/docs/reference/std/rotating_pool.md +0 -42
  208. package/docs/reference/std/scheduled.md +0 -51
  209. package/docs/reference/std/security_deposit.md +0 -46
  210. package/docs/reference/std/settlement_batch.md +0 -42
  211. package/docs/reference/std/swap.md +0 -46
  212. package/docs/reference/std/threshold_pool.md +0 -47
  213. package/docs/reference/std/weighted_distribution.md +0 -39
  214. package/docs/reference/types.md +0 -20
  215. package/docs/reference/udl-output.md +0 -13
  216. package/docs/sessions/2026-09-02-two-instruments.hsx +0 -21
  217. package/docs/sessions/2026-09-02-two-instruments.md +0 -41
  218. package/examples/01-first-program/README.md +0 -45
  219. package/examples/01-first-program/tip-jar.hsx +0 -17
  220. package/examples/02-imports-and-archetypes/README.md +0 -96
  221. package/examples/02-imports-and-archetypes/photo-booth.hsx +0 -37
  222. package/examples/03-diagnostics/README.md +0 -68
  223. package/examples/03-diagnostics/corner-shop-fixed.hsx +0 -22
  224. package/examples/03-diagnostics/corner-shop.hsx +0 -19
  225. package/examples/04-complete-product/README.md +0 -84
  226. package/examples/04-complete-product/study-hall.hsx +0 -67
  227. package/examples/05-watch-club/README.md +0 -9
  228. package/examples/05-watch-club/watch-club.hsx +0 -124
  229. package/examples/README.md +0 -18
  230. package/examples/advance/README.md +0 -3
  231. package/examples/advance/advance.hsx +0 -13
  232. package/examples/advance/advance.udl +0 -339
  233. package/examples/cancellable_booking/README.md +0 -3
  234. package/examples/cancellable_booking/cancellable_booking.hsx +0 -14
  235. package/examples/cancellable_booking/cancellable_booking.udl +0 -391
  236. package/examples/captured_payment/README.md +0 -3
  237. package/examples/captured_payment/captured_payment.hsx +0 -21
  238. package/examples/captured_payment/captured_payment.udl +0 -677
  239. package/examples/conditional_disbursement/README.md +0 -3
  240. package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -17
  241. package/examples/conditional_disbursement/conditional_disbursement.udl +0 -412
  242. package/examples/credit_facility/README.md +0 -3
  243. package/examples/credit_facility/credit_facility.hsx +0 -27
  244. package/examples/credit_facility/credit_facility.udl +0 -1373
  245. package/examples/held_payment/README.md +0 -3
  246. package/examples/held_payment/held_payment.hsx +0 -13
  247. package/examples/held_payment/held_payment.udl +0 -507
  248. package/examples/instant_transfer/README.md +0 -3
  249. package/examples/instant_transfer/instant_transfer.hsx +0 -16
  250. package/examples/instant_transfer/instant_transfer.udl +0 -364
  251. package/examples/metered/README.md +0 -3
  252. package/examples/metered/metered.hsx +0 -13
  253. package/examples/metered/metered.udl +0 -247
  254. package/examples/pooled_split/README.md +0 -3
  255. package/examples/pooled_split/pooled_split.hsx +0 -15
  256. package/examples/pooled_split/pooled_split.udl +0 -425
  257. package/examples/premium_forward/README.md +0 -3
  258. package/examples/premium_forward/premium_forward.hsx +0 -19
  259. package/examples/premium_forward/premium_forward.udl +0 -594
  260. package/examples/reconciled_payout/README.md +0 -3
  261. package/examples/reconciled_payout/reconciled_payout.hsx +0 -13
  262. package/examples/reconciled_payout/reconciled_payout.udl +0 -303
  263. package/examples/recurring_collection/README.md +0 -3
  264. package/examples/recurring_collection/recurring_collection.hsx +0 -21
  265. package/examples/recurring_collection/recurring_collection.udl +0 -1130
  266. package/examples/rotating_pool/README.md +0 -3
  267. package/examples/rotating_pool/rotating_pool.hsx +0 -18
  268. package/examples/rotating_pool/rotating_pool.udl +0 -4352
  269. package/examples/scheduled/README.md +0 -3
  270. package/examples/scheduled/scheduled.hsx +0 -12
  271. package/examples/scheduled/scheduled.udl +0 -335
  272. package/examples/security_deposit/README.md +0 -3
  273. package/examples/security_deposit/security_deposit.hsx +0 -21
  274. package/examples/security_deposit/security_deposit.udl +0 -293
  275. package/examples/settlement_batch/README.md +0 -3
  276. package/examples/settlement_batch/settlement_batch.hsx +0 -19
  277. package/examples/settlement_batch/settlement_batch.udl +0 -753
  278. package/examples/swap/README.md +0 -3
  279. package/examples/swap/swap.hsx +0 -19
  280. package/examples/swap/swap.udl +0 -876
  281. package/examples/threshold_pool/README.md +0 -3
  282. package/examples/threshold_pool/threshold_pool.hsx +0 -16
  283. package/examples/threshold_pool/threshold_pool.udl +0 -667
  284. package/examples/weighted_distribution/README.md +0 -3
  285. package/examples/weighted_distribution/weighted_distribution.hsx +0 -20
  286. package/examples/weighted_distribution/weighted_distribution.udl +0 -337
  287. package/skills/hsx/SKILL.md +0 -483
  288. package/src/emit.ts +0 -201
  289. package/src/entry-overrides.ts +0 -428
  290. package/src/ir.ts +0 -99
  291. package/src/limits.ts +0 -20
  292. package/src/lsp/server.ts +0 -460
  293. package/src/modules.ts +0 -446
  294. package/src/typecheck.ts +0 -3876
  295. package/src/version.ts +0 -5
  296. package/std/SEMANTICS.md +0 -130
  297. package/std/settlements/advance.hsx +0 -177
  298. package/std/settlements/cancellable_booking.hsx +0 -87
  299. package/std/settlements/captured_payment.hsx +0 -144
  300. package/std/settlements/conditional_disbursement.hsx +0 -185
  301. package/std/settlements/credit_facility.hsx +0 -167
  302. package/std/settlements/held_payment.hsx +0 -578
  303. package/std/settlements/index.hsx +0 -3
  304. package/std/settlements/instant_transfer.hsx +0 -190
  305. package/std/settlements/metered.hsx +0 -67
  306. package/std/settlements/pooled_split.hsx +0 -68
  307. package/std/settlements/premium_forward.hsx +0 -257
  308. package/std/settlements/reconciled_payout.hsx +0 -77
  309. package/std/settlements/recurring_collection.hsx +0 -27
  310. package/std/settlements/rotating_pool.hsx +0 -252
  311. package/std/settlements/scheduled.hsx +0 -641
  312. package/std/settlements/security_deposit.hsx +0 -166
  313. package/std/settlements/settlement_batch.hsx +0 -122
  314. package/std/settlements/swap.hsx +0 -1114
  315. package/std/settlements/threshold_pool.hsx +0 -221
  316. package/std/settlements/weighted_distribution.hsx +0 -141
@@ -1,12 +1,7 @@
1
1
  import { BUNDLED_STD_FILES } from "./std-bundle.ts";
2
-
3
2
  export interface StandardLibrary {
4
- readonly source: (specifier: string, name: string) => string | undefined;
3
+ source(header: string): string | undefined;
5
4
  }
6
-
7
5
  export const bundledStandardLibrary: StandardLibrary = {
8
- source(specifier: string, name: string): string | undefined {
9
- if (specifier !== "std/settlements") return undefined;
10
- return BUNDLED_STD_FILES.get(`settlements/${name}.hsx`);
11
- },
6
+ source: (header) => BUNDLED_STD_FILES.get(`${header}.hsx`),
12
7
  };
@@ -0,0 +1,36 @@
1
+ import { fail } from "./diagnostics.ts";
2
+ import type { Expr } from "./ast.ts";
3
+
4
+ /** Bounds use UDL units: minor units, basis points and milliseconds. */
5
+ export function tunableBounds(
6
+ type: Expr,
7
+ ): { minimum: number | string; maximum: number | string } | undefined {
8
+ const name =
9
+ type.kind === "call" || type.kind === "type"
10
+ ? type.name
11
+ : type.kind === "name"
12
+ ? type.value
13
+ : "";
14
+ if (name === "money") return { minimum: "0", maximum: "999999999999999999" };
15
+ if (name === "percent") return { minimum: 0, maximum: 10000 };
16
+ if (name === "duration")
17
+ return { minimum: 1, maximum: Number.MAX_SAFE_INTEGER };
18
+ if (name !== "integer") return;
19
+ if (type.kind === "call") {
20
+ const numbers = type.args.map((arg) =>
21
+ arg.kind === "number" ? Number(arg.value) : NaN,
22
+ );
23
+ if (
24
+ numbers.length !== 2 ||
25
+ numbers.some((value) => !Number.isSafeInteger(value)) ||
26
+ numbers[0]! > numbers[1]!
27
+ )
28
+ fail(
29
+ type,
30
+ "integer tunable needs ordered safe integer bounds",
31
+ "write integer(1, 12)",
32
+ );
33
+ return { minimum: numbers[0]!, maximum: numbers[1]! };
34
+ }
35
+ return { minimum: 0, maximum: Number.MAX_SAFE_INTEGER };
36
+ }
package/std/cards.hsx ADDED
@@ -0,0 +1,85 @@
1
+ header cards
2
+
3
+ // Activate holder and card before creating and approving an authorization.
4
+ // Expose authorization.create, approve and capture; expose decline and reverse
5
+ // separately if callers need those paths. Issuer declines only from pending;
6
+ // merchant captures or reverses an approved reservation. Expiry voids approved
7
+ // reservations or abandons pending ones; it does not delay a captured refund.
8
+ // Create one transaction for the captured authorization and expose its refund.
9
+ // Refund is available immediately from posted, even before authorization.expiresAt,
10
+ // and moves the whole captured amount from merchant to holder under programOperator.
11
+ // No partial capture, repeated capture or partial transaction refund is provided.
12
+ // Disputes need a posted transaction, issuer review and win before their deadline;
13
+ // win invokes the same refund. The alias never changes the declared action actor.
14
+
15
+ // Cost: authorization approve, capture and reverse each declare one transfer
16
+ // instruction. Transaction refund declares one transfer; a successful dispute
17
+ // invokes that refund. Card state changes alone declare no transfers.
18
+
19
+ instrument cardholder(holder: party) {
20
+ summary: "An account holder whose card access can be activated, suspended or closed."
21
+ fields { holder: account of holder }
22
+ lifecycle { states: [pending, active, suspended, closed], initial: pending }
23
+ action create {}
24
+ action activate { from: pending, to: active }
25
+ action suspend { from: active, to: suspended }
26
+ action resume { from: suspended, to: active }
27
+ action close { from: [active, suspended], to: closed }
28
+ }
29
+
30
+ instrument card(holder: ref<cards.cardholder>, spend_limit: money) {
31
+ summary: "A card with a per-spend ceiling that can be activated, frozen or cancelled."
32
+ fields { holder: ref<holder>, spendLimit: money = spend_limit }
33
+ lifecycle { states: [issued, active, frozen, cancelled], initial: issued }
34
+ action create { requires self.holder in [active] }
35
+ action activate { from: issued, to: active, requires self.holder in [active] }
36
+ action freeze { from: active, to: frozen }
37
+ action unfreeze { from: frozen, to: active, requires self.holder in [active] }
38
+ action cancel { from: [issued, active, frozen], to: cancelled }
39
+ }
40
+
41
+ instrument authorization(card: ref<cards.card>, merchant: party, issuer: party = programOperator) {
42
+ summary: "Reserve a card payment for the merchant, then capture it or release the reservation."
43
+ fields { card: ref<card>, merchant: account of merchant, amount: money, expiresAt: date, reservation: text? }
44
+ lifecycle { states: [pending, approved, captured, declined, reversed, expired], initial: pending }
45
+ action create {
46
+ requires self.card in [active]
47
+ requires self.card.holder in [active]
48
+ requires self.amount <= self.card.spendLimit
49
+ }
50
+ action approve {
51
+ from: pending, to: approved, actor: { party: issuer }, deadline: { at: self.expiresAt }
52
+ requires self.card in [active]
53
+ requires self.card.holder in [active]
54
+ moves reserve self.amount from self.card.holder.holder to merchant capture "reservation"
55
+ }
56
+ action capture { from: approved, to: captured, actor: { party: merchant }, deadline: { at: self.expiresAt }, moves post self.reservation }
57
+ action reverse { from: approved, to: reversed, actor: { party: merchant }, moves void self.reservation }
58
+ action decline { from: pending, to: declined, actor: { party: issuer } }
59
+ action expire { from: approved, to: expired, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }
60
+ action abandon { from: pending, to: expired, actor: clock, due: { at: self.expiresAt } }
61
+ }
62
+
63
+ instrument transaction(authorization: ref<cards.authorization>) {
64
+ summary: "Record a captured card payment and return its amount when refunded."
65
+ fields { authorization: ref<authorization>, amount: money = self.authorization.amount }
66
+ lifecycle { states: [posted, refunded], initial: posted }
67
+ action create { requires self.authorization in [captured]; requires unique "captured_authorization" on [self.authorization] }
68
+ action refund {
69
+ from: posted, to: refunded, actor: { party: programOperator }, input { reason: text }
70
+ moves self.amount from self.authorization.merchant to self.authorization.card.holder.holder
71
+ }
72
+ }
73
+
74
+ instrument dispute(transaction: ref<cards.transaction>, issuer: party = programOperator, within: duration = 90d) {
75
+ summary: "Review a disputed card payment before its deadline and refund it when the dispute is won."
76
+ fields { transaction: ref<transaction>, evidence: text, expiresAt: date = after(self.createdAt, within) }
77
+ lifecycle { states: [filed, reviewing, won, lost], initial: filed }
78
+ action create { requires self.transaction in [posted]; requires unique "disputed_transaction" on [self.transaction] }
79
+ action review { from: filed, to: reviewing, actor: { party: issuer } }
80
+ action win {
81
+ from: reviewing, to: won, actor: { party: issuer }, deadline: { at: self.expiresAt }
82
+ invoke: [{ reference: self.transaction, action: refund, input: { reason: { field: self.evidence } } }]
83
+ }
84
+ action lose { from: [filed, reviewing], to: lost, actor: clock, due: { at: self.expiresAt } }
85
+ }
@@ -0,0 +1,90 @@
1
+ header collections
2
+
3
+ // Economics: Company capital pays agencies as participant payouts; outside capital passes through.
4
+ // A company agency earns outside capital fees; its own capital fee is internal.
5
+
6
+ // Expose case.create, assign and recover as separate steps. Creation needs overdue
7
+ // outstanding principal and a matching slice/plan. Assignment belongs to capital;
8
+ // recovery belongs to agency and pays the linked payment before the agency fee.
9
+ // Payment must belong to the same plan. Recovered means that payment was processed,
10
+ // not that the entire debt was repaid. Fee debits plan.capital, not the borrower.
11
+ // Expose recall for capital and return_uncollected for agency when needed.
12
+ // Contacts need an assigned case, matching agency, local hours and rolling count.
13
+ // Contact outcomes are separate records. Reminder dispatch is operator-owned;
14
+ // before_due/overdue are clock-owned and require active debt. Scheduled admission
15
+ // does not prove delivery or retry. No collector can infer authority from an alias.
16
+
17
+ // Cost: case recovery invokes the linked payment and then charges its agency
18
+ // fee. Its count includes those child effects. Contact and reminder delivery costs
19
+ // are not provider prices in hsx cost; the executor supplies commercial tariffs.
20
+
21
+ instrument case(on: ref<financing.installments>[], agency: party, capital: party = programOperator, overdue: duration = 3d, fee: percent = 20%) {
22
+ revisioned: true
23
+ summary: "Refer an overdue installment without creating another debt."
24
+ fields { assignedAgency: account of agency, plan: ref<on>, slice: ref<on.slice>, payment: ref<on.payment>, eligibleAt: date = after(self.slice.dueAt, overdue), agencyFee: money = rate(self.payment.amount, fee) }
25
+ lifecycle { states: [opened, assigned, recovered, recalled, returned_uncollected], initial: opened }
26
+ action create {
27
+ due: { at: self.eligibleAt }
28
+ requires self.plan == self.slice.plan
29
+ requires self.slice.principalReceivable.balance > "0"
30
+ requires unique "overdue_referral" on [self.slice]
31
+ }
32
+ action assign { from: opened, to: assigned, actor: { party: capital } }
33
+ action recover { expansionLimit: 8192, from: assigned, to: recovered, actor: { party: agency }, requires self.payment.plan == self.plan, invoke: [{ reference: self.payment, action: pay, input: {} }, { reference: self.id, action: pay_fee, input: {} }] }
34
+ action pay_fee { allowZero: true, from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency economics { recipient: agency, company: { recipient: capital, company: { purpose: internal, sourceParty: capital }, participant: { purpose: earning, sourceParty: capital } }, participant: { recipient: capital, company: { purpose: participant_payout, sourceParty: capital }, participant: { purpose: pass_through, sourceParty: capital } } } }
35
+ action recall { from: [opened, assigned], to: recalled, actor: { party: capital } }
36
+ action return_uncollected { from: assigned, to: returned_uncollected, actor: { party: agency } }
37
+ }
38
+
39
+ instrument contact(case: ref<collections.case>[], agency: party, max_contacts: integer = 10, window: duration = 30d, contact_from: integer(0, 23) = 8, contact_until: integer(1, 23) = 20, timezone: text = "Asia/Riyadh") {
40
+ constraints { contact_from: less_than(contact_until) }
41
+ summary: "An agency contact admitted against the locked rolling count and local hours."
42
+ fields { agency: account of agency, case: ref<case>, attemptedAt: date?, evidence: text, maximum: integer = max_contacts }
43
+ lifecycle { states: [recorded], initial: recorded }
44
+ invariants count of { instrument: current(), reference: "case", anchor: self.case, states: [recorded], limit: 366, window: { field: attemptedAt, milliseconds: window } } <= self.maximum
45
+ action create {
46
+ actor: { party: agency }
47
+ set: { attemptedAt: { field: self.now } }
48
+ requires self.agency == self.case.assignedAgency; requires self.case in [assigned]; requires hours self.now between contact_from and contact_until timezone timezone
49
+ }
50
+ records {
51
+ outcome: {
52
+ fields { contact: ref<parent>, agency: account of agency, outcome: enum(reached, no_answer, promise_to_pay, disputed, wrong_contact, refused), evidence: text, recordedAt: date? }
53
+ lifecycle { states: [recorded], initial: recorded }
54
+ action create {
55
+ actor: { party: agency }
56
+ requires self.agency == self.contact.case.assignedAgency
57
+ set: { recordedAt: { field: self.now } }
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+
64
+ instrument reminder(on: ref<financing.installments>[], operator: party = programOperator, before_days: integer(0, 366) = 3, overdue_days: integer(1, 366) = 1, max_per_day: integer(1, 10) = 1, send_from: integer(0, 23) = 8, send_until: integer(1, 24) = 20, timezone: text = "Asia/Riyadh") {
65
+ constraints { send_from: less_than(send_until) }
66
+ summary: "Send an email to the installment borrower's stored contact address."
67
+ fields { slice: ref<on.slice> }
68
+ lifecycle { states: [ready], initial: ready }
69
+ action create { requires unique "installment_reminder" on [self.slice] }
70
+ action dispatch {
71
+ from: ready, to: ready, actor: { party: operator }
72
+ invoke: [{ reference: self.id, action: send, input: {} }]
73
+ }
74
+ action before_due {
75
+ from: ready, to: ready, actor: clock
76
+ due: { at: self.slice.dueAt, localDay: { days: before_days, direction: "before", hour: send_from, timezone: timezone } }
77
+ invoke: [{ selection: { instrument: current(), reference: "slice", anchor: self.slice, states: [ready], limit: 1, where: { slice.plan.status: { literal: "active" } } }, action: send, guard: { kind: compare, left: { field: self.slice.principalReceivable.balance }, operator: ">", right: { literal: "0" } }, input: {} }]
78
+ }
79
+ action overdue {
80
+ from: ready, to: ready, actor: clock
81
+ due: { at: self.slice.dueAt, localDay: { days: overdue_days, direction: "after", hour: send_from, timezone: timezone } }
82
+ invoke: [{ selection: { instrument: current(), reference: "slice", anchor: self.slice, states: [ready], limit: 1, where: { slice.plan.status: { literal: "active" } } }, action: send, guard: { kind: compare, left: { field: self.slice.principalReceivable.balance }, operator: ">", right: { literal: "0" } }, input: {} }]
83
+ }
84
+ action send {
85
+ from: ready, to: ready, actor: { parent: current() }
86
+ requires self.slice.plan in [active]
87
+ requires self.slice.principalReceivable.balance > "0"
88
+ reminder: { installment: self.slice, recipient: self.slice.plan.borrower, dueAt: self.slice.dueAt, channel: "email", template: "payment_reminder", beforeDays: before_days, overdueDays: overdue_days, maxPerDay: max_per_day, startHour: send_from, endHour: send_until, timezone: timezone }
89
+ }
90
+ }
package/std/escrow.hsx ADDED
@@ -0,0 +1,73 @@
1
+ header escrow
2
+
3
+ // Economics: Funding and unspent returns are principal. Acceptance realizes earnings for
4
+ // the company or a payout to a bound outside seller; tax is pass-through.
5
+
6
+ // Expose create, fund, deliver and accept for the purchase path. Create captures
7
+ // subject.price; acceptance pays payee less the configured fee, not payer.
8
+ // Expose dispute, verify_return and refund separately for the return path.
9
+ // Payer disputes delivered goods; payee verifies return evidence from disputed;
10
+ // payee refunds the entire held price from return_verified by default.
11
+ // Acceptance expiry enters disputed, never released. Neither verify_return nor
12
+ // refund becomes public merely because dispute is exposed. Clock actions remain
13
+ // internal. Expose cancel for a pending hold, including a checkout-funded hold;
14
+ // it returns its credited balance to payer before cancelling. Confirm requires
15
+ // the whole captured price in held. This flow has no partial release or refund.
16
+
17
+ // Cost: create declares one owned account. Fund and refund each declare one
18
+ // transfer; accept expands the configured fee and tax into moves. Run hsx cost
19
+ // on the composed program for instruction counts. The executor supplies prices.
20
+
21
+ instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
22
+ revisioned: true
23
+ summary: "Hold the subject price through delivery, acceptance and verified return."
24
+ fields {
25
+ payer: account of payer, payee: account of payee
26
+ held: account of self, deliveredAt: date?
27
+ price: money = subject.price
28
+ }
29
+ lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }
30
+ action create {
31
+ subject { price: money }
32
+ actor: { party: payer }
33
+ }
34
+ action fund {
35
+ from: pending, to: funded, actor: { party: payer }
36
+ subject { price: money }
37
+ moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
38
+ }
39
+ action confirm {
40
+ from: pending, to: funded
41
+ requires self.held.balance == self.price
42
+ }
43
+ action deliver {
44
+ from: funded, to: delivered, actor: { party: payee }
45
+ set: { deliveredAt: { field: self.now } }
46
+ }
47
+ action accept { allowZero: true, requires self.price > "0";
48
+ from: delivered, to: released, actor: { party: payer }
49
+ input { acceptanceReference: text }
50
+ deadline: { at: self.deliveredAt, offset: accept_within }
51
+ moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: participant_payout, sourceParty: programOperator } }, fee: { purpose: earning, sourceParty: payer }, tax: { purpose: pass_through, sourceParty: programOperator } }
52
+ }
53
+ action expire_acceptance {
54
+ from: delivered, to: disputed, actor: clock
55
+ due: { at: self.deliveredAt, offset: accept_within }
56
+ }
57
+ action dispute {
58
+ from: delivered, to: disputed, actor: { party: payer }
59
+ deadline: { at: self.deliveredAt, offset: accept_within }
60
+ }
61
+ action verify_return {
62
+ from: disputed, to: return_verified, actor: { party: payee }
63
+ input { returnEvidence: text }
64
+ }
65
+ action refund {
66
+ from: dispute.refund_after, to: refunded, actor: { party: payee }
67
+ moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
68
+ }
69
+ action cancel { allowZero: true,
70
+ from: pending, to: cancelled, actor: { party: payer }
71
+ moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
72
+ }
73
+ }