@hyperscale0/udl 1.0.0-alpha.1
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/AUTHORS +8 -0
- package/CHANGELOG.md +36 -0
- package/LICENSE +661 -0
- package/LICENSING.md +51 -0
- package/README.md +163 -0
- package/SECURITY.md +42 -0
- package/TRADEMARKS.md +35 -0
- package/conformance/README.md +79 -0
- package/conformance/invalid/blank-title.expected.json +10 -0
- package/conformance/invalid/blank-title.udl +50 -0
- package/conformance/invalid/depth-budget.expected.json +10 -0
- package/conformance/invalid/depth-budget.udl +49 -0
- package/conformance/invalid/format-version.expected.json +10 -0
- package/conformance/invalid/format-version.udl +50 -0
- package/conformance/invalid/invalid-utf8.expected.json +10 -0
- package/conformance/invalid/invalid-utf8.udl +1 -0
- package/conformance/invalid/malformed-json.expected.json +10 -0
- package/conformance/invalid/malformed-json.udl +1 -0
- package/conformance/invalid/missing-create-verb.expected.json +10 -0
- package/conformance/invalid/missing-create-verb.udl +45 -0
- package/conformance/invalid/not-an-object.expected.json +10 -0
- package/conformance/invalid/not-an-object.udl +1 -0
- package/conformance/invalid/noun-id-not-snake-case.expected.json +10 -0
- package/conformance/invalid/noun-id-not-snake-case.udl +50 -0
- package/conformance/invalid/unknown-key.expected.json +10 -0
- package/conformance/invalid/unknown-key.udl +51 -0
- package/conformance/invalid/unreachable-state.expected.json +10 -0
- package/conformance/invalid/unreachable-state.udl +51 -0
- package/conformance/invalid/verb-without-transition.expected.json +10 -0
- package/conformance/invalid/verb-without-transition.udl +55 -0
- package/conformance/valid/cards.expected.json +5 -0
- package/conformance/valid/cards.udl +1092 -0
- package/conformance/valid/commerce-escrow.expected.json +5 -0
- package/conformance/valid/commerce-escrow.udl +1011 -0
- package/conformance/valid/hand-edited.expected.json +5 -0
- package/conformance/valid/hand-edited.udl +1 -0
- package/conformance/valid/insured-car-marketplace.expected.json +5 -0
- package/conformance/valid/insured-car-marketplace.udl +831 -0
- package/conformance/valid/insured-travel.expected.json +5 -0
- package/conformance/valid/insured-travel.udl +2621 -0
- package/conformance/valid/minimal.expected.json +5 -0
- package/conformance/valid/minimal.udl +50 -0
- package/conformance/valid/protection.expected.json +5 -0
- package/conformance/valid/protection.udl +1136 -0
- package/dist/canonical.d.ts +2 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +35 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/duration.d.ts +3 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +23 -0
- package/dist/duration.js.map +1 -0
- package/dist/evolution.d.ts +63 -0
- package/dist/evolution.d.ts.map +1 -0
- package/dist/evolution.js +345 -0
- package/dist/evolution.js.map +1 -0
- package/dist/finance.d.ts +53 -0
- package/dist/finance.d.ts.map +1 -0
- package/dist/finance.js +511 -0
- package/dist/finance.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +33 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +33 -0
- package/dist/limits.js.map +1 -0
- package/dist/parser.d.ts +4 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +54 -0
- package/dist/parser.js.map +1 -0
- package/dist/schema.d.ts +757 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +281 -0
- package/dist/schema.js.map +1 -0
- package/dist/validation.d.ts +64 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1323 -0
- package/dist/validation.js.map +1 -0
- package/package.json +84 -0
- package/spec/README.md +137 -0
- package/spec/udl.schema.json +1019 -0
- package/src/canonical.ts +41 -0
- package/src/cli.ts +148 -0
- package/src/duration.ts +26 -0
- package/src/evolution.ts +587 -0
- package/src/finance.ts +775 -0
- package/src/index.ts +59 -0
- package/src/limits.ts +32 -0
- package/src/parser.ts +61 -0
- package/src/schema.ts +339 -0
- package/src/validation.ts +2053 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export { canonicalizeUdl, parseUdl } from "./parser.js";
|
|
2
|
+
export { serializeUdl } from "./canonical.js";
|
|
3
|
+
export { fixedIsoDurationMs } from "./duration.js";
|
|
4
|
+
export {
|
|
5
|
+
analyzeNounFinance,
|
|
6
|
+
type FinanceIssue,
|
|
7
|
+
type FinanceOptions,
|
|
8
|
+
} from "./finance.js";
|
|
9
|
+
export {
|
|
10
|
+
diffNounEvolution,
|
|
11
|
+
diffUdlEvolution,
|
|
12
|
+
snapshotUdlNoun,
|
|
13
|
+
} from "./evolution.js";
|
|
14
|
+
export type {
|
|
15
|
+
EvolutionFieldSnapshot,
|
|
16
|
+
EvolutionMoveSnapshot,
|
|
17
|
+
EvolutionStepSnapshot,
|
|
18
|
+
EvolutionTransitionSnapshot,
|
|
19
|
+
EvolutionVerbSnapshot,
|
|
20
|
+
NounEvolutionSnapshot,
|
|
21
|
+
} from "./evolution.js";
|
|
22
|
+
export {
|
|
23
|
+
UDL_FORMAT_VERSION,
|
|
24
|
+
udlDocumentSchema,
|
|
25
|
+
udlKernelOperationSchema,
|
|
26
|
+
} from "./schema.js";
|
|
27
|
+
export type {
|
|
28
|
+
UdlAggregate,
|
|
29
|
+
UdlAggregateCondition,
|
|
30
|
+
UdlBinding,
|
|
31
|
+
UdlDeclaredValuePolicy,
|
|
32
|
+
UdlDecision,
|
|
33
|
+
UdlDocument,
|
|
34
|
+
UdlDue,
|
|
35
|
+
UdlExample,
|
|
36
|
+
UdlGate,
|
|
37
|
+
UdlKernelOperation,
|
|
38
|
+
UdlLifecycle,
|
|
39
|
+
UdlLifecycleTransition,
|
|
40
|
+
UdlNoun,
|
|
41
|
+
UdlNounSubject,
|
|
42
|
+
UdlStep,
|
|
43
|
+
UdlSubject,
|
|
44
|
+
UdlUnwind,
|
|
45
|
+
UdlVerb,
|
|
46
|
+
} from "./schema.js";
|
|
47
|
+
export {
|
|
48
|
+
assertValidUdl,
|
|
49
|
+
openReferenceShapeBudget,
|
|
50
|
+
UdlError,
|
|
51
|
+
validateUdl,
|
|
52
|
+
validateUdlSchemaValue,
|
|
53
|
+
} from "./validation.js";
|
|
54
|
+
export type {
|
|
55
|
+
ReferenceShapeBudget,
|
|
56
|
+
UdlIssue,
|
|
57
|
+
UdlIssueCode,
|
|
58
|
+
UdlValidationResult,
|
|
59
|
+
} from "./validation.js";
|
package/src/limits.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const UDL_LIMITS = Object.freeze({
|
|
2
|
+
financeAccounts: 16,
|
|
3
|
+
financeEffects: 128,
|
|
4
|
+
financePathVariants: 256,
|
|
5
|
+
financeStates: 32,
|
|
6
|
+
financeTransitionEdges: 128,
|
|
7
|
+
financeTransitions: 64,
|
|
8
|
+
financeVerbs: 64,
|
|
9
|
+
financeWork: 4_096,
|
|
10
|
+
maxDepth: 24,
|
|
11
|
+
maxKeyLength: 128,
|
|
12
|
+
maxNodes: 10_000,
|
|
13
|
+
maxPatternLength: 320,
|
|
14
|
+
/**
|
|
15
|
+
* Upper bound on the match attempts a document-authored `pattern` can force
|
|
16
|
+
* the backtracking engine to explore at its single anchored start position.
|
|
17
|
+
* Every admitted pattern's search space is multiplied out at admission
|
|
18
|
+
* (alternation branches x optional atoms x variable-quantifier spans), so
|
|
19
|
+
* worst-case matching cost is this budget times the pattern length, not an
|
|
20
|
+
* exponential in the document author's choice of grammar.
|
|
21
|
+
*/
|
|
22
|
+
maxPatternPaths: 4_096,
|
|
23
|
+
/**
|
|
24
|
+
* Reference-shape classifications one `ReferenceShapeBudget` may buy. Each
|
|
25
|
+
* one compiles a JSON Schema validator, and a document controls both factors
|
|
26
|
+
* of the nouns x gate-fields product that asks for them.
|
|
27
|
+
*/
|
|
28
|
+
maxSchemaProbes: 2_048,
|
|
29
|
+
maxSourceBytes: 256 * 1_024,
|
|
30
|
+
maxStringLength: 2_048,
|
|
31
|
+
maxTotalStringLength: 256 * 1_024,
|
|
32
|
+
});
|
package/src/parser.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { serializeUdl } from "./canonical.js";
|
|
2
|
+
import { UDL_LIMITS } from "./limits.js";
|
|
3
|
+
import type { UdlDocument } from "./schema.js";
|
|
4
|
+
import { assertValidUdl, UdlError, type UdlIssue } from "./validation.js";
|
|
5
|
+
|
|
6
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
7
|
+
|
|
8
|
+
export function parseUdl(source: string | Uint8Array): UdlDocument {
|
|
9
|
+
const sourceBytes =
|
|
10
|
+
typeof source === "string"
|
|
11
|
+
? source.length > UDL_LIMITS.maxSourceBytes
|
|
12
|
+
? source.length
|
|
13
|
+
: new TextEncoder().encode(source).byteLength
|
|
14
|
+
: source.byteLength;
|
|
15
|
+
if (sourceBytes > UDL_LIMITS.maxSourceBytes) {
|
|
16
|
+
throw new UdlError([
|
|
17
|
+
{
|
|
18
|
+
code: "resource_limit",
|
|
19
|
+
message: `UDL source exceeds ${UDL_LIMITS.maxSourceBytes} bytes`,
|
|
20
|
+
path: "$",
|
|
21
|
+
},
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
return parseUdlText(
|
|
25
|
+
typeof source === "string" ? source : decodeUdlBytes(source),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseUdlText(source: string): UdlDocument {
|
|
30
|
+
let value: unknown;
|
|
31
|
+
try {
|
|
32
|
+
value = JSON.parse(source) as unknown;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new UdlError([
|
|
35
|
+
{
|
|
36
|
+
code: "invalid_json",
|
|
37
|
+
message:
|
|
38
|
+
error instanceof Error ? error.message : "could not parse JSON",
|
|
39
|
+
path: "$",
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
return assertValidUdl(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function canonicalizeUdl(source: string | Uint8Array): string {
|
|
47
|
+
return serializeUdl(parseUdl(source));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function decodeUdlBytes(source: Uint8Array): string {
|
|
51
|
+
try {
|
|
52
|
+
return utf8Decoder.decode(source);
|
|
53
|
+
} catch {
|
|
54
|
+
const issue: UdlIssue = {
|
|
55
|
+
code: "invalid_utf8",
|
|
56
|
+
message: "UDL bytes must be valid UTF-8",
|
|
57
|
+
path: "$",
|
|
58
|
+
};
|
|
59
|
+
throw new UdlError([issue]);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const UDL_FORMAT_VERSION = 1 as const;
|
|
4
|
+
|
|
5
|
+
const snakeCasePattern = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
|
|
6
|
+
const camelCasePattern = /^[a-z][A-Za-z0-9]*$/;
|
|
7
|
+
const fieldPathPattern = /^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)*$/;
|
|
8
|
+
const idPrefixPattern = /^[a-z]{2,8}$/;
|
|
9
|
+
const eventNamePattern = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/;
|
|
10
|
+
|
|
11
|
+
const udlSnakeCaseSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(snakeCasePattern, "must be a snake_case identifier");
|
|
14
|
+
const udlVerbNameSchema = z
|
|
15
|
+
.string()
|
|
16
|
+
.regex(snakeCasePattern, "must be a snake_case verb identifier");
|
|
17
|
+
const udlFieldNameSchema = z
|
|
18
|
+
.string()
|
|
19
|
+
.regex(camelCasePattern, "must be a camelCase field name");
|
|
20
|
+
|
|
21
|
+
const nonEmptyTextSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.refine((value) => value.trim().length > 0, "must not be blank");
|
|
24
|
+
const fieldPathSchema = z
|
|
25
|
+
.string()
|
|
26
|
+
.regex(fieldPathPattern, "must be a dot-separated camelCase field path");
|
|
27
|
+
const jsonObjectSchema = z.record(z.string(), z.json());
|
|
28
|
+
const stringMapSchema = z.record(nonEmptyTextSchema, nonEmptyTextSchema);
|
|
29
|
+
|
|
30
|
+
const udlDeclaredValuePolicySchema = z.enum(["required", "optional", "none"]);
|
|
31
|
+
|
|
32
|
+
const udlSubjectSchema = z.strictObject({
|
|
33
|
+
declaredValue: udlDeclaredValuePolicySchema,
|
|
34
|
+
kind: udlSnakeCaseSchema,
|
|
35
|
+
schema: jsonObjectSchema,
|
|
36
|
+
title: nonEmptyTextSchema,
|
|
37
|
+
version: z.number().int().positive(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const udlNounSubjectSchema = z.strictObject({
|
|
41
|
+
kinds: z.array(udlSnakeCaseSchema).min(1),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const udlLifecycleTransitionSchema = z.strictObject({
|
|
45
|
+
from: z.array(udlSnakeCaseSchema).min(1),
|
|
46
|
+
to: udlSnakeCaseSchema,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const udlLifecycleSchema = z.strictObject({
|
|
50
|
+
initial: udlSnakeCaseSchema,
|
|
51
|
+
states: z.array(udlSnakeCaseSchema).min(1),
|
|
52
|
+
transitions: z.record(udlVerbNameSchema, udlLifecycleTransitionSchema),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const udlUpdatePolicySchema = z.strictObject({
|
|
56
|
+
fields: z.array(udlFieldNameSchema).min(1),
|
|
57
|
+
states: z.array(udlSnakeCaseSchema).min(1),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const udlGateSchema = z.strictObject({
|
|
61
|
+
/** Local field key <- referenced instance path; create verbs only. */
|
|
62
|
+
bind: z.record(udlFieldNameSchema, fieldPathSchema).optional(),
|
|
63
|
+
field: udlFieldNameSchema,
|
|
64
|
+
/** Local instance path === referenced instance path at admission. */
|
|
65
|
+
match: z.record(fieldPathSchema, fieldPathSchema).optional(),
|
|
66
|
+
/** Opt-in over an optional reference field: absent passes, set is held to the clause. */
|
|
67
|
+
optional: z.literal(true).optional(),
|
|
68
|
+
statuses: z.array(udlSnakeCaseSchema).min(1),
|
|
69
|
+
/** One dependent ever per referenced instance; create verbs only. */
|
|
70
|
+
unique: z.literal(true).optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const udlBindingSchema = z.discriminatedUnion("from", [
|
|
74
|
+
z.strictObject({ from: z.literal("const"), value: z.string() }),
|
|
75
|
+
z.strictObject({ from: z.literal("instance"), path: fieldPathSchema }),
|
|
76
|
+
z.strictObject({ from: z.literal("input"), path: fieldPathSchema }),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const udlStepOperationSchema = z.enum([
|
|
80
|
+
"account.escrow.provision",
|
|
81
|
+
"account.freeze",
|
|
82
|
+
"account.unfreeze",
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const udlMoveOperationSchema = z.enum([
|
|
86
|
+
"internal_transfer.create",
|
|
87
|
+
"internal_transfer.reserve",
|
|
88
|
+
"internal_transfer.post",
|
|
89
|
+
"internal_transfer.void",
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
export const udlKernelOperationSchema = z.enum([
|
|
93
|
+
...udlStepOperationSchema.options,
|
|
94
|
+
...udlMoveOperationSchema.options,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const udlStepSchema = z.strictObject({
|
|
98
|
+
bind: z.record(fieldPathSchema, udlBindingSchema),
|
|
99
|
+
capture: stringMapSchema.optional(),
|
|
100
|
+
operation: udlStepOperationSchema,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const udlMoveSchema = z.strictObject({
|
|
104
|
+
bind: z.record(fieldPathSchema, udlBindingSchema),
|
|
105
|
+
capture: stringMapSchema.optional(),
|
|
106
|
+
key: udlSnakeCaseSchema,
|
|
107
|
+
operation: udlMoveOperationSchema,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const udlDueRecurrenceSchema = z.strictObject({
|
|
111
|
+
countField: udlFieldNameSchema.optional(),
|
|
112
|
+
period: z.union([
|
|
113
|
+
nonEmptyTextSchema,
|
|
114
|
+
z.strictObject({
|
|
115
|
+
calendar: z.enum(["gregorian", "hijri"]),
|
|
116
|
+
months: z.number().int().positive(),
|
|
117
|
+
}),
|
|
118
|
+
]),
|
|
119
|
+
untilField: udlFieldNameSchema.optional(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const udlDueParentStatusSchema = z.strictObject({
|
|
123
|
+
nounId: udlSnakeCaseSchema,
|
|
124
|
+
refField: udlFieldNameSchema,
|
|
125
|
+
statuses: z.array(udlSnakeCaseSchema).min(1),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const udlDueSchema = z.strictObject({
|
|
129
|
+
every: udlDueRecurrenceSchema.optional(),
|
|
130
|
+
field: udlFieldNameSchema,
|
|
131
|
+
offset: nonEmptyTextSchema.optional(),
|
|
132
|
+
whenParentStatus: udlDueParentStatusSchema.optional(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const udlDeadlineSchema = z.strictObject({
|
|
136
|
+
field: udlFieldNameSchema,
|
|
137
|
+
offset: nonEmptyTextSchema.optional(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const udlSetsAtSchema = z.strictObject({
|
|
141
|
+
field: udlFieldNameSchema,
|
|
142
|
+
offset: nonEmptyTextSchema,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const udlUnwindPenaltySchema = z.strictObject({
|
|
146
|
+
bps: z.number().int().min(0).max(9_999),
|
|
147
|
+
withinOffset: nonEmptyTextSchema.optional(),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const udlUnwindSchema = z.strictObject({
|
|
151
|
+
beforeField: udlFieldNameSchema.optional(),
|
|
152
|
+
confirm: udlVerbNameSchema,
|
|
153
|
+
penalty: z.array(udlUnwindPenaltySchema).min(1),
|
|
154
|
+
quote: udlVerbNameSchema,
|
|
155
|
+
refundableField: udlFieldNameSchema,
|
|
156
|
+
refundDestinationField: udlFieldNameSchema,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const udlDecisionSchema = z.strictObject({
|
|
160
|
+
capability: udlSnakeCaseSchema,
|
|
161
|
+
deadlineMs: z.number().int().positive(),
|
|
162
|
+
// A decision that never came back is not an approval. `approve` would let a
|
|
163
|
+
// document release money because a counterparty went quiet, so the grammar
|
|
164
|
+
// admits only the fail-closed answer. Every published document already
|
|
165
|
+
// writes `decline`; widening later is additive, narrowing later would not
|
|
166
|
+
// be.
|
|
167
|
+
onTimeout: z.literal("decline"),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const udlExampleSchema = z.strictObject({
|
|
171
|
+
input: jsonObjectSchema,
|
|
172
|
+
name: nonEmptyTextSchema,
|
|
173
|
+
output: z.json().optional(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const udlAggregateCheckSchema = z.discriminatedUnion("kind", [
|
|
177
|
+
z.strictObject({
|
|
178
|
+
amountField: udlFieldNameSchema,
|
|
179
|
+
kind: z.literal("sum_at_least"),
|
|
180
|
+
targetField: udlFieldNameSchema,
|
|
181
|
+
}),
|
|
182
|
+
z.strictObject({
|
|
183
|
+
amountField: udlFieldNameSchema,
|
|
184
|
+
kind: z.literal("sum_below"),
|
|
185
|
+
targetField: udlFieldNameSchema,
|
|
186
|
+
}),
|
|
187
|
+
z.strictObject({
|
|
188
|
+
amountField: udlFieldNameSchema,
|
|
189
|
+
kind: z.literal("sum_exactly"),
|
|
190
|
+
targetField: udlFieldNameSchema,
|
|
191
|
+
}),
|
|
192
|
+
// Siblings summed against a cap read from the LOCKED anchor row's
|
|
193
|
+
// machinery-seeded ref (never a stored field on the counted rows).
|
|
194
|
+
z.strictObject({
|
|
195
|
+
amountField: udlFieldNameSchema,
|
|
196
|
+
anchorRef: udlFieldNameSchema,
|
|
197
|
+
kind: z.literal("sum_within_anchor_ref"),
|
|
198
|
+
}),
|
|
199
|
+
z.strictObject({
|
|
200
|
+
kind: z.literal("count_at_least"),
|
|
201
|
+
targetField: udlFieldNameSchema,
|
|
202
|
+
}),
|
|
203
|
+
z.strictObject({ kind: z.literal("all_in") }),
|
|
204
|
+
z.strictObject({
|
|
205
|
+
field: udlFieldNameSchema,
|
|
206
|
+
kind: z.literal("count_equals_field"),
|
|
207
|
+
}),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
const udlAggregateConditionSchema = z.strictObject({
|
|
211
|
+
check: udlAggregateCheckSchema,
|
|
212
|
+
nounId: udlSnakeCaseSchema.optional(),
|
|
213
|
+
over: z.enum(["children", "siblings"]),
|
|
214
|
+
refField: udlFieldNameSchema,
|
|
215
|
+
statuses: z.array(udlSnakeCaseSchema).min(1),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// The tenant-backend decision port: the verb's caller asserts the acting
|
|
219
|
+
// party, checked at admission against the noun's party bindings for the
|
|
220
|
+
// allowed roles.
|
|
221
|
+
const udlPortSchema = z.strictObject({
|
|
222
|
+
allowedParties: z
|
|
223
|
+
.array(z.enum(["payer", "beneficiary", "subjectHolder"]))
|
|
224
|
+
.min(1),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// A same-instance conservation law: the piece fields (money, minor units)
|
|
228
|
+
// must sum exactly to the total field; admission refuses a create whose
|
|
229
|
+
// pieces diverge.
|
|
230
|
+
const udlPartitionSchema = z.strictObject({
|
|
231
|
+
pieceFields: z.array(udlFieldNameSchema).min(2),
|
|
232
|
+
totalField: udlFieldNameSchema,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const udlVerbSchema = z.strictObject({
|
|
236
|
+
deadline: udlDeadlineSchema.optional(),
|
|
237
|
+
decision: udlDecisionSchema.optional(),
|
|
238
|
+
description: nonEmptyTextSchema.optional(),
|
|
239
|
+
due: udlDueSchema.optional(),
|
|
240
|
+
earnable: z.boolean().optional(),
|
|
241
|
+
eventName: z.string().regex(eventNamePattern).optional(),
|
|
242
|
+
examples: z.array(udlExampleSchema).min(1).optional(),
|
|
243
|
+
input: jsonObjectSchema.optional(),
|
|
244
|
+
moves: z.array(udlMoveSchema).default([]),
|
|
245
|
+
port: udlPortSchema.optional(),
|
|
246
|
+
requiresAggregate: z.array(udlAggregateConditionSchema).min(1).optional(),
|
|
247
|
+
requiresDrainedAccount: z.strictObject({ path: fieldPathSchema }).optional(),
|
|
248
|
+
requiresRefs: z.array(udlGateSchema).min(1).optional(),
|
|
249
|
+
setsAt: udlSetsAtSchema.optional(),
|
|
250
|
+
steps: z.array(udlStepSchema),
|
|
251
|
+
summary: nonEmptyTextSchema,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const udlAggregateBaseShape = {
|
|
255
|
+
childNounId: udlSnakeCaseSchema,
|
|
256
|
+
childRefField: udlFieldNameSchema,
|
|
257
|
+
childStatuses: z.array(udlSnakeCaseSchema).min(1),
|
|
258
|
+
parentField: udlFieldNameSchema,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Two measures, mutually exclusive by strictness: a sum cap over a child money
|
|
262
|
+
// field, or a count cap (optionally windowed to the rolling `days` ending at
|
|
263
|
+
// the child's own as-written date-time value).
|
|
264
|
+
const udlAggregateSchema = z.union([
|
|
265
|
+
z.strictObject({
|
|
266
|
+
...udlAggregateBaseShape,
|
|
267
|
+
childField: udlFieldNameSchema,
|
|
268
|
+
}),
|
|
269
|
+
z.strictObject({
|
|
270
|
+
...udlAggregateBaseShape,
|
|
271
|
+
count: z.literal(true),
|
|
272
|
+
window: z
|
|
273
|
+
.strictObject({
|
|
274
|
+
field: udlFieldNameSchema,
|
|
275
|
+
days: z.number().int().min(1).max(3650),
|
|
276
|
+
})
|
|
277
|
+
.optional(),
|
|
278
|
+
}),
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
const udlNounSchema = z.strictObject({
|
|
282
|
+
aggregateInvariants: z.array(udlAggregateSchema).min(1).optional(),
|
|
283
|
+
description: nonEmptyTextSchema.optional(),
|
|
284
|
+
distinctParties: z.literal(true).optional(),
|
|
285
|
+
fields: z.record(udlFieldNameSchema, jsonObjectSchema),
|
|
286
|
+
id: udlSnakeCaseSchema,
|
|
287
|
+
idPrefix: z
|
|
288
|
+
.string()
|
|
289
|
+
.regex(idPrefixPattern, "must contain 2 to 8 lowercase letters"),
|
|
290
|
+
lifecycle: udlLifecycleSchema,
|
|
291
|
+
parties: z
|
|
292
|
+
.strictObject({
|
|
293
|
+
beneficiary: udlFieldNameSchema.optional(),
|
|
294
|
+
payer: udlFieldNameSchema.optional(),
|
|
295
|
+
subjectHolder: udlFieldNameSchema.optional(),
|
|
296
|
+
})
|
|
297
|
+
.optional(),
|
|
298
|
+
partitions: z.array(udlPartitionSchema).min(1).optional(),
|
|
299
|
+
required: z.array(udlFieldNameSchema),
|
|
300
|
+
subject: udlNounSubjectSchema.optional(),
|
|
301
|
+
summary: nonEmptyTextSchema,
|
|
302
|
+
title: nonEmptyTextSchema,
|
|
303
|
+
unwind: udlUnwindSchema.optional(),
|
|
304
|
+
update: udlUpdatePolicySchema.optional(),
|
|
305
|
+
verbs: z.record(udlVerbNameSchema, udlVerbSchema),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
export const udlDocumentSchema = z.strictObject({
|
|
309
|
+
nouns: z.array(udlNounSchema).min(1),
|
|
310
|
+
product: udlSnakeCaseSchema,
|
|
311
|
+
subjects: z.array(udlSubjectSchema),
|
|
312
|
+
title: nonEmptyTextSchema,
|
|
313
|
+
udl: z.literal(UDL_FORMAT_VERSION),
|
|
314
|
+
version: z.number().int().positive(),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
export type UdlAggregate = z.infer<typeof udlAggregateSchema>;
|
|
318
|
+
export type UdlAggregateCondition = z.infer<typeof udlAggregateConditionSchema>;
|
|
319
|
+
export type UdlBinding = z.infer<typeof udlBindingSchema>;
|
|
320
|
+
export type UdlDeclaredValuePolicy = z.infer<
|
|
321
|
+
typeof udlDeclaredValuePolicySchema
|
|
322
|
+
>;
|
|
323
|
+
export type UdlDecision = z.infer<typeof udlDecisionSchema>;
|
|
324
|
+
export type UdlDocument = z.infer<typeof udlDocumentSchema>;
|
|
325
|
+
export type UdlDue = z.infer<typeof udlDueSchema>;
|
|
326
|
+
export type UdlExample = z.infer<typeof udlExampleSchema>;
|
|
327
|
+
export type UdlGate = z.infer<typeof udlGateSchema>;
|
|
328
|
+
export type UdlKernelOperation = z.infer<typeof udlKernelOperationSchema>;
|
|
329
|
+
export type UdlLifecycle = z.infer<typeof udlLifecycleSchema>;
|
|
330
|
+
export type UdlLifecycleTransition = z.infer<
|
|
331
|
+
typeof udlLifecycleTransitionSchema
|
|
332
|
+
>;
|
|
333
|
+
export type UdlNoun = z.infer<typeof udlNounSchema>;
|
|
334
|
+
export type UdlNounSubject = z.infer<typeof udlNounSubjectSchema>;
|
|
335
|
+
export type UdlMove = z.infer<typeof udlMoveSchema>;
|
|
336
|
+
export type UdlStep = z.infer<typeof udlStepSchema>;
|
|
337
|
+
export type UdlSubject = z.infer<typeof udlSubjectSchema>;
|
|
338
|
+
export type UdlUnwind = z.infer<typeof udlUnwindSchema>;
|
|
339
|
+
export type UdlVerb = z.infer<typeof udlVerbSchema>;
|