@hyperscale0/udl 1.0.265 → 1.0.284
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 +34 -0
- package/README.md +1 -1
- package/SECURITY.md +15 -14
- package/dist/diagnostics.d.ts +7 -6
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/field-value.d.ts.map +1 -1
- package/dist/field-value.js +5 -6
- package/dist/field-value.js.map +1 -1
- package/dist/finance.d.ts +2 -6
- package/dist/finance.d.ts.map +1 -1
- package/dist/finance.js +20 -14
- package/dist/finance.js.map +1 -1
- package/dist/limits.d.ts +0 -1
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +0 -1
- package/dist/limits.js.map +1 -1
- package/dist/object-contract.d.ts +2 -2
- package/dist/object-contract.d.ts.map +1 -1
- package/dist/object-contract.js +4 -4
- package/dist/object-contract.js.map +1 -1
- package/dist/object-semantics-schema.d.ts +0 -10
- package/dist/object-semantics-schema.d.ts.map +1 -1
- package/dist/object-semantics.d.ts.map +1 -1
- package/dist/object-semantics.js +9 -9
- package/dist/object-semantics.js.map +1 -1
- package/dist/object-structures.d.ts.map +1 -1
- package/dist/object-structures.js +2 -3
- package/dist/object-structures.js.map +1 -1
- package/dist/primitives.d.ts +12 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +21 -0
- package/dist/primitives.js.map +1 -0
- package/dist/reporting-validation.d.ts.map +1 -1
- package/dist/reporting-validation.js +2 -1
- package/dist/reporting-validation.js.map +1 -1
- package/dist/reporting.d.ts +0 -5
- package/dist/reporting.d.ts.map +1 -1
- package/dist/reporting.js +0 -5
- package/dist/reporting.js.map +1 -1
- package/dist/schema.d.ts +1 -11
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -1
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +20 -43
- package/dist/validation.js.map +1 -1
- package/package.json +2 -2
- package/spec/README.md +24 -23
- package/spec/udl.schema.json +1 -36
- package/src/diagnostics.ts +7 -6
- package/src/field-value.ts +5 -6
- package/src/finance.ts +20 -22
- package/src/limits.ts +0 -1
- package/src/object-contract.ts +4 -10
- package/src/object-semantics.ts +14 -13
- package/src/object-structures.ts +2 -4
- package/src/primitives.ts +29 -0
- package/src/reporting-validation.ts +2 -1
- package/src/reporting.ts +0 -5
- package/src/schema.ts +2 -1
- package/src/validation.ts +22 -65
package/spec/README.md
CHANGED
|
@@ -24,22 +24,21 @@ A Build freezes this contract and its bindings for the executor.
|
|
|
24
24
|
Business instructions are actions composed from typed requirements and effects.
|
|
25
25
|
The four `internal_transfer` instructions are their money effects. The contract
|
|
26
26
|
expresses valid conditions; execution can still refuse for the current state,
|
|
27
|
-
authority, evidence or balance.
|
|
27
|
+
authority, evidence or balance. Validation executes nothing.
|
|
28
28
|
|
|
29
29
|
## JSON admission and canonical bytes
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
sparse arrays, symbol keys, hidden properties
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
`validateUdl` takes a decoded JSON value. The host reads and decodes document
|
|
32
|
+
text and owns its byte limit and duplicate member policy. Numbers must be safe
|
|
33
|
+
integers. Accessors, cycles, sparse arrays, symbol keys, hidden properties,
|
|
34
|
+
non-plain prototypes and `__proto__` members refuse before schema parsing. Shared
|
|
35
|
+
values count once per occurrence toward the budget. Admission permits at most
|
|
36
|
+
100,000 visited values, 32 nesting levels, 240 code units per key and 1 MiB of
|
|
37
|
+
UTF-8 key and string data. These bounds live in `src/limits.ts`.
|
|
38
38
|
|
|
39
39
|
Canonical JSON sorts object keys recursively, preserves array order and ends
|
|
40
40
|
with a newline. Schema defaults are part of the admitted document. Equivalent
|
|
41
|
-
key insertion orders and explicit defaults have the same digest.
|
|
42
|
-
compatibility and evolution comparisons use the same key ordering.
|
|
41
|
+
key insertion orders and explicit defaults have the same digest.
|
|
43
42
|
|
|
44
43
|
## Objects and subjects
|
|
45
44
|
|
|
@@ -66,12 +65,10 @@ Attachment parties resolve from authenticated authority, never from party accoun
|
|
|
66
65
|
IDs supplied in a caller body.
|
|
67
66
|
|
|
68
67
|
`object-contract.ts` projects `ObjectDiscovery` for
|
|
69
|
-
`GET /v1/products/{productId}/objects
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`fields` and `productBuildId`. Action responses carry the object and either a
|
|
74
|
-
financial instrument outcome or null.
|
|
68
|
+
`GET /v1/products/{productId}/objects` and exports the discovery and action
|
|
69
|
+
state types. Discovery preserves Build identity, authored field names, the full
|
|
70
|
+
optional create schema and each exposed action's requirements. The host defines
|
|
71
|
+
the object instance, list and action response types.
|
|
75
72
|
|
|
76
73
|
An object may declare `entryActions`, a list of exposed attachment creation
|
|
77
74
|
names. The host admits an active enrolled customer only to an entry action whose
|
|
@@ -82,8 +79,7 @@ participation never changes the owner.
|
|
|
82
79
|
An instrument may declare `scope: "product"`. Object creation can resolve a
|
|
83
80
|
reference to that instrument on another record in the same Product when every
|
|
84
81
|
retained party account matches the current bindings. Otherwise references stay
|
|
85
|
-
on the current record.
|
|
86
|
-
borrower; referencing them does not create a new limit.
|
|
82
|
+
on the current record.
|
|
87
83
|
|
|
88
84
|
## Object operations
|
|
89
85
|
|
|
@@ -144,7 +140,8 @@ These are ordinary paired moves in HSX, not executor loan rules.
|
|
|
144
140
|
A value is `{literal: value}` or `{field: path}`. Paths start with `self`, `input`, `subject`
|
|
145
141
|
or `party`. Reference fields allow typed traversal. Account paths expose locked,
|
|
146
142
|
read-only `.balance` and `.reserved` money values. `self.id`, `self.status`,
|
|
147
|
-
`self.createdAt` and `self.
|
|
143
|
+
`self.createdAt`, `self.now` and `self.productRevision` are sealed executor
|
|
144
|
+
values. Callers cannot set
|
|
148
145
|
constants, calculated fields, account bindings or capture fields. Create supplies
|
|
149
146
|
declared typed references; later actions cannot replace them. A ref declares `targetKind: "object" | "instrument"` and one target id or
|
|
150
147
|
a list of 1 to 16 distinct target ids in that namespace. Its stored value is an
|
|
@@ -161,9 +158,10 @@ Calculations form a finite dependency graph. `sum`, `subtract` and `minimum`
|
|
|
161
158
|
operate on money or integer fields, with operands of the same type as the target.
|
|
162
159
|
`rate`, `multiply`, `divide` and `shift` retain their typed operands. A shift's
|
|
163
160
|
`milliseconds` operand is a duration field or a non-negative safe integer literal.
|
|
164
|
-
Zero leaves the date unchanged; duration fields still require positive values. Rate
|
|
165
|
-
division round down.
|
|
166
|
-
|
|
161
|
+
Zero leaves the date unchanged; duration fields still require positive values. Rate,
|
|
162
|
+
ratio and division round down. UDL has no residual account. A program that
|
|
163
|
+
splits an amount computes its last share with `subtract`, so that share carries
|
|
164
|
+
the remainder. Cash and loss use this one rounding rule.
|
|
167
165
|
There is no largest-remainder allocation. Subtraction refuses a negative result. Integer results must
|
|
168
166
|
be safe integers. No calculation evaluates source text.
|
|
169
167
|
|
|
@@ -232,7 +230,10 @@ balances. An invariant holds before and after every affected transaction.
|
|
|
232
230
|
|
|
233
231
|
`hours` converts a date path to its literal IANA timezone and accepts `[start,end)`.
|
|
234
232
|
A start greater than end wraps midnight; equal endpoints admit no time.
|
|
235
|
-
`evidence` declares subject, family, check, result and maxAge.
|
|
233
|
+
`evidence` declares subject, family, check, result and maxAge. Optional `instruction`
|
|
234
|
+
is a path to a captured boundary instruction (HSX: `instruction self.receipt`).
|
|
235
|
+
Only instruction-bound requirements consume settlement evidence once; ordinary
|
|
236
|
+
identity evidence remains reusable. The subject is an
|
|
236
237
|
account or text id. The executor selects the newest matching subject binding,
|
|
237
238
|
checks its retained Build, protected input digest and adapter declaration, then
|
|
238
239
|
requires the declared result and age. A newer matching refusal or pending result
|
package/spec/udl.schema.json
CHANGED
|
@@ -1282,36 +1282,6 @@
|
|
|
1282
1282
|
"items": {
|
|
1283
1283
|
"$ref": "#/$defs/__schema40"
|
|
1284
1284
|
}
|
|
1285
|
-
},
|
|
1286
|
-
"lockTimeoutMs": {
|
|
1287
|
-
"type": "integer",
|
|
1288
|
-
"minimum": 1,
|
|
1289
|
-
"maximum": 2000,
|
|
1290
|
-
"$ref": "#/$defs/__schema29"
|
|
1291
|
-
},
|
|
1292
|
-
"captureTimeoutMs": {
|
|
1293
|
-
"type": "integer",
|
|
1294
|
-
"minimum": 1,
|
|
1295
|
-
"maximum": 10000,
|
|
1296
|
-
"$ref": "#/$defs/__schema29"
|
|
1297
|
-
},
|
|
1298
|
-
"maxRows": {
|
|
1299
|
-
"type": "integer",
|
|
1300
|
-
"minimum": 1,
|
|
1301
|
-
"maximum": 100000,
|
|
1302
|
-
"$ref": "#/$defs/__schema29"
|
|
1303
|
-
},
|
|
1304
|
-
"maxJoinRows": {
|
|
1305
|
-
"type": "integer",
|
|
1306
|
-
"minimum": 1,
|
|
1307
|
-
"maximum": 1000000,
|
|
1308
|
-
"$ref": "#/$defs/__schema29"
|
|
1309
|
-
},
|
|
1310
|
-
"maxBytes": {
|
|
1311
|
-
"type": "integer",
|
|
1312
|
-
"minimum": 1024,
|
|
1313
|
-
"maximum": 16777216,
|
|
1314
|
-
"$ref": "#/$defs/__schema29"
|
|
1315
1285
|
}
|
|
1316
1286
|
},
|
|
1317
1287
|
"required": [
|
|
@@ -1319,12 +1289,7 @@
|
|
|
1319
1289
|
"required",
|
|
1320
1290
|
"rowReconcile",
|
|
1321
1291
|
"reconcile",
|
|
1322
|
-
"unavailableFacts"
|
|
1323
|
-
"lockTimeoutMs",
|
|
1324
|
-
"captureTimeoutMs",
|
|
1325
|
-
"maxRows",
|
|
1326
|
-
"maxJoinRows",
|
|
1327
|
-
"maxBytes"
|
|
1292
|
+
"unavailableFacts"
|
|
1328
1293
|
],
|
|
1329
1294
|
"additionalProperties": false
|
|
1330
1295
|
},
|
package/src/diagnostics.ts
CHANGED
|
@@ -25,12 +25,13 @@ export interface UdlIssue {
|
|
|
25
25
|
message: string;
|
|
26
26
|
fix: string;
|
|
27
27
|
category: string;
|
|
28
|
-
stranded?:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
stranded?: Stranded;
|
|
29
|
+
}
|
|
30
|
+
export interface Stranded {
|
|
31
|
+
state: string;
|
|
32
|
+
accounts: string[];
|
|
33
|
+
actions: string[];
|
|
34
|
+
paths: Record<string, string[]>;
|
|
34
35
|
}
|
|
35
36
|
const fixes: Record<UdlIssueCode, string> = {
|
|
36
37
|
economic_party_unbound:
|
package/src/field-value.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { udlObjectIdSchema, type UdlField } from "./schema.js";
|
|
3
|
+
import { amount, integer, isMinorUnits, text } from "./primitives.js";
|
|
3
4
|
|
|
4
|
-
const amount = z.string().regex(/^(0|[1-9][0-9]{0,17})$/);
|
|
5
|
-
const text = z.string().min(1).max(2048);
|
|
6
5
|
// HSX dates are instants; say so when a caller sends a calendar date.
|
|
7
6
|
const dateTime = z.iso.datetime({
|
|
8
7
|
offset: true,
|
|
@@ -18,7 +17,7 @@ export function udlFieldValueSchema(field: UdlField): z.ZodType {
|
|
|
18
17
|
schema = amount
|
|
19
18
|
.refine(
|
|
20
19
|
(value) =>
|
|
21
|
-
|
|
20
|
+
isMinorUnits(value) &&
|
|
22
21
|
(field.minimum === undefined ||
|
|
23
22
|
BigInt(value) >= BigInt(field.minimum)) &&
|
|
24
23
|
(field.maximum === undefined ||
|
|
@@ -44,7 +43,7 @@ export function udlFieldValueSchema(field: UdlField): z.ZodType {
|
|
|
44
43
|
schema = dateTime;
|
|
45
44
|
break;
|
|
46
45
|
case "duration":
|
|
47
|
-
schema =
|
|
46
|
+
schema = integer.positive();
|
|
48
47
|
break;
|
|
49
48
|
case "text": {
|
|
50
49
|
let value = z
|
|
@@ -56,7 +55,7 @@ export function udlFieldValueSchema(field: UdlField): z.ZodType {
|
|
|
56
55
|
break;
|
|
57
56
|
}
|
|
58
57
|
case "integer": {
|
|
59
|
-
let value =
|
|
58
|
+
let value = integer;
|
|
60
59
|
if (field.minimum !== undefined) value = value.min(field.minimum);
|
|
61
60
|
if (field.maximum !== undefined) value = value.max(field.maximum);
|
|
62
61
|
schema = value;
|
|
@@ -78,7 +77,7 @@ export function udlFieldValueSchema(field: UdlField): z.ZodType {
|
|
|
78
77
|
: field.item === "date"
|
|
79
78
|
? dateTime
|
|
80
79
|
: field.item === "integer"
|
|
81
|
-
?
|
|
80
|
+
? integer
|
|
82
81
|
: field.item === "ref" && field.targetKind === "object"
|
|
83
82
|
? udlObjectIdSchema
|
|
84
83
|
: text;
|
package/src/finance.ts
CHANGED
|
@@ -4,16 +4,13 @@ import type {
|
|
|
4
4
|
UdlInstrument,
|
|
5
5
|
UdlValue,
|
|
6
6
|
} from "./schema.js";
|
|
7
|
+
import { targetIds } from "./primitives.js";
|
|
8
|
+
import type { Stranded } from "./diagnostics.js";
|
|
7
9
|
|
|
8
10
|
export interface FinanceIssue {
|
|
9
11
|
path: string;
|
|
10
12
|
message: string;
|
|
11
|
-
stranded?:
|
|
12
|
-
state: string;
|
|
13
|
-
accounts: string[];
|
|
14
|
-
actions: string[];
|
|
15
|
-
paths: Record<string, string[]>;
|
|
16
|
-
};
|
|
13
|
+
stranded?: Stranded;
|
|
17
14
|
}
|
|
18
15
|
type Sum = Map<string, bigint>;
|
|
19
16
|
type Balance = Sum | null;
|
|
@@ -59,6 +56,12 @@ export function analyzeInstrumentFinance(
|
|
|
59
56
|
.filter((f) => f.type === "account" && f.owner === "self")
|
|
60
57
|
.map((f) => `self.${f.name}`),
|
|
61
58
|
);
|
|
59
|
+
/** The owned account a `.balance` read names; this pass tracks no other account. */
|
|
60
|
+
const ownedAccount = (field: string): string | undefined => {
|
|
61
|
+
if (!field.endsWith(".balance")) return undefined;
|
|
62
|
+
const account = field.slice(0, -".balance".length);
|
|
63
|
+
return owned.has(account) ? account : undefined;
|
|
64
|
+
};
|
|
62
65
|
const shared = new Set<string>();
|
|
63
66
|
for (const owner of document?.instruments ?? []) {
|
|
64
67
|
if (owner.id === instrument.id) continue;
|
|
@@ -76,10 +79,7 @@ export function analyzeInstrumentFinance(
|
|
|
76
79
|
(field) => field.name === key,
|
|
77
80
|
);
|
|
78
81
|
if (reference?.type !== "ref") return [];
|
|
79
|
-
const ids =
|
|
80
|
-
typeof reference.target === "string"
|
|
81
|
-
? [reference.target]
|
|
82
|
-
: reference.target;
|
|
82
|
+
const ids = targetIds(reference.target);
|
|
83
83
|
return (
|
|
84
84
|
document?.instruments.filter((candidate) =>
|
|
85
85
|
ids.includes(candidate.id),
|
|
@@ -219,19 +219,21 @@ export function analyzeInstrumentFinance(
|
|
|
219
219
|
...downstream((field) => declared.has(field)),
|
|
220
220
|
]);
|
|
221
221
|
let reading = 0;
|
|
222
|
+
// The pass keeps no reserved total per account, so a reserved read is external even on an owned account.
|
|
222
223
|
const external = (field: string) =>
|
|
223
|
-
|
|
224
|
+
field.endsWith(".reserved") ||
|
|
225
|
+
(field.endsWith(".balance") && ownedAccount(field) === undefined);
|
|
224
226
|
const resolve = (sum: Sum): Balance => {
|
|
225
227
|
let result: Balance = new Map();
|
|
226
228
|
for (const [key, coefficient] of sum) {
|
|
227
|
-
const account = key
|
|
229
|
+
const account = ownedAccount(key);
|
|
228
230
|
const scoped =
|
|
229
231
|
balanceBound.has(key) ||
|
|
230
232
|
fresh.has(key) ||
|
|
231
233
|
live.has(key) ||
|
|
232
234
|
external(key);
|
|
233
235
|
const term: Balance =
|
|
234
|
-
|
|
236
|
+
account !== undefined
|
|
235
237
|
? (start.get(account) ?? null)
|
|
236
238
|
: new Map([[scoped ? `${scope}:${key}` : key, 1n]]);
|
|
237
239
|
result = add(result, term, coefficient);
|
|
@@ -245,11 +247,8 @@ export function analyzeInstrumentFinance(
|
|
|
245
247
|
const amount = (value: UdlValue, seen = new Set<string>()): Balance => {
|
|
246
248
|
if ("field" in value) {
|
|
247
249
|
if (calculated.has(value.field)) return calculated.get(value.field)!;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
owned.has(value.field.slice(0, -8))
|
|
251
|
-
)
|
|
252
|
-
return state.balances.get(value.field.slice(0, -8)) ?? null;
|
|
250
|
+
const account = ownedAccount(value.field);
|
|
251
|
+
if (account !== undefined) return state.balances.get(account) ?? null;
|
|
253
252
|
// Another account's balance moves between reads, so each read is its own symbol.
|
|
254
253
|
if (external(value.field))
|
|
255
254
|
return new Map([[`${scope}:${reading++}:${value.field}`, 1n]]);
|
|
@@ -277,10 +276,9 @@ export function analyzeInstrumentFinance(
|
|
|
277
276
|
[requirement.left, requirement.right],
|
|
278
277
|
[requirement.right, requirement.left],
|
|
279
278
|
] as const) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
279
|
+
const account =
|
|
280
|
+
"field" in left ? ownedAccount(left.field) : undefined;
|
|
281
|
+
if (account !== undefined) state.balances.set(account, amount(right));
|
|
284
282
|
}
|
|
285
283
|
}
|
|
286
284
|
for (const key of local.keys()) calculated.set(key, amount({ field: key }));
|
package/src/limits.ts
CHANGED
package/src/object-contract.ts
CHANGED
|
@@ -461,10 +461,9 @@ function requirementPossible(
|
|
|
461
461
|
export function objectActionState(
|
|
462
462
|
action: ObjectActionDiscovery,
|
|
463
463
|
fields: Readonly<Record<string, unknown>>,
|
|
464
|
-
options
|
|
464
|
+
options: ObjectActionStateOptions = {},
|
|
465
465
|
): ObjectActionState {
|
|
466
|
-
const attached =
|
|
467
|
-
typeof options === "boolean" ? options : (options?.attached ?? false);
|
|
466
|
+
const attached = options.attached ?? false;
|
|
468
467
|
const creationFilter = new Set(action.creationOnlyNames ?? []);
|
|
469
468
|
return {
|
|
470
469
|
...action,
|
|
@@ -477,12 +476,7 @@ export function objectActionState(
|
|
|
477
476
|
: undefined;
|
|
478
477
|
if (
|
|
479
478
|
conditions &&
|
|
480
|
-
!requirementPossible(
|
|
481
|
-
conditions,
|
|
482
|
-
action,
|
|
483
|
-
fields,
|
|
484
|
-
typeof options === "object" ? options : {},
|
|
485
|
-
)
|
|
479
|
+
!requirementPossible(conditions, action, fields, options)
|
|
486
480
|
)
|
|
487
481
|
return false;
|
|
488
482
|
const value = fields[field.name];
|
|
@@ -512,7 +506,7 @@ export function validateObjectActionSubject(
|
|
|
512
506
|
action: ObjectActionDiscovery,
|
|
513
507
|
storedFields: Readonly<Record<string, unknown>>,
|
|
514
508
|
submittedFields: Readonly<Record<string, unknown>>,
|
|
515
|
-
options
|
|
509
|
+
options: ObjectActionStateOptions = {},
|
|
516
510
|
): readonly SubjectRequirementIssue[] {
|
|
517
511
|
const origin = `${action.instrument}.${action.action}.subject`;
|
|
518
512
|
const issues: SubjectRequirementIssue[] = [];
|
package/src/object-semantics.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type UdlAction,
|
|
7
7
|
} from "./schema.js";
|
|
8
8
|
import { resolveField } from "./validation.js";
|
|
9
|
+
import { own, targetIds } from "./primitives.js";
|
|
9
10
|
import type {
|
|
10
11
|
DocumentSemantics,
|
|
11
12
|
SemanticAccount,
|
|
@@ -40,8 +41,6 @@ const labelled = <T extends UdlField>(field: T): T => ({
|
|
|
40
41
|
...field,
|
|
41
42
|
label: field.label ?? presentationLabel(field.name),
|
|
42
43
|
});
|
|
43
|
-
const targets = (target: string | string[]) =>
|
|
44
|
-
typeof target === "string" ? [target] : target;
|
|
45
44
|
|
|
46
45
|
function owner(
|
|
47
46
|
document: UdlDocument,
|
|
@@ -52,11 +51,12 @@ function owner(
|
|
|
52
51
|
return { kind: "adapter", adapter: value.adapter };
|
|
53
52
|
if (value === "self")
|
|
54
53
|
return { kind: "instrument", instrument: instrument.id };
|
|
55
|
-
const binding =
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
const binding = own(
|
|
55
|
+
document.objects
|
|
56
|
+
.flatMap((object) => object.attachments)
|
|
57
|
+
.find((attachment) => attachment.instrument === instrument.id)?.parties,
|
|
58
|
+
value,
|
|
59
|
+
);
|
|
60
60
|
if (binding)
|
|
61
61
|
return "role" in binding
|
|
62
62
|
? { kind: "role", role: binding.role }
|
|
@@ -82,7 +82,7 @@ function scopes(
|
|
|
82
82
|
field.targetKind === "instrument"
|
|
83
83
|
)
|
|
84
84
|
return document.instruments
|
|
85
|
-
.filter((candidate) =>
|
|
85
|
+
.filter((candidate) => targetIds(field.target).includes(candidate.id))
|
|
86
86
|
.flatMap((candidate) =>
|
|
87
87
|
scopes(document, candidate, `self.${rest.join(".")}`),
|
|
88
88
|
);
|
|
@@ -200,7 +200,7 @@ function relationships(
|
|
|
200
200
|
via: "field" as const,
|
|
201
201
|
name: field.name,
|
|
202
202
|
targetKind: field.targetKind,
|
|
203
|
-
targets:
|
|
203
|
+
targets: targetIds(field.target),
|
|
204
204
|
cardinality: { min: field.optional ? 0 : 1, max: 1 },
|
|
205
205
|
},
|
|
206
206
|
];
|
|
@@ -252,7 +252,7 @@ function relationships(
|
|
|
252
252
|
(action) =>
|
|
253
253
|
typeof action.actor === "object" &&
|
|
254
254
|
"parent" in action.actor &&
|
|
255
|
-
|
|
255
|
+
targetIds(action.actor.parent).includes(instrument.id),
|
|
256
256
|
);
|
|
257
257
|
if (
|
|
258
258
|
parentActor &&
|
|
@@ -260,7 +260,7 @@ function relationships(
|
|
|
260
260
|
(field) =>
|
|
261
261
|
field.type === "ref" &&
|
|
262
262
|
field.targetKind === "instrument" &&
|
|
263
|
-
|
|
263
|
+
targetIds(field.target).includes(instrument.id),
|
|
264
264
|
)
|
|
265
265
|
)
|
|
266
266
|
references.push({
|
|
@@ -292,7 +292,7 @@ function projectAction(
|
|
|
292
292
|
? { kind: actor }
|
|
293
293
|
: "party" in actor
|
|
294
294
|
? owner(document, instrument, actor.party)
|
|
295
|
-
: { kind: "parent", instruments:
|
|
295
|
+
: { kind: "parent", instruments: targetIds(actor.parent) },
|
|
296
296
|
],
|
|
297
297
|
from: transition?.from ?? [],
|
|
298
298
|
to:
|
|
@@ -360,7 +360,8 @@ export function projectDocumentSemantics(
|
|
|
360
360
|
initial: instrument.lifecycle.initial,
|
|
361
361
|
states: instrument.lifecycle.states.map((name) => ({
|
|
362
362
|
name,
|
|
363
|
-
label:
|
|
363
|
+
label:
|
|
364
|
+
own(instrument.lifecycle.labels, name) ?? presentationLabel(name),
|
|
364
365
|
terminal: isTerminalState(instrument, name),
|
|
365
366
|
})),
|
|
366
367
|
cancellationActions: [],
|
package/src/object-structures.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
SemanticAction,
|
|
4
4
|
SemanticInstrument,
|
|
5
5
|
} from "./object-semantics-schema.js";
|
|
6
|
+
import { targetIds } from "./primitives.js";
|
|
6
7
|
|
|
7
8
|
const hasMoney = (instrument: SemanticInstrument) =>
|
|
8
9
|
instrument.actions.some((action) => action.moneyEffects.length > 0);
|
|
@@ -149,10 +150,7 @@ export function projectStructures(
|
|
|
149
150
|
);
|
|
150
151
|
const funding = contributions.some((selection) => {
|
|
151
152
|
if (selection.anchor !== "self.id") return false;
|
|
152
|
-
const ids =
|
|
153
|
-
typeof selection.instrument === "string"
|
|
154
|
-
? [selection.instrument]
|
|
155
|
-
: selection.instrument;
|
|
153
|
+
const ids = targetIds(selection.instrument);
|
|
156
154
|
const children = instruments.filter(
|
|
157
155
|
(candidate) =>
|
|
158
156
|
ids.includes(candidate.id) &&
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
const minorUnits = /^(0|[1-9][0-9]{0,17})$/;
|
|
4
|
+
|
|
5
|
+
export const name = z
|
|
6
|
+
.string()
|
|
7
|
+
.regex(/^[a-z][a-zA-Z0-9_]*$/)
|
|
8
|
+
.max(80);
|
|
9
|
+
export const text = z.string().min(1).max(2048);
|
|
10
|
+
export const amount = z.string().regex(minorUnits);
|
|
11
|
+
export const integer = z.number().int().safe();
|
|
12
|
+
export const path = z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^[a-z][a-zA-Z0-9_]*(?:\.[a-z][a-zA-Z0-9_]*)*$/)
|
|
15
|
+
.max(240);
|
|
16
|
+
|
|
17
|
+
/** True for a money amount in minor units. Guard every BigInt() of an unchecked amount with it. */
|
|
18
|
+
export const isMinorUnits = (value: string): boolean => minorUnits.test(value);
|
|
19
|
+
|
|
20
|
+
/** Reads only the record's own keys, so names like `constructor` never reach the prototype. */
|
|
21
|
+
export function own<T>(
|
|
22
|
+
record: Record<string, T> | undefined,
|
|
23
|
+
key: string,
|
|
24
|
+
): T | undefined {
|
|
25
|
+
return record && Object.hasOwn(record, key) ? record[key] : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const targetIds = (target: string | string[]): string[] =>
|
|
29
|
+
typeof target === "string" ? [target] : target;
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ReportExpression,
|
|
5
5
|
ReportType,
|
|
6
6
|
} from "./reporting.js";
|
|
7
|
+
import { isMinorUnits } from "./primitives.js";
|
|
7
8
|
|
|
8
9
|
type Types = Map<string, ReportType>;
|
|
9
10
|
const sealedFields = new Map([
|
|
@@ -49,7 +50,7 @@ export function reportExpressionTypes(
|
|
|
49
50
|
const value = expression.value;
|
|
50
51
|
requireRule(
|
|
51
52
|
result.kind === "money"
|
|
52
|
-
? typeof value === "string" &&
|
|
53
|
+
? typeof value === "string" && isMinorUnits(value)
|
|
53
54
|
: result.kind === "integer"
|
|
54
55
|
? typeof value === "number" && Number.isSafeInteger(value)
|
|
55
56
|
: result.kind === "boolean"
|
package/src/reporting.ts
CHANGED
|
@@ -144,11 +144,6 @@ export const reportDefinitionSchema = z.strictObject({
|
|
|
144
144
|
rowReconcile: z.array(z.strictObject({ left: name, right: name })).max(16),
|
|
145
145
|
reconcile: z.array(z.strictObject({ left: name, right: name })).max(16),
|
|
146
146
|
unavailableFacts: z.array(z.string().min(1).max(240)).max(32),
|
|
147
|
-
lockTimeoutMs: integer.min(1).max(2000),
|
|
148
|
-
captureTimeoutMs: integer.min(1).max(10000),
|
|
149
|
-
maxRows: integer.min(1).max(100000),
|
|
150
|
-
maxJoinRows: integer.min(1).max(1000000),
|
|
151
|
-
maxBytes: integer.min(1024).max(16777216),
|
|
152
147
|
}),
|
|
153
148
|
output: z.strictObject({
|
|
154
149
|
profile: z.literal("internal.v1"),
|
package/src/schema.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { writeJson } from "./json.js";
|
|
|
5
5
|
|
|
6
6
|
export const UDL_FORMAT_VERSION = 1 as const;
|
|
7
7
|
/** Counts the root and every nested invocation, including selected and ranged children. */
|
|
8
|
-
export const MAX_ACTION_EXPANSION =
|
|
8
|
+
export const MAX_ACTION_EXPANSION = 8192;
|
|
9
9
|
const name = z
|
|
10
10
|
.string()
|
|
11
11
|
.regex(/^[a-z][a-zA-Z0-9_]*$/)
|
|
@@ -454,6 +454,7 @@ export const udlActionSchema = z.strictObject({
|
|
|
454
454
|
summary: text,
|
|
455
455
|
title: text.optional(),
|
|
456
456
|
publicAction: name.optional(),
|
|
457
|
+
// Retained Builds keep this key; execution uses MAX_ACTION_EXPANSION.
|
|
457
458
|
expansionLimit: z.literal(8192).optional(),
|
|
458
459
|
reminder: z
|
|
459
460
|
.strictObject({
|