@hyperscale0/udl 2.4.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 +16 -8
- 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/validation.js +1 -1
- package/dist/validation.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/validation.ts +2 -1
- package/src/vocabulary.ts +127 -0
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/validation.ts
CHANGED
|
@@ -5509,7 +5509,8 @@ const sealedFormatValidators: Readonly<
|
|
|
5509
5509
|
Record<string, (value: string) => boolean>
|
|
5510
5510
|
> = {
|
|
5511
5511
|
"hyperscale-date": (value) => z.iso.date().safeParse(value).success,
|
|
5512
|
-
"hyperscale-date-time": (value) =>
|
|
5512
|
+
"hyperscale-date-time": (value) =>
|
|
5513
|
+
z.iso.datetime({ offset: true }).safeParse(value).success,
|
|
5513
5514
|
"hyperscale-email": (value) => z.email().safeParse(value).success,
|
|
5514
5515
|
"hyperscale-uri": (value) => z.url().safeParse(value).success,
|
|
5515
5516
|
};
|
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" ||
|