50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from engine.devops_agent.spec import WorkflowSpec
|
|
|
|
WRITE_PERMISSIONS = {"issues", "pull_requests", "contents"}
|
|
|
|
|
|
def _is_write_permission(value: Any) -> bool:
|
|
return str(value).strip().lower() == "write"
|
|
|
|
|
|
def validate_workflow_spec(spec: WorkflowSpec) -> list[str]:
|
|
errors: list[str] = []
|
|
|
|
if spec.provider not in {"gitea"}:
|
|
errors.append(f"unsupported provider: {spec.provider}")
|
|
|
|
triggers = spec.frontmatter.get("on")
|
|
if not isinstance(triggers, dict) or not triggers:
|
|
errors.append("workflow spec must declare at least one trigger in 'on'")
|
|
|
|
permissions = spec.frontmatter.get("permissions") or {}
|
|
safe_outputs = spec.frontmatter.get("safe_outputs") or {}
|
|
|
|
if not isinstance(permissions, dict):
|
|
errors.append("'permissions' must be a mapping")
|
|
if not isinstance(safe_outputs, dict):
|
|
errors.append("'safe_outputs' must be a mapping")
|
|
if isinstance(permissions, dict):
|
|
has_write_permission = any(
|
|
permission_name in WRITE_PERMISSIONS and _is_write_permission(permission_value)
|
|
for permission_name, permission_value in permissions.items()
|
|
)
|
|
if has_write_permission and not safe_outputs:
|
|
errors.append("write permissions require declared safe_outputs")
|
|
|
|
policy = spec.frontmatter.get("policy") or {}
|
|
if policy and not isinstance(policy, dict):
|
|
errors.append("'policy' must be a mapping")
|
|
elif isinstance(policy, dict) and "path_scope" in policy:
|
|
path_scope = policy["path_scope"]
|
|
if not isinstance(path_scope, list) or any(
|
|
not isinstance(item, str) or not item.strip() for item in path_scope
|
|
):
|
|
errors.append("policy.path_scope must be a list of non-empty path prefixes")
|
|
|
|
return errors
|