@hyperscale0/udl 2.3.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 +26 -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.map +1 -1
- package/dist/effects.js +23 -1
- 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 +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- 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 +14 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +75 -131
- 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 +2 -1
- package/docs/funding-custody.md +165 -0
- package/docs/guide/09-schedules-and-allocation.md +130 -0
- package/docs/llms-full.txt +620 -101
- package/docs/llms.txt +1 -1
- 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 +27 -1
- package/src/evolution.ts +63 -3
- package/src/finance.ts +218 -24
- package/src/index.ts +30 -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 +112 -182
- 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,
|
|
@@ -68,6 +70,12 @@ export interface UdlValidationOptions {
|
|
|
68
70
|
readonly requireDecisionPartyBindings?: boolean;
|
|
69
71
|
}
|
|
70
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
|
+
*/
|
|
71
79
|
export function validateUdl(
|
|
72
80
|
value: unknown,
|
|
73
81
|
options: UdlValidationOptions = {},
|
|
@@ -296,6 +304,26 @@ function semanticIssues(
|
|
|
296
304
|
validateDocumentSchemas(document, add);
|
|
297
305
|
if (issues.length > 0) return issues;
|
|
298
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
|
+
|
|
299
327
|
addDuplicateIssues(
|
|
300
328
|
document.subjects.map((subject) => subject.kind),
|
|
301
329
|
["subjects"],
|
|
@@ -316,7 +344,6 @@ function semanticIssues(
|
|
|
316
344
|
);
|
|
317
345
|
|
|
318
346
|
validateCompositionDials(document, add);
|
|
319
|
-
validateInstrumentJourneys(document, add);
|
|
320
347
|
|
|
321
348
|
const subjects = new Map(
|
|
322
349
|
document.subjects.map((subject) => [subject.kind, subject] as const),
|
|
@@ -416,162 +443,6 @@ function semanticIssues(
|
|
|
416
443
|
return issues;
|
|
417
444
|
}
|
|
418
445
|
|
|
419
|
-
const scopedIdPattern =
|
|
420
|
-
/^\^([a-z]{2,8})_\(sandbox\|live\)_\[a-z0-9\]\{8,64\}\$$/;
|
|
421
|
-
|
|
422
|
-
function camel(value: string): string {
|
|
423
|
-
return value.replace(/_([a-z0-9])/g, (_match, character: string) =>
|
|
424
|
-
character.toUpperCase(),
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function journeyReferenceFields(
|
|
429
|
-
instrument: UdlInstrument,
|
|
430
|
-
actionName: string,
|
|
431
|
-
): ReadonlyMap<string, string> {
|
|
432
|
-
const action = instrument.actions[actionName];
|
|
433
|
-
if (!action) return new Map();
|
|
434
|
-
const fields = new Map<string, string>();
|
|
435
|
-
const addSchema = (name: string, schema: unknown) => {
|
|
436
|
-
const pattern = recordValue(schema).pattern;
|
|
437
|
-
if (typeof pattern !== "string") return;
|
|
438
|
-
const prefix = scopedIdPattern.exec(pattern)?.[1];
|
|
439
|
-
if (prefix) fields.set(name, prefix);
|
|
440
|
-
};
|
|
441
|
-
if (actionName === "create") {
|
|
442
|
-
const derivedFields = new Set(
|
|
443
|
-
(action.requiresRefs ?? []).flatMap((gate) =>
|
|
444
|
-
Object.keys(gate.bind ?? {}),
|
|
445
|
-
),
|
|
446
|
-
);
|
|
447
|
-
for (const name of instrument.required) {
|
|
448
|
-
if (!derivedFields.has(name)) addSchema(name, instrument.fields[name]);
|
|
449
|
-
}
|
|
450
|
-
} else {
|
|
451
|
-
fields.set(`${camel(instrument.id)}Id`, instrument.idPrefix);
|
|
452
|
-
}
|
|
453
|
-
const input = recordValue(action.input);
|
|
454
|
-
const required = Array.isArray(input.required)
|
|
455
|
-
? input.required.filter((name): name is string => typeof name === "string")
|
|
456
|
-
: [];
|
|
457
|
-
const properties = recordValue(input.properties);
|
|
458
|
-
for (const name of required) addSchema(name, properties[name]);
|
|
459
|
-
return fields;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Validate the part of authored journeys that canonical UDL can prove alone.
|
|
464
|
-
* A caller with the operation catalog validates root-operation examples and
|
|
465
|
-
* cross-kind bindings. UDL owns local examples, lifecycle order, and local
|
|
466
|
-
* reference completeness so raw UDL cannot bypass those laws.
|
|
467
|
-
*/
|
|
468
|
-
function validateInstrumentJourneys(
|
|
469
|
-
document: UdlDocument,
|
|
470
|
-
add: AddIssue,
|
|
471
|
-
): void {
|
|
472
|
-
const instruments = new Map(
|
|
473
|
-
document.instruments.map(
|
|
474
|
-
(instrument) => [instrument.id, instrument] as const,
|
|
475
|
-
),
|
|
476
|
-
);
|
|
477
|
-
for (const [instrumentIndex, owner] of document.instruments.entries()) {
|
|
478
|
-
for (const [journeyIndex, journey] of (owner.journeys ?? []).entries()) {
|
|
479
|
-
const base = [
|
|
480
|
-
"instruments",
|
|
481
|
-
instrumentIndex,
|
|
482
|
-
"journeys",
|
|
483
|
-
journeyIndex,
|
|
484
|
-
] as const;
|
|
485
|
-
const seen = new Set<string>();
|
|
486
|
-
const createdKindByStep = new Map<string, string>();
|
|
487
|
-
const stateByStep = new Map<string, string>();
|
|
488
|
-
for (const [stepIndex, step] of journey.steps.entries()) {
|
|
489
|
-
const stepBase = [...base, "steps", stepIndex] as const;
|
|
490
|
-
if (step.id) {
|
|
491
|
-
if (seen.has(step.id)) {
|
|
492
|
-
add(
|
|
493
|
-
[...stepBase, "id"],
|
|
494
|
-
`journey ${journey.id} declares step id ${step.id} more than once`,
|
|
495
|
-
"journey_duplicate_step_id",
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
const [instrumentId, actionName] = step.operation.split(".");
|
|
500
|
-
const instrument = instrumentId
|
|
501
|
-
? instruments.get(instrumentId)
|
|
502
|
-
: undefined;
|
|
503
|
-
const action =
|
|
504
|
-
instrument && actionName ? instrument.actions[actionName] : undefined;
|
|
505
|
-
if (!instrument) {
|
|
506
|
-
if (step.id) seen.add(step.id);
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
if (!action || !actionName) {
|
|
510
|
-
add(
|
|
511
|
-
[...stepBase, "operation"],
|
|
512
|
-
`journey ${journey.id} names unknown operation ${step.operation}`,
|
|
513
|
-
"journey_unknown_operation",
|
|
514
|
-
);
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
if (
|
|
518
|
-
!action.examples?.some((example) => example.name === step.example)
|
|
519
|
-
) {
|
|
520
|
-
add(
|
|
521
|
-
[...stepBase, "example"],
|
|
522
|
-
`journey ${journey.id} names unknown example ${step.example} on ${step.operation}`,
|
|
523
|
-
"journey_unknown_example",
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
for (const [field, prefix] of journeyReferenceFields(
|
|
527
|
-
instrument,
|
|
528
|
-
actionName,
|
|
529
|
-
)) {
|
|
530
|
-
const producer = step.bind[field];
|
|
531
|
-
if (!producer || !seen.has(producer)) {
|
|
532
|
-
add(
|
|
533
|
-
[...stepBase, "bind", field],
|
|
534
|
-
`journey ${journey.id} must bind ${step.operation}.${field} to an earlier step`,
|
|
535
|
-
"journey_unbound_reference",
|
|
536
|
-
);
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
const producedPrefix = createdKindByStep.get(producer);
|
|
540
|
-
if (producedPrefix && producedPrefix !== prefix) {
|
|
541
|
-
add(
|
|
542
|
-
[...stepBase, "bind", field],
|
|
543
|
-
`journey ${journey.id} binds ${step.operation}.${field} to ${producer}, which creates ${producedPrefix} instead of ${prefix}`,
|
|
544
|
-
"journey_unbound_reference",
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
if (actionName === "create") {
|
|
549
|
-
if (step.id) {
|
|
550
|
-
createdKindByStep.set(step.id, instrument.idPrefix);
|
|
551
|
-
stateByStep.set(step.id, instrument.lifecycle.initial);
|
|
552
|
-
seen.add(step.id);
|
|
553
|
-
}
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
const transition = instrument.lifecycle.transitions[actionName];
|
|
557
|
-
if (!transition) continue;
|
|
558
|
-
const ownProducer = step.bind[`${camel(instrument.id)}Id`];
|
|
559
|
-
const state = ownProducer ? stateByStep.get(ownProducer) : undefined;
|
|
560
|
-
if (state && !transition.from.includes(state)) {
|
|
561
|
-
add(
|
|
562
|
-
[...stepBase, "operation"],
|
|
563
|
-
`journey ${journey.id} runs ${step.operation} from ${state}; allowed states are ${transition.from.join(", ")}`,
|
|
564
|
-
"journey_invalid_transition",
|
|
565
|
-
);
|
|
566
|
-
} else if (ownProducer) {
|
|
567
|
-
stateByStep.set(ownProducer, transition.to);
|
|
568
|
-
}
|
|
569
|
-
if (step.id) seen.add(step.id);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
446
|
function structuralBudgetIssue(value: unknown): UdlIssue | undefined {
|
|
576
447
|
let discovered = 1;
|
|
577
448
|
let nodes = 0;
|
|
@@ -2916,7 +2787,11 @@ function validateActionUpdates(
|
|
|
2916
2787
|
add(fieldPath, `updated field ${field} is an aggregate cap`, "UDL5008");
|
|
2917
2788
|
}
|
|
2918
2789
|
});
|
|
2919
|
-
if (
|
|
2790
|
+
if (
|
|
2791
|
+
definition.moves.length > 0 ||
|
|
2792
|
+
definition.allocate ||
|
|
2793
|
+
definition.contributionStage
|
|
2794
|
+
) {
|
|
2920
2795
|
add(
|
|
2921
2796
|
[...actionBase, "updates"],
|
|
2922
2797
|
"an action cannot update fields while moving money",
|
|
@@ -2995,6 +2870,33 @@ function validateRemainder(
|
|
|
2995
2870
|
"UDL4001",
|
|
2996
2871
|
);
|
|
2997
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
|
+
}
|
|
2998
2900
|
const [totalRoot, totalKey] = remainder.totalPath.split(".");
|
|
2999
2901
|
const totalDeclared =
|
|
3000
2902
|
(totalRoot === "fields" &&
|
|
@@ -3189,10 +3091,24 @@ function validateActions(
|
|
|
3189
3091
|
);
|
|
3190
3092
|
|
|
3191
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
|
+
}
|
|
3192
3108
|
addDuplicateIssues(
|
|
3193
|
-
definition.requiresRefs.map((gate) => gate
|
|
3109
|
+
definition.requiresRefs.map((gate) => JSON.stringify(sortObject(gate))),
|
|
3194
3110
|
[...actionBase, "requiresRefs"],
|
|
3195
|
-
"gate
|
|
3111
|
+
"gate",
|
|
3196
3112
|
add,
|
|
3197
3113
|
);
|
|
3198
3114
|
definition.requiresRefs.forEach((gate, gateIndex) => {
|
|
@@ -3302,6 +3218,7 @@ function validateActions(
|
|
|
3302
3218
|
...Object.entries(instrument.actions).flatMap(
|
|
3303
3219
|
([candidateAction, candidate]) => [
|
|
3304
3220
|
...Object.keys(candidate.captureInput ?? {}),
|
|
3221
|
+
...Object.keys(candidate.captureEngine ?? {}),
|
|
3305
3222
|
...[...candidate.steps, ...candidate.moves].flatMap((step) =>
|
|
3306
3223
|
Object.keys(step.capture ?? {}),
|
|
3307
3224
|
),
|
|
@@ -3502,7 +3419,7 @@ function validateActions(
|
|
|
3502
3419
|
);
|
|
3503
3420
|
}
|
|
3504
3421
|
if (
|
|
3505
|
-
definition.due.offset &&
|
|
3422
|
+
typeof definition.due.offset === "string" &&
|
|
3506
3423
|
fixedIsoDurationMs(definition.due.offset) === null
|
|
3507
3424
|
) {
|
|
3508
3425
|
add(
|
|
@@ -3537,7 +3454,7 @@ function validateActions(
|
|
|
3537
3454
|
);
|
|
3538
3455
|
}
|
|
3539
3456
|
if (
|
|
3540
|
-
definition.deadline.offset &&
|
|
3457
|
+
typeof definition.deadline.offset === "string" &&
|
|
3541
3458
|
fixedIsoDurationMs(definition.deadline.offset) === null
|
|
3542
3459
|
) {
|
|
3543
3460
|
add(
|
|
@@ -4137,9 +4054,10 @@ function validatePayoutsAndSettlement(
|
|
|
4137
4054
|
}
|
|
4138
4055
|
|
|
4139
4056
|
const reservedRefs = new Set([
|
|
4140
|
-
...Object.values(instrument.actions).flatMap((action) =>
|
|
4141
|
-
Object.keys(action.captureInput ?? {}),
|
|
4142
|
-
|
|
4057
|
+
...Object.values(instrument.actions).flatMap((action) => [
|
|
4058
|
+
...Object.keys(action.captureInput ?? {}),
|
|
4059
|
+
...Object.keys(action.captureEngine ?? {}),
|
|
4060
|
+
]),
|
|
4143
4061
|
...Object.values(instrument.actions).flatMap((action) =>
|
|
4144
4062
|
[...action.steps, ...action.moves].flatMap((step) =>
|
|
4145
4063
|
Object.keys(step.capture ?? {}),
|
|
@@ -4166,6 +4084,9 @@ function validatePayoutsAndSettlement(
|
|
|
4166
4084
|
]
|
|
4167
4085
|
: [],
|
|
4168
4086
|
),
|
|
4087
|
+
...Object.values(instrument.actions).flatMap((action) =>
|
|
4088
|
+
action.allocate ? [action.allocate.capture] : [],
|
|
4089
|
+
),
|
|
4169
4090
|
...quoteRefKeys(instrument),
|
|
4170
4091
|
...(instrument.subject ? ["subject"] : []),
|
|
4171
4092
|
]);
|
|
@@ -4698,9 +4619,10 @@ function declaredRefKeys(instrument: UdlInstrument): ReadonlySet<string> {
|
|
|
4698
4619
|
...Object.values(instrument.actions).flatMap((action) =>
|
|
4699
4620
|
(action.reconcile ?? []).map((reconcile) => reconcile.capture),
|
|
4700
4621
|
),
|
|
4701
|
-
...Object.values(instrument.actions).flatMap((action) =>
|
|
4702
|
-
Object.keys(action.captureInput ?? {}),
|
|
4703
|
-
|
|
4622
|
+
...Object.values(instrument.actions).flatMap((action) => [
|
|
4623
|
+
...Object.keys(action.captureInput ?? {}),
|
|
4624
|
+
...Object.keys(action.captureEngine ?? {}),
|
|
4625
|
+
]),
|
|
4704
4626
|
...Object.values(instrument.actions).flatMap((action) =>
|
|
4705
4627
|
[...action.steps, ...action.moves].flatMap((step) =>
|
|
4706
4628
|
Object.keys(step.capture ?? {}),
|
|
@@ -4714,6 +4636,9 @@ function declaredRefKeys(instrument: UdlInstrument): ReadonlySet<string> {
|
|
|
4714
4636
|
]
|
|
4715
4637
|
: [],
|
|
4716
4638
|
),
|
|
4639
|
+
...Object.values(instrument.actions).flatMap((action) =>
|
|
4640
|
+
action.allocate ? [action.allocate.capture] : [],
|
|
4641
|
+
),
|
|
4717
4642
|
...quoteRefKeys(instrument),
|
|
4718
4643
|
...(instrument.subject ? ["subject"] : []),
|
|
4719
4644
|
]);
|
|
@@ -5554,34 +5479,28 @@ function reconcileExceptionProblemMessage(
|
|
|
5554
5479
|
* are written once. The value is disposable: no document ever carries it, and
|
|
5555
5480
|
* a host's real id grammar stays the host's business.
|
|
5556
5481
|
*/
|
|
5557
|
-
|
|
5558
|
-
`${prefix}_sandbox_0123456789abcdef`;
|
|
5559
|
-
|
|
5560
|
-
/** A prefix no host mints, so a schema that accepts it accepts anything. */
|
|
5561
|
-
const UNCLAIMED_PREFIX = "zzzz";
|
|
5562
|
-
|
|
5482
|
+
/** Classify the sealed ID pattern without compiling document-authored regexes. */
|
|
5563
5483
|
export function openReferenceShapeBudget(): ReferenceShapeBudget {
|
|
5564
5484
|
const answers = new WeakMap<object, Map<string, boolean>>();
|
|
5565
5485
|
let probes = 0;
|
|
5486
|
+
let exhausted = false;
|
|
5566
5487
|
return {
|
|
5567
5488
|
accepts(schema, prefix) {
|
|
5568
5489
|
const seen = answers.get(schema);
|
|
5569
5490
|
const cached = seen?.get(prefix);
|
|
5570
5491
|
if (cached !== undefined) return cached;
|
|
5571
|
-
if (probes >= UDL_LIMITS.maxSchemaProbes)
|
|
5492
|
+
if (probes >= UDL_LIMITS.maxSchemaProbes) {
|
|
5493
|
+
exhausted = true;
|
|
5494
|
+
return false;
|
|
5495
|
+
}
|
|
5572
5496
|
probes += 1;
|
|
5573
|
-
|
|
5574
|
-
const answer =
|
|
5575
|
-
validateUdlSchemaValue(schema, probeIdFor(prefix)).errors.length ===
|
|
5576
|
-
0 &&
|
|
5577
|
-
validateUdlSchemaValue(schema, probeIdFor(UNCLAIMED_PREFIX)).errors
|
|
5578
|
-
.length > 0;
|
|
5497
|
+
const answer = referencePatternPrefix(schema) === prefix;
|
|
5579
5498
|
if (seen) seen.set(prefix, answer);
|
|
5580
5499
|
else answers.set(schema, new Map([[prefix, answer]]));
|
|
5581
5500
|
return answer;
|
|
5582
5501
|
},
|
|
5583
5502
|
get exhausted() {
|
|
5584
|
-
return
|
|
5503
|
+
return exhausted;
|
|
5585
5504
|
},
|
|
5586
5505
|
};
|
|
5587
5506
|
}
|
|
@@ -5726,3 +5645,14 @@ function jsonPath(path: readonly PropertyKey[]): string {
|
|
|
5726
5645
|
}
|
|
5727
5646
|
return result;
|
|
5728
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
|
+
}
|