Merge remote-tracking branch 'prgs/master' into feat/issue-300-linked-issue-live-proof

This commit is contained in:
2026-07-07 09:57:54 -04:00
7 changed files with 952 additions and 0 deletions
+163
View File
@@ -4545,6 +4545,169 @@ def assess_full_suite_failure_approval_gate(
}
# ---------------------------------------------------------------------------
# Reconciler close gate (#309)
# ---------------------------------------------------------------------------
RECONCILER_CLOSE_REPORT_FIELDS = (
"identity/profile",
"close capability proof",
"PR live state",
"candidate head SHA",
"target branch SHA",
"ancestor proof",
"linked issue status",
"PR close result",
"issue close result",
"no review/merge confirmation",
)
def assess_reconciler_close_gate(
*,
pr_number: int | None,
pr_state: str | None,
candidate_head_sha: str | None,
live_head_sha: str | None,
target_branch: str | None,
target_branch_sha: str | None,
head_is_ancestor_of_target: bool | None,
close_pr_capability: bool,
close_issue_capability: bool = False,
linked_issue_state: str | None = None,
issue_resolved_by_landing: bool = False,
) -> dict:
"""#309: gate the dedicated reconciler close path for landed PRs.
The reconciler path may close a PR only with exact ``gitea.pr.close``
capability and full already-landed proof: live open PR, pinned head,
freshly fetched target branch SHA, and ancestry of the head in the
target. Non-landed PRs are never closable here, and the gate never
grants review, approval, request-changes, or merge.
Linked-issue closure: skipped when the issue is already closed;
allowed only when the issue is open, resolved by the landed commits,
and exact ``gitea.issue.close`` capability is proven.
"""
reasons: list[str] = []
if not isinstance(pr_number, int) or pr_number <= 0:
reasons.append("PR number missing or invalid (#309)")
if (pr_state or "").strip().lower() != "open":
reasons.append(
"PR is not live-verified open at mutation time; re-fetch the "
"PR before any reconciler close (#309)"
)
if not _FULL_SHA.match((candidate_head_sha or "").strip()):
reasons.append(
"candidate head SHA is not a full 40-hex commit SHA (#309)"
)
if live_head_sha is not None and candidate_head_sha and (
live_head_sha.strip() != candidate_head_sha.strip()
):
reasons.append(
"PR head changed since the ancestry proof was pinned; re-run "
"the already-landed check (#309)"
)
if not (target_branch or "").strip():
reasons.append("target branch missing (#309)")
if not _FULL_SHA.match((target_branch_sha or "").strip()):
reasons.append(
"target branch SHA missing or not a full 40-hex SHA; fetch the "
"target branch freshly before the ancestry check (#309)"
)
if head_is_ancestor_of_target is None:
reasons.append(
"ancestry of the PR head against the target branch was not "
"checked (#309)"
)
never_allowed = {
"review_allowed": False,
"approve_allowed": False,
"request_changes_allowed": False,
"merge_allowed": False,
}
if reasons:
return {
"outcome": "GATE_NOT_PROVEN",
"pr_close_allowed": False,
"issue_close_allowed": False,
"reasons": reasons,
"required_report_fields": RECONCILER_CLOSE_REPORT_FIELDS,
"safe_next_action": (
"re-fetch the PR and target branch, pin SHAs, run the "
"ancestry check, then re-run this gate"
),
**never_allowed,
}
if head_is_ancestor_of_target is False:
return {
"outcome": "NOT_LANDED_CLOSE_BLOCKED",
"pr_close_allowed": False,
"issue_close_allowed": False,
"reasons": [
f"PR #{pr_number} head is not an ancestor of "
f"{target_branch}; non-landed PRs cannot be closed through "
"the reconciler path (#309)"
],
"required_report_fields": RECONCILER_CLOSE_REPORT_FIELDS,
"safe_next_action": (
"route the PR through the normal review workflow; the "
"reconciler path only closes already-landed PRs"
),
**never_allowed,
}
if close_pr_capability is not True:
return {
"outcome": "RECOVERY_HANDOFF_REQUIRED",
"pr_close_allowed": False,
"issue_close_allowed": False,
"reasons": [
"exact gitea.pr.close capability not proven; produce a "
"recovery handoff instead of closing (#309)"
],
"required_report_fields": RECONCILER_CLOSE_REPORT_FIELDS,
"safe_next_action": (
"produce a recovery handoff naming the missing "
"gitea.pr.close capability and the completed ancestor proof"
),
**never_allowed,
}
issue_close_allowed = (
(linked_issue_state or "").strip().lower() == "open"
and issue_resolved_by_landing is True
and close_issue_capability is True
)
issue_close_reasons = []
if (linked_issue_state or "").strip().lower() == "closed":
issue_close_reasons.append(
"linked issue already closed; no issue close attempted (#309)"
)
elif not issue_close_allowed:
issue_close_reasons.append(
"issue close requires an open linked issue resolved by the "
"landed commits and exact gitea.issue.close capability (#309)"
)
return {
"outcome": "CLOSE_ALLOWED",
"pr_close_allowed": True,
"issue_close_allowed": issue_close_allowed,
"reasons": issue_close_reasons,
"required_report_fields": RECONCILER_CLOSE_REPORT_FIELDS,
"safe_next_action": (
"close the already-landed PR, report the close result, and "
"handle the linked issue per the proven capability"
),
**never_allowed,
}
# ---------------------------------------------------------------------------
# Identity disclosure (#305)
# ---------------------------------------------------------------------------