@ixo/domain.md 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +6 -0
  3. package/README.md +30 -0
  4. package/assets/domain-md.schema.json +1103 -0
  5. package/assets/oracle-capsule-jcs-vectors.json +57 -0
  6. package/assets/oracle-capsule-manifest-contract.md +72 -0
  7. package/assets/oracle-capsule-source-lock.schema.json +45 -0
  8. package/assets/oracle-capsule.schema.json +265 -0
  9. package/assets/rules.json +275 -0
  10. package/assets/spec.md +1434 -0
  11. package/assets/template-manifest.schema.json +57 -0
  12. package/dist/capsule/index.d.ts +75 -0
  13. package/dist/capsule/index.js +20 -0
  14. package/dist/capsule/index.js.map +1 -0
  15. package/dist/chunk-BYBC2TSS.js +597 -0
  16. package/dist/chunk-BYBC2TSS.js.map +1 -0
  17. package/dist/chunk-G7PZJYAJ.js +31826 -0
  18. package/dist/chunk-G7PZJYAJ.js.map +1 -0
  19. package/dist/chunk-R6QZRNEX.js +164 -0
  20. package/dist/chunk-R6QZRNEX.js.map +1 -0
  21. package/dist/chunk-T3STBWXP.js +46 -0
  22. package/dist/chunk-T3STBWXP.js.map +1 -0
  23. package/dist/chunk-W7MRQP7I.js +862 -0
  24. package/dist/chunk-W7MRQP7I.js.map +1 -0
  25. package/dist/chunk-XDIWMZVF.js +1150 -0
  26. package/dist/chunk-XDIWMZVF.js.map +1 -0
  27. package/dist/cli-KcUIQEiE.d.ts +171 -0
  28. package/dist/cli.d.ts +2 -0
  29. package/dist/cli.js +400 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.js +83 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/resolver-B3mwnjJq.d.ts +96 -0
  35. package/dist/spec.d.ts +20 -0
  36. package/dist/spec.js +23 -0
  37. package/dist/spec.js.map +1 -0
  38. package/dist/templates/index.d.ts +7 -0
  39. package/dist/templates/index.js +24 -0
  40. package/dist/templates/index.js.map +1 -0
  41. package/dist/workers-Dr8B8a2O.d.ts +19 -0
  42. package/dist/workers.d.ts +3 -0
  43. package/dist/workers.js +31 -0
  44. package/dist/workers.js.map +1 -0
  45. package/package.json +97 -0
  46. package/src/capsule/canonical.ts +74 -0
  47. package/src/capsule/index.ts +10 -0
  48. package/src/capsule/json.ts +373 -0
  49. package/src/capsule/types.ts +63 -0
  50. package/src/capsule/validate.ts +551 -0
  51. package/src/cli.ts +405 -0
  52. package/src/compiled-validators.d.ts +7 -0
  53. package/src/compiled-validators.js +32512 -0
  54. package/src/constants.ts +250 -0
  55. package/src/diff.ts +111 -0
  56. package/src/export.ts +28 -0
  57. package/src/index.ts +36 -0
  58. package/src/lint.ts +42 -0
  59. package/src/parser.ts +174 -0
  60. package/src/sarif.ts +49 -0
  61. package/src/semantic.ts +1156 -0
  62. package/src/spec.ts +48 -0
  63. package/src/templates/errors.ts +3 -0
  64. package/src/templates/index.ts +11 -0
  65. package/src/templates/manifest.ts +94 -0
  66. package/src/templates/render.ts +435 -0
  67. package/src/templates/resolver.ts +200 -0
  68. package/src/templates/types.ts +98 -0
  69. package/src/types.ts +195 -0
  70. package/src/workers.ts +6 -0
@@ -0,0 +1,597 @@
1
+ import {
2
+ getTemplateManifestSchema
3
+ } from "./chunk-T3STBWXP.js";
4
+ import {
5
+ lint
6
+ } from "./chunk-XDIWMZVF.js";
7
+ import {
8
+ MAX_LINKED_BYTES,
9
+ MAX_SCALAR_LENGTH,
10
+ MAX_YAML_DEPTH,
11
+ MAX_YAML_NODES,
12
+ SPEC_VERSION
13
+ } from "./chunk-G7PZJYAJ.js";
14
+
15
+ // src/templates/errors.ts
16
+ var TemplateValidationError = class extends Error {
17
+ name = "TemplateValidationError";
18
+ };
19
+
20
+ // src/templates/manifest.ts
21
+ import Ajv2020Import from "ajv/dist/2020.js";
22
+ import addFormatsImport from "ajv-formats";
23
+ import { isAlias, parseDocument, visit as visitYaml } from "yaml";
24
+ var Ajv2020 = Ajv2020Import;
25
+ var addFormats = addFormatsImport;
26
+ var ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false });
27
+ addFormats(ajv);
28
+ var validate = ajv.compile(getTemplateManifestSchema());
29
+ function parseTemplateManifest(bytes) {
30
+ if (bytes.byteLength > MAX_LINKED_BYTES)
31
+ throw new Error(`Template manifest exceeds ${MAX_LINKED_BYTES} bytes.`);
32
+ const source = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
33
+ const document = parseDocument(source, { prettyErrors: true, uniqueKeys: true, version: "1.2" });
34
+ if (document.errors.length > 0 || document.warnings.length > 0) {
35
+ throw new Error(
36
+ [...document.errors, ...document.warnings].map((item) => item.message).join("; ")
37
+ );
38
+ }
39
+ const unsafe = /* @__PURE__ */ new Set();
40
+ let nodes = 0;
41
+ let depth = 0;
42
+ let scalarTooLong = false;
43
+ visitYaml(document, {
44
+ Node(_key, node, path) {
45
+ const candidate = node;
46
+ nodes += 1;
47
+ depth = Math.max(depth, path.length + 1);
48
+ if (typeof candidate.value === "string" && candidate.value.length > MAX_SCALAR_LENGTH)
49
+ scalarTooLong = true;
50
+ if (isAlias(node)) unsafe.add("alias");
51
+ if (candidate.anchor) unsafe.add("anchor");
52
+ if (candidate.tag?.startsWith("!")) unsafe.add("custom-tag");
53
+ },
54
+ Pair(_key, pair) {
55
+ const key = pair.key;
56
+ if (key?.value === "<<") unsafe.add("merge-key");
57
+ }
58
+ });
59
+ if (nodes > MAX_YAML_NODES || depth > MAX_YAML_DEPTH || scalarTooLong) {
60
+ throw new Error("Template manifest exceeds YAML node, depth, or scalar limits.");
61
+ }
62
+ if (unsafe.size > 0)
63
+ throw new Error(
64
+ `Template manifest contains forbidden YAML features: ${[...unsafe].sort().join(", ")}.`
65
+ );
66
+ const value = document.toJS({ maxAliasCount: 0 });
67
+ if (!validate(value)) {
68
+ const message = (validate.errors ?? []).map((error) => `${error.instancePath || "$"} ${error.message ?? "is invalid"}`).join("; ");
69
+ throw new Error(`Invalid template manifest: ${message}`);
70
+ }
71
+ const manifest = value;
72
+ const derivedTypes = /* @__PURE__ */ new Set();
73
+ for (const bundle of manifest.bundles) {
74
+ if (derivedTypes.has(bundle.derived_type))
75
+ throw new Error(`Duplicate template bundle ${bundle.derived_type}.`);
76
+ derivedTypes.add(bundle.derived_type);
77
+ const paths = /* @__PURE__ */ new Set();
78
+ for (const file of bundle.files) {
79
+ if (!file.path.startsWith(`templates/${bundle.derived_type}/`)) {
80
+ throw new Error(
81
+ `Template path ${file.path} does not match bundle type ${bundle.derived_type}.`
82
+ );
83
+ }
84
+ if (paths.has(file.path)) throw new Error(`Duplicate template path ${file.path}.`);
85
+ paths.add(file.path);
86
+ }
87
+ const domainFiles = bundle.files.filter((file) => file.role === "domain.md");
88
+ if (domainFiles.length !== 1 || domainFiles[0]?.media_type !== "text/markdown" || domainFiles[0].required !== true || domainFiles[0].path !== `templates/${bundle.derived_type}/domain.md.tmpl`) {
89
+ throw new Error(
90
+ `Template bundle ${bundle.derived_type} must contain exactly one required Markdown domain.md.`
91
+ );
92
+ }
93
+ }
94
+ return manifest;
95
+ }
96
+
97
+ // src/templates/resolver.ts
98
+ import { lookup } from "dns/promises";
99
+ import { lstat, readFile, realpath } from "fs/promises";
100
+ import { request } from "https";
101
+ import { relative, resolve } from "path";
102
+ import { fileURLToPath, pathToFileURL } from "url";
103
+ import ipaddr from "ipaddr.js";
104
+ function isPublicAddress(address) {
105
+ const parsed = ipaddr.parse(address);
106
+ const range = parsed.range();
107
+ return range === "unicast";
108
+ }
109
+ function validateHttpsTarget(url, addresses = []) {
110
+ if (url.protocol !== "https:") throw new Error("Only HTTPS template retrieval is allowed.");
111
+ if (url.username || url.password) throw new Error("Credentials in template URLs are forbidden.");
112
+ if (!url.hostname) throw new Error("Template URL hostname is required.");
113
+ if (addresses.some((address) => !isPublicAddress(address))) {
114
+ throw new Error(`Template hostname ${url.hostname} resolves to a non-public address.`);
115
+ }
116
+ }
117
+ var FileTemplateResolver = class {
118
+ supports(uri) {
119
+ return uri.startsWith("file:") || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(uri);
120
+ }
121
+ async resolve(uri, context) {
122
+ const path = resolve(uri.startsWith("file:") ? fileURLToPath(uri) : uri);
123
+ const stat = await lstat(path);
124
+ if (!stat.isFile() || stat.isSymbolicLink())
125
+ throw new Error("Template resources must be regular files, not symlinks.");
126
+ if (stat.size > context.maxBytes)
127
+ throw new Error(`Template resource exceeds ${context.maxBytes} bytes.`);
128
+ const canonical2 = await realpath(path);
129
+ if (canonical2 !== path) throw new Error("Template resource paths must not contain symlinks.");
130
+ return { uri: pathToFileURL(canonical2).href, bytes: await readFile(canonical2) };
131
+ }
132
+ static async resolveRelative(manifestUri, logicalPath) {
133
+ const manifestPath = fileURLToPath(manifestUri);
134
+ const root = await realpath(resolve(manifestPath, ".."));
135
+ const requested = resolve(root, logicalPath);
136
+ const lexicalTraversal = relative(root, requested);
137
+ if (lexicalTraversal.startsWith("..") || lexicalTraversal.startsWith("/") || lexicalTraversal === "") {
138
+ throw new Error(`Template path escapes or aliases the manifest root: ${logicalPath}`);
139
+ }
140
+ let current = root;
141
+ for (const component of lexicalTraversal.split(/[\\/]/)) {
142
+ current = resolve(current, component);
143
+ if ((await lstat(current)).isSymbolicLink())
144
+ throw new Error(`Template path contains a symlink: ${logicalPath}`);
145
+ }
146
+ const target = await realpath(requested);
147
+ const traversal = relative(root, target);
148
+ if (traversal.startsWith("..") || traversal.startsWith("/") || traversal === "") {
149
+ throw new Error(`Template path escapes or aliases the manifest root: ${logicalPath}`);
150
+ }
151
+ return pathToFileURL(target).href;
152
+ }
153
+ };
154
+ var HttpsTemplateResolver = class {
155
+ maxRedirects;
156
+ constructor(options = {}) {
157
+ this.maxRedirects = options.maxRedirects ?? 3;
158
+ }
159
+ supports(uri) {
160
+ return uri.startsWith("https:");
161
+ }
162
+ async resolve(uri, context) {
163
+ return this.fetch(new URL(uri), context, 0);
164
+ }
165
+ async fetch(url, context, redirects) {
166
+ validateHttpsTarget(url);
167
+ const addresses = await lookup(url.hostname, { all: true, verbatim: true });
168
+ if (addresses.length === 0)
169
+ throw new Error(`Template hostname ${url.hostname} did not resolve.`);
170
+ validateHttpsTarget(
171
+ url,
172
+ addresses.map((entry) => entry.address)
173
+ );
174
+ const approved = addresses[0];
175
+ if (!approved) throw new Error(`Template hostname ${url.hostname} did not resolve.`);
176
+ return new Promise((resolvePromise, reject) => {
177
+ const req = request(
178
+ url,
179
+ {
180
+ headers: { accept: "application/yaml, application/json, text/markdown, text/plain" },
181
+ lookup(_hostname, _options, callback) {
182
+ callback(null, approved.address, approved.family);
183
+ },
184
+ servername: url.hostname
185
+ },
186
+ (response) => {
187
+ const status = response.statusCode ?? 0;
188
+ const location = response.headers.location;
189
+ if (status >= 300 && status < 400 && location) {
190
+ response.resume();
191
+ if (redirects >= this.maxRedirects) {
192
+ reject(new Error("Template retrieval exceeded the redirect limit."));
193
+ return;
194
+ }
195
+ const redirected = new URL(location, url);
196
+ this.fetch(redirected, context, redirects + 1).then(resolvePromise, reject);
197
+ return;
198
+ }
199
+ if (status !== 200) {
200
+ response.resume();
201
+ reject(new Error(`Template retrieval returned HTTP ${status}.`));
202
+ return;
203
+ }
204
+ const chunks = [];
205
+ let size = 0;
206
+ response.on("data", (chunk) => {
207
+ size += chunk.length;
208
+ if (size > context.maxBytes) {
209
+ req.destroy(new Error(`Template resource exceeds ${context.maxBytes} bytes.`));
210
+ return;
211
+ }
212
+ chunks.push(chunk);
213
+ });
214
+ response.on("end", () => {
215
+ resolvePromise({
216
+ uri: url.href,
217
+ bytes: Buffer.concat(chunks),
218
+ ...typeof response.headers["content-type"] === "string" ? { mediaType: response.headers["content-type"] } : {}
219
+ });
220
+ });
221
+ }
222
+ );
223
+ req.setTimeout(
224
+ context.timeoutMs,
225
+ () => req.destroy(new Error("Template retrieval timed out."))
226
+ );
227
+ req.on("error", reject);
228
+ req.end();
229
+ });
230
+ }
231
+ };
232
+ var IpfsTemplateResolver = class {
233
+ gateway;
234
+ https;
235
+ constructor(gateway, httpsResolver = new HttpsTemplateResolver()) {
236
+ this.gateway = new URL(gateway.endsWith("/") ? gateway : `${gateway}/`);
237
+ validateHttpsTarget(this.gateway);
238
+ this.https = httpsResolver;
239
+ }
240
+ supports(uri) {
241
+ return uri.startsWith("ipfs:");
242
+ }
243
+ async resolve(uri, context) {
244
+ const source = new URL(uri);
245
+ const cid = source.hostname;
246
+ if (!cid) throw new Error("IPFS URI must include a CID host.");
247
+ const path = source.pathname.replace(/^\/+/, "");
248
+ const target = new URL(`ipfs/${cid}/${path}`, this.gateway);
249
+ const result = await this.https.resolve(target.href, context);
250
+ return { ...result, uri };
251
+ }
252
+ };
253
+ function relativeTemplateUri(manifestUri, logicalPath) {
254
+ if (manifestUri.startsWith("ipfs:")) {
255
+ const base = new URL(manifestUri);
256
+ const root = base.pathname.slice(0, base.pathname.lastIndexOf("/") + 1);
257
+ base.pathname = `${root}${logicalPath}`;
258
+ return base.href;
259
+ }
260
+ return new URL(logicalPath, manifestUri).href;
261
+ }
262
+
263
+ // src/templates/render.ts
264
+ import { createHash } from "crypto";
265
+ import { basename } from "path";
266
+ import { CID } from "multiformats/cid";
267
+ import { sha256 } from "multiformats/hashes/sha2";
268
+ import { isAlias as isAlias2, parseDocument as parseDocument2, stringify, visit as visitYaml2 } from "yaml";
269
+ function sha(bytes) {
270
+ return createHash("sha256").update(bytes).digest("hex");
271
+ }
272
+ function canonical(value) {
273
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
274
+ if (isRecord(value)) {
275
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(",")}}`;
276
+ }
277
+ return JSON.stringify(value);
278
+ }
279
+ function deterministicUuid(seed) {
280
+ const bytes = createHash("sha256").update(seed).digest().subarray(0, 16);
281
+ bytes[6] = (bytes[6] ?? 0) & 15 | 80;
282
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
283
+ const hex = bytes.toString("hex");
284
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
285
+ }
286
+ async function verifyIdentity(resource, expectedSha, expectedCid) {
287
+ if (expectedSha && sha(resource.bytes) !== expectedSha.toLowerCase())
288
+ throw new TemplateValidationError(`SHA-256 mismatch for ${resource.uri}.`);
289
+ if (expectedCid) {
290
+ let cid;
291
+ try {
292
+ cid = CID.parse(expectedCid);
293
+ } catch {
294
+ throw new TemplateValidationError(`Invalid CID ${expectedCid}.`);
295
+ }
296
+ if (cid.code !== 85 || cid.multihash.code !== sha256.code) {
297
+ throw new TemplateValidationError(
298
+ `CID verification supports raw sha2-256 CIDs only: ${expectedCid}`
299
+ );
300
+ }
301
+ const digest = await sha256.digest(resource.bytes);
302
+ if (!Buffer.from(digest.digest).equals(Buffer.from(cid.multihash.digest)))
303
+ throw new TemplateValidationError(`CID mismatch for ${resource.uri}.`);
304
+ }
305
+ }
306
+ function chooseResolver(uri, resolvers) {
307
+ const resolver = resolvers.find((candidate) => candidate.supports(uri));
308
+ if (!resolver) throw new Error(`No template resolver supports ${uri}.`);
309
+ return resolver;
310
+ }
311
+ function isRecord(value) {
312
+ return typeof value === "object" && value !== null && !Array.isArray(value);
313
+ }
314
+ function parameterDefinitions(marker) {
315
+ if (!Array.isArray(marker.parameters))
316
+ throw new TemplateValidationError("Template marker parameters must be an array.");
317
+ const seen = /* @__PURE__ */ new Set();
318
+ return marker.parameters.map((value) => {
319
+ if (!isRecord(value) || typeof value.name !== "string" || !["string", "number", "boolean", "object", "array"].includes(String(value.type)) || typeof value.required !== "boolean" || value.fill_at !== "author") {
320
+ throw new TemplateValidationError(
321
+ "Every template parameter must declare name, supported type, required, and fill_at: author."
322
+ );
323
+ }
324
+ if (seen.has(value.name))
325
+ throw new TemplateValidationError(`Duplicate template parameter ${value.name}.`);
326
+ seen.add(value.name);
327
+ return {
328
+ name: value.name,
329
+ type: value.type,
330
+ required: value.required === true,
331
+ fill_at: "author"
332
+ };
333
+ });
334
+ }
335
+ function valueMatches(value, type) {
336
+ if (type === "array") return Array.isArray(value);
337
+ if (type === "object") return isRecord(value);
338
+ return typeof value === type;
339
+ }
340
+ function replaceExactString(value, before, after) {
341
+ if (value === before) return after;
342
+ if (Array.isArray(value)) return value.map((item) => replaceExactString(item, before, after));
343
+ if (!isRecord(value)) return value;
344
+ return Object.fromEntries(
345
+ Object.entries(value).map(([key, item]) => [key, replaceExactString(item, before, after)])
346
+ );
347
+ }
348
+ function substitute(value, parameters, allowed, used) {
349
+ if (Array.isArray(value))
350
+ return value.map((child) => substitute(child, parameters, allowed, used));
351
+ if (!isRecord(value)) return value;
352
+ if (Object.keys(value).length === 1 && typeof value.$param === "string") {
353
+ const definition = allowed.get(value.$param);
354
+ if (!definition)
355
+ throw new TemplateValidationError(`Unknown structured parameter ${value.$param}.`);
356
+ const resolved = parameters[value.$param];
357
+ if (resolved === void 0)
358
+ throw new TemplateValidationError(
359
+ `Required structured parameter ${value.$param} is unresolved.`
360
+ );
361
+ if (!valueMatches(resolved, definition.type))
362
+ throw new TemplateValidationError(`Parameter ${value.$param} must be ${definition.type}.`);
363
+ used.add(value.$param);
364
+ return resolved;
365
+ }
366
+ return Object.fromEntries(
367
+ Object.entries(value).map(([key, child]) => [
368
+ key,
369
+ substitute(child, parameters, allowed, used)
370
+ ])
371
+ );
372
+ }
373
+ function markdownEscape(value) {
374
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
375
+ throw new TemplateValidationError("Markdown parameters must be strings, numbers, or booleans.");
376
+ }
377
+ return String(value).replace(/[\\`*_[\]<>]/g, "\\$&").replace(/&/g, "&amp;");
378
+ }
379
+ function renderMarkdownTemplate(source, file, expectedProtocol, derivedType, parameters, draftId) {
380
+ const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(source);
381
+ if (!match?.[1])
382
+ throw new TemplateValidationError(`Template ${file.path} requires YAML frontmatter.`);
383
+ const yaml = parseDocument2(match[1], { prettyErrors: true, uniqueKeys: true, version: "1.2" });
384
+ if (yaml.errors.length > 0)
385
+ throw new TemplateValidationError(yaml.errors.map((error) => error.message).join("; "));
386
+ const unsafe = /* @__PURE__ */ new Set();
387
+ let nodes = 0;
388
+ let depth = 0;
389
+ let scalarTooLong = false;
390
+ visitYaml2(yaml, {
391
+ Node(_key, node, path) {
392
+ const candidate = node;
393
+ nodes += 1;
394
+ depth = Math.max(depth, path.length + 1);
395
+ if (typeof candidate.value === "string" && candidate.value.length > MAX_SCALAR_LENGTH)
396
+ scalarTooLong = true;
397
+ if (isAlias2(node)) unsafe.add("alias");
398
+ if (candidate.anchor) unsafe.add("anchor");
399
+ if (candidate.tag?.startsWith("!")) unsafe.add("custom-tag");
400
+ },
401
+ Pair(_key, pair) {
402
+ const key = pair.key;
403
+ if (key?.value === "<<") unsafe.add("merge-key");
404
+ }
405
+ });
406
+ if (nodes > MAX_YAML_NODES || depth > MAX_YAML_DEPTH || scalarTooLong || unsafe.size > 0) {
407
+ throw new TemplateValidationError(`Template ${file.path} contains unsafe or excessive YAML.`);
408
+ }
409
+ const frontmatter = yaml.toJS({ maxAliasCount: 0 });
410
+ const marker = frontmatter["x-template"];
411
+ const markerKeys = /* @__PURE__ */ new Set([
412
+ "is_template",
413
+ "instantiates_type",
414
+ "protocol",
415
+ "template_version",
416
+ "parameters"
417
+ ]);
418
+ if (!isRecord(marker) || marker.is_template !== true || marker.protocol !== expectedProtocol || marker.instantiates_type !== derivedType || typeof marker.template_version !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(marker.template_version) || Object.keys(marker).some((key) => !markerKeys.has(key) && !key.startsWith("x-"))) {
419
+ throw new TemplateValidationError(`Template marker mismatch in ${file.path}.`);
420
+ }
421
+ const definitions = parameterDefinitions(marker);
422
+ const allowed = new Map(definitions.map((definition) => [definition.name, definition]));
423
+ for (const key of Object.keys(parameters))
424
+ if (!allowed.has(key)) throw new TemplateValidationError(`Undeclared parameter ${key}.`);
425
+ for (const definition of definitions) {
426
+ if (definition.required && parameters[definition.name] === void 0)
427
+ throw new TemplateValidationError(`Required parameter ${definition.name} is unresolved.`);
428
+ if (parameters[definition.name] !== void 0 && !valueMatches(parameters[definition.name], definition.type))
429
+ throw new TemplateValidationError(`Parameter ${definition.name} must be ${definition.type}.`);
430
+ }
431
+ delete frontmatter["x-template"];
432
+ const used = /* @__PURE__ */ new Set();
433
+ const substitutedFrontmatter = substitute(frontmatter, parameters, allowed, used);
434
+ const originalSubject = isRecord(substitutedFrontmatter.domain) && typeof substitutedFrontmatter.domain.id === "string" ? substitutedFrontmatter.domain.id : void 0;
435
+ const draftSubject = `urn:uuid:${draftId}`;
436
+ const renderedFrontmatter = originalSubject === void 0 ? substitutedFrontmatter : replaceExactString(substitutedFrontmatter, originalSubject, draftSubject);
437
+ if (isRecord(renderedFrontmatter.domain)) {
438
+ renderedFrontmatter.domain.id = draftSubject;
439
+ renderedFrontmatter.domain.iid = null;
440
+ renderedFrontmatter.domain.type = derivedType;
441
+ renderedFrontmatter.domain.class = expectedProtocol;
442
+ renderedFrontmatter.domain.status = "draft";
443
+ }
444
+ if (isRecord(renderedFrontmatter.constitution))
445
+ renderedFrontmatter.constitution.subject = draftSubject;
446
+ renderedFrontmatter.version = SPEC_VERSION;
447
+ if (isRecord(renderedFrontmatter.conformance))
448
+ renderedFrontmatter.conformance.profile = "authoring_draft";
449
+ const body = source.slice(match[0].length).replace(/\{\{([A-Za-z0-9_.-]+)\}\}/g, (_whole, name) => {
450
+ const definition = allowed.get(name);
451
+ if (!definition) throw new TemplateValidationError(`Unknown Markdown parameter ${name}.`);
452
+ const value = parameters[name];
453
+ if (value === void 0)
454
+ throw new TemplateValidationError(`Markdown parameter ${name} is unresolved.`);
455
+ used.add(name);
456
+ return markdownEscape(value);
457
+ });
458
+ for (const definition of definitions) {
459
+ if (definition.required && !used.has(definition.name))
460
+ throw new TemplateValidationError(
461
+ `Required parameter ${definition.name} was declared but unused.`
462
+ );
463
+ }
464
+ if (/\{\{[^{}\n]+\}\}/.test(body))
465
+ throw new TemplateValidationError(`Unresolved placeholder remains in ${file.path}.`);
466
+ return new TextEncoder().encode(
467
+ `---
468
+ ${stringify(renderedFrontmatter, { lineWidth: 0 }).trimEnd()}
469
+ ---
470
+ ${body.replace(/^\n/, "")}`
471
+ );
472
+ }
473
+ async function resolveRelative(manifestUri, path) {
474
+ return manifestUri.startsWith("file:") ? FileTemplateResolver.resolveRelative(manifestUri, path) : relativeTemplateUri(manifestUri, path);
475
+ }
476
+ async function renderTemplateBundle(request2) {
477
+ const resolvers = request2.resolvers ?? [
478
+ new FileTemplateResolver(),
479
+ new HttpsTemplateResolver(),
480
+ ...request2.ipfsGateway ? [new IpfsTemplateResolver(request2.ipfsGateway)] : []
481
+ ];
482
+ if (request2.manifestUri.startsWith("https:") && !request2.manifestSha256 && !request2.manifestCid) {
483
+ throw new Error("HTTPS manifests require --manifest-sha256 or --manifest-cid.");
484
+ }
485
+ if (request2.manifestUri.startsWith("ipfs:") && !request2.ipfsGateway) {
486
+ throw new Error("IPFS templates require an explicit HTTPS gateway.");
487
+ }
488
+ const context = { maxBytes: MAX_LINKED_BYTES, timeoutMs: request2.timeoutMs ?? 1e4 };
489
+ const manifestResource = await chooseResolver(request2.manifestUri, resolvers).resolve(
490
+ request2.manifestUri,
491
+ context
492
+ );
493
+ const ipfsManifest = request2.manifestUri.startsWith("ipfs:") ? new URL(request2.manifestUri) : void 0;
494
+ if (ipfsManifest && ipfsManifest.pathname !== "" && ipfsManifest.pathname !== "/" && !request2.manifestSha256) {
495
+ throw new Error("IPFS manifests below a root path require an explicit SHA-256 digest.");
496
+ }
497
+ const impliedCid = ipfsManifest && (ipfsManifest.pathname === "" || ipfsManifest.pathname === "/") ? ipfsManifest.hostname : void 0;
498
+ await verifyIdentity(manifestResource, request2.manifestSha256, request2.manifestCid ?? impliedCid);
499
+ let manifest;
500
+ try {
501
+ manifest = parseTemplateManifest(manifestResource.bytes);
502
+ } catch (error) {
503
+ throw new TemplateValidationError(
504
+ `Template manifest is invalid: ${error instanceof Error ? error.message : String(error)}`
505
+ );
506
+ }
507
+ if (manifest.protocol !== request2.expectedProtocol)
508
+ throw new TemplateValidationError(
509
+ "Template manifest protocol does not match expectedProtocol."
510
+ );
511
+ const bundle = manifest.bundles.find(
512
+ (candidate) => candidate.derived_type === request2.derivedType
513
+ );
514
+ if (!bundle)
515
+ throw new TemplateValidationError(`Template manifest has no ${request2.derivedType} bundle.`);
516
+ const parameterDigest = sha(new TextEncoder().encode(canonical(request2.parameters)));
517
+ const draftId = deterministicUuid(
518
+ `${manifest.protocol}:${bundle.bundle_version}:${sha(manifestResource.bytes)}:${parameterDigest}`
519
+ );
520
+ const rendered = [];
521
+ for (const file of bundle.files) {
522
+ const uri = file.uri ?? await resolveRelative(manifestResource.uri, file.path);
523
+ const resource = await chooseResolver(uri, resolvers).resolve(uri, context);
524
+ if (!manifestResource.uri.startsWith("file:") && resource.uri.startsWith("file:")) {
525
+ throw new TemplateValidationError("Remote manifests cannot resolve local file resources.");
526
+ }
527
+ if (resource.bytes.byteLength > file.max_bytes) {
528
+ throw new TemplateValidationError(
529
+ `Template ${file.path} exceeds its declared max_bytes limit.`
530
+ );
531
+ }
532
+ await verifyIdentity(resource, file.sha256, file.cid);
533
+ let source;
534
+ try {
535
+ source = new TextDecoder("utf-8", { fatal: true }).decode(resource.bytes);
536
+ } catch {
537
+ throw new TemplateValidationError(`Template ${file.path} is not valid UTF-8.`);
538
+ }
539
+ const bytes = file.media_type === "text/markdown" ? renderMarkdownTemplate(
540
+ source,
541
+ file,
542
+ request2.expectedProtocol,
543
+ request2.derivedType,
544
+ request2.parameters,
545
+ draftId
546
+ ) : resource.bytes;
547
+ rendered.push({
548
+ role: file.role,
549
+ path: file.path.replace(/^templates\/[a-z0-9_-]+\//, "").replace(/\.tmpl$/, ""),
550
+ bytes,
551
+ sha256: sha(bytes),
552
+ sourceUri: resource.uri
553
+ });
554
+ }
555
+ const domain = rendered.find((file) => basename(file.path) === "domain.md");
556
+ if (!domain) throw new TemplateValidationError("Rendered bundle must contain domain.md.");
557
+ const domainText = new TextDecoder().decode(domain.bytes);
558
+ const report = lint(domainText, { sourceName: domain.path, expectedProfile: "authoring_draft" });
559
+ if (!report.ok)
560
+ throw new TemplateValidationError(
561
+ `Rendered domain.md failed validation: ${report.findings.filter((item) => item.severity === "error").map((item) => `${item.code}: ${item.message}`).join("; ")}`
562
+ );
563
+ const provenance = stringify(
564
+ {
565
+ provenance: {
566
+ state: "authoring_draft",
567
+ specification_version: SPEC_VERSION,
568
+ protocol: manifest.protocol,
569
+ protocol_version: manifest.protocol_version,
570
+ manifest: { uri: manifestResource.uri, sha256: sha(manifestResource.bytes) },
571
+ bundle: { derived_type: bundle.derived_type, version: bundle.bundle_version },
572
+ draft_domain_id: `urn:uuid:${draftId}`,
573
+ files: rendered.map((file) => ({
574
+ role: file.role,
575
+ path: file.path,
576
+ source_uri: file.sourceUri,
577
+ sha256: file.sha256
578
+ })),
579
+ parameters_digest: parameterDigest
580
+ }
581
+ },
582
+ { lineWidth: 0 }
583
+ );
584
+ return { files: rendered, provenance, report, manifest };
585
+ }
586
+
587
+ export {
588
+ TemplateValidationError,
589
+ parseTemplateManifest,
590
+ validateHttpsTarget,
591
+ FileTemplateResolver,
592
+ HttpsTemplateResolver,
593
+ IpfsTemplateResolver,
594
+ relativeTemplateUri,
595
+ renderTemplateBundle
596
+ };
597
+ //# sourceMappingURL=chunk-BYBC2TSS.js.map