chore: auto release commit (2026-07-14 11:56:45)
All checks were successful
技能自动化发布 / release (push) Successful in 5s

This commit is contained in:
2026-07-14 11:56:46 +08:00
parent a7baba7210
commit af7a43a702
17 changed files with 1239 additions and 123 deletions

View File

@@ -46,6 +46,11 @@ POLICY_IDS = (
"POLICY-CONTROL-003",
"POLICY-DATA-PATH-001",
"POLICY-SKILL-ACTION-001",
"POLICY-SKILL-ACTION-002",
"POLICY-SKILL-ACTION-003",
"POLICY-SKILL-ACTION-004",
"POLICY-ARCH-CORE-001",
"POLICY-DATA-SYNC-001",
)
STRUCTURE_PATHS = (
@@ -871,6 +876,241 @@ class TestPolicySkillAction001(unittest.TestCase):
)
class TestPolicySkillAction002(unittest.TestCase):
def test_schema_requires_execution_profile(self) -> None:
import json
path = os.path.join(get_skill_root(), "assets", "schemas", "skill-actions.schema.json")
with open(path, encoding="utf-8") as f:
schema = json.load(f)
action = schema["$defs"]["action"]
self.assertIn(
"executionProfile",
action.get("required") or [],
msg=_policy_msg(
"POLICY-SKILL-ACTION-002",
"assets/schemas/skill-actions.schema.json",
"executionProfile must be in action.required",
),
)
self.assertEqual(set(action["properties"]["executionProfile"].get("enum") or []), {"sync", "async"})
def test_manifest_actions_declare_execution_profile(self) -> None:
manifest = _load_actions_json(get_skill_root())
if manifest is None:
return
offenders: list[str] = []
for action in manifest.get("actions") or []:
profile = action.get("executionProfile")
if profile not in ("sync", "async"):
offenders.append(f"{action.get('id', '?')}: {profile!r}")
self.assertEqual(
offenders,
[],
msg=_policy_msg(
"POLICY-SKILL-ACTION-002",
"assets/actions.json",
"every action must set executionProfile to sync|async:\n" + "\n".join(offenders),
),
)
class TestPolicySkillAction003(unittest.TestCase):
def test_schema_defines_bind_and_toolbar_requires_it(self) -> None:
import json
path = os.path.join(get_skill_root(), "assets", "schemas", "skill-actions.schema.json")
with open(path, encoding="utf-8") as f:
schema = json.load(f)
self.assertIn(
"bind",
schema.get("$defs", {}),
msg=_policy_msg(
"POLICY-SKILL-ACTION-003",
"assets/schemas/skill-actions.schema.json",
"$defs.bind missing",
),
)
action = schema["$defs"]["action"]
self.assertIn("bind", action.get("properties", {}))
then_required = (action.get("then") or {}).get("required") or []
self.assertIn(
"bind",
then_required,
msg=_policy_msg(
"POLICY-SKILL-ACTION-003",
"assets/schemas/skill-actions.schema.json",
"action if/then must require bind when placements contains toolbar",
),
)
def test_toolbar_actions_have_bind_tables(self) -> None:
import re
table_re = re.compile(r"^[a-z][a-z0-9_]*$")
manifest = _load_actions_json(get_skill_root())
if manifest is None:
return
offenders: list[str] = []
for action in manifest.get("actions") or []:
if "toolbar" not in (action.get("placements") or []):
continue
bind = action.get("bind")
tables = bind.get("tables") if isinstance(bind, dict) else None
if not isinstance(tables, list) or not tables:
offenders.append(f"{action.get('id')}: missing bind.tables")
continue
if len(tables) != len(set(tables)):
offenders.append(f"{action.get('id')}: duplicate bind.tables")
for table in tables:
if not isinstance(table, str) or not table_re.fullmatch(table):
offenders.append(f"{action.get('id')}: invalid table {table!r}")
self.assertEqual(
offenders,
[],
msg=_policy_msg(
"POLICY-SKILL-ACTION-003",
"assets/actions.json",
"toolbar actions need legal bind.tables:\n" + "\n".join(offenders),
),
)
class TestPolicySkillAction004(unittest.TestCase):
def _forbidden_coupling_phrases(self) -> tuple[str, ...]:
# 拆开拼接,避免本测试源码或废止说明原文触发误报。
return (
"数据管理 toolbar " + "只挂",
"toolbar " + "只放 async",
"只挂 **" + "async** action",
"toolbar " + "只能 async",
"toolbar only " + "async",
)
def test_docs_do_not_couple_toolbar_to_async(self) -> None:
skill_root = get_skill_root()
paths = (
os.path.join(skill_root, "references", "ACTIONS.md"),
os.path.join(skill_root, "development", "SKILL_ACTION_RUNTIME.md"),
os.path.join(skill_root, "SKILL.md"),
)
hits: list[str] = []
for path in paths:
text = _read_text(path)
for phrase in self._forbidden_coupling_phrases():
if phrase in text:
hits.append(f"{os.path.relpath(path, skill_root)}: {phrase!r}")
self.assertEqual(
hits,
[],
msg=_policy_msg(
"POLICY-SKILL-ACTION-004",
"docs",
"placements must stay orthogonal to executionProfile:\n" + "\n".join(hits),
),
)
def test_runtime_doc_states_orthogonality(self) -> None:
text = _read_text(os.path.join(get_skill_root(), "development", "SKILL_ACTION_RUNTIME.md"))
for marker in ("正交", "sync|async", "toolbar"):
self.assertIn(
marker,
text,
msg=_policy_msg(
"POLICY-SKILL-ACTION-004",
"development/SKILL_ACTION_RUNTIME.md",
f"missing marker {marker!r}",
),
)
def test_schema_then_only_requires_bind_not_execution_profile(self) -> None:
"""toolbar→bind 的 if/then 不得顺带约束 executionProfile。"""
import json
path = os.path.join(get_skill_root(), "assets", "schemas", "skill-actions.schema.json")
with open(path, encoding="utf-8") as f:
schema = json.load(f)
action = schema["$defs"]["action"]
then = action.get("then") or {}
self.assertEqual(
then.get("required"),
["bind"],
msg=_policy_msg(
"POLICY-SKILL-ACTION-004",
"assets/schemas/skill-actions.schema.json",
"action.then must only require bind",
),
)
self.assertNotIn("properties", then)
self.assertNotIn("else", action)
matrix_test = os.path.join(get_skill_root(), "tests", "test_actions_manifest.py")
text = _read_text(matrix_test)
self.assertIn(
"test_schema_allows_all_placement_execution_profile_combinations",
text,
msg=_policy_msg(
"POLICY-SKILL-ACTION-004",
"tests/test_actions_manifest.py",
"missing Schema 4×2 orthogonality matrix test",
),
)
class TestPolicyArchCore001(unittest.TestCase):
def test_docs_declare_single_core_multi_entry(self) -> None:
skill_root = get_skill_root()
text = (
_read_text(os.path.join(skill_root, "development", "DEVELOPMENT.md"))
+ "\n"
+ _read_text(os.path.join(skill_root, "development", "SKILL_ACTION_RUNTIME.md"))
+ "\n"
+ _read_text(os.path.join(skill_root, "SKILL.md"))
)
for marker in ("单业务内核", "多入口", "domain service"):
self.assertIn(
marker,
text,
msg=_policy_msg(
"POLICY-ARCH-CORE-001",
"development docs / SKILL.md",
f"missing architecture marker {marker!r}",
),
)
sample = os.path.join(skill_root, "tests", "samples", "test_action_core_contract.py.sample")
self.assertTrue(
os.path.isfile(sample),
msg=_policy_msg(
"POLICY-ARCH-CORE-001",
"tests/samples/test_action_core_contract.py.sample",
"sample contract missing",
),
)
class TestPolicyDataSync001(unittest.TestCase):
def test_schema_md_declares_sync_semantics(self) -> None:
text = _read_text(os.path.join(get_skill_root(), "references", "SCHEMA.md"))
for marker in ("幂等", "空结果", "事务", "inserted", "导出当前数据表"):
self.assertIn(
marker,
text,
msg=_policy_msg(
"POLICY-DATA-SYNC-001",
"references/SCHEMA.md",
f"missing sync marker {marker!r}",
),
)
sample = os.path.join(get_skill_root(), "tests", "samples", "test_sync_contract.py.sample")
self.assertTrue(
os.path.isfile(sample),
msg=_policy_msg(
"POLICY-DATA-SYNC-001",
"tests/samples/test_sync_contract.py.sample",
"sample contract missing",
),
)
class TestPolicyDocs001(unittest.TestCase):
def test_policy_matrix_exists_and_lists_policy_ids(self) -> None:
skill_root = get_skill_root()