fix: resolve conflicts for PR #384
Merge prgs/master and keep read-only reconciliation workflow tasks (#301) alongside reconciler close capabilities (#309).
This commit is contained in:
@@ -4536,6 +4536,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)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user