feat: verify linked-issue consistency for the selected PR (Closes #314)
Add assess_linked_issue_consistency to review_proofs: reviewer final reports must report a "Linked issue status" that matches the issue(s) the selected PR was live-verified to link this session. Without live proof the field may only say "not verified in this session"; with proof, every linked issue must be named, and any other "Issue #N" mention anywhere in the report fails as stale leakage from a previous PR (the PR #280 / Issue #260-vs-#275 incident). Missing field fails closed. Tests cover correct linked issue, stale previous issue in both the status field and diagnostics, missing proof, unverified wording, multiple linked issues, and PR-number mentions not being mistaken for issue mentions. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -1052,6 +1052,89 @@ def _prs_from_list_response(list_prs_response: list | dict | None) -> list | Non
|
||||
return None
|
||||
|
||||
|
||||
# ── Linked-issue consistency (Issue #314) ────────────────────────────────────
|
||||
#
|
||||
# Stale linked-issue text from a previous PR must not leak into a final
|
||||
# report: the "Linked issue status" field must match the issue(s) the
|
||||
# selected PR actually links, verified live this session, and no other
|
||||
# "Issue #N" mention may appear unless it is a verified linked issue.
|
||||
|
||||
_ISSUE_MENTION_RE = re.compile(r"\bissue\s+#(\d+)", re.IGNORECASE)
|
||||
|
||||
|
||||
def _report_labeled_fields(report_text):
|
||||
"""Return {lowercase label: value} for every 'Label: value' line."""
|
||||
fields = {}
|
||||
for line in (report_text or "").splitlines():
|
||||
stripped = line.strip().lstrip("-*").strip()
|
||||
if ":" in stripped:
|
||||
k, v = stripped.split(":", 1)
|
||||
fields[k.strip().lower()] = v.strip()
|
||||
return fields
|
||||
|
||||
|
||||
def _issue_numbers_mentioned(text):
|
||||
"""Return the set of ints referenced as 'Issue #N' in *text*."""
|
||||
return {int(n) for n in _ISSUE_MENTION_RE.findall(text or "")}
|
||||
|
||||
|
||||
def assess_linked_issue_consistency(report_text, *, selected_pr=None,
|
||||
linked_issues=None):
|
||||
"""#314: linked-issue status must match the selected PR, live-verified.
|
||||
|
||||
*linked_issues* is the list of issue numbers the selected PR was
|
||||
live-verified to link this session, or None when no live verification
|
||||
happened. Without live proof the report may only say the status was
|
||||
not verified; with proof, the ``Linked issue status`` field must name
|
||||
every linked issue and no stale issue number may appear anywhere in
|
||||
the report.
|
||||
|
||||
Returns {'complete', 'downgraded', 'reasons'}; fails closed.
|
||||
"""
|
||||
fields = _report_labeled_fields(report_text)
|
||||
status_value = fields.get("linked issue status")
|
||||
|
||||
reasons = []
|
||||
|
||||
if status_value is None:
|
||||
reasons.append(
|
||||
"final report missing 'Linked issue status' field for the "
|
||||
"selected PR"
|
||||
)
|
||||
elif linked_issues is None:
|
||||
if not status_value.strip().lower().startswith("not verified"):
|
||||
reasons.append(
|
||||
"linked issue status claimed without live proof; report "
|
||||
"'Linked issue status: not verified in this session' or "
|
||||
"verify the linked issue live"
|
||||
)
|
||||
else:
|
||||
allowed = set(linked_issues)
|
||||
status_mentions = _issue_numbers_mentioned(status_value)
|
||||
for missing in sorted(allowed - status_mentions):
|
||||
reasons.append(
|
||||
f"linked issue #{missing} of selected PR "
|
||||
f"#{selected_pr} not reported in 'Linked issue status'"
|
||||
)
|
||||
for stale in sorted(status_mentions - allowed):
|
||||
reasons.append(
|
||||
f"'Linked issue status' names issue #{stale}, which is "
|
||||
f"not a live-verified linked issue of selected PR "
|
||||
f"#{selected_pr}"
|
||||
)
|
||||
for stale in sorted(_issue_numbers_mentioned(report_text) - allowed):
|
||||
reasons.append(
|
||||
f"stale issue #{stale} mentioned in final report but not "
|
||||
f"live-verified as linked to selected PR #{selected_pr}"
|
||||
)
|
||||
|
||||
return {
|
||||
"complete": not reasons,
|
||||
"downgraded": bool(reasons),
|
||||
"reasons": reasons,
|
||||
}
|
||||
|
||||
|
||||
def assess_role_boundary(proof=None, *, task_role=None, namespaces_used=None,
|
||||
justification=None):
|
||||
"""Assess reviewer/author role separation for blind queue workflows.
|
||||
|
||||
Reference in New Issue
Block a user