@hyperscale0/udl 2.2.0 → 2.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 +12 -0
- package/dist/effects.d.ts +0 -6
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +61 -21
- package/dist/effects.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/validation.d.ts +13 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +252 -56
- package/dist/validation.js.map +1 -1
- package/docs/README.md +3 -0
- package/docs/llms-full.txt +4 -1
- package/docs/llms.txt +1 -1
- package/docs/piece-plans.md +148 -0
- package/docs/reference/clauses.md +1 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/diagnostics.md +1 -1
- package/package.json +2 -2
- package/src/effects.ts +89 -21
- package/src/index.ts +1 -0
- package/src/validation.ts +340 -61
package/dist/validation.js
CHANGED
|
@@ -915,76 +915,272 @@ function validateInstrument(instrument, instrumentIndex, instruments, subjects,
|
|
|
915
915
|
}
|
|
916
916
|
}
|
|
917
917
|
}
|
|
918
|
-
|
|
919
|
-
|
|
918
|
+
for (const finIssue of instrumentFinanceIssues(instrument, {
|
|
919
|
+
plans: planResolution.plans,
|
|
920
|
+
})) {
|
|
921
|
+
add([...base, ...finIssue.path], finIssue.message, finIssue.code);
|
|
922
|
+
}
|
|
923
|
+
validateAggregates(instrument, base, instruments, references, add);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* The finance oracle over an instrument's resolved action plans, with paths
|
|
927
|
+
* rooted at the instrument. A piece-plan instrument is unfolded over the
|
|
928
|
+
* runtime's piece progress; any other instrument runs once per action plan
|
|
929
|
+
* combination. Contract-side callers pass the UDL projection of a blueprint
|
|
930
|
+
* definition so both boundaries prove the same machine.
|
|
931
|
+
*/
|
|
932
|
+
export function instrumentFinanceIssues(instrument, options = {}) {
|
|
933
|
+
const plans = options.plans ?? resolveUdlActionPlans(instrument).plans;
|
|
934
|
+
const financeOptions = options.penaltyMayBeNonzero === undefined
|
|
935
|
+
? {}
|
|
936
|
+
: { penaltyMayBeNonzero: options.penaltyMayBeNonzero };
|
|
937
|
+
const issues = [];
|
|
938
|
+
const seen = new Set();
|
|
939
|
+
const add = (path, message, code) => {
|
|
940
|
+
const key = `${code}:${path.join(".")}:${message}`;
|
|
941
|
+
if (seen.has(key))
|
|
942
|
+
return;
|
|
943
|
+
seen.add(key);
|
|
944
|
+
issues.push({ code, message, path });
|
|
945
|
+
};
|
|
946
|
+
const expansion = instrument.piecePlan
|
|
947
|
+
? expandPieceProgress(instrument, plans)
|
|
948
|
+
: undefined;
|
|
949
|
+
if (expansion === "bound") {
|
|
950
|
+
add(["actions"], `piece progress expansion exceeds reachable variant bound of ${UDL_LIMITS.maxActionExpansion}`, "UDL2010");
|
|
951
|
+
return issues;
|
|
952
|
+
}
|
|
953
|
+
if (expansion) {
|
|
954
|
+
for (const finIssue of analyzeInstrumentFinance(expansion.instrument, financeOptions)) {
|
|
955
|
+
const message = expansion.displayNames.reduce((text, [expanded, display]) => text.replaceAll(expanded, display), finIssue.message);
|
|
956
|
+
add(originFinancePath(expansion, finIssue.path), message, finIssue.code);
|
|
957
|
+
}
|
|
958
|
+
return issues;
|
|
959
|
+
}
|
|
960
|
+
const actionsWithPlans = Object.keys(instrument.actions).filter((aName) => plans.some((p) => p.action === aName));
|
|
920
961
|
let totalCombinations = 1;
|
|
921
962
|
const actionPlanMap = {};
|
|
922
963
|
for (const aName of actionsWithPlans) {
|
|
923
|
-
const actionPlans =
|
|
964
|
+
const actionPlans = plans.filter((p) => p.action === aName);
|
|
924
965
|
actionPlanMap[aName] = actionPlans;
|
|
925
966
|
totalCombinations *= actionPlans.length;
|
|
926
967
|
}
|
|
927
968
|
if (actionsWithPlans.length > 0 &&
|
|
928
969
|
totalCombinations > UDL_LIMITS.maxActionExpansion) {
|
|
929
|
-
add([
|
|
970
|
+
add(["actions"], `variant expansion exceeds combination bound of ${UDL_LIMITS.maxActionExpansion} (${totalCombinations} combinations)`, "UDL2010");
|
|
971
|
+
return issues;
|
|
930
972
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
for (const
|
|
939
|
-
|
|
940
|
-
result.push({ ...combo, [first]: plan });
|
|
941
|
-
}
|
|
973
|
+
const generateCombos = (keys) => {
|
|
974
|
+
if (keys.length === 0)
|
|
975
|
+
return [{}];
|
|
976
|
+
const [first, ...rest] = keys;
|
|
977
|
+
const restCombos = generateCombos(rest);
|
|
978
|
+
const result = [];
|
|
979
|
+
for (const plan of actionPlanMap[first]) {
|
|
980
|
+
for (const combo of restCombos) {
|
|
981
|
+
result.push({ ...combo, [first]: plan });
|
|
942
982
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
983
|
+
}
|
|
984
|
+
return result;
|
|
985
|
+
};
|
|
986
|
+
const combinations = actionsWithPlans.length > 0 ? generateCombos(actionsWithPlans) : [{}];
|
|
987
|
+
for (const combo of combinations) {
|
|
988
|
+
const expandedActions = {};
|
|
989
|
+
for (const [aName, aDef] of Object.entries(instrument.actions)) {
|
|
990
|
+
expandedActions[aName] = planExpandedAction(aDef, combo[aName]);
|
|
991
|
+
}
|
|
992
|
+
for (const finIssue of analyzeInstrumentFinance({ ...instrument, actions: expandedActions }, financeOptions)) {
|
|
993
|
+
add(finIssue.path, finIssue.message, finIssue.code);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return issues;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Only an action that moves money through calls is replaced by its expanded
|
|
1000
|
+
* leaves. Authored moves and steps always reach the oracle.
|
|
1001
|
+
*/
|
|
1002
|
+
function planExpandedAction(definition, plan) {
|
|
1003
|
+
if (!plan || (definition.calls?.length ?? 0) === 0)
|
|
1004
|
+
return definition;
|
|
1005
|
+
const steps = [];
|
|
1006
|
+
const moves = [];
|
|
1007
|
+
for (const leaf of plan.leaves) {
|
|
1008
|
+
if ("key" in leaf.step) {
|
|
1009
|
+
moves.push(leaf.step);
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
steps.push(leaf.step);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return { ...definition, moves, steps };
|
|
1016
|
+
}
|
|
1017
|
+
function progressKey(progress) {
|
|
1018
|
+
return `${progress.funded.join(",")}|${progress.consumed.join(",")}`;
|
|
1019
|
+
}
|
|
1020
|
+
/** Mirrors eligiblePieces in the engine's piece-plan dispatcher. */
|
|
1021
|
+
function eligiblePieces(plan, stage, progress) {
|
|
1022
|
+
return plan[`${stage}_order`].filter((id) => stage === "fund"
|
|
1023
|
+
? !progress.funded.includes(id)
|
|
1024
|
+
: progress.funded.includes(id) && !progress.consumed.includes(id));
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Unfolds a piece-plan instrument over the runtime's piece progress so the
|
|
1028
|
+
* ordinary lifecycle oracle sees exactly the states the dispatcher admits: a
|
|
1029
|
+
* piece-stage action moves the next eligible piece of its stage order, the
|
|
1030
|
+
* lifecycle state is retained until the stage's last piece moves, funding
|
|
1031
|
+
* cannot resume once a piece has left escrow, and an action gated on a drained
|
|
1032
|
+
* account is closed while a funded piece is still held. Every expanded state is
|
|
1033
|
+
* one (lifecycle state, progress) pair; expanded action names carry the piece
|
|
1034
|
+
* and the source state so each has exactly one transition. A quote-commit pair
|
|
1035
|
+
* is not carried through the expansion.
|
|
1036
|
+
*/
|
|
1037
|
+
function expandPieceProgress(instrument, plans) {
|
|
1038
|
+
const plan = instrument.piecePlan;
|
|
1039
|
+
if (!plan)
|
|
1040
|
+
return undefined;
|
|
1041
|
+
const stateName = (state, progress) => `${state}#${progressKey(progress)}`;
|
|
1042
|
+
const initialProgress = { funded: [], consumed: [] };
|
|
1043
|
+
const stateOrigins = new Map();
|
|
1044
|
+
const actionOrigins = new Map();
|
|
1045
|
+
const displayNames = [];
|
|
1046
|
+
const actions = {};
|
|
1047
|
+
const transitions = {};
|
|
1048
|
+
const pending = [
|
|
1049
|
+
{ state: instrument.lifecycle.initial, progress: initialProgress },
|
|
1050
|
+
];
|
|
1051
|
+
stateOrigins.set(stateName(instrument.lifecycle.initial, initialProgress), instrument.lifecycle.initial);
|
|
1052
|
+
const visit = (state, progress) => {
|
|
1053
|
+
const name = stateName(state, progress);
|
|
1054
|
+
if (stateOrigins.has(name))
|
|
1055
|
+
return;
|
|
1056
|
+
stateOrigins.set(name, state);
|
|
1057
|
+
pending.push({ state, progress });
|
|
1058
|
+
};
|
|
1059
|
+
while (pending.length > 0) {
|
|
1060
|
+
const { state, progress } = pending.shift();
|
|
1061
|
+
if (stateOrigins.size > UDL_LIMITS.maxActionExpansion)
|
|
1062
|
+
return "bound";
|
|
1063
|
+
const held = progress.funded.filter((id) => !progress.consumed.includes(id));
|
|
1064
|
+
for (const [actionName, transition] of Object.entries(instrument.lifecycle.transitions)) {
|
|
1065
|
+
if (!transition.from.includes(state))
|
|
1066
|
+
continue;
|
|
1067
|
+
const definition = instrument.actions[actionName];
|
|
1068
|
+
if (!definition)
|
|
1069
|
+
continue;
|
|
1070
|
+
if (definition.requiresDrainedAccount && held.length > 0)
|
|
1071
|
+
continue;
|
|
1072
|
+
const stage = definition.pieceStage;
|
|
1073
|
+
if (!stage) {
|
|
1074
|
+
const expanded = `${actionName}#${state}#${progressKey(progress)}`;
|
|
1075
|
+
const variant = plans.find((candidate) => candidate.action === actionName);
|
|
1076
|
+
actions[expanded] = planExpandedAction(definition, variant);
|
|
1077
|
+
transitions[expanded] = {
|
|
1078
|
+
from: [stateName(state, progress)],
|
|
1079
|
+
to: stateName(transition.to, progress),
|
|
1080
|
+
};
|
|
1081
|
+
actionOrigins.set(expanded, {
|
|
1082
|
+
action: actionName,
|
|
1083
|
+
...(variant && (definition.calls?.length ?? 0) > 0
|
|
1084
|
+
? { leafOrigins: variant.leaves.map((leaf) => leaf.originPath) }
|
|
1085
|
+
: {}),
|
|
1086
|
+
});
|
|
1087
|
+
displayNames.push([expanded, actionName]);
|
|
1088
|
+
visit(transition.to, progress);
|
|
1089
|
+
continue;
|
|
973
1090
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1091
|
+
if (stage.plan !== plan.id)
|
|
1092
|
+
continue;
|
|
1093
|
+
if (stage.stage === "fund" && progress.consumed.length > 0)
|
|
1094
|
+
continue;
|
|
1095
|
+
const eligible = eligiblePieces(plan, stage.stage, progress);
|
|
1096
|
+
const pieceId = eligible[0];
|
|
1097
|
+
if (pieceId === undefined)
|
|
1098
|
+
continue;
|
|
1099
|
+
const variant = plans.find((candidate) => candidate.action === actionName && candidate.pieceId === pieceId);
|
|
1100
|
+
if (!variant)
|
|
1101
|
+
continue;
|
|
1102
|
+
const next = stage.stage === "fund"
|
|
1103
|
+
? {
|
|
1104
|
+
funded: [...progress.funded, pieceId],
|
|
1105
|
+
consumed: progress.consumed,
|
|
983
1106
|
}
|
|
984
|
-
|
|
1107
|
+
: {
|
|
1108
|
+
funded: progress.funded,
|
|
1109
|
+
consumed: [...progress.consumed, pieceId],
|
|
1110
|
+
};
|
|
1111
|
+
const stageComplete = eligiblePieces(plan, stage.stage, next).length === 0;
|
|
1112
|
+
const target = stageComplete ? transition.to : state;
|
|
1113
|
+
const expanded = `${actionName}@${pieceId}#${state}#${progressKey(progress)}`;
|
|
1114
|
+
actions[expanded] = planExpandedAction(definition, variant);
|
|
1115
|
+
transitions[expanded] = {
|
|
1116
|
+
from: [stateName(state, progress)],
|
|
1117
|
+
to: stateName(target, next),
|
|
1118
|
+
};
|
|
1119
|
+
actionOrigins.set(expanded, {
|
|
1120
|
+
action: actionName,
|
|
1121
|
+
leafOrigins: variant.leaves.map((leaf) => leaf.originPath),
|
|
1122
|
+
});
|
|
1123
|
+
displayNames.push([expanded, `${actionName}[${pieceId}]`]);
|
|
1124
|
+
visit(target, next);
|
|
985
1125
|
}
|
|
986
1126
|
}
|
|
987
|
-
|
|
1127
|
+
for (const [actionName, definition] of Object.entries(instrument.actions)) {
|
|
1128
|
+
if (Object.hasOwn(instrument.lifecycle.transitions, actionName))
|
|
1129
|
+
continue;
|
|
1130
|
+
actions[actionName] = planExpandedAction(definition, plans.find((candidate) => candidate.action === actionName));
|
|
1131
|
+
actionOrigins.set(actionName, { action: actionName });
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
instrument: {
|
|
1135
|
+
...instrument,
|
|
1136
|
+
actions,
|
|
1137
|
+
lifecycle: {
|
|
1138
|
+
initial: stateName(instrument.lifecycle.initial, initialProgress),
|
|
1139
|
+
states: [...stateOrigins.keys()],
|
|
1140
|
+
transitions,
|
|
1141
|
+
},
|
|
1142
|
+
},
|
|
1143
|
+
displayNames: [
|
|
1144
|
+
...displayNames,
|
|
1145
|
+
...[...stateOrigins.entries()].map(([expanded, origin]) => [expanded, origin]),
|
|
1146
|
+
].sort((left, right) => right[0].length - left[0].length),
|
|
1147
|
+
actionOrigins,
|
|
1148
|
+
stateOrigins,
|
|
1149
|
+
originStates: instrument.lifecycle.states,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function originFinancePath(expansion, path) {
|
|
1153
|
+
const [head, second, third, fourth, ...rest] = path;
|
|
1154
|
+
if (head === "lifecycle" &&
|
|
1155
|
+
second === "states" &&
|
|
1156
|
+
typeof third === "number") {
|
|
1157
|
+
const expandedState = expansion.instrument.lifecycle.states[third];
|
|
1158
|
+
const origin = expandedState === undefined
|
|
1159
|
+
? undefined
|
|
1160
|
+
: expansion.stateOrigins.get(expandedState);
|
|
1161
|
+
const index = origin === undefined ? -1 : expansion.originStates.indexOf(origin);
|
|
1162
|
+
return index >= 0
|
|
1163
|
+
? ["lifecycle", "states", index]
|
|
1164
|
+
: ["lifecycle", "states"];
|
|
1165
|
+
}
|
|
1166
|
+
if (head === "actions" && typeof second === "string") {
|
|
1167
|
+
const origin = expansion.actionOrigins.get(second);
|
|
1168
|
+
if (!origin)
|
|
1169
|
+
return path;
|
|
1170
|
+
if (third === "moves" &&
|
|
1171
|
+
typeof fourth === "number" &&
|
|
1172
|
+
origin.leafOrigins?.[fourth]) {
|
|
1173
|
+
return ["actions", ...origin.leafOrigins[fourth], ...rest];
|
|
1174
|
+
}
|
|
1175
|
+
return [
|
|
1176
|
+
"actions",
|
|
1177
|
+
origin.action,
|
|
1178
|
+
...(third === undefined ? [] : [third]),
|
|
1179
|
+
...(fourth === undefined ? [] : [fourth]),
|
|
1180
|
+
...rest,
|
|
1181
|
+
];
|
|
1182
|
+
}
|
|
1183
|
+
return path;
|
|
988
1184
|
}
|
|
989
1185
|
function validatePiecePlan(instrument, base, references, add) {
|
|
990
1186
|
const plan = instrument.piecePlan;
|