fix(mcp): clear sticky reviewer denial on allowed author task route (#238)
Add sync_from_capability_result() so reviewer terminal mode is operation-scoped: a later allowed author capability resolution clears stale denial state and unblocks read-only tools like gitea_list_prs. Error messages now name the denied task and explain how to clear stale state. Inline trust-gate parsing fixes broken imports in assess_capability_stop_report on master. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
+32
-13
@@ -53,6 +53,28 @@ def enter_from_capability_result(capability: dict) -> dict | None:
|
||||
return dict(record)
|
||||
|
||||
|
||||
def _is_reviewer_denial(capability: dict) -> bool:
|
||||
task = (capability or {}).get("requested_task", "")
|
||||
required_role = (capability or {}).get("required_role_kind")
|
||||
return (
|
||||
required_role == "reviewer"
|
||||
or task in REVIEWER_CAPABILITY_TASKS
|
||||
)
|
||||
|
||||
|
||||
def sync_from_capability_result(capability: dict) -> dict | None:
|
||||
"""Enter or clear terminal mode from a capability resolution (#238).
|
||||
|
||||
Reviewer denials activate terminal mode for the denied operation only.
|
||||
A later allowed task route clears stale denial state so author read-only
|
||||
tools (e.g. ``list_prs``) are not permanently blocked.
|
||||
"""
|
||||
if (capability or {}).get("stop_required") and _is_reviewer_denial(capability):
|
||||
return enter_from_capability_result(capability)
|
||||
clear()
|
||||
return None
|
||||
|
||||
|
||||
def enter_from_route_result(route: dict) -> dict | None:
|
||||
"""Enter terminal mode from a role router wrong_role_stop (#206 compat)."""
|
||||
if (route or {}).get("route_result") != "wrong_role_stop":
|
||||
@@ -90,11 +112,13 @@ def check_reviewer_queue_tool(tool_name: str) -> tuple[bool, list[str]]:
|
||||
return True, []
|
||||
name = (tool_name or "").strip().lower().removeprefix("gitea_")
|
||||
if name in BLOCKED_QUEUE_TOOLS:
|
||||
denied_task = (_session_terminal or {}).get("requested_task") or "unknown"
|
||||
return False, [
|
||||
TERMINAL_REPORT_HEADING,
|
||||
f"Reviewer queue tool '{tool_name}' is blocked after "
|
||||
"capability denial (fail closed).",
|
||||
"Relaunch a reviewer MCP namespace to perform reviewer work.",
|
||||
f"Reviewer queue tool '{tool_name}' is blocked by the current "
|
||||
f"capability denial for task '{denied_task}' (fail closed).",
|
||||
"Resolve or route an allowed author task to clear stale denial "
|
||||
"state, or relaunch a reviewer MCP namespace for reviewer work.",
|
||||
]
|
||||
return True, []
|
||||
|
||||
@@ -120,11 +144,6 @@ def assess_capability_stop_report(
|
||||
capability_denied: bool = True,
|
||||
) -> dict:
|
||||
"""Validate final report purity after reviewer capability denial."""
|
||||
from review_proofs import (
|
||||
assess_empty_queue_report,
|
||||
parse_trust_gate_status_from_report,
|
||||
)
|
||||
|
||||
text = report_text or ""
|
||||
lower = text.lower()
|
||||
violations = []
|
||||
@@ -153,7 +172,11 @@ def assess_capability_stop_report(
|
||||
r"inventory empty",
|
||||
re.I,
|
||||
)
|
||||
parsed_status = parse_trust_gate_status_from_report(text)
|
||||
parsed_status = None
|
||||
for line in text.splitlines():
|
||||
if "pr_inventory_trust_gate.status:" in line.lower():
|
||||
parsed_status = line.split(":", 1)[1].strip()
|
||||
break
|
||||
effective_status = trust_gate_status or parsed_status
|
||||
if empty_queue_patterns.search(text):
|
||||
if effective_status != "trusted_empty":
|
||||
@@ -162,10 +185,6 @@ def assess_capability_stop_report(
|
||||
"pr_inventory_trust_gate.status == trusted_empty"
|
||||
)
|
||||
|
||||
empty_queue = assess_empty_queue_report(text)
|
||||
if empty_queue.get("claimed") and not empty_queue.get("proven"):
|
||||
violations.extend(empty_queue.get("reasons") or [])
|
||||
|
||||
ok, elig_violations = validate_eligibility_wording(text)
|
||||
violations.extend(elig_violations)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user