Files
Jyotisha/frontend/tests/skill-registry.test.ts
T
2026-08-15 05:10:37 +08:00

476 lines
15 KiB
TypeScript

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 { dirname, isAbsolute, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { test, type TestContext } from "node:test";
import {
computeSkillPackageSha256,
createSkillPackageRegistry,
resolveActiveSkillPackage,
resolveExactSkillPackage,
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<string, unknown>;
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> = {},
): 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");
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<string, unknown> = { ...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/,
);
});