Codify Skill Action sync/async and task-center gold standards (v1.0.40)
All checks were successful
技能自动化发布 / release (push) Successful in 7s
All checks were successful
技能自动化发布 / release (push) Successful in 7s
This commit is contained in:
@@ -45,6 +45,7 @@ POLICY_IDS = (
|
||||
"POLICY-CONTROL-002",
|
||||
"POLICY-CONTROL-003",
|
||||
"POLICY-DATA-PATH-001",
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
)
|
||||
|
||||
STRUCTURE_PATHS = (
|
||||
@@ -734,6 +735,142 @@ class TestPolicyDataPath001(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
_TEMPLATE_PLACEHOLDER_SLUG = "your-skill-slug"
|
||||
_RPA_SESSION_TOKENS = ("RpaVideoSession",)
|
||||
|
||||
|
||||
def _service_has_rpa_video_session(skill_root: str) -> bool:
|
||||
service_dir = os.path.join(skill_root, "scripts", "service")
|
||||
if not os.path.isdir(service_dir):
|
||||
return False
|
||||
for rel in _walk_files(skill_root, "scripts/service", suffix=".py"):
|
||||
text = _read_text(os.path.join(skill_root, rel))
|
||||
if any(token in text for token in _RPA_SESSION_TOKENS):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _load_actions_json(skill_root: str) -> dict | None:
|
||||
path = os.path.join(skill_root, "assets", "actions.json")
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
import json
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
class TestPolicySkillAction001(unittest.TestCase):
|
||||
def test_skill_action_runtime_doc_exists(self) -> None:
|
||||
path = os.path.join(get_skill_root(), "development", "SKILL_ACTION_RUNTIME.md")
|
||||
self.assertTrue(
|
||||
os.path.isfile(path),
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"development/SKILL_ACTION_RUNTIME.md",
|
||||
"file missing",
|
||||
),
|
||||
)
|
||||
text = _read_text(path)
|
||||
for marker in ("executionProfile", "async", "任务中心", "run_skill_action"):
|
||||
self.assertIn(
|
||||
marker,
|
||||
text,
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"development/SKILL_ACTION_RUNTIME.md",
|
||||
f"missing marker {marker!r}",
|
||||
),
|
||||
)
|
||||
|
||||
def test_schema_defines_execution_profile(self) -> None:
|
||||
import json
|
||||
|
||||
path = os.path.join(get_skill_root(), "assets", "schemas", "skill-actions.schema.json")
|
||||
self.assertTrue(os.path.isfile(path))
|
||||
with open(path, encoding="utf-8") as f:
|
||||
schema = json.load(f)
|
||||
action_props = schema.get("$defs", {}).get("action", {}).get("properties", {})
|
||||
profile = action_props.get("executionProfile")
|
||||
self.assertIsInstance(
|
||||
profile,
|
||||
dict,
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"assets/schemas/skill-actions.schema.json",
|
||||
"action.properties.executionProfile missing",
|
||||
),
|
||||
)
|
||||
self.assertEqual(set(profile.get("enum") or []), {"sync", "async"})
|
||||
|
||||
def test_actions_md_documents_execution_profile(self) -> None:
|
||||
path = os.path.join(get_skill_root(), "references", "ACTIONS.md")
|
||||
text = _read_text(path)
|
||||
for marker in ("executionProfile", "async", "任务中心", "SKILL_ACTION_RUNTIME"):
|
||||
self.assertIn(
|
||||
marker,
|
||||
text,
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"references/ACTIONS.md",
|
||||
f"missing marker {marker!r}",
|
||||
),
|
||||
)
|
||||
|
||||
def test_actions_json_actions_declare_execution_profile(self) -> None:
|
||||
skill_root = get_skill_root()
|
||||
manifest = _load_actions_json(skill_root)
|
||||
if manifest is None:
|
||||
return
|
||||
offenders: list[str] = []
|
||||
for action in manifest.get("actions") or []:
|
||||
action_id = action.get("id", "?")
|
||||
profile = action.get("executionProfile")
|
||||
if profile not in ("sync", "async"):
|
||||
offenders.append(f"{action_id}: executionProfile={profile!r}")
|
||||
self.assertEqual(
|
||||
offenders,
|
||||
[],
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"assets/actions.json",
|
||||
"every action must set executionProfile to sync|async:\n" + "\n".join(offenders),
|
||||
),
|
||||
)
|
||||
|
||||
def test_business_rpa_skill_has_async_agent_action(self) -> None:
|
||||
from util.constants import SKILL_SLUG
|
||||
|
||||
skill_root = get_skill_root()
|
||||
if SKILL_SLUG == _TEMPLATE_PLACEHOLDER_SLUG:
|
||||
self.skipTest("template placeholder slug skips business RPA action check")
|
||||
if not _service_has_rpa_video_session(skill_root):
|
||||
self.skipTest("no RpaVideoSession in scripts/service")
|
||||
manifest = _load_actions_json(skill_root)
|
||||
self.assertIsNotNone(
|
||||
manifest,
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"assets/actions.json",
|
||||
"RPA skill must provide assets/actions.json",
|
||||
),
|
||||
)
|
||||
found = False
|
||||
for action in manifest.get("actions") or []:
|
||||
placements = set(action.get("placements") or [])
|
||||
if action.get("executionProfile") == "async" and "agent" in placements:
|
||||
found = True
|
||||
break
|
||||
self.assertTrue(
|
||||
found,
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-001",
|
||||
"assets/actions.json",
|
||||
"RPA business skill needs at least one action with executionProfile=async and placements containing agent",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
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