@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
package/src/cost.ts CHANGED
@@ -1,579 +1,76 @@
1
- import {
2
- udlEffectKinds,
3
- type UdlDocument,
4
- type UdlEffectKind,
5
- } from "@hyperscale0/udl";
6
- import type { GeneralDiagnostic, JsonValue, TypedProgram } from "./ir.ts";
7
-
8
- export type HsxEffectKind = UdlEffectKind;
9
-
10
- export interface UdlCostTable {
11
- readonly version: string;
12
- /** Digest of the effective prices and fixed anchors after overrides. */
13
- readonly effectiveTableDigest: string;
14
- readonly currency: string;
15
- /** Billable runtime meters the platform's operation contract can emit. */
16
- readonly declaredMeters: readonly string[];
17
- readonly rows: readonly {
18
- readonly signature: string;
19
- readonly perEventMinor: string;
20
- readonly bps: number;
21
- readonly payer: "product" | "end_customer";
22
- /** Collection policy. Runtime resolves movement rows to execution or invoice. */
23
- readonly settlement: "per_event" | "invoice";
24
- readonly meter: string;
25
- readonly volumeMeter?: string;
26
- }[];
27
- readonly fixed: {
28
- readonly weights: Readonly<Record<string, number>>;
29
- readonly monthlyBaseMinor: string;
30
- readonly monthlyPerPointMinor: string;
31
- readonly activationBaseMinor: string;
32
- readonly activationPerPointMinor: string;
33
- };
34
- }
35
-
1
+ import { resolveField, type UdlDocument } from "@hyperscale0/udl";
36
2
  export interface UdlCostManifest {
37
- readonly costTableVersion: string;
38
- readonly effectiveTableDigest: string;
39
- readonly currency: string;
40
- /** Complete runtime meter allowlist, independent of clause effects and payer. */
41
- readonly declaredMeters: readonly string[];
42
- readonly fixed: {
43
- readonly complexityScore: number;
44
- readonly monthlyMinor: string;
45
- readonly activationMinor: string;
46
- readonly components: readonly {
47
- readonly weight: string;
48
- readonly count: number;
49
- }[];
50
- };
51
- readonly actions: readonly {
52
- readonly instrument: string;
53
- readonly action: string;
54
- readonly components: readonly {
55
- readonly signature: string;
56
- readonly count: number;
57
- readonly perEventMinor: string;
58
- readonly bps: number;
59
- readonly payer: "product" | "end_customer";
60
- readonly settlement: "per_event" | "invoice";
61
- readonly meter: string;
62
- readonly volumeMeter?: string;
63
- }[];
64
- readonly perEventMinor: string;
65
- readonly endCustomerPerEventMinor: string;
66
- }[];
67
- /** Frozen list prices for every declared runtime meter. */
68
- readonly meterPrices: readonly UdlCostTable["rows"][number][];
69
- readonly monthlyEstimate: {
70
- readonly expression: string;
71
- readonly variables: readonly string[];
72
- };
73
- }
74
-
75
- export type CostManifestResult =
76
- | { readonly diagnostics: readonly GeneralDiagnostic[]; readonly ok: false }
77
- | { readonly manifest: UdlCostManifest; readonly ok: true };
78
-
79
- export function buildCostManifest(
80
- program: TypedProgram,
81
- table: UdlCostTable | undefined,
82
- composesCatalogBlueprint: boolean,
83
- ): CostManifestResult {
84
- if (!table) {
85
- return {
86
- diagnostics: [
87
- {
88
- code: "HSX1301",
89
- fix: "supply the versioned UDL cost table to the compiler",
90
- message:
91
- "the compiler cannot price this program without a cost table",
92
- severity: "error",
93
- span: program.origin,
94
- },
95
- ],
96
- ok: false,
97
- };
98
- }
99
-
100
- if (!/^[A-Z]{3}$/.test(table.currency)) {
101
- return {
102
- diagnostics: [
103
- {
104
- code: "HSX1303",
105
- fix: "name one three-letter uppercase billing currency on the cost table",
106
- message: `cost table ${table.version} has invalid billing currency ${table.currency}`,
107
- severity: "error",
108
- span: program.origin,
109
- },
110
- ],
111
- ok: false,
112
- };
113
- }
114
-
115
- const diagnostics: GeneralDiagnostic[] = [];
116
- const rowsBySignature = new Map<string, UdlCostTable["rows"]>();
117
- for (const row of table.rows) {
118
- rowsBySignature.set(row.signature, [
119
- ...(rowsBySignature.get(row.signature) ?? []),
120
- row,
121
- ]);
122
- }
123
- const actions: UdlCostManifest["actions"][number][] = [];
124
- for (const instrument of program.instruments) {
125
- for (const action of instrument.actions) {
126
- const components: UdlCostManifest["actions"][number]["components"][number][] =
127
- [];
128
- let productPerEvent = 0n;
129
- let endCustomerPerEvent = 0n;
130
- for (const [signature, count] of effectCounts(action.effects)) {
131
- const rows = rowsBySignature.get(signature) ?? [];
132
- if (rows.length === 0) {
133
- diagnostics.push({
134
- code: "HSX1301",
135
- fix: `add ${signature} to cost table ${table.version}`,
136
- message: `${instrument.id}.${action.name} has unpriced effect signature ${signature}`,
137
- severity: "error",
138
- span: action.origin,
139
- });
140
- continue;
141
- }
142
- for (const row of rows) {
143
- if (!validPrice(row)) {
144
- diagnostics.push({
145
- code: "HSX1302",
146
- fix: `store ${signature} prices as nonnegative integers and name a volume meter for bps`,
147
- message: `cost table ${table.version} has an invalid ${signature} price`,
148
- severity: "error",
149
- span: action.origin,
150
- });
151
- continue;
152
- }
153
- const eventTotal = BigInt(row.perEventMinor) * BigInt(count);
154
- if (row.payer === "product") productPerEvent += eventTotal;
155
- else endCustomerPerEvent += eventTotal;
156
- components.push({
157
- bps: row.bps,
158
- count,
159
- meter: row.meter,
160
- payer: row.payer,
161
- perEventMinor: row.perEventMinor,
162
- settlement: row.settlement,
163
- signature,
164
- ...(row.volumeMeter ? { volumeMeter: row.volumeMeter } : {}),
165
- });
166
- }
167
- }
168
- actions.push({
169
- action: action.name,
170
- components,
171
- endCustomerPerEventMinor: endCustomerPerEvent.toString(),
172
- instrument: instrument.id,
173
- perEventMinor: productPerEvent.toString(),
174
- });
175
- }
176
- }
177
- const fixed = fixedCost(
178
- typedStructuralCounts(program, composesCatalogBlueprint),
179
- table,
180
- program.origin,
181
- diagnostics,
182
- );
183
- if (diagnostics.length > 0) return { diagnostics, ok: false };
184
- return { manifest: manifest(table, fixed, actions), ok: true };
185
- }
186
-
187
- export function computeUdlFixedCost(
188
- document: UdlDocument,
189
- table: UdlCostTable,
190
- composesCatalogBlueprint: boolean,
191
- ): UdlCostManifest["fixed"] {
192
- const diagnostics: GeneralDiagnostic[] = [];
193
- const fixed = fixedCost(
194
- udlStructuralCounts(document, composesCatalogBlueprint),
195
- table,
196
- { start: 0, end: 0 },
197
- diagnostics,
198
- );
199
- if (diagnostics.length > 0) throw new Error(diagnostics[0]?.message);
200
- return fixed;
201
- }
202
-
203
- export function buildUdlCostManifest(
204
- document: UdlDocument,
205
- table: UdlCostTable,
206
- composesCatalogBlueprint: boolean,
207
- ): UdlCostManifest {
208
- if (!/^[A-Z]{3}$/.test(table.currency)) {
209
- throw new Error(
210
- `cost table ${table.version} has invalid billing currency ${table.currency}`,
211
- );
212
- }
213
- const rowsBySignature = new Map<string, UdlCostTable["rows"]>();
214
- for (const row of table.rows) {
215
- rowsBySignature.set(row.signature, [
216
- ...(rowsBySignature.get(row.signature) ?? []),
217
- row,
218
- ]);
219
- }
220
- const actions: UdlCostManifest["actions"][number][] = [];
221
- for (const instrument of document.instruments) {
222
- for (const [actionName, action] of Object.entries(instrument.actions)) {
223
- const components: UdlCostManifest["actions"][number]["components"][number][] =
224
- [];
225
- let productPerEvent = 0n;
226
- let endCustomerPerEvent = 0n;
227
- const effects = (action.effects ?? {}) as Readonly<
228
- Partial<
229
- Record<
230
- HsxEffectKind,
231
- readonly { readonly signature: string; readonly source: string }[]
232
- >
233
- >
234
- >;
235
- for (const [signature, count] of effectCounts(effects)) {
236
- const rows = rowsBySignature.get(signature) ?? [];
237
- if (rows.length === 0)
238
- throw new Error(
239
- `${instrument.id}.${actionName} has unpriced effect signature ${signature}`,
240
- );
241
- for (const row of rows) {
242
- if (!validPrice(row))
243
- throw new Error(
244
- `cost table ${table.version} has an invalid ${signature} price`,
245
- );
246
- const eventTotal = BigInt(row.perEventMinor) * BigInt(count);
247
- if (row.payer === "product") productPerEvent += eventTotal;
248
- else endCustomerPerEvent += eventTotal;
249
- components.push({
250
- bps: row.bps,
251
- count,
252
- meter: row.meter,
253
- payer: row.payer,
254
- perEventMinor: row.perEventMinor,
255
- settlement: row.settlement,
256
- signature,
257
- ...(row.volumeMeter ? { volumeMeter: row.volumeMeter } : {}),
258
- });
259
- }
260
- }
261
- actions.push({
262
- action: actionName,
263
- components,
264
- endCustomerPerEventMinor: endCustomerPerEvent.toString(),
265
- instrument: instrument.id,
266
- perEventMinor: productPerEvent.toString(),
267
- });
268
- }
269
- }
270
- return manifest(
271
- table,
272
- computeUdlFixedCost(document, table, composesCatalogBlueprint),
273
- actions,
274
- );
275
- }
276
-
277
- export function evaluateUdlCostManifest(
278
- costManifest: UdlCostManifest,
279
- readings: Readonly<Record<string, string>>,
280
- ): string {
281
- let total = BigInt(costManifest.fixed.monthlyMinor);
282
- const priced = new Set<string>();
283
- for (const price of costManifest.meterPrices) {
284
- if (price.payer !== "product") continue;
285
- const key = pricingKey(price);
286
- if (priced.has(key)) continue;
287
- priced.add(key);
288
- const count = scaledDecimal(readings[price.meter] ?? "0");
289
- total += (BigInt(price.perEventMinor) * count) / 1_000_000n;
290
- if (price.bps > 0 && price.volumeMeter) {
291
- const volume = scaledDecimal(readings[price.volumeMeter] ?? "0");
292
- total += (volume * BigInt(price.bps)) / 10_000n / 1_000_000n;
3
+ actions: Record<
4
+ string,
5
+ {
6
+ transfers: number;
7
+ accounts: number;
8
+ invocations: number;
293
9
  }
294
- }
295
- return total.toString();
296
- }
297
-
298
- function manifest(
299
- table: UdlCostTable,
300
- fixed: UdlCostManifest["fixed"],
301
- actions: UdlCostManifest["actions"],
302
- ): UdlCostManifest {
303
- const declaredMeters = [...new Set(table.declaredMeters)].sort();
304
- const declaredMeterSet = new Set(declaredMeters);
305
- const meterPrices = table.rows.filter(
306
- (row) =>
307
- (row.perEventMinor !== "0" || row.bps > 0) &&
308
- (declaredMeterSet.has(row.meter) ||
309
- (row.volumeMeter !== undefined &&
310
- declaredMeterSet.has(row.volumeMeter))),
311
- );
312
- const variables = new Set<string>();
313
- const terms = [fixed.monthlyMinor];
314
- const priced = new Set<string>();
315
- for (const price of meterPrices) {
316
- if (price.payer !== "product") continue;
317
- const key = pricingKey(price);
318
- if (priced.has(key)) continue;
319
- priced.add(key);
320
- variables.add(price.meter);
321
- if (price.perEventMinor !== "0")
322
- terms.push(`${price.perEventMinor} * ${price.meter}`);
323
- if (price.bps > 0 && price.volumeMeter) {
324
- variables.add(price.volumeMeter);
325
- terms.push(`${price.bps} / 10000 * ${price.volumeMeter}`);
326
- }
327
- }
328
- return {
329
- actions,
330
- costTableVersion: table.version,
331
- currency: table.currency,
332
- declaredMeters,
333
- effectiveTableDigest: table.effectiveTableDigest,
334
- fixed,
335
- meterPrices,
336
- monthlyEstimate: {
337
- expression: terms.join(" + "),
338
- variables: [...variables].sort(),
339
- },
340
- };
341
- }
342
-
343
- function pricingKey(
344
- component: Pick<
345
- UdlCostTable["rows"][number],
346
- | "bps"
347
- | "meter"
348
- | "payer"
349
- | "perEventMinor"
350
- | "settlement"
351
- | "signature"
352
- | "volumeMeter"
353
- >,
354
- ): string {
355
- return [
356
- component.signature,
357
- component.meter,
358
- component.volumeMeter ?? "",
359
- component.perEventMinor,
360
- String(component.bps),
361
- component.payer,
362
- component.settlement,
363
- ].join("\u0000");
364
- }
365
-
366
- function effectCounts(
367
- effects: Readonly<
368
- Partial<
369
- Record<
370
- HsxEffectKind,
371
- readonly { readonly signature: string; readonly source: string }[]
372
- >
373
- >
374
- >,
375
- ): readonly [string, number][] {
376
- const counts = new Map<string, number>();
377
- for (const kind of udlEffectKinds) {
378
- for (const effect of effects[kind] ?? [])
379
- counts.set(effect.signature, (counts.get(effect.signature) ?? 0) + 1);
380
- }
381
- return [...counts.entries()].sort(([left], [right]) =>
382
- left.localeCompare(right),
383
- );
384
- }
385
-
386
- function fixedCost(
387
- counts: Readonly<Record<string, number>>,
388
- table: UdlCostTable,
389
- span: { readonly start: number; readonly end: number },
390
- diagnostics: GeneralDiagnostic[],
391
- ): UdlCostManifest["fixed"] {
392
- const components: UdlCostManifest["fixed"]["components"][number][] = [];
393
- let complexityScore = 0;
394
- for (const [weight, count] of Object.entries(counts)) {
395
- if (count === 0) continue;
396
- const value = table.fixed.weights[weight];
397
- if (value === undefined || !Number.isSafeInteger(value) || value < 0) {
398
- diagnostics.push({
399
- code: "HSX1302",
400
- fix: `add a nonnegative integer ${weight} weight to cost table ${table.version}`,
401
- message: `cost table ${table.version} has an invalid ${weight} complexity weight`,
402
- severity: "error",
403
- span,
404
- });
405
- continue;
10
+ >;
11
+ }
12
+ /** Counts sealed instructions after lowering. Prices belong to the executor's tariff. */
13
+ export function buildUdlCostManifest(document: UdlDocument): UdlCostManifest {
14
+ const actions: UdlCostManifest["actions"] = {};
15
+ const active = new Set<string>();
16
+ const visit = (
17
+ id: string,
18
+ name: string,
19
+ ): UdlCostManifest["actions"][string] => {
20
+ const key = `${id}.${name}`;
21
+ if (actions[key]) return actions[key];
22
+ if (active.has(key)) throw new Error(`Invocation cycle at ${key}`);
23
+ const instrument = document.instruments.find((item) => item.id === id);
24
+ const action = instrument?.actions[name];
25
+ if (!instrument || !action) throw new Error(`Unknown invocation ${key}`);
26
+ active.add(key);
27
+ const cost = {
28
+ transfers: action.moves.length,
29
+ accounts:
30
+ name === "create"
31
+ ? instrument.fields.filter(
32
+ (field) => field.type === "account" && field.owner === "self",
33
+ ).length
34
+ : 0,
35
+ invocations: 0,
36
+ };
37
+ for (const call of action.invoke ?? []) {
38
+ const ref =
39
+ "reference" in call
40
+ ? resolveField(document, instrument, call.reference, action.input)
41
+ : undefined;
42
+ const targets =
43
+ "instrument" in call
44
+ ? call.instrument
45
+ : "selection" in call
46
+ ? call.selection.instrument
47
+ : ref?.type === "ref"
48
+ ? ref.target
49
+ : [];
50
+ const children = (typeof targets === "string" ? [targets] : targets).map(
51
+ (target) => visit(target, call.action),
52
+ );
53
+ const count =
54
+ "selection" in call
55
+ ? call.selection.limit
56
+ : "instrument" in call
57
+ ? (call.range?.maximum ?? 1)
58
+ : 1;
59
+ // A union selects one target per row. Each meter takes its worst case.
60
+ cost.transfers +=
61
+ count * Math.max(0, ...children.map((child) => child.transfers));
62
+ cost.accounts +=
63
+ count * Math.max(0, ...children.map((child) => child.accounts));
64
+ cost.invocations +=
65
+ count *
66
+ (1 + Math.max(0, ...children.map((child) => child.invocations)));
406
67
  }
407
- complexityScore += value * count;
408
- components.push({ count, weight });
409
- }
410
- for (const [field, value] of Object.entries(table.fixed).filter(
411
- ([key]) => key !== "weights",
412
- )) {
413
- if (typeof value === "string" && nonnegativeInteger(value)) continue;
414
- diagnostics.push({
415
- code: "HSX1302",
416
- fix: `store fixed.${field} as a nonnegative decimal integer`,
417
- message: `cost table ${table.version} has an invalid fixed ${field} price`,
418
- severity: "error",
419
- span,
420
- });
421
- }
422
- return {
423
- activationMinor: (
424
- integerOrZero(table.fixed.activationBaseMinor) +
425
- integerOrZero(table.fixed.activationPerPointMinor) *
426
- BigInt(complexityScore)
427
- ).toString(),
428
- complexityScore,
429
- components,
430
- monthlyMinor: (
431
- integerOrZero(table.fixed.monthlyBaseMinor) +
432
- integerOrZero(table.fixed.monthlyPerPointMinor) * BigInt(complexityScore)
433
- ).toString(),
68
+ active.delete(key);
69
+ actions[key] = cost;
70
+ return cost;
434
71
  };
435
- }
436
-
437
- function typedStructuralCounts(
438
- program: TypedProgram,
439
- blueprint: boolean,
440
- ): Readonly<Record<string, number>> {
441
- const subjectKinds = new Set<string>();
442
- const decisions = new Set<string>();
443
- let flow = 0,
444
- move = 0,
445
- unwind = 0,
446
- aggregate = 0,
447
- gate = 0,
448
- timer = 0;
449
- for (const instrument of program.instruments) {
450
- for (const kind of arrayValue(objectValue(instrument.slots.subject)?.kinds))
451
- if (typeof kind === "string") subjectKinds.add(kind);
452
- if (instrument.actions.some((action) => action.slots.quote !== undefined))
453
- unwind += 1;
454
- aggregate += arrayValue(instrument.slots.aggregateInvariants).length;
455
- for (const action of instrument.actions) {
456
- flow += 1;
457
- move += arrayValue(action.slots.moves).length;
458
- gate += arrayValue(action.slots.requiresRefs).length;
459
- if (action.slots.deadline !== undefined) timer += 1;
460
- if (action.slots.due !== undefined) timer += 1;
461
- const capability = objectValue(action.slots.decision)?.capability;
462
- if (typeof capability === "string") decisions.add(capability);
463
- }
464
- }
465
- return countRecord(
466
- program.instruments.length,
467
- subjectKinds.size,
468
- flow,
469
- move,
470
- decisions.size,
471
- unwind,
472
- aggregate,
473
- gate,
474
- timer,
475
- blueprint,
476
- );
477
- }
478
-
479
- function udlStructuralCounts(
480
- document: UdlDocument,
481
- blueprint: boolean,
482
- ): Readonly<Record<string, number>> {
483
- const subjectKinds = new Set<string>();
484
- const decisions = new Set<string>();
485
- let flow = 0,
486
- move = 0,
487
- unwind = 0,
488
- aggregate = 0,
489
- gate = 0,
490
- timer = 0;
491
- for (const instrument of document.instruments) {
492
- for (const kind of instrument.subject?.kinds ?? []) subjectKinds.add(kind);
493
- if (Object.values(instrument.actions).some((action) => action.quote))
494
- unwind += 1;
495
- aggregate += instrument.aggregateInvariants?.length ?? 0;
496
- for (const action of Object.values(instrument.actions)) {
497
- flow += 1;
498
- move += action.moves.length;
499
- gate += action.requiresRefs?.length ?? 0;
500
- if (action.deadline) timer += 1;
501
- if (action.due) timer += 1;
502
- if (action.decision) decisions.add(action.decision.capability);
503
- }
504
- }
505
- return countRecord(
506
- document.instruments.length,
507
- subjectKinds.size,
508
- flow,
509
- move,
510
- decisions.size,
511
- unwind,
512
- aggregate,
513
- gate,
514
- timer,
515
- blueprint,
516
- );
517
- }
518
-
519
- function countRecord(
520
- instrument: number,
521
- subjectKind: number,
522
- flow: number,
523
- move: number,
524
- providerDecision: number,
525
- unwind: number,
526
- aggregateInvariant: number,
527
- gate: number,
528
- timer: number,
529
- blueprint: boolean,
530
- ): Readonly<Record<string, number>> {
531
- return {
532
- instrument,
533
- subject_kind: subjectKind,
534
- flow,
535
- move,
536
- provider_decision: providerDecision,
537
- unwind,
538
- aggregate_invariant: aggregateInvariant,
539
- gate,
540
- timer,
541
- blueprint: blueprint ? 1 : 0,
542
- };
543
- }
544
-
545
- function validPrice(row: UdlCostTable["rows"][number]): boolean {
546
- return (
547
- nonnegativeInteger(row.perEventMinor) &&
548
- Number.isSafeInteger(row.bps) &&
549
- row.bps >= 0 &&
550
- (row.settlement === "per_event" || row.settlement === "invoice") &&
551
- (row.bps === 0 || row.volumeMeter !== undefined)
552
- );
553
- }
554
-
555
- function arrayValue(value: JsonValue | undefined): readonly JsonValue[] {
556
- return Array.isArray(value) ? value : [];
557
- }
558
- function objectValue(
559
- value: JsonValue | undefined,
560
- ): Readonly<Record<string, JsonValue>> | undefined {
561
- return value !== null && typeof value === "object" && !Array.isArray(value)
562
- ? (value as Readonly<Record<string, JsonValue>>)
563
- : undefined;
564
- }
565
- function nonnegativeInteger(value: string): boolean {
566
- return /^(0|[1-9][0-9]*)$/.test(value);
567
- }
568
- function integerOrZero(value: string): bigint {
569
- return nonnegativeInteger(value) ? BigInt(value) : 0n;
570
- }
571
-
572
- function scaledDecimal(value: string): bigint {
573
- const match = /^(0|[1-9][0-9]*)(?:\.([0-9]{1,6}))?$/.exec(value);
574
- if (!match) throw new Error(`invalid meter reading ${value}`);
575
- return (
576
- BigInt(match[1] ?? "0") * 1_000_000n +
577
- BigInt((match[2] ?? "").padEnd(6, "0"))
578
- );
72
+ for (const instrument of document.instruments)
73
+ for (const name of Object.keys(instrument.actions))
74
+ visit(instrument.id, name);
75
+ return { actions };
579
76
  }