halfcode-compiler.xnl 0.2.8 → 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 (59) hide show
  1. package/README.md +5 -3
  2. package/dist/application-assembly.d.ts +1 -1
  3. package/dist/application-assembly.js +1 -1
  4. package/dist/authoring-runtime.d.ts +167 -1
  5. package/dist/authoring-runtime.js +2 -2
  6. package/dist/effective-content-identities-CV6XFBZy.js +118 -0
  7. package/dist/effective-content-identities-drMRhYE2.d.ts +14 -0
  8. package/dist/{index-B515mMkO.d.ts → index-CR7wJSSX.d.ts} +10 -6
  9. package/dist/{index-Cqh9pEBa.d.ts → index-DEikbOFh.d.ts} +2 -1
  10. package/dist/{index-CF4nEkOi.d.ts → index-DI3WtuXq.d.ts} +1 -1
  11. package/dist/index-DboIOEr8.d.ts +474 -0
  12. package/dist/index.d.ts +7 -5
  13. package/dist/index.js +7 -5
  14. package/dist/kind-definition.d.ts +118 -10
  15. package/dist/kind-definition.js +826 -15
  16. package/dist/resource-core-Brj8naBr.d.ts +47 -0
  17. package/dist/resource-core.d.ts +4 -3
  18. package/dist/resource-core.js +40 -141
  19. package/dist/resource-projection.d.ts +1 -1
  20. package/dist/skill-capsule.d.ts +2 -2
  21. package/dist/skill-capsule.js +1 -1
  22. package/dist/{src-3pnDAjBD.js → src-B0Hja_dk.js} +5 -7
  23. package/dist/{src-CaQ25rMj.js → src-DLJUCH0M.js} +11 -7
  24. package/dist/src-hrUy4DJ9.js +640 -0
  25. package/dist/{src-D3qSzGFn.js → src-jggOQtq3.js} +511 -113
  26. package/dist/system-skills/resource-dsl/KindDefinitions/SkillCapsule/manifest.xnl +15 -3
  27. package/dist/system-skills/resource-dsl/KindDefinitions/WikiPage/manifest.xnl +15 -3
  28. package/dist/system-skills/resource-dsl/SkillCapsules/System/SKILL.metadata.yaml +1 -0
  29. package/dist/system-skills/resource-dsl/SkillCapsules/System/manifest.xnl +1 -1
  30. package/dist/system-skills/resource-dsl/SkillCapsules/System/resource-mappings.xnl +1 -1
  31. package/dist/system-skills/resource-dsl/WikiPages/documents/manifest.xnl +1 -1
  32. package/dist/system-skills/resource-dsl/WikiPages/files/manifest.xnl +1 -1
  33. package/dist/system-skills/resource-dsl/WikiPages/index/manifest.xnl +1 -1
  34. package/dist/system-skills/resource-dsl/WikiPages/language/manifest.xnl +1 -1
  35. package/dist/system-skills/resource-dsl/WikiPages/migration/manifest.xnl +1 -1
  36. package/dist/system-skills/resource-dsl/WikiPages/projections/manifest.xnl +1 -1
  37. package/dist/system-skills/resource-dsl/WikiPages/skill-capsules/manifest.xnl +1 -1
  38. package/dist/system-skills/resource-dsl/documents.md +33 -17
  39. package/dist/system-skills/resource-dsl/examples/function.xnl +1 -1
  40. package/dist/system-skills/resource-dsl/examples/kind-definition.xnl +15 -3
  41. package/dist/system-skills/resource-dsl/examples/resource-mappings.xnl +1 -1
  42. package/dist/system-skills/resource-dsl/examples/resource-package.xnl +1 -1
  43. package/dist/system-skills/resource-dsl/examples/skill-capsule-authoring.xnl +1 -1
  44. package/dist/system-skills/resource-dsl/examples/skill-capsule-devops.xnl +1 -1
  45. package/dist/system-skills/resource-dsl/examples/skill-capsule-kind-definition.xnl +14 -3
  46. package/dist/system-skills/resource-dsl/examples/skill-capsule-resource-dsl.xnl +1 -1
  47. package/dist/system-skills/resource-dsl/examples/skill-capsule-run.xnl +1 -1
  48. package/dist/system-skills/resource-dsl/index.md +5 -5
  49. package/dist/system-skills/resource-dsl/language.md +10 -8
  50. package/dist/system-skills/resource-dsl/manifest.xnl +1 -1
  51. package/dist/system-skills/resource-dsl/migration.md +6 -3
  52. package/dist/system-skills/resource-dsl/projections.md +7 -6
  53. package/dist/system-skills/resource-dsl/skill-capsules.md +8 -7
  54. package/dist/system-skills/sys-halfcode-resource-dsl.plan.js +84 -59
  55. package/dist/testing.js +1 -1
  56. package/package.json +6 -2
  57. package/dist/index-CDIT65pI.d.ts +0 -192
  58. package/dist/resource-core-C8IonwKw.d.ts +0 -41
  59. package/dist/src-B87eJQat.js +0 -39
@@ -1,8 +1,92 @@
1
1
  import { n as wordToString, t as parseXnl } from "./dist-Bkv7YeVi.js";
2
- import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
3
- import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
2
+ import { lstat, readFile, readdir, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
4
  import { parse } from "yaml";
5
5
  import { createHash } from "node:crypto";
6
+ //#region ../resource-core/src/resource-package-read-port.ts
7
+ var ResourcePackageSourcePathError = class extends TypeError {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ResourcePackageSourcePathError";
11
+ }
12
+ };
13
+ function canonicalResourcePackageSourcePath(input) {
14
+ if (!input.startsWith("/")) throw new ResourcePackageSourcePathError("ResourcePackage source paths must be POSIX absolute");
15
+ if (input.includes("\\") || input.includes("\0")) throw new ResourcePackageSourcePathError("ResourcePackage source paths must not contain backslashes or NUL");
16
+ if (input === "/") return input;
17
+ if (input.endsWith("/") || input.includes("//")) throw new ResourcePackageSourcePathError("ResourcePackage source paths must not contain empty segments");
18
+ const segments = input.slice(1).split("/");
19
+ if (segments.some((segment) => segment === "." || segment === ".." || segment.length === 0)) throw new ResourcePackageSourcePathError("ResourcePackage source paths must not contain dot segments");
20
+ return `/${segments.join("/")}`;
21
+ }
22
+ function joinResourcePackageSourcePath(base, ...segments) {
23
+ const canonicalBase = canonicalResourcePackageSourcePath(base);
24
+ const additions = [];
25
+ for (const segment of segments) {
26
+ if (!segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || segment.includes("\0")) throw new ResourcePackageSourcePathError("ResourcePackage source path segments must be plain names");
27
+ additions.push(segment);
28
+ }
29
+ if (additions.length === 0) return canonicalBase;
30
+ return canonicalBase === "/" ? `/${additions.join("/")}` : `${canonicalBase}/${additions.join("/")}`;
31
+ }
32
+ function dirnameResourcePackageSourcePath(sourcePath) {
33
+ const canonical = canonicalResourcePackageSourcePath(sourcePath);
34
+ if (canonical === "/") return canonical;
35
+ const boundary = canonical.lastIndexOf("/");
36
+ return boundary === 0 ? "/" : canonical.slice(0, boundary);
37
+ }
38
+ function relativeResourcePackageSourcePath(rootPath, sourcePath) {
39
+ const root = canonicalResourcePackageSourcePath(rootPath);
40
+ const source = canonicalResourcePackageSourcePath(sourcePath);
41
+ if (source === root) return "";
42
+ const prefix = root === "/" ? "/" : `${root}/`;
43
+ return source.startsWith(prefix) ? source.slice(prefix.length) : void 0;
44
+ }
45
+ //#endregion
46
+ //#region ../resource-core/src/directory-resource-package-read-port.ts
47
+ function entryKind(entry) {
48
+ if (entry.isSymbolicLink()) return "symlink";
49
+ if (entry.isFile()) return "file";
50
+ if (entry.isDirectory()) return "directory";
51
+ return "other";
52
+ }
53
+ async function createDirectoryResourcePackageReadPort(rootDir) {
54
+ const requestedRoot = path.resolve(rootDir);
55
+ const boundary = await realpath(requestedRoot).catch(() => requestedRoot);
56
+ const lexicalPhysicalPath = (sourcePath) => {
57
+ const relative = relativeResourcePackageSourcePath("/", canonicalResourcePackageSourcePath(sourcePath));
58
+ if (relative === void 0) throw new TypeError("ResourcePackage source path is outside the directory port");
59
+ return path.resolve(boundary, relative);
60
+ };
61
+ const containedRealPath = async (sourcePath) => {
62
+ const candidate = lexicalPhysicalPath(sourcePath);
63
+ const resolved = await realpath(candidate).catch(() => void 0);
64
+ if (!resolved) return void 0;
65
+ if (resolved !== boundary && !resolved.startsWith(`${boundary}${path.sep}`)) return void 0;
66
+ return resolved;
67
+ };
68
+ return Object.freeze({
69
+ async stat(sourcePath) {
70
+ const candidate = lexicalPhysicalPath(sourcePath);
71
+ const info = await lstat(candidate).catch(() => void 0);
72
+ return info ? Object.freeze({ kind: entryKind(info) }) : void 0;
73
+ },
74
+ async readDirectory(sourcePath) {
75
+ const resolved = await containedRealPath(sourcePath);
76
+ if (!resolved) return void 0;
77
+ return (await readdir(resolved, { withFileTypes: true }).catch(() => void 0))?.map((entry) => Object.freeze({
78
+ name: entry.name,
79
+ kind: entryKind(entry)
80
+ }));
81
+ },
82
+ async readBytes(sourcePath) {
83
+ const resolved = await containedRealPath(sourcePath);
84
+ if (!resolved) return void 0;
85
+ return readFile(resolved).catch(() => void 0);
86
+ }
87
+ });
88
+ }
89
+ //#endregion
6
90
  //#region ../resource-core/src/canonical.ts
7
91
  function compareCodeUnits(left, right) {
8
92
  if (left < right) return -1;
@@ -362,14 +446,14 @@ function contentLocation(resourceId, key) {
362
446
  return `content:${resourceId || "unnamed"}${key === void 0 ? "" : `:${key}`}`;
363
447
  }
364
448
  //#endregion
365
- //#region ../resource-core/src/loaded-resource-tree-brand.ts
366
- const authenticLoadedResourceTrees = /* @__PURE__ */ new WeakSet();
367
- function markLoadedResourceTreeAuthentic(tree) {
368
- authenticLoadedResourceTrees.add(tree);
449
+ //#region ../resource-core/src/authored-resource-tree-brand.ts
450
+ const authenticAuthoredResourceTrees = /* @__PURE__ */ new WeakSet();
451
+ function markAuthoredResourceTreeAuthentic(tree) {
452
+ authenticAuthoredResourceTrees.add(tree);
369
453
  return tree;
370
454
  }
371
- function isLoadedResourceTreeAuthentic(tree) {
372
- return authenticLoadedResourceTrees.has(tree);
455
+ function isAuthoredResourceTreeAuthentic(tree) {
456
+ return authenticAuthoredResourceTrees.has(tree);
373
457
  }
374
458
  //#endregion
375
459
  //#region ../resource-core/src/readonly-map.ts
@@ -404,11 +488,148 @@ function readonlyMap(entries) {
404
488
  });
405
489
  }
406
490
  //#endregion
491
+ //#region ../resource-core/src/resource-version-contracts.ts
492
+ const RESOURCE_ENVELOPE_VERSION = "halfcode.resource-envelope/v1";
493
+ const CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS = Object.freeze({
494
+ ResourcePackage: 1,
495
+ KindDefinition: 1
496
+ });
497
+ /**
498
+ * Canonical, compiler-owned facts enforced by the v1 resource-envelope decoder.
499
+ * Consumers may fingerprint this descriptor, but must not restate it.
500
+ */
501
+ const RESOURCE_ENVELOPE_CONTRACT = Object.freeze({
502
+ envelopeVersion: RESOURCE_ENVELOPE_VERSION,
503
+ requiredFields: Object.freeze(["envelopeVersion", "specVersion"]),
504
+ optionalFields: Object.freeze(["lifecycle"]),
505
+ removedFields: Object.freeze(["apiVersion", "version"]),
506
+ removedFieldScopes: Object.freeze(["resource-root", "metadata"]),
507
+ specVersion: Object.freeze({
508
+ type: "integer",
509
+ minimum: 1,
510
+ maximum: Number.MAX_SAFE_INTEGER
511
+ }),
512
+ lifecycle: Object.freeze({ type: "string" }),
513
+ outputFields: Object.freeze([
514
+ "envelopeVersion",
515
+ "specVersion",
516
+ "lifecycle"
517
+ ]),
518
+ bootstrapKinds: Object.freeze({
519
+ ResourcePackage: Object.freeze({ writerSpecVersion: CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS.ResourcePackage }),
520
+ KindDefinition: Object.freeze({ writerSpecVersion: CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS.KindDefinition })
521
+ }),
522
+ catalogTraversal: Object.freeze({
523
+ authority: "resource-envelope",
524
+ containerTag: "Catalogs",
525
+ entryTags: Object.freeze([
526
+ "Catalog",
527
+ "FileResourceCatalog",
528
+ "DirectoryResourceCatalog",
529
+ "ManifestResourceCatalog"
530
+ ]),
531
+ sourceShapes: Object.freeze([
532
+ "single-file",
533
+ "directory",
534
+ "manifest"
535
+ ]),
536
+ shapeFields: Object.freeze([
537
+ "kind",
538
+ "shape",
539
+ "root",
540
+ "entry",
541
+ "scope"
542
+ ]),
543
+ phase: "authored-load-before-business-resolution"
544
+ }),
545
+ authorityFormats: Object.freeze({
546
+ packageManifest: "xnl",
547
+ resourceDocuments: Object.freeze(["xnl", "markdown"]),
548
+ markdownFrontmatter: "yaml",
549
+ markdownBody: "all-source-text-after-closing-frontmatter-and-one-separator-newline"
550
+ }),
551
+ identityProjection: Object.freeze({
552
+ xnl: "root-#id",
553
+ markdown: "frontmatter-metadata.fqn",
554
+ recordFields: Object.freeze(["resourceId", "fqn"])
555
+ }),
556
+ authoredSpecProjection: Object.freeze({
557
+ fields: Object.freeze([
558
+ "properties",
559
+ "body",
560
+ "subdomains",
561
+ "text"
562
+ ]),
563
+ resourceMetadataExcluded: true,
564
+ markdownBodyField: "text"
565
+ })
566
+ });
567
+ const RESOURCE_ENVELOPE_FINGERPRINT = digestCanonical(RESOURCE_ENVELOPE_CONTRACT);
568
+ function isSpecVersion(value) {
569
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
570
+ }
571
+ //#endregion
572
+ //#region ../resource-core/src/resource-envelope.ts
573
+ function decodeResourceMetadata(input) {
574
+ if ([input.fields, ...input.removedFieldContainers ?? []].some((container) => hasOwn(container, "apiVersion") || hasOwn(container, "version"))) return failure("RESOURCE_METADATA_FIELD_REMOVED", input, `${input.source} resources must not declare removed apiVersion or version metadata.`);
575
+ if (!hasOwn(input.fields, "envelopeVersion") || !hasOwn(input.fields, "specVersion")) return failure("RESOURCE_METADATA_FIELD_MISSING", input, `${input.source} resources require envelopeVersion and specVersion.`);
576
+ const envelopeVersion = input.fields.envelopeVersion;
577
+ if (typeof envelopeVersion !== "string" || envelopeVersion !== "halfcode.resource-envelope/v1") return failure("RESOURCE_ENVELOPE_VERSION_UNSUPPORTED", input, `${input.source} resource envelopeVersion must be '${RESOURCE_ENVELOPE_VERSION}'.`);
578
+ if (!isSpecVersion(input.fields.specVersion)) return failure("RESOURCE_SPEC_VERSION_INVALID", input, `${input.source} resource specVersion must be a positive safe integer.`);
579
+ if (input.lifecycle !== void 0 && typeof input.lifecycle !== "string") return failure("RESOURCE_METADATA_FIELD_INVALID", input, `${input.source} resource lifecycle must be a string when present.`);
580
+ return Object.freeze({ metadata: Object.freeze({
581
+ envelopeVersion,
582
+ specVersion: input.fields.specVersion,
583
+ ...input.lifecycle === void 0 ? {} : { lifecycle: input.lifecycle }
584
+ }) });
585
+ }
586
+ function failure(code, input, message) {
587
+ return Object.freeze({ diagnostic: Object.freeze({
588
+ code,
589
+ location: input.location,
590
+ message
591
+ }) });
592
+ }
593
+ function hasOwn(value, key) {
594
+ return Object.prototype.hasOwnProperty.call(value, key);
595
+ }
596
+ //#endregion
407
597
  //#region ../resource-core/src/xnl-loader.ts
598
+ async function safeStat(port, sourcePath) {
599
+ try {
600
+ return await port.stat(sourcePath);
601
+ } catch {
602
+ return;
603
+ }
604
+ }
605
+ async function safeReadDirectory(port, sourcePath) {
606
+ try {
607
+ return await port.readDirectory(sourcePath);
608
+ } catch {
609
+ return;
610
+ }
611
+ }
612
+ async function safeReadBytes(port, sourcePath) {
613
+ try {
614
+ return await port.readBytes(sourcePath);
615
+ } catch {
616
+ return;
617
+ }
618
+ }
408
619
  async function buildXnlResourceTree(options) {
409
- const requestedRoot = resolve(options.rootDir);
620
+ let rootPath;
621
+ try {
622
+ rootPath = canonicalResourcePackageSourcePath(options.rootPath);
623
+ } catch {
624
+ return { diagnostics: [{
625
+ code: "RESOURCE_SOURCE_PATH_INVALID",
626
+ location: "vfs://@/",
627
+ message: "ResourcePackage read-port rootPath must be canonical POSIX absolute."
628
+ }] };
629
+ }
410
630
  const context = {
411
- rootDir: await realpath(requestedRoot).catch(() => requestedRoot),
631
+ rootPath,
632
+ port: options.port,
412
633
  diagnostics: [],
413
634
  kindDefinitions: /* @__PURE__ */ new Map(),
414
635
  resources: [],
@@ -417,7 +638,13 @@ async function buildXnlResourceTree(options) {
417
638
  recordAuthorityDigests: /* @__PURE__ */ new WeakMap(),
418
639
  recordContributions: /* @__PURE__ */ new WeakMap()
419
640
  };
420
- const manifest = await loadManifest(resolve(context.rootDir, options.manifestPath), "manifest", context, true);
641
+ const manifestFile = resolvePackageRelativePath(context.rootPath, options.manifestPath);
642
+ if (!manifestFile) return { diagnostics: [{
643
+ code: "RESOURCE_SOURCE_PATH_INVALID",
644
+ location: `vfs://@/${options.manifestPath}`,
645
+ message: "ResourcePackage manifestPath must be a canonical package-relative file path."
646
+ }] };
647
+ const manifest = await loadManifest(manifestFile, "manifest", context, true);
421
648
  if (!manifest || context.diagnostics.length > 0) return { diagnostics: context.diagnostics };
422
649
  const byKind = /* @__PURE__ */ new Map();
423
650
  for (const resource of context.resources) {
@@ -430,7 +657,8 @@ async function buildXnlResourceTree(options) {
430
657
  const contentIdentities = readonlyMap(context.resources.map((resource) => [resource.resourceId, loadedContentIdentity(resource, context)]).sort(([left], [right]) => compareCodeUnits(left, right)));
431
658
  return {
432
659
  diagnostics: [],
433
- tree: markLoadedResourceTreeAuthentic(Object.freeze({
660
+ tree: markAuthoredResourceTreeAuthentic(Object.freeze({
661
+ stage: "authored",
434
662
  manifest,
435
663
  registry: Object.freeze({
436
664
  byKind: frozenByKind,
@@ -442,10 +670,18 @@ async function buildXnlResourceTree(options) {
442
670
  };
443
671
  }
444
672
  async function loadManifest(filePath, sourceShape, context, isRoot) {
445
- const record = await loadXnlRecord(filePath, sourceShape, context);
673
+ let record = await loadXnlRecord(filePath, sourceShape, context);
446
674
  if (!record) return void 0;
675
+ if (isRoot && record.kind === "ResourcePackage" && record.metadata.specVersion !== CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS.ResourcePackage) {
676
+ context.diagnostics.push({
677
+ code: "CORE_RESOURCE_SPEC_VERSION_UNSUPPORTED",
678
+ location: record.documentUri,
679
+ message: `ResourcePackage bootstrap only admits writer specVersion ${CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS.ResourcePackage}; received ${record.metadata.specVersion}.`
680
+ });
681
+ return;
682
+ }
447
683
  const catalogs = readCatalogs(record, context.diagnostics);
448
- for (const catalog of catalogs.filter((item) => item.kind === "KindDefinition")) await loadKindDefinitions(catalog, dirname(filePath), context);
684
+ for (const catalog of catalogs.filter((item) => item.kind === "KindDefinition")) await loadKindDefinitions(catalog, dirnameResourcePackageSourcePath(filePath), context);
449
685
  if (isRoot && record.kind !== "ResourcePackage") {
450
686
  const definition = context.kindDefinitions.get(record.kind);
451
687
  if (!definition) {
@@ -464,12 +700,13 @@ async function loadManifest(filePath, sourceShape, context, isRoot) {
464
700
  });
465
701
  return;
466
702
  }
467
- validateResourceApiVersion(record, definition, context.diagnostics);
468
- await validateRequiredFiles(dirname(filePath), record, definition, context.diagnostics);
703
+ record = bindSubject(record, definition.subjectFqn, context);
704
+ await validateRequiredFiles(dirnameResourcePackageSourcePath(filePath), record, definition, context);
469
705
  validateIdentity(record, context);
470
706
  context.resources.push(record);
471
707
  }
472
- for (const catalog of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(catalog, dirname(filePath), context);
708
+ if (isRoot && record.kind === "ResourcePackage") record = bindSubject(record, "Halfcode.ResourceKind.ResourcePackage", context);
709
+ for (const catalog of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(catalog, dirnameResourcePackageSourcePath(filePath), context);
473
710
  if (!isRoot) {
474
711
  validateIdentity(record, context);
475
712
  context.resources.push(record);
@@ -488,6 +725,9 @@ async function loadKindDefinitions(catalog, manifestDir, context) {
488
725
  const definition = normalizeKindDefinition(record, context.diagnostics);
489
726
  if (!definition) continue;
490
727
  context.kindDefinitions.set(definition.resourceKind, definition);
728
+ const authoredDefinition = bindSubject(record, "Halfcode.ResourceKind.KindDefinition", context);
729
+ validateIdentity(authoredDefinition, context);
730
+ context.resources.push(authoredDefinition);
491
731
  }
492
732
  }
493
733
  async function loadCatalog(catalog, manifestDir, context) {
@@ -511,18 +751,18 @@ async function loadCatalog(catalog, manifestDir, context) {
511
751
  const files = await catalogFiles(catalog, manifestDir, context);
512
752
  for (const file of files) {
513
753
  if (catalog.shape === "manifest") {
514
- const record = await loadXnlRecord(file, "manifest", context);
754
+ let record = await loadXnlRecord(file, "manifest", context);
515
755
  if (!record) continue;
516
756
  if (record.kind !== catalog.kind) {
517
757
  context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
518
758
  continue;
519
759
  }
520
- validateResourceApiVersion(record, definition, context.diagnostics);
521
- await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
760
+ record = bindSubject(record, definition.subjectFqn, context);
761
+ await validateRequiredFiles(dirnameResourcePackageSourcePath(file), record, definition, context);
522
762
  validateIdentity(record, context);
523
763
  context.resources.push(record);
524
764
  const catalogs = readCatalogs(record, context.diagnostics);
525
- for (const nested of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(nested, dirname(file), context);
765
+ for (const nested of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(nested, dirnameResourcePackageSourcePath(file), context);
526
766
  continue;
527
767
  }
528
768
  if (definition.documentCardinality === "many" && catalog.shape !== "single-file") {
@@ -534,22 +774,34 @@ async function loadCatalog(catalog, manifestDir, context) {
534
774
  continue;
535
775
  }
536
776
  const records = await loadResourceRecords(file, catalog.shape, context, definition.documentCardinality);
537
- for (const record of records) {
777
+ for (let record of records) {
538
778
  if (record.kind !== catalog.kind) {
539
779
  context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
540
780
  continue;
541
781
  }
542
- validateResourceApiVersion(record, definition, context.diagnostics);
543
- await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
782
+ record = bindSubject(record, definition.subjectFqn, context);
783
+ await validateRequiredFiles(dirnameResourcePackageSourcePath(file), record, definition, context);
544
784
  validateIdentity(record, context);
545
785
  context.resources.push(record);
546
786
  }
547
787
  }
548
788
  }
789
+ function bindSubject(record, subjectFqn, context) {
790
+ if (record.subjectFqn === subjectFqn) return record;
791
+ const bound = Object.freeze({
792
+ ...record,
793
+ subjectFqn
794
+ });
795
+ const authorityDigest = context.recordAuthorityDigests.get(record);
796
+ if (authorityDigest) context.recordAuthorityDigests.set(bound, authorityDigest);
797
+ const contributions = context.recordContributions.get(record);
798
+ if (contributions) context.recordContributions.set(bound, contributions);
799
+ return bound;
800
+ }
549
801
  async function catalogFiles(catalog, manifestDir, context) {
550
802
  const root = await resolveCatalogDirectory(catalog.root, manifestDir, catalog.location, context);
551
803
  if (!root) return [];
552
- const entries = await readdir(root, { withFileTypes: true }).catch(() => void 0);
804
+ const entries = await safeReadDirectory(context.port, root);
553
805
  if (!entries) {
554
806
  context.diagnostics.push({
555
807
  code: "RESOURCE_CATALOG_ROOT_MISSING",
@@ -558,6 +810,18 @@ async function catalogFiles(catalog, manifestDir, context) {
558
810
  });
559
811
  return [];
560
812
  }
813
+ const entryNames = /* @__PURE__ */ new Set();
814
+ for (const entry of entries) {
815
+ if (!entry || typeof entry.name !== "string" || !isPlainSourceEntryName(entry.name) || !isResourcePackageEntryKind(entry.kind) || entryNames.has(entry.name)) {
816
+ context.diagnostics.push({
817
+ code: "RESOURCE_SOURCE_ENTRY_INVALID",
818
+ location: catalog.location,
819
+ message: "ResourcePackage read port returned a non-canonical or duplicate directory entry."
820
+ });
821
+ return [];
822
+ }
823
+ entryNames.add(entry.name);
824
+ }
561
825
  if (catalog.shape === "single-file") {
562
826
  if (catalog.entry) {
563
827
  if (!isPlainEntry(catalog.entry) || !isSingleFileExtension(catalog.entry)) {
@@ -568,9 +832,9 @@ async function catalogFiles(catalog, manifestDir, context) {
568
832
  });
569
833
  return [];
570
834
  }
571
- return [join(root, catalog.entry)];
835
+ return [joinResourcePackageSourcePath(root, catalog.entry)];
572
836
  }
573
- return entries.filter((entry) => entry.isFile() && isSingleFileExtension(entry.name)).sort((left, right) => compareCodeUnits(left.name, right.name)).map((entry) => join(root, entry.name));
837
+ return entries.filter((entry) => entry.kind === "file" && isSingleFileExtension(entry.name)).sort((left, right) => compareCodeUnits(left.name, right.name)).map((entry) => joinResourcePackageSourcePath(root, entry.name));
574
838
  }
575
839
  if (!catalog.entry || !isPlainEntry(catalog.entry)) {
576
840
  context.diagnostics.push({
@@ -581,33 +845,30 @@ async function catalogFiles(catalog, manifestDir, context) {
581
845
  return [];
582
846
  }
583
847
  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
- });
848
+ const directEntry = joinResourcePackageSourcePath(root, catalog.entry);
849
+ const directStat = await safeStat(context.port, directEntry);
589
850
  if (!directStat) return [directEntry];
590
- if (directStat.isSymbolicLink() || !directStat.isFile()) {
851
+ if (directStat.kind !== "file") {
591
852
  context.diagnostics.push({
592
853
  code: "RESOURCE_CATALOG_ENTRY_INVALID",
593
- location: documentUriFor(context.rootDir, directEntry),
854
+ location: documentUriFor(context.rootPath, directEntry),
594
855
  message: "A root-scoped directory catalog entry must be a regular non-symbolic-link XNL file."
595
856
  });
596
857
  return [];
597
858
  }
598
859
  return [directEntry];
599
860
  }
600
- return entries.filter((entry) => entry.isDirectory()).sort((left, right) => compareCodeUnits(left.name, right.name)).map((entry) => join(root, entry.name, catalog.entry));
861
+ return entries.filter((entry) => entry.kind === "directory").sort((left, right) => compareCodeUnits(left.name, right.name)).map((entry) => joinResourcePackageSourcePath(root, entry.name, catalog.entry));
601
862
  }
602
863
  async function loadXnlRecord(filePath, sourceShape, context) {
603
864
  return (await loadXnlRecords(filePath, sourceShape, context, "one"))[0];
604
865
  }
605
866
  async function loadResourceRecords(filePath, sourceShape, context, documentCardinality) {
606
- if (extname(filePath).toLowerCase() !== ".md") return loadXnlRecords(filePath, sourceShape, context, documentCardinality);
867
+ if (!filePath.toLowerCase().endsWith(".md")) return loadXnlRecords(filePath, sourceShape, context, documentCardinality);
607
868
  if (sourceShape !== "single-file" || documentCardinality !== "one") {
608
869
  context.diagnostics.push({
609
870
  code: "RESOURCE_MARKDOWN_SOURCE_SHAPE_INVALID",
610
- location: documentUriFor(context.rootDir, filePath),
871
+ location: documentUriFor(context.rootPath, filePath),
611
872
  message: "Markdown resource authority requires a single-file catalog with documentCardinality one."
612
873
  });
613
874
  return [];
@@ -616,7 +877,7 @@ async function loadResourceRecords(filePath, sourceShape, context, documentCardi
616
877
  return record ? [record] : [];
617
878
  }
618
879
  async function loadMarkdownRecord(filePath, context) {
619
- const documentUri = documentUriFor(context.rootDir, filePath);
880
+ const documentUri = documentUriFor(context.rootPath, filePath);
620
881
  const authority = await readAuthorityFile(filePath, documentUri, context);
621
882
  if (!authority) return void 0;
622
883
  const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(authority.source);
@@ -650,17 +911,27 @@ async function loadMarkdownRecord(filePath, context) {
650
911
  const metadataValue = parsed.metadata;
651
912
  const specValue = parsed.spec;
652
913
  const kind = stringField(parsed.kind);
653
- const apiVersion = stringField(parsed.apiVersion);
654
914
  const metadataObject = isPlainObject(metadataValue) ? metadataValue : void 0;
655
915
  const resourceId = metadataObject ? stringField(metadataObject.fqn) : void 0;
656
- if (!kind || !apiVersion || !resourceId || specValue !== void 0 && !isPlainObject(specValue)) {
916
+ if (!kind || !resourceId || specValue !== void 0 && !isPlainObject(specValue)) {
657
917
  context.diagnostics.push({
658
918
  code: "RESOURCE_MARKDOWN_METADATA_INVALID",
659
919
  location: documentUri,
660
- message: "Markdown resource frontmatter requires apiVersion, kind, metadata.fqn and an optional object spec."
920
+ message: "Markdown resource frontmatter requires kind, metadata.fqn and an optional object spec."
661
921
  });
662
922
  return;
663
923
  }
924
+ const decoded = decodeResourceMetadata({
925
+ fields: parsed,
926
+ removedFieldContainers: metadataObject ? [metadataObject] : [],
927
+ lifecycle: metadataObject?.lifecycle,
928
+ location: documentUri,
929
+ source: "Markdown"
930
+ });
931
+ if ("diagnostic" in decoded) {
932
+ context.diagnostics.push(decoded.diagnostic);
933
+ return;
934
+ }
664
935
  let properties;
665
936
  try {
666
937
  properties = Object.freeze(normalizeExternalMap(specValue ?? {}));
@@ -672,34 +943,33 @@ async function loadMarkdownRecord(filePath, context) {
672
943
  });
673
944
  return;
674
945
  }
675
- const version = metadataObject ? stringField(metadataObject.version) : void 0;
676
- const lifecycle = metadataObject ? stringField(metadataObject.lifecycle) : void 0;
677
946
  const name = metadataObject ? stringField(metadataObject.name) : void 0;
678
947
  const description = stringField(properties.description);
679
948
  const node = Object.freeze({
680
949
  tag: kind,
681
950
  resourceId,
682
951
  metadata: Object.freeze({
683
- apiVersion,
684
- ...version ? { version } : {}
952
+ envelopeVersion: decoded.metadata.envelopeVersion,
953
+ specVersion: decoded.metadata.specVersion
685
954
  }),
686
955
  properties,
687
956
  body: Object.freeze([]),
688
- subdomains: Object.freeze({})
957
+ subdomains: Object.freeze({}),
958
+ text: authority.source.slice(match[0].length)
689
959
  });
690
960
  const record = Object.freeze({
961
+ stage: "authored",
691
962
  kind,
963
+ subjectFqn: kind,
692
964
  resourceId,
693
965
  fqn: resourceId,
694
966
  ...name ? { name } : {},
695
967
  ...description ? { description } : {},
696
- metadata: Object.freeze({
697
- apiVersion,
698
- ...lifecycle ? { lifecycle } : {},
699
- ...version ? { version } : {}
700
- }),
968
+ metadata: decoded.metadata,
969
+ authoredSpec: authoredSpecFromNode(node),
970
+ sourceContentDigest: authority.authorityDigest,
701
971
  sourceShape: "single-file",
702
- logicalPath: relative(context.rootDir, filePath).split(sep).join("/"),
972
+ logicalPath: logicalPathFor(context.rootPath, filePath),
703
973
  documentUri,
704
974
  format: "markdown",
705
975
  node
@@ -708,7 +978,7 @@ async function loadMarkdownRecord(filePath, context) {
708
978
  return record;
709
979
  }
710
980
  async function loadXnlRecords(filePath, sourceShape, context, documentCardinality) {
711
- const documentUri = documentUriFor(context.rootDir, filePath);
981
+ const documentUri = documentUriFor(context.rootPath, filePath);
712
982
  const authority = await readAuthorityFile(filePath, documentUri, context);
713
983
  if (!authority) return [];
714
984
  let roots;
@@ -740,16 +1010,24 @@ async function loadXnlRecords(filePath, sourceShape, context, documentCardinalit
740
1010
  return [];
741
1011
  }
742
1012
  return roots.flatMap((root) => {
743
- const record = resourceRecordFromRoot(root, filePath, sourceShape, context);
1013
+ const record = resourceRecordFromRoot(root, filePath, sourceShape, authority.authorityDigest, context);
744
1014
  if (record) context.recordAuthorityDigests.set(record, authority.authorityDigest);
745
1015
  return record ? [record] : [];
746
1016
  });
747
1017
  }
748
1018
  async function readAuthorityFile(filePath, documentUri, context) {
749
- const canonicalPath = resolve(filePath);
1019
+ const canonicalPath = canonicalResourcePackageSourcePath(filePath);
750
1020
  let pending = context.authorityFiles.get(canonicalPath);
751
1021
  if (!pending) {
752
- pending = readFile(canonicalPath).then((bytes) => {
1022
+ pending = safeReadBytes(context.port, canonicalPath).then((bytes) => {
1023
+ if (!bytes) {
1024
+ context.diagnostics.push({
1025
+ code: "RESOURCE_FILE_MISSING",
1026
+ location: documentUri,
1027
+ message: "Resource file does not exist."
1028
+ });
1029
+ return;
1030
+ }
753
1031
  try {
754
1032
  return Object.freeze({
755
1033
  source: new TextDecoder("utf-8", { fatal: true }).decode(bytes),
@@ -783,36 +1061,38 @@ function loadedContentIdentity(resource, context) {
783
1061
  contributions: context.recordContributions.get(resource) ?? []
784
1062
  });
785
1063
  }
786
- function resourceRecordFromRoot(root, filePath, sourceShape, context) {
787
- const documentUri = documentUriFor(context.rootDir, filePath);
1064
+ function resourceRecordFromRoot(root, filePath, sourceShape, sourceContentDigest, context) {
1065
+ const documentUri = documentUriFor(context.rootPath, filePath);
788
1066
  const node = normalizeNode(root);
789
1067
  const resourceId = node.resourceId;
790
- const apiVersion = asString(node.metadata.apiVersion);
791
- const lifecycle = asString(node.properties.lifecycle);
792
1068
  const description = asString(node.properties.description) ?? node.subdomains.Description?.text;
793
1069
  if (!resourceId) context.diagnostics.push({
794
1070
  code: "RESOURCE_IDENTITY_MISSING",
795
1071
  location: documentUri,
796
1072
  message: "XNL resource roots must declare a #id."
797
1073
  });
798
- if (!apiVersion) context.diagnostics.push({
799
- code: "RESOURCE_METADATA_FIELD_MISSING",
1074
+ if (!resourceId) return void 0;
1075
+ const decoded = decodeResourceMetadata({
1076
+ fields: node.metadata,
1077
+ lifecycle: node.properties.lifecycle,
800
1078
  location: documentUri,
801
- message: "XNL resources require metadata apiVersion."
802
- });
803
- if (!resourceId || !apiVersion) return void 0;
804
- const metadata = Object.freeze({
805
- apiVersion,
806
- ...lifecycle ? { lifecycle } : {},
807
- version: asString(node.metadata.version)
1079
+ source: "XNL"
808
1080
  });
809
- const logicalPath = relative(context.rootDir, filePath).split(sep).join("/");
1081
+ if ("diagnostic" in decoded) {
1082
+ context.diagnostics.push(decoded.diagnostic);
1083
+ return;
1084
+ }
1085
+ const logicalPath = logicalPathFor(context.rootPath, filePath);
810
1086
  return Object.freeze({
1087
+ stage: "authored",
811
1088
  kind: node.tag,
1089
+ subjectFqn: node.tag,
812
1090
  resourceId,
813
1091
  fqn: resourceId,
814
1092
  ...description ? { description } : {},
815
- metadata,
1093
+ metadata: decoded.metadata,
1094
+ authoredSpec: authoredSpecFromNode(node),
1095
+ sourceContentDigest,
816
1096
  sourceShape,
817
1097
  logicalPath,
818
1098
  documentUri,
@@ -820,6 +1100,14 @@ function resourceRecordFromRoot(root, filePath, sourceShape, context) {
820
1100
  node
821
1101
  });
822
1102
  }
1103
+ function authoredSpecFromNode(node) {
1104
+ return Object.freeze({
1105
+ properties: node.properties,
1106
+ body: node.body,
1107
+ subdomains: node.subdomains,
1108
+ ...node.text === void 0 ? {} : { text: node.text }
1109
+ });
1110
+ }
823
1111
  function normalizeNode(node) {
824
1112
  const subdomains = {};
825
1113
  if (node.kind === "DataElement" && node.extend) for (const name of node.extend.order) {
@@ -894,69 +1182,127 @@ function catalogShapeForTag(tag) {
894
1182
  if (tag === "ManifestResourceCatalog") return "manifest";
895
1183
  }
896
1184
  function normalizeKindDefinition(record, diagnostics) {
1185
+ if (record.metadata.specVersion !== CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS.KindDefinition) {
1186
+ diagnostics.push({
1187
+ code: "CORE_RESOURCE_SPEC_VERSION_UNSUPPORTED",
1188
+ location: record.documentUri,
1189
+ message: `KindDefinition bootstrap only admits writer specVersion ${CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS.KindDefinition}; received ${record.metadata.specVersion}.`
1190
+ });
1191
+ return;
1192
+ }
1193
+ if (Object.prototype.hasOwnProperty.call(record.node.properties, "currentApiVersion") || Object.prototype.hasOwnProperty.call(record.node.properties, "supportedApiVersions") || Object.prototype.hasOwnProperty.call(record.node.properties, "currentSpecVersion") || Object.prototype.hasOwnProperty.call(record.node.properties, "supportedSpecVersions")) {
1194
+ diagnostics.push({
1195
+ code: "KIND_DEFINITION_VERSION_FIELDS_REMOVED",
1196
+ location: record.documentUri,
1197
+ message: "KindDefinition must not declare current/supported API or spec version fields; declare exact SpecRevisions instead."
1198
+ });
1199
+ return;
1200
+ }
897
1201
  const resourceKind = asString(record.node.properties.resourceKind) ?? record.resourceId.split(".").at(-1);
1202
+ const subjectFqn = asString(record.node.properties.subjectFqn);
898
1203
  const shapes = record.node.properties.sourceShapes;
899
- const sourceShapes = Array.isArray(shapes) ? shapes.filter(isXnlShape) : [];
900
- const currentApiVersion = asString(record.node.properties.currentApiVersion);
901
- const versions = record.node.properties.supportedApiVersions;
902
- const supportedApiVersions = Array.isArray(versions) ? versions.filter((value) => typeof value === "string" && Boolean(value.trim())) : [];
1204
+ const sourceShapes = Array.isArray(shapes) ? [...new Set(shapes.filter(isXnlShape))].sort(compareCodeUnits) : [];
903
1205
  const cardinalityValue = asString(record.node.properties.documentCardinality) ?? "one";
904
- if (!resourceKind || sourceShapes.length === 0 || !currentApiVersion || supportedApiVersions.length === 0 || !isDocumentCardinality(cardinalityValue)) {
1206
+ if (!resourceKind || !subjectFqn || sourceShapes.length === 0 || !isDocumentCardinality(cardinalityValue)) {
905
1207
  diagnostics.push({
906
1208
  code: "KIND_DEFINITION_INVALID",
907
1209
  location: record.documentUri,
908
- message: "KindDefinition requires resourceKind (or an id suffix), sourceShapes, currentApiVersion, and supportedApiVersions."
1210
+ message: "KindDefinition requires resourceKind, subjectFqn, sourceShapes and one or more exact SpecRevisions."
909
1211
  });
910
1212
  return;
911
1213
  }
912
- if (!supportedApiVersions.includes(currentApiVersion)) {
1214
+ if (cardinalityValue === "many" && sourceShapes.some((shape) => shape !== "single-file")) {
913
1215
  diagnostics.push({
914
- code: "KIND_DEFINITION_VERSION_INVALID",
1216
+ code: "KIND_DEFINITION_CARDINALITY_INVALID",
915
1217
  location: record.documentUri,
916
- message: `Kind '${resourceKind}' currentApiVersion '${currentApiVersion}' must be included in supportedApiVersions.`
1218
+ message: `Kind '${resourceKind}' documentCardinality 'many' requires only the single-file source shape.`
917
1219
  });
918
1220
  return;
919
1221
  }
920
- if (cardinalityValue === "many" && sourceShapes.some((shape) => shape !== "single-file")) {
1222
+ const requiredFiles = ((record.node.subdomains.DescriptorContract?.subdomains.RequiredFiles)?.body ?? []).filter(isResourceNode).filter((node) => node.tag === "File").map((node) => asString(node.properties.name)).filter((value) => Boolean(value)).sort(compareCodeUnits);
1223
+ const specRevisions = normalizeSpecRevisions(record, diagnostics, digestCanonical({
1224
+ sourceShapes,
1225
+ documentCardinality: cardinalityValue,
1226
+ requiredFiles
1227
+ }));
1228
+ if (specRevisions.length === 0) {
921
1229
  diagnostics.push({
922
- code: "KIND_DEFINITION_CARDINALITY_INVALID",
1230
+ code: "KIND_DEFINITION_INVALID",
923
1231
  location: record.documentUri,
924
- message: `Kind '${resourceKind}' documentCardinality 'many' requires only the single-file source shape.`
1232
+ message: "KindDefinition requires one or more exact SpecRevisions."
925
1233
  });
926
1234
  return;
927
1235
  }
928
- const requiredFiles = ((record.node.subdomains.DescriptorContract?.subdomains.RequiredFiles)?.body ?? []).filter(isResourceNode).filter((node) => node.tag === "File").map((node) => asString(node.properties.name)).filter((value) => Boolean(value));
929
1236
  return Object.freeze({
930
1237
  resourceId: record.resourceId,
931
1238
  resourceKind,
1239
+ subjectFqn,
932
1240
  sourceShapes: Object.freeze([...sourceShapes]),
1241
+ specRevisions,
933
1242
  requiredFiles: Object.freeze([...requiredFiles]),
934
- currentApiVersion,
935
- supportedApiVersions: Object.freeze([...new Set(supportedApiVersions)]),
936
1243
  documentCardinality: cardinalityValue,
937
1244
  documentUri: record.documentUri
938
1245
  });
939
1246
  }
940
- function validateResourceApiVersion(record, definition, diagnostics) {
941
- if (definition.supportedApiVersions.includes(record.metadata.apiVersion)) return;
942
- diagnostics.push({
943
- code: "RESOURCE_API_VERSION_UNSUPPORTED",
944
- location: record.documentUri,
945
- message: `Kind '${record.kind}' does not support apiVersion '${record.metadata.apiVersion}'.`,
946
- hint: `Current apiVersion is '${definition.currentApiVersion}'.`
947
- });
1247
+ function normalizeSpecRevisions(record, diagnostics, _sourceContractFingerprint) {
1248
+ const node = record.node.subdomains.SpecRevisions;
1249
+ if (!node) return Object.freeze([]);
1250
+ const revisions = [];
1251
+ const seen = /* @__PURE__ */ new Set();
1252
+ for (const value of node.body) {
1253
+ if (!isResourceNode(value) || value.tag !== "SpecRevision") continue;
1254
+ const specVersion = value.properties.specVersion;
1255
+ const schemaRef = asString(value.properties.schemaRef);
1256
+ const schemaFingerprint = asSha256(value.properties.schemaFingerprint);
1257
+ const contractFingerprint = asSha256(value.properties.contractFingerprint);
1258
+ const semanticValidatorFingerprint = asSha256(value.properties.semanticValidatorFingerprint);
1259
+ const referenceProjectionFingerprint = asSha256(value.properties.referenceProjectionFingerprint);
1260
+ const compilerInputFingerprint = asSha256(value.properties.compilerInputFingerprint);
1261
+ const stability = asString(value.properties.stability);
1262
+ if (!isSpecVersion(specVersion) || !schemaRef || !schemaFingerprint || !contractFingerprint || !semanticValidatorFingerprint || !referenceProjectionFingerprint || !compilerInputFingerprint || !isRevisionStability(stability)) {
1263
+ diagnostics.push({
1264
+ code: "KIND_SPEC_REVISION_INVALID",
1265
+ location: `${record.documentUri}#SpecRevision:${value.resourceId ?? "unknown"}`,
1266
+ message: "SpecRevision requires a positive specVersion, schemaRef, exact schema/contract/semantic fingerprints and stability."
1267
+ });
1268
+ continue;
1269
+ }
1270
+ if (seen.has(specVersion)) {
1271
+ diagnostics.push({
1272
+ code: "KIND_SPEC_REVISION_CONFLICT",
1273
+ location: `${record.documentUri}#SpecRevision:${value.resourceId ?? specVersion}`,
1274
+ message: `KindDefinition declares specVersion ${specVersion} more than once.`
1275
+ });
1276
+ continue;
1277
+ }
1278
+ seen.add(specVersion);
1279
+ revisions.push(Object.freeze({
1280
+ specVersion,
1281
+ schemaRef,
1282
+ schemaFingerprint,
1283
+ contractFingerprint,
1284
+ semanticContract: Object.freeze({
1285
+ semanticValidatorFingerprint,
1286
+ referenceProjectionFingerprint,
1287
+ compilerInputFingerprint
1288
+ }),
1289
+ stability
1290
+ }));
1291
+ }
1292
+ return Object.freeze(revisions.sort((left, right) => left.specVersion - right.specVersion));
948
1293
  }
949
- async function validateRequiredFiles(resourceDir, record, definition, diagnostics) {
1294
+ async function validateRequiredFiles(resourceDir, record, definition, context) {
950
1295
  for (const name of definition.requiredFiles) {
951
1296
  if (!isPlainEntry(name)) {
952
- diagnostics.push({
1297
+ context.diagnostics.push({
953
1298
  code: "KIND_DEFINITION_REQUIRED_FILE_INVALID",
954
1299
  location: record.documentUri,
955
1300
  message: `Invalid required file '${name}'.`
956
1301
  });
957
1302
  continue;
958
1303
  }
959
- if (!await stat(join(resourceDir, name)).then((info) => info.isFile()).catch(() => false)) diagnostics.push({
1304
+ const target = joinResourcePackageSourcePath(resourceDir, name);
1305
+ if ((await safeStat(context.port, target))?.kind !== "file") context.diagnostics.push({
960
1306
  code: "RESOURCE_REQUIRED_FILE_MISSING",
961
1307
  location: `${record.documentUri}#${name}`,
962
1308
  message: `Resource is missing required material '${name}'.`
@@ -985,7 +1331,7 @@ async function resolveCatalogDirectory(uri, manifestDir, location, context) {
985
1331
  });
986
1332
  return;
987
1333
  }
988
- if (isAbsolute(decoded) || decoded.split(/[\\/]+/).includes("..") || /%2f|%5c/i.test(rawPath)) {
1334
+ if (decoded.startsWith("/") || /^[A-Za-z]:/u.test(decoded) || decoded.split(/[\\/]+/).includes("..") || /%2f|%5c/i.test(rawPath)) {
989
1335
  context.diagnostics.push({
990
1336
  code: "RESOURCE_REF_CONTAINMENT",
991
1337
  location,
@@ -993,11 +1339,8 @@ async function resolveCatalogDirectory(uri, manifestDir, location, context) {
993
1339
  });
994
1340
  return;
995
1341
  }
996
- const base = relativeMatch[1] === "@/" ? context.rootDir : manifestDir;
997
- const boundary = await realpath(context.rootDir);
998
- const candidate = resolve(base, decoded);
999
- const resolved = await realpath(candidate).catch(() => candidate);
1000
- if (resolved !== boundary && !resolved.startsWith(`${boundary}${sep}`)) {
1342
+ const candidate = resolvePackageRelativePath(relativeMatch[1] === "@/" ? context.rootPath : manifestDir, decoded);
1343
+ if (!candidate || relativeResourcePackageSourcePath(context.rootPath, candidate) === void 0) {
1001
1344
  context.diagnostics.push({
1002
1345
  code: "RESOURCE_REF_CONTAINMENT",
1003
1346
  location,
@@ -1005,7 +1348,7 @@ async function resolveCatalogDirectory(uri, manifestDir, location, context) {
1005
1348
  });
1006
1349
  return;
1007
1350
  }
1008
- return resolved;
1351
+ return candidate;
1009
1352
  }
1010
1353
  function validateIdentity(record, context) {
1011
1354
  const prior = context.seenIdentities.get(record.resourceId);
@@ -1020,8 +1363,13 @@ function validateIdentity(record, context) {
1020
1363
  }
1021
1364
  context.seenIdentities.set(record.resourceId, record.documentUri);
1022
1365
  }
1023
- function documentUriFor(rootDir, filePath) {
1024
- return `vfs://@/${relative(rootDir, filePath).split(sep).join("/")}`;
1366
+ function logicalPathFor(rootPath, filePath) {
1367
+ const relative = relativeResourcePackageSourcePath(rootPath, filePath);
1368
+ if (relative === void 0) throw new TypeError("Resource source path is outside the package root");
1369
+ return relative;
1370
+ }
1371
+ function documentUriFor(rootPath, filePath) {
1372
+ return `vfs://@/${logicalPathFor(rootPath, filePath)}`;
1025
1373
  }
1026
1374
  function kindMismatch(actual, expected, location) {
1027
1375
  return {
@@ -1039,9 +1387,25 @@ function isDocumentCardinality(value) {
1039
1387
  function isPlainEntry(value) {
1040
1388
  return Boolean(value) && !value.includes("/") && !value.includes("\\") && !value.includes("..");
1041
1389
  }
1390
+ function isPlainSourceEntryName(value) {
1391
+ return Boolean(value) && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && !value.includes("\0");
1392
+ }
1393
+ function isResourcePackageEntryKind(value) {
1394
+ return value === "file" || value === "directory" || value === "symlink" || value === "other";
1395
+ }
1042
1396
  function isSingleFileExtension(value) {
1043
- const extension = extname(value).toLowerCase();
1044
- return extension === ".xnl" || extension === ".md";
1397
+ const lower = value.toLowerCase();
1398
+ return lower.endsWith(".xnl") || lower.endsWith(".md");
1399
+ }
1400
+ function resolvePackageRelativePath(base, relativePath) {
1401
+ if (relativePath.includes("\\") || relativePath.includes("\0") || relativePath.startsWith("/")) return void 0;
1402
+ const segments = relativePath.split("/").filter((segment) => segment.length > 0);
1403
+ if (segments.some((segment) => segment === "." || segment === "..")) return void 0;
1404
+ try {
1405
+ return joinResourcePackageSourcePath(base, ...segments);
1406
+ } catch {
1407
+ return;
1408
+ }
1045
1409
  }
1046
1410
  function isPlainObject(value) {
1047
1411
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -1064,6 +1428,12 @@ function normalizeExternalValue(value) {
1064
1428
  function asString(value) {
1065
1429
  return typeof value === "string" && value.trim() ? value : void 0;
1066
1430
  }
1431
+ function asSha256(value) {
1432
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/u.test(value) ? value : void 0;
1433
+ }
1434
+ function isRevisionStability(value) {
1435
+ return value === "experimental" || value === "stable" || value === "deprecated";
1436
+ }
1067
1437
  function isResourceNode(value) {
1068
1438
  return typeof value === "object" && value !== null && !Array.isArray(value) && "tag" in value;
1069
1439
  }
@@ -1094,8 +1464,8 @@ async function loadResourceTree(options) {
1094
1464
  if (unsupported) throw new ResourceValidationError([unsupported]);
1095
1465
  const manifestPath = options.manifestPath ?? "manifest.xnl";
1096
1466
  const result = await buildXnlResourceTree({
1097
- ...options,
1098
- rootDir: resolve(options.rootDir),
1467
+ port: await createDirectoryResourcePackageReadPort(options.rootDir),
1468
+ rootPath: "/",
1099
1469
  manifestPath
1100
1470
  });
1101
1471
  if (result.diagnostics.length > 0 || !result.tree) throw new ResourceValidationError(result.diagnostics);
@@ -1106,11 +1476,31 @@ async function validateResourceTree(options) {
1106
1476
  if (unsupported) return [unsupported];
1107
1477
  const manifestPath = options.manifestPath ?? "manifest.xnl";
1108
1478
  return (await buildXnlResourceTree({
1109
- ...options,
1110
- rootDir: resolve(options.rootDir),
1479
+ port: await createDirectoryResourcePackageReadPort(options.rootDir),
1480
+ rootPath: "/",
1111
1481
  manifestPath
1112
1482
  })).diagnostics;
1113
1483
  }
1484
+ async function loadResourceTreeFromReadPort(options) {
1485
+ const unsupported = unsupportedReadPortAuthorityDiagnostic(options);
1486
+ if (unsupported) throw new ResourceValidationError([unsupported]);
1487
+ const result = await buildXnlResourceTree({
1488
+ port: options.port,
1489
+ rootPath: options.rootPath ?? "/",
1490
+ manifestPath: options.manifestPath ?? "manifest.xnl"
1491
+ });
1492
+ if (result.diagnostics.length > 0 || !result.tree) throw new ResourceValidationError(result.diagnostics);
1493
+ return result.tree;
1494
+ }
1495
+ async function validateResourceTreeFromReadPort(options) {
1496
+ const unsupported = unsupportedReadPortAuthorityDiagnostic(options);
1497
+ if (unsupported) return [unsupported];
1498
+ return (await buildXnlResourceTree({
1499
+ port: options.port,
1500
+ rootPath: options.rootPath ?? "/",
1501
+ manifestPath: options.manifestPath ?? "manifest.xnl"
1502
+ })).diagnostics;
1503
+ }
1114
1504
  function unsupportedAuthorityDiagnostic(options) {
1115
1505
  const manifestPath = options.manifestPath ?? "manifest.xnl";
1116
1506
  if (!manifestPath.toLowerCase().endsWith(".xnl")) return {
@@ -1119,5 +1509,13 @@ function unsupportedAuthorityDiagnostic(options) {
1119
1509
  message: "Resource package manifests must use XNL authority."
1120
1510
  };
1121
1511
  }
1512
+ function unsupportedReadPortAuthorityDiagnostic(options) {
1513
+ const manifestPath = options.manifestPath ?? "manifest.xnl";
1514
+ if (!manifestPath.toLowerCase().endsWith(".xnl")) return {
1515
+ code: "RESOURCE_AUTHORITY_FORMAT_UNSUPPORTED",
1516
+ location: `vfs://@/${manifestPath}`,
1517
+ message: "Resource package manifests must use XNL authority."
1518
+ };
1519
+ }
1122
1520
  //#endregion
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 };
1521
+ export { digestCanonical as C, dirnameResourcePackageSourcePath as D, canonicalResourcePackageSourcePath as E, joinResourcePackageSourcePath as O, compareCodeUnits as S, ResourcePackageSourcePathError as T, effectiveRegistryLayerBindings as _, validateResourceTreeFromReadPort as a, ResourceCompositionError as b, RESOURCE_ENVELOPE_CONTRACT as c, isSpecVersion as d, readonlyMap as f, sha256Digest as g, createResourceContentIdentity as h, validateResourceTree as i, relativeResourcePackageSourcePath as k, RESOURCE_ENVELOPE_FINGERPRINT as l, buildResourceDependencySnapshot as m, loadResourceTree as n, decodeResourceMetadata as o, isAuthoredResourceTreeAuthentic as p, loadResourceTreeFromReadPort as r, CORE_BOOTSTRAP_WRITER_SPEC_VERSIONS as s, ResourceValidationError as t, RESOURCE_ENVELOPE_VERSION as u, isEffectiveRegistryAuthentic as v, createDirectoryResourcePackageReadPort as w, canonicalJson as x, markEffectiveRegistryAuthentic as y };