@taprootio/docs-artifact 1.0.1 → 1.1.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 (42) hide show
  1. package/README.md +196 -30
  2. package/bin/taproot-docs-conformance.js +34 -13
  3. package/bin/taproot-docs-validate.js +44 -9
  4. package/fixtures/README.md +19 -0
  5. package/fixtures/prebuilt/conformance.json +444 -0
  6. package/fixtures/prebuilt/golden/espalier.tar.gz +0 -0
  7. package/fixtures/prebuilt/invalid/duplicate-json-key.json +4 -0
  8. package/fixtures/prebuilt/valid/espalier/404.html +2 -0
  9. package/fixtures/prebuilt/valid/espalier/api/show-toast/index.html +5 -0
  10. package/fixtures/prebuilt/valid/espalier/assets/icons.svg +1 -0
  11. package/fixtures/prebuilt/valid/espalier/assets/pulse.svg +1 -0
  12. package/fixtures/prebuilt/valid/espalier/assets/search-worker.js +1 -0
  13. package/fixtures/prebuilt/valid/espalier/assets/search.wasm +0 -0
  14. package/fixtures/prebuilt/valid/espalier/assets/site.css +3 -0
  15. package/fixtures/prebuilt/valid/espalier/assets/site.js +2 -0
  16. package/fixtures/prebuilt/valid/espalier/dist/-6iE9DOe.css +1 -0
  17. package/fixtures/prebuilt/valid/espalier/dist/_AN3XUT_.css +1 -0
  18. package/fixtures/prebuilt/valid/espalier/guides/index.html +2 -0
  19. package/fixtures/prebuilt/valid/espalier/index.html +2 -0
  20. package/fixtures/prebuilt/valid/espalier/pagefind/index/abc.pf_index +0 -0
  21. package/fixtures/prebuilt/valid/espalier/pagefind/pagefind.js +4 -0
  22. package/fixtures/prebuilt/valid/espalier/taproot-docs-prebuilt-manifest.json +135 -0
  23. package/index.d.ts +9 -0
  24. package/node.d.ts +1 -0
  25. package/package.json +28 -5
  26. package/prebuilt-archive.d.ts +27 -0
  27. package/prebuilt-conformance.d.ts +27 -0
  28. package/prebuilt-node.d.ts +7 -0
  29. package/prebuilt.d.ts +128 -0
  30. package/schema/taproot-docs-prebuilt-manifest.schema.json +156 -0
  31. package/src/constants.js +4 -0
  32. package/src/index.js +13 -0
  33. package/src/json.js +50 -26
  34. package/src/node.js +2 -0
  35. package/src/prebuilt-archive.js +248 -0
  36. package/src/prebuilt-artifact-validator.js +236 -0
  37. package/src/prebuilt-conformance.js +151 -0
  38. package/src/prebuilt-constants.js +55 -0
  39. package/src/prebuilt-manifest-validator.js +654 -0
  40. package/src/prebuilt-node.js +518 -0
  41. package/src/prebuilt-path.js +129 -0
  42. package/src/prebuilt.js +20 -0
package/README.md CHANGED
@@ -1,20 +1,134 @@
1
1
  # `@taprootio/docs-artifact`
2
2
 
3
3
  The canonical, independently versioned contract between documentation producers
4
- and Taproot Docs consumers. The public npm package contains all four authorities
5
- that must move together:
4
+ and Taproot Docs consumers. Version 1.1 adds a separate prebuilt contract without
5
+ changing or reinterpreting the released managed schema v1 contract. The public
6
+ npm package contains the authorities that must move together:
6
7
 
7
8
  - `schema/taproot-docs-manifest.schema.json` for the structural v1 shape;
8
9
  - the validator and deterministic serializer exported from the package root;
9
10
  - the Node directory validator and `taproot-docs-validate` command;
10
11
  - valid and adversarial fixtures exposed by `@taprootio/docs-artifact/conformance`.
11
12
 
12
- WTFM and Taproot must pin the same exact released package version. Neither
13
- repository copies the schema, path rules, markup allow-list, limits, or fixture
14
- data.
13
+ Prebuilt mode has its own `schema/taproot-docs-prebuilt-manifest.schema.json`,
14
+ `@taprootio/docs-artifact/prebuilt` validator and serializer,
15
+ `@taprootio/docs-artifact/prebuilt/node` directory validator, and
16
+ `@taprootio/docs-artifact/prebuilt/archive` deterministic encoder, and
17
+ `@taprootio/docs-artifact/prebuilt/conformance` cases and golden vector.
18
+ Consumers select one entry point and one manifest filename explicitly; they
19
+ never infer a mode from fields or archive contents.
20
+
21
+ Each producer and consumer pins the exact package release for its selected
22
+ contract. Managed artifacts produced under `1.0.1` remain valid and unchanged;
23
+ prebuilt producers and consumers pin `1.1.0`. No repository copies the schema,
24
+ path rules, markup allow-list, limits, or fixture data.
25
+
26
+ ## Prebuilt Docs contract
27
+
28
+ Prebuilt mode is a Docs publication mode for repository-built static output,
29
+ not a general static-hosting product. Its root is
30
+ `taproot-docs-prebuilt-manifest.json`, `schemaVersion` is `1`, `mode` is
31
+ `prebuilt`, and its required capability is
32
+ `taproot.docs.prebuilt.files.v1`. The closed manifest contains only immutable
33
+ GitHub source/build provenance, capabilities, `notFoundFile`, exact `files`,
34
+ stable `resources`, and direct `redirects`. It has no response headers, cache
35
+ directives, CSP fragments, routes table, origin allowlist, or executable hook.
36
+
37
+ Every payload file has one ASCII relative POSIX path, exact media type, byte
38
+ length, and `sha256:<64 lowercase hex>` digest. Paths use portable filename
39
+ segments whose first character is a letter, digit, `-`, or `_` and whose last
40
+ character is a letter or digit. They reject absolute paths, backslashes,
41
+ percent escapes, controls, empty or dot segments, Windows device aliases, and
42
+ the manifest name, and must fit both the 255-byte/64-directory envelope and
43
+ POSIX USTAR's 100-byte name plus 155-byte prefix fields. Exact and
44
+ ASCII-case-folded duplicates fail closed.
45
+ The complete directory is closed: every declared file must be a regular file,
46
+ and every other file, empty/unneeded directory, link, socket, FIFO, or device is
47
+ rejected.
48
+
49
+ The authenticated platform control route
50
+ `/__taproot/internal/published-site-routing` is matched first and is the only
51
+ reserved request route in the prebuilt file namespace. The edge then resolves
52
+ the exact host, site, environment, immutable output pointer, and publication
53
+ mode before applying public `/api/*` behavior. Managed mode retains the Taproot
54
+ API proxy. Prebuilt files and redirects own the complete remaining namespace,
55
+ including `/api/*`, and never fall through to that proxy.
56
+
57
+ Within the prebuilt namespace, the file tree is the request router.
58
+ `index.html` maps to `/`, `x/index.html` maps to `/x/`, and every other file
59
+ maps to its exact `/<path>`. Redirects resolve before file lookup, map one
60
+ canonical source directly to a stable resource key, and permit only status 301
61
+ or 308. A redirect source may not collide, including after ASCII case-folding,
62
+ with another redirect or a file route. `notFoundFile` is exactly `404.html` and
63
+ must be declared as `text/html; charset=utf-8`.
64
+
65
+ Resources contain exactly a durable `key`, one declared UTF-8 HTML `file`, and
66
+ its current display `title`. Two keys cannot claim one file, and the 404 cannot
67
+ be a successful-response resource. At publication, Taproot attaches the
68
+ resulting `SiteResource` id as origin object metadata for mapped HTML. Eligible
69
+ unmapped HTML is an explicit path-only analytics bucket. HTML 200 analytics stay
70
+ cookie-free and no browser analytics script is injected.
71
+
72
+ Taproot preserves every declared payload byte and publisher path. It owns and
73
+ may attach `Content-Type`, cache policy, CSP, framing, MIME-sniffing, referrer,
74
+ permissions, staging authorization/noindex, range, and content-encoding
75
+ behavior; publisher fields cannot override them. Every prebuilt response gets
76
+ this exact v1 policy, including HTML, the canonical 404, redirects, JavaScript
77
+ and worker scripts, WebAssembly, directly navigated SVG documents, and all
78
+ other assets:
79
+
80
+ ```text
81
+ default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; form-action 'self'; worker-src 'self'; manifest-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
82
+ ```
83
+
84
+ `'wasm-unsafe-eval'` permits the accepted same-origin Pagefind Wasm runtime
85
+ without permitting string evaluation through `'unsafe-eval'`. V1 has no
86
+ publisher-supplied origin allowlist or per-response relaxation.
87
+
88
+ The archive identifier is the distinct
89
+ `taproot-docs-prebuilt-tar-gzip-v1`. Its deterministic bytes contain the
90
+ canonical manifest first and the exact payload snapshots in lexicographic path
91
+ order. Each entry is a regular POSIX USTAR record with paths of at most 100
92
+ bytes stored entirely in the name field; longer paths split at the rightmost
93
+ slash that fits the 155-byte prefix and 100-byte name fields. Records use mode
94
+ `0000644`; zero uid/gid/mtime/devmajor/devminor numeric fields; typeflag `0`;
95
+ magic `ustar\0`; version `00`; empty owner/group names; six-octal-digit,
96
+ NUL-space checksum; zero content padding; and exactly two zero terminator
97
+ blocks. One gzip member
98
+ uses header bytes `1f 8b 08 00 00 00 00 00 00 ff`; every non-final stored
99
+ DEFLATE block is exactly 65,535 bytes and the final block holds the remaining
100
+ 1–65,535 bytes. The trailer contains the tar CRC-32 and low 32 bits of its byte
101
+ length. The committed representative archive and its byte length/SHA-256 are
102
+ the compatibility vector; platform `tar` or `gzip` output is not a substitute.
103
+ The separate platform upload ceiling remains 256 MiB compressed; conformance
104
+ under the artifact contract does not imply that an upload fits that independent
105
+ limit.
106
+
107
+ Public prebuilt ceilings are 8 MiB canonical manifest bytes, 25,000 files,
108
+ 512 MiB declared/actual uncompressed bytes, 64 MiB per file, 25,000 resources,
109
+ 10,000 redirects, 255 path bytes, and 64 directory levels. Object inputs retain
110
+ the package's bounded 250,000-value/64-level work envelope, and diagnostics use
111
+ the same stable bounded/sorted error contract as managed mode. Filesystem walks
112
+ retain the existing 40,000-entry work ceiling. The Node validator opens files
113
+ once with nonblocking/no-follow semantics, verifies the
114
+ descriptor and every path component before and after reads, repeats the closed
115
+ tree walk, and rejects identity, size, type, or ancestor races. Validation never
116
+ executes or parses customer HTML, CSS, JavaScript, WebAssembly, Pagefind data,
117
+ or SVG.
118
+
119
+ Ordinary prebuilt output may execute only on separately verified customer
120
+ production and staging hostnames. The only first-party platform-host exception
121
+ is the closed Espalier tuple: hosts `espalier.taproot.io` and
122
+ `espalier.taproot.test`, GitHub repository id `934883082` with locator
123
+ `taprootio/taproot-controls`, and `refs/heads/main`. Product ingestion must
124
+ enforce that complete tuple before serving executable prebuilt bytes beneath
125
+ `taproot.io` or `taproot.test`. WTFM remains managed.
15
126
 
16
127
  ## Artifact layout and portability
17
128
 
129
+ The remainder of this document describes the unchanged managed schema-v1
130
+ contract.
131
+
18
132
  The producer writes this additive payload into its ordinary static output:
19
133
 
20
134
  ```text
@@ -87,7 +201,8 @@ The npm package uses semantic versioning independently of `schemaVersion`:
87
201
  enforcement. A patch may reject an input that never conformed to the stated
88
202
  fail-closed rules.
89
203
 
90
- Producer and consumer release PRs update one exact dependency pin together. The
204
+ Producer and consumer release PRs coordinate each selected contract's exact
205
+ dependency pin. The
91
206
  private Taproot `docs-artifact-v<package-version>` tag does not publish npm
92
207
  directly. It verifies and copies the reviewed package allowlist into
93
208
  `packages/docs-artifact/` in public
@@ -189,28 +304,28 @@ ceiling as the runtime manifest and markup `lang` validators.
189
304
 
190
305
  The exported `LIMITS` object is the v1 ceiling. Important bounds are:
191
306
 
192
- | Input | Maximum |
193
- |---|---:|
194
- | Manifest | 2 MiB |
195
- | Object-manifest traversal / nesting depth | 250,000 values / 64 |
196
- | Managed semantic bytes | 256 MiB |
197
- | Listed semantic files | 20,000 |
198
- | Managed filesystem entries | 40,000 |
199
- | Managed directory depth | 32 |
200
- | Managed relative path | 512 characters |
201
- | Resources | 10,000 |
202
- | Locale variants | 20,000 |
203
- | Fragments | 20,000 |
204
- | Assets | 10,000 |
205
- | Redirects | 10,000 |
206
- | Navigation nodes / depth | 20,000 / 12 |
207
- | Markup elements / nesting depth per fragment | 100,000 / 128 |
208
- | One fragment | 2 MiB |
209
- | One asset | 25 MiB |
210
- | Decoded image canvas | 67,108,864 pixels |
211
- | Cumulative decoded animated frames | 67,108,864 pixels |
212
- | Animated image frames | 1,000 |
213
- | Consumer-supported capabilities | 100 entries, 200 characters each |
307
+ | Input | Maximum |
308
+ | -------------------------------------------- | -------------------------------: |
309
+ | Manifest | 2 MiB |
310
+ | Object-manifest traversal / nesting depth | 250,000 values / 64 |
311
+ | Managed semantic bytes | 256 MiB |
312
+ | Listed semantic files | 20,000 |
313
+ | Managed filesystem entries | 40,000 |
314
+ | Managed directory depth | 32 |
315
+ | Managed relative path | 512 characters |
316
+ | Resources | 10,000 |
317
+ | Locale variants | 20,000 |
318
+ | Fragments | 20,000 |
319
+ | Assets | 10,000 |
320
+ | Redirects | 10,000 |
321
+ | Navigation nodes / depth | 20,000 / 12 |
322
+ | Markup elements / nesting depth per fragment | 100,000 / 128 |
323
+ | One fragment | 2 MiB |
324
+ | One asset | 25 MiB |
325
+ | Decoded image canvas | 67,108,864 pixels |
326
+ | Cumulative decoded animated frames | 67,108,864 pixels |
327
+ | Animated image frames | 1,000 |
328
+ | Consumer-supported capabilities | 100 entries, 200 characters each |
214
329
 
215
330
  Navigation depth counts each top-level item as level one. A leaf at level 12 is
216
331
  valid; a child node at level 13 is rejected, while the global node ceiling still
@@ -349,14 +464,65 @@ const canonicalBytes = serializeManifest(manifestObject);
349
464
  const sharedCases = await loadConformanceCases();
350
465
  ```
351
466
 
467
+ The root also exports additive `validateManagedManifest`,
468
+ `assertValidManagedManifest`, `serializeManagedManifest`,
469
+ `validateManagedArtifact`, and `assertValidManagedArtifact` aliases plus
470
+ `MANAGED_*` identity constants. `@taprootio/docs-artifact/node` likewise exports
471
+ `validateManagedArtifactDirectory`. The legacy managed names above remain
472
+ unchanged.
473
+
474
+ Prebuilt consumers use the separate, explicit surface:
475
+
476
+ ```js
477
+ import {
478
+ PREBUILT_ARCHIVE_FORMAT,
479
+ serializePrebuiltManifest,
480
+ validatePrebuiltArtifact,
481
+ validatePrebuiltManifest,
482
+ } from "@taprootio/docs-artifact/prebuilt";
483
+ import { createDeterministicPrebuiltArchive } from "@taprootio/docs-artifact/prebuilt/archive";
484
+ import {
485
+ loadPrebuiltConformanceCases,
486
+ validatePrebuiltConformanceCase,
487
+ } from "@taprootio/docs-artifact/prebuilt/conformance";
488
+ import { validatePrebuiltArtifactDirectory } from "@taprootio/docs-artifact/prebuilt/node";
489
+
490
+ const manifestResult = validatePrebuiltManifest(manifestBytes);
491
+ const artifactResult = await validatePrebuiltArtifact(
492
+ manifestBytes,
493
+ exactFiles,
494
+ );
495
+ const directoryResult = await validatePrebuiltArtifactDirectory("_site");
496
+ const canonicalBytes = serializePrebuiltManifest(manifestObject);
497
+ const sharedCases = await loadPrebuiltConformanceCases();
498
+ const sharedResults = await Promise.all(
499
+ sharedCases.map(validatePrebuiltConformanceCase),
500
+ );
501
+ const archive = createDeterministicPrebuiltArchive(directoryResult.value);
502
+ const representativeVector = sharedCases.find((fixture) =>
503
+ fixture.expectedArchive
504
+ )?.expectedArchive;
505
+ ```
506
+
507
+ `createDeterministicPrebuiltArchive()` accepts only the isolated snapshot
508
+ returned by a successful prebuilt artifact or directory validation. It verifies
509
+ the snapshot's declared sizes and hashes again against the bytes it archives,
510
+ then returns `{ format, bytes, byteLength, contentHash }`. The representative
511
+ conformance case exposes its committed `expectedArchive` bytes, `byteLength`,
512
+ and `sha256` for independent producer and consumer byte-identity checks.
513
+ `validatePrebuiltConformanceCase()` runs ordinary cases against the in-memory
514
+ validator and safely materializes the published non-regular-file race case in a
515
+ disposable directory for the Node boundary.
516
+
352
517
  The assertion variants throw `DocsArtifactValidationError` with the same stable
353
518
  `errors` array. The CLI prints `code path: message` diagnostics and exits nonzero:
354
519
 
355
520
  ```bash
356
- npx --package=@taprootio/docs-artifact@1.0.1 taproot-docs-validate ./_site
521
+ npx --package=@taprootio/docs-artifact@1.1.0 taproot-docs-validate ./_site
522
+ npx --package=@taprootio/docs-artifact@1.1.0 taproot-docs-validate --mode prebuilt ./_site
357
523
  ```
358
524
 
359
525
  Consumers should assert error `code` and `path`, not human-readable wording.
360
526
  The package test suite also runs `npm pack --dry-run --json --ignore-scripts`
361
- and requires the exact reviewed 36-file tarball inventory, including the ISC
527
+ and requires the exact reviewed tarball inventory, including the ISC
362
528
  `LICENSE`, so a publishable file cannot appear or disappear silently.
@@ -2,19 +2,40 @@
2
2
 
3
3
  import { validateArtifact } from "../src/artifact-validator.js";
4
4
  import { loadConformanceCases } from "../src/conformance.js";
5
+ import { loadPrebuiltConformanceCases, validatePrebuiltConformanceCase } from "../src/prebuilt-conformance.js";
5
6
 
6
- let failed = false;
7
- for (const fixture of await loadConformanceCases()) {
8
- const result = await validateArtifact(fixture.manifest, fixture.files);
9
- const codes = result.ok ? [] : [...new Set(result.errors.map((error) => error.code))].sort();
10
- const expected = [...fixture.expectedCodes].sort();
11
- if (result.ok !== fixture.valid || JSON.stringify(codes) !== JSON.stringify(expected)) {
12
- failed = true;
13
- process.stderr.write(`${fixture.name}: expected ${JSON.stringify(expected)}, received ${JSON.stringify(codes)}\n`);
14
- }
15
- }
16
- if (failed) {
17
- process.exitCode = 1;
7
+ const arguments_ = process.argv.slice(2);
8
+ const modeIndex = arguments_.indexOf("--mode");
9
+ const mode = modeIndex === -1 ? "managed" : arguments_[modeIndex + 1];
10
+ const invalidArguments = modeIndex === -1
11
+ ? arguments_.length !== 0
12
+ : arguments_.length !== 2 || arguments_.filter((argument) => argument === "--mode").length !== 1;
13
+ if (!["managed", "prebuilt"].includes(mode) || invalidArguments) {
14
+ process.stderr.write("Usage: taproot-docs-conformance [--mode managed|prebuilt]\n");
15
+ process.exitCode = 2;
18
16
  } else {
19
- process.stdout.write("Taproot Docs artifact conformance fixtures passed.\n");
17
+ const loadCases = mode === "prebuilt" ? loadPrebuiltConformanceCases : loadConformanceCases;
18
+ let failed = false;
19
+ for (const fixture of await loadCases()) {
20
+ const result = mode === "prebuilt"
21
+ ? await validatePrebuiltConformanceCase(fixture)
22
+ : await validateArtifact(fixture.manifest, fixture.files);
23
+ const codes = result.ok ? [] : [...new Set(result.errors.map((error) => error.code))].sort();
24
+ const expected = [...fixture.expectedCodes].sort();
25
+ if (result.ok !== fixture.valid || JSON.stringify(codes) !== JSON.stringify(expected)) {
26
+ failed = true;
27
+ process.stderr.write(
28
+ `${fixture.name}: expected ${JSON.stringify(expected)}, received ${JSON.stringify(codes)}\n`,
29
+ );
30
+ }
31
+ }
32
+ if (failed) {
33
+ process.exitCode = 1;
34
+ } else {
35
+ process.stdout.write(
36
+ mode === "prebuilt"
37
+ ? "Taproot Docs prebuilt artifact conformance fixtures passed.\n"
38
+ : "Taproot Docs artifact conformance fixtures passed.\n",
39
+ );
40
+ }
20
41
  }
@@ -1,17 +1,52 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { validateArtifactDirectory } from "../src/node.js";
4
+ import { validatePrebuiltArtifactDirectory } from "../src/prebuilt-node.js";
4
5
 
5
6
  const arguments_ = process.argv.slice(2);
6
7
  const json = arguments_.includes("--json");
7
- const directory = arguments_.find((argument) => argument !== "--json") ?? process.cwd();
8
- const result = await validateArtifactDirectory(directory);
9
-
10
- if (json) {
11
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
12
- } else if (result.ok) {
13
- process.stdout.write(`Valid Taproot Docs artifact: ${result.value.fileCount} semantic files, ${result.value.totalBytes} bytes.\n`);
8
+ const modeIndex = arguments_.indexOf("--mode");
9
+ const mode = modeIndex === -1 ? "managed" : arguments_[modeIndex + 1];
10
+ const positional = arguments_.filter((argument, index) => (
11
+ argument !== "--json" && argument !== "--mode" && (modeIndex === -1 || index !== modeIndex + 1)
12
+ ));
13
+ const invalidArguments = arguments_.filter((argument) => argument === "--mode").length > 1
14
+ || arguments_.filter((argument) => argument === "--json").length > 1
15
+ || positional.length > 1
16
+ || positional.some((argument) => argument.startsWith("--"));
17
+ if (!["managed", "prebuilt"].includes(mode) || invalidArguments) {
18
+ process.stderr.write("Usage: taproot-docs-validate [--json] [--mode managed|prebuilt] [directory]\n");
19
+ process.exitCode = 2;
14
20
  } else {
15
- for (const error of result.errors) process.stderr.write(`${error.code} ${error.path}: ${error.message}\n`);
21
+ const directory = positional[0] ?? process.cwd();
22
+ const result = mode === "prebuilt"
23
+ ? await validatePrebuiltArtifactDirectory(directory)
24
+ : await validateArtifactDirectory(directory);
25
+
26
+ if (json) {
27
+ const output = mode === "prebuilt" && result.ok
28
+ ? {
29
+ ok: true,
30
+ value: {
31
+ manifest: result.value.manifest,
32
+ fileCount: result.value.fileCount,
33
+ totalBytes: result.value.totalBytes,
34
+ },
35
+ }
36
+ : result;
37
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
38
+ } else if (result.ok) {
39
+ if (mode === "prebuilt") {
40
+ process.stdout.write(
41
+ `Valid Taproot Docs prebuilt artifact: ${result.value.fileCount} payload files, ${result.value.totalBytes} bytes.\n`,
42
+ );
43
+ } else {
44
+ process.stdout.write(
45
+ `Valid Taproot Docs artifact: ${result.value.fileCount} semantic files, ${result.value.totalBytes} bytes.\n`,
46
+ );
47
+ }
48
+ } else {
49
+ for (const error of result.errors) process.stderr.write(`${error.code} ${error.path}: ${error.message}\n`);
50
+ }
51
+ process.exitCode = result.ok ? 0 : 1;
16
52
  }
17
- process.exitCode = result.ok ? 0 : 1;
@@ -22,3 +22,22 @@ contract regardless of host ICU data.
22
22
  localized fixture keeps its one PNG as reviewable base64 in source control; the
23
23
  loader materializes it at the manifest path without requiring a generated binary
24
24
  file in the contract repository.
25
+
26
+ Prebuilt cases are consumed through `loadPrebuiltConformanceCases()` from
27
+ `@taprootio/docs-artifact/prebuilt/conformance`. The representative
28
+ `prebuilt/valid/espalier/` fixture is a self-contained static tree with HTML,
29
+ repository JavaScript and bundler-shaped CSS, a worker, a minimal WebAssembly
30
+ module, an `/api/` content route, an SVG symbol sprite, an animated SVG,
31
+ Pagefind JavaScript and binary index data, a redirect, stable resources, and the
32
+ canonical 404. The sibling committed golden vector at
33
+ `prebuilt/golden/espalier.tar.gz` freezes the exact
34
+ deterministic USTAR/stored-gzip bytes; the loader exposes those bytes together
35
+ with their expected length and SHA-256. The fixture does not depend on a
36
+ checkout of the Espalier repository. Its adversarial cases pin traversal,
37
+ normalization and case-fold collisions, extra/missing files, hash/size and
38
+ media-type drift, every published collection/byte/path/depth/USTAR bound,
39
+ ambiguous redirects, missing resource and canonical-404 targets, a file that
40
+ becomes non-regular before its validated open, and rejection of
41
+ publisher-controlled headers, origins, routes, and validation hooks. Run the
42
+ loaded cases through `validatePrebuiltConformanceCase()` so both in-memory and
43
+ Node-boundary scenarios use their owning validator.