@hyperscale0/udl 4.2.1 → 4.4.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 +16 -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 +201 -12
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +28 -13
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +16 -4
- package/dist/validation.js.map +1 -1
- package/docs/README.md +30 -28
- package/package.json +2 -2
- package/spec/README.md +30 -28
- package/spec/udl.schema.json +113 -51
- package/src/object-contract.ts +135 -18
- package/src/schema.ts +35 -14
- package/src/validation.ts +28 -4
package/spec/README.md
CHANGED
|
@@ -8,8 +8,8 @@ lives in `src/schema.ts`. Generate `udl.schema.json` with
|
|
|
8
8
|
|
|
9
9
|
An object kind declares identity, title, authored field names, normalized fields
|
|
10
10
|
and display columns. Each attachment freezes its instrument identity and party
|
|
11
|
-
parameter bindings to `owner`, `actor`,
|
|
12
|
-
from the
|
|
11
|
+
parameter bindings to `owner`, `actor`, `operator`, or a declared Product party. Core resolves role bindings from the object row, session and Product, and
|
|
12
|
+
declared parties from the Product party binding. `authoredFields` records only names declared in the HSX
|
|
13
13
|
object block. `fields` is their union with attached action requirements. Matching
|
|
14
14
|
names must have matching types and constraints. Account fields belong to
|
|
15
15
|
instruments, never objects.
|
|
@@ -24,7 +24,7 @@ requirement is inferred from an operation name.
|
|
|
24
24
|
Object creation accepts `{}`. Its optional `fields` property accepts every
|
|
25
25
|
normalized field as optional. Requirements become mandatory only when the
|
|
26
26
|
attached action runs. Object creation performs no financial action. Instrument
|
|
27
|
-
creation remains an
|
|
27
|
+
creation remains internal; an attached create action may expose a public name.
|
|
28
28
|
Attachment parties resolve from authenticated authority, never from party account
|
|
29
29
|
IDs supplied in a caller body.
|
|
30
30
|
|
|
@@ -38,19 +38,12 @@ financial instrument outcome or null.
|
|
|
38
38
|
|
|
39
39
|
## Object operations
|
|
40
40
|
|
|
41
|
-
The Product
|
|
41
|
+
The [Product runtime guide](../../../docs/public/runtime.md) owns the six routes
|
|
42
|
+
for discovery, object creation, listing, retrieval, availability and execution.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
| POST | `/{kind}` | New object with optional metadata |
|
|
47
|
-
| GET | `/{kind}` | Cursor-paginated objects of a kind |
|
|
48
|
-
| GET | `/{kind}/{objectId}` | Object metadata, revision and Build ID |
|
|
49
|
-
| GET | `/{kind}/{objectId}/actions` | Availability and missing `requiredNow` fields |
|
|
50
|
-
| POST | `/{kind}/{objectId}/actions/{action}` | Updated object and optional instrument outcome |
|
|
51
|
-
|
|
52
|
-
Execution binds `productBuildId` and `expectedRevision`. The optional `fields`
|
|
53
|
-
collect subject metadata; optional `input` carries the action's typed input.
|
|
44
|
+
Execution binds `productBuildId`, `digest`, `target` and `expectedRevision`.
|
|
45
|
+
The target identifies an attachment or an existing instance on that attachment.
|
|
46
|
+
The optional `fields` collect subject metadata; optional `input` carries the action's typed input.
|
|
54
47
|
Missing requirements refuse execution. Callers reuse an idempotency key only for
|
|
55
48
|
retries of the same request. Staff Create for may name `ownerPrincipalId` through
|
|
56
49
|
its authorized path; ordinary ownership comes from the session.
|
|
@@ -61,19 +54,28 @@ A document declares SAR once. Money is a nonnegative minor-unit decimal string
|
|
|
61
54
|
of at most 18 digits. Percentages are basis points, durations are positive integer
|
|
62
55
|
milliseconds, and dates are timestamps with explicit offsets.
|
|
63
56
|
|
|
64
|
-
An account field
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
An account field binds a party, the instrument itself, or an adapter. Party owners
|
|
58
|
+
are names; `owner: "self"` provisions an account per instance. An adapter owner
|
|
59
|
+
is `{ "adapter": "insurer" }`, where `insurer` names an adapter declared in one
|
|
60
|
+
of the instrument's action subjects. The agreement retains that declaration's
|
|
61
|
+
provider identity. Missing or conflicting bindings refuse with
|
|
62
|
+
`subject_adapter_unbound`; changing an existing account binding refuses with
|
|
63
|
+
`account_binding_changed`. A null declaration can describe a draft but cannot
|
|
64
|
+
create an adapter-owned account.
|
|
65
|
+
|
|
66
|
+
Account creation and binding are executor work. Callers never supply account IDs.
|
|
70
67
|
Accounts declare `book: "cash" | "claim"`, defaulting to cash. Moves never cross books.
|
|
71
68
|
Only claim accounts may declare `contra: true` and permit a negative balance.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
Party and adapter accounts share owner, book and key within a Product; the key
|
|
70
|
+
defaults to `balance`. Adapter aliases for the same provider share that account.
|
|
71
|
+
The executor scopes provider accounts to the tenant and Product, with the Product
|
|
72
|
+
participant as ledger custodian. Providers are not parties. An account owned by
|
|
73
|
+
self defaults its key to the field name. Named capital, premium, income, debt and
|
|
74
|
+
loss balances use explicit keys. `party.buyer` is the buyer's default cash account.
|
|
75
|
+
|
|
76
|
+
A plain move to an adapter account credits its ledger balance. It does not prove
|
|
77
|
+
an external payout. Provider confirmation still uses a boundary reservation and
|
|
78
|
+
instruction-bound evidence. Account ownership adds no settlement operation.
|
|
77
79
|
|
|
78
80
|
Disbursement moves cash from lender to borrower and claims from borrower debt to
|
|
79
81
|
principal and profit receivables. Repayment moves borrower cash to lender cash and
|
|
@@ -91,7 +93,7 @@ declared typed references; later actions cannot replace them. A ref declares `ta
|
|
|
91
93
|
a list of 1 to 16 distinct target ids in that namespace. Its stored value is an
|
|
92
94
|
identity from one listed target. Path traversal exposes only fields
|
|
93
95
|
with compatible types on every target. Nested refs combine their target sets;
|
|
94
|
-
accounts must agree on owner, key, book
|
|
96
|
+
accounts must agree on owner, key, book and contra flags, and enums must
|
|
95
97
|
have the same values. A comparison or selection anchor must share a possible
|
|
96
98
|
reference target. An invoked input must accept every possible supplied target.
|
|
97
99
|
The executor checks the actual instance type at admission.
|
|
@@ -111,7 +113,7 @@ The only move instructions are `internal_transfer.create`,
|
|
|
111
113
|
Create and reserve declare an amount, from account and to account. Reserve captures
|
|
112
114
|
its executor-produced transfer identity into a declared self text field. Post and
|
|
113
115
|
void consume that identity. Callers cannot create or replace captured identities.
|
|
114
|
-
|
|
116
|
+
Adapter-owned endpoints use the same move vocabulary.
|
|
115
117
|
|
|
116
118
|
A loan, refund, payoff, write-off or distribution is library behavior built from
|
|
117
119
|
accounts, calculations and ordered moves. None has a privileged executor clause.
|
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
|
},
|
|
@@ -1919,7 +1922,23 @@
|
|
|
1919
1922
|
"const": "account"
|
|
1920
1923
|
},
|
|
1921
1924
|
"owner": {
|
|
1922
|
-
"
|
|
1925
|
+
"anyOf": [
|
|
1926
|
+
{
|
|
1927
|
+
"$ref": "#/$defs/__schema1"
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
"type": "object",
|
|
1931
|
+
"properties": {
|
|
1932
|
+
"adapter": {
|
|
1933
|
+
"$ref": "#/$defs/__schema1"
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
"required": [
|
|
1937
|
+
"adapter"
|
|
1938
|
+
],
|
|
1939
|
+
"additionalProperties": false
|
|
1940
|
+
}
|
|
1941
|
+
]
|
|
1923
1942
|
},
|
|
1924
1943
|
"key": {
|
|
1925
1944
|
"$ref": "#/$defs/__schema1"
|
|
@@ -2403,6 +2422,14 @@
|
|
|
2403
2422
|
},
|
|
2404
2423
|
"objectField": {
|
|
2405
2424
|
"$ref": "#/$defs/__schema1"
|
|
2425
|
+
},
|
|
2426
|
+
"when": {
|
|
2427
|
+
"minItems": 1,
|
|
2428
|
+
"maxItems": 128,
|
|
2429
|
+
"type": "array",
|
|
2430
|
+
"items": {
|
|
2431
|
+
"$ref": "#/$defs/__schema47"
|
|
2432
|
+
}
|
|
2406
2433
|
}
|
|
2407
2434
|
},
|
|
2408
2435
|
"required": [
|
|
@@ -2411,6 +2438,74 @@
|
|
|
2411
2438
|
"additionalProperties": false
|
|
2412
2439
|
},
|
|
2413
2440
|
"__schema47": {
|
|
2441
|
+
"minItems": 1,
|
|
2442
|
+
"maxItems": 32,
|
|
2443
|
+
"type": "array",
|
|
2444
|
+
"items": {
|
|
2445
|
+
"$ref": "#/$defs/__schema48"
|
|
2446
|
+
}
|
|
2447
|
+
},
|
|
2448
|
+
"__schema48": {
|
|
2449
|
+
"type": "object",
|
|
2450
|
+
"properties": {
|
|
2451
|
+
"instrument": {
|
|
2452
|
+
"$ref": "#/$defs/__schema6"
|
|
2453
|
+
},
|
|
2454
|
+
"action": {
|
|
2455
|
+
"$ref": "#/$defs/__schema1"
|
|
2456
|
+
},
|
|
2457
|
+
"guard": {
|
|
2458
|
+
"$ref": "#/$defs/__schema49"
|
|
2459
|
+
},
|
|
2460
|
+
"valueType": {
|
|
2461
|
+
"type": "string",
|
|
2462
|
+
"enum": [
|
|
2463
|
+
"money",
|
|
2464
|
+
"date"
|
|
2465
|
+
]
|
|
2466
|
+
}
|
|
2467
|
+
},
|
|
2468
|
+
"required": [
|
|
2469
|
+
"instrument",
|
|
2470
|
+
"action",
|
|
2471
|
+
"guard"
|
|
2472
|
+
],
|
|
2473
|
+
"additionalProperties": false
|
|
2474
|
+
},
|
|
2475
|
+
"__schema49": {
|
|
2476
|
+
"type": "object",
|
|
2477
|
+
"properties": {
|
|
2478
|
+
"kind": {
|
|
2479
|
+
"type": "string",
|
|
2480
|
+
"const": "compare"
|
|
2481
|
+
},
|
|
2482
|
+
"left": {
|
|
2483
|
+
"$ref": "#/$defs/__schema43"
|
|
2484
|
+
},
|
|
2485
|
+
"operator": {
|
|
2486
|
+
"type": "string",
|
|
2487
|
+
"enum": [
|
|
2488
|
+
"==",
|
|
2489
|
+
"!=",
|
|
2490
|
+
"<",
|
|
2491
|
+
"<=",
|
|
2492
|
+
">",
|
|
2493
|
+
">="
|
|
2494
|
+
]
|
|
2495
|
+
},
|
|
2496
|
+
"right": {
|
|
2497
|
+
"$ref": "#/$defs/__schema43"
|
|
2498
|
+
}
|
|
2499
|
+
},
|
|
2500
|
+
"required": [
|
|
2501
|
+
"kind",
|
|
2502
|
+
"left",
|
|
2503
|
+
"operator",
|
|
2504
|
+
"right"
|
|
2505
|
+
],
|
|
2506
|
+
"additionalProperties": false
|
|
2507
|
+
},
|
|
2508
|
+
"__schema50": {
|
|
2414
2509
|
"type": "object",
|
|
2415
2510
|
"properties": {
|
|
2416
2511
|
"binding": {
|
|
@@ -2472,7 +2567,7 @@
|
|
|
2472
2567
|
],
|
|
2473
2568
|
"additionalProperties": false
|
|
2474
2569
|
},
|
|
2475
|
-
"
|
|
2570
|
+
"__schema51": {
|
|
2476
2571
|
"oneOf": [
|
|
2477
2572
|
{
|
|
2478
2573
|
"type": "object",
|
|
@@ -2656,40 +2751,7 @@
|
|
|
2656
2751
|
}
|
|
2657
2752
|
]
|
|
2658
2753
|
},
|
|
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": {
|
|
2754
|
+
"__schema52": {
|
|
2693
2755
|
"type": "object",
|
|
2694
2756
|
"properties": {
|
|
2695
2757
|
"at": {
|
|
@@ -2738,7 +2800,7 @@
|
|
|
2738
2800
|
],
|
|
2739
2801
|
"additionalProperties": false
|
|
2740
2802
|
},
|
|
2741
|
-
"
|
|
2803
|
+
"__schema53": {
|
|
2742
2804
|
"oneOf": [
|
|
2743
2805
|
{
|
|
2744
2806
|
"type": "object",
|
|
@@ -2867,7 +2929,7 @@
|
|
|
2867
2929
|
}
|
|
2868
2930
|
]
|
|
2869
2931
|
},
|
|
2870
|
-
"
|
|
2932
|
+
"__schema54": {
|
|
2871
2933
|
"anyOf": [
|
|
2872
2934
|
{
|
|
2873
2935
|
"type": "object",
|
|
@@ -2982,14 +3044,14 @@
|
|
|
2982
3044
|
}
|
|
2983
3045
|
]
|
|
2984
3046
|
},
|
|
2985
|
-
"
|
|
3047
|
+
"__schema55": {
|
|
2986
3048
|
"type": "object",
|
|
2987
3049
|
"properties": {
|
|
2988
3050
|
"name": {
|
|
2989
3051
|
"$ref": "#/$defs/__schema2"
|
|
2990
3052
|
},
|
|
2991
3053
|
"input": {
|
|
2992
|
-
"$ref": "#/$defs/
|
|
3054
|
+
"$ref": "#/$defs/__schema56"
|
|
2993
3055
|
}
|
|
2994
3056
|
},
|
|
2995
3057
|
"required": [
|
|
@@ -2998,7 +3060,7 @@
|
|
|
2998
3060
|
],
|
|
2999
3061
|
"additionalProperties": false
|
|
3000
3062
|
},
|
|
3001
|
-
"
|
|
3063
|
+
"__schema56": {
|
|
3002
3064
|
"anyOf": [
|
|
3003
3065
|
{
|
|
3004
3066
|
"type": "string"
|
|
@@ -3015,7 +3077,7 @@
|
|
|
3015
3077
|
{
|
|
3016
3078
|
"type": "array",
|
|
3017
3079
|
"items": {
|
|
3018
|
-
"$ref": "#/$defs/
|
|
3080
|
+
"$ref": "#/$defs/__schema56"
|
|
3019
3081
|
}
|
|
3020
3082
|
},
|
|
3021
3083
|
{
|
|
@@ -3024,7 +3086,7 @@
|
|
|
3024
3086
|
"type": "string"
|
|
3025
3087
|
},
|
|
3026
3088
|
"additionalProperties": {
|
|
3027
|
-
"$ref": "#/$defs/
|
|
3089
|
+
"$ref": "#/$defs/__schema56"
|
|
3028
3090
|
}
|
|
3029
3091
|
}
|
|
3030
3092
|
]
|
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[] = [];
|