@hyperscale0/udl 2.2.0 → 2.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 +38 -0
- package/README.md +5 -1
- package/conformance/invalid/invalid-journeys.expected.json +3 -19
- package/conformance/invalid/invalid-journeys.udl +47 -49
- package/conformance/valid/attested.expected.json +6 -0
- package/conformance/valid/attested.udl +251 -0
- package/conformance/valid/hand-edited.expected.json +1 -1
- package/conformance/valid/hand-edited.udl +1 -1
- package/conformance/valid/minimal.expected.json +1 -1
- package/conformance/valid/minimal.udl +0 -15
- package/conformance/valid/vocabulary.expected.json +6 -0
- package/conformance/valid/vocabulary.udl +1999 -0
- package/dist/allocation.d.ts +60 -0
- package/dist/allocation.d.ts.map +1 -0
- package/dist/allocation.js +177 -0
- package/dist/allocation.js.map +1 -0
- package/dist/diagnostics.d.ts +1 -31
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +0 -30
- package/dist/diagnostics.js.map +1 -1
- package/dist/distribution.d.ts +15 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +49 -0
- package/dist/distribution.js.map +1 -0
- package/dist/effects.d.ts +0 -6
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +84 -22
- package/dist/effects.js.map +1 -1
- package/dist/evolution.d.ts +12 -0
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +42 -1
- package/dist/evolution.js.map +1 -1
- package/dist/finance.d.ts +18 -1
- package/dist/finance.d.ts.map +1 -1
- package/dist/finance.js +158 -27
- package/dist/finance.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/instrument-references.d.ts +5 -0
- package/dist/instrument-references.d.ts.map +1 -0
- package/dist/instrument-references.js +69 -0
- package/dist/instrument-references.js.map +1 -0
- package/dist/limits.d.ts +3 -3
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +3 -7
- package/dist/limits.js.map +1 -1
- package/dist/reference.d.ts +3 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.js +28 -0
- package/dist/reference.js.map +1 -0
- package/dist/schema.d.ts +1232 -83
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +398 -60
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts +27 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +327 -187
- package/dist/validation.js.map +1 -1
- package/dist/vocabulary.d.ts +23 -0
- package/dist/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary.js +965 -0
- package/dist/vocabulary.js.map +1 -0
- package/docs/README.md +5 -1
- package/docs/funding-custody.md +165 -0
- package/docs/guide/09-schedules-and-allocation.md +130 -0
- package/docs/llms-full.txt +623 -101
- package/docs/llms.txt +1 -1
- package/docs/piece-plans.md +148 -0
- package/docs/reference/clauses.md +451 -63
- package/docs/reference/cli.md +3 -1
- package/docs/reference/diagnostics.md +33 -38
- package/package.json +5 -6
- package/spec/udl.schema.json +1095 -118
- package/src/allocation.ts +259 -0
- package/src/diagnostics.ts +0 -32
- package/src/distribution.ts +61 -0
- package/src/effects.ts +116 -22
- package/src/evolution.ts +63 -3
- package/src/finance.ts +218 -24
- package/src/index.ts +31 -3
- package/src/instrument-references.ts +98 -0
- package/src/limits.ts +3 -7
- package/src/reference.ts +31 -0
- package/src/schema.ts +417 -66
- package/src/validation.ts +452 -243
- package/src/vocabulary.ts +1508 -0
package/dist/validation.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { referencePatternPrefix } from "./reference.js";
|
|
2
|
+
import { validateVocabulary } from "./vocabulary.js";
|
|
1
3
|
import { Validator, } from "@cfworker/json-schema";
|
|
2
4
|
import { z } from "zod";
|
|
3
5
|
import { udlClauseVocabulary, quoteExpiresAtRefKey, quoteFrozenRefKey, quoteSeededRefKeys, udlDocumentSchema, } from "./schema.js";
|
|
@@ -17,6 +19,12 @@ export class UdlError extends Error {
|
|
|
17
19
|
this.issues = Object.freeze([...issues]);
|
|
18
20
|
}
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate a complete document. Clause references to instruments must resolve
|
|
24
|
+
* within this document, even when the reference field is optional. A catalogue
|
|
25
|
+
* slice must include its referenced instruments; an external catalogue cannot
|
|
26
|
+
* supply missing product laws. Reference fields use the sealed public ID grammar.
|
|
27
|
+
*/
|
|
20
28
|
export function validateUdl(value, options = {}) {
|
|
21
29
|
const resourceIssue = structuralBudgetIssue(value);
|
|
22
30
|
if (resourceIssue)
|
|
@@ -211,11 +219,21 @@ function semanticIssues(document, references, options) {
|
|
|
211
219
|
validateDocumentSchemas(document, add);
|
|
212
220
|
if (issues.length > 0)
|
|
213
221
|
return issues;
|
|
222
|
+
// Vocabulary laws must accumulate with the existing semantic diagnostics.
|
|
223
|
+
validateVocabulary(document.instruments, (path, message) => add(path, message, path.some((part) => [
|
|
224
|
+
"attests",
|
|
225
|
+
"requiresInput",
|
|
226
|
+
"engineOwned",
|
|
227
|
+
"captureEngine",
|
|
228
|
+
"unique",
|
|
229
|
+
"port",
|
|
230
|
+
].includes(String(part)))
|
|
231
|
+
? "UDL5001"
|
|
232
|
+
: shapeIssueCode(path)));
|
|
214
233
|
addDuplicateIssues(document.subjects.map((subject) => subject.kind), ["subjects"], "subject kind", add);
|
|
215
234
|
addDuplicateIssues(document.instruments.map((instrument) => instrument.id), ["instruments"], "instrument id", add);
|
|
216
235
|
addDuplicateIssues(document.instruments.map((instrument) => instrument.idPrefix), ["instruments"], "instrument idPrefix", add);
|
|
217
236
|
validateCompositionDials(document, add);
|
|
218
|
-
validateInstrumentJourneys(document, add);
|
|
219
237
|
const subjects = new Map(document.subjects.map((subject) => [subject.kind, subject]));
|
|
220
238
|
const instruments = new Map(document.instruments.map((instrument) => [instrument.id, instrument]));
|
|
221
239
|
for (const [subjectIndex, subject] of document.subjects.entries()) {
|
|
@@ -280,121 +298,6 @@ function semanticIssues(document, references, options) {
|
|
|
280
298
|
}
|
|
281
299
|
return issues;
|
|
282
300
|
}
|
|
283
|
-
const scopedIdPattern = /^\^([a-z]{2,8})_\(sandbox\|live\)_\[a-z0-9\]\{8,64\}\$$/;
|
|
284
|
-
function camel(value) {
|
|
285
|
-
return value.replace(/_([a-z0-9])/g, (_match, character) => character.toUpperCase());
|
|
286
|
-
}
|
|
287
|
-
function journeyReferenceFields(instrument, actionName) {
|
|
288
|
-
const action = instrument.actions[actionName];
|
|
289
|
-
if (!action)
|
|
290
|
-
return new Map();
|
|
291
|
-
const fields = new Map();
|
|
292
|
-
const addSchema = (name, schema) => {
|
|
293
|
-
const pattern = recordValue(schema).pattern;
|
|
294
|
-
if (typeof pattern !== "string")
|
|
295
|
-
return;
|
|
296
|
-
const prefix = scopedIdPattern.exec(pattern)?.[1];
|
|
297
|
-
if (prefix)
|
|
298
|
-
fields.set(name, prefix);
|
|
299
|
-
};
|
|
300
|
-
if (actionName === "create") {
|
|
301
|
-
const derivedFields = new Set((action.requiresRefs ?? []).flatMap((gate) => Object.keys(gate.bind ?? {})));
|
|
302
|
-
for (const name of instrument.required) {
|
|
303
|
-
if (!derivedFields.has(name))
|
|
304
|
-
addSchema(name, instrument.fields[name]);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
fields.set(`${camel(instrument.id)}Id`, instrument.idPrefix);
|
|
309
|
-
}
|
|
310
|
-
const input = recordValue(action.input);
|
|
311
|
-
const required = Array.isArray(input.required)
|
|
312
|
-
? input.required.filter((name) => typeof name === "string")
|
|
313
|
-
: [];
|
|
314
|
-
const properties = recordValue(input.properties);
|
|
315
|
-
for (const name of required)
|
|
316
|
-
addSchema(name, properties[name]);
|
|
317
|
-
return fields;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Validate the part of authored journeys that canonical UDL can prove alone.
|
|
321
|
-
* A caller with the operation catalog validates root-operation examples and
|
|
322
|
-
* cross-kind bindings. UDL owns local examples, lifecycle order, and local
|
|
323
|
-
* reference completeness so raw UDL cannot bypass those laws.
|
|
324
|
-
*/
|
|
325
|
-
function validateInstrumentJourneys(document, add) {
|
|
326
|
-
const instruments = new Map(document.instruments.map((instrument) => [instrument.id, instrument]));
|
|
327
|
-
for (const [instrumentIndex, owner] of document.instruments.entries()) {
|
|
328
|
-
for (const [journeyIndex, journey] of (owner.journeys ?? []).entries()) {
|
|
329
|
-
const base = [
|
|
330
|
-
"instruments",
|
|
331
|
-
instrumentIndex,
|
|
332
|
-
"journeys",
|
|
333
|
-
journeyIndex,
|
|
334
|
-
];
|
|
335
|
-
const seen = new Set();
|
|
336
|
-
const createdKindByStep = new Map();
|
|
337
|
-
const stateByStep = new Map();
|
|
338
|
-
for (const [stepIndex, step] of journey.steps.entries()) {
|
|
339
|
-
const stepBase = [...base, "steps", stepIndex];
|
|
340
|
-
if (step.id) {
|
|
341
|
-
if (seen.has(step.id)) {
|
|
342
|
-
add([...stepBase, "id"], `journey ${journey.id} declares step id ${step.id} more than once`, "journey_duplicate_step_id");
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
const [instrumentId, actionName] = step.operation.split(".");
|
|
346
|
-
const instrument = instrumentId
|
|
347
|
-
? instruments.get(instrumentId)
|
|
348
|
-
: undefined;
|
|
349
|
-
const action = instrument && actionName ? instrument.actions[actionName] : undefined;
|
|
350
|
-
if (!instrument) {
|
|
351
|
-
if (step.id)
|
|
352
|
-
seen.add(step.id);
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (!action || !actionName) {
|
|
356
|
-
add([...stepBase, "operation"], `journey ${journey.id} names unknown operation ${step.operation}`, "journey_unknown_operation");
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
if (!action.examples?.some((example) => example.name === step.example)) {
|
|
360
|
-
add([...stepBase, "example"], `journey ${journey.id} names unknown example ${step.example} on ${step.operation}`, "journey_unknown_example");
|
|
361
|
-
}
|
|
362
|
-
for (const [field, prefix] of journeyReferenceFields(instrument, actionName)) {
|
|
363
|
-
const producer = step.bind[field];
|
|
364
|
-
if (!producer || !seen.has(producer)) {
|
|
365
|
-
add([...stepBase, "bind", field], `journey ${journey.id} must bind ${step.operation}.${field} to an earlier step`, "journey_unbound_reference");
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
const producedPrefix = createdKindByStep.get(producer);
|
|
369
|
-
if (producedPrefix && producedPrefix !== prefix) {
|
|
370
|
-
add([...stepBase, "bind", field], `journey ${journey.id} binds ${step.operation}.${field} to ${producer}, which creates ${producedPrefix} instead of ${prefix}`, "journey_unbound_reference");
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (actionName === "create") {
|
|
374
|
-
if (step.id) {
|
|
375
|
-
createdKindByStep.set(step.id, instrument.idPrefix);
|
|
376
|
-
stateByStep.set(step.id, instrument.lifecycle.initial);
|
|
377
|
-
seen.add(step.id);
|
|
378
|
-
}
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
const transition = instrument.lifecycle.transitions[actionName];
|
|
382
|
-
if (!transition)
|
|
383
|
-
continue;
|
|
384
|
-
const ownProducer = step.bind[`${camel(instrument.id)}Id`];
|
|
385
|
-
const state = ownProducer ? stateByStep.get(ownProducer) : undefined;
|
|
386
|
-
if (state && !transition.from.includes(state)) {
|
|
387
|
-
add([...stepBase, "operation"], `journey ${journey.id} runs ${step.operation} from ${state}; allowed states are ${transition.from.join(", ")}`, "journey_invalid_transition");
|
|
388
|
-
}
|
|
389
|
-
else if (ownProducer) {
|
|
390
|
-
stateByStep.set(ownProducer, transition.to);
|
|
391
|
-
}
|
|
392
|
-
if (step.id)
|
|
393
|
-
seen.add(step.id);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
301
|
function structuralBudgetIssue(value) {
|
|
399
302
|
let discovered = 1;
|
|
400
303
|
let nodes = 0;
|
|
@@ -915,76 +818,272 @@ function validateInstrument(instrument, instrumentIndex, instruments, subjects,
|
|
|
915
818
|
}
|
|
916
819
|
}
|
|
917
820
|
}
|
|
918
|
-
|
|
919
|
-
|
|
821
|
+
for (const finIssue of instrumentFinanceIssues(instrument, {
|
|
822
|
+
plans: planResolution.plans,
|
|
823
|
+
})) {
|
|
824
|
+
add([...base, ...finIssue.path], finIssue.message, finIssue.code);
|
|
825
|
+
}
|
|
826
|
+
validateAggregates(instrument, base, instruments, references, add);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* The finance oracle over an instrument's resolved action plans, with paths
|
|
830
|
+
* rooted at the instrument. A piece-plan instrument is unfolded over the
|
|
831
|
+
* runtime's piece progress; any other instrument runs once per action plan
|
|
832
|
+
* combination. Contract-side callers pass the UDL projection of a blueprint
|
|
833
|
+
* definition so both boundaries prove the same machine.
|
|
834
|
+
*/
|
|
835
|
+
export function instrumentFinanceIssues(instrument, options = {}) {
|
|
836
|
+
const plans = options.plans ?? resolveUdlActionPlans(instrument).plans;
|
|
837
|
+
const financeOptions = options.penaltyMayBeNonzero === undefined
|
|
838
|
+
? {}
|
|
839
|
+
: { penaltyMayBeNonzero: options.penaltyMayBeNonzero };
|
|
840
|
+
const issues = [];
|
|
841
|
+
const seen = new Set();
|
|
842
|
+
const add = (path, message, code) => {
|
|
843
|
+
const key = `${code}:${path.join(".")}:${message}`;
|
|
844
|
+
if (seen.has(key))
|
|
845
|
+
return;
|
|
846
|
+
seen.add(key);
|
|
847
|
+
issues.push({ code, message, path });
|
|
848
|
+
};
|
|
849
|
+
const expansion = instrument.piecePlan
|
|
850
|
+
? expandPieceProgress(instrument, plans)
|
|
851
|
+
: undefined;
|
|
852
|
+
if (expansion === "bound") {
|
|
853
|
+
add(["actions"], `piece progress expansion exceeds reachable variant bound of ${UDL_LIMITS.maxActionExpansion}`, "UDL2010");
|
|
854
|
+
return issues;
|
|
855
|
+
}
|
|
856
|
+
if (expansion) {
|
|
857
|
+
for (const finIssue of analyzeInstrumentFinance(expansion.instrument, financeOptions)) {
|
|
858
|
+
const message = expansion.displayNames.reduce((text, [expanded, display]) => text.replaceAll(expanded, display), finIssue.message);
|
|
859
|
+
add(originFinancePath(expansion, finIssue.path), message, finIssue.code);
|
|
860
|
+
}
|
|
861
|
+
return issues;
|
|
862
|
+
}
|
|
863
|
+
const actionsWithPlans = Object.keys(instrument.actions).filter((aName) => plans.some((p) => p.action === aName));
|
|
920
864
|
let totalCombinations = 1;
|
|
921
865
|
const actionPlanMap = {};
|
|
922
866
|
for (const aName of actionsWithPlans) {
|
|
923
|
-
const actionPlans =
|
|
867
|
+
const actionPlans = plans.filter((p) => p.action === aName);
|
|
924
868
|
actionPlanMap[aName] = actionPlans;
|
|
925
869
|
totalCombinations *= actionPlans.length;
|
|
926
870
|
}
|
|
927
871
|
if (actionsWithPlans.length > 0 &&
|
|
928
872
|
totalCombinations > UDL_LIMITS.maxActionExpansion) {
|
|
929
|
-
add([
|
|
873
|
+
add(["actions"], `variant expansion exceeds combination bound of ${UDL_LIMITS.maxActionExpansion} (${totalCombinations} combinations)`, "UDL2010");
|
|
874
|
+
return issues;
|
|
930
875
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
for (const
|
|
939
|
-
|
|
940
|
-
result.push({ ...combo, [first]: plan });
|
|
941
|
-
}
|
|
876
|
+
const generateCombos = (keys) => {
|
|
877
|
+
if (keys.length === 0)
|
|
878
|
+
return [{}];
|
|
879
|
+
const [first, ...rest] = keys;
|
|
880
|
+
const restCombos = generateCombos(rest);
|
|
881
|
+
const result = [];
|
|
882
|
+
for (const plan of actionPlanMap[first]) {
|
|
883
|
+
for (const combo of restCombos) {
|
|
884
|
+
result.push({ ...combo, [first]: plan });
|
|
942
885
|
}
|
|
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
|
-
|
|
886
|
+
}
|
|
887
|
+
return result;
|
|
888
|
+
};
|
|
889
|
+
const combinations = actionsWithPlans.length > 0 ? generateCombos(actionsWithPlans) : [{}];
|
|
890
|
+
for (const combo of combinations) {
|
|
891
|
+
const expandedActions = {};
|
|
892
|
+
for (const [aName, aDef] of Object.entries(instrument.actions)) {
|
|
893
|
+
expandedActions[aName] = planExpandedAction(aDef, combo[aName]);
|
|
894
|
+
}
|
|
895
|
+
for (const finIssue of analyzeInstrumentFinance({ ...instrument, actions: expandedActions }, financeOptions)) {
|
|
896
|
+
add(finIssue.path, finIssue.message, finIssue.code);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return issues;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Only an action that moves money through calls is replaced by its expanded
|
|
903
|
+
* leaves. Authored moves and steps always reach the oracle.
|
|
904
|
+
*/
|
|
905
|
+
function planExpandedAction(definition, plan) {
|
|
906
|
+
if (!plan || (definition.calls?.length ?? 0) === 0)
|
|
907
|
+
return definition;
|
|
908
|
+
const steps = [];
|
|
909
|
+
const moves = [];
|
|
910
|
+
for (const leaf of plan.leaves) {
|
|
911
|
+
if ("key" in leaf.step) {
|
|
912
|
+
moves.push(leaf.step);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
steps.push(leaf.step);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return { ...definition, moves, steps };
|
|
919
|
+
}
|
|
920
|
+
function progressKey(progress) {
|
|
921
|
+
return `${progress.funded.join(",")}|${progress.consumed.join(",")}`;
|
|
922
|
+
}
|
|
923
|
+
/** Mirrors eligiblePieces in the engine's piece-plan dispatcher. */
|
|
924
|
+
function eligiblePieces(plan, stage, progress) {
|
|
925
|
+
return plan[`${stage}_order`].filter((id) => stage === "fund"
|
|
926
|
+
? !progress.funded.includes(id)
|
|
927
|
+
: progress.funded.includes(id) && !progress.consumed.includes(id));
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Unfolds a piece-plan instrument over the runtime's piece progress so the
|
|
931
|
+
* ordinary lifecycle oracle sees exactly the states the dispatcher admits: a
|
|
932
|
+
* piece-stage action moves the next eligible piece of its stage order, the
|
|
933
|
+
* lifecycle state is retained until the stage's last piece moves, funding
|
|
934
|
+
* cannot resume once a piece has left escrow, and an action gated on a drained
|
|
935
|
+
* account is closed while a funded piece is still held. Every expanded state is
|
|
936
|
+
* one (lifecycle state, progress) pair; expanded action names carry the piece
|
|
937
|
+
* and the source state so each has exactly one transition. A quote-commit pair
|
|
938
|
+
* is not carried through the expansion.
|
|
939
|
+
*/
|
|
940
|
+
function expandPieceProgress(instrument, plans) {
|
|
941
|
+
const plan = instrument.piecePlan;
|
|
942
|
+
if (!plan)
|
|
943
|
+
return undefined;
|
|
944
|
+
const stateName = (state, progress) => `${state}#${progressKey(progress)}`;
|
|
945
|
+
const initialProgress = { funded: [], consumed: [] };
|
|
946
|
+
const stateOrigins = new Map();
|
|
947
|
+
const actionOrigins = new Map();
|
|
948
|
+
const displayNames = [];
|
|
949
|
+
const actions = {};
|
|
950
|
+
const transitions = {};
|
|
951
|
+
const pending = [
|
|
952
|
+
{ state: instrument.lifecycle.initial, progress: initialProgress },
|
|
953
|
+
];
|
|
954
|
+
stateOrigins.set(stateName(instrument.lifecycle.initial, initialProgress), instrument.lifecycle.initial);
|
|
955
|
+
const visit = (state, progress) => {
|
|
956
|
+
const name = stateName(state, progress);
|
|
957
|
+
if (stateOrigins.has(name))
|
|
958
|
+
return;
|
|
959
|
+
stateOrigins.set(name, state);
|
|
960
|
+
pending.push({ state, progress });
|
|
961
|
+
};
|
|
962
|
+
while (pending.length > 0) {
|
|
963
|
+
const { state, progress } = pending.shift();
|
|
964
|
+
if (stateOrigins.size > UDL_LIMITS.maxActionExpansion)
|
|
965
|
+
return "bound";
|
|
966
|
+
const held = progress.funded.filter((id) => !progress.consumed.includes(id));
|
|
967
|
+
for (const [actionName, transition] of Object.entries(instrument.lifecycle.transitions)) {
|
|
968
|
+
if (!transition.from.includes(state))
|
|
969
|
+
continue;
|
|
970
|
+
const definition = instrument.actions[actionName];
|
|
971
|
+
if (!definition)
|
|
972
|
+
continue;
|
|
973
|
+
if (definition.requiresDrainedAccount && held.length > 0)
|
|
974
|
+
continue;
|
|
975
|
+
const stage = definition.pieceStage;
|
|
976
|
+
if (!stage) {
|
|
977
|
+
const expanded = `${actionName}#${state}#${progressKey(progress)}`;
|
|
978
|
+
const variant = plans.find((candidate) => candidate.action === actionName);
|
|
979
|
+
actions[expanded] = planExpandedAction(definition, variant);
|
|
980
|
+
transitions[expanded] = {
|
|
981
|
+
from: [stateName(state, progress)],
|
|
982
|
+
to: stateName(transition.to, progress),
|
|
983
|
+
};
|
|
984
|
+
actionOrigins.set(expanded, {
|
|
985
|
+
action: actionName,
|
|
986
|
+
...(variant && (definition.calls?.length ?? 0) > 0
|
|
987
|
+
? { leafOrigins: variant.leaves.map((leaf) => leaf.originPath) }
|
|
988
|
+
: {}),
|
|
989
|
+
});
|
|
990
|
+
displayNames.push([expanded, actionName]);
|
|
991
|
+
visit(transition.to, progress);
|
|
992
|
+
continue;
|
|
973
993
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
994
|
+
if (stage.plan !== plan.id)
|
|
995
|
+
continue;
|
|
996
|
+
if (stage.stage === "fund" && progress.consumed.length > 0)
|
|
997
|
+
continue;
|
|
998
|
+
const eligible = eligiblePieces(plan, stage.stage, progress);
|
|
999
|
+
const pieceId = eligible[0];
|
|
1000
|
+
if (pieceId === undefined)
|
|
1001
|
+
continue;
|
|
1002
|
+
const variant = plans.find((candidate) => candidate.action === actionName && candidate.pieceId === pieceId);
|
|
1003
|
+
if (!variant)
|
|
1004
|
+
continue;
|
|
1005
|
+
const next = stage.stage === "fund"
|
|
1006
|
+
? {
|
|
1007
|
+
funded: [...progress.funded, pieceId],
|
|
1008
|
+
consumed: progress.consumed,
|
|
983
1009
|
}
|
|
984
|
-
|
|
1010
|
+
: {
|
|
1011
|
+
funded: progress.funded,
|
|
1012
|
+
consumed: [...progress.consumed, pieceId],
|
|
1013
|
+
};
|
|
1014
|
+
const stageComplete = eligiblePieces(plan, stage.stage, next).length === 0;
|
|
1015
|
+
const target = stageComplete ? transition.to : state;
|
|
1016
|
+
const expanded = `${actionName}@${pieceId}#${state}#${progressKey(progress)}`;
|
|
1017
|
+
actions[expanded] = planExpandedAction(definition, variant);
|
|
1018
|
+
transitions[expanded] = {
|
|
1019
|
+
from: [stateName(state, progress)],
|
|
1020
|
+
to: stateName(target, next),
|
|
1021
|
+
};
|
|
1022
|
+
actionOrigins.set(expanded, {
|
|
1023
|
+
action: actionName,
|
|
1024
|
+
leafOrigins: variant.leaves.map((leaf) => leaf.originPath),
|
|
1025
|
+
});
|
|
1026
|
+
displayNames.push([expanded, `${actionName}[${pieceId}]`]);
|
|
1027
|
+
visit(target, next);
|
|
985
1028
|
}
|
|
986
1029
|
}
|
|
987
|
-
|
|
1030
|
+
for (const [actionName, definition] of Object.entries(instrument.actions)) {
|
|
1031
|
+
if (Object.hasOwn(instrument.lifecycle.transitions, actionName))
|
|
1032
|
+
continue;
|
|
1033
|
+
actions[actionName] = planExpandedAction(definition, plans.find((candidate) => candidate.action === actionName));
|
|
1034
|
+
actionOrigins.set(actionName, { action: actionName });
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
instrument: {
|
|
1038
|
+
...instrument,
|
|
1039
|
+
actions,
|
|
1040
|
+
lifecycle: {
|
|
1041
|
+
initial: stateName(instrument.lifecycle.initial, initialProgress),
|
|
1042
|
+
states: [...stateOrigins.keys()],
|
|
1043
|
+
transitions,
|
|
1044
|
+
},
|
|
1045
|
+
},
|
|
1046
|
+
displayNames: [
|
|
1047
|
+
...displayNames,
|
|
1048
|
+
...[...stateOrigins.entries()].map(([expanded, origin]) => [expanded, origin]),
|
|
1049
|
+
].sort((left, right) => right[0].length - left[0].length),
|
|
1050
|
+
actionOrigins,
|
|
1051
|
+
stateOrigins,
|
|
1052
|
+
originStates: instrument.lifecycle.states,
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function originFinancePath(expansion, path) {
|
|
1056
|
+
const [head, second, third, fourth, ...rest] = path;
|
|
1057
|
+
if (head === "lifecycle" &&
|
|
1058
|
+
second === "states" &&
|
|
1059
|
+
typeof third === "number") {
|
|
1060
|
+
const expandedState = expansion.instrument.lifecycle.states[third];
|
|
1061
|
+
const origin = expandedState === undefined
|
|
1062
|
+
? undefined
|
|
1063
|
+
: expansion.stateOrigins.get(expandedState);
|
|
1064
|
+
const index = origin === undefined ? -1 : expansion.originStates.indexOf(origin);
|
|
1065
|
+
return index >= 0
|
|
1066
|
+
? ["lifecycle", "states", index]
|
|
1067
|
+
: ["lifecycle", "states"];
|
|
1068
|
+
}
|
|
1069
|
+
if (head === "actions" && typeof second === "string") {
|
|
1070
|
+
const origin = expansion.actionOrigins.get(second);
|
|
1071
|
+
if (!origin)
|
|
1072
|
+
return path;
|
|
1073
|
+
if (third === "moves" &&
|
|
1074
|
+
typeof fourth === "number" &&
|
|
1075
|
+
origin.leafOrigins?.[fourth]) {
|
|
1076
|
+
return ["actions", ...origin.leafOrigins[fourth], ...rest];
|
|
1077
|
+
}
|
|
1078
|
+
return [
|
|
1079
|
+
"actions",
|
|
1080
|
+
origin.action,
|
|
1081
|
+
...(third === undefined ? [] : [third]),
|
|
1082
|
+
...(fourth === undefined ? [] : [fourth]),
|
|
1083
|
+
...rest,
|
|
1084
|
+
];
|
|
1085
|
+
}
|
|
1086
|
+
return path;
|
|
988
1087
|
}
|
|
989
1088
|
function validatePiecePlan(instrument, base, references, add) {
|
|
990
1089
|
const plan = instrument.piecePlan;
|
|
@@ -1633,7 +1732,9 @@ function validateActionUpdates(instrument, action, definition, actionBase, add)
|
|
|
1633
1732
|
add(fieldPath, `updated field ${field} is an aggregate cap`, "UDL5008");
|
|
1634
1733
|
}
|
|
1635
1734
|
});
|
|
1636
|
-
if (definition.moves.length > 0
|
|
1735
|
+
if (definition.moves.length > 0 ||
|
|
1736
|
+
definition.allocate ||
|
|
1737
|
+
definition.contributionStage) {
|
|
1637
1738
|
add([...actionBase, "updates"], "an action cannot update fields while moving money", "UDL5008");
|
|
1638
1739
|
}
|
|
1639
1740
|
}
|
|
@@ -1666,6 +1767,19 @@ function validateRemainder(instrument, action, definition, actionBase, instrumen
|
|
|
1666
1767
|
remainder.accumulateRef === remainder.amountRef) {
|
|
1667
1768
|
add([...remainderBase, "accumulateRef"], "remainder accumulateRef must differ from amountRef", "UDL4001");
|
|
1668
1769
|
}
|
|
1770
|
+
addDuplicateIssues(remainder.subtractPaths ?? [], [...remainderBase, "subtractPaths"], "remainder operand", add);
|
|
1771
|
+
for (const path of remainder.subtractPaths ?? []) {
|
|
1772
|
+
const [root, key] = path.split(".");
|
|
1773
|
+
const declared = root === "fields" &&
|
|
1774
|
+
key !== undefined &&
|
|
1775
|
+
isMoneySchema(instrument.fields[key] ?? {}) &&
|
|
1776
|
+
!instrument.update?.fields.includes(key) &&
|
|
1777
|
+
!Object.values(instrument.actions).some((action) => action.updates?.includes(key));
|
|
1778
|
+
if (!declared ||
|
|
1779
|
+
path === remainder.totalPath ||
|
|
1780
|
+
path === `refs.${remainder.amountRef}`)
|
|
1781
|
+
add([...remainderBase, "subtractPaths"], `remainder subtraction ${path} must name distinct immutable money`, "UDL4001");
|
|
1782
|
+
}
|
|
1669
1783
|
const [totalRoot, totalKey] = remainder.totalPath.split(".");
|
|
1670
1784
|
const totalDeclared = (totalRoot === "fields" &&
|
|
1671
1785
|
totalKey !== undefined &&
|
|
@@ -1750,7 +1864,17 @@ function validateActions(instrument, base, instruments, references, add) {
|
|
|
1750
1864
|
definition.steps.forEach((step, stepIndex) => validateStep(instrument, definition, step, [...actionBase, "steps", stepIndex], add));
|
|
1751
1865
|
definition.moves.forEach((move, moveIndex) => validateStep(instrument, definition, move, [...actionBase, "moves", moveIndex], add));
|
|
1752
1866
|
if (definition.requiresRefs) {
|
|
1753
|
-
|
|
1867
|
+
const boundFields = new Map();
|
|
1868
|
+
for (const gate of definition.requiresRefs) {
|
|
1869
|
+
for (const [field, path] of Object.entries(gate.bind ?? {})) {
|
|
1870
|
+
const source = `${gate.field}:${path}`;
|
|
1871
|
+
const previous = boundFields.get(field);
|
|
1872
|
+
if (previous !== undefined && previous !== source)
|
|
1873
|
+
add([...actionBase, "requiresRefs"], `reference gates bind ${field} from conflicting sources`, "UDL5001");
|
|
1874
|
+
boundFields.set(field, source);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
addDuplicateIssues(definition.requiresRefs.map((gate) => JSON.stringify(sortObject(gate))), [...actionBase, "requiresRefs"], "gate", add);
|
|
1754
1878
|
definition.requiresRefs.forEach((gate, gateIndex) => {
|
|
1755
1879
|
addDuplicateIssues(gate.statuses, [...actionBase, "requiresRefs", gateIndex, "statuses"], "gate status", add);
|
|
1756
1880
|
const schema = instrument.fields[gate.field];
|
|
@@ -1811,6 +1935,7 @@ function validateActions(instrument, base, instruments, references, add) {
|
|
|
1811
1935
|
const existingRefs = new Set([
|
|
1812
1936
|
...Object.entries(instrument.actions).flatMap(([candidateAction, candidate]) => [
|
|
1813
1937
|
...Object.keys(candidate.captureInput ?? {}),
|
|
1938
|
+
...Object.keys(candidate.captureEngine ?? {}),
|
|
1814
1939
|
...[...candidate.steps, ...candidate.moves].flatMap((step) => Object.keys(step.capture ?? {})),
|
|
1815
1940
|
...(candidateAction !== action && candidate.signedSum
|
|
1816
1941
|
? [
|
|
@@ -1914,7 +2039,7 @@ function validateActions(instrument, base, instruments, references, add) {
|
|
|
1914
2039
|
else if (!isDateTimeFormat(schema.format)) {
|
|
1915
2040
|
add([...actionBase, "due", "field"], "due field must be a date-time field", "UDL5008");
|
|
1916
2041
|
}
|
|
1917
|
-
if (definition.due.offset &&
|
|
2042
|
+
if (typeof definition.due.offset === "string" &&
|
|
1918
2043
|
fixedIsoDurationMs(definition.due.offset) === null) {
|
|
1919
2044
|
add([...actionBase, "due", "offset"], "due.offset must be a fixed ISO-8601 duration using weeks, days, hours, minutes, or seconds", "UDL3001");
|
|
1920
2045
|
}
|
|
@@ -1931,7 +2056,7 @@ function validateActions(instrument, base, instruments, references, add) {
|
|
|
1931
2056
|
else if (!isDateTimeFormat(schema.format)) {
|
|
1932
2057
|
add([...actionBase, "deadline", "field"], "deadline field must be a date-time field", "UDL5008");
|
|
1933
2058
|
}
|
|
1934
|
-
if (definition.deadline.offset &&
|
|
2059
|
+
if (typeof definition.deadline.offset === "string" &&
|
|
1935
2060
|
fixedIsoDurationMs(definition.deadline.offset) === null) {
|
|
1936
2061
|
add([...actionBase, "deadline", "offset"], "deadline.offset must be a fixed ISO-8601 duration using weeks, days, hours, minutes, or seconds", "UDL3001");
|
|
1937
2062
|
}
|
|
@@ -2235,7 +2360,10 @@ function validatePayoutsAndSettlement(instrument, base, instruments, references,
|
|
|
2235
2360
|
}
|
|
2236
2361
|
}
|
|
2237
2362
|
const reservedRefs = new Set([
|
|
2238
|
-
...Object.values(instrument.actions).flatMap((action) =>
|
|
2363
|
+
...Object.values(instrument.actions).flatMap((action) => [
|
|
2364
|
+
...Object.keys(action.captureInput ?? {}),
|
|
2365
|
+
...Object.keys(action.captureEngine ?? {}),
|
|
2366
|
+
]),
|
|
2239
2367
|
...Object.values(instrument.actions).flatMap((action) => [...action.steps, ...action.moves].flatMap((step) => Object.keys(step.capture ?? {}))),
|
|
2240
2368
|
...Object.values(instrument.actions).flatMap((action) => action.signedSum
|
|
2241
2369
|
? [
|
|
@@ -2252,6 +2380,7 @@ function validatePayoutsAndSettlement(instrument, base, instruments, references,
|
|
|
2252
2380
|
: []),
|
|
2253
2381
|
]
|
|
2254
2382
|
: []),
|
|
2383
|
+
...Object.values(instrument.actions).flatMap((action) => action.allocate ? [action.allocate.capture] : []),
|
|
2255
2384
|
...quoteRefKeys(instrument),
|
|
2256
2385
|
...(instrument.subject ? ["subject"] : []),
|
|
2257
2386
|
]);
|
|
@@ -2552,7 +2681,10 @@ function declaredRefKeys(instrument) {
|
|
|
2552
2681
|
return new Set([
|
|
2553
2682
|
...Object.values(instrument.actions).flatMap((action) => action.payout ? [action.payout.capture] : []),
|
|
2554
2683
|
...Object.values(instrument.actions).flatMap((action) => (action.reconcile ?? []).map((reconcile) => reconcile.capture)),
|
|
2555
|
-
...Object.values(instrument.actions).flatMap((action) =>
|
|
2684
|
+
...Object.values(instrument.actions).flatMap((action) => [
|
|
2685
|
+
...Object.keys(action.captureInput ?? {}),
|
|
2686
|
+
...Object.keys(action.captureEngine ?? {}),
|
|
2687
|
+
]),
|
|
2556
2688
|
...Object.values(instrument.actions).flatMap((action) => [...action.steps, ...action.moves].flatMap((step) => Object.keys(step.capture ?? {}))),
|
|
2557
2689
|
...Object.values(instrument.actions).flatMap((action) => action.signedSum
|
|
2558
2690
|
? [
|
|
@@ -2560,6 +2692,7 @@ function declaredRefKeys(instrument) {
|
|
|
2560
2692
|
...action.signedSum.sources.map((source) => source.subtotalRef),
|
|
2561
2693
|
]
|
|
2562
2694
|
: []),
|
|
2695
|
+
...Object.values(instrument.actions).flatMap((action) => action.allocate ? [action.allocate.capture] : []),
|
|
2563
2696
|
...quoteRefKeys(instrument),
|
|
2564
2697
|
...(instrument.subject ? ["subject"] : []),
|
|
2565
2698
|
]);
|
|
@@ -3034,25 +3167,23 @@ function reconcileExceptionProblemMessage(code, parent, child, exception) {
|
|
|
3034
3167
|
* are written once. The value is disposable: no document ever carries it, and
|
|
3035
3168
|
* a host's real id grammar stays the host's business.
|
|
3036
3169
|
*/
|
|
3037
|
-
|
|
3038
|
-
/** A prefix no host mints, so a schema that accepts it accepts anything. */
|
|
3039
|
-
const UNCLAIMED_PREFIX = "zzzz";
|
|
3170
|
+
/** Classify the sealed ID pattern without compiling document-authored regexes. */
|
|
3040
3171
|
export function openReferenceShapeBudget() {
|
|
3041
3172
|
const answers = new WeakMap();
|
|
3042
3173
|
let probes = 0;
|
|
3174
|
+
let exhausted = false;
|
|
3043
3175
|
return {
|
|
3044
3176
|
accepts(schema, prefix) {
|
|
3045
3177
|
const seen = answers.get(schema);
|
|
3046
3178
|
const cached = seen?.get(prefix);
|
|
3047
3179
|
if (cached !== undefined)
|
|
3048
3180
|
return cached;
|
|
3049
|
-
if (probes >= UDL_LIMITS.maxSchemaProbes)
|
|
3181
|
+
if (probes >= UDL_LIMITS.maxSchemaProbes) {
|
|
3182
|
+
exhausted = true;
|
|
3050
3183
|
return false;
|
|
3184
|
+
}
|
|
3051
3185
|
probes += 1;
|
|
3052
|
-
const answer =
|
|
3053
|
-
0 &&
|
|
3054
|
-
validateUdlSchemaValue(schema, probeIdFor(UNCLAIMED_PREFIX)).errors
|
|
3055
|
-
.length > 0;
|
|
3186
|
+
const answer = referencePatternPrefix(schema) === prefix;
|
|
3056
3187
|
if (seen)
|
|
3057
3188
|
seen.set(prefix, answer);
|
|
3058
3189
|
else
|
|
@@ -3060,7 +3191,7 @@ export function openReferenceShapeBudget() {
|
|
|
3060
3191
|
return answer;
|
|
3061
3192
|
},
|
|
3062
3193
|
get exhausted() {
|
|
3063
|
-
return
|
|
3194
|
+
return exhausted;
|
|
3064
3195
|
},
|
|
3065
3196
|
};
|
|
3066
3197
|
}
|
|
@@ -3155,4 +3286,13 @@ function jsonPath(path) {
|
|
|
3155
3286
|
}
|
|
3156
3287
|
return result;
|
|
3157
3288
|
}
|
|
3289
|
+
function sortObject(value) {
|
|
3290
|
+
if (Array.isArray(value))
|
|
3291
|
+
return value.map(sortObject);
|
|
3292
|
+
if (value !== null && typeof value === "object")
|
|
3293
|
+
return Object.fromEntries(Object.entries(value)
|
|
3294
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
3295
|
+
.map(([key, item]) => [key, sortObject(item)]));
|
|
3296
|
+
return value;
|
|
3297
|
+
}
|
|
3158
3298
|
//# sourceMappingURL=validation.js.map
|