@hyperscale0/udl 4.2.0 → 4.3.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 +14 -0
- package/dist/object-contract.d.ts +11 -7
- package/dist/object-contract.d.ts.map +1 -1
- package/dist/object-contract.js +95 -5
- package/dist/object-contract.js.map +1 -1
- package/dist/schema.d.ts +183 -6
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +27 -12
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +7 -1
- package/dist/validation.js.map +1 -1
- package/docs/README.md +8 -15
- package/package.json +2 -2
- package/spec/README.md +8 -15
- package/spec/udl.schema.json +96 -50
- package/src/object-contract.ts +135 -18
- package/src/schema.ts +34 -13
- package/src/validation.ts +6 -1
package/spec/udl.schema.json
CHANGED
|
@@ -149,6 +149,9 @@
|
|
|
149
149
|
"name": {
|
|
150
150
|
"$ref": "#/$defs/__schema1"
|
|
151
151
|
},
|
|
152
|
+
"parent": {
|
|
153
|
+
"$ref": "#/$defs/__schema1"
|
|
154
|
+
},
|
|
152
155
|
"instrument": {
|
|
153
156
|
"$ref": "#/$defs/__schema6"
|
|
154
157
|
},
|
|
@@ -911,7 +914,7 @@
|
|
|
911
914
|
"maxItems": 16,
|
|
912
915
|
"type": "array",
|
|
913
916
|
"items": {
|
|
914
|
-
"$ref": "#/$defs/
|
|
917
|
+
"$ref": "#/$defs/__schema50"
|
|
915
918
|
}
|
|
916
919
|
}
|
|
917
920
|
},
|
|
@@ -932,14 +935,14 @@
|
|
|
932
935
|
"maxItems": 128,
|
|
933
936
|
"type": "array",
|
|
934
937
|
"items": {
|
|
935
|
-
"$ref": "#/$defs/
|
|
938
|
+
"$ref": "#/$defs/__schema51"
|
|
936
939
|
}
|
|
937
940
|
},
|
|
938
941
|
"due": {
|
|
939
|
-
"$ref": "#/$defs/
|
|
942
|
+
"$ref": "#/$defs/__schema52"
|
|
940
943
|
},
|
|
941
944
|
"deadline": {
|
|
942
|
-
"$ref": "#/$defs/
|
|
945
|
+
"$ref": "#/$defs/__schema52"
|
|
943
946
|
},
|
|
944
947
|
"set": {
|
|
945
948
|
"type": "object",
|
|
@@ -961,14 +964,14 @@
|
|
|
961
964
|
"maxItems": 256,
|
|
962
965
|
"type": "array",
|
|
963
966
|
"items": {
|
|
964
|
-
"$ref": "#/$defs/
|
|
967
|
+
"$ref": "#/$defs/__schema53"
|
|
965
968
|
}
|
|
966
969
|
},
|
|
967
970
|
"invoke": {
|
|
968
971
|
"maxItems": 16,
|
|
969
972
|
"type": "array",
|
|
970
973
|
"items": {
|
|
971
|
-
"$ref": "#/$defs/
|
|
974
|
+
"$ref": "#/$defs/__schema54"
|
|
972
975
|
}
|
|
973
976
|
}
|
|
974
977
|
},
|
|
@@ -995,14 +998,14 @@
|
|
|
995
998
|
"maxItems": 128,
|
|
996
999
|
"type": "array",
|
|
997
1000
|
"items": {
|
|
998
|
-
"$ref": "#/$defs/
|
|
1001
|
+
"$ref": "#/$defs/__schema51"
|
|
999
1002
|
}
|
|
1000
1003
|
},
|
|
1001
1004
|
"examples": {
|
|
1002
1005
|
"maxItems": 16,
|
|
1003
1006
|
"type": "array",
|
|
1004
1007
|
"items": {
|
|
1005
|
-
"$ref": "#/$defs/
|
|
1008
|
+
"$ref": "#/$defs/__schema55"
|
|
1006
1009
|
}
|
|
1007
1010
|
}
|
|
1008
1011
|
},
|
|
@@ -2403,6 +2406,14 @@
|
|
|
2403
2406
|
},
|
|
2404
2407
|
"objectField": {
|
|
2405
2408
|
"$ref": "#/$defs/__schema1"
|
|
2409
|
+
},
|
|
2410
|
+
"when": {
|
|
2411
|
+
"minItems": 1,
|
|
2412
|
+
"maxItems": 128,
|
|
2413
|
+
"type": "array",
|
|
2414
|
+
"items": {
|
|
2415
|
+
"$ref": "#/$defs/__schema47"
|
|
2416
|
+
}
|
|
2406
2417
|
}
|
|
2407
2418
|
},
|
|
2408
2419
|
"required": [
|
|
@@ -2411,6 +2422,74 @@
|
|
|
2411
2422
|
"additionalProperties": false
|
|
2412
2423
|
},
|
|
2413
2424
|
"__schema47": {
|
|
2425
|
+
"minItems": 1,
|
|
2426
|
+
"maxItems": 32,
|
|
2427
|
+
"type": "array",
|
|
2428
|
+
"items": {
|
|
2429
|
+
"$ref": "#/$defs/__schema48"
|
|
2430
|
+
}
|
|
2431
|
+
},
|
|
2432
|
+
"__schema48": {
|
|
2433
|
+
"type": "object",
|
|
2434
|
+
"properties": {
|
|
2435
|
+
"instrument": {
|
|
2436
|
+
"$ref": "#/$defs/__schema6"
|
|
2437
|
+
},
|
|
2438
|
+
"action": {
|
|
2439
|
+
"$ref": "#/$defs/__schema1"
|
|
2440
|
+
},
|
|
2441
|
+
"guard": {
|
|
2442
|
+
"$ref": "#/$defs/__schema49"
|
|
2443
|
+
},
|
|
2444
|
+
"valueType": {
|
|
2445
|
+
"type": "string",
|
|
2446
|
+
"enum": [
|
|
2447
|
+
"money",
|
|
2448
|
+
"date"
|
|
2449
|
+
]
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
"required": [
|
|
2453
|
+
"instrument",
|
|
2454
|
+
"action",
|
|
2455
|
+
"guard"
|
|
2456
|
+
],
|
|
2457
|
+
"additionalProperties": false
|
|
2458
|
+
},
|
|
2459
|
+
"__schema49": {
|
|
2460
|
+
"type": "object",
|
|
2461
|
+
"properties": {
|
|
2462
|
+
"kind": {
|
|
2463
|
+
"type": "string",
|
|
2464
|
+
"const": "compare"
|
|
2465
|
+
},
|
|
2466
|
+
"left": {
|
|
2467
|
+
"$ref": "#/$defs/__schema43"
|
|
2468
|
+
},
|
|
2469
|
+
"operator": {
|
|
2470
|
+
"type": "string",
|
|
2471
|
+
"enum": [
|
|
2472
|
+
"==",
|
|
2473
|
+
"!=",
|
|
2474
|
+
"<",
|
|
2475
|
+
"<=",
|
|
2476
|
+
">",
|
|
2477
|
+
">="
|
|
2478
|
+
]
|
|
2479
|
+
},
|
|
2480
|
+
"right": {
|
|
2481
|
+
"$ref": "#/$defs/__schema43"
|
|
2482
|
+
}
|
|
2483
|
+
},
|
|
2484
|
+
"required": [
|
|
2485
|
+
"kind",
|
|
2486
|
+
"left",
|
|
2487
|
+
"operator",
|
|
2488
|
+
"right"
|
|
2489
|
+
],
|
|
2490
|
+
"additionalProperties": false
|
|
2491
|
+
},
|
|
2492
|
+
"__schema50": {
|
|
2414
2493
|
"type": "object",
|
|
2415
2494
|
"properties": {
|
|
2416
2495
|
"binding": {
|
|
@@ -2472,7 +2551,7 @@
|
|
|
2472
2551
|
],
|
|
2473
2552
|
"additionalProperties": false
|
|
2474
2553
|
},
|
|
2475
|
-
"
|
|
2554
|
+
"__schema51": {
|
|
2476
2555
|
"oneOf": [
|
|
2477
2556
|
{
|
|
2478
2557
|
"type": "object",
|
|
@@ -2656,40 +2735,7 @@
|
|
|
2656
2735
|
}
|
|
2657
2736
|
]
|
|
2658
2737
|
},
|
|
2659
|
-
"
|
|
2660
|
-
"type": "object",
|
|
2661
|
-
"properties": {
|
|
2662
|
-
"kind": {
|
|
2663
|
-
"type": "string",
|
|
2664
|
-
"const": "compare"
|
|
2665
|
-
},
|
|
2666
|
-
"left": {
|
|
2667
|
-
"$ref": "#/$defs/__schema43"
|
|
2668
|
-
},
|
|
2669
|
-
"operator": {
|
|
2670
|
-
"type": "string",
|
|
2671
|
-
"enum": [
|
|
2672
|
-
"==",
|
|
2673
|
-
"!=",
|
|
2674
|
-
"<",
|
|
2675
|
-
"<=",
|
|
2676
|
-
">",
|
|
2677
|
-
">="
|
|
2678
|
-
]
|
|
2679
|
-
},
|
|
2680
|
-
"right": {
|
|
2681
|
-
"$ref": "#/$defs/__schema43"
|
|
2682
|
-
}
|
|
2683
|
-
},
|
|
2684
|
-
"required": [
|
|
2685
|
-
"kind",
|
|
2686
|
-
"left",
|
|
2687
|
-
"operator",
|
|
2688
|
-
"right"
|
|
2689
|
-
],
|
|
2690
|
-
"additionalProperties": false
|
|
2691
|
-
},
|
|
2692
|
-
"__schema50": {
|
|
2738
|
+
"__schema52": {
|
|
2693
2739
|
"type": "object",
|
|
2694
2740
|
"properties": {
|
|
2695
2741
|
"at": {
|
|
@@ -2738,7 +2784,7 @@
|
|
|
2738
2784
|
],
|
|
2739
2785
|
"additionalProperties": false
|
|
2740
2786
|
},
|
|
2741
|
-
"
|
|
2787
|
+
"__schema53": {
|
|
2742
2788
|
"oneOf": [
|
|
2743
2789
|
{
|
|
2744
2790
|
"type": "object",
|
|
@@ -2867,7 +2913,7 @@
|
|
|
2867
2913
|
}
|
|
2868
2914
|
]
|
|
2869
2915
|
},
|
|
2870
|
-
"
|
|
2916
|
+
"__schema54": {
|
|
2871
2917
|
"anyOf": [
|
|
2872
2918
|
{
|
|
2873
2919
|
"type": "object",
|
|
@@ -2982,14 +3028,14 @@
|
|
|
2982
3028
|
}
|
|
2983
3029
|
]
|
|
2984
3030
|
},
|
|
2985
|
-
"
|
|
3031
|
+
"__schema55": {
|
|
2986
3032
|
"type": "object",
|
|
2987
3033
|
"properties": {
|
|
2988
3034
|
"name": {
|
|
2989
3035
|
"$ref": "#/$defs/__schema2"
|
|
2990
3036
|
},
|
|
2991
3037
|
"input": {
|
|
2992
|
-
"$ref": "#/$defs/
|
|
3038
|
+
"$ref": "#/$defs/__schema56"
|
|
2993
3039
|
}
|
|
2994
3040
|
},
|
|
2995
3041
|
"required": [
|
|
@@ -2998,7 +3044,7 @@
|
|
|
2998
3044
|
],
|
|
2999
3045
|
"additionalProperties": false
|
|
3000
3046
|
},
|
|
3001
|
-
"
|
|
3047
|
+
"__schema56": {
|
|
3002
3048
|
"anyOf": [
|
|
3003
3049
|
{
|
|
3004
3050
|
"type": "string"
|
|
@@ -3015,7 +3061,7 @@
|
|
|
3015
3061
|
{
|
|
3016
3062
|
"type": "array",
|
|
3017
3063
|
"items": {
|
|
3018
|
-
"$ref": "#/$defs/
|
|
3064
|
+
"$ref": "#/$defs/__schema56"
|
|
3019
3065
|
}
|
|
3020
3066
|
},
|
|
3021
3067
|
{
|
|
@@ -3024,7 +3070,7 @@
|
|
|
3024
3070
|
"type": "string"
|
|
3025
3071
|
},
|
|
3026
3072
|
"additionalProperties": {
|
|
3027
|
-
"$ref": "#/$defs/
|
|
3073
|
+
"$ref": "#/$defs/__schema56"
|
|
3028
3074
|
}
|
|
3029
3075
|
}
|
|
3030
3076
|
]
|
package/src/object-contract.ts
CHANGED
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
type UdlField,
|
|
7
7
|
type UdlObjectField,
|
|
8
8
|
type UdlObjectKind,
|
|
9
|
+
type UdlSubjectRequirement,
|
|
10
|
+
type UdlValue,
|
|
11
|
+
type UdlInstrument,
|
|
9
12
|
} from "./schema.js";
|
|
10
13
|
|
|
11
14
|
export const requestAttributionSchema = z.union([
|
|
@@ -58,6 +61,10 @@ export interface ObjectActionDiscovery extends ObjectActionContext {
|
|
|
58
61
|
instrument: string;
|
|
59
62
|
action: string;
|
|
60
63
|
requirements: readonly UdlObjectField[];
|
|
64
|
+
requirementConditions?: Record<
|
|
65
|
+
string,
|
|
66
|
+
NonNullable<UdlSubjectRequirement["when"]>
|
|
67
|
+
>;
|
|
61
68
|
creationRequirements?: readonly UdlObjectField[];
|
|
62
69
|
creationOnlyNames?: readonly string[];
|
|
63
70
|
inputSchema: JsonSchemaDocument;
|
|
@@ -275,33 +282,36 @@ export function projectObjectDiscovery(
|
|
|
275
282
|
const isCreationAction =
|
|
276
283
|
name === "create" || (createHidden && canStartFromInitial);
|
|
277
284
|
|
|
278
|
-
const rawCreateReqs:
|
|
279
|
-
field: UdlField;
|
|
280
|
-
objectField?: string | undefined;
|
|
281
|
-
}[] = isCreationAction
|
|
285
|
+
const rawCreateReqs: UdlSubjectRequirement[] = isCreationAction
|
|
282
286
|
? [
|
|
283
287
|
...(instrument.actions.create?.subject?.requirements ?? []),
|
|
284
|
-
...attachmentRefListFields.map((field) => ({
|
|
288
|
+
...attachmentRefListFields.map((field) => ({
|
|
289
|
+
field: field as UdlObjectField,
|
|
290
|
+
})),
|
|
285
291
|
]
|
|
286
292
|
: [];
|
|
287
293
|
const actionReqs = action.subject?.requirements ?? [];
|
|
288
294
|
const actionReqNames = new Set(
|
|
289
295
|
actionReqs.map((r) => r.objectField ?? r.field.name),
|
|
290
296
|
);
|
|
291
|
-
const mergedReqs:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}[] = [...actionReqs];
|
|
297
|
+
const mergedReqs: UdlSubjectRequirement[] = actionReqs.map(
|
|
298
|
+
(requirement) => ({ ...requirement }),
|
|
299
|
+
);
|
|
295
300
|
for (const req of rawCreateReqs) {
|
|
296
301
|
const reqName = req.objectField ?? req.field.name;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
const existing = mergedReqs.find(
|
|
303
|
+
(r) => (r.objectField ?? r.field.name) === reqName,
|
|
304
|
+
);
|
|
305
|
+
if (!existing) mergedReqs.push({ ...req });
|
|
306
|
+
else if (!req.when) delete existing.when;
|
|
307
|
+
else if (existing.when)
|
|
308
|
+
existing.when = [...existing.when, ...req.when];
|
|
304
309
|
}
|
|
310
|
+
const requirementConditions = Object.fromEntries(
|
|
311
|
+
mergedReqs.flatMap((req) =>
|
|
312
|
+
req.when ? [[req.objectField ?? req.field.name, req.when]] : [],
|
|
313
|
+
),
|
|
314
|
+
);
|
|
305
315
|
const unbound = [
|
|
306
316
|
...(action.subject?.adapters ?? []),
|
|
307
317
|
...(isCreationAction
|
|
@@ -346,6 +356,9 @@ export function projectObjectDiscovery(
|
|
|
346
356
|
name: requirement.objectField ?? requirement.field.name,
|
|
347
357
|
}) as UdlObjectField,
|
|
348
358
|
),
|
|
359
|
+
...(Object.keys(requirementConditions).length
|
|
360
|
+
? { requirementConditions }
|
|
361
|
+
: {}),
|
|
349
362
|
...(creationRequirements.length > 0
|
|
350
363
|
? { creationRequirements }
|
|
351
364
|
: {}),
|
|
@@ -391,11 +404,104 @@ export function projectObjectDiscovery(
|
|
|
391
404
|
};
|
|
392
405
|
}
|
|
393
406
|
|
|
407
|
+
/** Values overwritten before invocation cannot decide a guard during discovery. */
|
|
408
|
+
export function objectActionSelf(
|
|
409
|
+
instrument: UdlInstrument,
|
|
410
|
+
actionName: string,
|
|
411
|
+
fields: Readonly<Record<string, unknown>> = {},
|
|
412
|
+
): Record<string, unknown> {
|
|
413
|
+
const action = instrument.actions[actionName]!;
|
|
414
|
+
const known = {
|
|
415
|
+
...Object.fromEntries(
|
|
416
|
+
instrument.fields.flatMap((field) =>
|
|
417
|
+
"value" in field ? [[field.name, field.value]] : [],
|
|
418
|
+
),
|
|
419
|
+
),
|
|
420
|
+
...fields,
|
|
421
|
+
};
|
|
422
|
+
for (const calculation of [
|
|
423
|
+
...instrument.calculate,
|
|
424
|
+
...(action.calculate ?? []),
|
|
425
|
+
])
|
|
426
|
+
delete known[calculation.target];
|
|
427
|
+
for (const name of Object.keys(action.set ?? {})) delete known[name];
|
|
428
|
+
return known;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export interface ObjectActionStateOptions {
|
|
432
|
+
attached?: boolean;
|
|
433
|
+
self?: Readonly<Record<string, unknown>>;
|
|
434
|
+
input?: Readonly<Record<string, unknown>>;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Unknown invocation state remains possible; a known false guard suppresses a prompt. */
|
|
438
|
+
function requirementPossible(
|
|
439
|
+
paths: NonNullable<UdlSubjectRequirement["when"]>,
|
|
440
|
+
action: ObjectActionDiscovery,
|
|
441
|
+
fields: Readonly<Record<string, unknown>>,
|
|
442
|
+
options: ObjectActionStateOptions,
|
|
443
|
+
): boolean {
|
|
444
|
+
return paths.some((path) =>
|
|
445
|
+
path.every((condition) => {
|
|
446
|
+
const read = (value: UdlValue): unknown => {
|
|
447
|
+
if ("literal" in value) return value.literal;
|
|
448
|
+
const [root, name, ...tail] = value.field.split(".");
|
|
449
|
+
if (!name || tail.length) return undefined;
|
|
450
|
+
if (root === "subject") return fields[name];
|
|
451
|
+
if (
|
|
452
|
+
condition.instrument !== action.instrument ||
|
|
453
|
+
condition.action !== action.action
|
|
454
|
+
)
|
|
455
|
+
return undefined;
|
|
456
|
+
if (root === "self") return options.self?.[name];
|
|
457
|
+
if (root === "input") return options.input?.[name];
|
|
458
|
+
return undefined;
|
|
459
|
+
};
|
|
460
|
+
let left = read(condition.guard.left);
|
|
461
|
+
let right = read(condition.guard.right);
|
|
462
|
+
if (left === undefined || right === undefined) return true;
|
|
463
|
+
try {
|
|
464
|
+
if (condition.valueType === "money") {
|
|
465
|
+
left = BigInt(String(left));
|
|
466
|
+
right = BigInt(String(right));
|
|
467
|
+
} else if (condition.valueType === "date") {
|
|
468
|
+
left = Date.parse(String(left));
|
|
469
|
+
right = Date.parse(String(right));
|
|
470
|
+
if (!Number.isFinite(left) || !Number.isFinite(right)) return true;
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
if (
|
|
476
|
+
typeof left !== typeof right ||
|
|
477
|
+
!["string", "number", "boolean", "bigint"].includes(typeof left)
|
|
478
|
+
)
|
|
479
|
+
return true;
|
|
480
|
+
const a = left as string | number | bigint;
|
|
481
|
+
const b = right as string | number | bigint;
|
|
482
|
+
switch (condition.guard.operator) {
|
|
483
|
+
case "==":
|
|
484
|
+
return left === right;
|
|
485
|
+
case "!=":
|
|
486
|
+
return left !== right;
|
|
487
|
+
case "<":
|
|
488
|
+
return a < b;
|
|
489
|
+
case "<=":
|
|
490
|
+
return a <= b;
|
|
491
|
+
case ">":
|
|
492
|
+
return a > b;
|
|
493
|
+
case ">=":
|
|
494
|
+
return a >= b;
|
|
495
|
+
}
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
394
500
|
/** Object-specific values determine missing fields; global discovery cannot. */
|
|
395
501
|
export function objectActionState(
|
|
396
502
|
action: ObjectActionDiscovery,
|
|
397
503
|
fields: Readonly<Record<string, unknown>>,
|
|
398
|
-
options?:
|
|
504
|
+
options?: ObjectActionStateOptions | boolean,
|
|
399
505
|
): ObjectActionState {
|
|
400
506
|
const attached =
|
|
401
507
|
typeof options === "boolean" ? options : (options?.attached ?? false);
|
|
@@ -404,6 +510,17 @@ export function objectActionState(
|
|
|
404
510
|
...action,
|
|
405
511
|
requiredNow: action.requirements
|
|
406
512
|
.filter((field) => {
|
|
513
|
+
const conditions = action.requirementConditions?.[field.name];
|
|
514
|
+
if (
|
|
515
|
+
conditions &&
|
|
516
|
+
!requirementPossible(
|
|
517
|
+
conditions,
|
|
518
|
+
action,
|
|
519
|
+
fields,
|
|
520
|
+
typeof options === "object" ? options : {},
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
return false;
|
|
407
524
|
const value = fields[field.name];
|
|
408
525
|
const optional =
|
|
409
526
|
field.optional || (attached && creationFilter.has(field.name));
|
|
@@ -431,7 +548,7 @@ export function validateObjectActionSubject(
|
|
|
431
548
|
action: ObjectActionDiscovery,
|
|
432
549
|
storedFields: Readonly<Record<string, unknown>>,
|
|
433
550
|
submittedFields: Readonly<Record<string, unknown>>,
|
|
434
|
-
options?:
|
|
551
|
+
options?: ObjectActionStateOptions | boolean,
|
|
435
552
|
): readonly SubjectRequirementIssue[] {
|
|
436
553
|
const origin = `${action.instrument}.${action.action}.subject`;
|
|
437
554
|
const issues: SubjectRequirementIssue[] = [];
|
package/src/schema.ts
CHANGED
|
@@ -154,10 +154,43 @@ export const udlObjectFieldSchema = z.discriminatedUnion("type", [
|
|
|
154
154
|
boundedListField,
|
|
155
155
|
]);
|
|
156
156
|
|
|
157
|
+
/** Values are constants or resolved typed paths, never executable strings. */
|
|
158
|
+
export const udlValueSchema = z.union([
|
|
159
|
+
z.strictObject({ literal: scalar }),
|
|
160
|
+
z.strictObject({ field: path }),
|
|
161
|
+
]);
|
|
162
|
+
const value = udlValueSchema;
|
|
163
|
+
|
|
164
|
+
const comparisonRequirement = z.strictObject({
|
|
165
|
+
kind: z.literal("compare"),
|
|
166
|
+
left: value,
|
|
167
|
+
operator: z.enum(["==", "!=", "<", "<=", ">", ">="]),
|
|
168
|
+
right: value,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Alternatives are OR; each invocation path is a conjunction of scoped guards.
|
|
172
|
+
export const udlSubjectConditionsSchema = z
|
|
173
|
+
.array(
|
|
174
|
+
z
|
|
175
|
+
.array(
|
|
176
|
+
z.strictObject({
|
|
177
|
+
instrument: instrumentId,
|
|
178
|
+
action: name,
|
|
179
|
+
guard: comparisonRequirement,
|
|
180
|
+
valueType: z.enum(["money", "date"]).optional(),
|
|
181
|
+
}),
|
|
182
|
+
)
|
|
183
|
+
.min(1)
|
|
184
|
+
.max(32),
|
|
185
|
+
)
|
|
186
|
+
.min(1)
|
|
187
|
+
.max(128);
|
|
188
|
+
|
|
157
189
|
export const udlSubjectRequirementSchema = z.strictObject({
|
|
158
190
|
field: udlObjectFieldSchema,
|
|
159
191
|
// Present only for an authored rename.
|
|
160
192
|
objectField: name.optional(),
|
|
193
|
+
when: udlSubjectConditionsSchema.optional(),
|
|
161
194
|
});
|
|
162
195
|
|
|
163
196
|
export const adapterSubjectSnapshotSchema = z.strictObject({
|
|
@@ -192,6 +225,7 @@ export type AttachmentPartyBinding = z.infer<
|
|
|
192
225
|
>;
|
|
193
226
|
export const udlObjectAttachmentSchema = z.strictObject({
|
|
194
227
|
name,
|
|
228
|
+
parent: name.optional(),
|
|
195
229
|
instrument: instrumentId,
|
|
196
230
|
parties: z.record(name, attachmentPartyBindingSchema),
|
|
197
231
|
});
|
|
@@ -207,12 +241,6 @@ export const udlObjectKindSchema = z.strictObject({
|
|
|
207
241
|
columns: z.array(name).max(8),
|
|
208
242
|
});
|
|
209
243
|
|
|
210
|
-
/** Values are constants or resolved typed paths, never executable strings. */
|
|
211
|
-
export const udlValueSchema = z.union([
|
|
212
|
-
z.strictObject({ literal: scalar }),
|
|
213
|
-
z.strictObject({ field: path }),
|
|
214
|
-
]);
|
|
215
|
-
const value = udlValueSchema;
|
|
216
244
|
const values = z.array(value).min(1).max(256);
|
|
217
245
|
|
|
218
246
|
const selection = z.strictObject({
|
|
@@ -295,13 +323,6 @@ export const udlCalculationSchema = z.discriminatedUnion("op", [
|
|
|
295
323
|
}),
|
|
296
324
|
]);
|
|
297
325
|
|
|
298
|
-
const comparisonRequirement = z.strictObject({
|
|
299
|
-
kind: z.literal("compare"),
|
|
300
|
-
left: value,
|
|
301
|
-
operator: z.enum(["==", "!=", "<", "<=", ">", ">="]),
|
|
302
|
-
right: value,
|
|
303
|
-
});
|
|
304
|
-
|
|
305
326
|
export const udlRequirementSchema = z.discriminatedUnion("kind", [
|
|
306
327
|
z.strictObject({
|
|
307
328
|
kind: z.literal("hours"),
|
package/src/validation.ts
CHANGED
|
@@ -315,7 +315,12 @@ function resolvePath(
|
|
|
315
315
|
}
|
|
316
316
|
if (root === "subject") {
|
|
317
317
|
const requirement = resolveSubjectRequirement(instrument, key, action);
|
|
318
|
-
if (!requirement
|
|
318
|
+
if (!requirement) return;
|
|
319
|
+
// Evidence collected at the action need not exist on the object; the
|
|
320
|
+
// declared requirement is the contract for a bare `subject.<name>`.
|
|
321
|
+
if (parts.length === 0 && !requirement.objectField)
|
|
322
|
+
return requirement.field;
|
|
323
|
+
if (!instrument.subject) return;
|
|
319
324
|
const object = document.objects.find(
|
|
320
325
|
(item) => item.id === instrument.subject,
|
|
321
326
|
);
|