@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/src/validation.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { referencePatternPrefix } from "./reference.js";
|
|
2
|
+
import { validateVocabulary } from "./vocabulary.js";
|
|
1
3
|
import {
|
|
2
4
|
Validator,
|
|
3
5
|
type OutputUnit,
|
|
@@ -19,11 +21,14 @@ import {
|
|
|
19
21
|
type UdlInstrument,
|
|
20
22
|
type UdlStep,
|
|
21
23
|
type UdlAction,
|
|
24
|
+
type UdlPieceStageStage,
|
|
22
25
|
} from "./schema.js";
|
|
23
26
|
import { fixedIsoDurationMs } from "./duration.js";
|
|
24
27
|
import {
|
|
25
28
|
analyzeInstrumentFinance,
|
|
26
29
|
financeAdmissionProblem,
|
|
30
|
+
type FinanceIssue,
|
|
31
|
+
type FinanceOptions,
|
|
27
32
|
} from "./finance.js";
|
|
28
33
|
import { UDL_LIMITS } from "./limits.js";
|
|
29
34
|
import {
|
|
@@ -65,6 +70,12 @@ export interface UdlValidationOptions {
|
|
|
65
70
|
readonly requireDecisionPartyBindings?: boolean;
|
|
66
71
|
}
|
|
67
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Validate a complete document. Clause references to instruments must resolve
|
|
75
|
+
* within this document, even when the reference field is optional. A catalogue
|
|
76
|
+
* slice must include its referenced instruments; an external catalogue cannot
|
|
77
|
+
* supply missing product laws. Reference fields use the sealed public ID grammar.
|
|
78
|
+
*/
|
|
68
79
|
export function validateUdl(
|
|
69
80
|
value: unknown,
|
|
70
81
|
options: UdlValidationOptions = {},
|
|
@@ -293,6 +304,26 @@ function semanticIssues(
|
|
|
293
304
|
validateDocumentSchemas(document, add);
|
|
294
305
|
if (issues.length > 0) return issues;
|
|
295
306
|
|
|
307
|
+
// Vocabulary laws must accumulate with the existing semantic diagnostics.
|
|
308
|
+
validateVocabulary(document.instruments, (path, message) =>
|
|
309
|
+
add(
|
|
310
|
+
path,
|
|
311
|
+
message,
|
|
312
|
+
path.some((part) =>
|
|
313
|
+
[
|
|
314
|
+
"attests",
|
|
315
|
+
"requiresInput",
|
|
316
|
+
"engineOwned",
|
|
317
|
+
"captureEngine",
|
|
318
|
+
"unique",
|
|
319
|
+
"port",
|
|
320
|
+
].includes(String(part)),
|
|
321
|
+
)
|
|
322
|
+
? "UDL5001"
|
|
323
|
+
: shapeIssueCode(path),
|
|
324
|
+
),
|
|
325
|
+
);
|
|
326
|
+
|
|
296
327
|
addDuplicateIssues(
|
|
297
328
|
document.subjects.map((subject) => subject.kind),
|
|
298
329
|
["subjects"],
|
|
@@ -313,7 +344,6 @@ function semanticIssues(
|
|
|
313
344
|
);
|
|
314
345
|
|
|
315
346
|
validateCompositionDials(document, add);
|
|
316
|
-
validateInstrumentJourneys(document, add);
|
|
317
347
|
|
|
318
348
|
const subjects = new Map(
|
|
319
349
|
document.subjects.map((subject) => [subject.kind, subject] as const),
|
|
@@ -413,162 +443,6 @@ function semanticIssues(
|
|
|
413
443
|
return issues;
|
|
414
444
|
}
|
|
415
445
|
|
|
416
|
-
const scopedIdPattern =
|
|
417
|
-
/^\^([a-z]{2,8})_\(sandbox\|live\)_\[a-z0-9\]\{8,64\}\$$/;
|
|
418
|
-
|
|
419
|
-
function camel(value: string): string {
|
|
420
|
-
return value.replace(/_([a-z0-9])/g, (_match, character: string) =>
|
|
421
|
-
character.toUpperCase(),
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function journeyReferenceFields(
|
|
426
|
-
instrument: UdlInstrument,
|
|
427
|
-
actionName: string,
|
|
428
|
-
): ReadonlyMap<string, string> {
|
|
429
|
-
const action = instrument.actions[actionName];
|
|
430
|
-
if (!action) return new Map();
|
|
431
|
-
const fields = new Map<string, string>();
|
|
432
|
-
const addSchema = (name: string, schema: unknown) => {
|
|
433
|
-
const pattern = recordValue(schema).pattern;
|
|
434
|
-
if (typeof pattern !== "string") return;
|
|
435
|
-
const prefix = scopedIdPattern.exec(pattern)?.[1];
|
|
436
|
-
if (prefix) fields.set(name, prefix);
|
|
437
|
-
};
|
|
438
|
-
if (actionName === "create") {
|
|
439
|
-
const derivedFields = new Set(
|
|
440
|
-
(action.requiresRefs ?? []).flatMap((gate) =>
|
|
441
|
-
Object.keys(gate.bind ?? {}),
|
|
442
|
-
),
|
|
443
|
-
);
|
|
444
|
-
for (const name of instrument.required) {
|
|
445
|
-
if (!derivedFields.has(name)) addSchema(name, instrument.fields[name]);
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
fields.set(`${camel(instrument.id)}Id`, instrument.idPrefix);
|
|
449
|
-
}
|
|
450
|
-
const input = recordValue(action.input);
|
|
451
|
-
const required = Array.isArray(input.required)
|
|
452
|
-
? input.required.filter((name): name is string => typeof name === "string")
|
|
453
|
-
: [];
|
|
454
|
-
const properties = recordValue(input.properties);
|
|
455
|
-
for (const name of required) addSchema(name, properties[name]);
|
|
456
|
-
return fields;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Validate the part of authored journeys that canonical UDL can prove alone.
|
|
461
|
-
* A caller with the operation catalog validates root-operation examples and
|
|
462
|
-
* cross-kind bindings. UDL owns local examples, lifecycle order, and local
|
|
463
|
-
* reference completeness so raw UDL cannot bypass those laws.
|
|
464
|
-
*/
|
|
465
|
-
function validateInstrumentJourneys(
|
|
466
|
-
document: UdlDocument,
|
|
467
|
-
add: AddIssue,
|
|
468
|
-
): void {
|
|
469
|
-
const instruments = new Map(
|
|
470
|
-
document.instruments.map(
|
|
471
|
-
(instrument) => [instrument.id, instrument] as const,
|
|
472
|
-
),
|
|
473
|
-
);
|
|
474
|
-
for (const [instrumentIndex, owner] of document.instruments.entries()) {
|
|
475
|
-
for (const [journeyIndex, journey] of (owner.journeys ?? []).entries()) {
|
|
476
|
-
const base = [
|
|
477
|
-
"instruments",
|
|
478
|
-
instrumentIndex,
|
|
479
|
-
"journeys",
|
|
480
|
-
journeyIndex,
|
|
481
|
-
] as const;
|
|
482
|
-
const seen = new Set<string>();
|
|
483
|
-
const createdKindByStep = new Map<string, string>();
|
|
484
|
-
const stateByStep = new Map<string, string>();
|
|
485
|
-
for (const [stepIndex, step] of journey.steps.entries()) {
|
|
486
|
-
const stepBase = [...base, "steps", stepIndex] as const;
|
|
487
|
-
if (step.id) {
|
|
488
|
-
if (seen.has(step.id)) {
|
|
489
|
-
add(
|
|
490
|
-
[...stepBase, "id"],
|
|
491
|
-
`journey ${journey.id} declares step id ${step.id} more than once`,
|
|
492
|
-
"journey_duplicate_step_id",
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
const [instrumentId, actionName] = step.operation.split(".");
|
|
497
|
-
const instrument = instrumentId
|
|
498
|
-
? instruments.get(instrumentId)
|
|
499
|
-
: undefined;
|
|
500
|
-
const action =
|
|
501
|
-
instrument && actionName ? instrument.actions[actionName] : undefined;
|
|
502
|
-
if (!instrument) {
|
|
503
|
-
if (step.id) seen.add(step.id);
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (!action || !actionName) {
|
|
507
|
-
add(
|
|
508
|
-
[...stepBase, "operation"],
|
|
509
|
-
`journey ${journey.id} names unknown operation ${step.operation}`,
|
|
510
|
-
"journey_unknown_operation",
|
|
511
|
-
);
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
if (
|
|
515
|
-
!action.examples?.some((example) => example.name === step.example)
|
|
516
|
-
) {
|
|
517
|
-
add(
|
|
518
|
-
[...stepBase, "example"],
|
|
519
|
-
`journey ${journey.id} names unknown example ${step.example} on ${step.operation}`,
|
|
520
|
-
"journey_unknown_example",
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
for (const [field, prefix] of journeyReferenceFields(
|
|
524
|
-
instrument,
|
|
525
|
-
actionName,
|
|
526
|
-
)) {
|
|
527
|
-
const producer = step.bind[field];
|
|
528
|
-
if (!producer || !seen.has(producer)) {
|
|
529
|
-
add(
|
|
530
|
-
[...stepBase, "bind", field],
|
|
531
|
-
`journey ${journey.id} must bind ${step.operation}.${field} to an earlier step`,
|
|
532
|
-
"journey_unbound_reference",
|
|
533
|
-
);
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
const producedPrefix = createdKindByStep.get(producer);
|
|
537
|
-
if (producedPrefix && producedPrefix !== prefix) {
|
|
538
|
-
add(
|
|
539
|
-
[...stepBase, "bind", field],
|
|
540
|
-
`journey ${journey.id} binds ${step.operation}.${field} to ${producer}, which creates ${producedPrefix} instead of ${prefix}`,
|
|
541
|
-
"journey_unbound_reference",
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (actionName === "create") {
|
|
546
|
-
if (step.id) {
|
|
547
|
-
createdKindByStep.set(step.id, instrument.idPrefix);
|
|
548
|
-
stateByStep.set(step.id, instrument.lifecycle.initial);
|
|
549
|
-
seen.add(step.id);
|
|
550
|
-
}
|
|
551
|
-
continue;
|
|
552
|
-
}
|
|
553
|
-
const transition = instrument.lifecycle.transitions[actionName];
|
|
554
|
-
if (!transition) continue;
|
|
555
|
-
const ownProducer = step.bind[`${camel(instrument.id)}Id`];
|
|
556
|
-
const state = ownProducer ? stateByStep.get(ownProducer) : undefined;
|
|
557
|
-
if (state && !transition.from.includes(state)) {
|
|
558
|
-
add(
|
|
559
|
-
[...stepBase, "operation"],
|
|
560
|
-
`journey ${journey.id} runs ${step.operation} from ${state}; allowed states are ${transition.from.join(", ")}`,
|
|
561
|
-
"journey_invalid_transition",
|
|
562
|
-
);
|
|
563
|
-
} else if (ownProducer) {
|
|
564
|
-
stateByStep.set(ownProducer, transition.to);
|
|
565
|
-
}
|
|
566
|
-
if (step.id) seen.add(step.id);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
446
|
function structuralBudgetIssue(value: unknown): UdlIssue | undefined {
|
|
573
447
|
let discovered = 1;
|
|
574
448
|
let nodes = 0;
|
|
@@ -1365,90 +1239,366 @@ function validateInstrument(
|
|
|
1365
1239
|
}
|
|
1366
1240
|
}
|
|
1367
1241
|
|
|
1368
|
-
|
|
1242
|
+
for (const finIssue of instrumentFinanceIssues(instrument, {
|
|
1243
|
+
plans: planResolution.plans,
|
|
1244
|
+
})) {
|
|
1245
|
+
add([...base, ...finIssue.path], finIssue.message, finIssue.code);
|
|
1246
|
+
}
|
|
1247
|
+
validateAggregates(instrument, base, instruments, references, add);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* The finance oracle over an instrument's resolved action plans, with paths
|
|
1252
|
+
* rooted at the instrument. A piece-plan instrument is unfolded over the
|
|
1253
|
+
* runtime's piece progress; any other instrument runs once per action plan
|
|
1254
|
+
* combination. Contract-side callers pass the UDL projection of a blueprint
|
|
1255
|
+
* definition so both boundaries prove the same machine.
|
|
1256
|
+
*/
|
|
1257
|
+
export function instrumentFinanceIssues(
|
|
1258
|
+
instrument: UdlInstrument,
|
|
1259
|
+
options: FinanceOptions & {
|
|
1260
|
+
readonly plans?: readonly ResolvedActionPlan[];
|
|
1261
|
+
} = {},
|
|
1262
|
+
): readonly FinanceIssue[] {
|
|
1263
|
+
const plans = options.plans ?? resolveUdlActionPlans(instrument).plans;
|
|
1264
|
+
const financeOptions: FinanceOptions =
|
|
1265
|
+
options.penaltyMayBeNonzero === undefined
|
|
1266
|
+
? {}
|
|
1267
|
+
: { penaltyMayBeNonzero: options.penaltyMayBeNonzero };
|
|
1268
|
+
const issues: FinanceIssue[] = [];
|
|
1269
|
+
const seen = new Set<string>();
|
|
1270
|
+
const add = (
|
|
1271
|
+
path: readonly PropertyKey[],
|
|
1272
|
+
message: string,
|
|
1273
|
+
code: UdlIssueCode,
|
|
1274
|
+
): void => {
|
|
1275
|
+
const key = `${code}:${path.join(".")}:${message}`;
|
|
1276
|
+
if (seen.has(key)) return;
|
|
1277
|
+
seen.add(key);
|
|
1278
|
+
issues.push({ code, message, path });
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
const expansion = instrument.piecePlan
|
|
1282
|
+
? expandPieceProgress(instrument, plans)
|
|
1283
|
+
: undefined;
|
|
1284
|
+
if (expansion === "bound") {
|
|
1285
|
+
add(
|
|
1286
|
+
["actions"],
|
|
1287
|
+
`piece progress expansion exceeds reachable variant bound of ${UDL_LIMITS.maxActionExpansion}`,
|
|
1288
|
+
"UDL2010",
|
|
1289
|
+
);
|
|
1290
|
+
return issues;
|
|
1291
|
+
}
|
|
1292
|
+
if (expansion) {
|
|
1293
|
+
for (const finIssue of analyzeInstrumentFinance(
|
|
1294
|
+
expansion.instrument,
|
|
1295
|
+
financeOptions,
|
|
1296
|
+
)) {
|
|
1297
|
+
const message = expansion.displayNames.reduce(
|
|
1298
|
+
(text, [expanded, display]) => text.replaceAll(expanded, display),
|
|
1299
|
+
finIssue.message,
|
|
1300
|
+
);
|
|
1301
|
+
add(originFinancePath(expansion, finIssue.path), message, finIssue.code);
|
|
1302
|
+
}
|
|
1303
|
+
return issues;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1369
1306
|
const actionsWithPlans = Object.keys(instrument.actions).filter((aName) =>
|
|
1370
|
-
|
|
1307
|
+
plans.some((p) => p.action === aName),
|
|
1371
1308
|
);
|
|
1372
|
-
|
|
1373
1309
|
let totalCombinations = 1;
|
|
1374
1310
|
const actionPlanMap: Record<string, ResolvedActionPlan[]> = {};
|
|
1375
1311
|
for (const aName of actionsWithPlans) {
|
|
1376
|
-
const actionPlans =
|
|
1312
|
+
const actionPlans = plans.filter((p) => p.action === aName);
|
|
1377
1313
|
actionPlanMap[aName] = actionPlans;
|
|
1378
1314
|
totalCombinations *= actionPlans.length;
|
|
1379
1315
|
}
|
|
1380
|
-
|
|
1381
1316
|
if (
|
|
1382
1317
|
actionsWithPlans.length > 0 &&
|
|
1383
1318
|
totalCombinations > UDL_LIMITS.maxActionExpansion
|
|
1384
1319
|
) {
|
|
1385
1320
|
add(
|
|
1386
|
-
[
|
|
1321
|
+
["actions"],
|
|
1387
1322
|
`variant expansion exceeds combination bound of ${UDL_LIMITS.maxActionExpansion} (${totalCombinations} combinations)`,
|
|
1388
1323
|
"UDL2010",
|
|
1389
1324
|
);
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
}
|
|
1325
|
+
return issues;
|
|
1326
|
+
}
|
|
1327
|
+
const generateCombos = (
|
|
1328
|
+
keys: string[],
|
|
1329
|
+
): Record<string, ResolvedActionPlan>[] => {
|
|
1330
|
+
if (keys.length === 0) return [{}];
|
|
1331
|
+
const [first, ...rest] = keys;
|
|
1332
|
+
const restCombos = generateCombos(rest);
|
|
1333
|
+
const result: Record<string, ResolvedActionPlan>[] = [];
|
|
1334
|
+
for (const plan of actionPlanMap[first!]!) {
|
|
1335
|
+
for (const combo of restCombos) {
|
|
1336
|
+
result.push({ ...combo, [first!]: plan });
|
|
1402
1337
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1338
|
+
}
|
|
1339
|
+
return result;
|
|
1340
|
+
};
|
|
1341
|
+
const combinations =
|
|
1342
|
+
actionsWithPlans.length > 0 ? generateCombos(actionsWithPlans) : [{}];
|
|
1343
|
+
for (const combo of combinations) {
|
|
1344
|
+
const expandedActions: Record<string, UdlAction> = {};
|
|
1345
|
+
for (const [aName, aDef] of Object.entries(instrument.actions)) {
|
|
1346
|
+
expandedActions[aName] = planExpandedAction(aDef, combo[aName]);
|
|
1347
|
+
}
|
|
1348
|
+
for (const finIssue of analyzeInstrumentFinance(
|
|
1349
|
+
{ ...instrument, actions: expandedActions },
|
|
1350
|
+
financeOptions,
|
|
1351
|
+
)) {
|
|
1352
|
+
add(finIssue.path, finIssue.message, finIssue.code);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return issues;
|
|
1356
|
+
}
|
|
1405
1357
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1358
|
+
/**
|
|
1359
|
+
* Only an action that moves money through calls is replaced by its expanded
|
|
1360
|
+
* leaves. Authored moves and steps always reach the oracle.
|
|
1361
|
+
*/
|
|
1362
|
+
function planExpandedAction(
|
|
1363
|
+
definition: UdlAction,
|
|
1364
|
+
plan: ResolvedActionPlan | undefined,
|
|
1365
|
+
): UdlAction {
|
|
1366
|
+
if (!plan || (definition.calls?.length ?? 0) === 0) return definition;
|
|
1367
|
+
const steps: UdlStep[] = [];
|
|
1368
|
+
const moves: UdlMove[] = [];
|
|
1369
|
+
for (const leaf of plan.leaves) {
|
|
1370
|
+
if ("key" in leaf.step) {
|
|
1371
|
+
moves.push(leaf.step as UdlMove);
|
|
1372
|
+
} else {
|
|
1373
|
+
steps.push(leaf.step as UdlStep);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return { ...definition, moves, steps };
|
|
1377
|
+
}
|
|
1409
1378
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1379
|
+
interface PieceProgress {
|
|
1380
|
+
readonly funded: readonly string[];
|
|
1381
|
+
readonly consumed: readonly string[];
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
interface PieceProgressExpansion {
|
|
1385
|
+
readonly instrument: UdlInstrument;
|
|
1386
|
+
/** Expanded action name to display name, longest first. */
|
|
1387
|
+
readonly displayNames: readonly (readonly [string, string])[];
|
|
1388
|
+
readonly actionOrigins: ReadonlyMap<
|
|
1389
|
+
string,
|
|
1390
|
+
{
|
|
1391
|
+
readonly action: string;
|
|
1392
|
+
readonly leafOrigins?: readonly (readonly string[])[];
|
|
1393
|
+
}
|
|
1394
|
+
>;
|
|
1395
|
+
readonly stateOrigins: ReadonlyMap<string, string>;
|
|
1396
|
+
readonly originStates: readonly string[];
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
function progressKey(progress: PieceProgress): string {
|
|
1400
|
+
return `${progress.funded.join(",")}|${progress.consumed.join(",")}`;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/** Mirrors eligiblePieces in the engine's piece-plan dispatcher. */
|
|
1404
|
+
function eligiblePieces(
|
|
1405
|
+
plan: NonNullable<UdlInstrument["piecePlan"]>,
|
|
1406
|
+
stage: UdlPieceStageStage,
|
|
1407
|
+
progress: PieceProgress,
|
|
1408
|
+
): readonly string[] {
|
|
1409
|
+
return plan[`${stage}_order`].filter((id) =>
|
|
1410
|
+
stage === "fund"
|
|
1411
|
+
? !progress.funded.includes(id)
|
|
1412
|
+
: progress.funded.includes(id) && !progress.consumed.includes(id),
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Unfolds a piece-plan instrument over the runtime's piece progress so the
|
|
1418
|
+
* ordinary lifecycle oracle sees exactly the states the dispatcher admits: a
|
|
1419
|
+
* piece-stage action moves the next eligible piece of its stage order, the
|
|
1420
|
+
* lifecycle state is retained until the stage's last piece moves, funding
|
|
1421
|
+
* cannot resume once a piece has left escrow, and an action gated on a drained
|
|
1422
|
+
* account is closed while a funded piece is still held. Every expanded state is
|
|
1423
|
+
* one (lifecycle state, progress) pair; expanded action names carry the piece
|
|
1424
|
+
* and the source state so each has exactly one transition. A quote-commit pair
|
|
1425
|
+
* is not carried through the expansion.
|
|
1426
|
+
*/
|
|
1427
|
+
function expandPieceProgress(
|
|
1428
|
+
instrument: UdlInstrument,
|
|
1429
|
+
plans: readonly ResolvedActionPlan[],
|
|
1430
|
+
): PieceProgressExpansion | "bound" | undefined {
|
|
1431
|
+
const plan = instrument.piecePlan;
|
|
1432
|
+
if (!plan) return undefined;
|
|
1433
|
+
const stateName = (state: string, progress: PieceProgress): string =>
|
|
1434
|
+
`${state}#${progressKey(progress)}`;
|
|
1435
|
+
const initialProgress: PieceProgress = { funded: [], consumed: [] };
|
|
1436
|
+
const stateOrigins = new Map<string, string>();
|
|
1437
|
+
const actionOrigins = new Map<
|
|
1438
|
+
string,
|
|
1439
|
+
{ action: string; leafOrigins?: readonly (readonly string[])[] }
|
|
1440
|
+
>();
|
|
1441
|
+
const displayNames: [string, string][] = [];
|
|
1442
|
+
const actions: Record<string, UdlAction> = {};
|
|
1443
|
+
const transitions: Record<string, { from: string[]; to: string }> = {};
|
|
1444
|
+
const pending: { state: string; progress: PieceProgress }[] = [
|
|
1445
|
+
{ state: instrument.lifecycle.initial, progress: initialProgress },
|
|
1446
|
+
];
|
|
1447
|
+
stateOrigins.set(
|
|
1448
|
+
stateName(instrument.lifecycle.initial, initialProgress),
|
|
1449
|
+
instrument.lifecycle.initial,
|
|
1450
|
+
);
|
|
1451
|
+
|
|
1452
|
+
const visit = (state: string, progress: PieceProgress): void => {
|
|
1453
|
+
const name = stateName(state, progress);
|
|
1454
|
+
if (stateOrigins.has(name)) return;
|
|
1455
|
+
stateOrigins.set(name, state);
|
|
1456
|
+
pending.push({ state, progress });
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
while (pending.length > 0) {
|
|
1460
|
+
const { state, progress } = pending.shift()!;
|
|
1461
|
+
if (stateOrigins.size > UDL_LIMITS.maxActionExpansion) return "bound";
|
|
1462
|
+
const held = progress.funded.filter(
|
|
1463
|
+
(id) => !progress.consumed.includes(id),
|
|
1464
|
+
);
|
|
1465
|
+
for (const [actionName, transition] of Object.entries(
|
|
1466
|
+
instrument.lifecycle.transitions,
|
|
1467
|
+
)) {
|
|
1468
|
+
if (!transition.from.includes(state)) continue;
|
|
1469
|
+
const definition = instrument.actions[actionName];
|
|
1470
|
+
if (!definition) continue;
|
|
1471
|
+
if (definition.requiresDrainedAccount && held.length > 0) continue;
|
|
1472
|
+
const stage = definition.pieceStage;
|
|
1473
|
+
if (!stage) {
|
|
1474
|
+
const expanded = `${actionName}#${state}#${progressKey(progress)}`;
|
|
1475
|
+
const variant = plans.find(
|
|
1476
|
+
(candidate) => candidate.action === actionName,
|
|
1477
|
+
);
|
|
1478
|
+
actions[expanded] = planExpandedAction(definition, variant);
|
|
1479
|
+
transitions[expanded] = {
|
|
1480
|
+
from: [stateName(state, progress)],
|
|
1481
|
+
to: stateName(transition.to, progress),
|
|
1482
|
+
};
|
|
1483
|
+
actionOrigins.set(expanded, {
|
|
1484
|
+
action: actionName,
|
|
1485
|
+
...(variant && (definition.calls?.length ?? 0) > 0
|
|
1486
|
+
? { leafOrigins: variant.leaves.map((leaf) => leaf.originPath) }
|
|
1487
|
+
: {}),
|
|
1488
|
+
});
|
|
1489
|
+
displayNames.push([expanded, actionName]);
|
|
1490
|
+
visit(transition.to, progress);
|
|
1491
|
+
continue;
|
|
1437
1492
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1493
|
+
if (stage.plan !== plan.id) continue;
|
|
1494
|
+
if (stage.stage === "fund" && progress.consumed.length > 0) continue;
|
|
1495
|
+
const eligible = eligiblePieces(plan, stage.stage, progress);
|
|
1496
|
+
const pieceId = eligible[0];
|
|
1497
|
+
if (pieceId === undefined) continue;
|
|
1498
|
+
const variant = plans.find(
|
|
1499
|
+
(candidate) =>
|
|
1500
|
+
candidate.action === actionName && candidate.pieceId === pieceId,
|
|
1501
|
+
);
|
|
1502
|
+
if (!variant) continue;
|
|
1503
|
+
const next: PieceProgress =
|
|
1504
|
+
stage.stage === "fund"
|
|
1505
|
+
? {
|
|
1506
|
+
funded: [...progress.funded, pieceId],
|
|
1507
|
+
consumed: progress.consumed,
|
|
1508
|
+
}
|
|
1509
|
+
: {
|
|
1510
|
+
funded: progress.funded,
|
|
1511
|
+
consumed: [...progress.consumed, pieceId],
|
|
1512
|
+
};
|
|
1513
|
+
const stageComplete =
|
|
1514
|
+
eligiblePieces(plan, stage.stage, next).length === 0;
|
|
1515
|
+
const target = stageComplete ? transition.to : state;
|
|
1516
|
+
const expanded = `${actionName}@${pieceId}#${state}#${progressKey(progress)}`;
|
|
1517
|
+
actions[expanded] = planExpandedAction(definition, variant);
|
|
1518
|
+
transitions[expanded] = {
|
|
1519
|
+
from: [stateName(state, progress)],
|
|
1520
|
+
to: stateName(target, next),
|
|
1441
1521
|
};
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1522
|
+
actionOrigins.set(expanded, {
|
|
1523
|
+
action: actionName,
|
|
1524
|
+
leafOrigins: variant.leaves.map((leaf) => leaf.originPath),
|
|
1525
|
+
});
|
|
1526
|
+
displayNames.push([expanded, `${actionName}[${pieceId}]`]);
|
|
1527
|
+
visit(target, next);
|
|
1449
1528
|
}
|
|
1450
1529
|
}
|
|
1451
|
-
|
|
1530
|
+
|
|
1531
|
+
for (const [actionName, definition] of Object.entries(instrument.actions)) {
|
|
1532
|
+
if (Object.hasOwn(instrument.lifecycle.transitions, actionName)) continue;
|
|
1533
|
+
actions[actionName] = planExpandedAction(
|
|
1534
|
+
definition,
|
|
1535
|
+
plans.find((candidate) => candidate.action === actionName),
|
|
1536
|
+
);
|
|
1537
|
+
actionOrigins.set(actionName, { action: actionName });
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
return {
|
|
1541
|
+
instrument: {
|
|
1542
|
+
...instrument,
|
|
1543
|
+
actions,
|
|
1544
|
+
lifecycle: {
|
|
1545
|
+
initial: stateName(instrument.lifecycle.initial, initialProgress),
|
|
1546
|
+
states: [...stateOrigins.keys()],
|
|
1547
|
+
transitions,
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
displayNames: [
|
|
1551
|
+
...displayNames,
|
|
1552
|
+
...[...stateOrigins.entries()].map(
|
|
1553
|
+
([expanded, origin]): [string, string] => [expanded, origin],
|
|
1554
|
+
),
|
|
1555
|
+
].sort((left, right) => right[0].length - left[0].length),
|
|
1556
|
+
actionOrigins,
|
|
1557
|
+
stateOrigins,
|
|
1558
|
+
originStates: instrument.lifecycle.states,
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function originFinancePath(
|
|
1563
|
+
expansion: PieceProgressExpansion,
|
|
1564
|
+
path: readonly PropertyKey[],
|
|
1565
|
+
): readonly PropertyKey[] {
|
|
1566
|
+
const [head, second, third, fourth, ...rest] = path;
|
|
1567
|
+
if (
|
|
1568
|
+
head === "lifecycle" &&
|
|
1569
|
+
second === "states" &&
|
|
1570
|
+
typeof third === "number"
|
|
1571
|
+
) {
|
|
1572
|
+
const expandedState = expansion.instrument.lifecycle.states[third];
|
|
1573
|
+
const origin =
|
|
1574
|
+
expandedState === undefined
|
|
1575
|
+
? undefined
|
|
1576
|
+
: expansion.stateOrigins.get(expandedState);
|
|
1577
|
+
const index =
|
|
1578
|
+
origin === undefined ? -1 : expansion.originStates.indexOf(origin);
|
|
1579
|
+
return index >= 0
|
|
1580
|
+
? ["lifecycle", "states", index]
|
|
1581
|
+
: ["lifecycle", "states"];
|
|
1582
|
+
}
|
|
1583
|
+
if (head === "actions" && typeof second === "string") {
|
|
1584
|
+
const origin = expansion.actionOrigins.get(second);
|
|
1585
|
+
if (!origin) return path;
|
|
1586
|
+
if (
|
|
1587
|
+
third === "moves" &&
|
|
1588
|
+
typeof fourth === "number" &&
|
|
1589
|
+
origin.leafOrigins?.[fourth]
|
|
1590
|
+
) {
|
|
1591
|
+
return ["actions", ...origin.leafOrigins[fourth], ...rest];
|
|
1592
|
+
}
|
|
1593
|
+
return [
|
|
1594
|
+
"actions",
|
|
1595
|
+
origin.action,
|
|
1596
|
+
...(third === undefined ? [] : [third]),
|
|
1597
|
+
...(fourth === undefined ? [] : [fourth]),
|
|
1598
|
+
...rest,
|
|
1599
|
+
];
|
|
1600
|
+
}
|
|
1601
|
+
return path;
|
|
1452
1602
|
}
|
|
1453
1603
|
|
|
1454
1604
|
function validatePiecePlan(
|
|
@@ -2637,7 +2787,11 @@ function validateActionUpdates(
|
|
|
2637
2787
|
add(fieldPath, `updated field ${field} is an aggregate cap`, "UDL5008");
|
|
2638
2788
|
}
|
|
2639
2789
|
});
|
|
2640
|
-
if (
|
|
2790
|
+
if (
|
|
2791
|
+
definition.moves.length > 0 ||
|
|
2792
|
+
definition.allocate ||
|
|
2793
|
+
definition.contributionStage
|
|
2794
|
+
) {
|
|
2641
2795
|
add(
|
|
2642
2796
|
[...actionBase, "updates"],
|
|
2643
2797
|
"an action cannot update fields while moving money",
|
|
@@ -2716,6 +2870,33 @@ function validateRemainder(
|
|
|
2716
2870
|
"UDL4001",
|
|
2717
2871
|
);
|
|
2718
2872
|
}
|
|
2873
|
+
addDuplicateIssues(
|
|
2874
|
+
remainder.subtractPaths ?? [],
|
|
2875
|
+
[...remainderBase, "subtractPaths"],
|
|
2876
|
+
"remainder operand",
|
|
2877
|
+
add,
|
|
2878
|
+
);
|
|
2879
|
+
for (const path of remainder.subtractPaths ?? []) {
|
|
2880
|
+
const [root, key] = path.split(".");
|
|
2881
|
+
const declared =
|
|
2882
|
+
root === "fields" &&
|
|
2883
|
+
key !== undefined &&
|
|
2884
|
+
isMoneySchema(instrument.fields[key] ?? {}) &&
|
|
2885
|
+
!instrument.update?.fields.includes(key) &&
|
|
2886
|
+
!Object.values(instrument.actions).some((action) =>
|
|
2887
|
+
action.updates?.includes(key),
|
|
2888
|
+
);
|
|
2889
|
+
if (
|
|
2890
|
+
!declared ||
|
|
2891
|
+
path === remainder.totalPath ||
|
|
2892
|
+
path === `refs.${remainder.amountRef}`
|
|
2893
|
+
)
|
|
2894
|
+
add(
|
|
2895
|
+
[...remainderBase, "subtractPaths"],
|
|
2896
|
+
`remainder subtraction ${path} must name distinct immutable money`,
|
|
2897
|
+
"UDL4001",
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2719
2900
|
const [totalRoot, totalKey] = remainder.totalPath.split(".");
|
|
2720
2901
|
const totalDeclared =
|
|
2721
2902
|
(totalRoot === "fields" &&
|
|
@@ -2910,10 +3091,24 @@ function validateActions(
|
|
|
2910
3091
|
);
|
|
2911
3092
|
|
|
2912
3093
|
if (definition.requiresRefs) {
|
|
3094
|
+
const boundFields = new Map<string, string>();
|
|
3095
|
+
for (const gate of definition.requiresRefs) {
|
|
3096
|
+
for (const [field, path] of Object.entries(gate.bind ?? {})) {
|
|
3097
|
+
const source = `${gate.field}:${path}`;
|
|
3098
|
+
const previous = boundFields.get(field);
|
|
3099
|
+
if (previous !== undefined && previous !== source)
|
|
3100
|
+
add(
|
|
3101
|
+
[...actionBase, "requiresRefs"],
|
|
3102
|
+
`reference gates bind ${field} from conflicting sources`,
|
|
3103
|
+
"UDL5001",
|
|
3104
|
+
);
|
|
3105
|
+
boundFields.set(field, source);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
2913
3108
|
addDuplicateIssues(
|
|
2914
|
-
definition.requiresRefs.map((gate) => gate
|
|
3109
|
+
definition.requiresRefs.map((gate) => JSON.stringify(sortObject(gate))),
|
|
2915
3110
|
[...actionBase, "requiresRefs"],
|
|
2916
|
-
"gate
|
|
3111
|
+
"gate",
|
|
2917
3112
|
add,
|
|
2918
3113
|
);
|
|
2919
3114
|
definition.requiresRefs.forEach((gate, gateIndex) => {
|
|
@@ -3023,6 +3218,7 @@ function validateActions(
|
|
|
3023
3218
|
...Object.entries(instrument.actions).flatMap(
|
|
3024
3219
|
([candidateAction, candidate]) => [
|
|
3025
3220
|
...Object.keys(candidate.captureInput ?? {}),
|
|
3221
|
+
...Object.keys(candidate.captureEngine ?? {}),
|
|
3026
3222
|
...[...candidate.steps, ...candidate.moves].flatMap((step) =>
|
|
3027
3223
|
Object.keys(step.capture ?? {}),
|
|
3028
3224
|
),
|
|
@@ -3223,7 +3419,7 @@ function validateActions(
|
|
|
3223
3419
|
);
|
|
3224
3420
|
}
|
|
3225
3421
|
if (
|
|
3226
|
-
definition.due.offset &&
|
|
3422
|
+
typeof definition.due.offset === "string" &&
|
|
3227
3423
|
fixedIsoDurationMs(definition.due.offset) === null
|
|
3228
3424
|
) {
|
|
3229
3425
|
add(
|
|
@@ -3258,7 +3454,7 @@ function validateActions(
|
|
|
3258
3454
|
);
|
|
3259
3455
|
}
|
|
3260
3456
|
if (
|
|
3261
|
-
definition.deadline.offset &&
|
|
3457
|
+
typeof definition.deadline.offset === "string" &&
|
|
3262
3458
|
fixedIsoDurationMs(definition.deadline.offset) === null
|
|
3263
3459
|
) {
|
|
3264
3460
|
add(
|
|
@@ -3858,9 +4054,10 @@ function validatePayoutsAndSettlement(
|
|
|
3858
4054
|
}
|
|
3859
4055
|
|
|
3860
4056
|
const reservedRefs = new Set([
|
|
3861
|
-
...Object.values(instrument.actions).flatMap((action) =>
|
|
3862
|
-
Object.keys(action.captureInput ?? {}),
|
|
3863
|
-
|
|
4057
|
+
...Object.values(instrument.actions).flatMap((action) => [
|
|
4058
|
+
...Object.keys(action.captureInput ?? {}),
|
|
4059
|
+
...Object.keys(action.captureEngine ?? {}),
|
|
4060
|
+
]),
|
|
3864
4061
|
...Object.values(instrument.actions).flatMap((action) =>
|
|
3865
4062
|
[...action.steps, ...action.moves].flatMap((step) =>
|
|
3866
4063
|
Object.keys(step.capture ?? {}),
|
|
@@ -3887,6 +4084,9 @@ function validatePayoutsAndSettlement(
|
|
|
3887
4084
|
]
|
|
3888
4085
|
: [],
|
|
3889
4086
|
),
|
|
4087
|
+
...Object.values(instrument.actions).flatMap((action) =>
|
|
4088
|
+
action.allocate ? [action.allocate.capture] : [],
|
|
4089
|
+
),
|
|
3890
4090
|
...quoteRefKeys(instrument),
|
|
3891
4091
|
...(instrument.subject ? ["subject"] : []),
|
|
3892
4092
|
]);
|
|
@@ -4419,9 +4619,10 @@ function declaredRefKeys(instrument: UdlInstrument): ReadonlySet<string> {
|
|
|
4419
4619
|
...Object.values(instrument.actions).flatMap((action) =>
|
|
4420
4620
|
(action.reconcile ?? []).map((reconcile) => reconcile.capture),
|
|
4421
4621
|
),
|
|
4422
|
-
...Object.values(instrument.actions).flatMap((action) =>
|
|
4423
|
-
Object.keys(action.captureInput ?? {}),
|
|
4424
|
-
|
|
4622
|
+
...Object.values(instrument.actions).flatMap((action) => [
|
|
4623
|
+
...Object.keys(action.captureInput ?? {}),
|
|
4624
|
+
...Object.keys(action.captureEngine ?? {}),
|
|
4625
|
+
]),
|
|
4425
4626
|
...Object.values(instrument.actions).flatMap((action) =>
|
|
4426
4627
|
[...action.steps, ...action.moves].flatMap((step) =>
|
|
4427
4628
|
Object.keys(step.capture ?? {}),
|
|
@@ -4435,6 +4636,9 @@ function declaredRefKeys(instrument: UdlInstrument): ReadonlySet<string> {
|
|
|
4435
4636
|
]
|
|
4436
4637
|
: [],
|
|
4437
4638
|
),
|
|
4639
|
+
...Object.values(instrument.actions).flatMap((action) =>
|
|
4640
|
+
action.allocate ? [action.allocate.capture] : [],
|
|
4641
|
+
),
|
|
4438
4642
|
...quoteRefKeys(instrument),
|
|
4439
4643
|
...(instrument.subject ? ["subject"] : []),
|
|
4440
4644
|
]);
|
|
@@ -5275,34 +5479,28 @@ function reconcileExceptionProblemMessage(
|
|
|
5275
5479
|
* are written once. The value is disposable: no document ever carries it, and
|
|
5276
5480
|
* a host's real id grammar stays the host's business.
|
|
5277
5481
|
*/
|
|
5278
|
-
|
|
5279
|
-
`${prefix}_sandbox_0123456789abcdef`;
|
|
5280
|
-
|
|
5281
|
-
/** A prefix no host mints, so a schema that accepts it accepts anything. */
|
|
5282
|
-
const UNCLAIMED_PREFIX = "zzzz";
|
|
5283
|
-
|
|
5482
|
+
/** Classify the sealed ID pattern without compiling document-authored regexes. */
|
|
5284
5483
|
export function openReferenceShapeBudget(): ReferenceShapeBudget {
|
|
5285
5484
|
const answers = new WeakMap<object, Map<string, boolean>>();
|
|
5286
5485
|
let probes = 0;
|
|
5486
|
+
let exhausted = false;
|
|
5287
5487
|
return {
|
|
5288
5488
|
accepts(schema, prefix) {
|
|
5289
5489
|
const seen = answers.get(schema);
|
|
5290
5490
|
const cached = seen?.get(prefix);
|
|
5291
5491
|
if (cached !== undefined) return cached;
|
|
5292
|
-
if (probes >= UDL_LIMITS.maxSchemaProbes)
|
|
5492
|
+
if (probes >= UDL_LIMITS.maxSchemaProbes) {
|
|
5493
|
+
exhausted = true;
|
|
5494
|
+
return false;
|
|
5495
|
+
}
|
|
5293
5496
|
probes += 1;
|
|
5294
|
-
|
|
5295
|
-
const answer =
|
|
5296
|
-
validateUdlSchemaValue(schema, probeIdFor(prefix)).errors.length ===
|
|
5297
|
-
0 &&
|
|
5298
|
-
validateUdlSchemaValue(schema, probeIdFor(UNCLAIMED_PREFIX)).errors
|
|
5299
|
-
.length > 0;
|
|
5497
|
+
const answer = referencePatternPrefix(schema) === prefix;
|
|
5300
5498
|
if (seen) seen.set(prefix, answer);
|
|
5301
5499
|
else answers.set(schema, new Map([[prefix, answer]]));
|
|
5302
5500
|
return answer;
|
|
5303
5501
|
},
|
|
5304
5502
|
get exhausted() {
|
|
5305
|
-
return
|
|
5503
|
+
return exhausted;
|
|
5306
5504
|
},
|
|
5307
5505
|
};
|
|
5308
5506
|
}
|
|
@@ -5447,3 +5645,14 @@ function jsonPath(path: readonly PropertyKey[]): string {
|
|
|
5447
5645
|
}
|
|
5448
5646
|
return result;
|
|
5449
5647
|
}
|
|
5648
|
+
|
|
5649
|
+
function sortObject(value: unknown): unknown {
|
|
5650
|
+
if (Array.isArray(value)) return value.map(sortObject);
|
|
5651
|
+
if (value !== null && typeof value === "object")
|
|
5652
|
+
return Object.fromEntries(
|
|
5653
|
+
Object.entries(value)
|
|
5654
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
5655
|
+
.map(([key, item]) => [key, sortObject(item)]),
|
|
5656
|
+
);
|
|
5657
|
+
return value;
|
|
5658
|
+
}
|