test(skills): cover same-name inspect/install provenance mixup
Pin ClawHub to GitHub-style identifiers and keep catalog metadata from being paired with a foreign same-named bundle.
This commit is contained in:
@@ -186,6 +186,101 @@ def test_check_for_skill_updates_does_not_fall_back_across_registries():
|
||||
assert "bundle" not in results[0], "must not carry a foreign registry's bundle"
|
||||
|
||||
|
||||
def test_resolve_does_not_pair_catalog_meta_with_foreign_same_name_bundle():
|
||||
"""Inspect metadata from one registry must not ship with another registry's files.
|
||||
|
||||
skills.sh can inspect ``owner/repo/skills/skillopt`` while ClawHub used to
|
||||
fetch by last path segment ``skillopt`` and return a different author's
|
||||
SKILL.md. The header then showed the requested identifier and the preview
|
||||
showed the wrong skill.
|
||||
"""
|
||||
from hermes_cli.skills_hub import _resolve_source_meta_and_bundle
|
||||
from tools.skills_hub import SkillBundle, SkillMeta
|
||||
|
||||
class CatalogSource:
|
||||
def inspect(self, identifier):
|
||||
return SkillMeta(
|
||||
name="skillopt",
|
||||
description="kanban-based pipelines",
|
||||
source="skills.sh",
|
||||
identifier="skills-sh/latipun7/agent-skill-collections/skills/skillopt",
|
||||
trust_level="community",
|
||||
)
|
||||
|
||||
def fetch(self, identifier):
|
||||
return None
|
||||
|
||||
class ForeignSlugSource:
|
||||
def inspect(self, identifier):
|
||||
return SkillMeta(
|
||||
name="skillopt",
|
||||
description="Train, evaluate, and improve Agent skill files",
|
||||
source="clawhub",
|
||||
identifier="skillopt",
|
||||
trust_level="community",
|
||||
)
|
||||
|
||||
def fetch(self, identifier):
|
||||
return SkillBundle(
|
||||
name="skillopt",
|
||||
files={"SKILL.md": "# Train, evaluate, and improve Agent skill files\n"},
|
||||
source="clawhub",
|
||||
identifier="skillopt",
|
||||
trust_level="community",
|
||||
)
|
||||
|
||||
meta, bundle, matched = _resolve_source_meta_and_bundle(
|
||||
"latipun7/agent-skill-collections/skills/skillopt",
|
||||
[CatalogSource(), ForeignSlugSource()],
|
||||
)
|
||||
|
||||
assert bundle is not None
|
||||
assert bundle.source == "clawhub"
|
||||
assert meta is not None
|
||||
assert meta.source == "clawhub"
|
||||
assert meta.identifier == "skillopt"
|
||||
assert "kanban-based" not in (meta.description or "")
|
||||
assert matched is not None
|
||||
assert matched.__class__ is ForeignSlugSource
|
||||
|
||||
|
||||
def test_resolve_keeps_catalog_meta_when_later_sources_do_not_fetch():
|
||||
from hermes_cli.skills_hub import _resolve_source_meta_and_bundle
|
||||
from tools.skills_hub import SkillMeta
|
||||
|
||||
class CatalogSource:
|
||||
def inspect(self, identifier):
|
||||
return SkillMeta(
|
||||
name="skillopt",
|
||||
description="kanban-based pipelines",
|
||||
source="skills.sh",
|
||||
identifier="skills-sh/latipun7/agent-skill-collections/skills/skillopt",
|
||||
trust_level="community",
|
||||
)
|
||||
|
||||
def fetch(self, identifier):
|
||||
return None
|
||||
|
||||
class QuietSource:
|
||||
def inspect(self, identifier):
|
||||
return None
|
||||
|
||||
def fetch(self, identifier):
|
||||
return None
|
||||
|
||||
meta, bundle, matched = _resolve_source_meta_and_bundle(
|
||||
"latipun7/agent-skill-collections/skills/skillopt",
|
||||
[CatalogSource(), QuietSource()],
|
||||
)
|
||||
|
||||
assert bundle is None
|
||||
assert meta is not None
|
||||
assert meta.source == "skills.sh"
|
||||
assert meta.identifier.endswith("latipun7/agent-skill-collections/skills/skillopt")
|
||||
assert matched is not None
|
||||
assert matched.__class__ is CatalogSource
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -370,6 +370,62 @@ class TestClawHubSource(unittest.TestCase):
|
||||
self.assertEqual(results[0].identifier, "only-skill")
|
||||
mock_write_cache.assert_called_once()
|
||||
|
||||
def test_parse_identifier_accepts_clawhub_shapes(self):
|
||||
self.assertEqual(ClawHubSource._parse_identifier("skillopt"), ("skillopt", None))
|
||||
self.assertEqual(ClawHubSource._parse_identifier("clawhub/skillopt"), ("skillopt", None))
|
||||
self.assertEqual(
|
||||
ClawHubSource._parse_identifier("@harrylabsj/skillopt"),
|
||||
("skillopt", "harrylabsj"),
|
||||
)
|
||||
self.assertEqual(
|
||||
ClawHubSource._parse_identifier("harrylabsj/skills/skillopt"),
|
||||
("skillopt", "harrylabsj"),
|
||||
)
|
||||
|
||||
def test_parse_identifier_rejects_github_style_paths(self):
|
||||
self.assertIsNone(
|
||||
ClawHubSource._parse_identifier("latipun7/agent-skill-collections/skillopt")
|
||||
)
|
||||
self.assertIsNone(
|
||||
ClawHubSource._parse_identifier(
|
||||
"latipun7/agent-skill-collections/skills/skillopt"
|
||||
)
|
||||
)
|
||||
self.assertIsNone(
|
||||
ClawHubSource._parse_identifier(
|
||||
"skills-sh/latipun7/agent-skill-collections/skills/skillopt"
|
||||
)
|
||||
)
|
||||
|
||||
@patch("tools.skills_hub.httpx.get")
|
||||
def test_inspect_does_not_claim_github_style_identifier(self, mock_get):
|
||||
meta = self.src.inspect("latipun7/agent-skill-collections/skills/skillopt")
|
||||
self.assertIsNone(meta)
|
||||
mock_get.assert_not_called()
|
||||
|
||||
@patch("tools.skills_hub.httpx.get")
|
||||
def test_fetch_does_not_claim_github_style_identifier(self, mock_get):
|
||||
bundle = self.src.fetch("latipun7/agent-skill-collections/skillopt")
|
||||
self.assertIsNone(bundle)
|
||||
mock_get.assert_not_called()
|
||||
|
||||
@patch("tools.skills_hub.httpx.get")
|
||||
def test_inspect_rejects_owner_mismatch_on_clawhub_url_path(self, mock_get):
|
||||
mock_get.return_value = _MockResponse(
|
||||
status_code=200,
|
||||
json_data={
|
||||
"slug": "skillopt",
|
||||
"displayName": "SkillOpt",
|
||||
"summary": "Train, evaluate, and improve Agent skill files",
|
||||
"owner": {"handle": "harrylabsj"},
|
||||
},
|
||||
)
|
||||
|
||||
meta = self.src.inspect("latipun7/skills/skillopt")
|
||||
|
||||
self.assertIsNone(meta)
|
||||
mock_get.assert_called_once()
|
||||
|
||||
|
||||
class TestClawHubCatalogWalkBounded(unittest.TestCase):
|
||||
"""max_items bounds the walk so browse's cold-start fallback renders one
|
||||
|
||||
Reference in New Issue
Block a user