@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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2026 Taproot IO, LLC
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
13
+ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,362 @@
1
+ # `@taprootio/docs-artifact`
2
+
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:
6
+
7
+ - `schema/taproot-docs-manifest.schema.json` for the structural v1 shape;
8
+ - the validator and deterministic serializer exported from the package root;
9
+ - the Node directory validator and `taproot-docs-validate` command;
10
+ - valid and adversarial fixtures exposed by `@taprootio/docs-artifact/conformance`.
11
+
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.
15
+
16
+ ## Artifact layout and portability
17
+
18
+ The producer writes this additive payload into its ordinary static output:
19
+
20
+ ```text
21
+ _site/
22
+ ├── index.html ordinary portable static site
23
+ ├── ... ordinary CSS, JS, pages, and assets
24
+ ├── taproot-docs-manifest.json semantic contract entry point
25
+ └── taproot-docs/
26
+ ├── fragments/ manifest-listed semantic HTML only
27
+ └── assets/ manifest-listed safe raster images only
28
+ ```
29
+
30
+ Taproot packages and validates the manifest plus exactly the files it lists. It
31
+ does not ingest, scrape, or execute the ordinary final HTML, CSS, or JavaScript.
32
+ `validateArtifactDirectory()` deliberately ignores unlisted files outside the
33
+ semantic payload, so the complete build remains deployable to any normal static
34
+ host. It enumerates the entire `taproot-docs/` subtree and rejects undeclared
35
+ files, symbolic links, and unsupported filesystem entry types there. A publisher
36
+ creating the Taproot upload must include only the manifest and listed semantic
37
+ files; `validateArtifact()` likewise rejects unexpected entries in that managed
38
+ subset.
39
+
40
+ ## Identity and document model
41
+
42
+ `resources[].key` is the durable, locale-independent document identity. It is a
43
+ producer-assigned lowercase logical key such as `guide:getting-started` or
44
+ `reference:esp-button`. It must not be derived again from `route`, `title`, or
45
+ array position. A route or title can change while the key remains fixed, which
46
+ lets Taproot preserve analytics and future feedback identity.
47
+
48
+ Each resource has:
49
+
50
+ - semantic kind, audience, and tags;
51
+ - one or more locale variants, including the declared default locale;
52
+ - a canonical route, title, description, ordered heading inventory, and source
53
+ location for each locale;
54
+ - one body fragment plus optional example or aside fragments, all carrying
55
+ exact UTF-8 byte counts and SHA-256 hashes.
56
+
57
+ Navigation is localized and references resource keys rather than routes.
58
+ Redirects map a canonical old route directly to a resource key and locale, so
59
+ the target is always a current resource variant rather than another redirect.
60
+ This excludes redirect chains and loops by construction. Assets have their own
61
+ stable keys, paths, exact byte counts, hashes, media types, and dimensions.
62
+
63
+ Repository provenance uses GitHub's stable repository id as authority and keeps
64
+ the owner/name locator for humans. The exact revision, ref, producer/version,
65
+ configuration hash, and `sourceDateEpoch` make build inputs inspectable without
66
+ introducing a wall-clock value that changes identical output.
67
+
68
+ ## Compatibility policy
69
+
70
+ The manifest `schemaVersion` is the wire-format major and v1 consumers accept
71
+ only `1`. Unknown schema versions fail closed. Within v1:
72
+
73
+ - additive behavior is advertised through `capabilities`;
74
+ - an unknown required capability fails validation;
75
+ - an unknown optional capability may be ignored, but it does not authorize
76
+ unknown fields, markup, media, or files—the closed v1 schema still applies;
77
+ - `taproot.docs.fragments.html.v1` is required by every v1 artifact.
78
+
79
+ The npm package uses semantic versioning independently of `schemaVersion`:
80
+
81
+ - a package major changes the JavaScript API, compatibility policy, or supported
82
+ manifest major;
83
+ - a package minor adds a backward-compatible validator entry point, understood
84
+ optional capability, or conformance case for an already supported manifest
85
+ major;
86
+ - a package patch fixes implementation, diagnostics, or documented security
87
+ enforcement. A patch may reject an input that never conformed to the stated
88
+ fail-closed rules.
89
+
90
+ Producer and consumer release PRs update one exact dependency pin together. The
91
+ private Taproot `docs-artifact-v<package-version>` tag does not publish npm
92
+ directly. It verifies and copies the reviewed package allowlist into
93
+ `packages/docs-artifact/` in public
94
+ [`taprootio/trunk`](https://github.com/taprootio/trunk), commits it, and creates
95
+ the matching public tag. Trunk's `publish-docs-artifact.yml` then requires that
96
+ tag to equal `package.json` and belong to public `main` before publishing from a
97
+ GitHub-hosted runner. npm provenance therefore resolves to the exact public
98
+ Trunk release rather than to private Taproot.
99
+
100
+ The public publish job binds the `npm-docs-artifact-publish` GitHub Environment.
101
+ The first registry publish is `1.0.1` and uses a one-time granular `NPM_TOKEN`
102
+ because npm cannot configure a trusted publisher before a package exists. The
103
+ immutable `1.0.0` Trunk tag records a bootstrap attempt that failed in package
104
+ tests before npm publish, so that version is intentionally absent from the
105
+ registry. Administrators then
106
+ configure the trusted publisher for `taprootio/trunk`, workflow
107
+ `publish-docs-artifact.yml`, and that Environment, and delete the bootstrap
108
+ token. Steady-state releases use short-lived OIDC only. The complete setup and
109
+ recovery procedure lives in Trunk's `RELEASING.md`; the private scaffold is
110
+ maintained under `release/trunk/public-repo/`.
111
+
112
+ ## Determinism
113
+
114
+ `serializeManifest()` validates first, recursively sorts object keys, emits two-
115
+ space JSON, and adds one final newline. Producers also must use these deterministic
116
+ array orders, which validation enforces:
117
+
118
+ - capabilities, locales, resources, resource locale variants, semantic audience
119
+ and tag lists, navigation locale sets, redirects, and assets are sorted by
120
+ JavaScript UTF-16 code-unit order (the locale-independent ordering used by
121
+ string relational comparison and `Array.prototype.sort()` without a
122
+ comparator);
123
+ - headings, fragments, and navigation nodes preserve their semantic document
124
+ order.
125
+
126
+ Paths are NFC-normalized, lowercase, relative POSIX paths. Percent escapes,
127
+ backslashes, controls, leading slashes, empty segments, and dot segments are
128
+ rejected rather than normalized silently. Every managed path segment also
129
+ rejects Windows device aliases (`con`, `prn`, `aux`, `nul`, `com1`–`com9`, and
130
+ `lpt1`–`lpt9`), including aliases followed by a file extension. The same
131
+ portable device-name rule applies to every route segment. Routes are lowercase
132
+ root-relative directory URLs with one trailing slash. Taproot shell path
133
+ prefixes including `/.well-known/`, `/_taproot/`, `/api/`, `/assets/`,
134
+ `/bust/`, `/pagefind/`, `/public/`, and `/taproot-docs/` are reserved. The
135
+ exact `/404/` route is also reserved. Root file routes
136
+ (`/404.html/`, `/favicon.ico/`, `/index.html/`, `/manifest.webmanifest/`,
137
+ `/robots.txt/`, `/sitemap.xml/`, and
138
+ `/taproot-docs-manifest.json/`) reserve their complete first-segment subtree so
139
+ a route cannot turn a published root file into a directory.
140
+
141
+ Manifest string bounds count Unicode scalar values, matching JSON Schema
142
+ `minLength` and `maxLength`; astral characters count once rather than as two
143
+ UTF-16 code units. The 2 MiB manifest ceiling applies both to supplied JSON bytes
144
+ and to the canonical UTF-8 serialization of an object input. A manifest that
145
+ cannot be serialized within that bound is not v1-conforming. Raw string input is
146
+ rejected first by a conservative UTF-16 length bound, then by an exact bounded
147
+ UTF-8 count, before the validator allocates its encoded bytes. Object input is
148
+ preflighted with cycle, depth, and traversal-work bounds; repeated references
149
+ count every serialized occurrence without retraversing the shared object. That
150
+ same traversal reads each own JSON property once and materializes a
151
+ null-prototype snapshot. Schema validation, canonical measurement,
152
+ serialization, artifact validation, and returned values use only that snapshot,
153
+ so accessors and Proxies cannot change a manifest between validation stages.
154
+ Uint8Array manifests and binary file entries are measured and copied through
155
+ their built-in internal slots; shadowed `byteLength` properties and caller
156
+ iterators are never treated as byte authority. ArrayBuffer and Uint8Array byte
157
+ lengths are checked against the manifest ceiling before a private snapshot is
158
+ allocated. Genuine Uint8Array and ArrayBuffer values from another JavaScript
159
+ realm are accepted; proxies, `Symbol.toStringTag` lookalikes, detached buffers,
160
+ and shared buffers fail closed.
161
+
162
+ `build.producerVersion` is SemVer 2.0. Numeric prerelease identifiers do not
163
+ permit leading zeroes (`1.0.0-0` and `1.0.0-alpha.1` are valid;
164
+ `1.0.0-01` is not). Numeric build identifiers may retain leading zeroes.
165
+
166
+ Duplicate resource keys, locale variants, routes, redirect sources, asset keys,
167
+ heading ids, fragment keys, declared file paths, navigation references, JSON
168
+ object keys, and supplied file entries fail deterministically. Diagnostics are
169
+ sorted by path, code, and message using locale-independent string ordering.
170
+ Diagnostic paths and messages are NFC-normalized, escape C0/C1 and bidirectional
171
+ formatting controls, and are bounded by exported scalar and UTF-8 byte ceilings
172
+ before they are returned or included in a thrown validation error.
173
+
174
+ All manifest string values and all decoded markup attribute values reject C0,
175
+ C1, and bidirectional formatting/override controls. These checks use the same
176
+ pinned code-point ranges in both validators, including when an allowed HTML
177
+ entity decodes into the inspected attribute value.
178
+
179
+ Docs v1 also pins a deterministic, registry-independent BCP 47 subset instead
180
+ of asking the host's ICU database to decide locale validity. A supported tag
181
+ contains a lowercase two- or three-letter language, an optional title-case
182
+ four-letter Script subtag, and an optional uppercase two-letter or three-digit
183
+ region subtag: for example `en`, `en-US`, `fr-FR`, `zh-Hant`, `es-419`, or
184
+ `abc-Latn-419`. Variants, extensions, private-use tags, grandfathered tags, and
185
+ other casing are outside v1. The JSON Schema uses this same grammar and length
186
+ ceiling as the runtime manifest and markup `lang` validators.
187
+
188
+ ## Content and resource bounds
189
+
190
+ The exported `LIMITS` object is the v1 ceiling. Important bounds are:
191
+
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 |
214
+
215
+ Navigation depth counts each top-level item as level one. A leaf at level 12 is
216
+ valid; a child node at level 13 is rejected, while the global node ceiling still
217
+ counts nodes across every locale and nested array.
218
+
219
+ In-memory artifact validation charges every inspected declared entry's actual
220
+ UTF-8 or binary length to the aggregate budget before per-file size-drift
221
+ handling. String content is checked for well-formed UTF-16 while its UTF-8 bytes
222
+ are counted in the same bounded pass; scanning stops as soon as the remaining
223
+ aggregate budget is exceeded. File iteration and all later hashing or media
224
+ parsing stop at that point.
225
+
226
+ Consumers may impose smaller product or license limits but may not reinterpret a
227
+ larger input as v1-conforming. Archive compression expansion, entry type, and
228
+ physical-storage quotas remain additional ingestion-boundary checks; the Node
229
+ directory validator already rejects symbolic links and non-regular entries
230
+ anywhere below the managed subtree. It collects only the remaining entry budget
231
+ plus one, then walks entries in canonical name order; an over-budget tree returns
232
+ only its deterministic limit diagnostic. Managed files are opened once in
233
+ nonblocking mode without following a final symbolic link, checked by `stat`
234
+ against the descriptor's exact declared byte count before allocation, and read
235
+ through that handle under one cumulative 256 MiB budget. Nonblocking opens make
236
+ an enumerated-file-to-FIFO race fail type validation instead of waiting for a
237
+ writer. Files are rejected if their identity or size changes during the read.
238
+ After each read, the validator re-lstats every path component,
239
+ re-resolves the final path beneath the artifact root's original real path, and
240
+ requires the resolved regular file to retain the opened handle's device and
241
+ inode. Replacing either the file or one of its ancestors with a symbolic link is
242
+ therefore rejected even if it races the handle read. Before success, the same
243
+ bounded managed-subtree enumeration is repeated and its relative path/type set
244
+ must match the original walk.
245
+
246
+ The Node directory API snapshots `supportedCapabilities` once, with the
247
+ exported entry ceiling, before reading the artifact. Arrays, sets, and one-shot
248
+ generator objects are safe to pass and are not revisited by the manifest and
249
+ artifact validation phases; a scalar string is not a capability collection.
250
+ Every supplied value must be a primitive canonical capability/resource-key
251
+ string no longer than 200 Unicode scalar values. Invalid collections, values,
252
+ and iterator failures produce bounded generic diagnostics.
253
+
254
+ ## Supported semantic HTML
255
+
256
+ Fragments are exact `text/html; charset=utf-8` bytes and use a small semantic
257
+ allow-list: headings `h2`–`h6`, paragraphs, lists, tables, figures, details,
258
+ code/preformatted text, common inline semantics, links, and images. The validator
259
+ rejects comments, declarations, custom elements, forms, scripts, styles, iframes,
260
+ event attributes, inline styles, classes, unquoted attributes, malformed nesting,
261
+ browser-reparenting content models, raw whitespace before tag names, and unknown
262
+ markup. HTML syntax recognizes only tab, line feed, form feed, carriage return,
263
+ and space as whitespace; other Unicode whitespace remains content or is rejected
264
+ where browsers do not treat it as syntax whitespace.
265
+
266
+ Entities are deliberately narrower than the full browser named-entity table.
267
+ Only semicolon-terminated `amp`, `apos`, `colon`, `gt`, `lt`, and `quot` named
268
+ references plus scalar numeric references are accepted. Raw or semicolonless
269
+ ampersands, unknown names, and numeric references that browsers replace or map
270
+ through control-character rules fail closed. Literal U+0000 text fails with
271
+ `markup.invalid_null`, matching the fail-closed handling of nulls in attributes
272
+ and numeric references. Element count and open-element
273
+ nesting stop immediately at the exported v1 ceilings above.
274
+
275
+ Internal links use `data-resource-key` plus optional `data-heading-id`; same-page
276
+ links may use `href="#heading-id"`; external links are credential-free HTTPS.
277
+ Images use `data-asset-key` and required `alt` text rather than `src`. Only GIF,
278
+ JPEG, PNG, and WebP assets are accepted, and bytes must match the declared
279
+ complete container structure, dimensions, size, and SHA-256. Validation walks
280
+ bounded container records and framing but does not decode pixels. GIF, JPEG,
281
+ PNG/APNG, and WebP canvas headers must remain within the decoded-pixel ceiling;
282
+ declared asset width multiplied by height is subject to the same runtime
283
+ ceiling. JSON Schema mirrors each dimension's maximum and documents this
284
+ cross-field product, which JSON Schema cannot express exactly. GIF, APNG, and
285
+ animated WebP frame records must remain within the animation-frame ceiling.
286
+ The sum of decoded frame rectangles in one GIF, APNG, or animated WebP must
287
+ also remain within the cumulative decoded-animation-pixel ceiling.
288
+ GIF frames require a valid global or local color table. Graphic Control,
289
+ Application, Plain Text, and Comment extensions follow their label-specific
290
+ block grammar; unknown extension labels and reserved packed-field bits fail
291
+ closed. PNG chunk types contain only ASCII letters with an uppercase reserved
292
+ third byte, and every IDAT chunk (including an empty one) belongs to one
293
+ contiguous run. PNG palette presence, entry count, and placement follow the
294
+ IHDR color type and bit depth. APNG frame controls and data use one gapless
295
+ sequence, valid dispose/blend operations, correct IDAT/fdAT ownership, and a
296
+ completed-frame count matching acTL. JPEG assets are complete baseline Huffman
297
+ interchange streams from SOI through EOI: referenced quantization and Huffman
298
+ tables, frame and scan component declarations, and entropy/restart framing are
299
+ validated. A referenced quantization table may appear before or after SOF0 but
300
+ must be defined before its component's SOS is accepted. Complete baseline
301
+ sequential images may use one interleaved scan or multiple scans; every frame
302
+ component must appear exactly once across them. DQT, DHT, DRI, COM, and APP
303
+ metadata may appear between scans, and each scan independently validates table
304
+ references, sequential spectral fields, restart cardinality, and entropy
305
+ framing. Baseline quantization tables contain 64 nonzero 8-bit coefficients;
306
+ Huffman tables have at most 256 baseline-valid symbols and never exhaust prefix
307
+ space, leaving the forbidden all-ones terminal code unused. Component sampling
308
+ products total at most 10. Entropy byte stuffing is exactly `FF 00`; repeated
309
+ `FF` bytes are fill only before a nonzero marker. A DRI scan contains exactly
310
+ `floor((MCUs - 1) / interval)` ordered restart markers, and every marker
311
+ separates nonempty entropy partitions. Progressive, arithmetic-coded, and
312
+ otherwise non-baseline streams fail closed.
313
+ Animated WebP frame rectangles use the format's doubled x/y units, must stay
314
+ within the VP8X canvas, must leave reserved flag bits clear, and must match the
315
+ dimensions of their embedded VP8 or VP8L image. WebP chunks with odd payload
316
+ lengths require a present zero pad byte. VP8X is first and unique for extended
317
+ files. Its ICC, alpha, Exif, XMP, and animation feature bits must exactly match
318
+ the chunks and decoded VP8L alpha-used headers observed in the container. The
319
+ canonical extended order is VP8X, optional unique ICCP, one still reconstruction
320
+ (ALPH immediately followed by VP8, or VP8L) or one animation reconstruction
321
+ (ANIM followed by a contiguous ANMF run), then optional unique EXIF and XMP
322
+ chunks in that order. Still and animated reconstruction are exclusive. An ANMF
323
+ payload is exactly one VP8L chunk or an optional ALPH followed by one VP8 chunk;
324
+ nested alpha observations are accumulated across all frames. VP8 keyframe tags
325
+ must describe a displayed, non-experimental frame with a bounded nonempty first
326
+ partition; VP8L requires bytes beyond its five-byte header. ALPH headers accept
327
+ only defined compression, filter, and preprocessing fields, and uncompressed
328
+ alpha must contain exactly one byte per frame pixel. Unrecognized top-level and
329
+ nested WebP chunks fail closed. These bounds fail before any consumer decodes
330
+ image payloads. SVG, HTML, CSS, JavaScript, fonts, and arbitrary downloads are
331
+ not managed Docs v1 assets.
332
+
333
+ ## API
334
+
335
+ ```js
336
+ import {
337
+ assertValidArtifact,
338
+ serializeManifest,
339
+ validateArtifact,
340
+ validateManifest,
341
+ } from "@taprootio/docs-artifact";
342
+ import { loadConformanceCases } from "@taprootio/docs-artifact/conformance";
343
+ import { validateArtifactDirectory } from "@taprootio/docs-artifact/node";
344
+
345
+ const manifestResult = validateManifest(manifestBytes);
346
+ const artifactResult = await validateArtifact(manifestBytes, semanticFiles);
347
+ const directoryResult = await validateArtifactDirectory("_site");
348
+ const canonicalBytes = serializeManifest(manifestObject);
349
+ const sharedCases = await loadConformanceCases();
350
+ ```
351
+
352
+ The assertion variants throw `DocsArtifactValidationError` with the same stable
353
+ `errors` array. The CLI prints `code path: message` diagnostics and exits nonzero:
354
+
355
+ ```bash
356
+ npx --package=@taprootio/docs-artifact@1.0.1 taproot-docs-validate ./_site
357
+ ```
358
+
359
+ Consumers should assert error `code` and `path`, not human-readable wording.
360
+ 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
362
+ `LICENSE`, so a publishable file cannot appear or disappear silently.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { validateArtifact } from "../src/artifact-validator.js";
4
+ import { loadConformanceCases } from "../src/conformance.js";
5
+
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;
18
+ } else {
19
+ process.stdout.write("Taproot Docs artifact conformance fixtures passed.\n");
20
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { validateArtifactDirectory } from "../src/node.js";
4
+
5
+ const arguments_ = process.argv.slice(2);
6
+ 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`);
14
+ } else {
15
+ for (const error of result.errors) process.stderr.write(`${error.code} ${error.path}: ${error.message}\n`);
16
+ }
17
+ process.exitCode = result.ok ? 0 : 1;
@@ -0,0 +1,11 @@
1
+ import type { ArtifactFileEntry } from "./index.js";
2
+
3
+ export interface DocsArtifactConformanceCase {
4
+ name: string;
5
+ valid: boolean;
6
+ expectedCodes: string[];
7
+ manifest: Uint8Array;
8
+ files: ArtifactFileEntry[];
9
+ }
10
+
11
+ export function loadConformanceCases(): Promise<DocsArtifactConformanceCase[]>;
@@ -0,0 +1,24 @@
1
+ # Conformance fixtures
2
+
3
+ Consume these cases through `loadConformanceCases()` from
4
+ `@taprootio/docs-artifact/conformance`. It returns manifest bytes and exact
5
+ `{ path, content }` entries for each case, including decoded binary assets and
6
+ manifest mutations used to isolate one invalid behavior.
7
+
8
+ Large adversarial markup and navigation-depth boundaries are represented
9
+ compactly in `conformance.json` with bounded generation recipes. The loader
10
+ materializes generated bytes and synchronizes the named descriptor's size and
11
+ SHA-256 before returning the case. Oversized-manifest cases similarly use
12
+ deterministic trailing-space padding, keeping reviewable fixtures small without
13
+ weakening the public case.
14
+
15
+ The published cases also pin accepted and rejected boundaries for the
16
+ registry-independent Docs v1 locale subset and reject C1 or bidirectional
17
+ formatting/override controls in manifest strings. These cases keep producers,
18
+ consumers, the runtime validator, and the JSON Schema on one deterministic text
19
+ contract regardless of host ICU data.
20
+
21
+ `valid/minimal/` is also a directly valid directory fixture. The complete
22
+ localized fixture keeps its one PNG as reviewable base64 in source control; the
23
+ loader materializes it at the manifest path without requiring a generated binary
24
+ file in the contract repository.