@hyperscale0/udl 2.5.0 → 2.6.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 +7 -1
- package/README.md +1 -1
- package/conformance/valid/vocabulary.expected.json +1 -1
- package/conformance/valid/vocabulary.udl +13 -0
- package/dist/evolution.d.ts +2 -0
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +7 -0
- package/dist/evolution.js.map +1 -1
- package/dist/schema.d.ts +73 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +27 -0
- package/dist/schema.js.map +1 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +87 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/guide/09-schedules-and-allocation.md +2 -0
- package/docs/llms-full.txt +25 -1
- package/docs/llms.txt +1 -1
- package/docs/reference/clauses.md +23 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/diagnostics.md +1 -1
- package/package.json +2 -2
- package/spec/udl.schema.json +62 -0
- package/src/evolution.ts +11 -0
- package/src/schema.ts +29 -0
- package/src/vocabulary.ts +127 -0
package/docs/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.
|
|
1
|
+
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.6.0. Edit the source, not llms-full.txt. -->
|
|
2
2
|
|
|
3
3
|
# UDL complete reference
|
|
4
4
|
|
|
@@ -266,6 +266,8 @@ The engine proves the advance and supplies trusted paid/consumed facts to the pl
|
|
|
266
266
|
|
|
267
267
|
`transitionsRefs` invokes named noncash lifecycle actions on referenced instruments in the same transaction. The engine applies every target gate, authority check and lifecycle precondition. Cycles, repeated target actions and cash-bearing or input-dependent target actions refuse. A child can lapse its cover; a refund can cancel a referenced funding record and prevent future actions through that record's lifecycle gates. This does not replace durable approval consumption tied to a downstream operation.
|
|
268
268
|
|
|
269
|
+
`cascade` executes a named lifecycle action on linked instances of another instrument inside the same transaction before evaluating the parent action's own admission gates. Target instances are identified either through caller input IDs via `inputField` or by matching a parent reference on target rows via `refField` filtered by lifecycle statuses. The cascading action cannot be create and cannot declare a decision. The target action must exist in lifecycle, cannot be create or engine-owned, cannot declare a decision, and cannot declare a cascade of its own. Because the cascade runs before the parent's admission gates, child status transitions take effect in time for parent aggregate requirements.
|
|
270
|
+
|
|
269
271
|
`requiresRefs.unique` accepts `{namespace, byFields}`. The uniqueness key contains the Product, namespace, referenced instance identity and immutable key values. It excludes instrument aliases and current status. A waived or refunded fine still occupies its key; cost uses a different kind value. The engine enforces this key under a shared constraint or lock.
|
|
270
272
|
|
|
271
273
|
Decision authentication, attestation verification and request hashing are admission concerns. The decision record must carry the requested instrument kind, instance, action, canonical input digest, deciding role and actor, expiry, verdict and evidence references. The engine computes the digest from the admitted request, authenticates the authority, checks expiry and revocation under locks, and binds consumption to the durable operation identity. Submitted actor and operation fields are not proof.
|
|
@@ -611,6 +613,28 @@ This page lists every entry in `udlClauseVocabulary`. The examples are copied fr
|
|
|
611
613
|
]
|
|
612
614
|
```
|
|
613
615
|
|
|
616
|
+
## cascade
|
|
617
|
+
|
|
618
|
+
- Scope: action
|
|
619
|
+
- UDL target: `cascade`
|
|
620
|
+
- Cardinality: many
|
|
621
|
+
- Linear outputs: none
|
|
622
|
+
- Effects: decides.cascade_transition per element
|
|
623
|
+
- Law: Requirements-as-data law and closure law
|
|
624
|
+
- Conformance source: `vocabulary.udl`
|
|
625
|
+
|
|
626
|
+
A cascade target action that declares a `port` is allowed only when the parent action declares a `port`: the engine forwards the port actor.
|
|
627
|
+
|
|
628
|
+
```json
|
|
629
|
+
[
|
|
630
|
+
{
|
|
631
|
+
"action": "close",
|
|
632
|
+
"inputField": "sliceIds",
|
|
633
|
+
"instrumentId": "slice"
|
|
634
|
+
}
|
|
635
|
+
]
|
|
636
|
+
```
|
|
637
|
+
|
|
614
638
|
## action library
|
|
615
639
|
|
|
616
640
|
- Scope: instrument
|
package/docs/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.
|
|
1
|
+
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.6.0. Edit the source, not clauses.md. -->
|
|
2
2
|
|
|
3
3
|
# Clause reference
|
|
4
4
|
|
|
@@ -254,6 +254,28 @@ This page lists every entry in `udlClauseVocabulary`. The examples are copied fr
|
|
|
254
254
|
]
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
+
## cascade
|
|
258
|
+
|
|
259
|
+
- Scope: action
|
|
260
|
+
- UDL target: `cascade`
|
|
261
|
+
- Cardinality: many
|
|
262
|
+
- Linear outputs: none
|
|
263
|
+
- Effects: decides.cascade_transition per element
|
|
264
|
+
- Law: Requirements-as-data law and closure law
|
|
265
|
+
- Conformance source: `vocabulary.udl`
|
|
266
|
+
|
|
267
|
+
A cascade target action that declares a `port` is allowed only when the parent action declares a `port`: the engine forwards the port actor.
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
[
|
|
271
|
+
{
|
|
272
|
+
"action": "close",
|
|
273
|
+
"inputField": "sliceIds",
|
|
274
|
+
"instrumentId": "slice"
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
```
|
|
278
|
+
|
|
257
279
|
## action library
|
|
258
280
|
|
|
259
281
|
- Scope: instrument
|
package/docs/reference/cli.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/udl",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "The Universal Domain Language: format spec, parser, validator, canonical serializer, and evolution diff.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"udl",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
"Amir Ayub",
|
|
84
84
|
"Sara AlBakaawi"
|
|
85
85
|
],
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "cd1e1ff5c188de94ff8bb0761cb3258746196bf2"
|
|
87
87
|
}
|
package/spec/udl.schema.json
CHANGED
|
@@ -1719,6 +1719,68 @@
|
|
|
1719
1719
|
"additionalProperties": false
|
|
1720
1720
|
}
|
|
1721
1721
|
},
|
|
1722
|
+
"cascade": {
|
|
1723
|
+
"minItems": 1,
|
|
1724
|
+
"maxItems": 8,
|
|
1725
|
+
"type": "array",
|
|
1726
|
+
"items": {
|
|
1727
|
+
"anyOf": [
|
|
1728
|
+
{
|
|
1729
|
+
"type": "object",
|
|
1730
|
+
"properties": {
|
|
1731
|
+
"instrumentId": {
|
|
1732
|
+
"type": "string",
|
|
1733
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
1734
|
+
"x-udl-reference": "instrument"
|
|
1735
|
+
},
|
|
1736
|
+
"action": {
|
|
1737
|
+
"type": "string",
|
|
1738
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
|
|
1739
|
+
},
|
|
1740
|
+
"inputField": {
|
|
1741
|
+
"type": "string",
|
|
1742
|
+
"pattern": "^[a-z][A-Za-z0-9]*$"
|
|
1743
|
+
}
|
|
1744
|
+
},
|
|
1745
|
+
"required": ["instrumentId", "action", "inputField"],
|
|
1746
|
+
"additionalProperties": false
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
"type": "object",
|
|
1750
|
+
"properties": {
|
|
1751
|
+
"instrumentId": {
|
|
1752
|
+
"type": "string",
|
|
1753
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
1754
|
+
"x-udl-reference": "instrument"
|
|
1755
|
+
},
|
|
1756
|
+
"action": {
|
|
1757
|
+
"type": "string",
|
|
1758
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
|
|
1759
|
+
},
|
|
1760
|
+
"refField": {
|
|
1761
|
+
"type": "string",
|
|
1762
|
+
"pattern": "^[a-z][A-Za-z0-9]*$"
|
|
1763
|
+
},
|
|
1764
|
+
"statuses": {
|
|
1765
|
+
"minItems": 1,
|
|
1766
|
+
"type": "array",
|
|
1767
|
+
"items": {
|
|
1768
|
+
"type": "string",
|
|
1769
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
},
|
|
1773
|
+
"required": [
|
|
1774
|
+
"instrumentId",
|
|
1775
|
+
"action",
|
|
1776
|
+
"refField",
|
|
1777
|
+
"statuses"
|
|
1778
|
+
],
|
|
1779
|
+
"additionalProperties": false
|
|
1780
|
+
}
|
|
1781
|
+
]
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1722
1784
|
"agentDescription": {
|
|
1723
1785
|
"type": "string",
|
|
1724
1786
|
"maxLength": 400
|
package/src/evolution.ts
CHANGED
|
@@ -80,6 +80,8 @@ export interface EvolutionActionSnapshot {
|
|
|
80
80
|
readonly setsAt: unknown;
|
|
81
81
|
/** Missing on snapshots written before signed child sums existed. */
|
|
82
82
|
readonly signedSum?: unknown;
|
|
83
|
+
/** Missing on snapshots written before cascade transitions existed. */
|
|
84
|
+
readonly cascade?: unknown;
|
|
83
85
|
readonly moves: readonly EvolutionMoveSnapshot[];
|
|
84
86
|
readonly steps: readonly EvolutionStepSnapshot[];
|
|
85
87
|
}
|
|
@@ -578,6 +580,9 @@ function snapshotUdlAction(definition: UdlAction): EvolutionActionSnapshot {
|
|
|
578
580
|
publicAction: definition.publicAction ?? null,
|
|
579
581
|
setsAt: definition.setsAt ?? null,
|
|
580
582
|
signedSum: definition.signedSum ?? null,
|
|
583
|
+
cascade: definition.cascade
|
|
584
|
+
? definition.cascade.map((entry) => ({ ...entry }))
|
|
585
|
+
: null,
|
|
581
586
|
moves: definition.moves.map((move) => ({
|
|
582
587
|
bind: move.bind,
|
|
583
588
|
capture: move.capture ?? {},
|
|
@@ -887,6 +892,12 @@ function diffActions(
|
|
|
887
892
|
) {
|
|
888
893
|
violations.push(`action ${action} changed its signed child sum`);
|
|
889
894
|
}
|
|
895
|
+
if (
|
|
896
|
+
descriptor.cascade !== undefined &&
|
|
897
|
+
stableStringify(descriptor.cascade) !== stableStringify(current.cascade)
|
|
898
|
+
) {
|
|
899
|
+
violations.push(`action ${action} changed its cascade rules`);
|
|
900
|
+
}
|
|
890
901
|
}
|
|
891
902
|
for (const [action, descriptor] of Object.entries(next)) {
|
|
892
903
|
if (previous[action] || descriptor.payout == null) continue;
|
package/src/schema.ts
CHANGED
|
@@ -909,6 +909,20 @@ const udlRequestAuthoritySchema = z.strictObject({
|
|
|
909
909
|
expiresField: udlFieldNameSchema,
|
|
910
910
|
});
|
|
911
911
|
|
|
912
|
+
const udlCascadeEntrySchema = z.union([
|
|
913
|
+
z.strictObject({
|
|
914
|
+
instrumentId: udlInstrumentIdSchema,
|
|
915
|
+
action: udlActionNameSchema,
|
|
916
|
+
inputField: udlFieldNameSchema,
|
|
917
|
+
}),
|
|
918
|
+
z.strictObject({
|
|
919
|
+
instrumentId: udlInstrumentIdSchema,
|
|
920
|
+
action: udlActionNameSchema,
|
|
921
|
+
refField: udlFieldNameSchema,
|
|
922
|
+
statuses: z.array(udlSnakeCaseSchema).min(1),
|
|
923
|
+
}),
|
|
924
|
+
]);
|
|
925
|
+
|
|
912
926
|
const udlActionShape = {
|
|
913
927
|
requestAuthority: udlRequestAuthoritySchema.optional(),
|
|
914
928
|
funding: udlFundingSchema.optional(),
|
|
@@ -954,6 +968,7 @@ const udlActionShape = {
|
|
|
954
968
|
.min(1)
|
|
955
969
|
.max(16)
|
|
956
970
|
.optional(),
|
|
971
|
+
cascade: z.array(udlCascadeEntrySchema).min(1).max(8).optional(),
|
|
957
972
|
agentDescription: udlAgentDescriptionSchema.optional(),
|
|
958
973
|
calls: z.array(udlCallSchema).min(1).optional(),
|
|
959
974
|
captureInput: z.record(udlFieldNameSchema, udlFieldNameSchema).optional(),
|
|
@@ -1217,6 +1232,19 @@ export const udlClauseVocabulary = [
|
|
|
1217
1232
|
},
|
|
1218
1233
|
],
|
|
1219
1234
|
},
|
|
1235
|
+
{
|
|
1236
|
+
cardinality: "many",
|
|
1237
|
+
scope: "action",
|
|
1238
|
+
spelling: "cascade",
|
|
1239
|
+
target: "cascade",
|
|
1240
|
+
effects: [
|
|
1241
|
+
{
|
|
1242
|
+
kind: "decides",
|
|
1243
|
+
per: "element",
|
|
1244
|
+
signature: { fixed: "cascade_transition" },
|
|
1245
|
+
},
|
|
1246
|
+
],
|
|
1247
|
+
},
|
|
1220
1248
|
{
|
|
1221
1249
|
cardinality: "one",
|
|
1222
1250
|
literalKeys: true,
|
|
@@ -1893,3 +1921,4 @@ export type UdlActionLibraryModule = z.infer<
|
|
|
1893
1921
|
typeof udlActionLibraryModuleSchema
|
|
1894
1922
|
>;
|
|
1895
1923
|
export type UdlActionLibrary = z.infer<typeof udlActionLibrarySchema>;
|
|
1924
|
+
export type UdlCascadeEntry = z.infer<typeof udlCascadeEntrySchema>;
|
package/src/vocabulary.ts
CHANGED
|
@@ -32,6 +32,12 @@ const mutable = (instrument: UdlInstrument, field: string) =>
|
|
|
32
32
|
Object.values(instrument.actions).some((action) =>
|
|
33
33
|
action.updates?.includes(field),
|
|
34
34
|
);
|
|
35
|
+
function instrumentInstanceKey(instrumentId: string): string {
|
|
36
|
+
const camel = instrumentId.replaceAll(/_([a-z])/g, (_, letter: string) =>
|
|
37
|
+
letter.toUpperCase(),
|
|
38
|
+
);
|
|
39
|
+
return `${camel}Id`;
|
|
40
|
+
}
|
|
35
41
|
|
|
36
42
|
/** Validate product laws independently of any instrument catalogue. */
|
|
37
43
|
/** The children buckets of an allocation owner that select this instrument or its template alias. */
|
|
@@ -952,6 +958,127 @@ export function validateVocabulary(
|
|
|
952
958
|
outgoing.push(`${target.id}.${transition.action}`);
|
|
953
959
|
}
|
|
954
960
|
edges.set(`${instrument.id}.${name}`, outgoing);
|
|
961
|
+
if (action.cascade) {
|
|
962
|
+
const ap = [...path, "cascade"];
|
|
963
|
+
if (name === "create" || action.decision) {
|
|
964
|
+
add(
|
|
965
|
+
ap,
|
|
966
|
+
"cascade is not allowed on create or actions with a decision",
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
const seenInputFields = new Set<string>();
|
|
970
|
+
for (const [i, entry] of action.cascade.entries()) {
|
|
971
|
+
const ep = [...ap, i];
|
|
972
|
+
const hasInput =
|
|
973
|
+
"inputField" in entry && entry.inputField !== undefined;
|
|
974
|
+
const hasRef = "refField" in entry && entry.refField !== undefined;
|
|
975
|
+
if ((!hasInput && !hasRef) || (hasInput && hasRef)) {
|
|
976
|
+
add(
|
|
977
|
+
ep,
|
|
978
|
+
"cascade must specify exactly one of inputField or refField",
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const target = byId.get(entry.instrumentId);
|
|
982
|
+
if (!target) {
|
|
983
|
+
add(
|
|
984
|
+
ep,
|
|
985
|
+
"cascade target instrument must be declared in the document",
|
|
986
|
+
);
|
|
987
|
+
} else {
|
|
988
|
+
const targetAction = target.actions[entry.action];
|
|
989
|
+
const targetTransition = target.lifecycle.transitions[entry.action];
|
|
990
|
+
if (
|
|
991
|
+
!targetAction ||
|
|
992
|
+
!targetTransition ||
|
|
993
|
+
entry.action === "create"
|
|
994
|
+
) {
|
|
995
|
+
add(
|
|
996
|
+
ep,
|
|
997
|
+
"cascade target action must exist in lifecycle and cannot be create",
|
|
998
|
+
);
|
|
999
|
+
} else {
|
|
1000
|
+
if (targetAction.engineOwned) {
|
|
1001
|
+
add(ep, "cascade target action cannot be engine-owned");
|
|
1002
|
+
}
|
|
1003
|
+
if (targetAction.cascade) {
|
|
1004
|
+
add(
|
|
1005
|
+
ep,
|
|
1006
|
+
"cascade target action cannot have a cascade of its own",
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
if (targetAction.decision) {
|
|
1010
|
+
add(ep, "cascade target action cannot have a decision");
|
|
1011
|
+
}
|
|
1012
|
+
if (targetAction.port && !action.port) {
|
|
1013
|
+
add(
|
|
1014
|
+
ep,
|
|
1015
|
+
`cascade target action ${entry.action} declares a port; the parent action ${name} must declare a port so the actor is forwarded`,
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
if (
|
|
1019
|
+
Array.isArray(targetAction.input?.required) &&
|
|
1020
|
+
targetAction.input.required.length > 0
|
|
1021
|
+
) {
|
|
1022
|
+
add(ep, "cascade target action cannot have required inputs");
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if ("refField" in entry && entry.refField !== undefined) {
|
|
1026
|
+
if (!entry.statuses || entry.statuses.length === 0) {
|
|
1027
|
+
add(ep, "cascade with refField requires statuses");
|
|
1028
|
+
}
|
|
1029
|
+
const targetRef = reference(target, entry.refField, ep);
|
|
1030
|
+
if (targetRef && targetRef.id !== instrument.id) {
|
|
1031
|
+
add(
|
|
1032
|
+
ep,
|
|
1033
|
+
"cascade target refField must reference this instrument",
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
if (targetTransition) {
|
|
1037
|
+
for (const status of entry.statuses ?? []) {
|
|
1038
|
+
if (!targetTransition.from.includes(status)) {
|
|
1039
|
+
add(
|
|
1040
|
+
ep,
|
|
1041
|
+
`cascade status ${status} is not an allowed from-state for ${target.id}.${entry.action}`,
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if ("inputField" in entry && entry.inputField !== undefined) {
|
|
1049
|
+
const reservedFields = new Set([
|
|
1050
|
+
"tenantId",
|
|
1051
|
+
"productId",
|
|
1052
|
+
"actorAccountId",
|
|
1053
|
+
instrumentInstanceKey(instrument.id),
|
|
1054
|
+
]);
|
|
1055
|
+
if (reservedFields.has(entry.inputField)) {
|
|
1056
|
+
add(
|
|
1057
|
+
ep,
|
|
1058
|
+
`cascade inputField ${entry.inputField} collides with a reserved field`,
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
if (Object.hasOwn(instrument.fields, entry.inputField)) {
|
|
1062
|
+
add(
|
|
1063
|
+
ep,
|
|
1064
|
+
`cascade inputField ${entry.inputField} collides with an instrument field`,
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
if (
|
|
1068
|
+
Object.hasOwn(object(action.input?.properties), entry.inputField)
|
|
1069
|
+
) {
|
|
1070
|
+
add(
|
|
1071
|
+
ep,
|
|
1072
|
+
`cascade inputField ${entry.inputField} collides with an action input property`,
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (seenInputFields.has(entry.inputField)) {
|
|
1076
|
+
add(ep, `cascade inputField ${entry.inputField} is repeated`);
|
|
1077
|
+
}
|
|
1078
|
+
seenInputFields.add(entry.inputField);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
955
1082
|
if (action.allocate || action.contributionStage) {
|
|
956
1083
|
if (
|
|
957
1084
|
name === "create" ||
|