Files
Gitea-Tools/issue_work_duplicate_gate.py
T
jcwalker3andClaude Opus 4.8 5547399037 fix(mcp): accept strict-descendant dead-session recovery (Closes #768)
Permit fail-closed recovery when a clean local head is a strict
descendant of the recorded PR/remote head, with server-side ancestry
proof. Propagate recovery evidence through commit, push, and PR
duplicate gates so an owning PR is not re-blocked as competing work.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-20 02:52:29 -05:00

323 lines
12 KiB
Python

"""Early duplicate-work detection for author work-issue sessions (#400)."""
from __future__ import annotations
from typing import Any, Mapping
import issue_claim_heartbeat as claim_hb
PHASE_LOCK = "lock_issue"
PHASE_COMMIT = "commit"
PHASE_PUSH = "push"
PHASE_CREATE_PR = "create_pr"
OUTCOME_DUPLICATE_PR_PREVENTED = "duplicate_pr_prevented"
OUTCOME_DUPLICATE_BRANCH_PREVENTED = "duplicate_branch_prevented"
OUTCOME_DUPLICATE_COMMIT_PREVENTED = "duplicate_commit_prevented"
OUTCOME_DUPLICATE_WORK_NOT_PREVENTED = "duplicate_work_not_prevented"
_ACTIVE_CLAIM_STATUSES = frozenset({"active", "awaiting_review"})
def _issue_pattern(issue_number: int) -> str:
return f"issue-{int(issue_number)}"
def _linked_open_pr(issue_number: int, open_prs: list[dict]) -> dict | None:
return claim_hb._linked_open_pr(issue_number, open_prs)
def _pr_links_issue(issue_number: int, pr: Mapping[str, Any]) -> bool:
"""Same linkage rule ``claim_hb._linked_open_pr`` applies, per PR.
``_linked_open_pr`` only yields the *first* match, which cannot answer
"is there exactly one linked PR?" — a question the owning-PR exemption
below must answer before it can trust any of them.
"""
pattern = _issue_pattern(issue_number)
head = (pr.get("head") or {}).get("ref") or ""
text = f"{pr.get('title', '')} {pr.get('body', '')}".lower()
if pattern in head.lower():
return True
return (
f"closes #{int(issue_number)}" in text
or f"fixes #{int(issue_number)}" in text
)
def _all_linked_open_prs(
issue_number: int, open_prs: list[dict]
) -> list[Mapping[str, Any]]:
return [pr for pr in (open_prs or []) if _pr_links_issue(issue_number, pr)]
def _assess_owning_pr_exemption(
issue_number: int,
*,
linked_open_prs: list[Mapping[str, Any]],
locked_branch: str | None,
recovered_owning_pr: Mapping[str, Any] | None,
) -> tuple[bool, list[str]]:
"""Is the linked open PR provably the one a sanctioned recovery owns (#755)?
``recovered_owning_pr`` is produced by
``issue_lock_recovery.owning_pr_recovery_evidence`` from a completed
server-side recovery assessment — it is never a caller-supplied field.
Every element is re-checked here against the live PR list this gate was
given, so a stale or partial token cannot widen the exemption.
Returns ``(exempt, diagnostic_reasons)``. Diagnostics are only emitted when
a token was offered and rejected, so a blocked caller can see which element
of ownership disagreed.
"""
if not recovered_owning_pr:
return False, []
notes: list[str] = []
token_issue = recovered_owning_pr.get("issue_number")
token_pr = recovered_owning_pr.get("pr_number")
token_branch = str(recovered_owning_pr.get("branch_name") or "").strip()
token_head = str(recovered_owning_pr.get("head_sha") or "").strip()
locked = (locked_branch or "").strip()
if token_issue is not None and int(token_issue) != int(issue_number):
notes.append(
f"recovery evidence is for issue #{token_issue}, not "
f"#{issue_number} (no owning-PR exemption)"
)
return False, notes
if not locked or not token_branch or locked != token_branch:
notes.append(
f"recovery evidence branch '{token_branch or 'unknown'}' does not "
f"match the branch being locked '{locked or 'unknown'}' "
"(no owning-PR exemption)"
)
return False, notes
if len(linked_open_prs) != 1:
numbers = ", ".join(
f"#{pr.get('number')}" for pr in linked_open_prs
) or "none"
notes.append(
f"{len(linked_open_prs)} open PRs link issue #{issue_number} "
f"({numbers}); recovery may only own exactly one "
"(no owning-PR exemption)"
)
return False, notes
only = linked_open_prs[0]
head_obj = only.get("head") or {}
only_number = only.get("number")
only_ref = str(head_obj.get("ref") or "").strip()
only_sha = str(head_obj.get("sha") or "").strip()
if token_pr is None or only_number is None or int(only_number) != int(token_pr):
notes.append(
f"linked open PR #{only_number} is not the recovered owning PR "
f"#{token_pr} (no owning-PR exemption)"
)
return False, notes
if only_ref != token_branch:
notes.append(
f"open PR #{only_number} head branch '{only_ref}' does not match "
f"the recovered branch '{token_branch}' (no owning-PR exemption)"
)
return False, notes
# #768: a descendant recovery is measured against the head the PR still
# shows, then publishes the local descendant — so the live PR head is the
# recorded head before that push and the accepted head after it. Both are
# server-derived and name the same owned PR, so both are accepted; anything
# else still fails closed.
token_accepted = str(recovered_owning_pr.get("accepted_head") or "").strip()
acceptable_heads = [head for head in (token_head, token_accepted) if head]
if not acceptable_heads or not only_sha or only_sha not in acceptable_heads:
notes.append(
f"open PR #{only_number} head {only_sha or 'unknown'} does not "
f"match the recovered head {token_head or 'unknown'}"
+ (
f" or the accepted head {token_accepted}"
if token_accepted and token_accepted != token_head
else ""
)
+ " (no owning-PR exemption)"
)
return False, notes
return True, [
f"open PR #{only_number} is the exact PR already owned by the "
f"recovering lock for issue #{issue_number} (branch '{token_branch}', "
f"head {only_sha}); not duplicate work"
]
def _matching_branches(
issue_number: int,
branch_names: list[str],
*,
locked_branch: str | None = None,
) -> list[str]:
pattern = _issue_pattern(issue_number)
matches = [
name for name in (branch_names or [])
if pattern in (name or "").lower()
]
if locked_branch:
locked = locked_branch.strip()
matches = [name for name in matches if name != locked]
return matches
def assess_work_issue_duplicate_gate(
issue_number: int,
*,
open_prs: list[dict] | None = None,
branch_names: list[str] | None = None,
claim_entry: dict | None = None,
locked_branch: str | None = None,
phase: str = PHASE_LOCK,
recovered_owning_pr: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
"""Fail closed when duplicate work is already in flight for an issue.
``recovered_owning_pr`` (#755) is server-derived evidence that a sanctioned
dead-session lock recovery already owns one specific open PR. It exempts
*only* that exact PR from the linked-open-PR blocker; every other duplicate
signal, and every mismatch, keeps failing closed.
"""
reasons: list[str] = []
outcome = OUTCOME_DUPLICATE_WORK_NOT_PREVENTED
prs = list(open_prs or [])
branches = list(branch_names or [])
pattern = _issue_pattern(issue_number)
linked = _linked_open_pr(issue_number, prs)
linked_open_prs = _all_linked_open_prs(issue_number, prs)
owning_pr_exempted = False
exemption_notes: list[str] = []
if linked:
owning_pr_exempted, exemption_notes = _assess_owning_pr_exemption(
issue_number,
linked_open_prs=linked_open_prs,
locked_branch=locked_branch,
recovered_owning_pr=recovered_owning_pr,
)
if not owning_pr_exempted:
reasons.append(
f"open PR #{linked.get('number')} already covers issue "
f"#{issue_number} (fail closed)"
)
reasons.extend(exemption_notes)
outcome = OUTCOME_DUPLICATE_PR_PREVENTED
conflicting_branches = _matching_branches(
issue_number, branches, locked_branch=locked_branch
)
if conflicting_branches:
names = ", ".join(conflicting_branches[:5])
reasons.append(
f"remote branch(es) already match issue pattern '{pattern}': "
f"{names} (fail closed)"
)
if outcome == OUTCOME_DUPLICATE_WORK_NOT_PREVENTED:
outcome = OUTCOME_DUPLICATE_BRANCH_PREVENTED
entry = claim_entry or {}
if entry.get("linked_open_pr") and not linked:
reasons.append(
f"claim inventory reports open PR #{entry['linked_open_pr']} "
f"for issue #{issue_number} (fail closed)"
)
outcome = OUTCOME_DUPLICATE_PR_PREVENTED
status = (entry.get("status") or "").strip().lower()
if status in _ACTIVE_CLAIM_STATUSES and not linked:
heartbeat = entry.get("latest_heartbeat") or {}
claim_branch = (heartbeat.get("branch") or "").strip()
if locked_branch and claim_branch and claim_branch != locked_branch:
reasons.append(
f"active claim lease on branch '{claim_branch}' blocks "
f"work on '{locked_branch}' for issue #{issue_number} "
"(fail closed)"
)
if outcome == OUTCOME_DUPLICATE_WORK_NOT_PREVENTED:
outcome = OUTCOME_DUPLICATE_BRANCH_PREVENTED
elif not locked_branch and status == "active":
reasons.append(
f"issue #{issue_number} has an active claim lease "
"(fail closed)"
)
if outcome == OUTCOME_DUPLICATE_WORK_NOT_PREVENTED:
outcome = OUTCOME_DUPLICATE_BRANCH_PREVENTED
if phase in {PHASE_COMMIT, PHASE_PUSH} and reasons:
if outcome == OUTCOME_DUPLICATE_PR_PREVENTED:
outcome = OUTCOME_DUPLICATE_COMMIT_PREVENTED
elif outcome == OUTCOME_DUPLICATE_BRANCH_PREVENTED:
outcome = OUTCOME_DUPLICATE_COMMIT_PREVENTED
block = bool(reasons)
return {
"block": block,
"performed": not block,
"issue_number": issue_number,
"phase": phase,
"outcome": outcome,
"linked_open_pr": linked.get("number") if linked else entry.get("linked_open_pr"),
"linked_open_pr_count": len(linked_open_prs),
"owning_pr_recovery_exempted": owning_pr_exempted,
"owning_pr_recovery_notes": list(exemption_notes),
"conflicting_branches": conflicting_branches,
"claim_status": status or None,
"reasons": reasons,
"safe_next_action": (
"stop before mutating; preserve local work and produce a "
"reconciliation handoff if a concurrent PR appeared after push"
if block and phase == PHASE_CREATE_PR
else "stop before mutating; do not commit or push duplicate work"
if block
else "proceed"
),
}
def assess_work_issue_duplicate_report(report_text: str) -> dict[str, Any]:
"""Require explicit duplicate-work outcome wording in work-issue reports."""
text = (report_text or "").lower()
markers = {
OUTCOME_DUPLICATE_PR_PREVENTED: (
"duplicate pr prevented",
"duplicate_pr_prevented",
),
OUTCOME_DUPLICATE_BRANCH_PREVENTED: (
"duplicate branch prevented",
"duplicate_branch_prevented",
),
OUTCOME_DUPLICATE_COMMIT_PREVENTED: (
"duplicate commit prevented",
"duplicate_commit_prevented",
),
OUTCOME_DUPLICATE_WORK_NOT_PREVENTED: (
"duplicate work not prevented",
"duplicate_work_not_prevented",
"no duplicate work",
),
}
matched = [
key for key, phrases in markers.items()
if any(phrase in text for phrase in phrases)
]
if len(matched) != 1:
return {
"complete": False,
"downgraded": True,
"reasons": [
"work-issue report must state exactly one duplicate-work "
"outcome (duplicate PR/branch/commit prevented, or "
"duplicate work not prevented)"
],
}
return {
"complete": True,
"downgraded": False,
"outcome": matched[0],
"reasons": [],
}