






















@@ -382,6 +382,190 @@ describe("skills-clawhub", () => {
382382}
383383});
384384385+it("persists the source URL from server-resolved verification provenance", async () => {
386+const workspaceDir = await tempDirs.make("openclaw-skills-source-");
387+const sourceUrl = "https://github.com/openclaw/skills/tree/def456/agentreceipt";
388+fetchClawHubSkillDetailMock.mockResolvedValueOnce({
389+skill: {
390+slug: "agentreceipt",
391+displayName: "AgentReceipt",
392+createdAt: 1,
393+updatedAt: 2,
394+},
395+latestVersion: {
396+version: "1.0.0",
397+createdAt: 3,
398+},
399+});
400+fetchClawHubSkillVerificationMock.mockResolvedValueOnce({
401+schema: "clawhub.skill.verify.v1",
402+ok: true,
403+decision: "pass",
404+reasons: [],
405+card: { available: true },
406+artifact: { sourceFingerprint: "source-fp" },
407+provenance: {
408+source: "server-resolved-github-import",
409+kind: "github",
410+url: sourceUrl,
411+repo: "openclaw/skills",
412+ref: "main",
413+commit: "def456",
414+path: "agentreceipt",
415+importedAt: 4,
416+},
417+security: { status: "clean" },
418+signature: { status: "unsigned" },
419+});
420+installPackageDirMock.mockImplementationOnce(async (params: { targetDir: string }) => {
421+await fs.mkdir(params.targetDir, { recursive: true });
422+await fs.writeFile(path.join(params.targetDir, "SKILL.md"), "# AgentReceipt\n", "utf8");
423+return { ok: true, targetDir: params.targetDir };
424+});
425+426+try {
427+const result = await installSkillFromClawHub({
428+ workspaceDir,
429+slug: "agentreceipt",
430+version: "1.0.0",
431+});
432+433+expectInstalledSkill(result, {
434+slug: "agentreceipt",
435+version: "1.0.0",
436+targetDir: path.join(workspaceDir, "skills", "agentreceipt"),
437+});
438+const lock = JSON.parse(
439+await fs.readFile(path.join(workspaceDir, ".clawhub", "lock.json"), "utf8"),
440+) as { skills: Record<string, Record<string, unknown>> };
441+expect(lock.skills.agentreceipt).toMatchObject({
442+ sourceUrl,
443+verification: {
444+provenance: {
445+source: "server-resolved-github-import",
446+kind: "github",
447+url: sourceUrl,
448+repo: "openclaw/skills",
449+ref: "main",
450+commit: "def456",
451+path: "agentreceipt",
452+importedAt: 4,
453+},
454+},
455+});
456+const origin = JSON.parse(
457+await fs.readFile(
458+path.join(workspaceDir, "skills", "agentreceipt", ".clawhub", "origin.json"),
459+"utf8",
460+),
461+) as Record<string, unknown>;
462+expect(origin.sourceUrl).toBe(sourceUrl);
463+} finally {
464+await fs.rm(workspaceDir, { recursive: true, force: true });
465+}
466+});
467+468+it("does not treat detail metadata as verified source provenance", async () => {
469+const workspaceDir = await tempDirs.make("openclaw-skills-source-");
470+fetchClawHubSkillDetailMock.mockResolvedValueOnce({
471+skill: {
472+slug: "agentreceipt",
473+displayName: "AgentReceipt",
474+createdAt: 1,
475+updatedAt: 2,
476+sourceUrl: "https://github.com/openclaw/skills/tree/latest/agentreceipt",
477+},
478+latestVersion: {
479+version: "1.0.0",
480+createdAt: 3,
481+sourceUrl: "https://github.com/openclaw/skills/tree/latest/agentreceipt",
482+},
483+});
484+fetchClawHubSkillVerificationMock.mockRejectedValueOnce(new Error("verification down"));
485+installPackageDirMock.mockImplementationOnce(async (params: { targetDir: string }) => {
486+await fs.mkdir(params.targetDir, { recursive: true });
487+await fs.writeFile(path.join(params.targetDir, "SKILL.md"), "# AgentReceipt\n", "utf8");
488+return { ok: true, targetDir: params.targetDir };
489+});
490+491+try {
492+const result = await installSkillFromClawHub({
493+ workspaceDir,
494+slug: "agentreceipt",
495+version: "1.0.0",
496+});
497+498+expectInstalledSkill(result, {
499+slug: "agentreceipt",
500+version: "1.0.0",
501+targetDir: path.join(workspaceDir, "skills", "agentreceipt"),
502+});
503+const lock = JSON.parse(
504+await fs.readFile(path.join(workspaceDir, ".clawhub", "lock.json"), "utf8"),
505+) as { skills: Record<string, Record<string, unknown>> };
506+expect(lock.skills.agentreceipt?.sourceUrl).toBeUndefined();
507+const origin = JSON.parse(
508+await fs.readFile(
509+path.join(workspaceDir, "skills", "agentreceipt", ".clawhub", "origin.json"),
510+"utf8",
511+),
512+) as Record<string, unknown>;
513+expect(origin.sourceUrl).toBeUndefined();
514+} finally {
515+await fs.rm(workspaceDir, { recursive: true, force: true });
516+}
517+});
518+519+it("does not trust URLs from unavailable verification provenance", async () => {
520+const workspaceDir = await tempDirs.make("openclaw-skills-source-");
521+fetchClawHubSkillVerificationMock.mockResolvedValueOnce({
522+schema: "clawhub.skill.verify.v1",
523+ok: true,
524+decision: "pass",
525+reasons: [],
526+card: { available: true },
527+artifact: { sourceFingerprint: "source-fp" },
528+provenance: {
529+source: "unavailable",
530+url: "https://github.com/openclaw/skills/tree/unverified/agentreceipt",
531+},
532+security: { status: "clean" },
533+signature: { status: "unsigned" },
534+});
535+installPackageDirMock.mockImplementationOnce(async (params: { targetDir: string }) => {
536+await fs.mkdir(params.targetDir, { recursive: true });
537+await fs.writeFile(path.join(params.targetDir, "SKILL.md"), "# AgentReceipt\n", "utf8");
538+return { ok: true, targetDir: params.targetDir };
539+});
540+541+try {
542+const result = await installSkillFromClawHub({
543+ workspaceDir,
544+slug: "agentreceipt",
545+version: "1.0.0",
546+});
547+548+expectInstalledSkill(result, {
549+slug: "agentreceipt",
550+version: "1.0.0",
551+targetDir: path.join(workspaceDir, "skills", "agentreceipt"),
552+});
553+const lock = JSON.parse(
554+await fs.readFile(path.join(workspaceDir, ".clawhub", "lock.json"), "utf8"),
555+) as { skills: Record<string, Record<string, unknown>> };
556+expect(lock.skills.agentreceipt?.sourceUrl).toBeUndefined();
557+const origin = JSON.parse(
558+await fs.readFile(
559+path.join(workspaceDir, "skills", "agentreceipt", ".clawhub", "origin.json"),
560+"utf8",
561+),
562+) as Record<string, unknown>;
563+expect(origin.sourceUrl).toBeUndefined();
564+} finally {
565+await fs.rm(workspaceDir, { recursive: true, force: true });
566+}
567+});
568+385569it("keeps installing when the ClawHub verification snapshot is unavailable", async () => {
386570const workspaceDir = await tempDirs.make("openclaw-skills-lock-");
387571fetchClawHubSkillVerificationMock.mockRejectedValueOnce(new Error("verification down"));
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。