@hyperscale0/udl 1.0.0 → 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 (234) hide show
  1. package/CHANGELOG.md +5 -216
  2. package/LICENSE.md +356 -0
  3. package/NOTICE.md +8 -0
  4. package/README.md +14 -106
  5. package/dist/canonical.d.ts.map +1 -1
  6. package/dist/canonical.js +1 -29
  7. package/dist/canonical.js.map +1 -1
  8. package/dist/diagnostics.d.ts +13 -165
  9. package/dist/diagnostics.d.ts.map +1 -1
  10. package/dist/diagnostics.js +33 -157
  11. package/dist/diagnostics.js.map +1 -1
  12. package/dist/field-value.d.ts +5 -0
  13. package/dist/field-value.d.ts.map +1 -0
  14. package/dist/field-value.js +85 -0
  15. package/dist/field-value.js.map +1 -0
  16. package/dist/finance.d.ts +10 -53
  17. package/dist/finance.d.ts.map +1 -1
  18. package/dist/finance.js +252 -536
  19. package/dist/finance.js.map +1 -1
  20. package/dist/index.d.ts +9 -13
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +9 -9
  23. package/dist/index.js.map +1 -1
  24. package/dist/json.d.ts +2 -0
  25. package/dist/json.d.ts.map +1 -0
  26. package/dist/json.js +30 -0
  27. package/dist/json.js.map +1 -0
  28. package/dist/limits.d.ts +3 -28
  29. package/dist/limits.d.ts.map +1 -1
  30. package/dist/limits.js +5 -31
  31. package/dist/limits.js.map +1 -1
  32. package/dist/object-contract.d.ts +140 -0
  33. package/dist/object-contract.d.ts.map +1 -0
  34. package/dist/object-contract.js +314 -0
  35. package/dist/object-contract.js.map +1 -0
  36. package/dist/object-semantics-schema.d.ts +5710 -0
  37. package/dist/object-semantics-schema.d.ts.map +1 -0
  38. package/dist/object-semantics-schema.js +119 -0
  39. package/dist/object-semantics-schema.js.map +1 -0
  40. package/dist/object-semantics.d.ts +6 -0
  41. package/dist/object-semantics.d.ts.map +1 -0
  42. package/dist/object-semantics.js +279 -0
  43. package/dist/object-semantics.js.map +1 -0
  44. package/dist/object-structures.d.ts +4 -0
  45. package/dist/object-structures.d.ts.map +1 -0
  46. package/dist/object-structures.js +134 -0
  47. package/dist/object-structures.js.map +1 -0
  48. package/dist/reporting-validation.d.ts +8 -0
  49. package/dist/reporting-validation.d.ts.map +1 -0
  50. package/dist/reporting-validation.js +252 -0
  51. package/dist/reporting-validation.js.map +1 -0
  52. package/dist/reporting.d.ts +477 -0
  53. package/dist/reporting.d.ts.map +1 -0
  54. package/dist/reporting.js +169 -0
  55. package/dist/reporting.js.map +1 -0
  56. package/dist/schema.d.ts +5572 -2710
  57. package/dist/schema.d.ts.map +1 -1
  58. package/dist/schema.js +444 -1121
  59. package/dist/schema.js.map +1 -1
  60. package/dist/validation.d.ts +10 -73
  61. package/dist/validation.d.ts.map +1 -1
  62. package/dist/validation.js +1109 -2569
  63. package/dist/validation.js.map +1 -1
  64. package/docs/assets/brand/manifest.json +30 -0
  65. package/docs/assets/brand/udl-horizontal-white.svg +1 -0
  66. package/docs/assets/brand/udl-horizontal.svg +1 -0
  67. package/docs/assets/brand/udl-stacked-white.svg +1 -0
  68. package/docs/assets/brand/udl-stacked.svg +1 -0
  69. package/docs/assets/udl.svg +11 -0
  70. package/package.json +13 -22
  71. package/spec/README.md +300 -172
  72. package/spec/cars.udl.json +249 -0
  73. package/spec/udl.schema.json +3066 -2242
  74. package/src/canonical.ts +1 -35
  75. package/src/diagnostics.ts +76 -209
  76. package/src/field-value.ts +87 -0
  77. package/src/finance.ts +327 -832
  78. package/src/index.ts +18 -95
  79. package/src/json.ts +34 -0
  80. package/src/limits.ts +5 -31
  81. package/src/object-contract.ts +559 -0
  82. package/src/object-semantics-schema.ts +148 -0
  83. package/src/object-semantics.ts +378 -0
  84. package/src/object-structures.ts +255 -0
  85. package/src/reporting-validation.ts +383 -0
  86. package/src/reporting.ts +178 -0
  87. package/src/schema.ts +510 -1277
  88. package/src/validation.ts +1630 -4545
  89. package/LICENSE +0 -661
  90. package/LICENSING.md +0 -51
  91. package/TRADEMARKS.md +0 -35
  92. package/conformance/README.md +0 -82
  93. package/conformance/evolution/action-contract.expected.json +0 -10
  94. package/conformance/evolution/action-contract.live.udl +0 -44
  95. package/conformance/evolution/action-contract.next.udl +0 -45
  96. package/conformance/evolution/product-identity.expected.json +0 -10
  97. package/conformance/evolution/product-identity.live.udl +0 -55
  98. package/conformance/evolution/product-identity.next.udl +0 -55
  99. package/conformance/evolution/version-required.expected.json +0 -10
  100. package/conformance/evolution/version-required.live.udl +0 -55
  101. package/conformance/evolution/version-required.next.udl +0 -58
  102. package/conformance/invalid/action-without-transition.expected.json +0 -10
  103. package/conformance/invalid/action-without-transition.udl +0 -60
  104. package/conformance/invalid/agent-description-too-long.expected.json +0 -10
  105. package/conformance/invalid/agent-description-too-long.udl +0 -57
  106. package/conformance/invalid/blank-title.expected.json +0 -10
  107. package/conformance/invalid/blank-title.udl +0 -54
  108. package/conformance/invalid/composition-dial-duplicate-key.expected.json +0 -10
  109. package/conformance/invalid/composition-dial-duplicate-key.udl +0 -73
  110. package/conformance/invalid/depth-budget.expected.json +0 -10
  111. package/conformance/invalid/depth-budget.udl +0 -49
  112. package/conformance/invalid/duplicate-subject.expected.json +0 -10
  113. package/conformance/invalid/duplicate-subject.udl +0 -230
  114. package/conformance/invalid/forged-effects.expected.json +0 -10
  115. package/conformance/invalid/forged-effects.udl +0 -76
  116. package/conformance/invalid/format-version.expected.json +0 -10
  117. package/conformance/invalid/format-version.udl +0 -54
  118. package/conformance/invalid/instrument-id-not-snake-case.expected.json +0 -10
  119. package/conformance/invalid/instrument-id-not-snake-case.udl +0 -54
  120. package/conformance/invalid/invalid-aggregate-gate-shape.expected.json +0 -10
  121. package/conformance/invalid/invalid-aggregate-gate-shape.udl +0 -1819
  122. package/conformance/invalid/invalid-check-duration.expected.json +0 -10
  123. package/conformance/invalid/invalid-check-duration.udl +0 -219
  124. package/conformance/invalid/invalid-dial-anchor.expected.json +0 -10
  125. package/conformance/invalid/invalid-dial-anchor.udl +0 -219
  126. package/conformance/invalid/invalid-exception-parent-ref.expected.json +0 -10
  127. package/conformance/invalid/invalid-exception-parent-ref.udl +0 -2487
  128. package/conformance/invalid/invalid-exposure-shape.expected.json +0 -10
  129. package/conformance/invalid/invalid-exposure-shape.udl +0 -1819
  130. package/conformance/invalid/invalid-remainder.expected.json +0 -10
  131. package/conformance/invalid/invalid-remainder.udl +0 -220
  132. package/conformance/invalid/invalid-schema-keyword.expected.json +0 -10
  133. package/conformance/invalid/invalid-schema-keyword.udl +0 -220
  134. package/conformance/invalid/invalid-utf8.expected.json +0 -10
  135. package/conformance/invalid/invalid-utf8.udl +0 -1
  136. package/conformance/invalid/malformed-json.expected.json +0 -10
  137. package/conformance/invalid/malformed-json.udl +0 -1
  138. package/conformance/invalid/missing-create-action.expected.json +0 -10
  139. package/conformance/invalid/missing-create-action.udl +0 -48
  140. package/conformance/invalid/missing-exception-amount-field.expected.json +0 -10
  141. package/conformance/invalid/missing-exception-amount-field.udl +0 -2487
  142. package/conformance/invalid/missing-exception-contract.expected.json +0 -14
  143. package/conformance/invalid/missing-exception-contract.udl +0 -2523
  144. package/conformance/invalid/missing-exception-reason-field.expected.json +0 -10
  145. package/conformance/invalid/missing-exception-reason-field.udl +0 -2487
  146. package/conformance/invalid/not-an-object.expected.json +0 -10
  147. package/conformance/invalid/not-an-object.udl +0 -1
  148. package/conformance/invalid/payout-reconcile-not-a-bank-debit.expected.json +0 -10
  149. package/conformance/invalid/payout-reconcile-not-a-bank-debit.udl +0 -259
  150. package/conformance/invalid/quote-freeze-set-incomplete.expected.json +0 -10
  151. package/conformance/invalid/quote-freeze-set-incomplete.udl +0 -261
  152. package/conformance/invalid/quote-named-reference-gate.expected.json +0 -10
  153. package/conformance/invalid/quote-named-reference-gate.udl +0 -50
  154. package/conformance/invalid/reconcile-named-reference-gate.expected.json +0 -10
  155. package/conformance/invalid/reconcile-named-reference-gate.udl +0 -50
  156. package/conformance/invalid/unknown-key.expected.json +0 -10
  157. package/conformance/invalid/unknown-key.udl +0 -55
  158. package/conformance/invalid/unknown-reference-gate-field.expected.json +0 -10
  159. package/conformance/invalid/unknown-reference-gate-field.udl +0 -1819
  160. package/conformance/invalid/unknown-required-field.expected.json +0 -10
  161. package/conformance/invalid/unknown-required-field.udl +0 -220
  162. package/conformance/invalid/unreachable-state.expected.json +0 -10
  163. package/conformance/invalid/unreachable-state.udl +0 -55
  164. package/conformance/invalid/wrong-exception-amount-field.expected.json +0 -10
  165. package/conformance/invalid/wrong-exception-amount-field.udl +0 -2487
  166. package/conformance/invalid/wrong-exception-reason-field.expected.json +0 -10
  167. package/conformance/invalid/wrong-exception-reason-field.udl +0 -2487
  168. package/conformance/valid/agent-description.expected.json +0 -6
  169. package/conformance/valid/agent-description.udl +0 -66
  170. package/conformance/valid/cards.expected.json +0 -6
  171. package/conformance/valid/cards.udl +0 -1579
  172. package/conformance/valid/commerce-escrow.expected.json +0 -6
  173. package/conformance/valid/commerce-escrow.udl +0 -1512
  174. package/conformance/valid/compiled-crowdfunding.expected.json +0 -6
  175. package/conformance/valid/compiled-crowdfunding.udl +0 -1843
  176. package/conformance/valid/compiled-watch-club.expected.json +0 -6
  177. package/conformance/valid/compiled-watch-club.udl +0 -2486
  178. package/conformance/valid/complete-contract.expected.json +0 -6
  179. package/conformance/valid/complete-contract.udl +0 -218
  180. package/conformance/valid/effect-signatures.expected.json +0 -6
  181. package/conformance/valid/effect-signatures.udl +0 -75
  182. package/conformance/valid/hand-edited.expected.json +0 -6
  183. package/conformance/valid/hand-edited.udl +0 -1
  184. package/conformance/valid/insured-car-marketplace.expected.json +0 -6
  185. package/conformance/valid/insured-car-marketplace.udl +0 -1050
  186. package/conformance/valid/insured-travel.expected.json +0 -6
  187. package/conformance/valid/insured-travel.udl +0 -3469
  188. package/conformance/valid/minimal.expected.json +0 -6
  189. package/conformance/valid/minimal.udl +0 -54
  190. package/conformance/valid/protection.expected.json +0 -6
  191. package/conformance/valid/protection.udl +0 -1551
  192. package/conformance/valid/string-escaping.expected.json +0 -6
  193. package/conformance/valid/string-escaping.udl +0 -54
  194. package/dist/check-profiles.d.ts +0 -57
  195. package/dist/check-profiles.d.ts.map +0 -1
  196. package/dist/check-profiles.js +0 -62
  197. package/dist/check-profiles.js.map +0 -1
  198. package/dist/cli.d.ts +0 -3
  199. package/dist/cli.d.ts.map +0 -1
  200. package/dist/cli.js +0 -184
  201. package/dist/cli.js.map +0 -1
  202. package/dist/effects.d.ts +0 -41
  203. package/dist/effects.d.ts.map +0 -1
  204. package/dist/effects.js +0 -110
  205. package/dist/effects.js.map +0 -1
  206. package/dist/evolution.d.ts +0 -120
  207. package/dist/evolution.d.ts.map +0 -1
  208. package/dist/evolution.js +0 -567
  209. package/dist/evolution.js.map +0 -1
  210. package/dist/parser.d.ts +0 -4
  211. package/dist/parser.d.ts.map +0 -1
  212. package/dist/parser.js +0 -44
  213. package/dist/parser.js.map +0 -1
  214. package/docs/README.md +0 -14
  215. package/docs/guide/01-a-document.md +0 -37
  216. package/docs/guide/02-money-steps.md +0 -23
  217. package/docs/guide/03-laws.md +0 -18
  218. package/docs/guide/04-fees-and-remainder.md +0 -15
  219. package/docs/guide/05-checks-updates-dials.md +0 -7
  220. package/docs/guide/06-effects.md +0 -11
  221. package/docs/guide/07-evolution.md +0 -11
  222. package/docs/guide/08-implementing.md +0 -13
  223. package/docs/llms-full.txt +0 -1241
  224. package/docs/llms.txt +0 -14
  225. package/docs/reference/canonical.md +0 -16
  226. package/docs/reference/clauses.md +0 -986
  227. package/docs/reference/cli.md +0 -22
  228. package/docs/reference/diagnostics.md +0 -32
  229. package/skills/udl/SKILL.md +0 -28
  230. package/src/check-profiles.ts +0 -80
  231. package/src/cli.ts +0 -196
  232. package/src/effects.ts +0 -163
  233. package/src/evolution.ts +0 -965
  234. package/src/parser.ts +0 -58
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hyperscale0/udl",
3
- "version": "1.0.0",
4
- "description": "The Universal Domain Language: format spec, parser, validator, canonical serializer, and evolution diff.",
3
+ "version": "1.0.123",
4
+ "description": "The Universal Domain Language: format spec and validator.",
5
5
  "keywords": [
6
6
  "udl",
7
7
  "schema",
@@ -19,37 +19,30 @@
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/hyperscale0/hyperscale-udl.git"
21
21
  },
22
- "license": "AGPL-3.0-only",
22
+ "license": "SEE LICENSE IN LICENSE.md",
23
23
  "author": "Hyperscale LLC",
24
24
  "type": "module",
25
25
  "sideEffects": false,
26
26
  "main": "./dist/index.js",
27
27
  "module": "./dist/index.js",
28
28
  "types": "./dist/index.d.ts",
29
- "bin": {
30
- "udl": "./dist/cli.js"
31
- },
32
29
  "exports": {
33
30
  ".": {
34
31
  "types": "./dist/index.d.ts",
35
32
  "default": "./dist/index.js"
36
33
  },
37
34
  "./spec/*": "./spec/*",
38
- "./conformance/*": "./conformance/*",
39
35
  "./package.json": "./package.json"
40
36
  },
41
37
  "files": [
38
+ "LICENSE.md",
39
+ "NOTICE.md",
42
40
  "AUTHORS",
43
41
  "CHANGELOG.md",
44
- "LICENSE",
45
- "LICENSING.md",
46
42
  "README.md",
47
43
  "SECURITY.md",
48
- "TRADEMARKS.md",
49
- "conformance",
50
44
  "docs",
51
45
  "dist",
52
- "skills",
53
46
  "spec",
54
47
  "src"
55
48
  ],
@@ -61,27 +54,25 @@
61
54
  },
62
55
  "scripts": {
63
56
  "build": "tsc -p tsconfig.build.json",
64
- "check": "bun run spec:check && bun run docs:check && bun test && tsc -p tsconfig.json && bun run build && bun scripts/check-bin.ts",
65
- "docs:build": "bun scripts/docs/build.ts --write",
66
- "docs:check": "bun scripts/docs/build.ts --check",
57
+ "typecheck": "tsc -p tsconfig.json",
58
+ "test": "bun test test",
67
59
  "postpack": "bun scripts/pack-exports.ts restore",
68
60
  "prepack": "bun run build && bun scripts/pack-exports.ts apply",
69
61
  "spec": "bun scripts/emit-spec.ts --write",
70
62
  "spec:check": "bun scripts/emit-spec.ts --check",
71
- "test": "bun test",
72
- "typecheck": "tsc -p tsconfig.json"
63
+ "check": "bun run spec:check && bun run typecheck && bun test test"
73
64
  },
74
65
  "dependencies": {
75
- "@cfworker/json-schema": "4.1.1",
76
- "zod": "4.4.3"
66
+ "zod": "4.6.5"
77
67
  },
78
68
  "devDependencies": {
79
- "@types/bun": "1.4.0",
80
- "typescript": "6.0.3"
69
+ "bun-types": "1.4.2",
70
+ "typescript": "7.0.2"
81
71
  },
82
72
  "contributors": [
83
73
  "Meshael AlBakaawi",
84
74
  "Amir Ayub",
85
75
  "Sara AlBakaawi"
86
- ]
76
+ ],
77
+ "gitHead": "3af04bb95dfbacee4caae46f0eaf2b88296a18a7"
87
78
  }
package/spec/README.md CHANGED
@@ -1,172 +1,300 @@
1
- # The UDL specification
2
-
3
- A UDL document describes one product in business terms: its subjects, its
4
- instruments, each instrument's lifecycle, the actions that move instances through it, and how
5
- money moves while they do. Everything generated from a document (SDKs, docs,
6
- tool surfaces, the running engine) is downstream of what is written here.
7
-
8
- UDL is the platform's header files and driver framework. Its grammar is the
9
- complete instrument vocabulary and the ABI between authored programs and the
10
- engine. An instrument definition can round-trip through UDL without dropping a
11
- clause.
12
-
13
- ## The schema is the authority
14
-
15
- `udl.schema.json` in this directory is JSON Schema 2020-12, generated from the
16
- Zod grammar in `src/schema.ts` by `scripts/emit-spec.ts`. It is checked in so
17
- that an implementation in any language can read it, and `bun run spec:check`
18
- fails the build when the committed bytes stop matching the generator.
19
-
20
- Prose copies of a machine-checked format drift. This document therefore carries
21
- no grammar tables and no field lists. Where anything below disagrees with
22
- `udl.schema.json`, the schema wins.
23
-
24
- The schema is generated from the _input_ view of the grammar, so it describes a
25
- document as an author writes it, before any default is filled in. A action may
26
- omit `moves`; a parser hands it back as `[]`.
27
-
28
- ## The ten laws
29
-
30
- These are judgment, not shape. They explain why the schema refuses what it
31
- refuses.
32
-
33
- 1. **One-sentence law.** Every concept in UDL is explainable in one sentence to
34
- someone who has never seen it. A concept that needs a paragraph is
35
- machinery, and machinery does not belong in the language.
36
- 2. **Purity law.** No bank or provider legacy enters UDL: no provider statement
37
- schemas, file drops, polling, batch windows, cutoff times, scheme names,
38
- currency reconciliation rules, or ISO or SWIFT message types. A `reconcile`
39
- clause names settlement evidence against a declared provider-side row. It
40
- does not model the provider file, transport, or matching machinery.
41
- 3. **Event law.** Every state change emits an event. Names derive from the instrument
42
- and the action's past tense (`escrow_order.released`); the optional
43
- `eventName` overrides only where the honest past tense is irregular.
44
- 4. **One-spine law.** Internal ledger money moves through four instructions and no others:
45
- `internal_transfer.create`, `.reserve`, `.post`, `.void`. Three account
46
- instructions (`account.escrow.provision`, `account.freeze`,
47
- `account.unfreeze`) complete the sealed kernel set. A `payout` intent hands
48
- a stored amount and beneficiary reference to the execution core. It is not
49
- a kernel instruction and cannot disguise an internal ledger move.
50
- 5. **Uniform object law.** Every instance carries an opaque prefixed id (from
51
- the instrument's `idPrefix`), a `status` drawn from its declared lifecycle, a
52
- creation timestamp, and a caller-owned metadata bag. Amounts are
53
- string-encoded integer minor units paired with a currency code, never JSON
54
- numbers.
55
- 6. **Requirements-as-data law.** Anything a caller must satisfy before a action
56
- unlocks is declared data: `due`, `deadline`, `requiresRefs`,
57
- `requiresChecks`, `requiresExposure`, `requiresAggregate`, `remainder`,
58
- `requiresDrainedAccount`, `commit`, `reconcile`. A reconcile declares one
59
- expectation: the amount, the currency, `credit` or `debit`, the ref naming
60
- the provider-side row, exactly one evidence source, a match law of `exact`,
61
- `tolerance` bounded by a named dial, or `window`, and a window given as a
62
- fixed duration or a stored deadline field. It ends matched or as a capped
63
- exception child, never as a silent wait; the child is declared and validated
64
- at admission rather than materialized, so past the window the transition
65
- refuses naming it. Settlement evidence for a payout is one reconcile against
66
- a debit statement line under any match law, reading the reference an earlier
67
- payout intent captured. No caller may assert that match. An action that carries `quote` prices a base into a charge
68
- and a net, names the fields the price depends on, and declares when the offer
69
- dies, as a fixed duration or a stored deadline field. Exactly one other
70
- action names it back through `commit`, and that action is the only one that
71
- may move the net. A `commit` gate reads the offer the quoting action priced
72
- and refuses once its deadline has passed or any frozen field has changed. It
73
- spends what the quote wrote instead of pricing again, so the number a caller
74
- is shown is the number they pay.
75
- 7. **Append-only evolution law.** Once a definition has live instances, adding
76
- states, transitions, optional fields, and actions is legal; removing,
77
- renaming, tightening, or changing a money step is not.
78
- `diffValidatedUdlEvolution` decides, on two documents the validator has
79
- already admitted; `udl diff` parses both files and then calls it. Evolution
80
- snapshots retain navigation, update examples, and parked-state reasons for
81
- complete inspection. The diff treats their prose as editable presentation
82
- and protects the parked state keys that carry lifecycle meaning.
83
- 8. **Naming law.** Instruments are `snake_case`, singular, plain business English.
84
- Actions are single words in imperative present. Operations are `instrument.action`.
85
- Fields are `camelCase`. The patterns live in the schema.
86
- 9. **Time law.** Delays the world imposes (settlement windows, activation
87
- periods, renewal cycles, retries) surface as honest statuses and timestamps
88
- on objects, never as processes the caller has to operate.
89
- 10. **Closure law.** A document is self-contained. Every lifecycle state is
90
- reachable from `create`, every reference resolves inside the document,
91
- every gate names a state that exists, and every funded balance is drained
92
- on every terminal path.
93
-
94
- ## Admission budgets and derived effects
95
-
96
- The 10,000-node limit bounds authored program complexity. The compiler derives
97
- `effects` rows from action clauses, so the node counter excludes every action
98
- `effects` subtree. Admission still checks those subtrees for shape, exact
99
- agreement with the clauses including row order, nesting depth, string limits,
100
- JSON-only values, and cycles.
101
-
102
- The canonical 33-instrument catalog measured 10,270 nodes with derived effects
103
- and 9,321 authored nodes without the effect subtrees on 2026-09-02. The fixed
104
- 10,000-node limit therefore leaves 679 authored nodes of headroom. The full
105
- effectful document validates as one document.
106
-
107
- ## What the schema cannot say
108
-
109
- JSON Schema pins shape. It does not pin meaning, and four classes of law live
110
- outside it:
111
-
112
- - **Blankness.** The schema says `"type": "string"` where the grammar says a
113
- string whose trimmed length is non-zero. A title of `" "` passes the schema
114
- and is refused by the parser.
115
- - **Closure and reference resolution.** Reachability, gate targets, party
116
- bindings, aggregate links, and authored-example validation are whole-document
117
- properties.
118
- - **Money-graph admission.** Whether a debit can be reached before its funding,
119
- and whether any terminal path strands value, is decided by walking the
120
- lifecycle.
121
- - **Evolution.** The legality of a change is a property of two documents, not
122
- one.
123
-
124
- All four are pinned as data in `../conformance/`. An implementation that reads
125
- only `udl.schema.json` will admit documents this one refuses; the conformance
126
- suite is what makes two implementations agree.
127
-
128
- ## Issue codes
129
-
130
- A refusal reports one or more issues, each with a stable `UDL####` code, a
131
- category, a fix, and a JSON path. Codes and paths form the conformance
132
- contract. Messages may become clearer. The generated
133
- [diagnostic reference](../docs/reference/diagnostics.md) lists every code.
134
-
135
- Paths are `$`-rooted with dotted keys and bracketed array indices:
136
- `$.instruments[0].lifecycle.states[2]`.
137
-
138
- ## Canonical form
139
-
140
- Every admitted document has one canonical byte sequence. The normative
141
- [canonical bytes law](../docs/reference/canonical.md) defines key and array
142
- order, number and string encoding, empty containers, the trailing line feed,
143
- and the SHA-256 digest. Serialization validates first, so an invalid document
144
- has no canonical form.
145
-
146
- ## Two version numbers
147
-
148
- **Format version** is what a document declares, as the literal `"udl": 1`. A
149
- document that declares any other value is refused. Format 1 is the only format
150
- that exists.
151
-
152
- **Package version** is the semver of `@hyperscale0/udl`, declared in
153
- `package.json`.
154
-
155
- They move independently, under one rule: **format 1 freezes when the package
156
- reaches 1.0.0.** The release candidate may still change what format 1 accepts,
157
- and every change appears in `../CHANGELOG.md`.
158
-
159
- After 1.0.0, an incompatible grammar change uses a new format literal. Readers
160
- keep an explicit decoder and validator for each supported old literal. A
161
- stored format 1 document remains readable under the format 1 rules. It does
162
- not acquire new required fields from the current package. Passing an old
163
- document to an evolution comparison can raise the matching `UDL7xxx` removal,
164
- tightening, lifecycle, or executable-change code, but a package upgrade alone
165
- does not rewrite its bytes. A product migration must validate and store its new
166
- document as a separate version before switching instances to it.
167
-
168
- The evolution codes are explicit. `UDL7001` protects stored identities,
169
- subjects, instruments, fields, lifecycles, actions, money clauses, gates, and
170
- policy. `UDL7002` requires a version increase for a semantic change.
171
- Evolution comparison admits the previous document first. A stored document
172
- that fails admission is `invalid_previous`, not an evolution issue.
1
+ # UDL 1
2
+
3
+ UDL is the Universal Domain Language, the typed contract between an HSX program
4
+ and its executor. The grammar
5
+ lives in `src/schema.ts`. Generate `udl.schema.json` with
6
+ `bun scripts/emit-spec.ts --write`.
7
+
8
+ ## Reading a compiled program
9
+
10
+ Start with `product`, `title` and `currency`. `objects` describes business records
11
+ and their attachments. Follow an attachment's instrument identity into
12
+ `instruments` for its retained fields, lifecycle and actions. `actionOrder`
13
+ preserves authored action order even though canonical JSON sorts object keys.
14
+ An action's `publicAction` supplies its exposed name; its actor and requirements
15
+ still control who may execute it and when.
16
+
17
+ Object metadata describes the subject. Instrument fields retain the agreement's
18
+ terms, account bindings and receipts. Party names identify authenticated or
19
+ configured participants. None of these makes a caller-supplied account ID valid.
20
+ A Build freezes this contract and its bindings for the executor.
21
+
22
+ Business instructions are actions composed from typed requirements and effects.
23
+ The four `internal_transfer` instructions are their money effects. The contract
24
+ expresses valid conditions; execution can still refuse for the current state,
25
+ authority, evidence or balance. Parsing and validation execute nothing.
26
+
27
+ ## JSON admission and canonical bytes
28
+
29
+ The parser refuses duplicate JSON member names, including escaped spellings of
30
+ one name. Decoded documents must contain finite JSON data. Accessors, cycles,
31
+ sparse arrays, symbol keys, hidden properties and `__proto__` members refuse
32
+ before schema parsing. Shared values count once per occurrence toward the budget.
33
+ Admission permits at most 100,000 visited values, 32 nesting levels, 240 code units
34
+ per key and 1 MiB of UTF-8 key and string data. Source text has a separate 1 MiB
35
+ byte limit. These bounds live in `src/limits.ts`.
36
+
37
+ Canonical JSON sorts object keys recursively, preserves array order and ends
38
+ with a newline. Schema defaults are part of the admitted document. Equivalent
39
+ key insertion orders and explicit defaults have the same digest. Object field
40
+ compatibility and evolution comparisons use the same key ordering.
41
+
42
+ ## Objects and subjects
43
+
44
+ An object kind declares identity, title, authored field names, normalized fields
45
+ and display columns. Each attachment freezes its instrument identity and party
46
+ parameter bindings to `owner`, `actor`, `operator`, or a declared Product party. Core resolves role bindings from the object row, session and Product, and
47
+ declared parties from the Product party binding. `authoredFields` records only names declared in the HSX
48
+ object block. `fields` is their union with attached action requirements. Matching
49
+ names must have matching types and constraints. Account fields belong to
50
+ instruments, never objects.
51
+
52
+ An instrument may name its object kind with `subject`. An action's `subject`
53
+ contains its requirements and any frozen adapter binding. Each requirement keeps
54
+ its source field and optional `objectField` rename. Adapter snapshots contain
55
+ provider, capability, operation, declaration digest and requirements. A null snapshot
56
+ marks an unbound adapter. It permits discovery but blocks that action. No provider
57
+ requirement is inferred from an operation name.
58
+
59
+ Object creation accepts `{}`. Its optional `fields` property accepts every
60
+ normalized field as optional. Requirements become mandatory only when the
61
+ attached action runs. Object creation performs no financial action. Instrument
62
+ creation remains internal; an attached create action may expose a public name.
63
+ Attachment parties resolve from authenticated authority, never from party account
64
+ IDs supplied in a caller body.
65
+
66
+ `object-contract.ts` projects `ObjectDiscovery` for
67
+ `GET /v1/products/{productId}/objects`. It exports the discovery, instance, list
68
+ and action response types. Discovery preserves Build identity, authored field
69
+ names, the full optional create schema and each exposed action's requirements.
70
+ Object instances carry `objectId`, `kind`, `revision`, optional `externalId`,
71
+ `fields` and `productBuildId`. Action responses carry the object and either a
72
+ financial instrument outcome or null.
73
+
74
+ An object may declare `entryActions`, a list of exposed attachment creation
75
+ names. The host admits an active enrolled customer only to an entry action whose
76
+ actor is the session's `actor` role. Other records remain closed. Retained
77
+ participants can read their record and use the actions granted to their roles;
78
+ participation never changes the owner.
79
+
80
+ An instrument may declare `scope: "product"`. Object creation can resolve a
81
+ reference to that instrument on another record in the same Product when every
82
+ retained party account matches the current bindings. Otherwise references stay
83
+ on the current record. Create and approve shared limits once per Product or
84
+ borrower; referencing them does not create a new limit.
85
+
86
+ ## Object operations
87
+
88
+ The host owns transport routes for object discovery, creation, listing,
89
+ retrieval, action availability, execution and agreement evidence.
90
+ `src/object-contract.ts` defines the language package's object projections.
91
+
92
+ Execution binds `productBuildId`, `digest`, `target` and `expectedRevision`.
93
+ The target identifies an attachment or an existing instance on that attachment.
94
+ The optional `fields` collect subject metadata; optional `input` carries the action's typed input.
95
+ Missing requirements refuse execution. Callers reuse an idempotency key only for
96
+ retries of the same request. Staff Create for may name `ownerPrincipalId` through
97
+ its authorized path; ordinary ownership comes from the session.
98
+
99
+ ## Accounts and money
100
+
101
+ A document declares SAR once. Money is a nonnegative minor-unit decimal string
102
+ of at most 18 digits. Percentages are basis points, durations are positive integer
103
+ milliseconds, and dates are timestamps with explicit offsets.
104
+
105
+ An account field binds a party, the instrument itself, or an adapter. Party owners
106
+ are names; `owner: "self"` provisions an account per instance. An adapter owner
107
+ is `{ "adapter": "insurer" }`, where `insurer` names an adapter declared in one
108
+ of the instrument's action subjects. The agreement retains that declaration's
109
+ provider identity. Missing or conflicting bindings refuse with
110
+ `subject_adapter_unbound`; changing an existing account binding refuses with
111
+ `account_binding_changed`. A null declaration can describe a draft but cannot
112
+ create an adapter-owned account.
113
+
114
+ Account creation and binding are executor work. Callers never supply account IDs.
115
+ Accounts declare `book: "cash" | "claim"`, defaulting to cash. Moves never cross books.
116
+ Only claim accounts may declare `contra: true` and permit a negative balance.
117
+ Party and adapter accounts share owner, book and key within a Product; the key
118
+ defaults to `balance`. Adapter aliases for the same provider share that account. Transfers between
119
+ known aliases of one account refuse, including self fields with the same key.
120
+ The executor scopes provider accounts to the tenant and Product, with the Product
121
+ participant as ledger custodian. Providers are not parties. An account owned by
122
+ self defaults its key to the field name. Named capital, premium, income, debt and
123
+ loss balances use explicit keys. `party.buyer` is the buyer's default cash account.
124
+
125
+ A plain move to an adapter account credits its ledger balance. It does not prove
126
+ an external payout. Provider confirmation still uses a boundary reservation and
127
+ instruction-bound evidence. Account ownership adds no settlement operation.
128
+
129
+ Disbursement moves cash from lender to borrower and claims from borrower debt to
130
+ principal and profit receivables. Repayment moves borrower cash to lender cash and
131
+ the same claim amounts from receivables back to borrower debt. Unearned profit is
132
+ cancelled by the claim move alone. Write-off moves the principal claim to the
133
+ lender's loss account, with no cash movement. The library declares when profit
134
+ is earned: on payment, by schedule or at disbursement.
135
+ These are ordinary paired moves in HSX, not executor loan rules.
136
+
137
+ A value is `{literal: value}` or `{field: path}`. Paths start with `self`, `input`, `subject`
138
+ or `party`. Reference fields allow typed traversal. Account paths expose locked,
139
+ read-only `.balance` and `.reserved` money values. `self.id`, `self.status`,
140
+ `self.createdAt` and `self.now` are sealed executor values. Callers cannot set
141
+ constants, calculated fields, account bindings or capture fields. Create supplies
142
+ declared typed references; later actions cannot replace them. A ref declares `targetKind: "object" | "instrument"` and one target id or
143
+ a list of 1 to 16 distinct target ids in that namespace. Its stored value is an
144
+ identity from one listed target. Path traversal exposes only fields
145
+ with compatible types on every target. Nested refs combine their target sets;
146
+ accounts must agree on owner, key, book and contra flags, and enums must
147
+ have the same values. A comparison or selection anchor must share a possible
148
+ reference target. An invoked input must accept every possible supplied target.
149
+ The executor checks the actual instance type at admission.
150
+
151
+ ## Calculation and movement
152
+
153
+ Calculations form a finite dependency graph. `sum`, `subtract` and `minimum`
154
+ operate on money or integer fields, with operands of the same type as the target.
155
+ `rate`, `multiply`, `divide` and `shift` retain their typed operands. A shift's
156
+ `milliseconds` operand is a duration field or a non-negative safe integer literal.
157
+ Zero leaves the date unchanged; duration fields still require positive values. Rate and
158
+ division round down. Weighted shares also round down; residual minor units go
159
+ to the declared residual account. Cash and loss use this one rounding rule.
160
+ There is no largest-remainder allocation. Subtraction refuses a negative result. Integer results must
161
+ be safe integers. No calculation evaluates source text.
162
+
163
+ The only move instructions are `internal_transfer.create`,
164
+ `internal_transfer.reserve`, `internal_transfer.post` and `internal_transfer.void`.
165
+ Create and reserve declare an amount, from account and to account. Reserve captures
166
+ its executor-produced transfer identity into a declared self text field. Post and
167
+ void consume that identity. Callers cannot create or replace captured identities.
168
+ Adapter-owned endpoints use the same move vocabulary.
169
+
170
+ Each move may declare `economics: { purpose, sourceParty, reversalOf? }`.
171
+ HSX attachment economics lower into this same move block on the attachment's
172
+ instrument. There is no separate override at runtime. The attachment's party
173
+ bindings include its economic source. Validation refuses an unbound money
174
+ party, a cash purpose on non-cash accounts, a void with authored economics,
175
+ and posting economics that conflict with the reservation. Non-cash accounting
176
+ can retain the `internal` purpose.
177
+ Purpose is `earning`, `principal`, `participant_payout`, `internal`,
178
+ `prepaid_credit`, or `pass_through`. Pass-through money is excluded from earnings
179
+ and costs. `sourceParty` names an authored party whose retained
180
+ participant must own the debit account. For an earning realized from company-held
181
+ customer money, it identifies the retained payer instead. The debit must be an
182
+ instrument account bound to the same agreement or a retained referenced agreement
183
+ in the same Build with the same payer; the company credit must be outside the
184
+ agreement. Evidence also retains the actual debit account participant.
185
+ `reversalOf` is a transfer reference path. A reversal preserves purpose, Product, Build and agreement, debits the
186
+ original recipient account and credits the original source participant. The
187
+ combined refunds cannot exceed the original posted amount. A post inherits its
188
+ reservation's economics; it cannot change them.
189
+ The terminal ledger evidence keeps these fields with the agreement, Build,
190
+ account participants, and move key. A move without economics has no inferred
191
+ classification.
192
+
193
+ A loan, refund, payoff, write-off or distribution is library behavior built from
194
+ accounts, calculations and ordered moves. None has a privileged executor clause.
195
+ Outstanding principal is an account balance. A schedule consists of explicit
196
+ dated child records, with positions 1 through n in declaration order. The library
197
+ uses ordinary comparisons and aggregates to constrain those records. There is no
198
+ recurrence process, allocation bucket, partition expander or schedule requirement
199
+ in the UDL kernel.
200
+
201
+ ## Admission and lifecycle
202
+
203
+ Actions declare typed input lists, requirements, an actor and an event. Lifecycle
204
+ edges name their source and destination states. Requirements and effects execute
205
+ atomically under the same account and reference locks. `set` copies typed values
206
+ to mutable fields. Action calculations read the locked snapshot and populate the
207
+ action draft before requirements. Ordered moves and invocations follow admission;
208
+ a zero create move refuses unless the action declares `allowZero: true`.
209
+ With that permission it records no transfer or capture. A reserve amount must
210
+ always be positive.
211
+ Invariants check the completed transaction. A due instant
212
+ is inclusive; a deadline is exclusive. Clock delays never extend deadlines.
213
+
214
+ Requirements are compare, state, unique, aggregate, evidence and hours.
215
+ A typed selection names one instrument or a bounded union, a reference field,
216
+ anchor, accepted states and row limit. Exceeding the limit refuses rather than
217
+ truncates. Optional equality filters apply to every selected type. An optional
218
+ window selects date values between `self.now - milliseconds` and `self.now`.
219
+ Aggregate sums use typed paths on the selected records, including account
220
+ balances. An invariant holds before and after every affected transaction.
221
+
222
+ `hours` converts a date path to its literal IANA timezone and accepts `[start,end)`.
223
+ A start greater than end wraps midnight; equal endpoints admit no time.
224
+ `evidence` declares subject, family, check, result and maxAge. The subject is an
225
+ account or text id. The executor selects the newest matching subject binding,
226
+ checks its retained Build, protected input digest and adapter declaration, then
227
+ requires the declared result and age. A newer matching refusal or pending result
228
+ prevents reuse of an older success.
229
+
230
+ `invoke` supplies typed inputs to a linked action or bounded selection. Its graph
231
+ is acyclic and bounded. Public names grant no authority; clock and parent actors
232
+ remain executor-owned. A public action on a subject instrument requires an
233
+ attachment on that object kind. State requirements refer to instrument instances;
234
+ objects have no instrument lifecycle. List extraction preserves the reference
235
+ namespace and target. Selection anchors and filters participate in calculation
236
+ cycle detection. Literal assignments must satisfy the destination field's constraints.
237
+
238
+ A captured transfer exposes its executor-owned `.status`. A boundary payout
239
+ reserves before dispatch. Only matching terminal confirmation can post or void
240
+ the reservation. An acknowledgement, transport failure or missing response
241
+ keeps the reservation. A refund is a separately funded move. Account balances
242
+ alone do not prove an external effect. The language adds no automatic
243
+ provider-failure reversal or deadline settlement policy.
244
+
245
+ ## Ten laws
246
+
247
+ 1. Each concept has one concrete meaning.
248
+ 2. Provider transport and credentials stay outside the contract.
249
+ 3. Every state change emits its declared event.
250
+ 4. Four transfer instructions are the only money movement vocabulary.
251
+ 5. Instances have an opaque identity and money uses integer minor units.
252
+ 6. Typed requirements and effects execute atomically.
253
+ 7. Live additions preserve existing meaning; development estates may recreate.
254
+ 8. Business names identify object kinds, attached instruments and public actions.
255
+ 9. Waiting uses lifecycle states and clocks. Before admission, the executor
256
+ catches up actor: clock actions with due instants on the locked instance,
257
+ references and selected rows, in chronological order. Catch-up commits its
258
+ own transaction and events before the caller request is evaluated. A refused
259
+ caller request rolls back only its own drafts. Caller deadlines are refusal
260
+ boundaries; they never execute the caller action. Separate clock actions
261
+ carry expiry consequences. Requirements and selections observe the resulting
262
+ clock state.
263
+ 10. References close, states are reachable, captures are linear, and terminal
264
+ instances have no remaining owned-account balances or reservations.
265
+
266
+ ## Clause inventory
267
+
268
+ The generated [JSON schema](../spec/udl.schema.json) owns the complete field
269
+ inventory, defaults and constraints. `src/schema.ts` is its source.
270
+
271
+ `calculate.aggregate` reads a typed selection and yields its count or a money sum. `calculate.ratio` computes floor(amount * numerator / denominator) with arbitrary-precision intermediates and refuses a zero denominator. Numerator and denominator share a numeric type. Selection order is a list of typed ascending paths, followed by identity as the final tie-break. `invoke {instrument, action: "create", input}` creates a child record in the same transaction; its inputs resolve in the caller, and the ordinary create actor and requirements still apply.
272
+
273
+ `calculate.at` reads a typed list at a one-based position and refuses an out-of-range index. Its result has the list item type. Integer divide accepts integer operands and rounds down. Every move may capture its transfer identity into a declared self text field; reserve requires a capture. Captures and their status paths are executor-owned.
274
+
275
+ ## Reporting definitions
276
+
277
+ An instrument may declare `reports`, a bounded array of strict ReportDefinition
278
+ values. Each definition contains identity/version, scope, datasets, time,
279
+ selection, calculation, validation, output and authority. The JSON schema defines
280
+ all fields. Expressions form an ordered graph with backward references, typed
281
+ money in integer minor units, explicit ratios and date operations. Source fields
282
+ must exist in every bound instrument. A scalar field cannot acquire a suffix;
283
+ account balances use the declared `<account>.balance` path. Company instrument reports require
284
+ cross-Build bindings and are not admitted in this version.
285
+
286
+ Joins declare one-or-many cardinality, missing-record policy and aggregates;
287
+ implicit row multiplication is forbidden. Compilation checks currencies, column
288
+ and expression types, output columns and sort keys. Execution must check complete
289
+ source populations, required facts, unique row and sort identities, reconciliation
290
+ and declared limits before publishing any result. Empty-population behavior and
291
+ unavailable facts are definition data. Request, read and release roles are
292
+ separate. Independent activation and retained deterministic replay belong to the
293
+ host; language acceptance alone does not authorize an artifact or external release.
294
+
295
+ Reporting source selections and foreign join keys must be computable from
296
+ PostgreSQL facts; ledger balances and ratios belong after capture. Aggregate
297
+ names are unique across stages. `average` requires an explicit `rounding` policy
298
+ (`floor` or `halfUp`) to retain integer and money minor-unit types. Numeric and
299
+ min/max aggregates skip nulls, return null for all-null inputs, and emit per-row
300
+ `_skippedNulls_<name>` counters in JSON and CSV. Required facts still refuse nulls.