@hyperscale0/udl 1.0.0-beta.1 → 1.0.0

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 (179) hide show
  1. package/CHANGELOG.md +78 -25
  2. package/README.md +37 -99
  3. package/conformance/README.md +44 -41
  4. package/conformance/evolution/action-contract.expected.json +10 -0
  5. package/conformance/evolution/action-contract.live.udl +44 -0
  6. package/conformance/evolution/action-contract.next.udl +45 -0
  7. package/conformance/evolution/product-identity.expected.json +10 -0
  8. package/conformance/evolution/product-identity.live.udl +55 -0
  9. package/conformance/evolution/product-identity.next.udl +55 -0
  10. package/conformance/evolution/version-required.expected.json +10 -0
  11. package/conformance/evolution/version-required.live.udl +55 -0
  12. package/conformance/evolution/version-required.next.udl +58 -0
  13. package/conformance/invalid/action-without-transition.expected.json +10 -0
  14. package/conformance/invalid/{verb-without-transition.udl → action-without-transition.udl} +7 -2
  15. package/conformance/invalid/agent-description-too-long.expected.json +10 -0
  16. package/conformance/invalid/agent-description-too-long.udl +57 -0
  17. package/conformance/invalid/blank-title.expected.json +1 -1
  18. package/conformance/invalid/blank-title.udl +6 -2
  19. package/conformance/invalid/composition-dial-duplicate-key.expected.json +10 -0
  20. package/conformance/invalid/composition-dial-duplicate-key.udl +73 -0
  21. package/conformance/invalid/depth-budget.expected.json +1 -1
  22. package/conformance/invalid/duplicate-subject.expected.json +10 -0
  23. package/conformance/invalid/duplicate-subject.udl +230 -0
  24. package/conformance/invalid/forged-effects.expected.json +10 -0
  25. package/conformance/invalid/forged-effects.udl +76 -0
  26. package/conformance/invalid/format-version.expected.json +1 -1
  27. package/conformance/invalid/format-version.udl +6 -2
  28. package/conformance/invalid/instrument-id-not-snake-case.expected.json +10 -0
  29. package/conformance/invalid/{noun-id-not-snake-case.udl → instrument-id-not-snake-case.udl} +6 -2
  30. package/conformance/invalid/invalid-aggregate-gate-shape.expected.json +10 -0
  31. package/conformance/invalid/invalid-aggregate-gate-shape.udl +1819 -0
  32. package/conformance/invalid/invalid-check-duration.expected.json +10 -0
  33. package/conformance/invalid/invalid-check-duration.udl +219 -0
  34. package/conformance/invalid/invalid-dial-anchor.expected.json +10 -0
  35. package/conformance/invalid/invalid-dial-anchor.udl +219 -0
  36. package/conformance/invalid/invalid-exception-parent-ref.expected.json +10 -0
  37. package/conformance/invalid/invalid-exception-parent-ref.udl +2487 -0
  38. package/conformance/invalid/invalid-exposure-shape.expected.json +10 -0
  39. package/conformance/invalid/invalid-exposure-shape.udl +1819 -0
  40. package/conformance/invalid/invalid-remainder.expected.json +10 -0
  41. package/conformance/invalid/invalid-remainder.udl +220 -0
  42. package/conformance/invalid/invalid-schema-keyword.expected.json +10 -0
  43. package/conformance/invalid/invalid-schema-keyword.udl +220 -0
  44. package/conformance/invalid/invalid-utf8.expected.json +1 -1
  45. package/conformance/invalid/malformed-json.expected.json +1 -1
  46. package/conformance/invalid/missing-create-action.expected.json +10 -0
  47. package/conformance/invalid/{missing-create-verb.udl → missing-create-action.udl} +5 -2
  48. package/conformance/invalid/missing-exception-amount-field.expected.json +10 -0
  49. package/conformance/invalid/missing-exception-amount-field.udl +2487 -0
  50. package/conformance/invalid/missing-exception-contract.expected.json +14 -0
  51. package/conformance/invalid/missing-exception-contract.udl +2523 -0
  52. package/conformance/invalid/missing-exception-reason-field.expected.json +10 -0
  53. package/conformance/invalid/missing-exception-reason-field.udl +2487 -0
  54. package/conformance/invalid/not-an-object.expected.json +1 -1
  55. package/conformance/invalid/payout-reconcile-not-a-bank-debit.expected.json +10 -0
  56. package/conformance/invalid/payout-reconcile-not-a-bank-debit.udl +259 -0
  57. package/conformance/invalid/quote-freeze-set-incomplete.expected.json +10 -0
  58. package/conformance/invalid/quote-freeze-set-incomplete.udl +261 -0
  59. package/conformance/invalid/quote-named-reference-gate.expected.json +10 -0
  60. package/conformance/invalid/quote-named-reference-gate.udl +50 -0
  61. package/conformance/invalid/reconcile-named-reference-gate.expected.json +10 -0
  62. package/conformance/invalid/reconcile-named-reference-gate.udl +50 -0
  63. package/conformance/invalid/unknown-key.expected.json +1 -1
  64. package/conformance/invalid/unknown-key.udl +6 -2
  65. package/conformance/invalid/unknown-reference-gate-field.expected.json +10 -0
  66. package/conformance/invalid/unknown-reference-gate-field.udl +1819 -0
  67. package/conformance/invalid/unknown-required-field.expected.json +10 -0
  68. package/conformance/invalid/unknown-required-field.udl +220 -0
  69. package/conformance/invalid/unreachable-state.expected.json +2 -2
  70. package/conformance/invalid/unreachable-state.udl +6 -2
  71. package/conformance/invalid/wrong-exception-amount-field.expected.json +10 -0
  72. package/conformance/invalid/wrong-exception-amount-field.udl +2487 -0
  73. package/conformance/invalid/wrong-exception-reason-field.expected.json +10 -0
  74. package/conformance/invalid/wrong-exception-reason-field.udl +2487 -0
  75. package/conformance/valid/agent-description.expected.json +6 -0
  76. package/conformance/valid/agent-description.udl +66 -0
  77. package/conformance/valid/cards.expected.json +1 -0
  78. package/conformance/valid/cards.udl +843 -356
  79. package/conformance/valid/commerce-escrow.expected.json +1 -0
  80. package/conformance/valid/commerce-escrow.udl +792 -291
  81. package/conformance/valid/compiled-crowdfunding.expected.json +6 -0
  82. package/conformance/valid/compiled-crowdfunding.udl +1843 -0
  83. package/conformance/valid/compiled-watch-club.expected.json +6 -0
  84. package/conformance/valid/compiled-watch-club.udl +2486 -0
  85. package/conformance/valid/complete-contract.expected.json +6 -0
  86. package/conformance/valid/complete-contract.udl +218 -0
  87. package/conformance/valid/effect-signatures.expected.json +6 -0
  88. package/conformance/valid/effect-signatures.udl +75 -0
  89. package/conformance/valid/hand-edited.expected.json +1 -0
  90. package/conformance/valid/hand-edited.udl +1 -1
  91. package/conformance/valid/insured-car-marketplace.expected.json +1 -0
  92. package/conformance/valid/insured-car-marketplace.udl +534 -315
  93. package/conformance/valid/insured-travel.expected.json +2 -1
  94. package/conformance/valid/insured-travel.udl +1489 -641
  95. package/conformance/valid/minimal.expected.json +1 -0
  96. package/conformance/valid/minimal.udl +18 -14
  97. package/conformance/valid/protection.expected.json +1 -0
  98. package/conformance/valid/protection.udl +719 -304
  99. package/conformance/valid/string-escaping.expected.json +6 -0
  100. package/conformance/valid/string-escaping.udl +54 -0
  101. package/dist/canonical.d.ts +2 -0
  102. package/dist/canonical.d.ts.map +1 -1
  103. package/dist/canonical.js +7 -0
  104. package/dist/canonical.js.map +1 -1
  105. package/dist/check-profiles.d.ts +57 -0
  106. package/dist/check-profiles.d.ts.map +1 -0
  107. package/dist/check-profiles.js +62 -0
  108. package/dist/check-profiles.js.map +1 -0
  109. package/dist/cli.js +48 -4
  110. package/dist/cli.js.map +1 -1
  111. package/dist/diagnostics.d.ts +168 -0
  112. package/dist/diagnostics.d.ts.map +1 -0
  113. package/dist/diagnostics.js +164 -0
  114. package/dist/diagnostics.js.map +1 -0
  115. package/dist/effects.d.ts +41 -0
  116. package/dist/effects.d.ts.map +1 -0
  117. package/dist/effects.js +110 -0
  118. package/dist/effects.js.map +1 -0
  119. package/dist/evolution.d.ts +51 -18
  120. package/dist/evolution.d.ts.map +1 -1
  121. package/dist/evolution.js +244 -102
  122. package/dist/evolution.js.map +1 -1
  123. package/dist/finance.d.ts +17 -13
  124. package/dist/finance.d.ts.map +1 -1
  125. package/dist/finance.js +165 -109
  126. package/dist/finance.js.map +1 -1
  127. package/dist/index.d.ts +12 -8
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +8 -5
  130. package/dist/index.js.map +1 -1
  131. package/dist/limits.d.ts +2 -2
  132. package/dist/limits.d.ts.map +1 -1
  133. package/dist/limits.js +4 -3
  134. package/dist/limits.js.map +1 -1
  135. package/dist/parser.d.ts.map +1 -1
  136. package/dist/parser.js +6 -16
  137. package/dist/parser.js.map +1 -1
  138. package/dist/schema.d.ts +2059 -135
  139. package/dist/schema.d.ts.map +1 -1
  140. package/dist/schema.js +759 -73
  141. package/dist/schema.js.map +1 -1
  142. package/dist/validation.d.ts +22 -9
  143. package/dist/validation.d.ts.map +1 -1
  144. package/dist/validation.js +1463 -457
  145. package/dist/validation.js.map +1 -1
  146. package/docs/README.md +14 -0
  147. package/docs/guide/01-a-document.md +37 -0
  148. package/docs/guide/02-money-steps.md +23 -0
  149. package/docs/guide/03-laws.md +18 -0
  150. package/docs/guide/04-fees-and-remainder.md +15 -0
  151. package/docs/guide/05-checks-updates-dials.md +7 -0
  152. package/docs/guide/06-effects.md +11 -0
  153. package/docs/guide/07-evolution.md +11 -0
  154. package/docs/guide/08-implementing.md +13 -0
  155. package/docs/llms-full.txt +1241 -0
  156. package/docs/llms.txt +14 -0
  157. package/docs/reference/canonical.md +16 -0
  158. package/docs/reference/clauses.md +986 -0
  159. package/docs/reference/cli.md +22 -0
  160. package/docs/reference/diagnostics.md +32 -0
  161. package/package.json +6 -2
  162. package/skills/udl/SKILL.md +28 -0
  163. package/spec/README.md +82 -51
  164. package/spec/udl.schema.json +1191 -119
  165. package/src/canonical.ts +11 -0
  166. package/src/check-profiles.ts +80 -0
  167. package/src/cli.ts +49 -3
  168. package/src/diagnostics.ts +221 -0
  169. package/src/effects.ts +163 -0
  170. package/src/evolution.ts +375 -125
  171. package/src/finance.ts +255 -160
  172. package/src/index.ts +50 -14
  173. package/src/limits.ts +4 -3
  174. package/src/parser.ts +15 -18
  175. package/src/schema.ts +855 -81
  176. package/src/validation.ts +2666 -560
  177. package/conformance/invalid/missing-create-verb.expected.json +0 -10
  178. package/conformance/invalid/noun-id-not-snake-case.expected.json +0 -10
  179. package/conformance/invalid/verb-without-transition.expected.json +0 -10
package/dist/schema.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { udlCheckEvidenceProfiles } from "./check-profiles.js";
2
3
  export const UDL_FORMAT_VERSION = 1;
3
4
  const snakeCasePattern = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
4
5
  const camelCasePattern = /^[a-z][A-Za-z0-9]*$/;
@@ -8,18 +9,26 @@ const eventNamePattern = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/;
8
9
  const udlSnakeCaseSchema = z
9
10
  .string()
10
11
  .regex(snakeCasePattern, "must be a snake_case identifier");
11
- const udlVerbNameSchema = z
12
+ const udlActionNameSchema = z
12
13
  .string()
13
- .regex(snakeCasePattern, "must be a snake_case verb identifier");
14
+ .regex(snakeCasePattern, "must be a snake_case action identifier");
14
15
  const udlFieldNameSchema = z
15
16
  .string()
16
17
  .regex(camelCasePattern, "must be a camelCase field name");
17
- export const udlPublicIntentSchema = z
18
+ export const udlPublicActionSchema = z
18
19
  .string()
19
- .regex(camelCasePattern, "must be a camelCase public intent name");
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");
20
25
  const nonEmptyTextSchema = z
21
26
  .string()
22
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);
23
32
  const fieldPathSchema = z
24
33
  .string()
25
34
  .regex(fieldPathPattern, "must be a dot-separated camelCase field path");
@@ -28,6 +37,25 @@ const instanceValuePathSchema = z
28
37
  .regex(/^(?:fields|refs)\.[a-z][A-Za-z0-9]*$/, "must name one declared fields.* or refs.* value");
29
38
  const jsonObjectSchema = z.record(z.string(), z.json());
30
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);
31
59
  const udlDeclaredValuePolicySchema = z.enum(["required", "optional", "none"]);
32
60
  const udlSubjectSchema = z.strictObject({
33
61
  declaredValue: udlDeclaredValuePolicySchema,
@@ -36,9 +64,15 @@ const udlSubjectSchema = z.strictObject({
36
64
  title: nonEmptyTextSchema,
37
65
  version: z.number().int().positive(),
38
66
  });
39
- const udlNounSubjectSchema = z.strictObject({
67
+ const udlInstrumentSubjectSchema = z.strictObject({
68
+ extensible: z.boolean().optional(),
40
69
  kinds: z.array(udlSnakeCaseSchema).min(1),
41
70
  });
71
+ const udlExampleSchema = z.strictObject({
72
+ input: jsonObjectSchema,
73
+ name: nonEmptyTextSchema,
74
+ output: z.json().optional(),
75
+ });
42
76
  const udlLifecycleTransitionSchema = z.strictObject({
43
77
  from: z.array(udlSnakeCaseSchema).min(1),
44
78
  to: udlSnakeCaseSchema,
@@ -46,14 +80,15 @@ const udlLifecycleTransitionSchema = z.strictObject({
46
80
  const udlLifecycleSchema = z.strictObject({
47
81
  initial: udlSnakeCaseSchema,
48
82
  states: z.array(udlSnakeCaseSchema).min(1),
49
- transitions: z.record(udlVerbNameSchema, udlLifecycleTransitionSchema),
83
+ transitions: z.record(udlActionNameSchema, udlLifecycleTransitionSchema),
50
84
  });
51
85
  const udlUpdatePolicySchema = z.strictObject({
86
+ examples: z.array(udlExampleSchema).optional(),
52
87
  fields: z.array(udlFieldNameSchema).min(1),
53
88
  states: z.array(udlSnakeCaseSchema).min(1),
54
89
  });
55
90
  const udlGateSchema = z.strictObject({
56
- /** Local field key <- referenced instance path; create verbs only. */
91
+ /** Local field key <- referenced instance path; create actions only. */
57
92
  bind: z.record(udlFieldNameSchema, fieldPathSchema).optional(),
58
93
  field: udlFieldNameSchema,
59
94
  /** Local instance path === referenced instance path at admission. */
@@ -61,7 +96,7 @@ const udlGateSchema = z.strictObject({
61
96
  /** Opt-in over an optional reference field: absent passes, set is held to the clause. */
62
97
  optional: z.literal(true).optional(),
63
98
  statuses: z.array(udlSnakeCaseSchema).min(1),
64
- /** One dependent ever per referenced instance; create verbs only. */
99
+ /** One dependent ever per referenced instance; create actions only. */
65
100
  unique: z.literal(true).optional(),
66
101
  });
67
102
  const udlBindingSchema = z.discriminatedUnion("from", [
@@ -97,17 +132,22 @@ const udlMoveSchema = z.strictObject({
97
132
  });
98
133
  const udlDueRecurrenceSchema = z.strictObject({
99
134
  countField: udlFieldNameSchema.optional(),
135
+ delinquency: z.literal("parent_policy").optional(),
136
+ drainAction: udlActionNameSchema.optional(),
100
137
  period: z.union([
101
138
  nonEmptyTextSchema,
102
139
  z.strictObject({
103
140
  calendar: z.enum(["gregorian", "hijri"]),
141
+ monthEnd: z.literal("clamp_to_last_day"),
104
142
  months: z.number().int().positive(),
105
143
  }),
106
144
  ]),
145
+ liability: z.literal("one_open").optional(),
146
+ untilAction: udlActionNameSchema.optional(),
107
147
  untilField: udlFieldNameSchema.optional(),
108
148
  });
109
149
  const udlDueParentStatusSchema = z.strictObject({
110
- nounId: udlSnakeCaseSchema,
150
+ instrumentId: udlSnakeCaseSchema,
111
151
  refField: udlFieldNameSchema,
112
152
  statuses: z.array(udlSnakeCaseSchema).min(1),
113
153
  });
@@ -126,21 +166,62 @@ const udlSetsAtSchema = z.strictObject({
126
166
  marker: z.literal(true).optional(),
127
167
  offset: nonEmptyTextSchema,
128
168
  });
129
- const udlUnwindPenaltySchema = z.strictObject({
169
+ const udlQuoteChargeSchema = z.strictObject({
130
170
  bps: z.number().int().min(0).max(9_999),
131
171
  withinOffset: nonEmptyTextSchema.optional(),
132
172
  });
133
- const udlUnwindSchema = z.strictObject({
134
- beforeField: udlFieldNameSchema.optional(),
135
- confirm: udlVerbNameSchema,
136
- penalty: z.array(udlUnwindPenaltySchema).min(1),
137
- quote: udlVerbNameSchema,
138
- refundableField: udlFieldNameSchema,
139
- refundDestinationField: udlFieldNameSchema,
140
- });
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,
191
+ };
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
+ }
141
222
  const udlDecisionSchema = z.strictObject({
142
- capability: udlSnakeCaseSchema,
143
- deadlineMs: z.number().int().positive(),
223
+ capability: udlProviderFamilyIdSchema,
224
+ deadlineMs: z.number().int().positive().max(15_000),
144
225
  // A decision that never came back is not an approval. `approve` would let a
145
226
  // document release money because a counterparty went quiet, so the grammar
146
227
  // admits only the fail-closed answer. Every published document already
@@ -148,11 +229,6 @@ const udlDecisionSchema = z.strictObject({
148
229
  // be.
149
230
  onTimeout: z.literal("decline"),
150
231
  });
151
- const udlExampleSchema = z.strictObject({
152
- input: jsonObjectSchema,
153
- name: nonEmptyTextSchema,
154
- output: z.json().optional(),
155
- });
156
232
  const udlAggregateCheckSchema = z.discriminatedUnion("kind", [
157
233
  z.strictObject({
158
234
  amountField: udlFieldNameSchema,
@@ -188,7 +264,7 @@ const udlAggregateCheckSchema = z.discriminatedUnion("kind", [
188
264
  ]);
189
265
  const udlAggregateConditionSchema = z.strictObject({
190
266
  check: udlAggregateCheckSchema,
191
- nounId: udlSnakeCaseSchema.optional(),
267
+ instrumentId: udlSnakeCaseSchema.optional(),
192
268
  over: z.enum(["children", "siblings"]),
193
269
  refField: udlFieldNameSchema,
194
270
  statuses: z.array(udlSnakeCaseSchema).min(1),
@@ -198,11 +274,11 @@ const udlExposureRequirementSchema = z.strictObject({
198
274
  anchorField: udlFieldNameSchema,
199
275
  capField: udlFieldNameSchema,
200
276
  capOnAnchor: z.literal(true).optional(),
201
- childNounId: udlSnakeCaseSchema,
277
+ childInstrumentId: udlSnakeCaseSchema,
202
278
  statuses: z.array(udlSnakeCaseSchema).min(1),
203
279
  });
204
- // The tenant-backend decision port: the verb's caller asserts the acting
205
- // party, checked at admission against the noun's party bindings for the
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
206
282
  // allowed roles.
207
283
  const udlPortSchema = z.strictObject({
208
284
  allowedParties: z
@@ -218,13 +294,60 @@ const udlPayoutSchema = z.strictObject({
218
294
  sourceAccountField: udlFieldNameSchema,
219
295
  speed: z.literal("standard"),
220
296
  });
221
- const udlRequiresSettlementSchema = z.strictObject({
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. */
222
309
  capture: udlFieldNameSchema,
223
- payoutRef: 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,
327
+ }),
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
+ ]),
224
347
  });
225
348
  const udlSignedSumSourceSchema = z.strictObject({
226
349
  amountField: udlFieldNameSchema,
227
- nounId: udlSnakeCaseSchema,
350
+ instrumentId: udlSnakeCaseSchema,
228
351
  refField: udlFieldNameSchema,
229
352
  sign: z.enum(["add", "subtract"]),
230
353
  statuses: z.array(udlSnakeCaseSchema).min(1),
@@ -247,9 +370,78 @@ const udlDistributeSchema = z.strictObject({
247
370
  statuses: z.array(udlSnakeCaseSchema).min(1),
248
371
  weightField: udlFieldNameSchema,
249
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
+ 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,
405
+ }),
406
+ 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,
414
+ }),
415
+ 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,
422
+ }),
423
+ z.strictObject({
424
+ key: udlSnakeCaseSchema,
425
+ kind: z.literal("unwind_penalty"),
426
+ summary: nonEmptyTextSchema,
427
+ title: nonEmptyTextSchema,
428
+ }),
429
+ ]);
250
430
  // A same-instance conservation law: the piece fields (money, minor units)
251
431
  // must sum exactly to the total field; admission refuses a create whose
252
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
+ });
253
445
  const udlPartitionSchema = z.strictObject({
254
446
  pieceFields: z.array(udlFieldNameSchema).min(2),
255
447
  totalField: udlFieldNameSchema,
@@ -263,35 +455,105 @@ const udlDerivedAmountSchema = z.strictObject({
263
455
  }),
264
456
  sourceField: udlFieldNameSchema,
265
457
  });
266
- const udlVerbSchema = z.strictObject({
458
+ const udlFeeLeafRuleSchema = z.discriminatedUnion("kind", [
459
+ z.strictObject({
460
+ bps: z.number().int().min(1).max(9_999),
461
+ kind: z.literal("bps"),
462
+ }),
463
+ z.strictObject({
464
+ currencyField: udlFieldNameSchema,
465
+ field: udlFieldNameSchema,
466
+ kind: z.literal("exact"),
467
+ }),
468
+ ]);
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
+ }),
490
+ ]),
491
+ });
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(),
267
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(),
268
522
  deadline: udlDeadlineSchema.optional(),
269
523
  decision: udlDecisionSchema.optional(),
270
524
  distribute: udlDistributeSchema.optional(),
271
525
  description: nonEmptyTextSchema.optional(),
272
526
  due: udlDueSchema.optional(),
273
527
  earnable: z.boolean().optional(),
528
+ effects: udlEffectsSchema.optional(),
274
529
  eventName: z.string().regex(eventNamePattern).optional(),
275
530
  examples: z.array(udlExampleSchema).min(1).optional(),
276
531
  input: jsonObjectSchema.optional(),
277
532
  moves: z.array(udlMoveSchema).default([]),
278
533
  payout: udlPayoutSchema.optional(),
279
534
  port: udlPortSchema.optional(),
280
- publicIntent: udlPublicIntentSchema
535
+ quote: udlQuoteSchema.optional(),
536
+ publicAction: udlPublicActionSchema
281
537
  .optional()
282
- .describe("Author-approved public intent name; the containing verb key remains the lifecycle and execution identity"),
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(),
283
542
  requiresAggregate: z.array(udlAggregateConditionSchema).min(1).optional(),
543
+ requiresChecks: z.array(udlCheckRequirementSchema).optional(),
284
544
  requiresDrainedAccount: z.strictObject({ path: fieldPathSchema }).optional(),
285
545
  requiresExposure: z.array(udlExposureRequirementSchema).min(1).optional(),
286
546
  requiresRefs: z.array(udlGateSchema).min(1).optional(),
287
- requiresSettlement: udlRequiresSettlementSchema.optional(),
547
+ sandboxFailurePoint: z.enum(["funding", "release"]).optional(),
288
548
  setsAt: udlSetsAtSchema.optional(),
289
549
  signedSum: udlSignedSumSchema.optional(),
290
550
  steps: z.array(udlStepSchema),
291
551
  summary: nonEmptyTextSchema,
292
- });
552
+ updates: z.array(udlFieldNameSchema).min(1).optional(),
553
+ };
554
+ const udlActionSchema = z.strictObject(udlActionShape);
293
555
  const udlAggregateBaseShape = {
294
- childNounId: udlSnakeCaseSchema,
556
+ childInstrumentId: udlSnakeCaseSchema,
295
557
  childRefField: udlFieldNameSchema,
296
558
  childStatuses: z.array(udlSnakeCaseSchema).min(1),
297
559
  parentField: udlFieldNameSchema,
@@ -315,18 +577,25 @@ const udlAggregateSchema = z.union([
315
577
  .optional(),
316
578
  }),
317
579
  ]);
318
- const udlNounSchema = z.strictObject({
580
+ const udlInstrumentShape = {
581
+ actionOrder: z.array(udlActionNameSchema).min(1),
582
+ agentDescription: udlAgentDescriptionSchema.optional(),
319
583
  aggregateInvariants: z.array(udlAggregateSchema).min(1).optional(),
320
- computedMoneyRefs: z.array(udlFieldNameSchema).min(1).optional(),
584
+ callerParkedStates: z
585
+ .record(udlSnakeCaseSchema, nonEmptyTextSchema)
586
+ .optional(),
321
587
  description: nonEmptyTextSchema.optional(),
588
+ dials: z.array(udlDialSchema).optional(),
322
589
  distinctParties: z.literal(true).optional(),
323
590
  derivedAmounts: z.array(udlDerivedAmountSchema).min(1).max(4).optional(),
591
+ feeRules: z.array(udlFeeRuleSchema).min(1).max(4).optional(),
324
592
  fields: z.record(udlFieldNameSchema, jsonObjectSchema),
325
593
  id: udlSnakeCaseSchema,
326
594
  idPrefix: z
327
595
  .string()
328
596
  .regex(idPrefixPattern, "must contain 2 to 8 lowercase letters"),
329
597
  lifecycle: udlLifecycleSchema,
598
+ nav: z.array(nonEmptyTextSchema).min(1).optional(),
330
599
  parties: z
331
600
  .strictObject({
332
601
  beneficiary: udlFieldNameSchema.optional(),
@@ -336,15 +605,425 @@ const udlNounSchema = z.strictObject({
336
605
  .optional(),
337
606
  partitions: z.array(udlPartitionSchema).min(1).optional(),
338
607
  required: z.array(udlFieldNameSchema),
339
- subject: udlNounSubjectSchema.optional(),
608
+ subject: udlInstrumentSubjectSchema.optional(),
340
609
  summary: nonEmptyTextSchema,
610
+ surfaceVisibility: z.enum(["internal", "public", "system"]).optional(),
611
+ templateId: udlSnakeCaseSchema.optional(),
341
612
  title: nonEmptyTextSchema,
342
- unwind: udlUnwindSchema.optional(),
343
613
  update: udlUpdatePolicySchema.optional(),
344
- verbs: z.record(udlVerbNameSchema, udlVerbSchema),
345
- });
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
+ ];
346
1024
  const udlDocumentShapeSchema = z.strictObject({
347
- nouns: z.array(udlNounSchema).min(1),
1025
+ dials: z.array(udlCompositionDialSchema).min(1).optional(),
1026
+ instruments: z.array(udlInstrumentSchema).min(1),
348
1027
  product: udlSnakeCaseSchema,
349
1028
  subjects: z.array(udlSubjectSchema),
350
1029
  title: nonEmptyTextSchema,
@@ -355,7 +1034,7 @@ const moneyPatterns = new Set(["^[1-9][0-9]{0,17}$", "^(0|[1-9][0-9]{0,17})$"]);
355
1034
  function isMoneyField(schema) {
356
1035
  return (typeof schema?.pattern === "string" && moneyPatterns.has(schema.pattern));
357
1036
  }
358
- function referenceTargets(schema, nouns) {
1037
+ function referenceTargets(schema, instruments) {
359
1038
  if (schema.type !== "string" || typeof schema.pattern !== "string")
360
1039
  return [];
361
1040
  // Parse the scoped-ID pattern. Executing an author regex here would bypass
@@ -375,40 +1054,47 @@ function referenceTargets(schema, nouns) {
375
1054
  if (typeof schema.maxLength === "number" && probe.length > schema.maxLength) {
376
1055
  return [];
377
1056
  }
378
- return nouns.filter((noun) => noun.idPrefix === prefix);
1057
+ return instruments.filter((instrument) => instrument.idPrefix === prefix);
379
1058
  }
380
- function declaredMoneyRefKeys(noun) {
1059
+ function declaredMoneyRefKeys(instrument) {
381
1060
  return new Set([
382
- ...(noun.computedMoneyRefs ?? []),
383
- ...Object.values(noun.verbs).flatMap((verb) => [
384
- ...(verb.signedSum
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
385
1071
  ? [
386
- verb.signedSum.amountRef,
387
- ...verb.signedSum.sources.map((source) => source.subtotalRef),
1072
+ action.signedSum.amountRef,
1073
+ ...action.signedSum.sources.map((source) => source.subtotalRef),
388
1074
  ]
389
1075
  : []),
390
- ...(verb.distribute ? [verb.distribute.amountRef] : []),
1076
+ ...(action.distribute ? [action.distribute.amountRef] : []),
1077
+ ...(action.quote ? [action.quote.chargeRef, action.quote.netRef] : []),
391
1078
  ]),
392
- ...(noun.unwind ? ["unwindRefund", "unwindPenalty"] : []),
393
1079
  ]);
394
1080
  }
395
1081
  export const udlDocumentSchema = udlDocumentShapeSchema.superRefine((document, context) => {
396
- document.nouns.forEach((noun, nounIndex) => {
397
- noun.derivedAmounts?.forEach((amount, amountIndex) => {
1082
+ document.instruments.forEach((instrument, instrumentIndex) => {
1083
+ instrument.derivedAmounts?.forEach((amount, amountIndex) => {
398
1084
  const base = [
399
- "nouns",
400
- nounIndex,
1085
+ "instruments",
1086
+ instrumentIndex,
401
1087
  "derivedAmounts",
402
1088
  amountIndex,
403
1089
  ];
404
- if (!isMoneyField(noun.fields[amount.field])) {
1090
+ if (!isMoneyField(instrument.fields[amount.field])) {
405
1091
  context.addIssue({
406
1092
  code: "custom",
407
1093
  message: `derived amount target ${amount.field} must be a declared money field`,
408
1094
  path: [...base, "field"],
409
1095
  });
410
1096
  }
411
- if (!isMoneyField(noun.fields[amount.sourceField])) {
1097
+ if (!isMoneyField(instrument.fields[amount.sourceField])) {
412
1098
  context.addIssue({
413
1099
  code: "custom",
414
1100
  message: `derived amount source ${amount.sourceField} must be a declared money field`,
@@ -423,29 +1109,29 @@ export const udlDocumentSchema = udlDocumentShapeSchema.superRefine((document, c
423
1109
  });
424
1110
  }
425
1111
  });
426
- Object.entries(noun.verbs).forEach(([verbName, verb]) => {
427
- const distribute = verb.distribute;
1112
+ Object.entries(instrument.actions).forEach(([actionName, action]) => {
1113
+ const distribute = action.distribute;
428
1114
  if (!distribute)
429
1115
  return;
430
1116
  const base = [
431
- "nouns",
432
- nounIndex,
433
- "verbs",
434
- verbName,
1117
+ "instruments",
1118
+ instrumentIndex,
1119
+ "actions",
1120
+ actionName,
435
1121
  "distribute",
436
1122
  ];
437
- const refSchema = noun.fields[distribute.refField];
1123
+ const refSchema = instrument.fields[distribute.refField];
438
1124
  const parents = refSchema
439
- ? referenceTargets(refSchema, document.nouns)
1125
+ ? referenceTargets(refSchema, document.instruments)
440
1126
  : [];
441
1127
  if (parents.length !== 1) {
442
1128
  context.addIssue({
443
1129
  code: "custom",
444
- message: `distribute refField ${distribute.refField} must identify exactly one parent noun`,
1130
+ message: `distribute refField ${distribute.refField} must identify exactly one parent instrument`,
445
1131
  path: [...base, "refField"],
446
1132
  });
447
1133
  }
448
- if (!isMoneyField(noun.fields[distribute.weightField])) {
1134
+ if (!isMoneyField(instrument.fields[distribute.weightField])) {
449
1135
  context.addIssue({
450
1136
  code: "custom",
451
1137
  message: `distribute weightField ${distribute.weightField} must be a declared money field`,
@@ -453,11 +1139,11 @@ export const udlDocumentSchema = udlDocumentShapeSchema.superRefine((document, c
453
1139
  });
454
1140
  }
455
1141
  distribute.statuses.forEach((status, statusIndex) => {
456
- if (noun.lifecycle.states.includes(status))
1142
+ if (instrument.lifecycle.states.includes(status))
457
1143
  return;
458
1144
  context.addIssue({
459
1145
  code: "custom",
460
- message: `distribute status ${status} is not declared by ${noun.id}`,
1146
+ message: `distribute status ${status} is not declared by ${instrument.id}`,
461
1147
  path: [...base, "statuses", statusIndex],
462
1148
  });
463
1149
  });