"""Invariant tests pinning task_capability_map role assignments (#722/#723). Added with the operator-authorized break-glass repair for incident #722: commit 970e68b remapped ten reviewer tasks to ``role="merger"`` while every configured merger profile forbids the review permissions, so no configured profile could resolve any formal review task (``matching_configured_profile`` was empty repository-wide). These tests fail loudly if that class of regression recurs: - every role-exclusive formal-review task must be satisfiable by at least one canonical role profile (permission AND role together); - the capability map must agree with ``role_session_router`` task sets; - ``adopt_merger_pr_lease`` stays merger-only (the legitimate hunk of 970e68b, preserved by the repair); - merger profiles must not be able to resolve review_pr/approve_pr. """ import unittest import gitea_config from role_session_router import MERGER_TASKS, REVIEWER_TASKS from task_capability_map import ( ROLE_EXCLUSIVE_TASKS, TASK_CAPABILITY_MAP, required_permission, required_role, ) # Canonical role-profile permission shape. Mirrors the configured # author/reviewer/merger/reconciler profiles (profiles.json v2 role split): # reviewers review/approve/request changes but never merge; mergers merge but # never review/approve/request changes. CANONICAL_ROLE_PROFILES = { "author": { "allowed": [ "gitea.read", "gitea.branch.create", "gitea.branch.push", "gitea.repo.commit", "gitea.pr.create", "gitea.pr.comment", "gitea.issue.create", "gitea.issue.comment", "gitea.issue.close", ], "forbidden": [ "gitea.pr.approve", "gitea.pr.request_changes", "gitea.pr.merge", ], }, "reviewer": { "allowed": [ "gitea.read", "gitea.pr.review", "gitea.pr.approve", "gitea.pr.request_changes", "gitea.pr.comment", "gitea.issue.comment", ], "forbidden": [ "gitea.branch.create", "gitea.branch.push", "gitea.repo.commit", "gitea.pr.create", "gitea.pr.merge", ], }, "merger": { "allowed": [ "gitea.read", "gitea.pr.merge", "gitea.pr.comment", "gitea.issue.comment", ], "forbidden": [ "gitea.branch.create", "gitea.branch.push", "gitea.repo.commit", "gitea.pr.create", "gitea.pr.approve", "gitea.pr.review", "gitea.pr.request_changes", ], }, "reconciler": { "allowed": [ "gitea.read", "gitea.pr.close", "gitea.pr.comment", "gitea.issue.comment", "gitea.branch.delete", "gitea.decision_lock.irrecoverable_recovery", ], "forbidden": [ "gitea.pr.approve", "gitea.pr.merge", "gitea.pr.review", "gitea.pr.request_changes", "gitea.pr.create", "gitea.branch.create", "gitea.branch.push", "gitea.repo.commit", ], }, } # Role-exclusive formal-review tasks (mirrors the resolver's role-exclusive # handling for review work): permission alone is not enough — the profile's # role kind must also match, so both dimensions are pinned here. FORMAL_REVIEW_TASKS = ( "review_pr", "approve_pr", "request_changes_pr", "blind_pr_queue_review", "pr_queue_cleanup", "pr-queue-cleanup", ) # Complete resolver role-exclusive set on master when #723 was reconstructed. # The shared constant must replace this exact inline authority without dropping # later lease and PR-sync aliases added after the preserved source commits. EXPECTED_ROLE_EXCLUSIVE_TASKS = frozenset( { "acquire_reviewer_pr_lease", "gitea_acquire_reviewer_pr_lease", "review_pr", "approve_pr", "request_changes_pr", "blind_pr_queue_review", "pr_queue_cleanup", "pr-queue-cleanup", "merge_pr", "acquire_merger_pr_lease", "gitea_acquire_merger_pr_lease", "adopt_merger_pr_lease", "gitea_adopt_merger_pr_lease", "release_merger_pr_lease", "gitea_release_merger_pr_lease", "create_branch", "push_branch", "create_pr", "commit_files", "gitea_commit_files", "address_pr_change_requests", "update_pr_branch_by_merge", "gitea_update_pr_branch_by_merge", "delete_branch", "cleanup_merged_pr_branch", "reconciliation_cleanup", "work_issue", "work-issue", } ) def _profile_satisfies(role_name, task): """True when the canonical *role_name* profile can perform *task*.""" profile = CANONICAL_ROLE_PROFILES[role_name] ok, _reason = gitea_config.check_operation( required_permission(task), profile["allowed"], profile["forbidden"] ) return ok and role_name == required_role(task) class TestFormalReviewProfileCoverage(unittest.TestCase): """#722: some configured profile must be able to formally review.""" def test_every_formal_review_task_has_a_satisfying_role_profile(self): for task in FORMAL_REVIEW_TASKS: with self.subTest(task=task): satisfying = [ role for role in CANONICAL_ROLE_PROFILES if _profile_satisfies(role, task) ] self.assertTrue( satisfying, f"no canonical role profile satisfies both permission " f"{required_permission(task)!r} and role " f"{required_role(task)!r} for task {task!r} — formal " f"review would be impossible for every configured " f"profile (incident #722)", ) def test_formal_review_tasks_are_reviewer_role(self): for task in FORMAL_REVIEW_TASKS: with self.subTest(task=task): self.assertEqual(required_role(task), "reviewer") class TestMapRouterAgreement(unittest.TestCase): """#723 AC2: the map and the role session router must not drift.""" def test_reviewer_tasks_map_to_reviewer_role(self): for task in sorted(REVIEWER_TASKS): with self.subTest(task=task): self.assertEqual( required_role(task), "reviewer", f"router classifies {task!r} as a reviewer task but the " f"capability map assigns role {required_role(task)!r}", ) def test_merger_tasks_map_to_merger_role(self): for task in sorted(MERGER_TASKS): with self.subTest(task=task): self.assertEqual( required_role(task), "merger", f"router classifies {task!r} as a merger task but the " f"capability map assigns role {required_role(task)!r}", ) class TestMergerBoundary(unittest.TestCase): """Preserve the legitimate hunk of 970e68b and the merger fence.""" def test_adopt_merger_pr_lease_requires_merger_role(self): self.assertEqual(required_role("adopt_merger_pr_lease"), "merger") self.assertEqual( required_permission("adopt_merger_pr_lease"), "gitea.pr.comment" ) def test_merge_pr_requires_merger_role(self): self.assertEqual(required_role("merge_pr"), "merger") self.assertEqual(required_permission("merge_pr"), "gitea.pr.merge") def test_merger_profile_cannot_resolve_formal_review_tasks(self): merger = CANONICAL_ROLE_PROFILES["merger"] for task in ("review_pr", "approve_pr", "request_changes_pr"): with self.subTest(task=task): ok, reason = gitea_config.check_operation( required_permission(task), merger["allowed"], merger["forbidden"], ) self.assertFalse( ok, f"merger profile must not hold {task!r} permission " f"(got reason {reason!r})", ) class TestRoleExclusiveSetIntegrity(unittest.TestCase): """#723: the shared set is complete, mapped, and role-satisfiable.""" def test_complete_current_role_exclusive_set(self): self.assertEqual(ROLE_EXCLUSIVE_TASKS, EXPECTED_ROLE_EXCLUSIVE_TASKS) def test_every_role_exclusive_task_exists_in_capability_map(self): for task in sorted(ROLE_EXCLUSIVE_TASKS): with self.subTest(task=task): self.assertIn(task, TASK_CAPABILITY_MAP) def test_formal_review_tasks_are_role_exclusive(self): self.assertTrue(set(FORMAL_REVIEW_TASKS) <= ROLE_EXCLUSIVE_TASKS) def test_every_role_exclusive_task_has_a_satisfying_profile(self): for task in sorted(ROLE_EXCLUSIVE_TASKS): with self.subTest(task=task): role = required_role(task) self.assertIn(role, CANONICAL_ROLE_PROFILES) self.assertTrue( _profile_satisfies(role, task), f"canonical {role!r} profile cannot satisfy {task!r}", ) if __name__ == "__main__": unittest.main()