惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
云风的 BLOG
云风的 BLOG
人人都是产品经理
人人都是产品经理
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
J
Java Code Geeks
博客园 - 聂微东
B
Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
腾讯CDC
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Azure Blog
Microsoft Azure Blog
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
美团技术团队
博客园 - Franky
Google DeepMind News
Google DeepMind News
V
V2EX
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
The Cloudflare Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(skills): preserve ClawHub origin provenance on readba...
Alix-007 · 2026-06-16 · via Recent Commits to openclaw:main

@@ -60,6 +60,7 @@ vi.mock("../../infra/fs-safe.js", () => ({

60606161

const {

6262

installSkillFromClawHub,

63+

resolveClawHubSkillStatusLinkSync,

6364

resolveClawHubSkillVerificationTarget,

6465

searchSkillsFromClawHub,

6566

updateSkillsFromClawHub,

@@ -1362,3 +1363,160 @@ describe("skills-clawhub", () => {

13621363

expect(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+

});