@hyperscale0/udl 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 (234) hide show
  1. package/CHANGELOG.md +5 -211
  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/dist/schema.js CHANGED
@@ -1,1171 +1,494 @@
1
- import { z } from "zod";
2
- import { udlCheckEvidenceProfiles } from "./check-profiles.js";
1
+ import * as z from "zod";
2
+ import { reportDefinitionSchema } from "./reporting.js";
3
+ import { writeJson } from "./json.js";
3
4
  export const UDL_FORMAT_VERSION = 1;
4
- const snakeCasePattern = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
5
- const camelCasePattern = /^[a-z][A-Za-z0-9]*$/;
6
- const fieldPathPattern = /^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)*$/;
7
- const idPrefixPattern = /^[a-z]{2,8}$/;
8
- const eventNamePattern = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/;
9
- const udlSnakeCaseSchema = z
5
+ /** Counts the root and every nested invocation, including selected and ranged children. */
6
+ export const MAX_ACTION_EXPANSION = 4096;
7
+ const name = z
10
8
  .string()
11
- .regex(snakeCasePattern, "must be a snake_case identifier");
12
- const udlActionNameSchema = z
9
+ .regex(/^[a-z][a-zA-Z0-9_]*$/)
10
+ .max(80);
11
+ const text = z.string().min(1).max(2048);
12
+ const amount = z.string().regex(/^(0|[1-9][0-9]{0,17})$/);
13
+ const integer = z.number().int().safe();
14
+ const scalar = z.union([z.string(), integer, z.boolean()]);
15
+ const path = z
13
16
  .string()
14
- .regex(snakeCasePattern, "must be a snake_case action identifier");
15
- const udlFieldNameSchema = z
16
- .string()
17
- .regex(camelCasePattern, "must be a camelCase field name");
18
- export const udlPublicActionSchema = z
19
- .string()
20
- .regex(camelCasePattern, "must be a camelCase public action name");
21
- export const udlInstrumentActionIdSchema = z
22
- .string()
23
- .max(160)
24
- .regex(/^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/, "must name an instrument action as instrument_id.action_key");
25
- const nonEmptyTextSchema = z
26
- .string()
27
- .refine((value) => value.trim().length > 0, "must not be blank");
28
- // Agent-facing presentation prose. Every MCP tool description an agent reads is
29
- // this string, so it is capped: a 400-character ceiling keeps a 60-tool surface
30
- // under the token budget a client spends before it can pick a tool.
31
- const udlAgentDescriptionSchema = nonEmptyTextSchema.max(400);
32
- const fieldPathSchema = z
33
- .string()
34
- .regex(fieldPathPattern, "must be a dot-separated camelCase field path");
35
- const instanceValuePathSchema = z
36
- .string()
37
- .regex(/^(?:fields|refs)\.[a-z][A-Za-z0-9]*$/, "must name one declared fields.* or refs.* value");
38
- const jsonObjectSchema = z.record(z.string(), z.json());
39
- const stringMapSchema = z.record(nonEmptyTextSchema, nonEmptyTextSchema);
40
- const providerFamilyIds = [
41
- "accounts_ledger_first_party",
42
- "identity_verification",
43
- "national_identity",
44
- "credit_bureau",
45
- "sanctions_screening",
46
- "enforcement_instrument",
47
- "credit_servicing",
48
- "collections_agency",
49
- "settlement_ingestion",
50
- "payouts",
51
- "banking_or_card_issuing",
52
- "insurance_carrier",
53
- "insurance_claims_adjuster",
54
- "air_carrier",
55
- "lodging_supplier",
56
- "email_or_notification",
57
- ];
58
- export const udlProviderFamilyIdSchema = z.enum(providerFamilyIds);
59
- const udlDeclaredValuePolicySchema = z.enum(["required", "optional", "none"]);
60
- const udlSubjectSchema = z.strictObject({
61
- declaredValue: udlDeclaredValuePolicySchema,
62
- kind: udlSnakeCaseSchema,
63
- schema: jsonObjectSchema,
64
- title: nonEmptyTextSchema,
65
- version: z.number().int().positive(),
66
- });
67
- const udlInstrumentSubjectSchema = z.strictObject({
68
- extensible: z.boolean().optional(),
69
- kinds: z.array(udlSnakeCaseSchema).min(1),
70
- });
71
- const udlExampleSchema = z.strictObject({
72
- input: jsonObjectSchema,
73
- name: nonEmptyTextSchema,
74
- output: z.json().optional(),
75
- });
76
- const udlLifecycleTransitionSchema = z.strictObject({
77
- from: z.array(udlSnakeCaseSchema).min(1),
78
- to: udlSnakeCaseSchema,
79
- });
80
- const udlLifecycleSchema = z.strictObject({
81
- initial: udlSnakeCaseSchema,
82
- states: z.array(udlSnakeCaseSchema).min(1),
83
- transitions: z.record(udlActionNameSchema, udlLifecycleTransitionSchema),
84
- });
85
- const udlUpdatePolicySchema = z.strictObject({
86
- examples: z.array(udlExampleSchema).optional(),
87
- fields: z.array(udlFieldNameSchema).min(1),
88
- states: z.array(udlSnakeCaseSchema).min(1),
89
- });
90
- const udlGateSchema = z.strictObject({
91
- /** Local field key <- referenced instance path; create actions only. */
92
- bind: z.record(udlFieldNameSchema, fieldPathSchema).optional(),
93
- field: udlFieldNameSchema,
94
- /** Local instance path === referenced instance path at admission. */
95
- match: z.record(fieldPathSchema, fieldPathSchema).optional(),
96
- /** Opt-in over an optional reference field: absent passes, set is held to the clause. */
17
+ .regex(/^[a-z][a-zA-Z0-9_]*(?:\.[a-z][a-zA-Z0-9_]*)*$/)
18
+ .max(240);
19
+ const instrumentId = name.meta({ "x-udl-reference": "instrument" });
20
+ const objectKindId = name.meta({ "x-udl-reference": "object" });
21
+ export const udlPartyNameSchema = name;
22
+ export const udlObjectIdSchema = z.string().min(1).max(160);
23
+ export const udlExternalIdSchema = z.string().min(1).max(2048);
24
+ export const udlObjectRevisionSchema = integer.positive();
25
+ /** Currency belongs to the document. Accounts and amounts cannot override it. */
26
+ export const udlPartySchema = z.strictObject({
27
+ kind: z.enum(["person", "business", "staff"]),
28
+ role: name.optional(),
29
+ });
30
+ export const udlFamilySchema = z.strictObject({
31
+ module: name,
32
+ exportPath: path,
33
+ revision: integer.positive(),
34
+ });
35
+ const fieldBase = {
36
+ name,
37
+ label: text.optional(),
97
38
  optional: z.literal(true).optional(),
98
- statuses: z.array(udlSnakeCaseSchema).min(1),
99
- /** One dependent ever per referenced instance; create actions only. */
100
- unique: z.literal(true).optional(),
101
- });
102
- const udlBindingSchema = z.discriminatedUnion("from", [
103
- z.strictObject({ from: z.literal("const"), value: z.string() }),
104
- z.strictObject({ from: z.literal("instance"), path: fieldPathSchema }),
105
- z.strictObject({ from: z.literal("input"), path: fieldPathSchema }),
106
- ]);
107
- const udlStepOperationSchema = z.enum([
108
- "account.escrow.provision",
109
- "account.freeze",
110
- "account.unfreeze",
111
- ]);
112
- const udlMoveOperationSchema = z.enum([
113
- "internal_transfer.create",
114
- "internal_transfer.reserve",
115
- "internal_transfer.post",
116
- "internal_transfer.void",
117
- ]);
118
- export const udlKernelOperationSchema = z.enum([
119
- ...udlStepOperationSchema.options,
120
- ...udlMoveOperationSchema.options,
121
- ]);
122
- const udlStepSchema = z.strictObject({
123
- bind: z.record(fieldPathSchema, udlBindingSchema),
124
- capture: stringMapSchema.optional(),
125
- operation: udlStepOperationSchema,
126
- });
127
- const udlMoveSchema = z.strictObject({
128
- bind: z.record(fieldPathSchema, udlBindingSchema),
129
- capture: stringMapSchema.optional(),
130
- key: udlSnakeCaseSchema,
131
- operation: udlMoveOperationSchema,
132
- });
133
- const udlDueRecurrenceSchema = z.strictObject({
134
- countField: udlFieldNameSchema.optional(),
135
- delinquency: z.literal("parent_policy").optional(),
136
- drainAction: udlActionNameSchema.optional(),
137
- period: z.union([
138
- nonEmptyTextSchema,
139
- z.strictObject({
140
- calendar: z.enum(["gregorian", "hijri"]),
141
- monthEnd: z.literal("clamp_to_last_day"),
142
- months: z.number().int().positive(),
143
- }),
144
- ]),
145
- liability: z.literal("one_open").optional(),
146
- untilAction: udlActionNameSchema.optional(),
147
- untilField: udlFieldNameSchema.optional(),
148
- });
149
- const udlDueParentStatusSchema = z.strictObject({
150
- instrumentId: udlSnakeCaseSchema,
151
- refField: udlFieldNameSchema,
152
- statuses: z.array(udlSnakeCaseSchema).min(1),
153
- });
154
- const udlDueSchema = z.strictObject({
155
- every: udlDueRecurrenceSchema.optional(),
156
- field: udlFieldNameSchema,
157
- offset: nonEmptyTextSchema.optional(),
158
- whenParentStatus: udlDueParentStatusSchema.optional(),
159
- });
160
- const udlDeadlineSchema = z.strictObject({
161
- field: udlFieldNameSchema,
162
- offset: nonEmptyTextSchema.optional(),
163
- });
164
- const udlSetsAtSchema = z.strictObject({
165
- field: udlFieldNameSchema,
166
- marker: z.literal(true).optional(),
167
- offset: nonEmptyTextSchema,
168
- });
169
- const udlQuoteChargeSchema = z.strictObject({
170
- bps: z.number().int().min(0).max(9_999),
171
- withinOffset: nonEmptyTextSchema.optional(),
172
- });
173
- // A priced, expiring offer. The quoting action splits `baseField` into a charge
174
- // and a net at its own clock, freezes the fields the price was read from, and
175
- // stamps an expiry. Only the paired `commit` action may spend the offer, and
176
- // only while it is unexpired and its frozen fields still hold.
177
- const udlQuoteShape = {
178
- /** Date-time whose remaining time picks the charge tier; omit for elapsed age. */
179
- anchorField: udlFieldNameSchema.optional(),
180
- baseField: udlFieldNameSchema,
181
- chargeRef: udlFieldNameSchema,
182
- charges: z.array(udlQuoteChargeSchema).min(1),
183
- /** Fixed ISO-8601 duration from the quoting action, or a stored deadline. */
184
- expires: z.union([
185
- z.strictObject({ field: udlFieldNameSchema }),
186
- z.strictObject({ offset: nonEmptyTextSchema }),
187
- ]),
188
- fixes: z.array(udlFieldNameSchema).min(1),
189
- netDestinationField: udlFieldNameSchema,
190
- netRef: udlFieldNameSchema,
39
+ description: text.optional(),
40
+ sensitive: z.literal(true).optional(),
191
41
  };
192
- const udlQuoteSchema = z.strictObject(udlQuoteShape);
193
- /**
194
- * Where a quoting action stamps the instant its offer stops being committable.
195
- * Derived from the author's net ref rather than a fixed name, so two offers on
196
- * one instrument never share the slot.
197
- */
198
- export function quoteExpiresAtRefKey(quote) {
199
- return `${quote.netRef}ExpiresAt`;
200
- }
201
- /**
202
- * Where a quoting action stamps the fingerprint of the field values it froze.
203
- * The committing action recomputes it against the live fields, which is how an
204
- * offer priced on one set of terms refuses to pay out against another.
205
- */
206
- export function quoteFrozenRefKey(quote) {
207
- return `${quote.netRef}Frozen`;
208
- }
209
- /**
210
- * Every ref key one quote seeds: the charge and the net the author named, plus
211
- * the two the machinery derives to hold the offer itself. One derivation, read
212
- * by the validator, the compiler, and the runtime alike.
213
- */
214
- export function quoteSeededRefKeys(quote) {
215
- return [
216
- quote.chargeRef,
217
- quote.netRef,
218
- quoteExpiresAtRefKey(quote),
219
- quoteFrozenRefKey(quote),
220
- ];
221
- }
222
- const udlDecisionSchema = z.strictObject({
223
- capability: udlProviderFamilyIdSchema,
224
- deadlineMs: z.number().int().positive().max(15_000),
225
- // A decision that never came back is not an approval. `approve` would let a
226
- // document release money because a counterparty went quiet, so the grammar
227
- // admits only the fail-closed answer. Every published document already
228
- // writes `decline`; widening later is additive, narrowing later would not
229
- // be.
230
- onTimeout: z.literal("decline"),
231
- });
232
- const udlAggregateCheckSchema = z.discriminatedUnion("kind", [
42
+ const referenceField = z.strictObject({
43
+ ...fieldBase,
44
+ type: z.literal("ref"),
45
+ targetKind: z.enum(["instrument", "object"]),
46
+ targetFamily: udlFamilySchema.optional(),
47
+ target: z.union([name, z.array(name).min(1).max(16)]),
48
+ });
49
+ const listField = z.strictObject({
50
+ ...fieldBase,
51
+ type: z.literal("list"),
52
+ item: z.enum(["money", "date", "text", "integer", "ref"]),
53
+ targetKind: z.enum(["instrument", "object"]).optional(),
54
+ targetFamily: udlFamilySchema.optional(),
55
+ target: name.optional(),
56
+ maxItems: integer.min(1).max(366),
57
+ });
58
+ export const udlFieldSchema = z.discriminatedUnion("type", [
233
59
  z.strictObject({
234
- amountField: udlFieldNameSchema,
235
- kind: z.literal("sum_at_least"),
236
- targetField: udlFieldNameSchema,
60
+ ...fieldBase,
61
+ type: z.literal("money"),
62
+ value: amount.optional(),
63
+ minimum: amount.optional(),
64
+ maximum: amount.optional(),
237
65
  }),
238
66
  z.strictObject({
239
- amountField: udlFieldNameSchema,
240
- kind: z.literal("sum_below"),
241
- targetField: udlFieldNameSchema,
67
+ ...fieldBase,
68
+ type: z.literal("account"),
69
+ owner: z.union([name, z.strictObject({ adapter: name })]),
70
+ key: name.optional(),
71
+ book: z.enum(["cash", "claim"]).default("cash"),
72
+ contra: z.literal(true).optional(),
73
+ }),
74
+ referenceField,
75
+ z.strictObject({
76
+ ...fieldBase,
77
+ type: z.literal("date"),
78
+ value: z.iso.datetime({ offset: true }).optional(),
79
+ }),
80
+ z.strictObject({
81
+ ...fieldBase,
82
+ type: z.literal("duration"),
83
+ value: integer.positive().optional(),
84
+ }),
85
+ z.strictObject({
86
+ ...fieldBase,
87
+ type: z.literal("text"),
88
+ value: text.optional(),
89
+ minLength: integer.positive().max(2048).optional(),
90
+ maxLength: integer.positive().max(2048).optional(),
91
+ // One anchored ASCII character class and a fixed repetition. No executable patterns.
92
+ pattern: z
93
+ .string()
94
+ .regex(/^\^\[[A-Za-z0-9-]+\]\{[1-9][0-9]{0,3}\}\$$/)
95
+ .optional(),
242
96
  }),
243
97
  z.strictObject({
244
- amountField: udlFieldNameSchema,
245
- kind: z.literal("sum_exactly"),
246
- targetField: udlFieldNameSchema,
98
+ ...fieldBase,
99
+ type: z.literal("integer"),
100
+ value: integer.optional(),
101
+ minimum: integer.optional(),
102
+ maximum: integer.optional(),
247
103
  }),
248
- // Siblings summed against a cap read from the LOCKED anchor row's
249
- // machinery-seeded ref (never a stored field on the counted rows).
250
104
  z.strictObject({
251
- amountField: udlFieldNameSchema,
252
- anchorRef: udlFieldNameSchema,
253
- kind: z.literal("sum_within_anchor_ref"),
105
+ ...fieldBase,
106
+ type: z.literal("percent"),
107
+ value: integer.min(0).max(10000).optional(),
254
108
  }),
255
109
  z.strictObject({
256
- kind: z.literal("count_at_least"),
257
- targetField: udlFieldNameSchema,
110
+ ...fieldBase,
111
+ type: z.literal("boolean"),
112
+ value: z.boolean().optional(),
258
113
  }),
259
- z.strictObject({ kind: z.literal("all_in") }),
260
114
  z.strictObject({
261
- field: udlFieldNameSchema,
262
- kind: z.literal("count_equals_field"),
115
+ ...fieldBase,
116
+ type: z.literal("enum"),
117
+ values: z.array(name).min(1).max(64),
118
+ value: name.optional(),
263
119
  }),
120
+ listField,
264
121
  ]);
265
- const udlAggregateConditionSchema = z.strictObject({
266
- check: udlAggregateCheckSchema,
267
- instrumentId: udlSnakeCaseSchema.optional(),
268
- over: z.enum(["children", "siblings"]),
269
- refField: udlFieldNameSchema,
270
- statuses: z.array(udlSnakeCaseSchema).min(1),
271
- });
272
- const udlExposureRequirementSchema = z.strictObject({
273
- amountField: udlFieldNameSchema,
274
- anchorField: udlFieldNameSchema,
275
- capField: udlFieldNameSchema,
276
- capOnAnchor: z.literal(true).optional(),
277
- childInstrumentId: udlSnakeCaseSchema,
278
- statuses: z.array(udlSnakeCaseSchema).min(1),
279
- });
280
- // The tenant-backend decision port: the action's caller asserts the acting
281
- // party, checked at admission against the instrument's party bindings for the
282
- // allowed roles.
283
- const udlPortSchema = z.strictObject({
284
- allowedParties: z
285
- .array(z.enum(["payer", "beneficiary", "subjectHolder"]))
286
- .min(1),
287
- });
288
- const udlPayoutSchema = z.strictObject({
289
- amount: instanceValuePathSchema,
290
- beneficiaryField: udlFieldNameSchema,
291
- beneficiaryPartyField: udlFieldNameSchema,
292
- capture: udlFieldNameSchema,
293
- currencyField: udlFieldNameSchema,
294
- sourceAccountField: udlFieldNameSchema,
295
- speed: z.literal("standard"),
122
+ const [moneyField, , refField, dateField, durationField, textField, integerField, percentField, booleanField, enumField, boundedListField,] = udlFieldSchema.options;
123
+ export const udlObjectFieldSchema = z.discriminatedUnion("type", [
124
+ moneyField,
125
+ refField,
126
+ dateField,
127
+ durationField,
128
+ textField,
129
+ integerField,
130
+ percentField,
131
+ booleanField,
132
+ enumField,
133
+ boundedListField,
134
+ ]);
135
+ /** Values are constants or resolved typed paths, never executable strings. */
136
+ export const udlValueSchema = z.union([
137
+ z.strictObject({ literal: scalar }),
138
+ z.strictObject({ field: path }),
139
+ ]);
140
+ const value = udlValueSchema;
141
+ const comparisonRequirement = z.strictObject({
142
+ kind: z.literal("compare"),
143
+ left: value,
144
+ operator: z.enum(["==", "!=", "<", "<=", ">", ">="]),
145
+ right: value,
146
+ });
147
+ // Alternatives are OR; each invocation path is a conjunction of scoped guards.
148
+ export const udlSubjectConditionsSchema = z
149
+ .array(z
150
+ .array(z.strictObject({
151
+ instrument: instrumentId,
152
+ action: name,
153
+ guard: comparisonRequirement,
154
+ valueType: z.enum(["money", "date"]).optional(),
155
+ }))
156
+ .min(1)
157
+ .max(32))
158
+ .min(1)
159
+ .max(128);
160
+ export const udlSubjectRequirementSchema = z.strictObject({
161
+ field: udlObjectFieldSchema,
162
+ // Present only for an authored rename.
163
+ objectField: name.optional(),
164
+ when: udlSubjectConditionsSchema.optional(),
165
+ });
166
+ export const adapterSubjectSnapshotSchema = z.strictObject({
167
+ provider: text,
168
+ capability: text,
169
+ operation: text,
170
+ declarationDigest: z.string().regex(/^[a-f0-9]{64}$/),
171
+ requirements: z.array(udlObjectFieldSchema).max(128),
172
+ });
173
+ export const udlActionSubjectSchema = z.strictObject({
174
+ requirements: z.array(udlSubjectRequirementSchema).max(128),
175
+ adapters: z
176
+ .array(z.strictObject({
177
+ binding: name,
178
+ // null means unavailable, never "no requirements".
179
+ snapshot: adapterSubjectSnapshotSchema.nullable(),
180
+ renames: z.record(name, name).optional(),
181
+ }))
182
+ .max(16),
183
+ });
184
+ export const subjectPartyRoles = ["owner", "actor", "operator"];
185
+ export const attachmentPartyBindingSchema = z.union([
186
+ z.strictObject({ role: z.enum(subjectPartyRoles) }),
187
+ z.strictObject({ party: name }),
188
+ ]);
189
+ export const udlObjectAttachmentSchema = z.strictObject({
190
+ name,
191
+ title: text.optional(),
192
+ parent: name.optional(),
193
+ instrument: instrumentId,
194
+ parties: z.record(name, attachmentPartyBindingSchema),
195
+ });
196
+ export const udlObjectKindSchema = z.strictObject({
197
+ entryActions: z.array(name).min(1).max(128).optional(),
198
+ id: objectKindId,
199
+ title: text,
200
+ authoredFields: z.array(name).max(256),
201
+ attachments: z.array(udlObjectAttachmentSchema).max(256),
202
+ fields: z.array(udlObjectFieldSchema).max(256),
203
+ columns: z.array(name).max(8),
204
+ });
205
+ const values = z.array(value).min(1).max(256);
206
+ const selection = z.strictObject({
207
+ family: udlFamilySchema.optional(),
208
+ instrument: z.union([instrumentId, z.array(instrumentId).min(1).max(16)]),
209
+ reference: name,
210
+ anchor: path,
211
+ states: z.array(name).min(1).max(64),
212
+ limit: integer.min(1).max(366),
213
+ order: z.array(path).min(1).max(4).optional(),
214
+ window: z
215
+ .strictObject({ field: name, milliseconds: integer.positive() })
216
+ .optional(),
217
+ where: z
218
+ .record(path, value)
219
+ .refine((filters) => Object.keys(filters).length <= 8, "at most eight selection filters")
220
+ .meta({ maxProperties: 8 })
221
+ .optional(),
296
222
  });
297
- /**
298
- * One expectation an action asserts about money it cannot see yet: how much,
299
- * in which currency, which way, and against whose provider-side row. Evidence
300
- * that lands later either matches under the declared law or the expectation
301
- * breaks: past the window the kernel raises the declared exception child, binds
302
- * the unmatched amount and the break reason onto it, and spends the due
303
- * occurrence. There is no third outcome and no silent wait.
304
- */
305
- const udlReconcileSchema = z.strictObject({
306
- /** `fields.<name>` or `refs.<name>`: the money this action says will land. */
307
- amount: instanceValuePathSchema,
308
- /** Where the matched evidence id lands in refs. */
309
- capture: udlFieldNameSchema,
310
- /** The ref naming the provider-side row the evidence must cite. */
311
- counterpartyRef: udlFieldNameSchema,
312
- currencyField: udlFieldNameSchema,
313
- direction: z.enum(["credit", "debit"]),
314
- /** Exactly one source. A reconcile never reads two ledgers at once. */
315
- evidence: z.enum(["movement", "provider_confirmation", "statement_line"]),
316
- /**
317
- * Where an expectation that never matched lands. The named child instrument
318
- * carries the break, and `maxOpen` caps how many of them one instance may
319
- * hold open at once, the same count measure the aggregate invariants use.
320
- */
321
- exception: z.strictObject({
322
- amountField: udlFieldNameSchema,
323
- childInstrumentId: udlSnakeCaseSchema,
324
- maxOpen: z.number().int().min(1).max(1_000),
325
- reasonField: udlFieldNameSchema,
326
- refField: udlFieldNameSchema,
223
+ /** A finite, ordered calculation graph. Every result is a declared field. */
224
+ export const udlCalculationSchema = z.discriminatedUnion("op", [
225
+ z.strictObject({
226
+ target: name,
227
+ op: z.literal("at"),
228
+ list: path,
229
+ position: value,
327
230
  }),
328
- /**
329
- * How a candidate becomes the match. `exact` demands equal minor units,
330
- * `tolerance` allows a bounded difference, `window` drops the amount test
331
- * for a source that nets several items into one line.
332
- */
333
- match: z.discriminatedUnion("law", [
334
- z.strictObject({ law: z.literal("exact") }),
335
- z.strictObject({
336
- dial: udlSnakeCaseSchema,
337
- law: z.literal("tolerance"),
338
- minorUnits: z.number().int().positive(),
339
- }),
340
- z.strictObject({ law: z.literal("window") }),
341
- ]),
342
- /** Fixed ISO-8601 duration from the expecting action, or a stored deadline. */
343
- within: z.union([
344
- z.strictObject({ field: udlFieldNameSchema }),
345
- z.strictObject({ offset: nonEmptyTextSchema }),
346
- ]),
347
- });
348
- const udlSignedSumSourceSchema = z.strictObject({
349
- amountField: udlFieldNameSchema,
350
- instrumentId: udlSnakeCaseSchema,
351
- refField: udlFieldNameSchema,
352
- sign: z.enum(["add", "subtract"]),
353
- statuses: z.array(udlSnakeCaseSchema).min(1),
354
- subtotalRef: udlFieldNameSchema,
355
- });
356
- const udlSignedSumSchema = z.strictObject({
357
- amountRef: udlFieldNameSchema,
358
- onNegative: z.literal("refuse"),
359
- onZero: z.enum(["refuse", "skip_steps"]),
360
- sources: z.array(udlSignedSumSourceSchema).min(1),
361
- });
362
- const udlDistributeSchema = z.strictObject({
363
- amountRef: udlFieldNameSchema,
364
- onZero: z.enum(["refuse", "skip_steps"]),
365
- pool: z.strictObject({
366
- from: z.literal("parent"),
367
- path: fieldPathSchema,
231
+ z.strictObject({
232
+ target: name,
233
+ op: z.literal("aggregate"),
234
+ selection,
235
+ measure: z.union([z.literal("count"), z.strictObject({ sum: path })]),
368
236
  }),
369
- refField: udlFieldNameSchema,
370
- statuses: z.array(udlSnakeCaseSchema).min(1),
371
- weightField: udlFieldNameSchema,
372
- });
373
- const udlRemainderCollectedSchema = z.strictObject({
374
- amountField: udlFieldNameSchema,
375
- instrumentId: udlSnakeCaseSchema,
376
- path: z.literal("refs").optional(),
377
- refField: udlFieldNameSchema,
378
- statuses: z.array(udlSnakeCaseSchema).min(1),
379
- });
380
- const udlRemainderSchema = z.strictObject({
381
- accumulateRef: udlFieldNameSchema.optional(),
382
- amountRef: udlFieldNameSchema,
383
- collected: z.array(udlRemainderCollectedSchema).optional(),
384
- inputKey: udlFieldNameSchema.optional(),
385
- onZero: z.enum(["refuse", "skip_steps"]),
386
- totalPath: instanceValuePathSchema,
387
- });
388
- const udlCheckRequirementVariants = udlCheckEvidenceProfiles.map((profile) => z.strictObject({
389
- checkKind: z.literal(profile.checkKind),
390
- family: z.literal(profile.family),
391
- maxAge: nonEmptyTextSchema.optional(),
392
- statuses: z.array(z.enum(profile.statuses)).min(1),
393
- subjectField: udlFieldNameSchema,
394
- }));
395
- const udlCheckRequirementSchema = z.union(udlCheckRequirementVariants);
396
- const udlDialSchema = z.discriminatedUnion("kind", [
397
237
  z.strictObject({
398
- field: udlFieldNameSchema,
399
- key: udlSnakeCaseSchema,
400
- kind: z.literal("window"),
401
- maxOffset: nonEmptyTextSchema.optional(),
402
- minOffset: nonEmptyTextSchema.optional(),
403
- summary: nonEmptyTextSchema,
404
- title: nonEmptyTextSchema,
238
+ target: name,
239
+ op: z.literal("ratio"),
240
+ amount: value,
241
+ numerator: value,
242
+ denominator: value,
243
+ rounding: z.literal("floor"),
244
+ }),
245
+ z.strictObject({ target: name, op: z.literal("sum"), values }),
246
+ z.strictObject({
247
+ target: name,
248
+ op: z.literal("subtract"),
249
+ base: value,
250
+ subtract: values,
405
251
  }),
406
252
  z.strictObject({
407
- action: udlActionNameSchema,
408
- key: udlSnakeCaseSchema,
409
- kind: z.literal("decision_deadline_ms"),
410
- maxMs: z.number().int().positive().max(15_000).optional(),
411
- minMs: z.number().int().positive().max(15_000).optional(),
412
- summary: nonEmptyTextSchema,
413
- title: nonEmptyTextSchema,
253
+ target: name,
254
+ op: z.literal("rate"),
255
+ base: value,
256
+ bps: value,
257
+ rounding: z.literal("floor"),
414
258
  }),
259
+ z.strictObject({ target: name, op: z.literal("minimum"), values }),
415
260
  z.strictObject({
416
- key: udlSnakeCaseSchema,
417
- kind: z.literal("reconcile_tolerance"),
418
- /** Ceiling on the minor units any reconcile may forgive under this dial. */
419
- maxMinorUnits: z.number().int().positive().max(1_000_000),
420
- summary: nonEmptyTextSchema,
421
- title: nonEmptyTextSchema,
261
+ target: name,
262
+ op: z.literal("multiply"),
263
+ amount: value,
264
+ units: value,
422
265
  }),
423
266
  z.strictObject({
424
- key: udlSnakeCaseSchema,
425
- kind: z.literal("unwind_penalty"),
426
- summary: nonEmptyTextSchema,
427
- title: nonEmptyTextSchema,
267
+ target: name,
268
+ op: z.literal("divide"),
269
+ amount: value,
270
+ divisor: value,
271
+ rounding: z.literal("floor"),
272
+ }),
273
+ z.strictObject({
274
+ target: name,
275
+ op: z.literal("shift"),
276
+ date: value,
277
+ milliseconds: value,
278
+ direction: z.enum(["before", "after"]),
428
279
  }),
429
280
  ]);
430
- // A same-instance conservation law: the piece fields (money, minor units)
431
- // must sum exactly to the total field; admission refuses a create whose
432
- // pieces diverge.
433
- // A composition-level dial. Instrument dials tune one instrument's frozen slots;
434
- // this one is a property of the whole product, so it hangs off the document.
435
- // `confirmation_threshold` carries minor units of the kernel currency: a
436
- // money-moving tool call whose declared money inputs sum below the configured
437
- // value skips the explicit confirmation argument.
438
- const udlCompositionDialSchema = z.strictObject({
439
- key: udlSnakeCaseSchema,
440
- kind: z.literal("confirmation_threshold"),
441
- maxMinor: z.number().int().positive().max(1_000_000_000_000).optional(),
442
- summary: nonEmptyTextSchema,
443
- title: nonEmptyTextSchema,
444
- });
445
- const udlPartitionSchema = z.strictObject({
446
- pieceFields: z.array(udlFieldNameSchema).min(2),
447
- totalField: udlFieldNameSchema,
448
- });
449
- const udlDerivedAmountSchema = z.strictObject({
450
- field: udlFieldNameSchema,
451
- rounding: z.literal("floor"),
452
- rule: z.strictObject({
453
- bps: z.number().int().min(1).max(9_999),
454
- kind: z.literal("percentage_of"),
281
+ export const udlRequirementSchema = z.discriminatedUnion("kind", [
282
+ z.strictObject({
283
+ kind: z.literal("hours"),
284
+ at: path,
285
+ start: integer.min(0).max(23),
286
+ end: integer.min(0).max(23),
287
+ timezone: text,
288
+ }),
289
+ comparisonRequirement,
290
+ z.strictObject({
291
+ kind: z.literal("state"),
292
+ reference: path,
293
+ states: z.array(name).min(1).max(64),
455
294
  }),
456
- sourceField: udlFieldNameSchema,
457
- });
458
- const udlFeeLeafRuleSchema = z.discriminatedUnion("kind", [
459
295
  z.strictObject({
460
- bps: z.number().int().min(1).max(9_999),
461
- kind: z.literal("bps"),
296
+ kind: z.literal("unique"),
297
+ namespace: name,
298
+ fields: z.array(path).min(1).max(8),
462
299
  }),
463
300
  z.strictObject({
464
- currencyField: udlFieldNameSchema,
465
- field: udlFieldNameSchema,
466
- kind: z.literal("exact"),
301
+ kind: z.literal("aggregate"),
302
+ selection,
303
+ measure: z.union([z.literal("count"), z.strictObject({ sum: path })]),
304
+ operator: z.enum(["==", "<=", ">=", "<", ">"]),
305
+ value,
306
+ }),
307
+ z.strictObject({
308
+ kind: z.literal("evidence"),
309
+ instruction: path.optional(),
310
+ subject: path,
311
+ family: name,
312
+ check: name,
313
+ result: name,
314
+ maxAge: integer.positive(),
467
315
  }),
468
316
  ]);
469
- const nonNegativeDecimalIntegerSchema = z
470
- .string()
471
- .regex(/^(0|[1-9][0-9]*)$/, "must be a nonnegative decimal integer");
472
- const udlFeeRuleSchema = z.strictObject({
473
- amountField: udlFieldNameSchema,
474
- baseField: udlFieldNameSchema,
475
- bearerField: udlFieldNameSchema,
476
- position: z.enum(["carved", "on_top"]),
477
- rule: z.discriminatedUnion("kind", [
478
- ...udlFeeLeafRuleSchema.options,
479
- z.strictObject({
480
- kind: z.literal("tiered"),
481
- tiers: z
482
- .array(z.strictObject({
483
- fromInclusive: nonNegativeDecimalIntegerSchema,
484
- rule: udlFeeLeafRuleSchema,
485
- toExclusive: nonNegativeDecimalIntegerSchema.optional(),
486
- }))
487
- .min(1)
488
- .max(16),
489
- }),
317
+ export const udlKernelOperationSchema = z.enum([
318
+ "internal_transfer.create",
319
+ "internal_transfer.reserve",
320
+ "internal_transfer.post",
321
+ "internal_transfer.void",
322
+ ]);
323
+ const moveEconomics = z.strictObject({
324
+ purpose: z.enum([
325
+ "earning",
326
+ "principal",
327
+ "participant_payout",
328
+ "internal",
329
+ "prepaid_credit",
330
+ "pass_through",
490
331
  ]),
332
+ sourceParty: name,
333
+ reversalOf: path.optional(),
491
334
  });
492
- const udlDecidedAmountSchema = z.strictObject({
493
- boundField: udlFieldNameSchema,
494
- field: udlFieldNameSchema,
495
- remainderAction: udlActionNameSchema,
496
- });
497
- const udlEffectRowSchema = z.strictObject({
498
- signature: nonEmptyTextSchema,
499
- source: nonEmptyTextSchema,
500
- });
501
- const udlNotifyEffectSchema = z.strictObject({
502
- channel: udlSnakeCaseSchema,
503
- role: udlSnakeCaseSchema,
504
- signature: nonEmptyTextSchema,
505
- source: nonEmptyTextSchema,
506
- });
507
- const udlEffectsShape = {
508
- decides: z.array(udlEffectRowSchema).min(1).optional(),
509
- holds: z.array(udlEffectRowSchema).min(1).optional(),
510
- moves: z.array(udlEffectRowSchema).min(1).optional(),
511
- notifies: z.array(udlNotifyEffectSchema).min(1).optional(),
512
- reads: z.array(udlEffectRowSchema).min(1).optional(),
513
- schedules: z.array(udlEffectRowSchema).min(1).optional(),
514
- };
515
- const udlEffectsSchema = z.strictObject(udlEffectsShape);
516
- const udlActionShape = {
517
- agentDescription: udlAgentDescriptionSchema.optional(),
518
- captureInput: z.record(udlFieldNameSchema, udlFieldNameSchema).optional(),
519
- /** Names the quoting action whose offer this action spends. */
520
- commit: udlActionNameSchema.optional(),
521
- decidedAmount: udlDecidedAmountSchema.optional(),
522
- deadline: udlDeadlineSchema.optional(),
523
- decision: udlDecisionSchema.optional(),
524
- distribute: udlDistributeSchema.optional(),
525
- description: nonEmptyTextSchema.optional(),
526
- due: udlDueSchema.optional(),
527
- earnable: z.boolean().optional(),
528
- effects: udlEffectsSchema.optional(),
529
- eventName: z.string().regex(eventNamePattern).optional(),
530
- examples: z.array(udlExampleSchema).min(1).optional(),
531
- input: jsonObjectSchema.optional(),
532
- moves: z.array(udlMoveSchema).default([]),
533
- payout: udlPayoutSchema.optional(),
534
- port: udlPortSchema.optional(),
535
- quote: udlQuoteSchema.optional(),
536
- publicAction: udlPublicActionSchema
537
- .optional()
538
- .describe("Author-approved public action name; the containing action key remains the lifecycle and execution identity"),
539
- principal: z.literal("user_session").optional(),
540
- reconcile: z.array(udlReconcileSchema).min(1).optional(),
541
- remainder: udlRemainderSchema.optional(),
542
- requiresAggregate: z.array(udlAggregateConditionSchema).min(1).optional(),
543
- requiresChecks: z.array(udlCheckRequirementSchema).optional(),
544
- requiresDrainedAccount: z.strictObject({ path: fieldPathSchema }).optional(),
545
- requiresExposure: z.array(udlExposureRequirementSchema).min(1).optional(),
546
- requiresRefs: z.array(udlGateSchema).min(1).optional(),
547
- sandboxFailurePoint: z.enum(["funding", "release"]).optional(),
548
- setsAt: udlSetsAtSchema.optional(),
549
- signedSum: udlSignedSumSchema.optional(),
550
- steps: z.array(udlStepSchema),
551
- summary: nonEmptyTextSchema,
552
- updates: z.array(udlFieldNameSchema).min(1).optional(),
553
- };
554
- const udlActionSchema = z.strictObject(udlActionShape);
555
- const udlAggregateBaseShape = {
556
- childInstrumentId: udlSnakeCaseSchema,
557
- childRefField: udlFieldNameSchema,
558
- childStatuses: z.array(udlSnakeCaseSchema).min(1),
559
- parentField: udlFieldNameSchema,
560
- };
561
- // Two measures, mutually exclusive by strictness: a sum cap over a child money
562
- // field, or a count cap (optionally windowed to the rolling `days` ending at
563
- // the child's own as-written date-time value).
564
- const udlAggregateSchema = z.union([
335
+ export const udlMoveSchema = z.discriminatedUnion("operation", [
565
336
  z.strictObject({
566
- ...udlAggregateBaseShape,
567
- childField: udlFieldNameSchema,
337
+ key: name,
338
+ operation: z.literal("internal_transfer.create"),
339
+ economics: moveEconomics.optional(),
340
+ capture: name.optional(),
341
+ amount: value,
342
+ from: path,
343
+ to: path,
568
344
  }),
569
345
  z.strictObject({
570
- ...udlAggregateBaseShape,
571
- count: z.literal(true),
572
- window: z
573
- .strictObject({
574
- field: udlFieldNameSchema,
575
- days: z.number().int().min(1).max(3650),
576
- })
577
- .optional(),
346
+ key: name,
347
+ operation: z.literal("internal_transfer.reserve"),
348
+ economics: moveEconomics.optional(),
349
+ boundary: z.strictObject({ adapter: name }).optional(),
350
+ amount: value,
351
+ from: path,
352
+ to: path,
353
+ capture: name,
354
+ }),
355
+ z.strictObject({
356
+ key: name,
357
+ operation: z.literal("internal_transfer.post"),
358
+ economics: moveEconomics.optional(),
359
+ capture: name.optional(),
360
+ transfer: path,
361
+ }),
362
+ z.strictObject({
363
+ key: name,
364
+ operation: z.literal("internal_transfer.void"),
365
+ economics: moveEconomics.optional(),
366
+ capture: name.optional(),
367
+ transfer: path,
578
368
  }),
579
369
  ]);
580
- const udlInstrumentShape = {
581
- actionOrder: z.array(udlActionNameSchema).min(1),
582
- agentDescription: udlAgentDescriptionSchema.optional(),
583
- aggregateInvariants: z.array(udlAggregateSchema).min(1).optional(),
584
- callerParkedStates: z
585
- .record(udlSnakeCaseSchema, nonEmptyTextSchema)
370
+ const clock = z.strictObject({
371
+ at: path,
372
+ offset: integer.optional(),
373
+ localDay: z
374
+ .strictObject({
375
+ days: integer.min(0).max(366),
376
+ direction: z.enum(["before", "after"]),
377
+ hour: integer.min(0).max(23),
378
+ timezone: text,
379
+ })
586
380
  .optional(),
587
- description: nonEmptyTextSchema.optional(),
588
- dials: z.array(udlDialSchema).optional(),
589
- distinctParties: z.literal(true).optional(),
590
- derivedAmounts: z.array(udlDerivedAmountSchema).min(1).max(4).optional(),
591
- feeRules: z.array(udlFeeRuleSchema).min(1).max(4).optional(),
592
- fields: z.record(udlFieldNameSchema, jsonObjectSchema),
593
- id: udlSnakeCaseSchema,
594
- idPrefix: z
595
- .string()
596
- .regex(idPrefixPattern, "must contain 2 to 8 lowercase letters"),
597
- lifecycle: udlLifecycleSchema,
598
- nav: z.array(nonEmptyTextSchema).min(1).optional(),
599
- parties: z
381
+ });
382
+ export const udlActionSchema = z.strictObject({
383
+ allowZero: z.literal(true).optional(),
384
+ purpose: z.enum(["obligation", "option"]).optional(),
385
+ summary: text,
386
+ title: text.optional(),
387
+ publicAction: name.optional(),
388
+ expansionLimit: z.literal(8192).optional(),
389
+ reminder: z
600
390
  .strictObject({
601
- beneficiary: udlFieldNameSchema.optional(),
602
- payer: udlFieldNameSchema.optional(),
603
- subjectHolder: udlFieldNameSchema.optional(),
391
+ installment: path,
392
+ recipient: path,
393
+ dueAt: path,
394
+ channel: z.literal("email"),
395
+ template: z.literal("payment_reminder"),
396
+ beforeDays: integer.min(0).max(366),
397
+ overdueDays: integer.min(1).max(366),
398
+ maxPerDay: integer.min(1).max(10),
399
+ startHour: integer.min(0).max(23),
400
+ endHour: integer.min(1).max(24),
401
+ timezone: text,
604
402
  })
605
403
  .optional(),
606
- partitions: z.array(udlPartitionSchema).min(1).optional(),
607
- required: z.array(udlFieldNameSchema),
608
- subject: udlInstrumentSubjectSchema.optional(),
609
- summary: nonEmptyTextSchema,
610
- surfaceVisibility: z.enum(["internal", "public", "system"]).optional(),
611
- templateId: udlSnakeCaseSchema.optional(),
612
- title: nonEmptyTextSchema,
613
- update: udlUpdatePolicySchema.optional(),
614
- actions: z.record(udlActionNameSchema, udlActionSchema),
615
- };
616
- const udlInstrumentSchema = z.strictObject(udlInstrumentShape);
617
- export const udlClauseVocabulary = [
618
- {
619
- cardinality: "one",
620
- scope: "action",
621
- spelling: "agent description",
622
- target: "agentDescription",
623
- },
624
- {
625
- cardinality: "one",
626
- scope: "action",
627
- spelling: "capture input",
628
- target: "captureInput",
629
- },
630
- {
631
- cardinality: "one",
632
- // The committing action consumes the quote through its priced moves.
633
- // An extra row here would bill the same movement twice.
634
- effects: [],
635
- scope: "action",
636
- spelling: "commit",
637
- target: "commit",
638
- },
639
- {
640
- cardinality: "one",
641
- effects: [
642
- {
643
- kind: "holds",
644
- per: "clause",
645
- signature: { fixed: "quote" },
646
- },
647
- {
648
- kind: "schedules",
649
- per: "clause",
650
- signature: { fixed: "expiry" },
651
- },
652
- ],
653
- linearOutputs: ["netRef"],
654
- scope: "action",
655
- spelling: "quote",
656
- target: "quote",
657
- },
658
- {
659
- cardinality: "one",
660
- linearOutputs: ["field"],
661
- scope: "action",
662
- spelling: "decided amount",
663
- target: "decidedAmount",
664
- },
665
- {
666
- cardinality: "one",
667
- effects: [
668
- {
669
- kind: "schedules",
670
- per: "clause",
671
- signature: { fixed: "deadline" },
672
- },
673
- ],
674
- scope: "action",
675
- spelling: "deadline",
676
- target: "deadline",
677
- },
678
- {
679
- cardinality: "one",
680
- effects: [
681
- {
682
- kind: "decides",
683
- per: "clause",
684
- signature: { fromField: "capability" },
685
- },
686
- ],
687
- scope: "action",
688
- spelling: "decision",
689
- target: "decision",
690
- },
691
- {
692
- cardinality: "one",
693
- linearOutputs: ["amountRef"],
694
- scope: "action",
695
- spelling: "computes distribute",
696
- target: "distribute",
697
- },
698
- {
699
- cardinality: "one",
700
- scope: "action",
701
- spelling: "description",
702
- target: "description",
703
- },
704
- {
705
- cardinality: "one",
706
- effects: [
707
- {
708
- kind: "schedules",
709
- per: "clause",
710
- signature: { fixed: "due" },
711
- },
712
- ],
713
- scope: "action",
714
- spelling: "due",
715
- target: "due",
716
- },
717
- {
718
- cardinality: "one",
719
- scope: "action",
720
- spelling: "earnable",
721
- target: "earnable",
722
- },
723
- {
724
- cardinality: "one",
725
- scope: "action",
726
- spelling: "event name",
727
- target: "eventName",
728
- },
729
- {
730
- cardinality: "many",
731
- scope: "action",
732
- spelling: "examples",
733
- target: "examples",
734
- },
735
- {
736
- cardinality: "one",
737
- scope: "action",
738
- spelling: "input",
739
- target: "input",
740
- },
741
- {
742
- cardinality: "many",
743
- effects: [
744
- {
745
- kind: "moves",
746
- per: "element",
747
- signature: { movementClass: true },
748
- },
749
- {
750
- kind: "holds",
751
- per: "element",
752
- signature: { fixed: "reserve" },
753
- },
754
- ],
755
- linearSink: true,
756
- scope: "action",
757
- spelling: "moves",
758
- target: "moves",
759
- },
760
- {
761
- cardinality: "one",
762
- effects: [
763
- {
764
- kind: "moves",
765
- per: "clause",
766
- signature: { fixed: "payout.external" },
767
- },
768
- ],
769
- linearSink: true,
770
- scope: "action",
771
- spelling: "payout",
772
- target: "payout",
773
- },
774
- {
775
- cardinality: "one",
776
- effects: [
777
- {
778
- kind: "decides",
779
- per: "clause",
780
- signature: { fixed: "tenant_port" },
781
- },
782
- ],
783
- scope: "action",
784
- spelling: "port",
785
- target: "port",
786
- },
787
- {
788
- cardinality: "one",
789
- scope: "action",
790
- spelling: "principal",
791
- target: "principal",
792
- },
793
- {
794
- cardinality: "one",
795
- scope: "action",
796
- spelling: "public action",
797
- target: "publicAction",
798
- },
799
- {
800
- cardinality: "many",
801
- effects: [
802
- {
803
- kind: "reads",
804
- per: "element",
805
- signature: { fixed: "reconcile" },
806
- },
807
- ],
808
- scope: "action",
809
- spelling: "reconcile",
810
- target: "reconcile",
811
- },
812
- {
813
- cardinality: "one",
814
- linearOutputs: ["amountRef"],
815
- scope: "action",
816
- spelling: "computes remainder",
817
- target: "remainder",
818
- },
819
- {
820
- cardinality: "many",
821
- effects: [
822
- {
823
- kind: "reads",
824
- per: "clause",
825
- signature: { fixed: "requires_aggregate" },
826
- },
827
- ],
828
- scope: "action",
829
- spelling: "requires aggregate",
830
- target: "requiresAggregate",
831
- },
832
- {
833
- cardinality: "many",
834
- effects: [
835
- {
836
- kind: "reads",
837
- per: "clause",
838
- signature: { fixed: "requires_checks" },
839
- },
840
- ],
841
- scope: "action",
842
- spelling: "requires checks",
843
- target: "requiresChecks",
844
- },
845
- {
846
- cardinality: "one",
847
- scope: "action",
848
- spelling: "requires drained",
849
- target: "requiresDrainedAccount",
850
- },
851
- {
852
- cardinality: "many",
853
- scope: "action",
854
- spelling: "requires exposure",
855
- target: "requiresExposure",
856
- },
857
- {
858
- cardinality: "many",
859
- effects: [
860
- {
861
- kind: "reads",
862
- per: "clause",
863
- signature: { fixed: "requires_refs" },
864
- },
865
- ],
866
- scope: "action",
867
- spelling: "requires refs",
868
- target: "requiresRefs",
869
- },
870
- {
871
- cardinality: "one",
872
- scope: "action",
873
- spelling: "sandbox failure point",
874
- target: "sandboxFailurePoint",
875
- },
876
- {
877
- cardinality: "one",
878
- scope: "action",
879
- spelling: "sets at",
880
- target: "setsAt",
881
- },
882
- {
883
- cardinality: "one",
884
- linearOutputs: ["amountRef"],
885
- scope: "action",
886
- spelling: "computes signed_sum",
887
- target: "signedSum",
888
- },
889
- {
890
- cardinality: "many",
891
- linearSink: true,
892
- scope: "action",
893
- spelling: "steps",
894
- target: "steps",
895
- },
896
- {
897
- cardinality: "one",
898
- scope: "action",
899
- spelling: "summary",
900
- target: "summary",
901
- },
902
- {
903
- cardinality: "many",
904
- scope: "action",
905
- spelling: "updates",
906
- target: "updates",
907
- },
908
- {
909
- cardinality: "many",
910
- effects: [
911
- {
912
- kind: "notifies",
913
- per: "element",
914
- signature: { fromField: "channel" },
915
- },
916
- ],
917
- scope: "action",
918
- spelling: "notify",
919
- target: "effects.notifies",
920
- },
921
- {
922
- cardinality: "one",
923
- scope: "instrument",
924
- spelling: "agent description",
925
- target: "agentDescription",
926
- },
927
- {
928
- cardinality: "many",
929
- scope: "instrument",
930
- spelling: "aggregate invariants",
931
- target: "aggregateInvariants",
932
- },
933
- {
934
- cardinality: "one",
935
- scope: "instrument",
936
- spelling: "caller parked states",
937
- target: "callerParkedStates",
938
- },
939
- {
940
- cardinality: "one",
941
- scope: "instrument",
942
- spelling: "description",
943
- target: "description",
944
- },
945
- {
946
- cardinality: "many",
947
- scope: "instrument",
948
- spelling: "dials",
949
- target: "dials",
950
- },
951
- {
952
- cardinality: "one",
953
- scope: "instrument",
954
- spelling: "distinct parties",
955
- target: "distinctParties",
956
- },
957
- {
958
- cardinality: "many",
959
- scope: "instrument",
960
- spelling: "computes derived",
961
- target: "derivedAmounts",
962
- },
963
- {
964
- cardinality: "many",
965
- scope: "instrument",
966
- spelling: "computes fees",
967
- target: "feeRules",
968
- },
969
- {
970
- cardinality: "one",
971
- scope: "instrument",
972
- spelling: "id prefix",
973
- target: "idPrefix",
974
- },
975
- {
976
- cardinality: "many",
977
- scope: "instrument",
978
- spelling: "nav",
979
- target: "nav",
980
- },
981
- {
982
- cardinality: "many",
983
- scope: "instrument",
984
- spelling: "partitions",
985
- target: "partitions",
986
- },
987
- {
988
- cardinality: "one",
989
- scope: "instrument",
990
- spelling: "subject",
991
- target: "subject",
992
- },
993
- {
994
- cardinality: "one",
995
- scope: "instrument",
996
- spelling: "summary",
997
- target: "summary",
998
- },
999
- {
1000
- cardinality: "one",
1001
- scope: "instrument",
1002
- spelling: "surface visibility",
1003
- target: "surfaceVisibility",
1004
- },
1005
- {
1006
- cardinality: "one",
1007
- scope: "instrument",
1008
- spelling: "template id",
1009
- target: "templateId",
1010
- },
1011
- {
1012
- cardinality: "one",
1013
- scope: "instrument",
1014
- spelling: "title",
1015
- target: "title",
1016
- },
1017
- {
1018
- cardinality: "one",
1019
- scope: "instrument",
1020
- spelling: "update",
1021
- target: "update",
1022
- },
1023
- ];
1024
- const udlDocumentShapeSchema = z.strictObject({
1025
- dials: z.array(udlCompositionDialSchema).min(1).optional(),
1026
- instruments: z.array(udlInstrumentSchema).min(1),
1027
- product: udlSnakeCaseSchema,
1028
- subjects: z.array(udlSubjectSchema),
1029
- title: nonEmptyTextSchema,
1030
- udl: z.literal(UDL_FORMAT_VERSION),
1031
- version: z.number().int().positive(),
404
+ event: text,
405
+ actor: z.union([
406
+ z.literal("caller"),
407
+ z.literal("clock"),
408
+ z.strictObject({ party: name }),
409
+ z.strictObject({
410
+ parent: z.union([instrumentId, z.array(instrumentId).min(1).max(16)]),
411
+ }),
412
+ ]),
413
+ subject: udlActionSubjectSchema.optional(),
414
+ input: z.array(udlFieldSchema).max(128),
415
+ requires: z.array(udlRequirementSchema).max(128),
416
+ due: clock.optional(),
417
+ deadline: clock.optional(),
418
+ set: z.record(name, value).optional(),
419
+ calculate: z.array(udlCalculationSchema).max(128).optional(),
420
+ moves: z.array(udlMoveSchema).max(256),
421
+ invoke: z
422
+ .array(z.union([
423
+ z.strictObject({
424
+ instrument: instrumentId,
425
+ action: z.literal("create"),
426
+ input: z.record(name, value),
427
+ guard: comparisonRequirement.optional(),
428
+ range: z
429
+ .strictObject({
430
+ count: value,
431
+ maximum: integer.min(1).max(366),
432
+ bind: name,
433
+ })
434
+ .optional(),
435
+ }),
436
+ z.strictObject({
437
+ reference: path,
438
+ action: name,
439
+ input: z.record(name, value),
440
+ guard: comparisonRequirement.optional(),
441
+ }),
442
+ z.strictObject({
443
+ selection,
444
+ action: name,
445
+ input: z.record(name, value),
446
+ guard: comparisonRequirement.optional(),
447
+ }),
448
+ ]))
449
+ .max(16)
450
+ .optional(),
1032
451
  });
1033
- const moneyPatterns = new Set(["^[1-9][0-9]{0,17}$", "^(0|[1-9][0-9]{0,17})$"]);
1034
- function isMoneyField(schema) {
1035
- return (typeof schema?.pattern === "string" && moneyPatterns.has(schema.pattern));
1036
- }
1037
- function referenceTargets(schema, instruments) {
1038
- if (schema.type !== "string" || typeof schema.pattern !== "string")
1039
- return [];
1040
- // Parse the scoped-ID pattern. Executing an author regex here would bypass
1041
- // the semantic validator's branch and quantifier budgets.
1042
- const match = /^\^([a-z]{2,8})_\(sandbox\|live\)_\[a-z0-9\]\{8,64\}\$$/.exec(schema.pattern);
1043
- if (!match)
1044
- return [];
1045
- const prefix = match[1];
1046
- const probe = `${prefix}_sandbox_0123456789abcdef`;
1047
- if (typeof schema.const === "string" && schema.const !== probe)
1048
- return [];
1049
- if (Array.isArray(schema.enum) && !schema.enum.includes(probe))
1050
- return [];
1051
- if (typeof schema.minLength === "number" && probe.length < schema.minLength) {
1052
- return [];
1053
- }
1054
- if (typeof schema.maxLength === "number" && probe.length > schema.maxLength) {
1055
- return [];
1056
- }
1057
- return instruments.filter((instrument) => instrument.idPrefix === prefix);
1058
- }
1059
- function declaredMoneyRefKeys(instrument) {
1060
- return new Set([
1061
- ...Object.values(instrument.actions).flatMap((action) => [
1062
- ...(action.remainder
1063
- ? [
1064
- action.remainder.amountRef,
1065
- ...(action.remainder.accumulateRef
1066
- ? [action.remainder.accumulateRef]
1067
- : []),
1068
- ]
1069
- : []),
1070
- ...(action.signedSum
1071
- ? [
1072
- action.signedSum.amountRef,
1073
- ...action.signedSum.sources.map((source) => source.subtotalRef),
1074
- ]
1075
- : []),
1076
- ...(action.distribute ? [action.distribute.amountRef] : []),
1077
- ...(action.quote ? [action.quote.chargeRef, action.quote.netRef] : []),
1078
- ]),
1079
- ]);
1080
- }
1081
- export const udlDocumentSchema = udlDocumentShapeSchema.superRefine((document, context) => {
1082
- document.instruments.forEach((instrument, instrumentIndex) => {
1083
- instrument.derivedAmounts?.forEach((amount, amountIndex) => {
1084
- const base = [
1085
- "instruments",
1086
- instrumentIndex,
1087
- "derivedAmounts",
1088
- amountIndex,
1089
- ];
1090
- if (!isMoneyField(instrument.fields[amount.field])) {
1091
- context.addIssue({
1092
- code: "custom",
1093
- message: `derived amount target ${amount.field} must be a declared money field`,
1094
- path: [...base, "field"],
1095
- });
1096
- }
1097
- if (!isMoneyField(instrument.fields[amount.sourceField])) {
1098
- context.addIssue({
1099
- code: "custom",
1100
- message: `derived amount source ${amount.sourceField} must be a declared money field`,
1101
- path: [...base, "sourceField"],
1102
- });
1103
- }
1104
- if (amount.field === amount.sourceField) {
1105
- context.addIssue({
1106
- code: "custom",
1107
- message: "a derived amount cannot derive from itself",
1108
- path: [...base, "sourceField"],
1109
- });
1110
- }
1111
- });
1112
- Object.entries(instrument.actions).forEach(([actionName, action]) => {
1113
- const distribute = action.distribute;
1114
- if (!distribute)
1115
- return;
1116
- const base = [
1117
- "instruments",
1118
- instrumentIndex,
1119
- "actions",
1120
- actionName,
1121
- "distribute",
1122
- ];
1123
- const refSchema = instrument.fields[distribute.refField];
1124
- const parents = refSchema
1125
- ? referenceTargets(refSchema, document.instruments)
1126
- : [];
1127
- if (parents.length !== 1) {
1128
- context.addIssue({
1129
- code: "custom",
1130
- message: `distribute refField ${distribute.refField} must identify exactly one parent instrument`,
1131
- path: [...base, "refField"],
1132
- });
1133
- }
1134
- if (!isMoneyField(instrument.fields[distribute.weightField])) {
1135
- context.addIssue({
1136
- code: "custom",
1137
- message: `distribute weightField ${distribute.weightField} must be a declared money field`,
1138
- path: [...base, "weightField"],
1139
- });
1140
- }
1141
- distribute.statuses.forEach((status, statusIndex) => {
1142
- if (instrument.lifecycle.states.includes(status))
1143
- return;
1144
- context.addIssue({
1145
- code: "custom",
1146
- message: `distribute status ${status} is not declared by ${instrument.id}`,
1147
- path: [...base, "statuses", statusIndex],
1148
- });
1149
- });
1150
- const parent = parents[0];
1151
- if (!parent)
1152
- return;
1153
- const poolMatch = /^(fields|refs)\.([a-z][A-Za-z0-9]*)$/.exec(distribute.pool.path);
1154
- const root = poolMatch?.[1];
1155
- const key = poolMatch?.[2];
1156
- const poolDeclared = root === "fields"
1157
- ? isMoneyField(parent.fields[key ?? ""])
1158
- : root === "refs"
1159
- ? declaredMoneyRefKeys(parent).has(key ?? "")
1160
- : false;
1161
- if (!poolDeclared) {
1162
- context.addIssue({
1163
- code: "custom",
1164
- message: `distribute pool ${distribute.pool.path} must be a declared money field or ref of ${parent.id}`,
1165
- path: [...base, "pool", "path"],
1166
- });
1167
- }
1168
- });
1169
- });
452
+ export const udlLifecycleSchema = z.strictObject({
453
+ states: z.array(name).min(1).max(64),
454
+ labels: z.record(name, text).optional(),
455
+ initial: name,
456
+ transitions: z.record(name, z.strictObject({ from: z.array(name).min(1).max(64), to: name })),
457
+ });
458
+ export const udlInstrumentSchema = z.strictObject({
459
+ scope: z.literal("product").optional(),
460
+ reports: z.array(reportDefinitionSchema).max(16).optional(),
461
+ revisioned: z.literal(true).optional(),
462
+ family: udlFamilySchema.optional(),
463
+ id: instrumentId,
464
+ subject: objectKindId.optional(),
465
+ title: text,
466
+ summary: text,
467
+ fields: z.array(udlFieldSchema).max(256),
468
+ calculate: z.array(udlCalculationSchema).max(128),
469
+ lifecycle: udlLifecycleSchema,
470
+ actions: z.record(name, udlActionSchema),
471
+ actionOrder: z.array(name).min(1).max(128),
472
+ invariants: z.array(udlRequirementSchema).max(128).optional(),
473
+ examples: z
474
+ .array(z.strictObject({ name: text, input: z.json() }))
475
+ .max(16)
476
+ .optional(),
1170
477
  });
478
+ export const udlDocumentSchema = z.strictObject({
479
+ udl: z.literal(UDL_FORMAT_VERSION),
480
+ version: integer.positive(),
481
+ product: name,
482
+ title: text,
483
+ currency: z.literal("SAR"),
484
+ parties: z.record(name, udlPartySchema),
485
+ objects: z.array(udlObjectKindSchema).max(256),
486
+ instruments: z.array(udlInstrumentSchema).max(256),
487
+ });
488
+ /** Names and display text may differ; executable constraints must match. */
489
+ export function sameObjectField(left, right) {
490
+ const signature = (field) => writeJson(Object.fromEntries(Object.entries(field).filter(([key, value]) => !["name", "label", "description", "optional"].includes(key) &&
491
+ value !== undefined)));
492
+ return signature(left) === signature(right);
493
+ }
1171
494
  //# sourceMappingURL=schema.js.map