@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/src/schema.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { udlCheckEvidenceProfiles } from "./check-profiles.js";
4
+ import type { UdlEffectKind } from "./effects.js";
5
+
3
6
  export const UDL_FORMAT_VERSION = 1 as const;
4
7
 
5
8
  const snakeCasePattern = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
@@ -11,20 +14,32 @@ const eventNamePattern = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/;
11
14
  const udlSnakeCaseSchema = z
12
15
  .string()
13
16
  .regex(snakeCasePattern, "must be a snake_case identifier");
14
- const udlVerbNameSchema = z
17
+ const udlActionNameSchema = z
15
18
  .string()
16
- .regex(snakeCasePattern, "must be a snake_case verb identifier");
19
+ .regex(snakeCasePattern, "must be a snake_case action identifier");
17
20
  const udlFieldNameSchema = z
18
21
  .string()
19
22
  .regex(camelCasePattern, "must be a camelCase field name");
20
23
 
21
- export const udlPublicIntentSchema = z
24
+ export const udlPublicActionSchema = z
25
+ .string()
26
+ .regex(camelCasePattern, "must be a camelCase public action name");
27
+
28
+ export const udlInstrumentActionIdSchema = z
22
29
  .string()
23
- .regex(camelCasePattern, "must be a camelCase public intent name");
30
+ .max(160)
31
+ .regex(
32
+ /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/,
33
+ "must name an instrument action as instrument_id.action_key",
34
+ );
24
35
 
25
36
  const nonEmptyTextSchema = z
26
37
  .string()
27
38
  .refine((value) => value.trim().length > 0, "must not be blank");
39
+ // Agent-facing presentation prose. Every MCP tool description an agent reads is
40
+ // this string, so it is capped: a 400-character ceiling keeps a 60-tool surface
41
+ // under the token budget a client spends before it can pick a tool.
42
+ const udlAgentDescriptionSchema = nonEmptyTextSchema.max(400);
28
43
  const fieldPathSchema = z
29
44
  .string()
30
45
  .regex(fieldPathPattern, "must be a dot-separated camelCase field path");
@@ -37,6 +52,26 @@ const instanceValuePathSchema = z
37
52
  const jsonObjectSchema = z.record(z.string(), z.json());
38
53
  const stringMapSchema = z.record(nonEmptyTextSchema, nonEmptyTextSchema);
39
54
 
55
+ const providerFamilyIds = [
56
+ "accounts_ledger_first_party",
57
+ "identity_verification",
58
+ "national_identity",
59
+ "credit_bureau",
60
+ "sanctions_screening",
61
+ "enforcement_instrument",
62
+ "credit_servicing",
63
+ "collections_agency",
64
+ "settlement_ingestion",
65
+ "payouts",
66
+ "banking_or_card_issuing",
67
+ "insurance_carrier",
68
+ "insurance_claims_adjuster",
69
+ "air_carrier",
70
+ "lodging_supplier",
71
+ "email_or_notification",
72
+ ] as const;
73
+ export const udlProviderFamilyIdSchema = z.enum(providerFamilyIds);
74
+
40
75
  const udlDeclaredValuePolicySchema = z.enum(["required", "optional", "none"]);
41
76
 
42
77
  const udlSubjectSchema = z.strictObject({
@@ -47,10 +82,17 @@ const udlSubjectSchema = z.strictObject({
47
82
  version: z.number().int().positive(),
48
83
  });
49
84
 
50
- const udlNounSubjectSchema = z.strictObject({
85
+ const udlInstrumentSubjectSchema = z.strictObject({
86
+ extensible: z.boolean().optional(),
51
87
  kinds: z.array(udlSnakeCaseSchema).min(1),
52
88
  });
53
89
 
90
+ const udlExampleSchema = z.strictObject({
91
+ input: jsonObjectSchema,
92
+ name: nonEmptyTextSchema,
93
+ output: z.json().optional(),
94
+ });
95
+
54
96
  const udlLifecycleTransitionSchema = z.strictObject({
55
97
  from: z.array(udlSnakeCaseSchema).min(1),
56
98
  to: udlSnakeCaseSchema,
@@ -59,16 +101,17 @@ const udlLifecycleTransitionSchema = z.strictObject({
59
101
  const udlLifecycleSchema = z.strictObject({
60
102
  initial: udlSnakeCaseSchema,
61
103
  states: z.array(udlSnakeCaseSchema).min(1),
62
- transitions: z.record(udlVerbNameSchema, udlLifecycleTransitionSchema),
104
+ transitions: z.record(udlActionNameSchema, udlLifecycleTransitionSchema),
63
105
  });
64
106
 
65
107
  const udlUpdatePolicySchema = z.strictObject({
108
+ examples: z.array(udlExampleSchema).optional(),
66
109
  fields: z.array(udlFieldNameSchema).min(1),
67
110
  states: z.array(udlSnakeCaseSchema).min(1),
68
111
  });
69
112
 
70
113
  const udlGateSchema = z.strictObject({
71
- /** Local field key <- referenced instance path; create verbs only. */
114
+ /** Local field key <- referenced instance path; create actions only. */
72
115
  bind: z.record(udlFieldNameSchema, fieldPathSchema).optional(),
73
116
  field: udlFieldNameSchema,
74
117
  /** Local instance path === referenced instance path at admission. */
@@ -76,7 +119,7 @@ const udlGateSchema = z.strictObject({
76
119
  /** Opt-in over an optional reference field: absent passes, set is held to the clause. */
77
120
  optional: z.literal(true).optional(),
78
121
  statuses: z.array(udlSnakeCaseSchema).min(1),
79
- /** One dependent ever per referenced instance; create verbs only. */
122
+ /** One dependent ever per referenced instance; create actions only. */
80
123
  unique: z.literal(true).optional(),
81
124
  });
82
125
 
@@ -119,18 +162,23 @@ const udlMoveSchema = z.strictObject({
119
162
 
120
163
  const udlDueRecurrenceSchema = z.strictObject({
121
164
  countField: udlFieldNameSchema.optional(),
165
+ delinquency: z.literal("parent_policy").optional(),
166
+ drainAction: udlActionNameSchema.optional(),
122
167
  period: z.union([
123
168
  nonEmptyTextSchema,
124
169
  z.strictObject({
125
170
  calendar: z.enum(["gregorian", "hijri"]),
171
+ monthEnd: z.literal("clamp_to_last_day"),
126
172
  months: z.number().int().positive(),
127
173
  }),
128
174
  ]),
175
+ liability: z.literal("one_open").optional(),
176
+ untilAction: udlActionNameSchema.optional(),
129
177
  untilField: udlFieldNameSchema.optional(),
130
178
  });
131
179
 
132
180
  const udlDueParentStatusSchema = z.strictObject({
133
- nounId: udlSnakeCaseSchema,
181
+ instrumentId: udlSnakeCaseSchema,
134
182
  refField: udlFieldNameSchema,
135
183
  statuses: z.array(udlSnakeCaseSchema).min(1),
136
184
  });
@@ -153,23 +201,72 @@ const udlSetsAtSchema = z.strictObject({
153
201
  offset: nonEmptyTextSchema,
154
202
  });
155
203
 
156
- const udlUnwindPenaltySchema = z.strictObject({
204
+ const udlQuoteChargeSchema = z.strictObject({
157
205
  bps: z.number().int().min(0).max(9_999),
158
206
  withinOffset: nonEmptyTextSchema.optional(),
159
207
  });
160
208
 
161
- const udlUnwindSchema = z.strictObject({
162
- beforeField: udlFieldNameSchema.optional(),
163
- confirm: udlVerbNameSchema,
164
- penalty: z.array(udlUnwindPenaltySchema).min(1),
165
- quote: udlVerbNameSchema,
166
- refundableField: udlFieldNameSchema,
167
- refundDestinationField: udlFieldNameSchema,
168
- });
209
+ // A priced, expiring offer. The quoting action splits `baseField` into a charge
210
+ // and a net at its own clock, freezes the fields the price was read from, and
211
+ // stamps an expiry. Only the paired `commit` action may spend the offer, and
212
+ // only while it is unexpired and its frozen fields still hold.
213
+ const udlQuoteShape = {
214
+ /** Date-time whose remaining time picks the charge tier; omit for elapsed age. */
215
+ anchorField: udlFieldNameSchema.optional(),
216
+ baseField: udlFieldNameSchema,
217
+ chargeRef: udlFieldNameSchema,
218
+ charges: z.array(udlQuoteChargeSchema).min(1),
219
+ /** Fixed ISO-8601 duration from the quoting action, or a stored deadline. */
220
+ expires: z.union([
221
+ z.strictObject({ field: udlFieldNameSchema }),
222
+ z.strictObject({ offset: nonEmptyTextSchema }),
223
+ ]),
224
+ fixes: z.array(udlFieldNameSchema).min(1),
225
+ netDestinationField: udlFieldNameSchema,
226
+ netRef: udlFieldNameSchema,
227
+ };
228
+ const udlQuoteSchema = z.strictObject(udlQuoteShape);
229
+
230
+ /**
231
+ * Where a quoting action stamps the instant its offer stops being committable.
232
+ * Derived from the author's net ref rather than a fixed name, so two offers on
233
+ * one instrument never share the slot.
234
+ */
235
+ export function quoteExpiresAtRefKey(quote: {
236
+ readonly netRef: string;
237
+ }): string {
238
+ return `${quote.netRef}ExpiresAt`;
239
+ }
240
+
241
+ /**
242
+ * Where a quoting action stamps the fingerprint of the field values it froze.
243
+ * The committing action recomputes it against the live fields, which is how an
244
+ * offer priced on one set of terms refuses to pay out against another.
245
+ */
246
+ export function quoteFrozenRefKey(quote: { readonly netRef: string }): string {
247
+ return `${quote.netRef}Frozen`;
248
+ }
249
+
250
+ /**
251
+ * Every ref key one quote seeds: the charge and the net the author named, plus
252
+ * the two the machinery derives to hold the offer itself. One derivation, read
253
+ * by the validator, the compiler, and the runtime alike.
254
+ */
255
+ export function quoteSeededRefKeys(quote: {
256
+ readonly chargeRef: string;
257
+ readonly netRef: string;
258
+ }): readonly string[] {
259
+ return [
260
+ quote.chargeRef,
261
+ quote.netRef,
262
+ quoteExpiresAtRefKey(quote),
263
+ quoteFrozenRefKey(quote),
264
+ ];
265
+ }
169
266
 
170
267
  const udlDecisionSchema = z.strictObject({
171
- capability: udlSnakeCaseSchema,
172
- deadlineMs: z.number().int().positive(),
268
+ capability: udlProviderFamilyIdSchema,
269
+ deadlineMs: z.number().int().positive().max(15_000),
173
270
  // A decision that never came back is not an approval. `approve` would let a
174
271
  // document release money because a counterparty went quiet, so the grammar
175
272
  // admits only the fail-closed answer. Every published document already
@@ -178,12 +275,6 @@ const udlDecisionSchema = z.strictObject({
178
275
  onTimeout: z.literal("decline"),
179
276
  });
180
277
 
181
- const udlExampleSchema = z.strictObject({
182
- input: jsonObjectSchema,
183
- name: nonEmptyTextSchema,
184
- output: z.json().optional(),
185
- });
186
-
187
278
  const udlAggregateCheckSchema = z.discriminatedUnion("kind", [
188
279
  z.strictObject({
189
280
  amountField: udlFieldNameSchema,
@@ -220,7 +311,7 @@ const udlAggregateCheckSchema = z.discriminatedUnion("kind", [
220
311
 
221
312
  const udlAggregateConditionSchema = z.strictObject({
222
313
  check: udlAggregateCheckSchema,
223
- nounId: udlSnakeCaseSchema.optional(),
314
+ instrumentId: udlSnakeCaseSchema.optional(),
224
315
  over: z.enum(["children", "siblings"]),
225
316
  refField: udlFieldNameSchema,
226
317
  statuses: z.array(udlSnakeCaseSchema).min(1),
@@ -231,12 +322,12 @@ const udlExposureRequirementSchema = z.strictObject({
231
322
  anchorField: udlFieldNameSchema,
232
323
  capField: udlFieldNameSchema,
233
324
  capOnAnchor: z.literal(true).optional(),
234
- childNounId: udlSnakeCaseSchema,
325
+ childInstrumentId: udlSnakeCaseSchema,
235
326
  statuses: z.array(udlSnakeCaseSchema).min(1),
236
327
  });
237
328
 
238
- // The tenant-backend decision port: the verb's caller asserts the acting
239
- // party, checked at admission against the noun's party bindings for the
329
+ // The tenant-backend decision port: the action's caller asserts the acting
330
+ // party, checked at admission against the instrument's party bindings for the
240
331
  // allowed roles.
241
332
  const udlPortSchema = z.strictObject({
242
333
  allowedParties: z
@@ -254,14 +345,61 @@ const udlPayoutSchema = z.strictObject({
254
345
  speed: z.literal("standard"),
255
346
  });
256
347
 
257
- const udlRequiresSettlementSchema = z.strictObject({
348
+ /**
349
+ * One expectation an action asserts about money it cannot see yet: how much,
350
+ * in which currency, which way, and against whose provider-side row. Evidence
351
+ * that lands later either matches under the declared law or the expectation
352
+ * breaks: past the window the kernel raises the declared exception child, binds
353
+ * the unmatched amount and the break reason onto it, and spends the due
354
+ * occurrence. There is no third outcome and no silent wait.
355
+ */
356
+ const udlReconcileSchema = z.strictObject({
357
+ /** `fields.<name>` or `refs.<name>`: the money this action says will land. */
358
+ amount: instanceValuePathSchema,
359
+ /** Where the matched evidence id lands in refs. */
258
360
  capture: udlFieldNameSchema,
259
- payoutRef: udlFieldNameSchema,
361
+ /** The ref naming the provider-side row the evidence must cite. */
362
+ counterpartyRef: udlFieldNameSchema,
363
+ currencyField: udlFieldNameSchema,
364
+ direction: z.enum(["credit", "debit"]),
365
+ /** Exactly one source. A reconcile never reads two ledgers at once. */
366
+ evidence: z.enum(["movement", "provider_confirmation", "statement_line"]),
367
+ /**
368
+ * Where an expectation that never matched lands. The named child instrument
369
+ * carries the break, and `maxOpen` caps how many of them one instance may
370
+ * hold open at once, the same count measure the aggregate invariants use.
371
+ */
372
+ exception: z.strictObject({
373
+ amountField: udlFieldNameSchema,
374
+ childInstrumentId: udlSnakeCaseSchema,
375
+ maxOpen: z.number().int().min(1).max(1_000),
376
+ reasonField: udlFieldNameSchema,
377
+ refField: udlFieldNameSchema,
378
+ }),
379
+ /**
380
+ * How a candidate becomes the match. `exact` demands equal minor units,
381
+ * `tolerance` allows a bounded difference, `window` drops the amount test
382
+ * for a source that nets several items into one line.
383
+ */
384
+ match: z.discriminatedUnion("law", [
385
+ z.strictObject({ law: z.literal("exact") }),
386
+ z.strictObject({
387
+ dial: udlSnakeCaseSchema,
388
+ law: z.literal("tolerance"),
389
+ minorUnits: z.number().int().positive(),
390
+ }),
391
+ z.strictObject({ law: z.literal("window") }),
392
+ ]),
393
+ /** Fixed ISO-8601 duration from the expecting action, or a stored deadline. */
394
+ within: z.union([
395
+ z.strictObject({ field: udlFieldNameSchema }),
396
+ z.strictObject({ offset: nonEmptyTextSchema }),
397
+ ]),
260
398
  });
261
399
 
262
400
  const udlSignedSumSourceSchema = z.strictObject({
263
401
  amountField: udlFieldNameSchema,
264
- nounId: udlSnakeCaseSchema,
402
+ instrumentId: udlSnakeCaseSchema,
265
403
  refField: udlFieldNameSchema,
266
404
  sign: z.enum(["add", "subtract"]),
267
405
  statuses: z.array(udlSnakeCaseSchema).min(1),
@@ -287,9 +425,98 @@ const udlDistributeSchema = z.strictObject({
287
425
  weightField: udlFieldNameSchema,
288
426
  });
289
427
 
428
+ const udlRemainderCollectedSchema = z.strictObject({
429
+ amountField: udlFieldNameSchema,
430
+ instrumentId: udlSnakeCaseSchema,
431
+ path: z.literal("refs").optional(),
432
+ refField: udlFieldNameSchema,
433
+ statuses: z.array(udlSnakeCaseSchema).min(1),
434
+ });
435
+
436
+ const udlRemainderSchema = z.strictObject({
437
+ accumulateRef: udlFieldNameSchema.optional(),
438
+ amountRef: udlFieldNameSchema,
439
+ collected: z.array(udlRemainderCollectedSchema).optional(),
440
+ inputKey: udlFieldNameSchema.optional(),
441
+ onZero: z.enum(["refuse", "skip_steps"]),
442
+ totalPath: instanceValuePathSchema,
443
+ });
444
+
445
+ type UdlCheckRequirementShape = {
446
+ checkKind: string;
447
+ family: z.infer<typeof udlProviderFamilyIdSchema>;
448
+ maxAge?: string;
449
+ statuses: string[];
450
+ subjectField: string;
451
+ };
452
+
453
+ const udlCheckRequirementVariants = udlCheckEvidenceProfiles.map((profile) =>
454
+ z.strictObject({
455
+ checkKind: z.literal(profile.checkKind),
456
+ family: z.literal(profile.family),
457
+ maxAge: nonEmptyTextSchema.optional(),
458
+ statuses: z.array(z.enum(profile.statuses)).min(1),
459
+ subjectField: udlFieldNameSchema,
460
+ }),
461
+ ) as unknown as readonly [
462
+ z.ZodType<UdlCheckRequirementShape>,
463
+ z.ZodType<UdlCheckRequirementShape>,
464
+ ...z.ZodType<UdlCheckRequirementShape>[],
465
+ ];
466
+
467
+ const udlCheckRequirementSchema = z.union(udlCheckRequirementVariants);
468
+
469
+ const udlDialSchema = z.discriminatedUnion("kind", [
470
+ z.strictObject({
471
+ field: udlFieldNameSchema,
472
+ key: udlSnakeCaseSchema,
473
+ kind: z.literal("window"),
474
+ maxOffset: nonEmptyTextSchema.optional(),
475
+ minOffset: nonEmptyTextSchema.optional(),
476
+ summary: nonEmptyTextSchema,
477
+ title: nonEmptyTextSchema,
478
+ }),
479
+ z.strictObject({
480
+ action: udlActionNameSchema,
481
+ key: udlSnakeCaseSchema,
482
+ kind: z.literal("decision_deadline_ms"),
483
+ maxMs: z.number().int().positive().max(15_000).optional(),
484
+ minMs: z.number().int().positive().max(15_000).optional(),
485
+ summary: nonEmptyTextSchema,
486
+ title: nonEmptyTextSchema,
487
+ }),
488
+ z.strictObject({
489
+ key: udlSnakeCaseSchema,
490
+ kind: z.literal("reconcile_tolerance"),
491
+ /** Ceiling on the minor units any reconcile may forgive under this dial. */
492
+ maxMinorUnits: z.number().int().positive().max(1_000_000),
493
+ summary: nonEmptyTextSchema,
494
+ title: nonEmptyTextSchema,
495
+ }),
496
+ z.strictObject({
497
+ key: udlSnakeCaseSchema,
498
+ kind: z.literal("unwind_penalty"),
499
+ summary: nonEmptyTextSchema,
500
+ title: nonEmptyTextSchema,
501
+ }),
502
+ ]);
503
+
290
504
  // A same-instance conservation law: the piece fields (money, minor units)
291
505
  // must sum exactly to the total field; admission refuses a create whose
292
506
  // pieces diverge.
507
+ // A composition-level dial. Instrument dials tune one instrument's frozen slots;
508
+ // this one is a property of the whole product, so it hangs off the document.
509
+ // `confirmation_threshold` carries minor units of the kernel currency: a
510
+ // money-moving tool call whose declared money inputs sum below the configured
511
+ // value skips the explicit confirmation argument.
512
+ const udlCompositionDialSchema = z.strictObject({
513
+ key: udlSnakeCaseSchema,
514
+ kind: z.literal("confirmation_threshold"),
515
+ maxMinor: z.number().int().positive().max(1_000_000_000_000).optional(),
516
+ summary: nonEmptyTextSchema,
517
+ title: nonEmptyTextSchema,
518
+ });
519
+
293
520
  const udlPartitionSchema = z.strictObject({
294
521
  pieceFields: z.array(udlFieldNameSchema).min(2),
295
522
  totalField: udlFieldNameSchema,
@@ -305,38 +532,117 @@ const udlDerivedAmountSchema = z.strictObject({
305
532
  sourceField: udlFieldNameSchema,
306
533
  });
307
534
 
308
- const udlVerbSchema = z.strictObject({
535
+ const udlFeeLeafRuleSchema = z.discriminatedUnion("kind", [
536
+ z.strictObject({
537
+ bps: z.number().int().min(1).max(9_999),
538
+ kind: z.literal("bps"),
539
+ }),
540
+ z.strictObject({
541
+ currencyField: udlFieldNameSchema,
542
+ field: udlFieldNameSchema,
543
+ kind: z.literal("exact"),
544
+ }),
545
+ ]);
546
+
547
+ const nonNegativeDecimalIntegerSchema = z
548
+ .string()
549
+ .regex(/^(0|[1-9][0-9]*)$/, "must be a nonnegative decimal integer");
550
+
551
+ const udlFeeRuleSchema = z.strictObject({
552
+ amountField: udlFieldNameSchema,
553
+ baseField: udlFieldNameSchema,
554
+ bearerField: udlFieldNameSchema,
555
+ position: z.enum(["carved", "on_top"]),
556
+ rule: z.discriminatedUnion("kind", [
557
+ ...udlFeeLeafRuleSchema.options,
558
+ z.strictObject({
559
+ kind: z.literal("tiered"),
560
+ tiers: z
561
+ .array(
562
+ z.strictObject({
563
+ fromInclusive: nonNegativeDecimalIntegerSchema,
564
+ rule: udlFeeLeafRuleSchema,
565
+ toExclusive: nonNegativeDecimalIntegerSchema.optional(),
566
+ }),
567
+ )
568
+ .min(1)
569
+ .max(16),
570
+ }),
571
+ ]),
572
+ });
573
+
574
+ const udlDecidedAmountSchema = z.strictObject({
575
+ boundField: udlFieldNameSchema,
576
+ field: udlFieldNameSchema,
577
+ remainderAction: udlActionNameSchema,
578
+ });
579
+
580
+ const udlEffectRowSchema = z.strictObject({
581
+ signature: nonEmptyTextSchema,
582
+ source: nonEmptyTextSchema,
583
+ });
584
+
585
+ const udlNotifyEffectSchema = z.strictObject({
586
+ channel: udlSnakeCaseSchema,
587
+ role: udlSnakeCaseSchema,
588
+ signature: nonEmptyTextSchema,
589
+ source: nonEmptyTextSchema,
590
+ });
591
+
592
+ const udlEffectsShape = {
593
+ decides: z.array(udlEffectRowSchema).min(1).optional(),
594
+ holds: z.array(udlEffectRowSchema).min(1).optional(),
595
+ moves: z.array(udlEffectRowSchema).min(1).optional(),
596
+ notifies: z.array(udlNotifyEffectSchema).min(1).optional(),
597
+ reads: z.array(udlEffectRowSchema).min(1).optional(),
598
+ schedules: z.array(udlEffectRowSchema).min(1).optional(),
599
+ } satisfies Record<UdlEffectKind, z.ZodType>;
600
+ const udlEffectsSchema = z.strictObject(udlEffectsShape);
601
+
602
+ const udlActionShape = {
603
+ agentDescription: udlAgentDescriptionSchema.optional(),
309
604
  captureInput: z.record(udlFieldNameSchema, udlFieldNameSchema).optional(),
605
+ /** Names the quoting action whose offer this action spends. */
606
+ commit: udlActionNameSchema.optional(),
607
+ decidedAmount: udlDecidedAmountSchema.optional(),
310
608
  deadline: udlDeadlineSchema.optional(),
311
609
  decision: udlDecisionSchema.optional(),
312
610
  distribute: udlDistributeSchema.optional(),
313
611
  description: nonEmptyTextSchema.optional(),
314
612
  due: udlDueSchema.optional(),
315
613
  earnable: z.boolean().optional(),
614
+ effects: udlEffectsSchema.optional(),
316
615
  eventName: z.string().regex(eventNamePattern).optional(),
317
616
  examples: z.array(udlExampleSchema).min(1).optional(),
318
617
  input: jsonObjectSchema.optional(),
319
618
  moves: z.array(udlMoveSchema).default([]),
320
619
  payout: udlPayoutSchema.optional(),
321
620
  port: udlPortSchema.optional(),
322
- publicIntent: udlPublicIntentSchema
621
+ quote: udlQuoteSchema.optional(),
622
+ publicAction: udlPublicActionSchema
323
623
  .optional()
324
624
  .describe(
325
- "Author-approved public intent name; the containing verb key remains the lifecycle and execution identity",
625
+ "Author-approved public action name; the containing action key remains the lifecycle and execution identity",
326
626
  ),
627
+ principal: z.literal("user_session").optional(),
628
+ reconcile: z.array(udlReconcileSchema).min(1).optional(),
629
+ remainder: udlRemainderSchema.optional(),
327
630
  requiresAggregate: z.array(udlAggregateConditionSchema).min(1).optional(),
631
+ requiresChecks: z.array(udlCheckRequirementSchema).optional(),
328
632
  requiresDrainedAccount: z.strictObject({ path: fieldPathSchema }).optional(),
329
633
  requiresExposure: z.array(udlExposureRequirementSchema).min(1).optional(),
330
634
  requiresRefs: z.array(udlGateSchema).min(1).optional(),
331
- requiresSettlement: udlRequiresSettlementSchema.optional(),
635
+ sandboxFailurePoint: z.enum(["funding", "release"]).optional(),
332
636
  setsAt: udlSetsAtSchema.optional(),
333
637
  signedSum: udlSignedSumSchema.optional(),
334
638
  steps: z.array(udlStepSchema),
335
639
  summary: nonEmptyTextSchema,
336
- });
640
+ updates: z.array(udlFieldNameSchema).min(1).optional(),
641
+ };
642
+ const udlActionSchema = z.strictObject(udlActionShape);
337
643
 
338
644
  const udlAggregateBaseShape = {
339
- childNounId: udlSnakeCaseSchema,
645
+ childInstrumentId: udlSnakeCaseSchema,
340
646
  childRefField: udlFieldNameSchema,
341
647
  childStatuses: z.array(udlSnakeCaseSchema).min(1),
342
648
  parentField: udlFieldNameSchema,
@@ -362,18 +668,25 @@ const udlAggregateSchema = z.union([
362
668
  }),
363
669
  ]);
364
670
 
365
- const udlNounSchema = z.strictObject({
671
+ const udlInstrumentShape = {
672
+ actionOrder: z.array(udlActionNameSchema).min(1),
673
+ agentDescription: udlAgentDescriptionSchema.optional(),
366
674
  aggregateInvariants: z.array(udlAggregateSchema).min(1).optional(),
367
- computedMoneyRefs: z.array(udlFieldNameSchema).min(1).optional(),
675
+ callerParkedStates: z
676
+ .record(udlSnakeCaseSchema, nonEmptyTextSchema)
677
+ .optional(),
368
678
  description: nonEmptyTextSchema.optional(),
679
+ dials: z.array(udlDialSchema).optional(),
369
680
  distinctParties: z.literal(true).optional(),
370
681
  derivedAmounts: z.array(udlDerivedAmountSchema).min(1).max(4).optional(),
682
+ feeRules: z.array(udlFeeRuleSchema).min(1).max(4).optional(),
371
683
  fields: z.record(udlFieldNameSchema, jsonObjectSchema),
372
684
  id: udlSnakeCaseSchema,
373
685
  idPrefix: z
374
686
  .string()
375
687
  .regex(idPrefixPattern, "must contain 2 to 8 lowercase letters"),
376
688
  lifecycle: udlLifecycleSchema,
689
+ nav: z.array(nonEmptyTextSchema).min(1).optional(),
377
690
  parties: z
378
691
  .strictObject({
379
692
  beneficiary: udlFieldNameSchema.optional(),
@@ -383,16 +696,462 @@ const udlNounSchema = z.strictObject({
383
696
  .optional(),
384
697
  partitions: z.array(udlPartitionSchema).min(1).optional(),
385
698
  required: z.array(udlFieldNameSchema),
386
- subject: udlNounSubjectSchema.optional(),
699
+ subject: udlInstrumentSubjectSchema.optional(),
387
700
  summary: nonEmptyTextSchema,
701
+ surfaceVisibility: z.enum(["internal", "public", "system"]).optional(),
702
+ templateId: udlSnakeCaseSchema.optional(),
388
703
  title: nonEmptyTextSchema,
389
- unwind: udlUnwindSchema.optional(),
390
704
  update: udlUpdatePolicySchema.optional(),
391
- verbs: z.record(udlVerbNameSchema, udlVerbSchema),
392
- });
705
+ actions: z.record(udlActionNameSchema, udlActionSchema),
706
+ };
707
+ const udlInstrumentSchema = z.strictObject(udlInstrumentShape);
708
+
709
+ type UdlActionClauseTarget = keyof typeof udlActionShape;
710
+ type UdlInstrumentClauseTarget = keyof typeof udlInstrumentShape;
711
+ type UdlActionNestedClauseTarget = `effects.${keyof typeof udlEffectsShape}`;
712
+
713
+ export type UdlClauseCardinality = "one" | "many";
714
+
715
+ export interface UdlClauseEffect {
716
+ readonly kind: UdlEffectKind;
717
+ readonly per: "clause" | "element";
718
+ readonly signature:
719
+ | { readonly fixed: string }
720
+ | { readonly fromField: string }
721
+ | { readonly movementClass: true };
722
+ }
723
+
724
+ interface UdlClauseVocabularyMetadata {
725
+ readonly cardinality?: UdlClauseCardinality;
726
+ readonly effects?: readonly UdlClauseEffect[];
727
+ readonly linearOutputs?: readonly string[];
728
+ readonly linearSink?: true;
729
+ readonly spelling: string;
730
+ }
731
+
732
+ export type UdlClauseVocabularyEntry = UdlClauseVocabularyMetadata &
733
+ (
734
+ | {
735
+ readonly scope: "action";
736
+ readonly target: UdlActionClauseTarget | UdlActionNestedClauseTarget;
737
+ }
738
+ | {
739
+ readonly scope: "instrument";
740
+ readonly target: UdlInstrumentClauseTarget;
741
+ }
742
+ );
743
+
744
+ export const udlClauseVocabulary = [
745
+ {
746
+ cardinality: "one",
747
+ scope: "action",
748
+ spelling: "agent description",
749
+ target: "agentDescription",
750
+ },
751
+ {
752
+ cardinality: "one",
753
+ scope: "action",
754
+ spelling: "capture input",
755
+ target: "captureInput",
756
+ },
757
+ {
758
+ cardinality: "one",
759
+ // The committing action consumes the quote through its priced moves.
760
+ // An extra row here would bill the same movement twice.
761
+ effects: [],
762
+ scope: "action",
763
+ spelling: "commit",
764
+ target: "commit",
765
+ },
766
+ {
767
+ cardinality: "one",
768
+ effects: [
769
+ {
770
+ kind: "holds",
771
+ per: "clause",
772
+ signature: { fixed: "quote" },
773
+ },
774
+ {
775
+ kind: "schedules",
776
+ per: "clause",
777
+ signature: { fixed: "expiry" },
778
+ },
779
+ ],
780
+ linearOutputs: ["netRef"],
781
+ scope: "action",
782
+ spelling: "quote",
783
+ target: "quote",
784
+ },
785
+ {
786
+ cardinality: "one",
787
+ linearOutputs: ["field"],
788
+ scope: "action",
789
+ spelling: "decided amount",
790
+ target: "decidedAmount",
791
+ },
792
+ {
793
+ cardinality: "one",
794
+ effects: [
795
+ {
796
+ kind: "schedules",
797
+ per: "clause",
798
+ signature: { fixed: "deadline" },
799
+ },
800
+ ],
801
+ scope: "action",
802
+ spelling: "deadline",
803
+ target: "deadline",
804
+ },
805
+ {
806
+ cardinality: "one",
807
+ effects: [
808
+ {
809
+ kind: "decides",
810
+ per: "clause",
811
+ signature: { fromField: "capability" },
812
+ },
813
+ ],
814
+ scope: "action",
815
+ spelling: "decision",
816
+ target: "decision",
817
+ },
818
+ {
819
+ cardinality: "one",
820
+ linearOutputs: ["amountRef"],
821
+ scope: "action",
822
+ spelling: "computes distribute",
823
+ target: "distribute",
824
+ },
825
+ {
826
+ cardinality: "one",
827
+ scope: "action",
828
+ spelling: "description",
829
+ target: "description",
830
+ },
831
+ {
832
+ cardinality: "one",
833
+ effects: [
834
+ {
835
+ kind: "schedules",
836
+ per: "clause",
837
+ signature: { fixed: "due" },
838
+ },
839
+ ],
840
+ scope: "action",
841
+ spelling: "due",
842
+ target: "due",
843
+ },
844
+ {
845
+ cardinality: "one",
846
+ scope: "action",
847
+ spelling: "earnable",
848
+ target: "earnable",
849
+ },
850
+ {
851
+ cardinality: "one",
852
+ scope: "action",
853
+ spelling: "event name",
854
+ target: "eventName",
855
+ },
856
+ {
857
+ cardinality: "many",
858
+ scope: "action",
859
+ spelling: "examples",
860
+ target: "examples",
861
+ },
862
+ {
863
+ cardinality: "one",
864
+ scope: "action",
865
+ spelling: "input",
866
+ target: "input",
867
+ },
868
+ {
869
+ cardinality: "many",
870
+ effects: [
871
+ {
872
+ kind: "moves",
873
+ per: "element",
874
+ signature: { movementClass: true },
875
+ },
876
+ {
877
+ kind: "holds",
878
+ per: "element",
879
+ signature: { fixed: "reserve" },
880
+ },
881
+ ],
882
+ linearSink: true,
883
+ scope: "action",
884
+ spelling: "moves",
885
+ target: "moves",
886
+ },
887
+ {
888
+ cardinality: "one",
889
+ effects: [
890
+ {
891
+ kind: "moves",
892
+ per: "clause",
893
+ signature: { fixed: "payout.external" },
894
+ },
895
+ ],
896
+ linearSink: true,
897
+ scope: "action",
898
+ spelling: "payout",
899
+ target: "payout",
900
+ },
901
+ {
902
+ cardinality: "one",
903
+ effects: [
904
+ {
905
+ kind: "decides",
906
+ per: "clause",
907
+ signature: { fixed: "tenant_port" },
908
+ },
909
+ ],
910
+ scope: "action",
911
+ spelling: "port",
912
+ target: "port",
913
+ },
914
+ {
915
+ cardinality: "one",
916
+ scope: "action",
917
+ spelling: "principal",
918
+ target: "principal",
919
+ },
920
+ {
921
+ cardinality: "one",
922
+ scope: "action",
923
+ spelling: "public action",
924
+ target: "publicAction",
925
+ },
926
+ {
927
+ cardinality: "many",
928
+ effects: [
929
+ {
930
+ kind: "reads",
931
+ per: "element",
932
+ signature: { fixed: "reconcile" },
933
+ },
934
+ ],
935
+ scope: "action",
936
+ spelling: "reconcile",
937
+ target: "reconcile",
938
+ },
939
+ {
940
+ cardinality: "one",
941
+ linearOutputs: ["amountRef"],
942
+ scope: "action",
943
+ spelling: "computes remainder",
944
+ target: "remainder",
945
+ },
946
+ {
947
+ cardinality: "many",
948
+ effects: [
949
+ {
950
+ kind: "reads",
951
+ per: "clause",
952
+ signature: { fixed: "requires_aggregate" },
953
+ },
954
+ ],
955
+ scope: "action",
956
+ spelling: "requires aggregate",
957
+ target: "requiresAggregate",
958
+ },
959
+ {
960
+ cardinality: "many",
961
+ effects: [
962
+ {
963
+ kind: "reads",
964
+ per: "clause",
965
+ signature: { fixed: "requires_checks" },
966
+ },
967
+ ],
968
+ scope: "action",
969
+ spelling: "requires checks",
970
+ target: "requiresChecks",
971
+ },
972
+ {
973
+ cardinality: "one",
974
+ scope: "action",
975
+ spelling: "requires drained",
976
+ target: "requiresDrainedAccount",
977
+ },
978
+ {
979
+ cardinality: "many",
980
+ scope: "action",
981
+ spelling: "requires exposure",
982
+ target: "requiresExposure",
983
+ },
984
+ {
985
+ cardinality: "many",
986
+ effects: [
987
+ {
988
+ kind: "reads",
989
+ per: "clause",
990
+ signature: { fixed: "requires_refs" },
991
+ },
992
+ ],
993
+ scope: "action",
994
+ spelling: "requires refs",
995
+ target: "requiresRefs",
996
+ },
997
+ {
998
+ cardinality: "one",
999
+ scope: "action",
1000
+ spelling: "sandbox failure point",
1001
+ target: "sandboxFailurePoint",
1002
+ },
1003
+ {
1004
+ cardinality: "one",
1005
+ scope: "action",
1006
+ spelling: "sets at",
1007
+ target: "setsAt",
1008
+ },
1009
+ {
1010
+ cardinality: "one",
1011
+ linearOutputs: ["amountRef"],
1012
+ scope: "action",
1013
+ spelling: "computes signed_sum",
1014
+ target: "signedSum",
1015
+ },
1016
+ {
1017
+ cardinality: "many",
1018
+ linearSink: true,
1019
+ scope: "action",
1020
+ spelling: "steps",
1021
+ target: "steps",
1022
+ },
1023
+ {
1024
+ cardinality: "one",
1025
+ scope: "action",
1026
+ spelling: "summary",
1027
+ target: "summary",
1028
+ },
1029
+ {
1030
+ cardinality: "many",
1031
+ scope: "action",
1032
+ spelling: "updates",
1033
+ target: "updates",
1034
+ },
1035
+ {
1036
+ cardinality: "many",
1037
+ effects: [
1038
+ {
1039
+ kind: "notifies",
1040
+ per: "element",
1041
+ signature: { fromField: "channel" },
1042
+ },
1043
+ ],
1044
+ scope: "action",
1045
+ spelling: "notify",
1046
+ target: "effects.notifies",
1047
+ },
1048
+ {
1049
+ cardinality: "one",
1050
+ scope: "instrument",
1051
+ spelling: "agent description",
1052
+ target: "agentDescription",
1053
+ },
1054
+ {
1055
+ cardinality: "many",
1056
+ scope: "instrument",
1057
+ spelling: "aggregate invariants",
1058
+ target: "aggregateInvariants",
1059
+ },
1060
+ {
1061
+ cardinality: "one",
1062
+ scope: "instrument",
1063
+ spelling: "caller parked states",
1064
+ target: "callerParkedStates",
1065
+ },
1066
+ {
1067
+ cardinality: "one",
1068
+ scope: "instrument",
1069
+ spelling: "description",
1070
+ target: "description",
1071
+ },
1072
+ {
1073
+ cardinality: "many",
1074
+ scope: "instrument",
1075
+ spelling: "dials",
1076
+ target: "dials",
1077
+ },
1078
+ {
1079
+ cardinality: "one",
1080
+ scope: "instrument",
1081
+ spelling: "distinct parties",
1082
+ target: "distinctParties",
1083
+ },
1084
+ {
1085
+ cardinality: "many",
1086
+ scope: "instrument",
1087
+ spelling: "computes derived",
1088
+ target: "derivedAmounts",
1089
+ },
1090
+ {
1091
+ cardinality: "many",
1092
+ scope: "instrument",
1093
+ spelling: "computes fees",
1094
+ target: "feeRules",
1095
+ },
1096
+ {
1097
+ cardinality: "one",
1098
+ scope: "instrument",
1099
+ spelling: "id prefix",
1100
+ target: "idPrefix",
1101
+ },
1102
+ {
1103
+ cardinality: "many",
1104
+ scope: "instrument",
1105
+ spelling: "nav",
1106
+ target: "nav",
1107
+ },
1108
+ {
1109
+ cardinality: "many",
1110
+ scope: "instrument",
1111
+ spelling: "partitions",
1112
+ target: "partitions",
1113
+ },
1114
+ {
1115
+ cardinality: "one",
1116
+ scope: "instrument",
1117
+ spelling: "subject",
1118
+ target: "subject",
1119
+ },
1120
+ {
1121
+ cardinality: "one",
1122
+ scope: "instrument",
1123
+ spelling: "summary",
1124
+ target: "summary",
1125
+ },
1126
+ {
1127
+ cardinality: "one",
1128
+ scope: "instrument",
1129
+ spelling: "surface visibility",
1130
+ target: "surfaceVisibility",
1131
+ },
1132
+ {
1133
+ cardinality: "one",
1134
+ scope: "instrument",
1135
+ spelling: "template id",
1136
+ target: "templateId",
1137
+ },
1138
+ {
1139
+ cardinality: "one",
1140
+ scope: "instrument",
1141
+ spelling: "title",
1142
+ target: "title",
1143
+ },
1144
+ {
1145
+ cardinality: "one",
1146
+ scope: "instrument",
1147
+ spelling: "update",
1148
+ target: "update",
1149
+ },
1150
+ ] as const satisfies readonly UdlClauseVocabularyEntry[];
393
1151
 
394
1152
  const udlDocumentShapeSchema = z.strictObject({
395
- nouns: z.array(udlNounSchema).min(1),
1153
+ dials: z.array(udlCompositionDialSchema).min(1).optional(),
1154
+ instruments: z.array(udlInstrumentSchema).min(1),
396
1155
  product: udlSnakeCaseSchema,
397
1156
  subjects: z.array(udlSubjectSchema),
398
1157
  title: nonEmptyTextSchema,
@@ -400,7 +1159,7 @@ const udlDocumentShapeSchema = z.strictObject({
400
1159
  version: z.number().int().positive(),
401
1160
  });
402
1161
 
403
- type ParsedNoun = z.infer<typeof udlNounSchema>;
1162
+ type ParsedInstrument = z.infer<typeof udlInstrumentSchema>;
404
1163
 
405
1164
  const moneyPatterns = new Set(["^[1-9][0-9]{0,17}$", "^(0|[1-9][0-9]{0,17})$"]);
406
1165
 
@@ -414,8 +1173,8 @@ function isMoneyField(
414
1173
 
415
1174
  function referenceTargets(
416
1175
  schema: Readonly<Record<string, unknown>>,
417
- nouns: readonly ParsedNoun[],
418
- ): readonly ParsedNoun[] {
1176
+ instruments: readonly ParsedInstrument[],
1177
+ ): readonly ParsedInstrument[] {
419
1178
  if (schema.type !== "string" || typeof schema.pattern !== "string") return [];
420
1179
  // Parse the scoped-ID pattern. Executing an author regex here would bypass
421
1180
  // the semantic validator's branch and quantifier budgets.
@@ -433,43 +1192,52 @@ function referenceTargets(
433
1192
  if (typeof schema.maxLength === "number" && probe.length > schema.maxLength) {
434
1193
  return [];
435
1194
  }
436
- return nouns.filter((noun) => noun.idPrefix === prefix);
1195
+ return instruments.filter((instrument) => instrument.idPrefix === prefix);
437
1196
  }
438
1197
 
439
- function declaredMoneyRefKeys(noun: ParsedNoun): ReadonlySet<string> {
1198
+ function declaredMoneyRefKeys(
1199
+ instrument: ParsedInstrument,
1200
+ ): ReadonlySet<string> {
440
1201
  return new Set([
441
- ...(noun.computedMoneyRefs ?? []),
442
- ...Object.values(noun.verbs).flatMap((verb) => [
443
- ...(verb.signedSum
1202
+ ...Object.values(instrument.actions).flatMap((action) => [
1203
+ ...(action.remainder
1204
+ ? [
1205
+ action.remainder.amountRef,
1206
+ ...(action.remainder.accumulateRef
1207
+ ? [action.remainder.accumulateRef]
1208
+ : []),
1209
+ ]
1210
+ : []),
1211
+ ...(action.signedSum
444
1212
  ? [
445
- verb.signedSum.amountRef,
446
- ...verb.signedSum.sources.map((source) => source.subtotalRef),
1213
+ action.signedSum.amountRef,
1214
+ ...action.signedSum.sources.map((source) => source.subtotalRef),
447
1215
  ]
448
1216
  : []),
449
- ...(verb.distribute ? [verb.distribute.amountRef] : []),
1217
+ ...(action.distribute ? [action.distribute.amountRef] : []),
1218
+ ...(action.quote ? [action.quote.chargeRef, action.quote.netRef] : []),
450
1219
  ]),
451
- ...(noun.unwind ? ["unwindRefund", "unwindPenalty"] : []),
452
1220
  ]);
453
1221
  }
454
1222
 
455
1223
  export const udlDocumentSchema = udlDocumentShapeSchema.superRefine(
456
1224
  (document, context) => {
457
- document.nouns.forEach((noun, nounIndex) => {
458
- noun.derivedAmounts?.forEach((amount, amountIndex) => {
1225
+ document.instruments.forEach((instrument, instrumentIndex) => {
1226
+ instrument.derivedAmounts?.forEach((amount, amountIndex) => {
459
1227
  const base = [
460
- "nouns",
461
- nounIndex,
1228
+ "instruments",
1229
+ instrumentIndex,
462
1230
  "derivedAmounts",
463
1231
  amountIndex,
464
1232
  ] as const;
465
- if (!isMoneyField(noun.fields[amount.field])) {
1233
+ if (!isMoneyField(instrument.fields[amount.field])) {
466
1234
  context.addIssue({
467
1235
  code: "custom",
468
1236
  message: `derived amount target ${amount.field} must be a declared money field`,
469
1237
  path: [...base, "field"],
470
1238
  });
471
1239
  }
472
- if (!isMoneyField(noun.fields[amount.sourceField])) {
1240
+ if (!isMoneyField(instrument.fields[amount.sourceField])) {
473
1241
  context.addIssue({
474
1242
  code: "custom",
475
1243
  message: `derived amount source ${amount.sourceField} must be a declared money field`,
@@ -485,29 +1253,29 @@ export const udlDocumentSchema = udlDocumentShapeSchema.superRefine(
485
1253
  }
486
1254
  });
487
1255
 
488
- Object.entries(noun.verbs).forEach(([verbName, verb]) => {
489
- const distribute = verb.distribute;
1256
+ Object.entries(instrument.actions).forEach(([actionName, action]) => {
1257
+ const distribute = action.distribute;
490
1258
  if (!distribute) return;
491
1259
  const base = [
492
- "nouns",
493
- nounIndex,
494
- "verbs",
495
- verbName,
1260
+ "instruments",
1261
+ instrumentIndex,
1262
+ "actions",
1263
+ actionName,
496
1264
  "distribute",
497
1265
  ] as const;
498
- const refSchema = noun.fields[distribute.refField];
1266
+ const refSchema = instrument.fields[distribute.refField];
499
1267
  const parents = refSchema
500
- ? referenceTargets(refSchema, document.nouns)
1268
+ ? referenceTargets(refSchema, document.instruments)
501
1269
  : [];
502
1270
  if (parents.length !== 1) {
503
1271
  context.addIssue({
504
1272
  code: "custom",
505
- message: `distribute refField ${distribute.refField} must identify exactly one parent noun`,
1273
+ message: `distribute refField ${distribute.refField} must identify exactly one parent instrument`,
506
1274
  path: [...base, "refField"],
507
1275
  });
508
1276
  }
509
1277
 
510
- if (!isMoneyField(noun.fields[distribute.weightField])) {
1278
+ if (!isMoneyField(instrument.fields[distribute.weightField])) {
511
1279
  context.addIssue({
512
1280
  code: "custom",
513
1281
  message: `distribute weightField ${distribute.weightField} must be a declared money field`,
@@ -516,10 +1284,10 @@ export const udlDocumentSchema = udlDocumentShapeSchema.superRefine(
516
1284
  }
517
1285
 
518
1286
  distribute.statuses.forEach((status, statusIndex) => {
519
- if (noun.lifecycle.states.includes(status)) return;
1287
+ if (instrument.lifecycle.states.includes(status)) return;
520
1288
  context.addIssue({
521
1289
  code: "custom",
522
- message: `distribute status ${status} is not declared by ${noun.id}`,
1290
+ message: `distribute status ${status} is not declared by ${instrument.id}`,
523
1291
  path: [...base, "statuses", statusIndex],
524
1292
  });
525
1293
  });
@@ -556,8 +1324,11 @@ export type UdlDeclaredValuePolicy = z.infer<
556
1324
  typeof udlDeclaredValuePolicySchema
557
1325
  >;
558
1326
  export type UdlDecision = z.infer<typeof udlDecisionSchema>;
1327
+ export type UdlCompositionDial = z.infer<typeof udlCompositionDialSchema>;
1328
+ export type UdlDial = z.infer<typeof udlDialSchema>;
559
1329
  export type UdlDocument = z.infer<typeof udlDocumentSchema>;
560
1330
  export type UdlDue = z.infer<typeof udlDueSchema>;
1331
+ export type UdlEffects = z.infer<typeof udlEffectsSchema>;
561
1332
  export type UdlExample = z.infer<typeof udlExampleSchema>;
562
1333
  export type UdlGate = z.infer<typeof udlGateSchema>;
563
1334
  export type UdlKernelOperation = z.infer<typeof udlKernelOperationSchema>;
@@ -565,12 +1336,15 @@ export type UdlLifecycle = z.infer<typeof udlLifecycleSchema>;
565
1336
  export type UdlLifecycleTransition = z.infer<
566
1337
  typeof udlLifecycleTransitionSchema
567
1338
  >;
568
- export type UdlNoun = z.infer<typeof udlNounSchema>;
569
- export type UdlNounSubject = z.infer<typeof udlNounSubjectSchema>;
1339
+ export type UdlInstrument = z.infer<typeof udlInstrumentSchema>;
1340
+ export type UdlInstrumentSubject = z.infer<typeof udlInstrumentSubjectSchema>;
570
1341
  export type UdlMove = z.infer<typeof udlMoveSchema>;
571
1342
  export type UdlPayout = z.infer<typeof udlPayoutSchema>;
572
- export type UdlRequiresSettlement = z.infer<typeof udlRequiresSettlementSchema>;
1343
+ export type UdlQuote = z.infer<typeof udlQuoteSchema>;
1344
+ export type UdlProviderFamilyId = z.infer<typeof udlProviderFamilyIdSchema>;
1345
+ export type UdlCheckRequirement = z.infer<typeof udlCheckRequirementSchema>;
1346
+ export type UdlReconcile = z.infer<typeof udlReconcileSchema>;
1347
+ export type UdlRemainder = z.infer<typeof udlRemainderSchema>;
573
1348
  export type UdlStep = z.infer<typeof udlStepSchema>;
574
1349
  export type UdlSubject = z.infer<typeof udlSubjectSchema>;
575
- export type UdlUnwind = z.infer<typeof udlUnwindSchema>;
576
- export type UdlVerb = z.infer<typeof udlVerbSchema>;
1350
+ export type UdlAction = z.infer<typeof udlActionSchema>;