import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { basename, dirname, isAbsolute, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { test, type TestContext } from "node:test"; import { computeSkillPackageSha256, createSkillPackageRegistry, resolveActiveSkillPackage, resolveExactSkillPackage, resolveSkillPackageRuntimePath, resolveSkillPackageVersion, verifyAllActiveSkillPackages, type SkillPackageIdentity, } from "../src/lib/skill-package-registry.ts"; const sourceCommit = "0fd111d16b45796086a6c1d0945dbd3de6755d8a"; const projectRoot = fileURLToPath(new URL("../../", import.meta.url)); type RegistryEntry = SkillPackageIdentity | Record; function sha256(bytes: string | Buffer): string { return createHash("sha256").update(bytes).digest("hex"); } function fixture(t: TestContext): { root: string; registryPath: string; writeSkill: (packagePath: string, contents: string) => string; writeRegistry: (packages: RegistryEntry[]) => void; } { const root = mkdtempSync(join(tmpdir(), "skill-registry-test-")); const registryPath = join(root, "skills", "skill-package-registry.json"); mkdirSync(dirname(registryPath), { recursive: true }); t.after(() => rmSync(root, { recursive: true, force: true })); return { root, registryPath, writeSkill(packagePath, contents) { const directory = join(root, packagePath); mkdirSync(directory, { recursive: true }); writeFileSync(join(directory, "SKILL.md"), contents); return computeSkillPackageSha256(directory); }, writeRegistry(packages) { writeFileSync( registryPath, `${JSON.stringify({ schemaVersion: 1, packages }, null, 2)}\n`, ); }, }; } function identity( overrides: Partial = {}, ): SkillPackageIdentity { return { name: "example-skill", version: "1.0.0", sha256: "a".repeat(64), sourceCommit, packagePath: "skills/example-skill-1.0.0", status: "active", ...overrides, }; } test("checked-in registry verifies all three active packages and returns absolute directories", () => { const packages = verifyAllActiveSkillPackages({ projectRoot }); assert.deepEqual( packages.map(({ name, version, sha256 }) => ({ name, version, sha256 })), [ { name: "jyotish-vedic-astrology", version: "6.9.14", sha256: "d3d6d05c1da25bb684af3be591687f0321b9a3a69a9896454ea3e25b57b6071b", }, { name: "jyotish-birth-time-rectification", version: "10.0.0", sha256: "b66f243d266e12527b6934cc0c5925df654a9e7bcbaabea3bf244b2e1dfaf2e7", }, { name: "jyotish-personal-report", version: "1.0.0", sha256: "23149b9e1146b5a66b0762520b58eb750b67d95007144b959f0ff3d9ee2bf982", }, ], ); for (const resolvedPackage of packages) { assert.equal(isAbsolute(resolvedPackage.resolvedPath), true); assert.equal( resolvedPackage.resolvedPath, resolve(projectRoot, resolvedPackage.packagePath), ); } assert.equal(packages[0]?.sourceCommit, null); assert.equal(packages[1]?.sourceCommit, null); assert.equal(packages[2]?.sourceCommit, null); const active = resolveActiveSkillPackage("jyotish-vedic-astrology", { projectRoot, }); assert.equal(active.version, "6.9.14"); const runtimePath = resolveSkillPackageRuntimePath(active); assert.equal(basename(runtimePath), active.name); assert.equal(realpathSync(runtimePath), active.resolvedPath); assert.notEqual(runtimePath, active.resolvedPath); assert.deepEqual( resolveExactSkillPackage(active.name, active.version, active.sha256, { projectRoot, }), active, ); }); test("exact resolution of an old deprecated package is independent of active switches", (t) => { const f = fixture(t); const oldHash = f.writeSkill("skills/example-1.0.0", "old immutable bytes\n"); const currentHash = f.writeSkill( "skills/example-2.0.0", "current immutable bytes\n", ); const nextHash = f.writeSkill("skills/example-3.0.0", "next immutable bytes\n"); const oldIdentity = identity({ version: "1.0.0", sha256: oldHash, packagePath: "skills/example-1.0.0", status: "deprecated", }); const currentIdentity = identity({ version: "2.0.0", sha256: currentHash, packagePath: "skills/example-2.0.0", }); const nextIdentity = identity({ version: "3.0.0", sha256: nextHash, packagePath: "skills/example-3.0.0", status: "deprecated", }); f.writeRegistry([oldIdentity, currentIdentity, nextIdentity]); const registry = createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }); assert.equal(registry.resolveActive("example-skill").version, "2.0.0"); assert.deepEqual(registry.resolveExact("example-skill", "1.0.0", oldHash), { ...oldIdentity, resolvedPath: realpathSync(join(f.root, "skills/example-1.0.0")), }); f.writeRegistry([ oldIdentity, { ...currentIdentity, status: "deprecated" }, { ...nextIdentity, status: "active" }, ]); assert.equal(registry.resolveActive("example-skill").version, "3.0.0"); assert.equal( registry.resolveExact("example-skill", "1.0.0", oldHash).sha256, oldHash, ); }); test("version resolution requires one non-blocked identity and verifies its bytes", (t) => { const f = fixture(t); const oldHash = f.writeSkill("skills/example-old", "old version\n"); const oldIdentity = identity({ sha256: oldHash, packagePath: "skills/example-old", status: "deprecated", }); f.writeRegistry([oldIdentity]); const registry = createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }); assert.throws(() => registry.resolveActive("example-skill"), /No active/); assert.deepEqual( resolveSkillPackageVersion("example-skill", "1.0.0", { projectRoot: f.root, registryPath: f.registryPath, }), { ...oldIdentity, resolvedPath: realpathSync(join(f.root, "skills/example-old")), }, ); const alternateHash = f.writeSkill( "skills/example-old-repacked", "different bytes\n", ); f.writeRegistry([ oldIdentity, identity({ sha256: alternateHash, packagePath: "skills/example-old-repacked", status: "deprecated", }), ]); assert.throws( () => resolveSkillPackageVersion("example-skill", "1.0.0", { projectRoot: f.root, registryPath: f.registryPath, }), /Expected one registry identity/, ); f.writeRegistry([{ ...oldIdentity, status: "blocked" }]); assert.throws( () => resolveSkillPackageVersion("example-skill", "1.0.0", { projectRoot: f.root, registryPath: f.registryPath, }), /blocked/, ); }); test("missing lookup identities fail closed", (t) => { const f = fixture(t); f.writeRegistry([]); const options = { projectRoot: f.root, registryPath: f.registryPath }; const registry = createSkillPackageRegistry(options); assert.throws(() => registry.resolveActive("example-skill"), /No active/); assert.throws( () => registry.resolveExact("example-skill", "1.0.0", "a".repeat(64)), /No exact skill package/, ); assert.throws( () => resolveSkillPackageVersion("example-skill", "1.0.0", options), /found 0/, ); }); test("missing identity fields fail closed", (t) => { const f = fixture(t); f.writeSkill("skills/example-skill-1.0.0", "bytes\n"); const missingHash: Record = { ...identity() }; delete missingHash.sha256; f.writeRegistry([missingHash]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /missing sha256/, ); }); test("hash mismatch fails closed after reading the complete package tree", (t) => { const f = fixture(t); const packagePath = "skills/example-skill-1.0.0"; const expectedHash = f.writeSkill(packagePath, "expected bytes\n"); mkdirSync(join(f.root, packagePath, "references"), { recursive: true }); writeFileSync( join(f.root, packagePath, "references", "runtime-policy.md"), "tampered reference bytes\n", ); f.writeRegistry([identity({ sha256: expectedHash })]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /SHA-256 mismatch/, ); }); test("package hash covers paths, executable bits, references, scripts, and assets", (t) => { const f = fixture(t); const packageDirectory = join(f.root, "skills", "example-skill-1.0.0"); mkdirSync(join(packageDirectory, "references"), { recursive: true }); mkdirSync(join(packageDirectory, "scripts"), { recursive: true }); mkdirSync(join(packageDirectory, "assets"), { recursive: true }); writeFileSync(join(packageDirectory, "SKILL.md"), "skill bytes\n"); writeFileSync(join(packageDirectory, "references", "guide.md"), "guide v1\n"); writeFileSync(join(packageDirectory, "scripts", "run.py"), "print('v1')\n"); writeFileSync(join(packageDirectory, "assets", "template.txt"), "asset v1\n"); const initial = computeSkillPackageSha256(packageDirectory); writeFileSync(join(packageDirectory, "references", "guide.md"), "guide v2\n"); const changedReference = computeSkillPackageSha256(packageDirectory); assert.notEqual(changedReference, initial); writeFileSync(join(packageDirectory, "references", "guide.md"), "guide v1\n"); writeFileSync(join(packageDirectory, "assets", "template.txt"), "asset v2\n"); assert.notEqual(computeSkillPackageSha256(packageDirectory), initial); }); test("path traversal and symlink escape fail closed", (t) => { const f = fixture(t); f.writeRegistry([ identity({ packagePath: "skills/../outside", sha256: sha256("outside\n"), }), ]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /unsafe packagePath/, ); const outside = mkdtempSync(join(tmpdir(), "skill-registry-outside-")); t.after(() => rmSync(outside, { recursive: true, force: true })); writeFileSync(join(outside, "SKILL.md"), "outside\n"); const packageDirectory = join(f.root, "skills", "symlinked-skill"); mkdirSync(packageDirectory, { recursive: true }); symlinkSync(join(outside, "SKILL.md"), join(packageDirectory, "SKILL.md")); f.writeRegistry([ identity({ packagePath: "skills/symlinked-skill", sha256: sha256("outside\n"), }), ]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /resolves outside the project root/, ); }); test("symbolic links are rejected even when their target stays inside the project root", (t) => { const f = fixture(t); const packageDirectory = join(f.root, "skills", "symlinked-skill"); const sharedDirectory = join(f.root, "shared"); mkdirSync(packageDirectory, { recursive: true }); mkdirSync(sharedDirectory, { recursive: true }); writeFileSync(join(packageDirectory, "SKILL.md"), "skill bytes\n"); writeFileSync(join(sharedDirectory, "guide.md"), "shared bytes\n"); symlinkSync(sharedDirectory, join(packageDirectory, "references")); f.writeRegistry([ identity({ packagePath: "skills/symlinked-skill", sha256: "a".repeat(64), }), ]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /must not be a symbolic link/, ); }); test("blocked packages are rejected by active, exact, and version resolution", (t) => { const f = fixture(t); const hash = f.writeSkill("skills/example-skill-1.0.0", "blocked bytes\n"); f.writeRegistry([identity({ sha256: hash, status: "blocked" })]); const registry = createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }); assert.throws(() => registry.resolveActive("example-skill"), /No active/); assert.throws( () => registry.resolveExact("example-skill", "1.0.0", hash), /blocked/, ); assert.throws( () => registry.resolveVersion("example-skill", "1.0.0"), /blocked/, ); }); test("duplicate active packages fail the entire registry closed", (t) => { const f = fixture(t); const firstHash = f.writeSkill("skills/example-1.0.0", "first\n"); const secondHash = f.writeSkill("skills/example-2.0.0", "second\n"); f.writeRegistry([ identity({ sha256: firstHash, packagePath: "skills/example-1.0.0", }), identity({ version: "2.0.0", sha256: secondHash, packagePath: "skills/example-2.0.0", }), ]); const registry = createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }); assert.throws(() => registry.resolveActive("example-skill"), /duplicate active/); assert.throws(() => registry.verifyAllActive(), /duplicate active/); }); test("nullable source commits are accepted while malformed non-null commits fail closed", (t) => { const f = fixture(t); const hash = f.writeSkill("skills/example-skill-1.0.0", "bytes\n"); f.writeRegistry([identity({ sha256: hash, sourceCommit: null })]); const resolved = createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"); assert.equal(resolved.sourceCommit, null); f.writeRegistry([identity({ sha256: hash, sourceCommit: "not-a-commit" })]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /invalid sourceCommit/, ); }); test("invalid hash, commit, and non-directory package paths fail closed", (t) => { const f = fixture(t); f.writeRegistry([identity({ sha256: "A".repeat(64) })]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /invalid sha256/, ); f.writeRegistry([identity({ sourceCommit: "not-a-commit" })]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /invalid sourceCommit/, ); const filePath = join(f.root, "skills", "not-a-directory"); writeFileSync(filePath, "file\n"); f.writeRegistry([ identity({ packagePath: "skills/not-a-directory", sha256: sha256("file\n"), }), ]); assert.throws( () => createSkillPackageRegistry({ projectRoot: f.root, registryPath: f.registryPath, }).resolveActive("example-skill"), /must be a directory/, ); });