@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.
- package/CHANGELOG.md +78 -25
- package/README.md +37 -99
- package/conformance/README.md +44 -41
- package/conformance/evolution/action-contract.expected.json +10 -0
- package/conformance/evolution/action-contract.live.udl +44 -0
- package/conformance/evolution/action-contract.next.udl +45 -0
- package/conformance/evolution/product-identity.expected.json +10 -0
- package/conformance/evolution/product-identity.live.udl +55 -0
- package/conformance/evolution/product-identity.next.udl +55 -0
- package/conformance/evolution/version-required.expected.json +10 -0
- package/conformance/evolution/version-required.live.udl +55 -0
- package/conformance/evolution/version-required.next.udl +58 -0
- package/conformance/invalid/action-without-transition.expected.json +10 -0
- package/conformance/invalid/{verb-without-transition.udl → action-without-transition.udl} +7 -2
- package/conformance/invalid/agent-description-too-long.expected.json +10 -0
- package/conformance/invalid/agent-description-too-long.udl +57 -0
- package/conformance/invalid/blank-title.expected.json +1 -1
- package/conformance/invalid/blank-title.udl +6 -2
- package/conformance/invalid/composition-dial-duplicate-key.expected.json +10 -0
- package/conformance/invalid/composition-dial-duplicate-key.udl +73 -0
- package/conformance/invalid/depth-budget.expected.json +1 -1
- package/conformance/invalid/duplicate-subject.expected.json +10 -0
- package/conformance/invalid/duplicate-subject.udl +230 -0
- package/conformance/invalid/forged-effects.expected.json +10 -0
- package/conformance/invalid/forged-effects.udl +76 -0
- package/conformance/invalid/format-version.expected.json +1 -1
- package/conformance/invalid/format-version.udl +6 -2
- package/conformance/invalid/instrument-id-not-snake-case.expected.json +10 -0
- package/conformance/invalid/{noun-id-not-snake-case.udl → instrument-id-not-snake-case.udl} +6 -2
- package/conformance/invalid/invalid-aggregate-gate-shape.expected.json +10 -0
- package/conformance/invalid/invalid-aggregate-gate-shape.udl +1819 -0
- package/conformance/invalid/invalid-check-duration.expected.json +10 -0
- package/conformance/invalid/invalid-check-duration.udl +219 -0
- package/conformance/invalid/invalid-dial-anchor.expected.json +10 -0
- package/conformance/invalid/invalid-dial-anchor.udl +219 -0
- package/conformance/invalid/invalid-exception-parent-ref.expected.json +10 -0
- package/conformance/invalid/invalid-exception-parent-ref.udl +2487 -0
- package/conformance/invalid/invalid-exposure-shape.expected.json +10 -0
- package/conformance/invalid/invalid-exposure-shape.udl +1819 -0
- package/conformance/invalid/invalid-remainder.expected.json +10 -0
- package/conformance/invalid/invalid-remainder.udl +220 -0
- package/conformance/invalid/invalid-schema-keyword.expected.json +10 -0
- package/conformance/invalid/invalid-schema-keyword.udl +220 -0
- package/conformance/invalid/invalid-utf8.expected.json +1 -1
- package/conformance/invalid/malformed-json.expected.json +1 -1
- package/conformance/invalid/missing-create-action.expected.json +10 -0
- package/conformance/invalid/{missing-create-verb.udl → missing-create-action.udl} +5 -2
- package/conformance/invalid/missing-exception-amount-field.expected.json +10 -0
- package/conformance/invalid/missing-exception-amount-field.udl +2487 -0
- package/conformance/invalid/missing-exception-contract.expected.json +14 -0
- package/conformance/invalid/missing-exception-contract.udl +2523 -0
- package/conformance/invalid/missing-exception-reason-field.expected.json +10 -0
- package/conformance/invalid/missing-exception-reason-field.udl +2487 -0
- package/conformance/invalid/not-an-object.expected.json +1 -1
- package/conformance/invalid/payout-reconcile-not-a-bank-debit.expected.json +10 -0
- package/conformance/invalid/payout-reconcile-not-a-bank-debit.udl +259 -0
- package/conformance/invalid/quote-freeze-set-incomplete.expected.json +10 -0
- package/conformance/invalid/quote-freeze-set-incomplete.udl +261 -0
- package/conformance/invalid/quote-named-reference-gate.expected.json +10 -0
- package/conformance/invalid/quote-named-reference-gate.udl +50 -0
- package/conformance/invalid/reconcile-named-reference-gate.expected.json +10 -0
- package/conformance/invalid/reconcile-named-reference-gate.udl +50 -0
- package/conformance/invalid/unknown-key.expected.json +1 -1
- package/conformance/invalid/unknown-key.udl +6 -2
- package/conformance/invalid/unknown-reference-gate-field.expected.json +10 -0
- package/conformance/invalid/unknown-reference-gate-field.udl +1819 -0
- package/conformance/invalid/unknown-required-field.expected.json +10 -0
- package/conformance/invalid/unknown-required-field.udl +220 -0
- package/conformance/invalid/unreachable-state.expected.json +2 -2
- package/conformance/invalid/unreachable-state.udl +6 -2
- package/conformance/invalid/wrong-exception-amount-field.expected.json +10 -0
- package/conformance/invalid/wrong-exception-amount-field.udl +2487 -0
- package/conformance/invalid/wrong-exception-reason-field.expected.json +10 -0
- package/conformance/invalid/wrong-exception-reason-field.udl +2487 -0
- package/conformance/valid/agent-description.expected.json +6 -0
- package/conformance/valid/agent-description.udl +66 -0
- package/conformance/valid/cards.expected.json +1 -0
- package/conformance/valid/cards.udl +843 -356
- package/conformance/valid/commerce-escrow.expected.json +1 -0
- package/conformance/valid/commerce-escrow.udl +792 -291
- package/conformance/valid/compiled-crowdfunding.expected.json +6 -0
- package/conformance/valid/compiled-crowdfunding.udl +1843 -0
- package/conformance/valid/compiled-watch-club.expected.json +6 -0
- package/conformance/valid/compiled-watch-club.udl +2486 -0
- package/conformance/valid/complete-contract.expected.json +6 -0
- package/conformance/valid/complete-contract.udl +218 -0
- package/conformance/valid/effect-signatures.expected.json +6 -0
- package/conformance/valid/effect-signatures.udl +75 -0
- package/conformance/valid/hand-edited.expected.json +1 -0
- package/conformance/valid/hand-edited.udl +1 -1
- package/conformance/valid/insured-car-marketplace.expected.json +1 -0
- package/conformance/valid/insured-car-marketplace.udl +534 -315
- package/conformance/valid/insured-travel.expected.json +2 -1
- package/conformance/valid/insured-travel.udl +1489 -641
- package/conformance/valid/minimal.expected.json +1 -0
- package/conformance/valid/minimal.udl +18 -14
- package/conformance/valid/protection.expected.json +1 -0
- package/conformance/valid/protection.udl +719 -304
- package/conformance/valid/string-escaping.expected.json +6 -0
- package/conformance/valid/string-escaping.udl +54 -0
- package/dist/canonical.d.ts +2 -0
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +7 -0
- package/dist/canonical.js.map +1 -1
- package/dist/check-profiles.d.ts +57 -0
- package/dist/check-profiles.d.ts.map +1 -0
- package/dist/check-profiles.js +62 -0
- package/dist/check-profiles.js.map +1 -0
- package/dist/cli.js +48 -4
- package/dist/cli.js.map +1 -1
- package/dist/diagnostics.d.ts +168 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +164 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/effects.d.ts +41 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +110 -0
- package/dist/effects.js.map +1 -0
- package/dist/evolution.d.ts +51 -18
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +244 -102
- package/dist/evolution.js.map +1 -1
- package/dist/finance.d.ts +17 -13
- package/dist/finance.d.ts.map +1 -1
- package/dist/finance.js +165 -109
- package/dist/finance.js.map +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -2
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -3
- package/dist/limits.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +6 -16
- package/dist/parser.js.map +1 -1
- package/dist/schema.d.ts +2059 -135
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +759 -73
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts +22 -9
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +1463 -457
- package/dist/validation.js.map +1 -1
- package/docs/README.md +14 -0
- package/docs/guide/01-a-document.md +37 -0
- package/docs/guide/02-money-steps.md +23 -0
- package/docs/guide/03-laws.md +18 -0
- package/docs/guide/04-fees-and-remainder.md +15 -0
- package/docs/guide/05-checks-updates-dials.md +7 -0
- package/docs/guide/06-effects.md +11 -0
- package/docs/guide/07-evolution.md +11 -0
- package/docs/guide/08-implementing.md +13 -0
- package/docs/llms-full.txt +1241 -0
- package/docs/llms.txt +14 -0
- package/docs/reference/canonical.md +16 -0
- package/docs/reference/clauses.md +986 -0
- package/docs/reference/cli.md +22 -0
- package/docs/reference/diagnostics.md +32 -0
- package/package.json +6 -2
- package/skills/udl/SKILL.md +28 -0
- package/spec/README.md +82 -51
- package/spec/udl.schema.json +1191 -119
- package/src/canonical.ts +11 -0
- package/src/check-profiles.ts +80 -0
- package/src/cli.ts +49 -3
- package/src/diagnostics.ts +221 -0
- package/src/effects.ts +163 -0
- package/src/evolution.ts +375 -125
- package/src/finance.ts +255 -160
- package/src/index.ts +50 -14
- package/src/limits.ts +4 -3
- package/src/parser.ts +15 -18
- package/src/schema.ts +855 -81
- package/src/validation.ts +2666 -560
- package/conformance/invalid/missing-create-verb.expected.json +0 -10
- package/conformance/invalid/noun-id-not-snake-case.expected.json +0 -10
- 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
|
|
12
|
+
const udlActionNameSchema = z
|
|
12
13
|
.string()
|
|
13
|
-
.regex(snakeCasePattern, "must be a snake_case
|
|
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
|
|
18
|
+
export const udlPublicActionSchema = z
|
|
18
19
|
.string()
|
|
19
|
-
.regex(camelCasePattern, "must be a camelCase public
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
169
|
+
const udlQuoteChargeSchema = z.strictObject({
|
|
130
170
|
bps: z.number().int().min(0).max(9_999),
|
|
131
171
|
withinOffset: nonEmptyTextSchema.optional(),
|
|
132
172
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
277
|
+
childInstrumentId: udlSnakeCaseSchema,
|
|
202
278
|
statuses: z.array(udlSnakeCaseSchema).min(1),
|
|
203
279
|
});
|
|
204
|
-
// The tenant-backend decision port: the
|
|
205
|
-
// party, checked at admission against 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
535
|
+
quote: udlQuoteSchema.optional(),
|
|
536
|
+
publicAction: udlPublicActionSchema
|
|
281
537
|
.optional()
|
|
282
|
-
.describe("Author-approved public
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
580
|
+
const udlInstrumentShape = {
|
|
581
|
+
actionOrder: z.array(udlActionNameSchema).min(1),
|
|
582
|
+
agentDescription: udlAgentDescriptionSchema.optional(),
|
|
319
583
|
aggregateInvariants: z.array(udlAggregateSchema).min(1).optional(),
|
|
320
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
1057
|
+
return instruments.filter((instrument) => instrument.idPrefix === prefix);
|
|
379
1058
|
}
|
|
380
|
-
function declaredMoneyRefKeys(
|
|
1059
|
+
function declaredMoneyRefKeys(instrument) {
|
|
381
1060
|
return new Set([
|
|
382
|
-
...(
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
387
|
-
...
|
|
1072
|
+
action.signedSum.amountRef,
|
|
1073
|
+
...action.signedSum.sources.map((source) => source.subtotalRef),
|
|
388
1074
|
]
|
|
389
1075
|
: []),
|
|
390
|
-
...(
|
|
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.
|
|
397
|
-
|
|
1082
|
+
document.instruments.forEach((instrument, instrumentIndex) => {
|
|
1083
|
+
instrument.derivedAmounts?.forEach((amount, amountIndex) => {
|
|
398
1084
|
const base = [
|
|
399
|
-
"
|
|
400
|
-
|
|
1085
|
+
"instruments",
|
|
1086
|
+
instrumentIndex,
|
|
401
1087
|
"derivedAmounts",
|
|
402
1088
|
amountIndex,
|
|
403
1089
|
];
|
|
404
|
-
if (!isMoneyField(
|
|
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(
|
|
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(
|
|
427
|
-
const 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
|
-
"
|
|
432
|
-
|
|
433
|
-
"
|
|
434
|
-
|
|
1117
|
+
"instruments",
|
|
1118
|
+
instrumentIndex,
|
|
1119
|
+
"actions",
|
|
1120
|
+
actionName,
|
|
435
1121
|
"distribute",
|
|
436
1122
|
];
|
|
437
|
-
const refSchema =
|
|
1123
|
+
const refSchema = instrument.fields[distribute.refField];
|
|
438
1124
|
const parents = refSchema
|
|
439
|
-
? referenceTargets(refSchema, document.
|
|
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
|
|
1130
|
+
message: `distribute refField ${distribute.refField} must identify exactly one parent instrument`,
|
|
445
1131
|
path: [...base, "refField"],
|
|
446
1132
|
});
|
|
447
1133
|
}
|
|
448
|
-
if (!isMoneyField(
|
|
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 (
|
|
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 ${
|
|
1146
|
+
message: `distribute status ${status} is not declared by ${instrument.id}`,
|
|
461
1147
|
path: [...base, "statuses", statusIndex],
|
|
462
1148
|
});
|
|
463
1149
|
});
|