mirror of
https://git.openapi.site/https://github.com/desirecore/market.git
synced 2026-07-23 04:23:46 +08:00
fix(skills): 禁止自动注入完整技能内容 (#49)
## Summary - 将 dashscope-image-gen、image-to-image、markdown、tech-diagram、xiaomi-tts 改为仅按需加载,并递增 patch 版本 - 在 Market validator 与 JSON Schema 中禁止 `disable-model-invocation: false` - 将回归测试接入 `i18n Validate`,同步中英文技能编写规范 ## Root cause and impact 这 5 个技能在首次引入时即声明 `disable-model-invocation: false`,导致完整技能正文进入普通请求的 system prompt。修改后市场技能只能声明 `true` 或省略该字段,完整内容仅在显式调用 Skill 工具后加载。 ## Validation - `uv run --quiet scripts/i18n/test_validate_i18n.py` - `uv run --quiet scripts/i18n/validate-i18n.py` - `uv run --quiet scripts/i18n/translate.py --check` - `python3 -m json.tool scripts/i18n/schema/skill-frontmatter.schema.json` - `actionlint .github/workflows/i18n-validate.yml` - `git diff --check`
This commit is contained in:
@@ -48,7 +48,10 @@
|
||||
"enum": ["enabled", "disabled", "deprecated", "experimental"]
|
||||
},
|
||||
"disable-model-invocation": {
|
||||
"type": "boolean"
|
||||
"description": "Must be true or omitted. Market skills may only load their full instructions on demand; automatic full-content injection is prohibited.",
|
||||
"type": "boolean",
|
||||
"const": true,
|
||||
"default": true
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
|
||||
52
scripts/i18n/test_validate_i18n.py
Normal file
52
scripts/i18n/test_validate_i18n.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["pyyaml>=6.0"]
|
||||
# ///
|
||||
"""Unit tests for market validation policies."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
VALIDATOR_PATH = Path(__file__).with_name("validate-i18n.py")
|
||||
SPEC = importlib.util.spec_from_file_location("market_validate_i18n", VALIDATOR_PATH)
|
||||
assert SPEC is not None and SPEC.loader is not None
|
||||
VALIDATOR = importlib.util.module_from_spec(SPEC)
|
||||
sys.modules[SPEC.name] = VALIDATOR
|
||||
SPEC.loader.exec_module(VALIDATOR)
|
||||
|
||||
|
||||
class ModelInvocationPolicyTests(unittest.TestCase):
|
||||
def validate(self, frontmatter: dict[str, object]) -> list[object]:
|
||||
report = VALIDATOR.Report()
|
||||
VALIDATOR.validate_model_invocation_policy(
|
||||
frontmatter,
|
||||
"skills/example/SKILL.md",
|
||||
report,
|
||||
)
|
||||
return report.issues
|
||||
|
||||
def test_allows_true(self) -> None:
|
||||
self.assertEqual([], self.validate({"disable-model-invocation": True}))
|
||||
|
||||
def test_allows_omitted_field(self) -> None:
|
||||
self.assertEqual([], self.validate({}))
|
||||
|
||||
def test_rejects_false(self) -> None:
|
||||
issues = self.validate({"disable-model-invocation": False})
|
||||
self.assertEqual(1, len(issues))
|
||||
self.assertEqual("model-invocation-policy", issues[0].rule)
|
||||
|
||||
def test_rejects_non_boolean_value(self) -> None:
|
||||
issues = self.validate({"disable-model-invocation": "true"})
|
||||
self.assertEqual(1, len(issues))
|
||||
self.assertEqual("model-invocation-policy", issues[0].rule)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -18,6 +18,7 @@ Checks:
|
||||
9. Skill/Agent counts and builtin skill index match the repository contents.
|
||||
10. Skill, Agent, and entry.json category references exist in categories.json.
|
||||
11. entry.json pointers have the required marketplace fields and safe source URLs.
|
||||
12. Market Skills set `disable-model-invocation` to true or omit it; false is prohibited.
|
||||
|
||||
Exit codes:
|
||||
0 = pass
|
||||
@@ -108,6 +109,21 @@ def heading_count(text: str) -> int:
|
||||
return len(HEADING_PATTERN.findall(text or ""))
|
||||
|
||||
|
||||
def validate_model_invocation_policy(
|
||||
frontmatter: dict[str, Any],
|
||||
path: str,
|
||||
report: Report,
|
||||
) -> None:
|
||||
"""Reject market skills that request full-content system-prompt injection."""
|
||||
value = frontmatter.get("disable-model-invocation")
|
||||
if value is not None and value is not True:
|
||||
report.add(Issue(
|
||||
path,
|
||||
"model-invocation-policy",
|
||||
"disable-model-invocation must be true or omitted; automatic full-content injection is prohibited",
|
||||
))
|
||||
|
||||
|
||||
def validate_skill(
|
||||
skill_dir: Path,
|
||||
report: Report,
|
||||
@@ -128,6 +144,8 @@ def validate_skill(
|
||||
return
|
||||
assert fm is not None and body is not None
|
||||
|
||||
validate_model_invocation_policy(fm, f"{rel_dir}/SKILL.md", report)
|
||||
|
||||
name = fm.get("name", "")
|
||||
description = fm.get("description", "")
|
||||
market = fm.get("market") or {}
|
||||
|
||||
Reference in New Issue
Block a user