@hyperscale0/udl 4.4.0 → 4.5.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 +25 -0
- package/README.md +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +1 -29
- package/dist/canonical.js.map +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +9 -6
- package/dist/diagnostics.js.map +1 -1
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +6 -16
- package/dist/evolution.js.map +1 -1
- package/dist/json.d.ts +2 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +30 -0
- package/dist/json.js.map +1 -0
- package/dist/limits.d.ts +3 -30
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -31
- package/dist/limits.js.map +1 -1
- package/dist/object-contract.d.ts +1 -0
- package/dist/object-contract.d.ts.map +1 -1
- package/dist/object-contract.js +4 -1
- package/dist/object-contract.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +23 -0
- package/dist/parser.js.map +1 -1
- package/dist/reporting-validation.d.ts.map +1 -1
- package/dist/reporting-validation.js +12 -5
- package/dist/reporting-validation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -4
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +157 -43
- package/dist/validation.js.map +1 -1
- package/docs/README.md +48 -32
- package/package.json +2 -2
- package/spec/README.md +48 -32
- package/src/canonical.ts +1 -35
- package/src/diagnostics.ts +9 -7
- package/src/evolution.ts +8 -18
- package/src/json.ts +34 -0
- package/src/limits.ts +4 -31
- package/src/object-contract.ts +6 -1
- package/src/parser.ts +22 -0
- package/src/reporting-validation.ts +12 -10
- package/src/schema.ts +7 -10
- package/src/validation.ts +220 -47
package/src/validation.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import { issue, type UdlIssue } from "./diagnostics.js";
|
|
19
19
|
import { validateReportDefinition } from "./reporting-validation.js";
|
|
20
20
|
import { analyzeInstrumentFinance } from "./finance.js";
|
|
21
|
+
import { UDL_LIMITS } from "./limits.js";
|
|
22
|
+
import { udlFieldValueSchema } from "./object-contract.js";
|
|
21
23
|
|
|
22
24
|
export const RESERVED_OBJECT_NAMES = [
|
|
23
25
|
"objectId",
|
|
@@ -33,7 +35,7 @@ function hasParty(
|
|
|
33
35
|
instrument: UdlInstrument | undefined,
|
|
34
36
|
name: string,
|
|
35
37
|
): boolean {
|
|
36
|
-
if (document.parties
|
|
38
|
+
if (own(document.parties, name)) return true;
|
|
37
39
|
if (!instrument?.subject) return false;
|
|
38
40
|
return (
|
|
39
41
|
document.objects
|
|
@@ -56,9 +58,9 @@ function moneyParty(
|
|
|
56
58
|
): boolean {
|
|
57
59
|
return (
|
|
58
60
|
hasParty(document, instrument, name) &&
|
|
59
|
-
(document.parties
|
|
60
|
-
(!document.parties
|
|
61
|
-
(!instrument?.subject && document.parties
|
|
61
|
+
(own(document.parties, name)?.kind === "business" ||
|
|
62
|
+
(!own(document.parties, name) && !!instrument?.subject) ||
|
|
63
|
+
(!instrument?.subject && own(document.parties, name)?.kind === "person"))
|
|
62
64
|
);
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -72,34 +74,73 @@ export class UdlError extends Error {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
/** Reject
|
|
77
|
+
/** Reject non-JSON properties before either the schema or serializer reads them. */
|
|
76
78
|
function bounded(value: unknown): boolean {
|
|
77
79
|
const active = new Set<object>();
|
|
80
|
+
const encoder = new TextEncoder();
|
|
78
81
|
let nodes = 0;
|
|
79
82
|
let bytes = 0;
|
|
83
|
+
const string = (value: string): boolean => {
|
|
84
|
+
if (value.length > UDL_LIMITS.maxTotalStringLength) return false;
|
|
85
|
+
bytes += encoder.encode(value).byteLength;
|
|
86
|
+
return bytes <= UDL_LIMITS.maxTotalStringLength;
|
|
87
|
+
};
|
|
80
88
|
const visit = (v: unknown, depth: number): boolean => {
|
|
81
|
-
if (++nodes >
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return bytes <= 1048576;
|
|
85
|
-
}
|
|
89
|
+
if (++nodes > UDL_LIMITS.maxNodes || depth > UDL_LIMITS.maxDepth)
|
|
90
|
+
return false;
|
|
91
|
+
if (typeof v === "string") return string(v);
|
|
86
92
|
if (v === null || typeof v === "boolean") return true;
|
|
87
93
|
if (typeof v === "number") return Number.isSafeInteger(v);
|
|
88
94
|
if (typeof v !== "object" || active.has(v)) return false;
|
|
95
|
+
const array = Array.isArray(v);
|
|
89
96
|
if (
|
|
90
|
-
!
|
|
97
|
+
!array &&
|
|
91
98
|
Object.getPrototypeOf(v) !== Object.prototype &&
|
|
92
99
|
Object.getPrototypeOf(v) !== null
|
|
93
100
|
)
|
|
94
101
|
return false;
|
|
102
|
+
const keys = Reflect.ownKeys(v);
|
|
103
|
+
if (
|
|
104
|
+
array &&
|
|
105
|
+
(v.length > UDL_LIMITS.maxNodes || keys.length !== v.length + 1)
|
|
106
|
+
)
|
|
107
|
+
return false;
|
|
95
108
|
active.add(v);
|
|
96
|
-
const
|
|
97
|
-
(
|
|
98
|
-
|
|
109
|
+
for (const key of keys) {
|
|
110
|
+
if (array && key === "length") continue;
|
|
111
|
+
if (
|
|
112
|
+
typeof key !== "string" ||
|
|
113
|
+
key === "__proto__" ||
|
|
114
|
+
key.length > UDL_LIMITS.maxKeyLength ||
|
|
115
|
+
!string(key)
|
|
116
|
+
)
|
|
117
|
+
return false;
|
|
118
|
+
if (array && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= v.length))
|
|
119
|
+
return false;
|
|
120
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, key);
|
|
121
|
+
if (
|
|
122
|
+
!descriptor?.enumerable ||
|
|
123
|
+
!("value" in descriptor) ||
|
|
124
|
+
!visit(descriptor.value, depth + 1)
|
|
125
|
+
)
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
99
128
|
active.delete(v);
|
|
100
|
-
return
|
|
129
|
+
return true;
|
|
101
130
|
};
|
|
102
|
-
|
|
131
|
+
try {
|
|
132
|
+
return visit(value, 0);
|
|
133
|
+
} catch {
|
|
134
|
+
// Proxies may throw during reflection. They are not decoded JSON.
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function own<T>(
|
|
140
|
+
record: Record<string, T> | undefined,
|
|
141
|
+
key: string,
|
|
142
|
+
): T | undefined {
|
|
143
|
+
return record && Object.hasOwn(record, key) ? record[key] : undefined;
|
|
103
144
|
}
|
|
104
145
|
|
|
105
146
|
const targetIds = (target: string | string[]): string[] =>
|
|
@@ -422,7 +463,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
422
463
|
);
|
|
423
464
|
|
|
424
465
|
for (const name of subjectPartyRoles)
|
|
425
|
-
if (document.parties
|
|
466
|
+
if (own(document.parties, name))
|
|
426
467
|
add(
|
|
427
468
|
`$.parties.${name}`,
|
|
428
469
|
`${name} is a reserved subject role`,
|
|
@@ -503,6 +544,17 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
503
544
|
}
|
|
504
545
|
}
|
|
505
546
|
}
|
|
547
|
+
if (f.type === "text" && f.value !== undefined) {
|
|
548
|
+
try {
|
|
549
|
+
if (
|
|
550
|
+
!udlFieldValueSchema({ ...f, value: undefined }).safeParse(f.value)
|
|
551
|
+
.success
|
|
552
|
+
)
|
|
553
|
+
add(where, `${f.name} constant violates its text constraints`);
|
|
554
|
+
} catch {
|
|
555
|
+
// Invalid character classes already have a diagnostic above.
|
|
556
|
+
}
|
|
557
|
+
}
|
|
506
558
|
if (f.type === "ref") {
|
|
507
559
|
duplicate(targetIds(f.target), where);
|
|
508
560
|
for (const id of targetIds(f.target)) {
|
|
@@ -613,7 +665,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
613
665
|
);
|
|
614
666
|
for (const [attachmentIndex, attachment] of obj.attachments.entries()) {
|
|
615
667
|
for (const [parameter, binding] of Object.entries(attachment.parties))
|
|
616
|
-
if ("party" in binding && !document.parties
|
|
668
|
+
if ("party" in binding && !own(document.parties, binding.party))
|
|
617
669
|
add(
|
|
618
670
|
`${base}.attachments[${attachmentIndex}].parties.${parameter}`,
|
|
619
671
|
`unknown declared party ${binding.party}`,
|
|
@@ -666,10 +718,10 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
666
718
|
|
|
667
719
|
const reportIds = new Set<string>();
|
|
668
720
|
for (const [index, instrument] of document.instruments.entries()) {
|
|
669
|
-
for (const report of instrument.reports ?? []) {
|
|
670
|
-
const path = `$.instruments[${index}].reports`;
|
|
721
|
+
for (const [reportIndex, report] of (instrument.reports ?? []).entries()) {
|
|
722
|
+
const path = `$.instruments[${index}].reports[${reportIndex}]`;
|
|
671
723
|
if (reportIds.has(report.identity.id))
|
|
672
|
-
add(path, `duplicate report ${report.identity.id}
|
|
724
|
+
add(path, `duplicate report ${report.identity.id}`, "UDL2001");
|
|
673
725
|
reportIds.add(report.identity.id);
|
|
674
726
|
try {
|
|
675
727
|
validateReportDefinition(report, document);
|
|
@@ -687,6 +739,18 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
687
739
|
const base = `$.instruments[${index}]`;
|
|
688
740
|
if (inst.subject && !byObjectId.has(inst.subject))
|
|
689
741
|
add(base, `unknown subject object ${inst.subject}`);
|
|
742
|
+
if (
|
|
743
|
+
inst.subject &&
|
|
744
|
+
Object.values(inst.actions).some((action) => action.publicAction) &&
|
|
745
|
+
!byObjectId
|
|
746
|
+
.get(inst.subject)
|
|
747
|
+
?.attachments.some((attachment) => attachment.instrument === inst.id)
|
|
748
|
+
)
|
|
749
|
+
add(
|
|
750
|
+
`${base}.subject`,
|
|
751
|
+
"public subject actions require an object attachment",
|
|
752
|
+
"UDL5001",
|
|
753
|
+
);
|
|
690
754
|
const field = (
|
|
691
755
|
p: string,
|
|
692
756
|
input: readonly UdlField[] = [],
|
|
@@ -731,12 +795,34 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
731
795
|
: type === "integer" || type === "percent" || type === "duration"
|
|
732
796
|
? typeof v.literal === "number" &&
|
|
733
797
|
Number.isSafeInteger(v.literal) &&
|
|
734
|
-
(type !== "percent" || (v.literal >= 0 && v.literal <= 10000))
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
798
|
+
(type !== "percent" || (v.literal >= 0 && v.literal <= 10000)) &&
|
|
799
|
+
(type !== "duration" || v.literal > 0)
|
|
800
|
+
: type === "date"
|
|
801
|
+
? udlFieldValueSchema({
|
|
802
|
+
name: "literal",
|
|
803
|
+
type: "date",
|
|
804
|
+
}).safeParse(v.literal).success
|
|
805
|
+
: type === "boolean"
|
|
806
|
+
? typeof v.literal === "boolean"
|
|
807
|
+
: typeof v.literal === "string";
|
|
738
808
|
if (!valid) add(where, `literal must have type ${type}`);
|
|
739
809
|
};
|
|
810
|
+
const checkAssignment = (
|
|
811
|
+
value: UdlValue,
|
|
812
|
+
target: UdlField,
|
|
813
|
+
where: string,
|
|
814
|
+
action: UdlAction,
|
|
815
|
+
) => {
|
|
816
|
+
checkValue(value, target.type, where, action.input, action);
|
|
817
|
+
if ("literal" in value) {
|
|
818
|
+
try {
|
|
819
|
+
if (!udlFieldValueSchema(target).safeParse(value.literal).success)
|
|
820
|
+
add(where, `literal violates constraints of ${target.name}`);
|
|
821
|
+
} catch {
|
|
822
|
+
// Invalid field constraints are reported by checkFields.
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
};
|
|
740
826
|
checkFields(inst.fields, `${base}.fields`, false, inst);
|
|
741
827
|
const captures = new Set(
|
|
742
828
|
Object.values(inst.actions).flatMap((a) => [
|
|
@@ -856,7 +942,8 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
856
942
|
where,
|
|
857
943
|
);
|
|
858
944
|
const dependencies = new Map<string, string[]>();
|
|
859
|
-
for (const c of calculations) {
|
|
945
|
+
for (const [calculationIndex, c] of calculations.entries()) {
|
|
946
|
+
const calculationPath = `${where}[${calculationIndex}]`;
|
|
860
947
|
const arithmetic = ["sum", "subtract", "minimum", "divide"].includes(
|
|
861
948
|
c.op,
|
|
862
949
|
);
|
|
@@ -881,7 +968,18 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
881
968
|
checkValue(v, arithmetic ? resultType : "money", where, input, act);
|
|
882
969
|
};
|
|
883
970
|
if (c.op === "at") {
|
|
884
|
-
expect(c.list, "list", where, input, act);
|
|
971
|
+
const list = expect(c.list, "list", where, input, act);
|
|
972
|
+
if (
|
|
973
|
+
result?.type === "ref" &&
|
|
974
|
+
list?.type === "list" &&
|
|
975
|
+
(result.targetKind !== list.targetKind ||
|
|
976
|
+
result.target !== list.target)
|
|
977
|
+
)
|
|
978
|
+
add(
|
|
979
|
+
calculationPath,
|
|
980
|
+
"list extraction requires the same reference target",
|
|
981
|
+
"UDL5001",
|
|
982
|
+
);
|
|
885
983
|
checkValue(c.position, "integer", where, input, act);
|
|
886
984
|
if ("literal" in c.position && Number(c.position.literal) < 1)
|
|
887
985
|
add(where, "list positions start at one");
|
|
@@ -889,6 +987,10 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
889
987
|
}
|
|
890
988
|
if (c.op === "aggregate") {
|
|
891
989
|
const selected = checkSelection(c.selection, where, input);
|
|
990
|
+
operands.push(
|
|
991
|
+
{ field: c.selection.anchor },
|
|
992
|
+
...Object.values(c.selection.where ?? {}),
|
|
993
|
+
);
|
|
892
994
|
if (
|
|
893
995
|
c.measure !== "count" &&
|
|
894
996
|
(!selected ||
|
|
@@ -911,11 +1013,24 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
911
1013
|
checkValue(c.numerator, type, where, input, act);
|
|
912
1014
|
checkValue(c.denominator, type, where, input, act);
|
|
913
1015
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
)
|
|
918
|
-
|
|
1016
|
+
for (const [operand, positive] of [
|
|
1017
|
+
[c.numerator, false],
|
|
1018
|
+
[c.denominator, true],
|
|
1019
|
+
] as const) {
|
|
1020
|
+
if (
|
|
1021
|
+
"literal" in operand &&
|
|
1022
|
+
(typeof operand.literal === "number" ||
|
|
1023
|
+
(typeof operand.literal === "string" &&
|
|
1024
|
+
/^(0|[1-9][0-9]{0,17})$/.test(operand.literal))) &&
|
|
1025
|
+
(positive
|
|
1026
|
+
? BigInt(operand.literal) <= 0n
|
|
1027
|
+
: BigInt(operand.literal) < 0n)
|
|
1028
|
+
)
|
|
1029
|
+
add(
|
|
1030
|
+
calculationPath,
|
|
1031
|
+
"ratio numerator must be nonnegative and denominator positive",
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
919
1034
|
operands.push(c.numerator, c.denominator);
|
|
920
1035
|
}
|
|
921
1036
|
if (c.op === "sum" || c.op === "minimum") c.values.forEach(money);
|
|
@@ -946,7 +1061,19 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
946
1061
|
}
|
|
947
1062
|
if (c.op === "shift") {
|
|
948
1063
|
checkValue(c.date, "date", where, input, act);
|
|
949
|
-
checkValue(
|
|
1064
|
+
checkValue(
|
|
1065
|
+
c.milliseconds,
|
|
1066
|
+
"literal" in c.milliseconds ? "integer" : "duration",
|
|
1067
|
+
where,
|
|
1068
|
+
input,
|
|
1069
|
+
act,
|
|
1070
|
+
);
|
|
1071
|
+
if (
|
|
1072
|
+
"literal" in c.milliseconds &&
|
|
1073
|
+
typeof c.milliseconds.literal === "number" &&
|
|
1074
|
+
c.milliseconds.literal < 0
|
|
1075
|
+
)
|
|
1076
|
+
add(where, "shift milliseconds must be nonnegative");
|
|
950
1077
|
operands.push(c.date, c.milliseconds);
|
|
951
1078
|
}
|
|
952
1079
|
dependencies.set(
|
|
@@ -983,13 +1110,13 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
983
1110
|
duplicate(inst.actionOrder, `${base}.actionOrder`);
|
|
984
1111
|
if (
|
|
985
1112
|
inst.actionOrder.length !== Object.keys(inst.actions).length ||
|
|
986
|
-
inst.actionOrder.some((a) => !inst.actions
|
|
1113
|
+
inst.actionOrder.some((a) => !own(inst.actions, a))
|
|
987
1114
|
)
|
|
988
1115
|
add(base, "actionOrder must list every action once");
|
|
989
1116
|
const reachable = new Set([inst.lifecycle.initial]);
|
|
990
1117
|
for (const [action, edge] of Object.entries(inst.lifecycle.transitions)) {
|
|
991
1118
|
if (
|
|
992
|
-
!inst.actions
|
|
1119
|
+
!own(inst.actions, action) ||
|
|
993
1120
|
action === "create" ||
|
|
994
1121
|
(edge.to !== "preserve" && !states.has(edge.to)) ||
|
|
995
1122
|
edge.from.some((s) => !states.has(s))
|
|
@@ -1050,6 +1177,12 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1050
1177
|
}
|
|
1051
1178
|
} else if (req.kind === "state") {
|
|
1052
1179
|
const target = expect(req.reference, "ref", where, input, act);
|
|
1180
|
+
if (target?.type === "ref" && target.targetKind !== "instrument")
|
|
1181
|
+
add(
|
|
1182
|
+
where,
|
|
1183
|
+
"state gates require an instrument reference",
|
|
1184
|
+
"UDL5001",
|
|
1185
|
+
);
|
|
1053
1186
|
if (
|
|
1054
1187
|
target?.type === "ref" &&
|
|
1055
1188
|
target.targetKind === "instrument" &&
|
|
@@ -1115,7 +1248,10 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1115
1248
|
checkRequirements(inst.invariants, `${base}.invariants`);
|
|
1116
1249
|
for (const [actionName, action] of Object.entries(inst.actions)) {
|
|
1117
1250
|
const where = `${base}.actions.${actionName}`;
|
|
1118
|
-
if (
|
|
1251
|
+
if (
|
|
1252
|
+
actionName !== "create" &&
|
|
1253
|
+
!own(inst.lifecycle.transitions, actionName)
|
|
1254
|
+
)
|
|
1119
1255
|
add(where, "action needs a lifecycle transition", "UDL3001");
|
|
1120
1256
|
if (action.subject) {
|
|
1121
1257
|
const subBase = `${where}.subject`;
|
|
@@ -1127,7 +1263,25 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1127
1263
|
`${subBase}.requirements`,
|
|
1128
1264
|
true,
|
|
1129
1265
|
);
|
|
1130
|
-
|
|
1266
|
+
duplicate(
|
|
1267
|
+
action.subject.requirements.map(
|
|
1268
|
+
(requirement) => requirement.field.name,
|
|
1269
|
+
),
|
|
1270
|
+
`${subBase}.requirements`,
|
|
1271
|
+
);
|
|
1272
|
+
for (const [
|
|
1273
|
+
requirementIndex,
|
|
1274
|
+
req,
|
|
1275
|
+
] of action.subject.requirements.entries()) {
|
|
1276
|
+
for (const condition of (req.when ?? []).flat()) {
|
|
1277
|
+
const target = byId.get(condition.instrument);
|
|
1278
|
+
if (!own(target?.actions, condition.action))
|
|
1279
|
+
add(
|
|
1280
|
+
`${subBase}.requirements[${requirementIndex}].when`,
|
|
1281
|
+
"subject condition needs a declared instrument action",
|
|
1282
|
+
"UDL5001",
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1131
1285
|
if (req.field.optional)
|
|
1132
1286
|
add(
|
|
1133
1287
|
`${subBase}.requirements`,
|
|
@@ -1162,7 +1316,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1162
1316
|
);
|
|
1163
1317
|
for (const reqField of adapter.snapshot.requirements) {
|
|
1164
1318
|
const objectName =
|
|
1165
|
-
adapter.renames
|
|
1319
|
+
own(adapter.renames, reqField.name) ?? reqField.name;
|
|
1166
1320
|
const requirement = action.subject.requirements.find(
|
|
1167
1321
|
(item) =>
|
|
1168
1322
|
(item.objectField ?? item.field.name) === objectName &&
|
|
@@ -1293,13 +1447,32 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1293
1447
|
from.book !== to.book
|
|
1294
1448
|
)
|
|
1295
1449
|
add(where, "moves cannot cross account books", "UDL4001");
|
|
1450
|
+
const accountOwner = (
|
|
1451
|
+
field: Extract<UdlField, { type: "account" }>,
|
|
1452
|
+
path: string,
|
|
1453
|
+
): string => {
|
|
1454
|
+
if (typeof field.owner === "string") return `party:${field.owner}`;
|
|
1455
|
+
const binding = field.owner.adapter;
|
|
1456
|
+
const provider =
|
|
1457
|
+
path === `self.${field.name}`
|
|
1458
|
+
? Object.values(inst.actions)
|
|
1459
|
+
.flatMap((action) => action.subject?.adapters ?? [])
|
|
1460
|
+
.find(
|
|
1461
|
+
(entry) => entry.binding === binding && entry.snapshot,
|
|
1462
|
+
)?.snapshot?.provider
|
|
1463
|
+
: undefined;
|
|
1464
|
+
return provider ? `provider:${provider}` : `binding:${binding}`;
|
|
1465
|
+
};
|
|
1296
1466
|
const sameBoundAccount =
|
|
1297
1467
|
from?.type === "account" &&
|
|
1298
1468
|
to?.type === "account" &&
|
|
1299
|
-
from.owner !== "self"
|
|
1300
|
-
|
|
1469
|
+
(from.owner !== "self" ||
|
|
1470
|
+
(move.from === `self.${from.name}` &&
|
|
1471
|
+
move.to === `self.${to.name}`)) &&
|
|
1472
|
+
accountOwner(from, move.from) === accountOwner(to, move.to) &&
|
|
1301
1473
|
from.book === to.book &&
|
|
1302
|
-
(from.key ?? "
|
|
1474
|
+
(from.key ?? (from.owner === "self" ? from.name : "balance")) ===
|
|
1475
|
+
(to.key ?? (to.owner === "self" ? to.name : "balance"));
|
|
1303
1476
|
if (move.from === move.to || sameBoundAccount)
|
|
1304
1477
|
add(where, "a transfer needs distinct accounts", "UDL4001");
|
|
1305
1478
|
} else expect(move.transfer, "text", where, action.input, action);
|
|
@@ -1325,7 +1498,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1325
1498
|
inst.calculate.some((c) => c.target === key)
|
|
1326
1499
|
)
|
|
1327
1500
|
add(where, `cannot write immutable field ${key}`);
|
|
1328
|
-
else
|
|
1501
|
+
else checkAssignment(v, target, `${where}.set.${key}`, action);
|
|
1329
1502
|
}
|
|
1330
1503
|
const checkArguments = (
|
|
1331
1504
|
targetId: string | undefined,
|
|
@@ -1334,14 +1507,14 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1334
1507
|
binding?: string,
|
|
1335
1508
|
) => {
|
|
1336
1509
|
const target = targetId
|
|
1337
|
-
? byId.get(targetId)?.actions
|
|
1510
|
+
? own(byId.get(targetId)?.actions, actionName)
|
|
1338
1511
|
: undefined;
|
|
1339
1512
|
if (!target) return;
|
|
1340
1513
|
for (const required of target.input)
|
|
1341
1514
|
if (
|
|
1342
1515
|
!required.optional &&
|
|
1343
1516
|
!("value" in required && required.value !== undefined) &&
|
|
1344
|
-
!(required.name
|
|
1517
|
+
!Object.hasOwn(values, required.name)
|
|
1345
1518
|
)
|
|
1346
1519
|
add(where, `missing target input ${required.name}`);
|
|
1347
1520
|
for (const [name, value] of Object.entries(values)) {
|
|
@@ -1355,7 +1528,7 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1355
1528
|
add(where, "range binding requires an integer target input");
|
|
1356
1529
|
continue;
|
|
1357
1530
|
}
|
|
1358
|
-
|
|
1531
|
+
checkAssignment(value, declared, where, action);
|
|
1359
1532
|
if (declared.type === "ref" && "field" in value) {
|
|
1360
1533
|
const supplied = field(value.field, action.input, action);
|
|
1361
1534
|
if (
|
|
@@ -1414,10 +1587,10 @@ export function validateUdl(value: unknown): UdlValidationResult {
|
|
|
1414
1587
|
"invocation references an existing instance and cannot create it again",
|
|
1415
1588
|
);
|
|
1416
1589
|
const targetInst = byId.get(targetId);
|
|
1417
|
-
|
|
1590
|
+
const targetAction = own(targetInst?.actions, call.action);
|
|
1591
|
+
if (!targetInst || !targetAction)
|
|
1418
1592
|
add(where, "invocation target must name a declared action");
|
|
1419
1593
|
else if (!call.guard) {
|
|
1420
|
-
const targetAction = targetInst.actions[call.action]!;
|
|
1421
1594
|
if (
|
|
1422
1595
|
inst.subject &&
|
|
1423
1596
|
targetInst.subject &&
|