fix(profiles): canonical reconciler ops and guard raw branch delete (#687)
Address REQUEST_CHANGES on PR #688: - migrate_profiles: emit fully canonical reconciler/author/reviewer defaults; canonicalize explicit ops; fail if reconciler loses required pr.close/read - gitea_delete_branch: deny reconciler (and non-author roles); refuse preservation/protected branches before any API call - gitea_cleanup_merged_pr_branch: require reconciler role only; block preservation/evidence branches via branch_cleanup_guard - docs: end-to-end operator runbook for profile migrate/apply/reconnect/cleanup - tests: migration canonicalization, idempotency, raw-delete denial, guarded cleanup success and unmerged/preserve rejections Closes #687.
This commit is contained in:
+70
-7
@@ -5925,6 +5925,65 @@ def gitea_delete_branch(
|
||||
"permission_report": _permission_block_report("gitea.branch.delete"),
|
||||
}
|
||||
|
||||
# Possessing gitea.branch.delete alone is not enough for arbitrary deletion.
|
||||
# task_capability_map maps delete_branch → author; reconciler must use the
|
||||
# guarded cleanup_merged_pr_branch path only (#687 / #514).
|
||||
profile = get_profile()
|
||||
active_role = _profile_role_kind(profile)
|
||||
required_role = task_capability_map.required_role("delete_branch")
|
||||
if active_role == "reconciler":
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"required_permission": "gitea.branch.delete",
|
||||
"required_role_kind": required_role,
|
||||
"active_role_kind": active_role,
|
||||
"reasons": [
|
||||
"reconciler profile cannot use raw gitea_delete_branch; "
|
||||
"use gitea_cleanup_merged_pr_branch for a fully merged PR "
|
||||
"source branch only (fail closed)"
|
||||
],
|
||||
"exact_next_action": (
|
||||
"Call gitea_cleanup_merged_pr_branch with pr_number, the "
|
||||
"exact PR head branch, and confirmation "
|
||||
"'CLEANUP MERGED PR <n> BRANCH <branch>' after capability "
|
||||
"resolve for cleanup_merged_pr_branch."
|
||||
),
|
||||
"permission_report": _permission_block_report("gitea.branch.delete"),
|
||||
}
|
||||
if active_role != required_role:
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"required_permission": "gitea.branch.delete",
|
||||
"required_role_kind": required_role,
|
||||
"active_role_kind": active_role,
|
||||
"reasons": [
|
||||
f"Active profile role '{active_role}' cannot perform "
|
||||
f"{required_role} task 'delete_branch' even when "
|
||||
"gitea.branch.delete is present (fail closed)"
|
||||
],
|
||||
"permission_report": _permission_block_report("gitea.branch.delete"),
|
||||
}
|
||||
|
||||
if branch_cleanup_guard.is_preservation_or_evidence_branch(branch):
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"required_permission": "gitea.branch.delete",
|
||||
"reasons": [
|
||||
f"branch '{branch}' is a preservation/evidence branch and "
|
||||
"cannot be deleted (fail closed)"
|
||||
],
|
||||
}
|
||||
if branch in branch_cleanup_guard.PROTECTED_BRANCHES:
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"required_permission": "gitea.branch.delete",
|
||||
"reasons": [f"branch '{branch}' is protected (fail closed)"],
|
||||
}
|
||||
|
||||
audit_allowed, audit_reasons = (
|
||||
audit_reconciliation_mode.check_audit_mutation_allowed("delete_branch")
|
||||
)
|
||||
@@ -5976,18 +6035,20 @@ def gitea_cleanup_merged_pr_branch(
|
||||
}
|
||||
|
||||
profile = get_profile()
|
||||
active_role = _role_kind(
|
||||
profile.get("allowed_operations", []),
|
||||
profile.get("forbidden_operations", []),
|
||||
)
|
||||
if active_role == "reviewer":
|
||||
active_role = _profile_role_kind(profile)
|
||||
# cleanup_merged_pr_branch is reconciler-owned (task_capability_map).
|
||||
# Author/reviewer/merger must not reach this path even if they somehow
|
||||
# hold gitea.branch.delete.
|
||||
if active_role != "reconciler":
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"required_permission": "gitea.branch.delete",
|
||||
"required_role_kind": "reconciler",
|
||||
"active_role_kind": active_role,
|
||||
"reasons": [
|
||||
"reviewer profile is not authorized for merged branch cleanup "
|
||||
"(fail closed)"
|
||||
f"profile role '{active_role}' is not authorized for merged "
|
||||
"branch cleanup; required role is reconciler (fail closed)"
|
||||
],
|
||||
"permission_report": _permission_block_report("gitea.branch.delete"),
|
||||
}
|
||||
@@ -11269,6 +11330,8 @@ def gitea_resolve_task_capability(
|
||||
"gitea_commit_files",
|
||||
"address_pr_change_requests",
|
||||
"delete_branch",
|
||||
"cleanup_merged_pr_branch",
|
||||
"reconciliation_cleanup",
|
||||
"work_issue",
|
||||
"work-issue",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user