halfcode-compiler.xnl 0.2.6 → 0.2.8

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.
@@ -1,10 +1,8 @@
1
1
  import { n as wordToString, t as parseXnl } from "./dist-Bkv7YeVi.js";
2
- import { mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
3
- import { dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
2
+ import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
3
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { parse } from "yaml";
5
5
  import { createHash } from "node:crypto";
6
- import { tmpdir } from "node:os";
7
- import { pathToFileURL } from "node:url";
8
6
  //#region ../resource-core/src/canonical.ts
9
7
  function compareCodeUnits(left, right) {
10
8
  if (left < right) return -1;
@@ -75,17 +73,17 @@ function createResourceContentIdentity(input) {
75
73
  strictCanonicalInput: false,
76
74
  validateClaimedDigest: false
77
75
  });
78
- if (diagnostics.length > 0 || !identity) throw new ResourceCompositionError(stableDiagnostics$1(diagnostics));
76
+ if (diagnostics.length > 0 || !identity) throw new ResourceCompositionError(stableDiagnostics(diagnostics));
79
77
  return identity;
80
78
  }
81
79
  function buildResourceDependencySnapshot(input) {
82
80
  const diagnostics = [];
83
- if (!isEffectiveRegistryAuthentic(input.registry) || input.registry.revision !== input.registry.compositionRevision) throw new ResourceCompositionError(Object.freeze([diagnostic$1("RESOURCE_EFFECTIVE_REGISTRY_UNTRUSTED", "effective-registry:revision", "Dependency snapshots require an authentic immutable registry returned by composeLayeredResourceRegistry().")]));
81
+ if (!isEffectiveRegistryAuthentic(input.registry) || input.registry.revision !== input.registry.compositionRevision) throw new ResourceCompositionError(Object.freeze([diagnostic("RESOURCE_EFFECTIVE_REGISTRY_UNTRUSTED", "effective-registry:revision", "Dependency snapshots require an authentic immutable registry returned by composeLayeredResourceRegistry().")]));
84
82
  const roots = validateRoots(input.roots, input.registry, diagnostics);
85
83
  const edges = validateEdges(input.edges, input.registry, diagnostics);
86
84
  validateContentIdentityIndex(input.contentIdentities, diagnostics);
87
85
  const validatedIdentities = validateEffectiveContentIdentities(input.registry, input.contentIdentities, diagnostics);
88
- if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics$1(diagnostics));
86
+ if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics(diagnostics));
89
87
  const registryRevision = digestCanonical({
90
88
  compositionRevision: input.registry.compositionRevision,
91
89
  resources: [...validatedIdentities.values()].sort((left, right) => compareCodeUnits(left.resourceId, right.resourceId)).map((identity) => ({
@@ -109,7 +107,7 @@ function buildResourceDependencySnapshot(input) {
109
107
  if (visiting.has(resourceId)) {
110
108
  const cycleStart = path.indexOf(resourceId);
111
109
  const cycle = [...path.slice(cycleStart), resourceId];
112
- diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_CYCLE", dependencyLocation(resourceId), `Resource dependency cycle detected: ${cycle.join(" -> ")}.`));
110
+ diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_CYCLE", dependencyLocation(resourceId), `Resource dependency cycle detected: ${cycle.join(" -> ")}.`));
113
111
  return;
114
112
  }
115
113
  if (visited.has(resourceId)) return;
@@ -125,7 +123,7 @@ function buildResourceDependencySnapshot(input) {
125
123
  visited.add(resourceId);
126
124
  };
127
125
  for (const root of roots) visit(root);
128
- if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics$1(diagnostics));
126
+ if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics(diagnostics));
129
127
  const closure = Object.freeze([...reachable].sort(compareCodeUnits).map((resourceId) => {
130
128
  const entry = input.registry.byId.get(resourceId);
131
129
  const identity = validatedIdentities.get(resourceId);
@@ -163,19 +161,19 @@ function validateRoots(inputRoots, registry, diagnostics) {
163
161
  for (const value of inputRoots) {
164
162
  const resourceId = value.trim();
165
163
  if (resourceId.length === 0) {
166
- diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_ROOT_EMPTY", "dependency-root:unnamed", "Dependency snapshot roots must be non-empty resource ids."));
164
+ diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOT_EMPTY", "dependency-root:unnamed", "Dependency snapshot roots must be non-empty resource ids."));
167
165
  continue;
168
166
  }
169
167
  if (seen.has(resourceId)) {
170
- diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_ROOT_DUPLICATE", dependencyLocation(resourceId), `Dependency snapshot root '${resourceId}' is declared more than once.`));
168
+ diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOT_DUPLICATE", dependencyLocation(resourceId), `Dependency snapshot root '${resourceId}' is declared more than once.`));
171
169
  continue;
172
170
  }
173
171
  seen.add(resourceId);
174
172
  roots.push(resourceId);
175
173
  const entry = registry.byId.get(resourceId);
176
- if (!entry || !entry.resource) diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_ROOT_MISSING", dependencyLocation(resourceId), `Dependency snapshot root '${resourceId}' is not an effective resource.`));
174
+ if (!entry || !entry.resource) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOT_MISSING", dependencyLocation(resourceId), `Dependency snapshot root '${resourceId}' is not an effective resource.`));
177
175
  }
178
- if (roots.length === 0 && diagnostics.length === 0) diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_ROOTS_EMPTY", "dependency-root:none", "Dependency snapshot requires at least one explicit root."));
176
+ if (roots.length === 0 && diagnostics.length === 0) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOTS_EMPTY", "dependency-root:none", "Dependency snapshot requires at least one explicit root."));
179
177
  return roots;
180
178
  }
181
179
  function validateEdges(inputEdges, registry, diagnostics) {
@@ -188,13 +186,13 @@ function validateEdges(inputEdges, registry, diagnostics) {
188
186
  declaredBy: inputEdge.declaredBy.trim()
189
187
  });
190
188
  if (!edge.fromResourceId || !edge.toResourceId || !edge.relation || !edge.declaredBy) {
191
- diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_EDGE_INVALID", `dependency-edge:${index}`, "Dependency edges require non-empty fromResourceId, toResourceId, relation, and declaredBy fields."));
189
+ diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_EDGE_INVALID", `dependency-edge:${index}`, "Dependency edges require non-empty fromResourceId, toResourceId, relation, and declaredBy fields."));
192
190
  return;
193
191
  }
194
192
  const source = registry.byId.get(edge.fromResourceId);
195
193
  const target = registry.byId.get(edge.toResourceId);
196
- if (!source?.resource) diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_SOURCE_MISSING", dependencyLocation(edge.fromResourceId), `Dependency edge source '${edge.fromResourceId}' is not an effective resource.`));
197
- if (!target?.resource) diagnostics.push(diagnostic$1("RESOURCE_DEPENDENCY_TARGET_MISSING", dependencyLocation(edge.toResourceId), `Dependency edge target '${edge.toResourceId}' is not an effective resource.`));
194
+ if (!source?.resource) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_SOURCE_MISSING", dependencyLocation(edge.fromResourceId), `Dependency edge source '${edge.fromResourceId}' is not an effective resource.`));
195
+ if (!target?.resource) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_TARGET_MISSING", dependencyLocation(edge.toResourceId), `Dependency edge target '${edge.toResourceId}' is not an effective resource.`));
198
196
  values.set(edgeKey(edge), edge);
199
197
  });
200
198
  return [...values.values()].sort(compareEdges);
@@ -202,41 +200,41 @@ function validateEdges(inputEdges, registry, diagnostics) {
202
200
  function normalizeContentIdentity(input, diagnostics, options) {
203
201
  const initialDiagnosticCount = diagnostics.length;
204
202
  if (!isRecord(input)) {
205
- diagnostics.push(diagnostic$1("RESOURCE_CONTENT_IDENTITY_INVALID", contentLocation(""), "Resource content identity must be an object fact."));
203
+ diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_INVALID", contentLocation(""), "Resource content identity must be an object fact."));
206
204
  return;
207
205
  }
208
206
  const rawResourceId = input.resourceId;
209
207
  const resourceId = typeof rawResourceId === "string" ? rawResourceId.trim() : "";
210
- if (resourceId.length === 0) diagnostics.push(diagnostic$1("RESOURCE_CONTENT_IDENTITY_ID_EMPTY", contentLocation(""), "Resource content identity requires a non-empty resource id."));
211
- else if (options.strictCanonicalInput && rawResourceId !== resourceId) diagnostics.push(diagnostic$1("RESOURCE_CONTENT_IDENTITY_NON_CANONICAL", contentLocation(resourceId), `Resource content identity '${resourceId}' must use its canonical id without surrounding whitespace.`));
208
+ if (resourceId.length === 0) diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_ID_EMPTY", contentLocation(""), "Resource content identity requires a non-empty resource id."));
209
+ else if (options.strictCanonicalInput && rawResourceId !== resourceId) diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_NON_CANONICAL", contentLocation(resourceId), `Resource content identity '${resourceId}' must use its canonical id without surrounding whitespace.`));
212
210
  const authorityDigest = typeof input.authorityDigest === "string" ? input.authorityDigest : "";
213
- if (!isSha256Digest(authorityDigest)) diagnostics.push(diagnostic$1("RESOURCE_CONTENT_AUTHORITY_DIGEST_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has an invalid authority digest.`));
211
+ if (!isSha256Digest(authorityDigest)) diagnostics.push(diagnostic("RESOURCE_CONTENT_AUTHORITY_DIGEST_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has an invalid authority digest.`));
214
212
  const rawContributions = input.contributions;
215
213
  let contributionFacts = [];
216
214
  if (rawContributions === void 0 && !options.requireContributions) contributionFacts = [];
217
- else if (!Array.isArray(rawContributions)) diagnostics.push(diagnostic$1("RESOURCE_DIGEST_CONTRIBUTIONS_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' must provide an explicit digest contribution array.`));
215
+ else if (!Array.isArray(rawContributions)) diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTIONS_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' must provide an explicit digest contribution array.`));
218
216
  else contributionFacts = rawContributions;
219
217
  const contributions = /* @__PURE__ */ new Map();
220
218
  for (const fact of contributionFacts) {
221
219
  if (!isRecord(fact)) {
222
- diagnostics.push(diagnostic$1("RESOURCE_DIGEST_CONTRIBUTION_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has a non-object digest contribution.`));
220
+ diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has a non-object digest contribution.`));
223
221
  continue;
224
222
  }
225
223
  const rawKey = fact.key;
226
224
  const key = typeof rawKey === "string" ? rawKey.trim() : "";
227
225
  if (key.length === 0) {
228
- diagnostics.push(diagnostic$1("RESOURCE_DIGEST_CONTRIBUTION_KEY_EMPTY", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has a digest contribution with an empty key.`));
226
+ diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_KEY_EMPTY", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has a digest contribution with an empty key.`));
229
227
  continue;
230
228
  }
231
- if (options.strictCanonicalInput && rawKey !== key) diagnostics.push(diagnostic$1("RESOURCE_DIGEST_CONTRIBUTION_NON_CANONICAL", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId}' must use its canonical key.`));
229
+ if (options.strictCanonicalInput && rawKey !== key) diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_NON_CANONICAL", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId}' must use its canonical key.`));
232
230
  const digest = typeof fact.digest === "string" ? fact.digest : "";
233
231
  if (!isSha256Digest(digest)) {
234
- diagnostics.push(diagnostic$1("RESOURCE_DIGEST_CONTRIBUTION_INVALID", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' is invalid.`));
232
+ diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_INVALID", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' is invalid.`));
235
233
  continue;
236
234
  }
237
235
  const sourceUri = fact.sourceUri;
238
236
  if (sourceUri !== void 0 && typeof sourceUri !== "string") {
239
- diagnostics.push(diagnostic$1("RESOURCE_DIGEST_CONTRIBUTION_SOURCE_INVALID", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId}' has an invalid source URI.`));
237
+ diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_SOURCE_INVALID", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId}' has an invalid source URI.`));
240
238
  continue;
241
239
  }
242
240
  const contribution = Object.freeze({
@@ -247,7 +245,7 @@ function normalizeContentIdentity(input, diagnostics, options) {
247
245
  const current = contributions.get(key);
248
246
  if (current) {
249
247
  const conflicting = current.digest !== digest;
250
- diagnostics.push(diagnostic$1(conflicting ? "RESOURCE_DIGEST_CONTRIBUTION_CONFLICT" : "RESOURCE_DIGEST_CONTRIBUTION_DUPLICATE", contentLocation(resourceId, key), conflicting ? `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' has conflicting digests.` : `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' is declared more than once.`));
248
+ diagnostics.push(diagnostic(conflicting ? "RESOURCE_DIGEST_CONTRIBUTION_CONFLICT" : "RESOURCE_DIGEST_CONTRIBUTION_DUPLICATE", contentLocation(resourceId, key), conflicting ? `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' has conflicting digests.` : `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' is declared more than once.`));
251
249
  continue;
252
250
  }
253
251
  contributions.set(key, contribution);
@@ -265,11 +263,11 @@ function normalizeContentIdentity(input, diagnostics, options) {
265
263
  if (options.validateClaimedDigest) {
266
264
  const claimedDigest = input.contentDigest;
267
265
  if (typeof claimedDigest !== "string" || !isSha256Digest(claimedDigest)) {
268
- diagnostics.push(diagnostic$1("RESOURCE_CONTENT_DIGEST_INVALID", contentLocation(resourceId), `Resource '${resourceId}' has an invalid claimed content digest.`));
266
+ diagnostics.push(diagnostic("RESOURCE_CONTENT_DIGEST_INVALID", contentLocation(resourceId), `Resource '${resourceId}' has an invalid claimed content digest.`));
269
267
  return;
270
268
  }
271
269
  if (claimedDigest !== contentDigest) {
272
- diagnostics.push(diagnostic$1("RESOURCE_CONTENT_DIGEST_MISMATCH", contentLocation(resourceId), `Resource '${resourceId}' claimed a content digest that does not match its explicit authority and contributions.`));
270
+ diagnostics.push(diagnostic("RESOURCE_CONTENT_DIGEST_MISMATCH", contentLocation(resourceId), `Resource '${resourceId}' claimed a content digest that does not match its explicit authority and contributions.`));
273
271
  return;
274
272
  }
275
273
  }
@@ -286,7 +284,7 @@ function validateEffectiveContentIdentities(registry, identities, diagnostics) {
286
284
  for (const entry of effectiveEntries) {
287
285
  const input = identities.get(entry.resourceId);
288
286
  if (input === void 0) {
289
- diagnostics.push(diagnostic$1("RESOURCE_CONTENT_IDENTITY_MISSING", dependencyLocation(entry.resourceId), `Effective resource '${entry.resourceId}' has no explicit content identity.`));
287
+ diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_MISSING", dependencyLocation(entry.resourceId), `Effective resource '${entry.resourceId}' has no explicit content identity.`));
290
288
  continue;
291
289
  }
292
290
  const identity = normalizeContentIdentity(input, diagnostics, {
@@ -301,7 +299,7 @@ function validateEffectiveContentIdentities(registry, identities, diagnostics) {
301
299
  function validateContentIdentityIndex(identities, diagnostics) {
302
300
  for (const [key, identity] of [...identities.entries()].sort(([left], [right]) => compareCodeUnits(left, right))) {
303
301
  const identityResourceId = isRecord(identity) && typeof identity.resourceId === "string" ? identity.resourceId : void 0;
304
- if (key !== identityResourceId) diagnostics.push(diagnostic$1("RESOURCE_CONTENT_IDENTITY_KEY_MISMATCH", dependencyLocation(key), `Content identity index key '${key}' does not match identity '${identityResourceId ?? "invalid"}'.`));
302
+ if (key !== identityResourceId) diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_KEY_MISMATCH", dependencyLocation(key), `Content identity index key '${key}' does not match identity '${identityResourceId ?? "invalid"}'.`));
305
303
  }
306
304
  }
307
305
  function isSha256Digest(value) {
@@ -347,10 +345,10 @@ function cloneOrigin(origin) {
347
345
  ...origin.logicalPath === void 0 ? {} : { logicalPath: origin.logicalPath }
348
346
  });
349
347
  }
350
- function stableDiagnostics$1(diagnostics) {
348
+ function stableDiagnostics(diagnostics) {
351
349
  return Object.freeze([...diagnostics].sort((left, right) => compareCodeUnits(left.location, right.location) || compareCodeUnits(left.code, right.code)));
352
350
  }
353
- function diagnostic$1(code, location, message) {
351
+ function diagnostic(code, location, message) {
354
352
  return Object.freeze({
355
353
  code,
356
354
  location,
@@ -374,224 +372,6 @@ function isLoadedResourceTreeAuthentic(tree) {
374
372
  return authenticLoadedResourceTrees.has(tree);
375
373
  }
376
374
  //#endregion
377
- //#region ../resource-core/src/code-package.ts
378
- const CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY = "code-binding:module";
379
- /** @internal Node effect implementation bound by the public facade. */
380
- const nodeCodePackageRuntime = Object.freeze({
381
- realpath,
382
- readFile,
383
- stat,
384
- prepareModuleSource: async ({ bytes }) => {
385
- const directory = await mkdtemp(join(tmpdir(), "halfcode-code-package-module-"));
386
- const sourcePath = join(directory, "entry.mjs");
387
- try {
388
- await writeFile(sourcePath, bytes, { flag: "wx" });
389
- } catch (error) {
390
- await rm(directory, {
391
- recursive: true,
392
- force: true
393
- }).catch(() => void 0);
394
- throw error;
395
- }
396
- return Object.freeze({
397
- url: pathToFileURL(sourcePath).href,
398
- dispose: async () => rm(directory, {
399
- recursive: true,
400
- force: true
401
- })
402
- });
403
- },
404
- importModule: async (url) => import(url)
405
- });
406
- var CodePackageLoadError = class extends Error {
407
- diagnostics;
408
- constructor(diagnostics) {
409
- super(`CodePackage loading failed with ${diagnostics.length} diagnostic(s)`);
410
- this.name = "CodePackageLoadError";
411
- this.diagnostics = Object.freeze([...diagnostics]);
412
- }
413
- };
414
- /** @internal Used by the canonical XNL loader before it brands the completed tree. */
415
- function normalizeCodePackageBinding(record) {
416
- const diagnostics = [];
417
- if (record.sourceShape !== "code-package") {
418
- diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_SHAPE_INVALID", record.documentUri, `Resource '${record.resourceId}' does not use the code-package source shape.`));
419
- return { diagnostics };
420
- }
421
- const declaredEntry = stringValue(record.node.properties.entry);
422
- const declaredRuntime = stringValue(record.node.properties.runtime);
423
- const declaredModuleUri = declaredEntry ? normalizeModuleUri(`vfs://./${declaredEntry}`) : void 0;
424
- if (!declaredEntry) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_DECLARATION_MISSING", record.documentUri, `CodePackage resource '${record.resourceId}' requires a manifest entry.`));
425
- else if (!declaredModuleUri) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_DECLARATION_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has an invalid manifest entry.`));
426
- if (!declaredRuntime) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_RUNTIME_MISSING", record.documentUri, `CodePackage resource '${record.resourceId}' requires a manifest runtime.`));
427
- const node = record.node.subdomains.CodeBinding;
428
- if (!node || node.tag !== "CodeBinding") {
429
- diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_BINDING_MISSING", record.documentUri, `CodePackage resource '${record.resourceId}' requires one CodeBinding.`));
430
- return { diagnostics };
431
- }
432
- if (node.properties.package !== void 0) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_BINDING_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' must bind a package-local module, not an external package.`));
433
- const rawModule = stringValue(node.properties.module);
434
- const moduleUri = rawModule === void 0 ? void 0 : normalizeModuleUri(rawModule);
435
- if (!moduleUri) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_MODULE_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' requires a canonical package-local .js or .mjs module URI.`));
436
- const rawExport = node.properties.export;
437
- const exportName = stringValue(rawExport);
438
- if (rawExport !== void 0 && (!exportName || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName))) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_EXPORT_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has an invalid named export binding.`));
439
- if (moduleUri && declaredModuleUri && moduleUri !== declaredModuleUri) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_BINDING_MISMATCH", record.documentUri, `CodePackage resource '${record.resourceId}' manifest entry and CodeBinding.module must identify the same package-local module.`));
440
- if (diagnostics.length > 0 || !moduleUri) return { diagnostics: stableDiagnostics(diagnostics) };
441
- return {
442
- binding: Object.freeze({
443
- resourceId: record.resourceId,
444
- moduleUri,
445
- ...exportName ? { exportName } : {}
446
- }),
447
- diagnostics: Object.freeze([])
448
- };
449
- }
450
- /** @internal Resolves and reads the exact entry bytes under the descriptor directory. */
451
- async function readCodePackageEntry(runtime, rootDir, record, binding) {
452
- const diagnostics = [];
453
- const realRoot = await runtime.realpath(resolve(rootDir)).catch(() => void 0);
454
- if (!realRoot) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ROOT_MISSING", record.documentUri, `The resource root for '${record.resourceId}' is missing or unreadable.`)] };
455
- if (!isSafeLogicalPath(record.logicalPath)) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_PROVENANCE_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has unsafe logical provenance.`)] };
456
- const descriptorPath = resolve(realRoot, record.logicalPath);
457
- const descriptorDirectory = await runtime.realpath(dirname(descriptorPath)).catch(() => void 0);
458
- if (!descriptorDirectory || !isWithin(realRoot, descriptorDirectory)) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_CONTAINMENT", record.documentUri, `CodePackage resource '${record.resourceId}' resolves outside the resource root.`)] };
459
- const localPath = moduleLocalPath(binding.moduleUri);
460
- if (!localPath) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_MODULE_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has an invalid module URI.`)] };
461
- const entryPath = await runtime.realpath(resolve(descriptorDirectory, ...localPath.split("/"))).catch(() => void 0);
462
- if (!entryPath) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_MISSING", `${record.documentUri}#CodeBinding`, `CodePackage entry is missing for '${record.resourceId}'.`)] };
463
- if (!isWithin(descriptorDirectory, entryPath) || entryPath === descriptorDirectory) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_CONTAINMENT", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' resolves outside its package directory.`));
464
- if (!(await runtime.stat(entryPath).catch(() => void 0))?.isFile()) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_INVALID", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' must be a regular file.`));
465
- if (diagnostics.length > 0) return { diagnostics: stableDiagnostics(diagnostics) };
466
- const bytes = await runtime.readFile(entryPath).catch(() => void 0);
467
- if (!bytes) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_MISSING", `${record.documentUri}#CodeBinding`, `CodePackage entry is unreadable for '${record.resourceId}'.`)] };
468
- const resourceDirectory = posix.dirname(record.logicalPath);
469
- return {
470
- entryPath,
471
- bytes,
472
- sourceUri: `vfs://@/${resourceDirectory === "." ? localPath : `${resourceDirectory}/${localPath}`}`,
473
- diagnostics: Object.freeze([])
474
- };
475
- }
476
- async function loadCodePackageModule(input) {
477
- return loadCodePackageModuleWithRuntime(nodeCodePackageRuntime, input);
478
- }
479
- /** @internal Pure orchestration over an injected effect runtime; hidden by the package facade. */
480
- async function loadCodePackageModuleWithRuntime(runtime, input) {
481
- if (!isLoadedResourceTreeAuthentic(input.tree)) throw loadError("RESOURCE_CODE_PACKAGE_TREE_UNTRUSTED", `code-package:${input.resourceId}`, "CodePackage modules require an authentic LoadedResourceTree.");
482
- const record = findResource(input.tree, input.resourceId);
483
- const binding = input.tree.codePackageBindings.get(input.resourceId);
484
- const identity = input.tree.contentIdentities.get(input.resourceId);
485
- if (!record || record.sourceShape !== "code-package" || !binding || !identity) throw loadError("RESOURCE_CODE_PACKAGE_RESOURCE_INVALID", `code-package:${input.resourceId}`, `Resource '${input.resourceId}' is not a loaded CodePackage.`);
486
- const entry = await readCodePackageEntry(runtime, input.rootDir, record, binding);
487
- if (entry.diagnostics.length > 0 || !entry.entryPath || !entry.bytes) throw new CodePackageLoadError(entry.diagnostics);
488
- const contribution = identity.contributions.find((item) => item.key === CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY);
489
- if (!contribution || contribution.digest !== sha256Digest(entry.bytes) || contribution.sourceUri !== entry.sourceUri) throw loadError("RESOURCE_CODE_PACKAGE_REVISION_STALE", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' changed after its ResourceTree was loaded.`);
490
- let preparedSource;
491
- try {
492
- preparedSource = await runtime.prepareModuleSource({
493
- bytes: entry.bytes,
494
- revision: identity.contentDigest
495
- });
496
- } catch {
497
- throw loadError("RESOURCE_CODE_PACKAGE_IMPORT_PREPARE_FAILED", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' could not be prepared for import.`);
498
- }
499
- let moduleNamespace;
500
- let importFailed = false;
501
- try {
502
- moduleNamespace = await runtime.importModule(preparedSource.url);
503
- } catch {
504
- importFailed = true;
505
- moduleNamespace = Object.freeze({});
506
- }
507
- const cleanupFailed = await preparedSource.dispose().then(() => false, () => true);
508
- if (importFailed) throw loadError("RESOURCE_CODE_PACKAGE_IMPORT_FAILED", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' could not be imported.`);
509
- if (cleanupFailed) throw loadError("RESOURCE_CODE_PACKAGE_IMPORT_CLEANUP_FAILED", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' could not be cleaned up after import.`);
510
- if (binding.exportName && !(binding.exportName in moduleNamespace)) throw loadError("RESOURCE_CODE_PACKAGE_EXPORT_MISSING", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' does not export '${binding.exportName}'.`);
511
- const currentBytes = await runtime.readFile(entry.entryPath).catch(() => void 0);
512
- if (!currentBytes || sha256Digest(currentBytes) !== contribution.digest) throw loadError("RESOURCE_CODE_PACKAGE_REVISION_STALE", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' changed while it was being imported.`);
513
- const frozenNamespace = Object.freeze(Object.fromEntries(Object.keys(moduleNamespace).sort(compareCodeUnits).map((name) => [name, moduleNamespace[name]])));
514
- return Object.freeze({
515
- resourceId: record.resourceId,
516
- binding,
517
- revision: identity.contentDigest,
518
- moduleNamespace: frozenNamespace
519
- });
520
- }
521
- function admitCodePackageExports(loaded, contract) {
522
- return admitCandidates([{
523
- loaded,
524
- contract
525
- }]);
526
- }
527
- function admitCodePackages(loaded, contract) {
528
- return admitCandidates(loaded.map((item) => ({
529
- loaded: item,
530
- contract
531
- })));
532
- }
533
- function admitCandidates(packages) {
534
- const definitions = /* @__PURE__ */ new Map();
535
- for (const { loaded, contract } of packages) {
536
- const names = loaded.binding.exportName ? [loaded.binding.exportName] : Object.keys(loaded.moduleNamespace).sort(compareCodeUnits);
537
- for (const name of names) {
538
- const exported = loaded.moduleNamespace[name];
539
- const candidates = Array.isArray(exported) ? exported : [exported];
540
- for (const candidate of candidates) {
541
- if (!contract.isDefinition(candidate)) continue;
542
- const identity = contract.identityOf(candidate).trim();
543
- if (!identity) throw new TypeError(`CodePackage definition from '${loaded.resourceId}' has an empty identity`);
544
- if (definitions.has(identity)) throw new TypeError(`Duplicate CodePackage definition identity '${identity}'`);
545
- definitions.set(identity, candidate);
546
- }
547
- }
548
- }
549
- return Object.freeze([...definitions.entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([, definition]) => definition));
550
- }
551
- function normalizeModuleUri(value) {
552
- const prefix = "vfs://./";
553
- if (!value.startsWith(prefix) || value !== value.trim() || value.includes("%") || value.includes("\\") || value.includes("?") || value.includes("#")) return void 0;
554
- const localPath = value.slice(8);
555
- if (!localPath || isAbsolute(localPath) || posix.isAbsolute(localPath)) return void 0;
556
- const parts = localPath.split("/");
557
- if (parts.some((part) => !part || part === "." || part === "..")) return void 0;
558
- const extension = extname(localPath).toLowerCase();
559
- if (extension !== ".js" && extension !== ".mjs") return void 0;
560
- return `${prefix}${parts.join("/")}`;
561
- }
562
- function moduleLocalPath(moduleUri) {
563
- return normalizeModuleUri(moduleUri)?.slice(8);
564
- }
565
- function findResource(tree, resourceId) {
566
- for (const records of tree.registry.byKind.values()) {
567
- const record = records.find((item) => item.resourceId === resourceId);
568
- if (record) return record;
569
- }
570
- }
571
- function isSafeLogicalPath(value) {
572
- return Boolean(value) && !isAbsolute(value) && !value.includes("\\") && !value.split("/").some((part) => !part || part === "." || part === "..");
573
- }
574
- function isWithin(boundary, candidate) {
575
- const path = relative(boundary, candidate);
576
- return path === "" || !path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path);
577
- }
578
- function stringValue(value) {
579
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
580
- }
581
- function loadError(code, location, message) {
582
- return new CodePackageLoadError([diagnostic(code, location, message)]);
583
- }
584
- function stableDiagnostics(diagnostics) {
585
- return Object.freeze([...diagnostics].sort((left, right) => compareCodeUnits(left.location, right.location) || compareCodeUnits(left.code, right.code)));
586
- }
587
- function diagnostic(code, location, message) {
588
- return Object.freeze({
589
- code,
590
- location,
591
- message
592
- });
593
- }
594
- //#endregion
595
375
  //#region ../resource-core/src/readonly-map.ts
596
376
  function readonlyMap(entries) {
597
377
  const values = new Map(entries);
@@ -635,8 +415,7 @@ async function buildXnlResourceTree(options) {
635
415
  seenIdentities: /* @__PURE__ */ new Map(),
636
416
  authorityFiles: /* @__PURE__ */ new Map(),
637
417
  recordAuthorityDigests: /* @__PURE__ */ new WeakMap(),
638
- recordContributions: /* @__PURE__ */ new WeakMap(),
639
- codePackageBindings: /* @__PURE__ */ new Map()
418
+ recordContributions: /* @__PURE__ */ new WeakMap()
640
419
  };
641
420
  const manifest = await loadManifest(resolve(context.rootDir, options.manifestPath), "manifest", context, true);
642
421
  if (!manifest || context.diagnostics.length > 0) return { diagnostics: context.diagnostics };
@@ -649,7 +428,6 @@ async function buildXnlResourceTree(options) {
649
428
  const frozenByKind = readonlyMap([...byKind.entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([kind, records]) => [kind, Object.freeze([...records].sort((left, right) => compareCodeUnits(left.resourceId, right.resourceId)))]));
650
429
  const frozenKindDefinitions = readonlyMap([...context.kindDefinitions.entries()].sort(([left], [right]) => compareCodeUnits(left, right)));
651
430
  const contentIdentities = readonlyMap(context.resources.map((resource) => [resource.resourceId, loadedContentIdentity(resource, context)]).sort(([left], [right]) => compareCodeUnits(left, right)));
652
- const codePackageBindings = readonlyMap([...context.codePackageBindings.entries()].sort(([left], [right]) => compareCodeUnits(left, right)));
653
431
  return {
654
432
  diagnostics: [],
655
433
  tree: markLoadedResourceTreeAuthentic(Object.freeze({
@@ -659,24 +437,38 @@ async function buildXnlResourceTree(options) {
659
437
  kindDefinitions: frozenKindDefinitions
660
438
  }),
661
439
  diagnostics: Object.freeze([]),
662
- contentIdentities,
663
- codePackageBindings
440
+ contentIdentities
664
441
  }))
665
442
  };
666
443
  }
667
444
  async function loadManifest(filePath, sourceShape, context, isRoot) {
668
445
  const record = await loadXnlRecord(filePath, sourceShape, context);
669
446
  if (!record) return void 0;
670
- if (isRoot && record.kind !== "ResourcePackage") {
671
- context.diagnostics.push({
672
- code: "RESOURCE_XNL_ROOT_INVALID",
673
- location: record.documentUri,
674
- message: "The root manifest.xnl must contain one ResourcePackage root."
675
- });
676
- return;
677
- }
678
447
  const catalogs = readCatalogs(record, context.diagnostics);
679
448
  for (const catalog of catalogs.filter((item) => item.kind === "KindDefinition")) await loadKindDefinitions(catalog, dirname(filePath), context);
449
+ if (isRoot && record.kind !== "ResourcePackage") {
450
+ const definition = context.kindDefinitions.get(record.kind);
451
+ if (!definition) {
452
+ context.diagnostics.push({
453
+ code: "KIND_DEFINITION_MISSING",
454
+ location: record.documentUri,
455
+ message: `No KindDefinition is registered for semantic root kind '${record.kind}'.`
456
+ });
457
+ return;
458
+ }
459
+ if (!definition.sourceShapes.includes("manifest")) {
460
+ context.diagnostics.push({
461
+ code: "RESOURCE_SOURCE_SHAPE_NOT_ALLOWED",
462
+ location: record.documentUri,
463
+ message: `Semantic root kind '${record.kind}' does not allow source shape 'manifest'.`
464
+ });
465
+ return;
466
+ }
467
+ validateResourceApiVersion(record, definition, context.diagnostics);
468
+ await validateRequiredFiles(dirname(filePath), record, definition, context.diagnostics);
469
+ validateIdentity(record, context);
470
+ context.resources.push(record);
471
+ }
680
472
  for (const catalog of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(catalog, dirname(filePath), context);
681
473
  if (!isRoot) {
682
474
  validateIdentity(record, context);
@@ -749,7 +541,6 @@ async function loadCatalog(catalog, manifestDir, context) {
749
541
  }
750
542
  validateResourceApiVersion(record, definition, context.diagnostics);
751
543
  await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
752
- if (catalog.shape === "code-package" && !await prepareCodePackageRecord(record, context)) continue;
753
544
  validateIdentity(record, context);
754
545
  context.resources.push(record);
755
546
  }
@@ -785,10 +576,27 @@ async function catalogFiles(catalog, manifestDir, context) {
785
576
  context.diagnostics.push({
786
577
  code: "RESOURCE_CATALOG_ENTRY_INVALID",
787
578
  location: catalog.location,
788
- message: "Directory, manifest and code-package catalogs require a plain XNL entry filename."
579
+ message: "Directory and manifest catalogs require a plain XNL entry filename."
789
580
  });
790
581
  return [];
791
582
  }
583
+ if (catalog.shape === "directory" && catalog.scope === "root") {
584
+ const directEntry = join(root, catalog.entry);
585
+ const directStat = await lstat(directEntry).catch((error) => {
586
+ if (error.code === "ENOENT") return void 0;
587
+ throw error;
588
+ });
589
+ if (!directStat) return [directEntry];
590
+ if (directStat.isSymbolicLink() || !directStat.isFile()) {
591
+ context.diagnostics.push({
592
+ code: "RESOURCE_CATALOG_ENTRY_INVALID",
593
+ location: documentUriFor(context.rootDir, directEntry),
594
+ message: "A root-scoped directory catalog entry must be a regular non-symbolic-link XNL file."
595
+ });
596
+ return [];
597
+ }
598
+ return [directEntry];
599
+ }
792
600
  return entries.filter((entry) => entry.isDirectory()).sort((left, right) => compareCodeUnits(left.name, right.name)).map((entry) => join(root, entry.name, catalog.entry));
793
601
  }
794
602
  async function loadXnlRecord(filePath, sourceShape, context) {
@@ -975,22 +783,6 @@ function loadedContentIdentity(resource, context) {
975
783
  contributions: context.recordContributions.get(resource) ?? []
976
784
  });
977
785
  }
978
- async function prepareCodePackageRecord(record, context) {
979
- const normalized = normalizeCodePackageBinding(record);
980
- context.diagnostics.push(...normalized.diagnostics);
981
- if (!normalized.binding) return false;
982
- const entry = await readCodePackageEntry(nodeCodePackageRuntime, context.rootDir, record, normalized.binding);
983
- context.diagnostics.push(...entry.diagnostics);
984
- if (!entry.bytes || !entry.sourceUri) return false;
985
- const contribution = Object.freeze({
986
- key: CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY,
987
- digest: sha256Digest(entry.bytes),
988
- sourceUri: entry.sourceUri
989
- });
990
- context.recordContributions.set(record, Object.freeze([contribution]));
991
- context.codePackageBindings.set(record.resourceId, normalized.binding);
992
- return true;
993
- }
994
786
  function resourceRecordFromRoot(root, filePath, sourceShape, context) {
995
787
  const documentUri = documentUriFor(context.rootDir, filePath);
996
788
  const node = normalizeNode(root);
@@ -1061,18 +853,26 @@ function readCatalogs(record, diagnostics) {
1061
853
  if (!catalogs) return [];
1062
854
  const out = [];
1063
855
  for (const value of catalogs.body) {
1064
- if (!isResourceNode(value) || value.tag !== "Catalog") continue;
856
+ if (!isResourceNode(value)) continue;
857
+ const fixedShape = catalogShapeForTag(value.tag);
858
+ if (value.tag !== "Catalog" && !fixedShape) continue;
1065
859
  const id = value.resourceId;
1066
- const kind = asString(value.properties.kind);
860
+ const kind = asString(value.properties.resourceKind) ?? asString(value.properties.kind);
1067
861
  const root = asString(value.properties.root);
1068
862
  const entry = asString(value.properties.entry);
1069
- const shapeValue = asString(value.properties.shape) ?? (entry ? "directory" : "single-file");
1070
- const location = `${record.documentUri}#Catalog:${id ?? "unknown"}`;
1071
- if (!id || !kind || !root || !isXnlShape(shapeValue)) {
863
+ const declaredShape = asString(value.properties.shape);
864
+ const shapeValue = fixedShape ?? declaredShape ?? (entry ? "directory" : "single-file");
865
+ const declaredScope = asString(value.properties.scope);
866
+ const scope = declaredScope ?? "children";
867
+ const location = `${record.documentUri}#${value.tag}:${id ?? "unknown"}`;
868
+ const shapeConflict = Boolean(fixedShape && declaredShape && declaredShape !== fixedShape);
869
+ const invalidScope = scope !== "root" && scope !== "children";
870
+ const scopeNotAllowed = Boolean(declaredScope && shapeValue !== "directory");
871
+ if (!id || !kind || !root || !isXnlShape(shapeValue) || shapeConflict || invalidScope || scopeNotAllowed) {
1072
872
  diagnostics.push({
1073
873
  code: "RESOURCE_CATALOG_INVALID",
1074
874
  location,
1075
- message: "Catalog requires #id, kind, root and a valid shape."
875
+ message: "Resource catalog requires #id, resourceKind (or legacy kind), root, a tag-consistent shape and a valid directory scope."
1076
876
  });
1077
877
  continue;
1078
878
  }
@@ -1082,11 +882,17 @@ function readCatalogs(record, diagnostics) {
1082
882
  root,
1083
883
  entry,
1084
884
  shape: shapeValue,
885
+ scope,
1085
886
  location
1086
887
  });
1087
888
  }
1088
889
  return out;
1089
890
  }
891
+ function catalogShapeForTag(tag) {
892
+ if (tag === "FileResourceCatalog") return "single-file";
893
+ if (tag === "DirectoryResourceCatalog") return "directory";
894
+ if (tag === "ManifestResourceCatalog") return "manifest";
895
+ }
1090
896
  function normalizeKindDefinition(record, diagnostics) {
1091
897
  const resourceKind = asString(record.node.properties.resourceKind) ?? record.resourceId.split(".").at(-1);
1092
898
  const shapes = record.node.properties.sourceShapes;
@@ -1225,7 +1031,7 @@ function kindMismatch(actual, expected, location) {
1225
1031
  };
1226
1032
  }
1227
1033
  function isXnlShape(value) {
1228
- return value === "single-file" || value === "directory" || value === "manifest" || value === "code-package";
1034
+ return value === "single-file" || value === "directory" || value === "manifest";
1229
1035
  }
1230
1036
  function isDocumentCardinality(value) {
1231
1037
  return value === "one" || value === "many";
@@ -1314,4 +1120,4 @@ function unsupportedAuthorityDiagnostic(options) {
1314
1120
  };
1315
1121
  }
1316
1122
  //#endregion
1317
- export { ResourceCompositionError as _, CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY as a, admitCodePackages as c, buildResourceDependencySnapshot as d, createResourceContentIdentity as f, markEffectiveRegistryAuthentic as g, isEffectiveRegistryAuthentic as h, readonlyMap as i, loadCodePackageModule as l, effectiveRegistryLayerBindings as m, loadResourceTree as n, CodePackageLoadError as o, sha256Digest as p, validateResourceTree as r, admitCodePackageExports as s, ResourceValidationError as t, isLoadedResourceTreeAuthentic as u, compareCodeUnits as v, digestCanonical as y };
1123
+ export { isLoadedResourceTreeAuthentic as a, sha256Digest as c, markEffectiveRegistryAuthentic as d, ResourceCompositionError as f, readonlyMap as i, effectiveRegistryLayerBindings as l, digestCanonical as m, loadResourceTree as n, buildResourceDependencySnapshot as o, compareCodeUnits as p, validateResourceTree as r, createResourceContentIdentity as s, ResourceValidationError as t, isEffectiveRegistryAuthentic as u };