chore: auto release commit (2026-07-14 11:56:45)
All checks were successful
技能自动化发布 / release (push) Successful in 5s
All checks were successful
技能自动化发布 / release (push) Successful in 5s
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user