




















@@ -60,6 +60,7 @@ vi.mock("../../infra/fs-safe.js", () => ({
60606161const {
6262 installSkillFromClawHub,
63+ resolveClawHubSkillStatusLinkSync,
6364 resolveClawHubSkillVerificationTarget,
6465 searchSkillsFromClawHub,
6566 updateSkillsFromClawHub,
@@ -1362,3 +1363,160 @@ describe("skills-clawhub", () => {
13621363expect(listClawHubSkillsMock).not.toHaveBeenCalled();
13631364});
13641365});
1366+1367+describe("ClawHub origin provenance readback", () => {
1368+async function writeOriginWithProvenance(params: {
1369+workspaceDir: string;
1370+slug: string;
1371+origin: Record<string, unknown>;
1372+lockSkill?: Record<string, unknown>;
1373+}) {
1374+const skillDir = path.join(params.workspaceDir, "skills", params.slug);
1375+await fs.mkdir(path.join(skillDir, ".clawhub"), { recursive: true });
1376+await fs.writeFile(path.join(skillDir, "SKILL.md"), "# Skill\n", "utf8");
1377+await fs.writeFile(
1378+path.join(skillDir, ".clawhub", "origin.json"),
1379+`${JSON.stringify(params.origin, null, 2)}\n`,
1380+"utf8",
1381+);
1382+await fs.mkdir(path.join(params.workspaceDir, ".clawhub"), { recursive: true });
1383+await fs.writeFile(
1384+path.join(params.workspaceDir, ".clawhub", "lock.json"),
1385+`${JSON.stringify(
1386+ {
1387+ version: 1,
1388+ skills: {
1389+ [params.slug]: params.lockSkill ?? {
1390+ version: params.origin.installedVersion,
1391+ installedAt: params.origin.installedAt,
1392+ registry: params.origin.registry,
1393+ },
1394+ },
1395+ },
1396+ null,
1397+ 2,
1398+ )}\n`,
1399+"utf8",
1400+);
1401+return skillDir;
1402+}
1403+1404+it("restores matching provenance and rejects one-sided origin edits", async () => {
1405+const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-origin-prov-"));
1406+try {
1407+const artifact = {
1408+kind: "clawpack" as const,
1409+sha256: "a".repeat(64),
1410+integrity: "sha256-test",
1411+};
1412+const skillFile = { path: "SKILL.md", sha256: "b".repeat(64) };
1413+const sourceUrl = "https://github.com/acme/skills/tree/abc/agentreceipt";
1414+const origin = {
1415+version: 1,
1416+registry: "https://clawhub.ai",
1417+slug: "agentreceipt",
1418+installedVersion: "1.0.0",
1419+installedAt: 123,
1420+ sourceUrl,
1421+ artifact,
1422+ skillFile,
1423+};
1424+const skillDir = await writeOriginWithProvenance({
1425+ workspaceDir,
1426+slug: "agentreceipt",
1427+ origin,
1428+lockSkill: {
1429+version: "1.0.0",
1430+installedAt: 123,
1431+registry: "https://clawhub.ai",
1432+ sourceUrl,
1433+ artifact,
1434+ skillFile,
1435+},
1436+});
1437+1438+const link = resolveClawHubSkillStatusLinkSync({
1439+ workspaceDir,
1440+ skillDir,
1441+skillKey: "agentreceipt",
1442+});
1443+1444+expect(link?.status).toBe("linked");
1445+expect(link?.valid).toBe(true);
1446+if (link?.status !== "linked") {
1447+throw new Error(`expected linked status, got ${link?.status}`);
1448+}
1449+expect(link.artifact).toEqual(artifact);
1450+expect(link.skillFile).toEqual(skillFile);
1451+expect(link.sourceUrl).toBe(sourceUrl);
1452+1453+const originPath = path.join(skillDir, ".clawhub", "origin.json");
1454+for (const override of [
1455+{ sourceUrl: "https://github.com/acme/skills/tree/tampered/agentreceipt" },
1456+{
1457+artifact: {
1458+kind: "clawpack",
1459+sha256: "c".repeat(64),
1460+integrity: "sha256-tampered",
1461+},
1462+},
1463+{ skillFile: { path: "SKILL.md", sha256: "d".repeat(64) } },
1464+]) {
1465+await fs.writeFile(
1466+originPath,
1467+`${JSON.stringify({ ...origin, ...override }, null, 2)}\n`,
1468+"utf8",
1469+);
1470+expect(
1471+resolveClawHubSkillStatusLinkSync({
1472+ workspaceDir,
1473+ skillDir,
1474+skillKey: "agentreceipt",
1475+}),
1476+).toMatchObject({
1477+status: "invalid",
1478+valid: false,
1479+reason: expect.stringContaining("does not match the workspace ClawHub lockfile"),
1480+});
1481+}
1482+} finally {
1483+await fs.rm(workspaceDir, { recursive: true, force: true });
1484+}
1485+});
1486+1487+it("drops malformed provenance fields while keeping the link valid", async () => {
1488+const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-origin-prov-"));
1489+try {
1490+const skillDir = await writeOriginWithProvenance({
1491+ workspaceDir,
1492+slug: "agentreceipt",
1493+origin: {
1494+version: 1,
1495+registry: "https://clawhub.ai",
1496+slug: "agentreceipt",
1497+installedVersion: "1.0.0",
1498+installedAt: 123,
1499+sourceUrl: " ",
1500+artifact: { kind: "bogus", sha256: 42, integrity: "" },
1501+skillFile: { path: "", sha256: "c".repeat(64) },
1502+},
1503+});
1504+1505+const link = resolveClawHubSkillStatusLinkSync({
1506+ workspaceDir,
1507+ skillDir,
1508+skillKey: "agentreceipt",
1509+});
1510+1511+expect(link?.status).toBe("linked");
1512+if (link?.status !== "linked") {
1513+throw new Error(`expected linked status, got ${link?.status}`);
1514+}
1515+expect(link.artifact).toBeUndefined();
1516+expect(link.skillFile).toBeUndefined();
1517+expect(link.sourceUrl).toBeUndefined();
1518+} finally {
1519+await fs.rm(workspaceDir, { recursive: true, force: true });
1520+}
1521+});
1522+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。