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.
- package/README.md +5 -3
- package/dist/application-assembly.d.ts +1 -1
- package/dist/application-assembly.js +1 -1
- package/dist/authoring-runtime.d.ts +167 -1
- package/dist/authoring-runtime.js +2 -2
- package/dist/effective-content-identities-CV6XFBZy.js +118 -0
- package/dist/effective-content-identities-drMRhYE2.d.ts +14 -0
- package/dist/{index-B515mMkO.d.ts → index-CR7wJSSX.d.ts} +10 -6
- package/dist/{index-Cqh9pEBa.d.ts → index-DEikbOFh.d.ts} +2 -1
- package/dist/{index-CF4nEkOi.d.ts → index-DI3WtuXq.d.ts} +1 -1
- package/dist/index-DboIOEr8.d.ts +474 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +7 -5
- package/dist/kind-definition.d.ts +118 -10
- package/dist/kind-definition.js +826 -15
- package/dist/resource-core-Brj8naBr.d.ts +47 -0
- package/dist/resource-core.d.ts +4 -3
- package/dist/resource-core.js +40 -141
- package/dist/resource-projection.d.ts +1 -1
- package/dist/skill-capsule.d.ts +2 -2
- package/dist/skill-capsule.js +1 -1
- package/dist/{src-3pnDAjBD.js → src-B0Hja_dk.js} +5 -7
- package/dist/{src-CaQ25rMj.js → src-DLJUCH0M.js} +11 -7
- package/dist/src-hrUy4DJ9.js +640 -0
- package/dist/{src-D3qSzGFn.js → src-jggOQtq3.js} +511 -113
- package/dist/system-skills/resource-dsl/KindDefinitions/SkillCapsule/manifest.xnl +15 -3
- package/dist/system-skills/resource-dsl/KindDefinitions/WikiPage/manifest.xnl +15 -3
- package/dist/system-skills/resource-dsl/SkillCapsules/System/SKILL.metadata.yaml +1 -0
- package/dist/system-skills/resource-dsl/SkillCapsules/System/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/SkillCapsules/System/resource-mappings.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/documents/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/files/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/index/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/language/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/migration/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/projections/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/WikiPages/skill-capsules/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/documents.md +33 -17
- package/dist/system-skills/resource-dsl/examples/function.xnl +1 -1
- package/dist/system-skills/resource-dsl/examples/kind-definition.xnl +15 -3
- package/dist/system-skills/resource-dsl/examples/resource-mappings.xnl +1 -1
- package/dist/system-skills/resource-dsl/examples/resource-package.xnl +1 -1
- package/dist/system-skills/resource-dsl/examples/skill-capsule-authoring.xnl +1 -1
- package/dist/system-skills/resource-dsl/examples/skill-capsule-devops.xnl +1 -1
- package/dist/system-skills/resource-dsl/examples/skill-capsule-kind-definition.xnl +14 -3
- package/dist/system-skills/resource-dsl/examples/skill-capsule-resource-dsl.xnl +1 -1
- package/dist/system-skills/resource-dsl/examples/skill-capsule-run.xnl +1 -1
- package/dist/system-skills/resource-dsl/index.md +5 -5
- package/dist/system-skills/resource-dsl/language.md +10 -8
- package/dist/system-skills/resource-dsl/manifest.xnl +1 -1
- package/dist/system-skills/resource-dsl/migration.md +6 -3
- package/dist/system-skills/resource-dsl/projections.md +7 -6
- package/dist/system-skills/resource-dsl/skill-capsules.md +8 -7
- package/dist/system-skills/sys-halfcode-resource-dsl.plan.js +84 -59
- package/dist/testing.js +1 -1
- package/package.json +6 -2
- package/dist/index-CDIT65pI.d.ts +0 -192
- package/dist/resource-core-C8IonwKw.d.ts +0 -41
- 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
|
|
3
|
-
import
|
|
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/
|
|
366
|
-
const
|
|
367
|
-
function
|
|
368
|
-
|
|
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
|
|
372
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
468
|
-
await validateRequiredFiles(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
521
|
-
await validateRequiredFiles(
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
543
|
-
await validateRequiredFiles(
|
|
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
|
|
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 [
|
|
835
|
+
return [joinResourcePackageSourcePath(root, catalog.entry)];
|
|
572
836
|
}
|
|
573
|
-
return entries.filter((entry) => entry.
|
|
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 =
|
|
585
|
-
const directStat = await
|
|
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.
|
|
851
|
+
if (directStat.kind !== "file") {
|
|
591
852
|
context.diagnostics.push({
|
|
592
853
|
code: "RESOURCE_CATALOG_ENTRY_INVALID",
|
|
593
|
-
location: documentUriFor(context.
|
|
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.
|
|
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 (
|
|
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.
|
|
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.
|
|
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 || !
|
|
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
|
|
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
|
-
|
|
684
|
-
|
|
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:
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
...version ? { version } : {}
|
|
700
|
-
}),
|
|
968
|
+
metadata: decoded.metadata,
|
|
969
|
+
authoredSpec: authoredSpecFromNode(node),
|
|
970
|
+
sourceContentDigest: authority.authorityDigest,
|
|
701
971
|
sourceShape: "single-file",
|
|
702
|
-
logicalPath:
|
|
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.
|
|
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 =
|
|
1019
|
+
const canonicalPath = canonicalResourcePackageSourcePath(filePath);
|
|
750
1020
|
let pending = context.authorityFiles.get(canonicalPath);
|
|
751
1021
|
if (!pending) {
|
|
752
|
-
pending =
|
|
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.
|
|
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 (!
|
|
799
|
-
|
|
1074
|
+
if (!resourceId) return void 0;
|
|
1075
|
+
const decoded = decodeResourceMetadata({
|
|
1076
|
+
fields: node.metadata,
|
|
1077
|
+
lifecycle: node.properties.lifecycle,
|
|
800
1078
|
location: documentUri,
|
|
801
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
1210
|
+
message: "KindDefinition requires resourceKind, subjectFqn, sourceShapes and one or more exact SpecRevisions."
|
|
909
1211
|
});
|
|
910
1212
|
return;
|
|
911
1213
|
}
|
|
912
|
-
if (
|
|
1214
|
+
if (cardinalityValue === "many" && sourceShapes.some((shape) => shape !== "single-file")) {
|
|
913
1215
|
diagnostics.push({
|
|
914
|
-
code: "
|
|
1216
|
+
code: "KIND_DEFINITION_CARDINALITY_INVALID",
|
|
915
1217
|
location: record.documentUri,
|
|
916
|
-
message: `Kind '${resourceKind}'
|
|
1218
|
+
message: `Kind '${resourceKind}' documentCardinality 'many' requires only the single-file source shape.`
|
|
917
1219
|
});
|
|
918
1220
|
return;
|
|
919
1221
|
}
|
|
920
|
-
|
|
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: "
|
|
1230
|
+
code: "KIND_DEFINITION_INVALID",
|
|
923
1231
|
location: record.documentUri,
|
|
924
|
-
message:
|
|
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
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
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
|
|
997
|
-
|
|
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
|
|
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
|
|
1024
|
-
|
|
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
|
|
1044
|
-
return
|
|
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
|
-
|
|
1098
|
-
|
|
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
|
-
|
|
1110
|
-
|
|
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 {
|
|
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 };
|