@kumwe/studio-core 0.1.0-alpha.10

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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/THIRD_PARTY_NOTICES.md +9 -0
  4. package/dist/binding-projection.d.ts +47 -0
  5. package/dist/binding-projection.d.ts.map +1 -0
  6. package/dist/binding-projection.js +253 -0
  7. package/dist/binding-projection.js.map +1 -0
  8. package/dist/canonical.d.ts +20 -0
  9. package/dist/canonical.d.ts.map +1 -0
  10. package/dist/canonical.js +100 -0
  11. package/dist/canonical.js.map +1 -0
  12. package/dist/clone.d.ts +2 -0
  13. package/dist/clone.d.ts.map +1 -0
  14. package/dist/clone.js +4 -0
  15. package/dist/clone.js.map +1 -0
  16. package/dist/commands.d.ts +18 -0
  17. package/dist/commands.d.ts.map +1 -0
  18. package/dist/commands.js +799 -0
  19. package/dist/commands.js.map +1 -0
  20. package/dist/contributions.d.ts +93 -0
  21. package/dist/contributions.d.ts.map +1 -0
  22. package/dist/contributions.js +435 -0
  23. package/dist/contributions.js.map +1 -0
  24. package/dist/entry-commands.d.ts +3 -0
  25. package/dist/entry-commands.d.ts.map +1 -0
  26. package/dist/entry-commands.js +36 -0
  27. package/dist/entry-commands.js.map +1 -0
  28. package/dist/extension-sdk.d.ts +51 -0
  29. package/dist/extension-sdk.d.ts.map +1 -0
  30. package/dist/extension-sdk.js +260 -0
  31. package/dist/extension-sdk.js.map +1 -0
  32. package/dist/history.d.ts +22 -0
  33. package/dist/history.d.ts.map +1 -0
  34. package/dist/history.js +81 -0
  35. package/dist/history.js.map +1 -0
  36. package/dist/host-session.d.ts +74 -0
  37. package/dist/host-session.d.ts.map +1 -0
  38. package/dist/host-session.js +713 -0
  39. package/dist/host-session.js.map +1 -0
  40. package/dist/index.d.ts +23 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +23 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/layout.d.ts +66 -0
  45. package/dist/layout.d.ts.map +1 -0
  46. package/dist/layout.js +240 -0
  47. package/dist/layout.js.map +1 -0
  48. package/dist/migrations.d.ts +47 -0
  49. package/dist/migrations.d.ts.map +1 -0
  50. package/dist/migrations.js +83 -0
  51. package/dist/migrations.js.map +1 -0
  52. package/dist/model-commands.d.ts +3 -0
  53. package/dist/model-commands.d.ts.map +1 -0
  54. package/dist/model-commands.js +21 -0
  55. package/dist/model-commands.js.map +1 -0
  56. package/dist/modes.d.ts +47 -0
  57. package/dist/modes.d.ts.map +1 -0
  58. package/dist/modes.js +280 -0
  59. package/dist/modes.js.map +1 -0
  60. package/dist/negotiation.d.ts +23 -0
  61. package/dist/negotiation.d.ts.map +1 -0
  62. package/dist/negotiation.js +65 -0
  63. package/dist/negotiation.js.map +1 -0
  64. package/dist/production-values.d.ts +12 -0
  65. package/dist/production-values.d.ts.map +1 -0
  66. package/dist/production-values.js +224 -0
  67. package/dist/production-values.js.map +1 -0
  68. package/dist/production.d.ts +86 -0
  69. package/dist/production.d.ts.map +1 -0
  70. package/dist/production.js +811 -0
  71. package/dist/production.js.map +1 -0
  72. package/dist/profile-validator.d.ts +47 -0
  73. package/dist/profile-validator.d.ts.map +1 -0
  74. package/dist/profile-validator.js +887 -0
  75. package/dist/profile-validator.js.map +1 -0
  76. package/dist/recipes.d.ts +15 -0
  77. package/dist/recipes.d.ts.map +1 -0
  78. package/dist/recipes.js +34 -0
  79. package/dist/recipes.js.map +1 -0
  80. package/dist/registry.d.ts +17 -0
  81. package/dist/registry.d.ts.map +1 -0
  82. package/dist/registry.js +56 -0
  83. package/dist/registry.js.map +1 -0
  84. package/dist/schema-profile.d.ts +27 -0
  85. package/dist/schema-profile.d.ts.map +1 -0
  86. package/dist/schema-profile.js +1015 -0
  87. package/dist/schema-profile.js.map +1 -0
  88. package/dist/semver.d.ts +20 -0
  89. package/dist/semver.d.ts.map +1 -0
  90. package/dist/semver.js +128 -0
  91. package/dist/semver.js.map +1 -0
  92. package/dist/session.d.ts +73 -0
  93. package/dist/session.d.ts.map +1 -0
  94. package/dist/session.js +191 -0
  95. package/dist/session.js.map +1 -0
  96. package/dist/url-policy.d.ts +45 -0
  97. package/dist/url-policy.d.ts.map +1 -0
  98. package/dist/url-policy.js +248 -0
  99. package/dist/url-policy.js.map +1 -0
  100. package/dist/validation.d.ts +13 -0
  101. package/dist/validation.d.ts.map +1 -0
  102. package/dist/validation.js +444 -0
  103. package/dist/validation.js.map +1 -0
  104. package/package.json +41 -0
@@ -0,0 +1,1015 @@
1
+ import { canonicalStringify } from './canonical.js';
2
+ import { compileProfileSchema } from './profile-validator.js';
3
+ const DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
4
+ const MAX_ALTERNATIVES = 64;
5
+ const MAX_DESCRIPTION_LENGTH = 10_000;
6
+ const MAX_ENUM_MEMBERS = 1_024;
7
+ const MAX_EXAMPLES = 100;
8
+ const MAX_JSON_DEPTH = 64;
9
+ const MAX_JSON_ITEMS = 10_000;
10
+ const MAX_JSON_PROPERTIES = 1_000;
11
+ const MAX_REFERENCES = 128;
12
+ const MAX_REFERENCE_LENGTH = 500;
13
+ const MAX_SCHEMA_BYTES = 262_144;
14
+ const MAX_SCHEMA_DEPTH = 32;
15
+ const MAX_SCHEMA_MAP_PROPERTIES = 512;
16
+ const MAX_SCHEMA_NODES = 1_024;
17
+ const MAX_OBJECT_KEY_LENGTH = 200;
18
+ const MAX_PROPERTY_NAMES = 512;
19
+ const MAX_TITLE_LENGTH = 1_000;
20
+ /** Published complexity limits, pinned to `$defs/limits` in the meta-schema. */
21
+ export const STUDIO_SCHEMA_PROFILE_LIMITS = Object.freeze({
22
+ maxAlternatives: MAX_ALTERNATIVES,
23
+ maxDescriptionLength: MAX_DESCRIPTION_LENGTH,
24
+ maxEnumMembers: MAX_ENUM_MEMBERS,
25
+ maxExamples: MAX_EXAMPLES,
26
+ maxJsonDepth: MAX_JSON_DEPTH,
27
+ maxJsonItems: MAX_JSON_ITEMS,
28
+ maxJsonProperties: MAX_JSON_PROPERTIES,
29
+ maxObjectKeyLength: MAX_OBJECT_KEY_LENGTH,
30
+ maxPropertyNames: MAX_PROPERTY_NAMES,
31
+ maxReferenceLength: MAX_REFERENCE_LENGTH,
32
+ maxReferences: MAX_REFERENCES,
33
+ maxSchemaBytes: MAX_SCHEMA_BYTES,
34
+ maxSchemaDepth: MAX_SCHEMA_DEPTH,
35
+ maxSchemaMapProperties: MAX_SCHEMA_MAP_PROPERTIES,
36
+ maxSchemaNodes: MAX_SCHEMA_NODES,
37
+ maxTitleLength: MAX_TITLE_LENGTH,
38
+ });
39
+ export const STUDIO_SCHEMA_PROFILE_ERROR_CODES = Object.freeze([
40
+ 'invalid-root',
41
+ 'unsupported-keyword',
42
+ 'invalid-keyword-value',
43
+ 'unsafe-member',
44
+ 'limit-exceeded',
45
+ 'invalid-reference',
46
+ 'recursive-schema',
47
+ ]);
48
+ /** A deterministic admission failure suitable for cross-runtime corpus comparison. */
49
+ export class StudioSchemaProfileError extends TypeError {
50
+ code;
51
+ /** JSON Pointer to the rejected schema location; the empty string is the root. */
52
+ schemaPath;
53
+ constructor(code, schemaPath, message, options) {
54
+ super(message, options);
55
+ this.name = 'StudioSchemaProfileError';
56
+ this.code = code;
57
+ this.schemaPath = schemaPath;
58
+ }
59
+ }
60
+ const allowedKeywords = new Set([
61
+ '$defs',
62
+ '$ref',
63
+ '$schema',
64
+ 'additionalProperties',
65
+ 'allOf',
66
+ 'anyOf',
67
+ 'const',
68
+ 'default',
69
+ 'dependentRequired',
70
+ 'description',
71
+ 'else',
72
+ 'enum',
73
+ 'examples',
74
+ 'exclusiveMaximum',
75
+ 'exclusiveMinimum',
76
+ 'if',
77
+ 'items',
78
+ 'maxItems',
79
+ 'maxLength',
80
+ 'maxProperties',
81
+ 'maximum',
82
+ 'minItems',
83
+ 'minLength',
84
+ 'minProperties',
85
+ 'minimum',
86
+ 'multipleOf',
87
+ 'not',
88
+ 'oneOf',
89
+ 'prefixItems',
90
+ 'properties',
91
+ 'propertyNames',
92
+ 'readOnly',
93
+ 'required',
94
+ 'then',
95
+ 'title',
96
+ 'type',
97
+ 'uniqueItems',
98
+ 'writeOnly',
99
+ ]);
100
+ const typeNames = new Set(['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']);
101
+ class SchemaByteLimitError extends RangeError {
102
+ }
103
+ class SchemaBytePreflightDeferred extends TypeError {
104
+ }
105
+ /**
106
+ * Admit and compile one contributed block property schema. The alpha profile
107
+ * is deliberately object-rooted, closed, local-reference-only, non-recursive,
108
+ * and format-free. The returned interpreter performs no code generation.
109
+ */
110
+ export function compileStudioPropertySchema(schema) {
111
+ if (!isRecord(schema)) {
112
+ reject('invalid-root', '', 'Studio property schema root must be a JSON Schema object.');
113
+ }
114
+ // Measure before sorting or recursively interpreting attacker-controlled
115
+ // maps. Canonical member order does not affect encoded length, so this
116
+ // bounded, iterative pass can fail oversized inputs without first
117
+ // allocating the canonical document or doing O(n log n) work.
118
+ try {
119
+ assertCanonicalSchemaByteBudget(schema);
120
+ }
121
+ catch (error) {
122
+ if (error instanceof SchemaByteLimitError) {
123
+ reject('limit-exceeded', '', `Studio property schema exceeds ${MAX_SCHEMA_BYTES} canonical UTF-8 bytes.`);
124
+ }
125
+ if (error instanceof SchemaBytePreflightDeferred) {
126
+ // Precise structural admission below owns diagnostics for JavaScript
127
+ // values that cannot have come from decoded JSON (cycles, aliases,
128
+ // sparse arrays, undefined, or exotic prototypes).
129
+ }
130
+ else {
131
+ reject('invalid-root', '', 'Studio property schema must be a bounded canonical JSON document.', error);
132
+ }
133
+ }
134
+ const state = {
135
+ references: 0,
136
+ schemaNodes: 0,
137
+ seen: new WeakSet(),
138
+ };
139
+ const admissionFailures = [];
140
+ captureAdmissionFailure(() => visitSchema(schema, '', 1, state), admissionFailures);
141
+ captureAdmissionFailure(() => assertNonRecursiveSchema(schema), admissionFailures);
142
+ captureAdmissionFailure(() => assertClosedObjectRoot(schema), admissionFailures);
143
+ const admissionFailure = firstAdmissionFailure(schema, admissionFailures);
144
+ if (admissionFailure !== undefined) {
145
+ throw admissionFailure;
146
+ }
147
+ try {
148
+ return compileProfileSchema(schema);
149
+ }
150
+ catch (error) {
151
+ // All public operand checks run above. Reaching this branch means the
152
+ // interpreter found an inconsistency, so fail closed without exposing an
153
+ // implementation-specific error taxonomy.
154
+ reject('invalid-keyword-value', '', 'Studio property schema does not compile under the strict profile.', error);
155
+ }
156
+ }
157
+ /** Assert that a value is an admitted Studio property schema. */
158
+ export function assertStudioPropertySchema(schema) {
159
+ compileStudioPropertySchema(schema);
160
+ }
161
+ function visitSchema(value, path, depth, state) {
162
+ if (!isRecord(value)) {
163
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a JSON Schema object.`);
164
+ }
165
+ trackObject(value, path, state);
166
+ trackSchemaNode(path, depth, state);
167
+ for (const [keyword, operand] of boundedSchemaEntries(value)) {
168
+ const keywordPath = appendPointer(path, keyword);
169
+ assertSafeObjectKey(keyword, path);
170
+ if (!allowedKeywords.has(keyword)) {
171
+ reject('unsupported-keyword', keywordPath, `${displayPath(keywordPath)} uses keyword ${JSON.stringify(keyword)}, which is not allowed by the Studio Schema Profile.`);
172
+ }
173
+ switch (keyword) {
174
+ case '$defs':
175
+ case 'properties':
176
+ visitSchemaMap(operand, keywordPath, depth + 1, state);
177
+ break;
178
+ case 'additionalProperties':
179
+ case 'else':
180
+ case 'if':
181
+ case 'items':
182
+ case 'not':
183
+ case 'propertyNames':
184
+ case 'then':
185
+ visitSubschema(operand, keywordPath, depth + 1, state);
186
+ break;
187
+ case 'allOf':
188
+ case 'anyOf':
189
+ case 'oneOf':
190
+ case 'prefixItems':
191
+ visitSchemaArray(operand, keywordPath, depth + 1, state);
192
+ break;
193
+ case '$ref':
194
+ visitReference(operand, keywordPath, state);
195
+ break;
196
+ case '$schema':
197
+ if (operand !== DRAFT_2020_12) {
198
+ reject('invalid-keyword-value', keywordPath, `${displayPath(keywordPath)} must declare JSON Schema Draft 2020-12.`);
199
+ }
200
+ break;
201
+ case 'enum':
202
+ visitEnum(operand, keywordPath, 1, state);
203
+ break;
204
+ case 'examples':
205
+ visitExamples(operand, keywordPath, 1, state);
206
+ break;
207
+ case 'dependentRequired':
208
+ visitDependentRequired(operand, keywordPath, state);
209
+ break;
210
+ case 'required':
211
+ visitNameArray(operand, keywordPath, MAX_PROPERTY_NAMES, state);
212
+ break;
213
+ case 'type':
214
+ visitType(operand, keywordPath, state);
215
+ break;
216
+ case 'description':
217
+ visitBoundedString(operand, keywordPath, MAX_DESCRIPTION_LENGTH);
218
+ break;
219
+ case 'title':
220
+ visitBoundedString(operand, keywordPath, MAX_TITLE_LENGTH);
221
+ break;
222
+ case 'maxItems':
223
+ case 'maxLength':
224
+ case 'maxProperties':
225
+ case 'minItems':
226
+ case 'minLength':
227
+ case 'minProperties':
228
+ visitNonNegativeInteger(operand, keywordPath);
229
+ break;
230
+ case 'exclusiveMaximum':
231
+ case 'exclusiveMinimum':
232
+ case 'maximum':
233
+ case 'minimum':
234
+ visitFiniteNumber(operand, keywordPath);
235
+ break;
236
+ case 'multipleOf':
237
+ visitFiniteNumber(operand, keywordPath);
238
+ if (operand <= 0) {
239
+ reject('invalid-keyword-value', keywordPath, `${displayPath(keywordPath)} must be greater than zero.`);
240
+ }
241
+ break;
242
+ case 'readOnly':
243
+ case 'uniqueItems':
244
+ case 'writeOnly':
245
+ if (typeof operand !== 'boolean') {
246
+ reject('invalid-keyword-value', keywordPath, `${displayPath(keywordPath)} must be a boolean.`);
247
+ }
248
+ break;
249
+ case 'const':
250
+ case 'default':
251
+ visitJsonValue(operand, keywordPath, 1, state);
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ function visitSchemaMap(value, path, depth, state) {
257
+ if (!isRecord(value)) {
258
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be an object of schemas.`);
259
+ }
260
+ trackObject(value, path, state);
261
+ const keys = Object.keys(value);
262
+ if (keys.length > MAX_SCHEMA_MAP_PROPERTIES) {
263
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${MAX_SCHEMA_MAP_PROPERTIES} schema entries.`);
264
+ }
265
+ for (const name of keys.sort(compareCodeUnits)) {
266
+ assertSafeObjectKey(name, path);
267
+ visitSchema(value[name], appendPointer(path, name), depth, state);
268
+ }
269
+ }
270
+ function visitSchemaArray(value, path, depth, state) {
271
+ if (!Array.isArray(value) || !isDenseArray(value)) {
272
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a dense JSON array of schemas.`);
273
+ }
274
+ if (value.length === 0) {
275
+ reject('invalid-keyword-value', path, `${displayPath(path)} must contain at least one schema.`);
276
+ }
277
+ if (value.length > MAX_ALTERNATIVES) {
278
+ reject('limit-exceeded', path, `${displayPath(path)} must contain at most ${MAX_ALTERNATIVES} schemas.`);
279
+ }
280
+ trackObject(value, path, state);
281
+ for (const [index, schema] of value.entries()) {
282
+ visitSubschema(schema, appendPointer(path, String(index)), depth, state);
283
+ }
284
+ }
285
+ function visitSubschema(value, path, depth, state) {
286
+ if (typeof value === 'boolean') {
287
+ trackSchemaNode(path, depth, state);
288
+ return;
289
+ }
290
+ visitSchema(value, path, depth, state);
291
+ }
292
+ function trackSchemaNode(path, depth, state) {
293
+ if (depth > MAX_SCHEMA_DEPTH) {
294
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds the Studio Schema Profile depth limit.`);
295
+ }
296
+ state.schemaNodes += 1;
297
+ if (state.schemaNodes > MAX_SCHEMA_NODES) {
298
+ reject('limit-exceeded', path, `Studio property schema exceeds ${MAX_SCHEMA_NODES} schema nodes.`);
299
+ }
300
+ }
301
+ function visitReference(value, path, state) {
302
+ if (!isPortableLocalReference(value)) {
303
+ reject('invalid-reference', path, `${displayPath(path)} must be a bounded local JSON Pointer reference.`);
304
+ }
305
+ state.references += 1;
306
+ if (state.references > MAX_REFERENCES) {
307
+ reject('limit-exceeded', path, `Studio property schema exceeds ${MAX_REFERENCES} references.`);
308
+ }
309
+ }
310
+ function visitEnum(value, path, depth, state) {
311
+ if (!Array.isArray(value) || !isDenseArray(value)) {
312
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a dense JSON array.`);
313
+ }
314
+ if (value.length === 0) {
315
+ reject('invalid-keyword-value', path, `${displayPath(path)} must contain at least one value.`);
316
+ }
317
+ if (value.length > MAX_ENUM_MEMBERS) {
318
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${MAX_ENUM_MEMBERS} members.`);
319
+ }
320
+ trackObject(value, path, state);
321
+ const members = new Set();
322
+ for (const [index, member] of value.entries()) {
323
+ visitJsonValue(member, appendPointer(path, String(index)), depth, state);
324
+ const canonical = canonicalStringify(member, {
325
+ maximumDepth: MAX_JSON_DEPTH + 1,
326
+ });
327
+ if (members.has(canonical)) {
328
+ reject('invalid-keyword-value', appendPointer(path, String(index)), `${displayPath(path)} must contain unique JSON values.`);
329
+ }
330
+ members.add(canonical);
331
+ }
332
+ }
333
+ function visitExamples(value, path, depth, state) {
334
+ if (!Array.isArray(value) || !isDenseArray(value)) {
335
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a dense JSON array.`);
336
+ }
337
+ if (value.length > MAX_EXAMPLES) {
338
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${MAX_EXAMPLES} examples.`);
339
+ }
340
+ trackObject(value, path, state);
341
+ for (const [index, example] of value.entries()) {
342
+ visitJsonValue(example, appendPointer(path, String(index)), depth, state);
343
+ }
344
+ }
345
+ function visitDependentRequired(value, path, state) {
346
+ if (!isRecord(value)) {
347
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be an object of property-name arrays.`);
348
+ }
349
+ trackObject(value, path, state);
350
+ const keys = Object.keys(value);
351
+ if (keys.length > MAX_SCHEMA_MAP_PROPERTIES) {
352
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${MAX_SCHEMA_MAP_PROPERTIES} dependency entries.`);
353
+ }
354
+ for (const name of keys.sort(compareCodeUnits)) {
355
+ assertSafeObjectKey(name, path);
356
+ visitNameArray(value[name], appendPointer(path, name), MAX_PROPERTY_NAMES, state);
357
+ }
358
+ }
359
+ function visitNameArray(value, path, maximum, state) {
360
+ if (!Array.isArray(value) || !isDenseArray(value)) {
361
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a dense array of property names.`);
362
+ }
363
+ if (value.length > maximum) {
364
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${maximum} property names.`);
365
+ }
366
+ trackObject(value, path, state);
367
+ const names = new Set();
368
+ for (const [index, name] of value.entries()) {
369
+ if (typeof name !== 'string') {
370
+ reject('invalid-keyword-value', appendPointer(path, String(index)), `${displayPath(path)} must contain only property-name strings.`);
371
+ }
372
+ assertSafeObjectKey(name, path, appendPointer(path, String(index)));
373
+ if (names.has(name)) {
374
+ reject('invalid-keyword-value', appendPointer(path, String(index)), `${displayPath(path)} must list unique property names.`);
375
+ }
376
+ names.add(name);
377
+ }
378
+ }
379
+ function visitType(value, path, state) {
380
+ if (typeof value === 'string') {
381
+ if (!typeNames.has(value)) {
382
+ reject('invalid-keyword-value', path, `${displayPath(path)} names an unknown JSON Schema type.`);
383
+ }
384
+ return;
385
+ }
386
+ if (!Array.isArray(value) || !isDenseArray(value) || value.length === 0 || value.length > 7) {
387
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a type name or a non-empty array of at most seven names.`);
388
+ }
389
+ trackObject(value, path, state);
390
+ const names = new Set();
391
+ for (const [index, name] of value.entries()) {
392
+ if (typeof name !== 'string' || !typeNames.has(name) || names.has(name)) {
393
+ reject('invalid-keyword-value', appendPointer(path, String(index)), `${displayPath(path)} must list unique, known JSON Schema type names.`);
394
+ }
395
+ names.add(name);
396
+ }
397
+ }
398
+ function visitBoundedString(value, path, maximum) {
399
+ if (typeof value !== 'string') {
400
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a string.`);
401
+ }
402
+ if (codePointLength(value) > maximum) {
403
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${maximum} characters.`);
404
+ }
405
+ }
406
+ function visitNonNegativeInteger(value, path) {
407
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
408
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a non-negative integer.`);
409
+ }
410
+ }
411
+ function visitFiniteNumber(value, path) {
412
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
413
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a finite number.`);
414
+ }
415
+ }
416
+ function visitJsonValue(value, path, depth, state) {
417
+ if (value === null ||
418
+ typeof value === 'boolean' ||
419
+ typeof value === 'string' ||
420
+ (typeof value === 'number' && Number.isFinite(value))) {
421
+ return;
422
+ }
423
+ if (depth > MAX_JSON_DEPTH) {
424
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds the Studio Schema Profile JSON depth limit.`);
425
+ }
426
+ if (Array.isArray(value)) {
427
+ if (!isDenseArray(value)) {
428
+ reject('invalid-keyword-value', path, `${displayPath(path)} must be a dense JSON array.`);
429
+ }
430
+ trackObject(value, path, state);
431
+ if (value.length > MAX_JSON_ITEMS) {
432
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${MAX_JSON_ITEMS} JSON items.`);
433
+ }
434
+ for (const [index, entry] of value.entries()) {
435
+ visitJsonValue(entry, appendPointer(path, String(index)), depth + 1, state);
436
+ }
437
+ return;
438
+ }
439
+ if (isRecord(value)) {
440
+ trackObject(value, path, state);
441
+ const keys = Object.keys(value);
442
+ if (keys.length > MAX_JSON_PROPERTIES) {
443
+ reject('limit-exceeded', path, `${displayPath(path)} exceeds ${MAX_JSON_PROPERTIES} JSON properties.`);
444
+ }
445
+ for (const key of keys.sort(compareCodeUnits)) {
446
+ assertSafeObjectKey(key, path);
447
+ visitJsonValue(value[key], appendPointer(path, key), depth + 1, state);
448
+ }
449
+ return;
450
+ }
451
+ reject('invalid-keyword-value', path, `${displayPath(path)} is not JSON-compatible.`);
452
+ }
453
+ function assertClosedObjectRoot(schema) {
454
+ // Root invariants follow the same UTF-16 member precedence as the general
455
+ // admission walk: additionalProperties sorts before type.
456
+ if (schema.additionalProperties !== false) {
457
+ reject('invalid-root', '/additionalProperties', 'Studio property schema root must declare additionalProperties: false.');
458
+ }
459
+ if (schema.type !== 'object') {
460
+ reject('invalid-root', '/type', 'Studio property schema root must declare exactly type "object".');
461
+ }
462
+ }
463
+ function captureAdmissionFailure(action, failures) {
464
+ try {
465
+ action();
466
+ }
467
+ catch (error) {
468
+ if (error instanceof StudioSchemaProfileError) {
469
+ failures.push(error);
470
+ return;
471
+ }
472
+ throw error;
473
+ }
474
+ }
475
+ function firstAdmissionFailure(root, failures) {
476
+ let first;
477
+ for (const failure of failures) {
478
+ if (first === undefined ||
479
+ compareAdmissionPaths(root, failure.schemaPath, first.schemaPath) < 0) {
480
+ first = failure;
481
+ }
482
+ }
483
+ return first;
484
+ }
485
+ /**
486
+ * Compare two diagnostic locations in the same order the admission grammar
487
+ * visits them: object members by UTF-16 code unit, array members by numeric
488
+ * index, and a container before any descendant. Missing root invariants are
489
+ * virtual object members, so they participate without a special-case pass
490
+ * precedence.
491
+ */
492
+ function compareAdmissionPaths(root, left, right) {
493
+ const leftTokens = pointerTokens(left);
494
+ const rightTokens = pointerTokens(right);
495
+ let parent = root;
496
+ const sharedLength = Math.min(leftTokens.length, rightTokens.length);
497
+ for (let index = 0; index < sharedLength; index += 1) {
498
+ const leftToken = leftTokens[index];
499
+ const rightToken = rightTokens[index];
500
+ if (leftToken === undefined || rightToken === undefined) {
501
+ break;
502
+ }
503
+ if (leftToken !== rightToken) {
504
+ if (Array.isArray(parent)) {
505
+ const leftIndex = Number(leftToken);
506
+ const rightIndex = Number(rightToken);
507
+ if (Number.isSafeInteger(leftIndex) && Number.isSafeInteger(rightIndex)) {
508
+ return leftIndex - rightIndex;
509
+ }
510
+ }
511
+ return compareCodeUnits(leftToken, rightToken);
512
+ }
513
+ if ((isRecord(parent) || Array.isArray(parent)) && Object.hasOwn(parent, leftToken)) {
514
+ parent = parent[leftToken];
515
+ }
516
+ else {
517
+ parent = undefined;
518
+ }
519
+ }
520
+ return leftTokens.length - rightTokens.length;
521
+ }
522
+ function pointerTokens(pointer) {
523
+ if (pointer === '') {
524
+ return [];
525
+ }
526
+ return pointer
527
+ .slice(1)
528
+ .split('/')
529
+ .map((token) => token.replaceAll('~1', '/').replaceAll('~0', '~'));
530
+ }
531
+ function assertSafeObjectKey(key, path, rejectionPath = appendPointer(path, key)) {
532
+ if (codePointLength(key) > MAX_OBJECT_KEY_LENGTH) {
533
+ reject('limit-exceeded', rejectionPath, `${displayPath(path)} contains an object member name longer than ${MAX_OBJECT_KEY_LENGTH} characters.`);
534
+ }
535
+ if (key.length === 0 ||
536
+ key === '__proto__' ||
537
+ key === 'constructor' ||
538
+ key === 'prototype' ||
539
+ containsControlCharacter(key)) {
540
+ reject('unsafe-member', rejectionPath, `${displayPath(path)} contains forbidden object member name ${JSON.stringify(key)}.`);
541
+ }
542
+ }
543
+ function assertNonRecursiveSchema(root) {
544
+ const failures = [];
545
+ const indexes = new Map();
546
+ const adjacency = [];
547
+ const reverseAdjacency = [];
548
+ const referenceSites = [];
549
+ const expanded = new WeakSet();
550
+ let eligibleReferences = 0;
551
+ const appendGraphPath = (parent, token) => ({
552
+ parent,
553
+ token,
554
+ });
555
+ const graphPathPointer = (path) => {
556
+ const tokens = [];
557
+ let current = path;
558
+ while (current !== undefined) {
559
+ tokens.push(current.token);
560
+ current = current.parent;
561
+ }
562
+ let pointer = '';
563
+ for (let index = tokens.length - 1; index >= 0; index -= 1) {
564
+ const token = tokens[index];
565
+ if (token !== undefined) {
566
+ pointer = appendPointer(pointer, token);
567
+ }
568
+ }
569
+ return pointer;
570
+ };
571
+ const ensureNode = (node) => {
572
+ const existing = indexes.get(node);
573
+ if (existing !== undefined) {
574
+ return existing;
575
+ }
576
+ const index = adjacency.length;
577
+ indexes.set(node, index);
578
+ adjacency.push([]);
579
+ reverseAdjacency.push([]);
580
+ return index;
581
+ };
582
+ const connect = (source, target) => {
583
+ adjacency[source]?.push(target);
584
+ reverseAdjacency[target]?.push(source);
585
+ };
586
+ ensureNode(root);
587
+ const stack = [
588
+ { depth: 1, diagnosticsEligible: true, node: root, path: undefined },
589
+ ];
590
+ while (stack.length > 0) {
591
+ const frame = stack.pop();
592
+ if (frame === undefined || expanded.has(frame.node)) {
593
+ continue;
594
+ }
595
+ expanded.add(frame.node);
596
+ const source = ensureNode(frame.node);
597
+ const children = [];
598
+ const addChild = (value, path, diagnosticsEligible = frame.diagnosticsEligible) => {
599
+ if (!isRecord(value)) {
600
+ return;
601
+ }
602
+ const target = ensureNode(value);
603
+ connect(source, target);
604
+ const depth = frame.depth + 1;
605
+ children.push({
606
+ depth,
607
+ diagnosticsEligible: diagnosticsEligible && depth <= MAX_SCHEMA_DEPTH,
608
+ node: value,
609
+ path,
610
+ });
611
+ };
612
+ for (const [keyword, operand] of boundedSchemaEntries(frame.node)) {
613
+ const keywordPath = appendGraphPath(frame.path, keyword);
614
+ switch (keyword) {
615
+ case '$defs':
616
+ case 'properties':
617
+ if (isRecord(operand)) {
618
+ const names = Object.keys(operand);
619
+ const childrenEligible = names.length <= MAX_SCHEMA_MAP_PROPERTIES;
620
+ if (childrenEligible) {
621
+ names.sort(compareCodeUnits);
622
+ }
623
+ for (const name of names) {
624
+ addChild(operand[name], appendGraphPath(keywordPath, name), frame.diagnosticsEligible && childrenEligible);
625
+ }
626
+ }
627
+ break;
628
+ case '$ref':
629
+ if (isPortableLocalReference(operand)) {
630
+ const reportsDiagnostic = frame.diagnosticsEligible && (eligibleReferences += 1) <= MAX_REFERENCES;
631
+ const referencePath = reportsDiagnostic ? graphPathPointer(keywordPath) : '';
632
+ try {
633
+ const target = resolveLocalReference(root, operand, referencePath);
634
+ if (!target.schemaPosition) {
635
+ if (reportsDiagnostic) {
636
+ failures.push(new StudioSchemaProfileError('invalid-reference', referencePath, `Local schema reference ${operand} does not resolve to a schema position.`));
637
+ }
638
+ }
639
+ else if (isRecord(target.value)) {
640
+ const targetIndex = ensureNode(target.value);
641
+ connect(source, targetIndex);
642
+ if (reportsDiagnostic) {
643
+ referenceSites.push({ path: referencePath, source, target: targetIndex });
644
+ }
645
+ }
646
+ }
647
+ catch (error) {
648
+ if (error instanceof StudioSchemaProfileError) {
649
+ if (reportsDiagnostic) {
650
+ failures.push(error);
651
+ }
652
+ }
653
+ else {
654
+ throw error;
655
+ }
656
+ }
657
+ }
658
+ break;
659
+ case 'additionalProperties':
660
+ case 'else':
661
+ case 'if':
662
+ case 'items':
663
+ case 'not':
664
+ case 'propertyNames':
665
+ case 'then':
666
+ addChild(operand, keywordPath);
667
+ break;
668
+ case 'allOf':
669
+ case 'anyOf':
670
+ case 'oneOf':
671
+ case 'prefixItems':
672
+ if (Array.isArray(operand)) {
673
+ const childrenEligible = operand.length > 0 && operand.length <= MAX_ALTERNATIVES && isDenseArray(operand);
674
+ for (let index = 0; index < operand.length; index += 1) {
675
+ if (Object.hasOwn(operand, index)) {
676
+ addChild(operand[index], appendGraphPath(keywordPath, String(index)), frame.diagnosticsEligible && childrenEligible);
677
+ }
678
+ }
679
+ }
680
+ break;
681
+ }
682
+ }
683
+ for (let index = children.length - 1; index >= 0; index -= 1) {
684
+ const child = children[index];
685
+ if (child !== undefined) {
686
+ stack.push(child);
687
+ }
688
+ }
689
+ }
690
+ const components = stronglyConnectedComponents(adjacency, reverseAdjacency);
691
+ for (const site of referenceSites) {
692
+ if (components[site.source] === components[site.target]) {
693
+ failures.push(new StudioSchemaProfileError('recursive-schema', site.path, 'Recursive contributed schemas are not admitted by the alpha profile.'));
694
+ }
695
+ }
696
+ const failure = firstAdmissionFailure(root, failures);
697
+ if (failure !== undefined) {
698
+ throw failure;
699
+ }
700
+ }
701
+ function stronglyConnectedComponents(adjacency, reverseAdjacency) {
702
+ const visited = new Uint8Array(adjacency.length);
703
+ const finishOrder = [];
704
+ for (let start = 0; start < adjacency.length; start += 1) {
705
+ if (visited[start] !== 0) {
706
+ continue;
707
+ }
708
+ visited[start] = 1;
709
+ const stack = [{ edge: 0, node: start }];
710
+ while (stack.length > 0) {
711
+ const frame = stack[stack.length - 1];
712
+ if (frame === undefined) {
713
+ break;
714
+ }
715
+ const edges = adjacency[frame.node] ?? [];
716
+ const target = edges[frame.edge];
717
+ if (target !== undefined) {
718
+ frame.edge += 1;
719
+ if (visited[target] === 0) {
720
+ visited[target] = 1;
721
+ stack.push({ edge: 0, node: target });
722
+ }
723
+ }
724
+ else {
725
+ finishOrder.push(frame.node);
726
+ stack.pop();
727
+ }
728
+ }
729
+ }
730
+ const components = new Int32Array(adjacency.length);
731
+ components.fill(-1);
732
+ let component = 0;
733
+ for (let order = finishOrder.length - 1; order >= 0; order -= 1) {
734
+ const start = finishOrder[order];
735
+ if (start === undefined || components[start] !== -1) {
736
+ continue;
737
+ }
738
+ components[start] = component;
739
+ const stack = [start];
740
+ while (stack.length > 0) {
741
+ const node = stack.pop();
742
+ if (node === undefined) {
743
+ continue;
744
+ }
745
+ for (const source of reverseAdjacency[node] ?? []) {
746
+ if (components[source] === -1) {
747
+ components[source] = component;
748
+ stack.push(source);
749
+ }
750
+ }
751
+ }
752
+ component += 1;
753
+ }
754
+ return components;
755
+ }
756
+ function resolveLocalReference(root, reference, path) {
757
+ if (reference === '#') {
758
+ return { schemaPosition: true, value: root };
759
+ }
760
+ let current = root;
761
+ let position = 'schema';
762
+ for (const encodedToken of reference.slice(2).split('/')) {
763
+ const token = encodedToken.replaceAll('~1', '/').replaceAll('~0', '~');
764
+ let nextPosition = 'other';
765
+ if (position === 'schema' && isRecord(current)) {
766
+ switch (token) {
767
+ case '$defs':
768
+ case 'properties':
769
+ nextPosition = 'schema-map';
770
+ break;
771
+ case 'additionalProperties':
772
+ case 'else':
773
+ case 'if':
774
+ case 'items':
775
+ case 'not':
776
+ case 'propertyNames':
777
+ case 'then':
778
+ nextPosition = 'schema';
779
+ break;
780
+ case 'allOf':
781
+ case 'anyOf':
782
+ case 'oneOf':
783
+ case 'prefixItems':
784
+ nextPosition = 'schema-array';
785
+ break;
786
+ }
787
+ }
788
+ else if (position === 'schema-map' && isRecord(current)) {
789
+ nextPosition = 'schema';
790
+ }
791
+ else if (position === 'schema-array' && Array.isArray(current)) {
792
+ nextPosition = 'schema';
793
+ }
794
+ if (!isRecord(current) && !Array.isArray(current)) {
795
+ reject('invalid-reference', path, `Local schema reference ${reference} does not resolve to a schema.`);
796
+ }
797
+ if (!Object.hasOwn(current, token)) {
798
+ reject('invalid-reference', path, `Local schema reference ${reference} does not resolve to a schema.`);
799
+ }
800
+ current = current[token];
801
+ position = nextPosition;
802
+ }
803
+ if (typeof current !== 'boolean' && !isRecord(current)) {
804
+ reject('invalid-reference', path, `Local schema reference ${reference} does not resolve to a schema.`);
805
+ }
806
+ return { schemaPosition: position === 'schema', value: current };
807
+ }
808
+ function reject(code, schemaPath, message, cause) {
809
+ throw new StudioSchemaProfileError(code, schemaPath, message, cause === undefined ? undefined : { cause });
810
+ }
811
+ function appendPointer(pointer, token) {
812
+ return `${pointer}/${token.replaceAll('~', '~0').replaceAll('/', '~1')}`;
813
+ }
814
+ function displayPath(path) {
815
+ return path === '' ? 'schema root' : path;
816
+ }
817
+ function containsControlCharacter(value) {
818
+ for (let index = 0; index < value.length; index += 1) {
819
+ const code = value.charCodeAt(index);
820
+ if (code <= 0x1f || code === 0x7f) {
821
+ return true;
822
+ }
823
+ }
824
+ return false;
825
+ }
826
+ function isPortableLocalReference(value) {
827
+ return (typeof value === 'string' &&
828
+ codePointLength(value) <= MAX_REFERENCE_LENGTH &&
829
+ !containsControlCharacter(value) &&
830
+ /^#(?:\/(?:[A-Za-z0-9._!$&'()*+,;=:@-]|~[01])*)*$/u.test(value));
831
+ }
832
+ function codePointLength(value) {
833
+ let length = 0;
834
+ for (let index = 0; index < value.length; index += 1) {
835
+ length += 1;
836
+ const code = value.charCodeAt(index);
837
+ if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
838
+ const next = value.charCodeAt(index + 1);
839
+ if ((next & 0xfc00) === 0xdc00) {
840
+ index += 1;
841
+ }
842
+ }
843
+ }
844
+ return length;
845
+ }
846
+ /**
847
+ * Enforce the canonical byte ceiling without materialising or sorting the
848
+ * canonical document. Object order cannot change encoded length. This pass
849
+ * is iterative so an over-deep untrusted value cannot exhaust the JavaScript
850
+ * call stack before the published schema/JSON depth checks run.
851
+ */
852
+ function assertCanonicalSchemaByteBudget(root) {
853
+ const stack = [root];
854
+ const seen = new WeakSet();
855
+ let bytes = 0;
856
+ const consume = (amount) => {
857
+ bytes += amount;
858
+ if (bytes > MAX_SCHEMA_BYTES) {
859
+ throw new SchemaByteLimitError();
860
+ }
861
+ };
862
+ while (stack.length > 0) {
863
+ const value = stack.pop();
864
+ if (value === null) {
865
+ consume(4);
866
+ continue;
867
+ }
868
+ switch (typeof value) {
869
+ case 'boolean':
870
+ consume(value ? 4 : 5);
871
+ continue;
872
+ case 'number': {
873
+ if (!Number.isFinite(value)) {
874
+ throw new SchemaBytePreflightDeferred();
875
+ }
876
+ const encoded = JSON.stringify(Object.is(value, -0) ? 0 : value);
877
+ consume(encoded.length);
878
+ continue;
879
+ }
880
+ case 'string':
881
+ consumeCanonicalJsonString(value, consume);
882
+ continue;
883
+ case 'object':
884
+ break;
885
+ default:
886
+ throw new SchemaBytePreflightDeferred();
887
+ }
888
+ if (seen.has(value)) {
889
+ throw new SchemaBytePreflightDeferred();
890
+ }
891
+ seen.add(value);
892
+ if (Array.isArray(value)) {
893
+ const members = value;
894
+ // Length alone accounts for brackets and separators. Reject an
895
+ // obviously oversized array before Object.keys allocates an entry for
896
+ // every member while checking density.
897
+ consume(2 + Math.max(0, members.length - 1));
898
+ if (!isDenseArray(members)) {
899
+ throw new SchemaBytePreflightDeferred();
900
+ }
901
+ for (let index = members.length - 1; index >= 0; index -= 1) {
902
+ const member = members[index];
903
+ if (member === undefined) {
904
+ throw new SchemaBytePreflightDeferred();
905
+ }
906
+ stack.push(member);
907
+ }
908
+ continue;
909
+ }
910
+ if (!isRecord(value)) {
911
+ throw new SchemaBytePreflightDeferred();
912
+ }
913
+ const keys = Object.keys(value);
914
+ consume(2 + Math.max(0, keys.length - 1));
915
+ for (let index = keys.length - 1; index >= 0; index -= 1) {
916
+ const key = keys[index];
917
+ if (key === undefined) {
918
+ continue;
919
+ }
920
+ const member = value[key];
921
+ if (member === undefined) {
922
+ throw new SchemaBytePreflightDeferred();
923
+ }
924
+ consumeCanonicalJsonString(key, consume);
925
+ consume(1);
926
+ stack.push(member);
927
+ }
928
+ }
929
+ }
930
+ function consumeCanonicalJsonString(value, consume) {
931
+ consume(2); // opening and closing quotes
932
+ for (let index = 0; index < value.length; index += 1) {
933
+ const code = value.charCodeAt(index);
934
+ if (code === 0x22 ||
935
+ code === 0x5c ||
936
+ code === 0x08 ||
937
+ code === 0x09 ||
938
+ code === 0x0a ||
939
+ code === 0x0c ||
940
+ code === 0x0d) {
941
+ consume(2);
942
+ }
943
+ else if (code <= 0x1f) {
944
+ consume(6);
945
+ }
946
+ else if (code <= 0x7f) {
947
+ consume(1);
948
+ }
949
+ else if (code <= 0x7ff) {
950
+ consume(2);
951
+ }
952
+ else if (code >= 0xd800 && code <= 0xdbff) {
953
+ const next = value.charCodeAt(index + 1);
954
+ if (index + 1 < value.length && (next & 0xfc00) === 0xdc00) {
955
+ consume(4);
956
+ index += 1;
957
+ }
958
+ else {
959
+ consume(6);
960
+ }
961
+ }
962
+ else if (code >= 0xdc00 && code <= 0xdfff) {
963
+ consume(6);
964
+ }
965
+ else {
966
+ consume(3);
967
+ }
968
+ }
969
+ }
970
+ /**
971
+ * Sort at most the closed keyword set plus the first invalid member. This
972
+ * preserves deterministic first-error precedence without sorting an
973
+ * arbitrarily large attacker-controlled schema object.
974
+ */
975
+ function boundedSchemaEntries(value) {
976
+ const keys = Object.keys(value);
977
+ if (keys.length <= allowedKeywords.size) {
978
+ return keys.sort(compareCodeUnits).map((key) => [key, value[key]]);
979
+ }
980
+ const candidates = [];
981
+ let firstInvalid;
982
+ for (const key of keys) {
983
+ if (allowedKeywords.has(key)) {
984
+ candidates.push(key);
985
+ }
986
+ else if (firstInvalid === undefined || compareCodeUnits(key, firstInvalid) < 0) {
987
+ firstInvalid = key;
988
+ }
989
+ }
990
+ if (firstInvalid !== undefined) {
991
+ candidates.push(firstInvalid);
992
+ }
993
+ return candidates.sort(compareCodeUnits).map((key) => [key, value[key]]);
994
+ }
995
+ function isDenseArray(value) {
996
+ const keys = Object.keys(value);
997
+ return keys.length === value.length && keys.every((key, index) => key === String(index));
998
+ }
999
+ function compareCodeUnits(left, right) {
1000
+ return left < right ? -1 : left > right ? 1 : 0;
1001
+ }
1002
+ function trackObject(value, path, state) {
1003
+ if (state.seen.has(value)) {
1004
+ reject('invalid-root', path, `${displayPath(path)} reuses or cycles a JSON object.`);
1005
+ }
1006
+ state.seen.add(value);
1007
+ }
1008
+ function isRecord(value) {
1009
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1010
+ return false;
1011
+ }
1012
+ const prototype = Object.getPrototypeOf(value);
1013
+ return prototype === Object.prototype || prototype === null;
1014
+ }
1015
+ //# sourceMappingURL=schema-profile.js.map