@taprootio/docs-artifact 1.0.1

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 (36) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +362 -0
  3. package/bin/taproot-docs-conformance.js +20 -0
  4. package/bin/taproot-docs-validate.js +17 -0
  5. package/conformance.d.ts +11 -0
  6. package/fixtures/README.md +24 -0
  7. package/fixtures/conformance.json +1630 -0
  8. package/fixtures/invalid/duplicate-json-key.json +1 -0
  9. package/fixtures/invalid/hash-drift/taproot-docs/fragments/welcome.html +1 -0
  10. package/fixtures/invalid/size-drift/taproot-docs/fragments/welcome.html +1 -0
  11. package/fixtures/invalid/unsafe-markup/taproot-docs/fragments/welcome.html +1 -0
  12. package/fixtures/valid/complete/taproot-docs/assets/pixel.png.base64 +1 -0
  13. package/fixtures/valid/complete/taproot-docs/fragments/button.en-us.html +1 -0
  14. package/fixtures/valid/complete/taproot-docs/fragments/button.fr-fr.html +1 -0
  15. package/fixtures/valid/complete/taproot-docs/fragments/getting-started.en-us.html +3 -0
  16. package/fixtures/valid/complete/taproot-docs/fragments/getting-started.fr-fr.html +3 -0
  17. package/fixtures/valid/complete/taproot-docs-manifest.json +231 -0
  18. package/fixtures/valid/minimal/taproot-docs/fragments/welcome.html +1 -0
  19. package/fixtures/valid/minimal/taproot-docs-manifest.json +80 -0
  20. package/index.d.ts +204 -0
  21. package/node.d.ts +6 -0
  22. package/package.json +54 -0
  23. package/schema/taproot-docs-manifest.schema.json +487 -0
  24. package/src/artifact-validator.js +870 -0
  25. package/src/binary.js +67 -0
  26. package/src/conformance.js +578 -0
  27. package/src/constants.js +104 -0
  28. package/src/errors.js +139 -0
  29. package/src/index.js +18 -0
  30. package/src/json.js +516 -0
  31. package/src/manifest-validator.js +650 -0
  32. package/src/markup.js +578 -0
  33. package/src/node-internal.js +4 -0
  34. package/src/node.js +513 -0
  35. package/src/path.js +103 -0
  36. package/src/text.js +30 -0
package/src/json.js ADDED
@@ -0,0 +1,516 @@
1
+ import { LIMITS } from "./constants.js";
2
+ import { classifyBinaryInput, snapshotBinaryInput } from "./binary.js";
3
+ import { sanitizeValidationError } from "./errors.js";
4
+
5
+ class StrictJsonError extends Error {
6
+ constructor(code, message, path = "$") {
7
+ super(message);
8
+ this.code = code;
9
+ this.path = path;
10
+ }
11
+ }
12
+
13
+ class StrictJsonParser {
14
+ constructor(text) {
15
+ this.text = text;
16
+ this.offset = 0;
17
+ }
18
+
19
+ parse() {
20
+ this.#skipWhitespace();
21
+ const value = this.#parseValue("$", 0);
22
+ this.#skipWhitespace();
23
+ if (this.offset !== this.text.length) {
24
+ throw new StrictJsonError("json.invalid", `Unexpected content at offset ${this.offset}.`);
25
+ }
26
+ return value;
27
+ }
28
+
29
+ #parseValue(path, depth) {
30
+ if (depth > 64) {
31
+ throw new StrictJsonError("json.too_deep", "JSON nesting may not exceed 64 levels.", path);
32
+ }
33
+ const char = this.text[this.offset];
34
+ if (char === "{") return this.#parseObject(path, depth + 1);
35
+ if (char === "[") return this.#parseArray(path, depth + 1);
36
+ if (char === '"') return this.#parseString(path);
37
+ if (char === "t" && this.text.startsWith("true", this.offset)) {
38
+ this.offset += 4;
39
+ return true;
40
+ }
41
+ if (char === "f" && this.text.startsWith("false", this.offset)) {
42
+ this.offset += 5;
43
+ return false;
44
+ }
45
+ if (char === "n" && this.text.startsWith("null", this.offset)) {
46
+ this.offset += 4;
47
+ return null;
48
+ }
49
+ if (char === "-" || (char >= "0" && char <= "9")) return this.#parseNumber(path);
50
+ throw new StrictJsonError("json.invalid", `Expected a JSON value at offset ${this.offset}.`, path);
51
+ }
52
+
53
+ #parseObject(path, depth) {
54
+ this.offset += 1;
55
+ const object = Object.create(null);
56
+ const keys = new Set();
57
+ this.#skipWhitespace();
58
+ if (this.text[this.offset] === "}") {
59
+ this.offset += 1;
60
+ return object;
61
+ }
62
+
63
+ while (this.offset < this.text.length) {
64
+ if (this.text[this.offset] !== '"') {
65
+ throw new StrictJsonError("json.invalid", `Expected an object key at offset ${this.offset}.`, path);
66
+ }
67
+ const key = this.#parseString(path);
68
+ const childPath = `${path}.${key}`;
69
+ if (keys.has(key)) {
70
+ throw new StrictJsonError("json.duplicate_key", `Duplicate JSON object key '${key}'.`, childPath);
71
+ }
72
+ keys.add(key);
73
+ this.#skipWhitespace();
74
+ if (this.text[this.offset] !== ":") {
75
+ throw new StrictJsonError("json.invalid", `Expected ':' after object key at offset ${this.offset}.`, childPath);
76
+ }
77
+ this.offset += 1;
78
+ this.#skipWhitespace();
79
+ object[key] = this.#parseValue(childPath, depth);
80
+ this.#skipWhitespace();
81
+ const delimiter = this.text[this.offset];
82
+ if (delimiter === "}") {
83
+ this.offset += 1;
84
+ return object;
85
+ }
86
+ if (delimiter !== ",") {
87
+ throw new StrictJsonError("json.invalid", `Expected ',' or '}' at offset ${this.offset}.`, path);
88
+ }
89
+ this.offset += 1;
90
+ this.#skipWhitespace();
91
+ }
92
+ throw new StrictJsonError("json.invalid", "Unterminated JSON object.", path);
93
+ }
94
+
95
+ #parseArray(path, depth) {
96
+ this.offset += 1;
97
+ const values = [];
98
+ this.#skipWhitespace();
99
+ if (this.text[this.offset] === "]") {
100
+ this.offset += 1;
101
+ return values;
102
+ }
103
+
104
+ while (this.offset < this.text.length) {
105
+ values.push(this.#parseValue(`${path}[${values.length}]`, depth));
106
+ this.#skipWhitespace();
107
+ const delimiter = this.text[this.offset];
108
+ if (delimiter === "]") {
109
+ this.offset += 1;
110
+ return values;
111
+ }
112
+ if (delimiter !== ",") {
113
+ throw new StrictJsonError("json.invalid", `Expected ',' or ']' at offset ${this.offset}.`, path);
114
+ }
115
+ this.offset += 1;
116
+ this.#skipWhitespace();
117
+ }
118
+ throw new StrictJsonError("json.invalid", "Unterminated JSON array.", path);
119
+ }
120
+
121
+ #parseString(path) {
122
+ this.offset += 1;
123
+ let value = "";
124
+ while (this.offset < this.text.length) {
125
+ const char = this.text[this.offset];
126
+ if (char === '"') {
127
+ this.offset += 1;
128
+ return value;
129
+ }
130
+ if (char.charCodeAt(0) < 0x20) {
131
+ throw new StrictJsonError("json.invalid", `Unescaped control character at offset ${this.offset}.`, path);
132
+ }
133
+ if (char !== "\\") {
134
+ value += char;
135
+ this.offset += 1;
136
+ continue;
137
+ }
138
+
139
+ this.offset += 1;
140
+ const escape = this.text[this.offset];
141
+ const simple = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: "\t" };
142
+ if (Object.hasOwn(simple, escape)) {
143
+ value += simple[escape];
144
+ this.offset += 1;
145
+ continue;
146
+ }
147
+ if (escape !== "u") {
148
+ throw new StrictJsonError("json.invalid", `Invalid string escape at offset ${this.offset}.`, path);
149
+ }
150
+ const first = this.#parseUnicodeEscape(path);
151
+ if (first >= 0xd800 && first <= 0xdbff) {
152
+ if (this.text[this.offset] !== "\\" || this.text[this.offset + 1] !== "u") {
153
+ throw new StrictJsonError("json.invalid_unicode", "High surrogate must be followed by a low surrogate.", path);
154
+ }
155
+ this.offset += 1;
156
+ const second = this.#parseUnicodeEscape(path);
157
+ if (second < 0xdc00 || second > 0xdfff) {
158
+ throw new StrictJsonError("json.invalid_unicode", "High surrogate must be followed by a low surrogate.", path);
159
+ }
160
+ value += String.fromCodePoint(0x10000 + ((first - 0xd800) << 10) + (second - 0xdc00));
161
+ } else if (first >= 0xdc00 && first <= 0xdfff) {
162
+ throw new StrictJsonError("json.invalid_unicode", "A low surrogate cannot appear without a high surrogate.", path);
163
+ } else {
164
+ value += String.fromCharCode(first);
165
+ }
166
+ }
167
+ throw new StrictJsonError("json.invalid", "Unterminated JSON string.", path);
168
+ }
169
+
170
+ #parseUnicodeEscape(path) {
171
+ this.offset += 1;
172
+ const hex = this.text.slice(this.offset, this.offset + 4);
173
+ if (!/^[0-9a-fA-F]{4}$/u.test(hex)) {
174
+ throw new StrictJsonError("json.invalid_unicode", `Invalid Unicode escape at offset ${this.offset}.`, path);
175
+ }
176
+ this.offset += 4;
177
+ return Number.parseInt(hex, 16);
178
+ }
179
+
180
+ #parseNumber(path) {
181
+ const match = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/u.exec(this.text.slice(this.offset));
182
+ if (!match) {
183
+ throw new StrictJsonError("json.invalid", `Invalid number at offset ${this.offset}.`, path);
184
+ }
185
+ this.offset += match[0].length;
186
+ const number = Number(match[0]);
187
+ if (!Number.isFinite(number)) {
188
+ throw new StrictJsonError("json.invalid_number", "JSON numbers must be finite.", path);
189
+ }
190
+ return number;
191
+ }
192
+
193
+ #skipWhitespace() {
194
+ while (this.offset < this.text.length && /[\u0009\u000a\u000d\u0020]/u.test(this.text[this.offset])) {
195
+ this.offset += 1;
196
+ }
197
+ }
198
+ }
199
+
200
+ export function classifyManifestInput(input) {
201
+ if (typeof input === "string") return { kind: "string" };
202
+ if (input === null || (typeof input !== "object" && typeof input !== "function")) return { kind: "other" };
203
+ const binary = classifyBinaryInput(input);
204
+ if (binary.kind !== "other") return binary;
205
+ try {
206
+ Object.getPrototypeOf(input);
207
+ } catch {
208
+ return { kind: "invalid" };
209
+ }
210
+ return { kind: "object" };
211
+ }
212
+
213
+ function asBytes(input, classification = classifyManifestInput(input)) {
214
+ if (typeof input === "string") {
215
+ if (input.length > LIMITS.manifestBytes) {
216
+ throw new StrictJsonError("manifest.too_large", `Manifest bytes may not exceed ${LIMITS.manifestBytes}.`);
217
+ }
218
+ let byteLength = 0;
219
+ for (let offset = 0; offset < input.length; offset += 1) {
220
+ const first = input.charCodeAt(offset);
221
+ if (first >= 0xd800 && first <= 0xdbff) {
222
+ const second = input.charCodeAt(offset + 1);
223
+ if (second < 0xdc00 || second > 0xdfff) {
224
+ throw new StrictJsonError("json.invalid_unicode", "Manifest text must contain only well-formed Unicode scalar values.");
225
+ }
226
+ byteLength += 4;
227
+ offset += 1;
228
+ } else if (first >= 0xdc00 && first <= 0xdfff) {
229
+ throw new StrictJsonError("json.invalid_unicode", "Manifest text must contain only well-formed Unicode scalar values.");
230
+ } else {
231
+ byteLength += first <= 0x7f ? 1 : first <= 0x7ff ? 2 : 3;
232
+ }
233
+ if (byteLength > LIMITS.manifestBytes) {
234
+ throw new StrictJsonError("manifest.too_large", `Manifest bytes may not exceed ${LIMITS.manifestBytes}.`);
235
+ }
236
+ }
237
+ return new TextEncoder().encode(input);
238
+ }
239
+ if (classification.kind === "uint8array" || classification.kind === "arraybuffer") {
240
+ const snapshot = snapshotBinaryInput(input, LIMITS.manifestBytes, classification);
241
+ if (snapshot.kind === "too_large") {
242
+ throw new StrictJsonError("manifest.too_large", `Manifest bytes may not exceed ${LIMITS.manifestBytes}.`);
243
+ }
244
+ if (snapshot.kind === "bytes") return snapshot.bytes;
245
+ }
246
+ throw new TypeError("Manifest input must be a string, Uint8Array, or ArrayBuffer.");
247
+ }
248
+
249
+ export function parseManifestJson(input, classification = classifyManifestInput(input)) {
250
+ let bytes;
251
+ try {
252
+ bytes = asBytes(input, classification);
253
+ } catch (error) {
254
+ if (error instanceof StrictJsonError) {
255
+ return { ok: false, errors: [sanitizeValidationError(error)] };
256
+ }
257
+ return { ok: false, errors: [sanitizeValidationError({ code: "json.invalid_input", path: "$", message: error.message })] };
258
+ }
259
+ if (bytes.byteLength > LIMITS.manifestBytes) {
260
+ return {
261
+ ok: false,
262
+ errors: [sanitizeValidationError({
263
+ code: "manifest.too_large",
264
+ path: "$",
265
+ message: `Manifest bytes may not exceed ${LIMITS.manifestBytes}.`,
266
+ })],
267
+ };
268
+ }
269
+
270
+ let text;
271
+ try {
272
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
273
+ } catch {
274
+ return { ok: false, errors: [sanitizeValidationError({ code: "json.invalid_utf8", path: "$", message: "Manifest bytes must be valid UTF-8." })] };
275
+ }
276
+ if (text.charCodeAt(0) === 0xfeff) {
277
+ return { ok: false, errors: [sanitizeValidationError({ code: "json.bom", path: "$", message: "Manifest JSON must not start with a byte-order mark." })] };
278
+ }
279
+
280
+ try {
281
+ return { ok: true, value: new StrictJsonParser(text).parse() };
282
+ } catch (error) {
283
+ if (error instanceof StrictJsonError) {
284
+ return { ok: false, errors: [sanitizeValidationError(error)] };
285
+ }
286
+ throw error;
287
+ }
288
+ }
289
+
290
+ function canonicalize(value) {
291
+ if (Array.isArray(value)) return value.map(canonicalize);
292
+ if (value !== null && typeof value === "object") {
293
+ const result = Object.create(null);
294
+ for (const key of Object.keys(value).sort()) result[key] = canonicalize(value[key]);
295
+ return result;
296
+ }
297
+ return value;
298
+ }
299
+
300
+ export function canonicalJson(value) {
301
+ return `${JSON.stringify(canonicalize(value), null, 2)}\n`;
302
+ }
303
+
304
+ function utf8Length(value) {
305
+ let bytes = 0;
306
+ for (const scalar of value) {
307
+ const codePoint = scalar.codePointAt(0);
308
+ bytes += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
309
+ }
310
+ return bytes;
311
+ }
312
+
313
+ export function canonicalJsonByteLength(value, maximumBytes = Number.MAX_SAFE_INTEGER) {
314
+ let bytes = 0;
315
+ const add = (count) => {
316
+ bytes += count;
317
+ return bytes <= maximumBytes;
318
+ };
319
+ const visit = (candidate, depth) => {
320
+ if (Array.isArray(candidate)) {
321
+ if (candidate.length === 0) return add(2);
322
+ if (!add(2)) return false;
323
+ for (let index = 0; index < candidate.length; index += 1) {
324
+ if (!add((depth + 1) * 2) || !visit(candidate[index], depth + 1)) return false;
325
+ if (!add(index + 1 < candidate.length ? 2 : 1)) return false;
326
+ }
327
+ return add((depth * 2) + 1);
328
+ }
329
+ if (candidate !== null && typeof candidate === "object") {
330
+ const keys = Object.keys(candidate).sort();
331
+ if (keys.length === 0) return add(2);
332
+ if (!add(2)) return false;
333
+ for (let index = 0; index < keys.length; index += 1) {
334
+ const key = keys[index];
335
+ const encodedKey = JSON.stringify(key);
336
+ if (!add(((depth + 1) * 2) + utf8Length(encodedKey) + 2) || !visit(candidate[key], depth + 1)) return false;
337
+ if (!add(index + 1 < keys.length ? 2 : 1)) return false;
338
+ }
339
+ return add((depth * 2) + 1);
340
+ }
341
+ return add(utf8Length(JSON.stringify(candidate)));
342
+ };
343
+ visit(value, 0);
344
+ if (bytes <= maximumBytes) add(1);
345
+ return bytes > maximumBytes ? maximumBytes + 1 : bytes;
346
+ }
347
+
348
+ class ManifestObjectPreflightError extends Error {
349
+ constructor(code, message) {
350
+ super(message);
351
+ this.code = code;
352
+ }
353
+ }
354
+
355
+ const OMITTED_JSON_VALUE = Symbol("taproot.docs.omittedJsonValue");
356
+
357
+ function jsonStringByteLength(value, maximumBytes) {
358
+ let bytes = 2;
359
+ for (let offset = 0; offset < value.length; offset += 1) {
360
+ const first = value.charCodeAt(offset);
361
+ if (first === 0x22 || first === 0x5c || first === 0x08 || first === 0x09 || first === 0x0a || first === 0x0c || first === 0x0d) {
362
+ bytes += 2;
363
+ } else if (first <= 0x1f || (first >= 0xd800 && first <= 0xdfff)) {
364
+ if (first >= 0xd800 && first <= 0xdbff) {
365
+ const second = value.charCodeAt(offset + 1);
366
+ if (second >= 0xdc00 && second <= 0xdfff) {
367
+ bytes += 4;
368
+ offset += 1;
369
+ } else {
370
+ bytes += 6;
371
+ }
372
+ } else {
373
+ bytes += 6;
374
+ }
375
+ } else {
376
+ bytes += first <= 0x7f ? 1 : first <= 0x7ff ? 2 : 3;
377
+ }
378
+ if (bytes > maximumBytes) return maximumBytes + 1;
379
+ }
380
+ return bytes;
381
+ }
382
+
383
+ export function preflightManifestObject(value) {
384
+ const visiting = new WeakSet();
385
+ const cachedSubtrees = new WeakMap();
386
+ const state = { work: 0 };
387
+ const add = (left, right) => Math.min(LIMITS.manifestBytes + 1, left + right);
388
+ const consumeWork = (logicalWork = 1) => {
389
+ state.work += logicalWork;
390
+ if (state.work > LIMITS.manifestObjectWork) {
391
+ throw new ManifestObjectPreflightError(
392
+ "manifest.too_large",
393
+ `Manifest object validation work may not exceed ${LIMITS.manifestObjectWork} values.`,
394
+ );
395
+ }
396
+ };
397
+ const visit = (candidate, depth) => {
398
+ if (typeof candidate === "string") {
399
+ consumeWork();
400
+ return {
401
+ bytes: jsonStringByteLength(candidate, LIMITS.manifestBytes),
402
+ logicalWork: 1,
403
+ relativeDepth: -1,
404
+ snapshot: candidate,
405
+ };
406
+ }
407
+ if (candidate === null || typeof candidate !== "object") {
408
+ consumeWork();
409
+ if (typeof candidate === "bigint") {
410
+ throw new ManifestObjectPreflightError("manifest.invalid_graph", "Manifest object values must use JSON-compatible types.");
411
+ }
412
+ if (["undefined", "function", "symbol"].includes(typeof candidate)) {
413
+ return { bytes: 0, logicalWork: 1, relativeDepth: -1, snapshot: OMITTED_JSON_VALUE };
414
+ }
415
+ const encoded = JSON.stringify(candidate);
416
+ const snapshot = typeof candidate === "number" && !Number.isFinite(candidate)
417
+ ? null
418
+ : Object.is(candidate, -0)
419
+ ? 0
420
+ : candidate;
421
+ return { bytes: encoded.length, logicalWork: 1, relativeDepth: -1, snapshot };
422
+ }
423
+ if (visiting.has(candidate)) {
424
+ throw new ManifestObjectPreflightError("manifest.cyclic", "Manifest objects may not contain cyclic references.");
425
+ }
426
+ const cached = cachedSubtrees.get(candidate);
427
+ if (cached !== undefined) {
428
+ if (depth + cached.relativeDepth > LIMITS.manifestObjectDepth) {
429
+ throw new ManifestObjectPreflightError(
430
+ "manifest.object_too_deep",
431
+ `Manifest object nesting may not exceed ${LIMITS.manifestObjectDepth} levels.`,
432
+ );
433
+ }
434
+ consumeWork(cached.logicalWork);
435
+ return cached;
436
+ }
437
+ if (depth > LIMITS.manifestObjectDepth) {
438
+ throw new ManifestObjectPreflightError(
439
+ "manifest.object_too_deep",
440
+ `Manifest object nesting may not exceed ${LIMITS.manifestObjectDepth} levels.`,
441
+ );
442
+ }
443
+ const workAtStart = state.work;
444
+ consumeWork();
445
+ visiting.add(candidate);
446
+ let bytes = 2;
447
+ let relativeDepth = 0;
448
+ let snapshot;
449
+ if (Array.isArray(candidate)) {
450
+ const length = Reflect.get(candidate, "length");
451
+ if (!Number.isSafeInteger(length) || length < 0 || length > 0xffffffff) {
452
+ throw new ManifestObjectPreflightError("manifest.invalid_graph", "Manifest arrays must have a valid JSON length.");
453
+ }
454
+ snapshot = new Array(length);
455
+ for (let index = 0; index < length; index += 1) {
456
+ if (index > 0) bytes = add(bytes, 1);
457
+ const key = String(index);
458
+ const descriptor = Reflect.getOwnPropertyDescriptor(candidate, key);
459
+ if (descriptor !== undefined) {
460
+ const child = visit(Reflect.get(candidate, key), depth + 1);
461
+ bytes = add(bytes, child.bytes);
462
+ relativeDepth = Math.max(relativeDepth, child.relativeDepth + 1);
463
+ snapshot[index] = child.snapshot === OMITTED_JSON_VALUE ? null : child.snapshot;
464
+ } else {
465
+ consumeWork();
466
+ bytes = add(bytes, 4);
467
+ snapshot[index] = null;
468
+ }
469
+ }
470
+ } else {
471
+ snapshot = Object.create(null);
472
+ let first = true;
473
+ for (const key of Object.keys(candidate)) {
474
+ const child = visit(Reflect.get(candidate, key), depth + 1);
475
+ if (child.snapshot === OMITTED_JSON_VALUE) continue;
476
+ if (!first) bytes = add(bytes, 1);
477
+ first = false;
478
+ bytes = add(bytes, jsonStringByteLength(key, LIMITS.manifestBytes));
479
+ bytes = add(bytes, 1);
480
+ bytes = add(bytes, child.bytes);
481
+ relativeDepth = Math.max(relativeDepth, child.relativeDepth + 1);
482
+ snapshot[key] = child.snapshot;
483
+ }
484
+ }
485
+ visiting.delete(candidate);
486
+ const result = {
487
+ bytes,
488
+ logicalWork: state.work - workAtStart,
489
+ relativeDepth,
490
+ snapshot,
491
+ };
492
+ cachedSubtrees.set(candidate, result);
493
+ return result;
494
+ };
495
+
496
+ try {
497
+ const result = visit(value, 0);
498
+ return {
499
+ ok: true,
500
+ exceedsByteLimit: result.bytes > LIMITS.manifestBytes,
501
+ value: result.snapshot === OMITTED_JSON_VALUE ? undefined : result.snapshot,
502
+ };
503
+ } catch (error) {
504
+ if (error instanceof ManifestObjectPreflightError) {
505
+ return { ok: false, errors: [sanitizeValidationError({ code: error.code, path: "$", message: error.message })] };
506
+ }
507
+ return {
508
+ ok: false,
509
+ errors: [sanitizeValidationError({
510
+ code: "manifest.invalid_graph",
511
+ path: "$",
512
+ message: "Manifest object properties could not be traversed safely.",
513
+ })],
514
+ };
515
+ }
516
+ }