@hyperscale0/hsx 1.0.0-rc.1 → 1.0.123

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 (311) hide show
  1. package/CHANGELOG.md +5 -239
  2. package/LICENSE.md +356 -0
  3. package/NOTICE.md +8 -0
  4. package/README.md +45 -43
  5. package/bin/hsx.ts +0 -5
  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 -4
  9. package/dist/bin/hsx.js.map +1 -1
  10. package/dist/src/ast.d.ts +113 -302
  11. package/dist/src/ast.d.ts.map +1 -1
  12. package/dist/src/ast.js +7 -47
  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 -17
  23. package/dist/src/cli.d.ts.map +1 -1
  24. package/dist/src/cli.js +92 -259
  25. package/dist/src/cli.js.map +1 -1
  26. package/dist/src/compile.d.ts +34 -68
  27. package/dist/src/compile.d.ts.map +1 -1
  28. package/dist/src/compile.js +2949 -118
  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 +8 -11
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/index.js +5 -8
  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 +2 -0
  79. package/dist/src/std-bundle.d.ts.map +1 -0
  80. package/dist/src/std-bundle.js +15 -0
  81. package/dist/src/std-bundle.js.map +1 -0
  82. package/dist/src/std-library.d.ts +5 -0
  83. package/dist/src/std-library.d.ts.map +1 -0
  84. package/dist/src/std-library.js +5 -0
  85. package/dist/src/std-library.js.map +1 -0
  86. package/dist/src/tunables.d.ts +7 -0
  87. package/dist/src/tunables.d.ts.map +1 -0
  88. package/dist/src/tunables.js +27 -0
  89. package/dist/src/tunables.js.map +1 -0
  90. package/docs/README.md +528 -23
  91. package/docs/assets/brand/hsx-horizontal-white.svg +1 -0
  92. package/docs/assets/brand/hsx-horizontal.svg +1 -0
  93. package/docs/assets/brand/hsx-stacked-white.svg +1 -0
  94. package/docs/assets/brand/hsx-stacked.svg +1 -0
  95. package/docs/assets/brand/manifest.json +30 -0
  96. package/docs/assets/hsx.svg +11 -0
  97. package/examples/cards.hsx +34 -0
  98. package/examples/collections.hsx +37 -0
  99. package/examples/cost-table.json +633 -385
  100. package/examples/escrow.hsx +24 -0
  101. package/examples/financing.hsx +32 -0
  102. package/examples/insurance.hsx +30 -0
  103. package/examples/lending.hsx +57 -0
  104. package/examples/library.hsx +15 -0
  105. package/examples/marketplace.hsx +32 -0
  106. package/examples/money.hsx +19 -0
  107. package/examples/rental-deposit.hsx +60 -0
  108. package/examples/repair-approval.hsx +44 -0
  109. package/examples/reporting.hsx +28 -0
  110. package/examples/savings.hsx +21 -0
  111. package/examples/serviced.hsx +25 -0
  112. package/examples/wallet.hsx +24 -0
  113. package/package.json +27 -17
  114. package/src/ast.ts +84 -411
  115. package/src/attachment-economics.ts +103 -0
  116. package/src/binding-contract.ts +104 -0
  117. package/src/cli.ts +98 -309
  118. package/src/compile.ts +4063 -212
  119. package/src/cost.ts +71 -574
  120. package/src/diagnostics.ts +57 -437
  121. package/src/examples-bundle.ts +19 -0
  122. package/src/examples.ts +9 -0
  123. package/src/format.ts +12 -256
  124. package/src/header-source.ts +54 -0
  125. package/src/headers.ts +195 -0
  126. package/src/index.ts +16 -29
  127. package/src/keywords.ts +134 -0
  128. package/src/language.ts +629 -0
  129. package/src/lex.ts +131 -192
  130. package/src/parse.ts +489 -1195
  131. package/src/std-bundle.ts +15 -0
  132. package/src/std-library.ts +7 -0
  133. package/src/tunables.ts +36 -0
  134. package/std/cards.hsx +85 -0
  135. package/std/collections.hsx +90 -0
  136. package/std/escrow.hsx +73 -0
  137. package/std/financing.hsx +683 -0
  138. package/std/insurance.hsx +89 -0
  139. package/std/lending.hsx +138 -0
  140. package/std/marketplace.hsx +123 -0
  141. package/std/money.hsx +173 -0
  142. package/std/reporting.hsx +753 -0
  143. package/std/savings.hsx +68 -0
  144. package/std/wallet.hsx +57 -0
  145. package/LICENSE +0 -661
  146. package/LICENSING.md +0 -52
  147. package/TRADEMARKS.md +0 -35
  148. package/dist/src/emit.d.ts +0 -35
  149. package/dist/src/emit.d.ts.map +0 -1
  150. package/dist/src/emit.js +0 -359
  151. package/dist/src/emit.js.map +0 -1
  152. package/dist/src/entry-overrides.d.ts +0 -58
  153. package/dist/src/entry-overrides.d.ts.map +0 -1
  154. package/dist/src/entry-overrides.js +0 -284
  155. package/dist/src/entry-overrides.js.map +0 -1
  156. package/dist/src/ir.d.ts +0 -66
  157. package/dist/src/ir.d.ts.map +0 -1
  158. package/dist/src/ir.js +0 -15
  159. package/dist/src/ir.js.map +0 -1
  160. package/dist/src/limits.d.ts +0 -21
  161. package/dist/src/limits.d.ts.map +0 -1
  162. package/dist/src/limits.js +0 -21
  163. package/dist/src/limits.js.map +0 -1
  164. package/dist/src/modules.d.ts +0 -36
  165. package/dist/src/modules.d.ts.map +0 -1
  166. package/dist/src/modules.js +0 -372
  167. package/dist/src/modules.js.map +0 -1
  168. package/dist/src/typecheck.d.ts +0 -8
  169. package/dist/src/typecheck.d.ts.map +0 -1
  170. package/dist/src/typecheck.js +0 -2958
  171. package/dist/src/typecheck.js.map +0 -1
  172. package/dist/src/version.d.ts +0 -5
  173. package/dist/src/version.d.ts.map +0 -1
  174. package/dist/src/version.js +0 -5
  175. package/dist/src/version.js.map +0 -1
  176. package/docs/guide/01-first-program.md +0 -24
  177. package/docs/guide/02-money.md +0 -25
  178. package/docs/guide/03-instruments.md +0 -22
  179. package/docs/guide/04-lifecycles.md +0 -25
  180. package/docs/guide/05-fees-and-splits.md +0 -45
  181. package/docs/guide/06-schedules.md +0 -22
  182. package/docs/guide/07-composition.md +0 -46
  183. package/docs/guide/08-writing-a-module.md +0 -26
  184. package/docs/guide/09-cost.md +0 -24
  185. package/docs/guide/10-diagnostics.md +0 -27
  186. package/docs/llms-full.txt +0 -1738
  187. package/docs/llms.txt +0 -45
  188. package/docs/reference/cli.md +0 -33
  189. package/docs/reference/diagnostics.md +0 -619
  190. package/docs/reference/grammar.md +0 -39
  191. package/docs/reference/std/advance.md +0 -35
  192. package/docs/reference/std/cancellable_booking.md +0 -35
  193. package/docs/reference/std/captured_payment.md +0 -43
  194. package/docs/reference/std/conditional_disbursement.md +0 -33
  195. package/docs/reference/std/credit_facility.md +0 -35
  196. package/docs/reference/std/held_payment.md +0 -64
  197. package/docs/reference/std/instant_transfer.md +0 -30
  198. package/docs/reference/std/metered.md +0 -27
  199. package/docs/reference/std/pooled_split.md +0 -26
  200. package/docs/reference/std/premium_forward.md +0 -39
  201. package/docs/reference/std/reconciled_payout.md +0 -33
  202. package/docs/reference/std/recurring_collection.md +0 -25
  203. package/docs/reference/std/rotating_pool.md +0 -42
  204. package/docs/reference/std/scheduled.md +0 -51
  205. package/docs/reference/std/security_deposit.md +0 -46
  206. package/docs/reference/std/settlement_batch.md +0 -42
  207. package/docs/reference/std/swap.md +0 -46
  208. package/docs/reference/std/threshold_pool.md +0 -47
  209. package/docs/reference/std/weighted_distribution.md +0 -39
  210. package/docs/reference/types.md +0 -20
  211. package/docs/reference/udl-output.md +0 -14
  212. package/docs/sessions/2026-09-02-two-instruments.hsx +0 -21
  213. package/docs/sessions/2026-09-02-two-instruments.md +0 -41
  214. package/examples/01-first-program/README.md +0 -45
  215. package/examples/01-first-program/tip-jar.hsx +0 -17
  216. package/examples/02-imports-and-archetypes/README.md +0 -99
  217. package/examples/02-imports-and-archetypes/photo-booth.hsx +0 -37
  218. package/examples/03-diagnostics/README.md +0 -68
  219. package/examples/03-diagnostics/corner-shop-fixed.hsx +0 -22
  220. package/examples/03-diagnostics/corner-shop.hsx +0 -19
  221. package/examples/04-complete-product/README.md +0 -87
  222. package/examples/04-complete-product/study-hall.hsx +0 -67
  223. package/examples/05-watch-club/README.md +0 -9
  224. package/examples/05-watch-club/watch-club.hsx +0 -124
  225. package/examples/README.md +0 -18
  226. package/examples/advance/README.md +0 -3
  227. package/examples/advance/advance.hsx +0 -13
  228. package/examples/advance/advance.udl +0 -339
  229. package/examples/cancellable_booking/README.md +0 -3
  230. package/examples/cancellable_booking/cancellable_booking.hsx +0 -14
  231. package/examples/cancellable_booking/cancellable_booking.udl +0 -391
  232. package/examples/captured_payment/README.md +0 -3
  233. package/examples/captured_payment/captured_payment.hsx +0 -21
  234. package/examples/captured_payment/captured_payment.udl +0 -677
  235. package/examples/conditional_disbursement/README.md +0 -3
  236. package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -17
  237. package/examples/conditional_disbursement/conditional_disbursement.udl +0 -412
  238. package/examples/credit_facility/README.md +0 -3
  239. package/examples/credit_facility/credit_facility.hsx +0 -27
  240. package/examples/credit_facility/credit_facility.udl +0 -1373
  241. package/examples/held_payment/README.md +0 -3
  242. package/examples/held_payment/held_payment.hsx +0 -13
  243. package/examples/held_payment/held_payment.udl +0 -507
  244. package/examples/instant_transfer/README.md +0 -3
  245. package/examples/instant_transfer/instant_transfer.hsx +0 -16
  246. package/examples/instant_transfer/instant_transfer.udl +0 -364
  247. package/examples/metered/README.md +0 -3
  248. package/examples/metered/metered.hsx +0 -13
  249. package/examples/metered/metered.udl +0 -247
  250. package/examples/pooled_split/README.md +0 -3
  251. package/examples/pooled_split/pooled_split.hsx +0 -15
  252. package/examples/pooled_split/pooled_split.udl +0 -425
  253. package/examples/premium_forward/README.md +0 -3
  254. package/examples/premium_forward/premium_forward.hsx +0 -19
  255. package/examples/premium_forward/premium_forward.udl +0 -594
  256. package/examples/reconciled_payout/README.md +0 -3
  257. package/examples/reconciled_payout/reconciled_payout.hsx +0 -13
  258. package/examples/reconciled_payout/reconciled_payout.udl +0 -303
  259. package/examples/recurring_collection/README.md +0 -3
  260. package/examples/recurring_collection/recurring_collection.hsx +0 -21
  261. package/examples/recurring_collection/recurring_collection.udl +0 -1130
  262. package/examples/rotating_pool/README.md +0 -3
  263. package/examples/rotating_pool/rotating_pool.hsx +0 -18
  264. package/examples/rotating_pool/rotating_pool.udl +0 -4352
  265. package/examples/scheduled/README.md +0 -3
  266. package/examples/scheduled/scheduled.hsx +0 -12
  267. package/examples/scheduled/scheduled.udl +0 -335
  268. package/examples/security_deposit/README.md +0 -3
  269. package/examples/security_deposit/security_deposit.hsx +0 -21
  270. package/examples/security_deposit/security_deposit.udl +0 -293
  271. package/examples/settlement_batch/README.md +0 -3
  272. package/examples/settlement_batch/settlement_batch.hsx +0 -19
  273. package/examples/settlement_batch/settlement_batch.udl +0 -753
  274. package/examples/swap/README.md +0 -3
  275. package/examples/swap/swap.hsx +0 -19
  276. package/examples/swap/swap.udl +0 -876
  277. package/examples/threshold_pool/README.md +0 -3
  278. package/examples/threshold_pool/threshold_pool.hsx +0 -16
  279. package/examples/threshold_pool/threshold_pool.udl +0 -667
  280. package/examples/weighted_distribution/README.md +0 -3
  281. package/examples/weighted_distribution/weighted_distribution.hsx +0 -20
  282. package/examples/weighted_distribution/weighted_distribution.udl +0 -337
  283. package/skills/hsx/SKILL.md +0 -483
  284. package/src/emit.ts +0 -447
  285. package/src/entry-overrides.ts +0 -428
  286. package/src/ir.ts +0 -99
  287. package/src/limits.ts +0 -20
  288. package/src/modules.ts +0 -444
  289. package/src/typecheck.ts +0 -3876
  290. package/src/version.ts +0 -5
  291. package/std/SEMANTICS.md +0 -130
  292. package/std/settlements/advance.hsx +0 -177
  293. package/std/settlements/cancellable_booking.hsx +0 -87
  294. package/std/settlements/captured_payment.hsx +0 -144
  295. package/std/settlements/conditional_disbursement.hsx +0 -185
  296. package/std/settlements/credit_facility.hsx +0 -167
  297. package/std/settlements/held_payment.hsx +0 -578
  298. package/std/settlements/index.hsx +0 -3
  299. package/std/settlements/instant_transfer.hsx +0 -190
  300. package/std/settlements/metered.hsx +0 -67
  301. package/std/settlements/pooled_split.hsx +0 -68
  302. package/std/settlements/premium_forward.hsx +0 -257
  303. package/std/settlements/reconciled_payout.hsx +0 -77
  304. package/std/settlements/recurring_collection.hsx +0 -27
  305. package/std/settlements/rotating_pool.hsx +0 -252
  306. package/std/settlements/scheduled.hsx +0 -641
  307. package/std/settlements/security_deposit.hsx +0 -166
  308. package/std/settlements/settlement_batch.hsx +0 -122
  309. package/std/settlements/swap.hsx +0 -1114
  310. package/std/settlements/threshold_pool.hsx +0 -221
  311. 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
  }