"""Shared task→permission map for resolver and tool gates (#69). ``gitea_resolve_task_capability`` and issue-mutating MCP tools must agree on which profile operation each task requires. This module is the single source of truth; regression tests assert tool gates cannot drift from it. """ from __future__ import annotations TASK_CAPABILITY_MAP: dict[str, dict[str, str]] = { "create_issue": { "permission": "gitea.issue.create", "role": "author", }, "comment_issue": { "permission": "gitea.issue.comment", "role": "author", }, "close_issue": { "permission": "gitea.issue.close", "role": "author", }, "claim_issue": { "permission": "gitea.issue.comment", "role": "author", }, "mark_issue": { "permission": "gitea.issue.comment", "role": "author", }, "lock_issue": { "permission": "gitea.issue.comment", "role": "author", }, "set_issue_labels": { "permission": "gitea.issue.comment", "role": "author", }, "create_branch": { "permission": "gitea.branch.create", "role": "author", }, "push_branch": { "permission": "gitea.branch.push", "role": "author", }, "create_pr": { "permission": "gitea.pr.create", "role": "author", }, "comment_pr": { "permission": "gitea.pr.comment", "role": "author", }, "close_pr": { "permission": "gitea.pr.close", "role": "author", }, "address_pr_change_requests": { "permission": "gitea.branch.push", "role": "author", }, "review_pr": { "permission": "gitea.pr.review", "role": "reviewer", }, "merge_pr": { "permission": "gitea.pr.merge", "role": "merger", }, "blind_pr_queue_review": { "permission": "gitea.pr.review", "role": "reviewer", }, "pr_queue_cleanup": { "permission": "gitea.pr.review", "role": "reviewer", }, "pr-queue-cleanup": { "permission": "gitea.pr.review", "role": "reviewer", }, "request_changes_pr": { "permission": "gitea.pr.request_changes", "role": "reviewer", }, "approve_pr": { "permission": "gitea.pr.approve", "role": "reviewer", }, "delete_branch": { "permission": "gitea.branch.delete", "role": "author", }, "commit_files": { "permission": "gitea.repo.commit", "role": "author", }, "gitea_commit_files": { "permission": "gitea.repo.commit", "role": "author", }, "reconcile_merged_cleanups": { "permission": "gitea.read", "role": "author", }, "work_issue": { "permission": "gitea.pr.create", "role": "author", }, "work-issue": { "permission": "gitea.pr.create", "role": "author", }, "reconcile_landed_pr": { "permission": "gitea.read", "role": "author", }, "reconcile-landed-pr": { "permission": "gitea.read", "role": "author", }, "reconcile_already_landed_pr": { "permission": "gitea.pr.close", "role": "reconciler", }, # #309: dedicated reconciler path for already-landed open PRs. Exact # close capabilities only — never review/approve/request_changes/merge. "reconcile_close_landed_pr": { "permission": "gitea.pr.close", "role": "reconciler", }, "reconcile_close_landed_issue": { "permission": "gitea.issue.close", "role": "reconciler", }, "post_heartbeat": { "permission": "gitea.issue.comment", "role": "author", }, "reconcile_issue_claims": { "permission": "gitea.read", "role": "author", }, "cleanup_stale_claims": { "permission": "gitea.issue.comment", "role": "author", }, } # Issue-mutating MCP tools and their resolver task keys. ISSUE_MUTATION_TOOL_TASKS: dict[str, str] = { "gitea_create_issue": "create_issue", "gitea_close_issue": "close_issue", "gitea_create_issue_comment": "comment_issue", "gitea_mark_issue": "mark_issue", "gitea_set_issue_labels": "set_issue_labels", "gitea_commit_files": "commit_files", } def required_permission(task: str) -> str: """Return the canonical operation a *task* requires (fail closed).""" try: return TASK_CAPABILITY_MAP[task]["permission"] except KeyError as exc: raise KeyError(f"Unknown task/action: {task!r} (fail closed)") from exc def required_role(task: str) -> str: """Return author/reviewer role kind for *task* (fail closed).""" try: return TASK_CAPABILITY_MAP[task]["role"] except KeyError as exc: raise KeyError(f"Unknown task/action: {task!r} (fail closed)") from exc def tool_required_permission(tool_name: str) -> str: """Return the operation an issue-mutating tool must gate on.""" return required_permission(ISSUE_MUTATION_TOOL_TASKS[tool_name])