fix(gateway): allow standard skill frontmatter metadata (#1103)

* fix(gateway): allow standard skill frontmatter metadata

Accept standard optional frontmatter fields during .skill installs so external skills with version, author, or compatibility metadata do not fail validation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* docs: sync skill installer metadata behavior

Document the skill install allowlist so user-facing and backend contributor docs match the gateway validation contract.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ryanba
2026-03-13 21:23:35 +08:00
committed by GitHub
parent 03cafea715
commit cda9fb7bca
4 changed files with 83 additions and 4 deletions

View File

@@ -0,0 +1,60 @@
from collections.abc import Callable
from pathlib import Path
from typing import cast
import src.gateway.routers.skills as skills_router
VALIDATE_SKILL_FRONTMATTER = cast(
Callable[[Path], tuple[bool, str, str | None]],
getattr(skills_router, "_validate_skill_frontmatter"),
)
def _write_skill(skill_dir: Path, frontmatter: str) -> None:
skill_dir.mkdir(parents=True, exist_ok=True)
(skill_dir / "SKILL.md").write_text(frontmatter, encoding="utf-8")
def test_validate_skill_frontmatter_allows_standard_optional_metadata(tmp_path: Path) -> None:
skill_dir = tmp_path / "demo-skill"
_write_skill(
skill_dir,
"""---
name: demo-skill
description: Demo skill
version: 1.0.0
author: example.com/demo
compatibility: OpenClaw >= 1.0
license: MIT
---
# Demo Skill
""",
)
valid, message, skill_name = VALIDATE_SKILL_FRONTMATTER(skill_dir)
assert valid is True
assert message == "Skill is valid!"
assert skill_name == "demo-skill"
def test_validate_skill_frontmatter_still_rejects_unknown_keys(tmp_path: Path) -> None:
skill_dir = tmp_path / "demo-skill"
_write_skill(
skill_dir,
"""---
name: demo-skill
description: Demo skill
unsupported: true
---
# Demo Skill
""",
)
valid, message, skill_name = VALIDATE_SKILL_FRONTMATTER(skill_dir)
assert valid is False
assert "unsupported" in message
assert skill_name is None