fix: resolve conflicts for PR #385

Merge prgs/master and keep work-issue author routing (#139) alongside
reconciler close task routing (#309).
This commit is contained in:
2026-07-07 09:58:45 -04:00
15 changed files with 2293 additions and 0 deletions
+471
View File
@@ -1242,6 +1242,121 @@ def assess_workspace_mutation_consistency(report_text, observed_commands=None):
}
# ── Reconciliation controller-handoff schema (Issue #303) ────────────────────
#
# Already-landed PR reconciliation is neither author issue work nor
# reviewer merge work; its handoff needs its own schema. Stale
# author/reviewer fields fail validation, and observed git ref
# mutations (fetch) must be reported under the precise category.
RECONCILIATION_ELIGIBILITY_CLASS = "ALREADY_LANDED_RECONCILE_REQUIRED"
RECONCILIATION_HANDOFF_FIELDS = (
("Task", ("task",)),
("Repo", ("repo",)),
("Role/profile", ("role/profile", "role", "profile")),
("Identity", ("identity",)),
("Selected PR", ("selected pr",)),
("PR live state", ("pr live state",)),
("Candidate head SHA", ("candidate head sha",)),
("Target branch", ("target branch",)),
("Target branch SHA", ("target branch sha",)),
("Ancestor proof", ("ancestor proof",)),
("Linked issue", ("linked issue",)),
("Linked issue live status", ("linked issue live status",)),
("Eligibility class", ("eligibility class",)),
("Capabilities proven", ("capabilities proven",)),
("Missing capabilities", ("missing capabilities",)),
("PR comments posted", ("pr comments posted",)),
("Issue comments posted", ("issue comments posted",)),
("PRs closed", ("prs closed",)),
("Issues closed", ("issues closed",)),
("Git ref mutations", ("git ref mutations",)),
("Worktree mutations", ("worktree mutations",)),
("MCP/Gitea mutations", ("mcp/gitea mutations",)),
("External-state mutations", ("external-state mutations",)),
("Read-only diagnostics", ("read-only diagnostics",)),
("Blocker", ("blocker",)),
("Safe next action", ("safe next action",)),
("No review/merge confirmation", ("no review/merge",)),
)
RECONCILIATION_FORBIDDEN_FIELDS = (
"pr number opened",
"pinned reviewed head",
"scratch worktree used",
"workspace mutations",
"issue lock proof",
"claim/comment status",
)
def assess_reconciliation_handoff(report_text, observed_commands=None):
"""#303: validate the dedicated reconciliation handoff schema.
Requires a Controller Handoff section carrying every reconciliation
field, ``Eligibility class: ALREADY_LANDED_RECONCILE_REQUIRED``, and
no stale author/reviewer fields. *observed_commands* is the git
command log; an observed fetch/pull must be reported under ``Git ref
mutations`` (never claimed none).
Returns {'complete', 'downgraded', 'reasons'}; fails closed.
"""
section = _handoff_section_lines(report_text)
if section is None:
return {
"complete": False,
"downgraded": True,
"reasons": [
"reconciliation report has no section titled exactly "
f"'{HANDOFF_HEADING}'"
],
}
fields = {}
for line in section:
stripped = line.strip().lstrip("-*").strip()
if ":" in stripped:
k, v = stripped.split(":", 1)
fields[k.strip().lower()] = v.strip()
reasons = []
for name, aliases in RECONCILIATION_HANDOFF_FIELDS:
if not any(label.startswith(alias)
for label in fields for alias in aliases):
reasons.append(
f"reconciliation handoff missing required field: {name}"
)
for stale in RECONCILIATION_FORBIDDEN_FIELDS:
if any(label.startswith(stale) for label in fields):
reasons.append(
f"reconciliation handoff must not include stale field "
f"'{stale}'"
)
eligibility = fields.get("eligibility class", "")
if eligibility and RECONCILIATION_ELIGIBILITY_CLASS not in eligibility.upper():
reasons.append(
"reconciliation handoff eligibility class must be "
f"'{RECONCILIATION_ELIGIBILITY_CLASS}' (got '{eligibility}')"
)
_, ref_cmds = _classify_git_commands(observed_commands)
if ref_cmds and _field_claims_none(fields, "git ref mutations"):
reasons.append(
"observed git ref mutation not reported under 'Git ref "
f"mutations': {ref_cmds[0]}"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
# ── Already-landed report wording (Issue #298) ───────────────────────────────
#
# An already-landed PR is reconciliation-only: it must never be called
@@ -1589,6 +1704,11 @@ def build_final_report(checkout_proof, inventory, validation, contamination,
if report_text
else {"proven": True, "block": False, "reasons": [], "violations": []}
)
inventory_worktree = (
assess_inventory_worktree_report(report_text)
if report_text
else {"proven": True, "block": False, "reasons": []}
)
if baseline_validation is None and report_text:
baseline_validation = assess_reviewer_baseline_validation_proof(
report_text=report_text,
@@ -1750,6 +1870,11 @@ def build_final_report(checkout_proof, inventory, validation, contamination,
"queue-status report violates loaded workflow proof gates (#339)"
)
downgrade_reasons.extend(queue_status_report.get("reasons", []))
if not inventory_worktree.get("proven"):
downgrade_reasons.append(
"inventory pagination or worktree fields inconsistent (#293)"
)
downgrade_reasons.extend(inventory_worktree.get("reasons", []))
if not baseline_validation.get("proven"):
downgrade_reasons.append(
"reviewer baseline validation proof missing or failed (#325)"
@@ -1843,6 +1968,10 @@ def build_final_report(checkout_proof, inventory, validation, contamination,
"queue_status_violations": list(
queue_status_report.get("violations") or []
),
"inventory_worktree_proven": bool(inventory_worktree.get("proven")),
"inventory_worktree_violations": list(
inventory_worktree.get("reasons") or []
),
"baseline_validation_proven": bool(baseline_validation.get("proven")),
"baseline_validation_violations": list(
baseline_validation.get("violations") or []
@@ -4087,6 +4216,168 @@ def assess_reviewer_baseline_validation_proof(
# ---------------------------------------------------------------------------
# Partial reconciliation policy (#302)
# ---------------------------------------------------------------------------
# Durable policy choice for already-landed PR reconciliation when
# ``gitea.pr.close`` is unavailable: Option B, comment-then-stop.
PARTIAL_RECONCILIATION_POLICY = "comment_then_stop"
PARTIAL_RECONCILIATION_COMMENT_FIELDS = (
"PR head SHA",
"target branch SHA",
"ancestor proof",
"linked issue status",
"required missing capability",
)
def resolve_partial_reconciliation_plan(
*,
ancestor_proven: bool,
capabilities: dict | None,
) -> dict:
"""#302: decide reconciliation mutations from proven capabilities.
Implements the comment-then-stop policy (Option B): with ancestor
proof but no ``close_pr`` capability, a reconciliation comment with
the full proof is posted when ``comment_pr`` is proven, then the
workflow stops for an authorized close. Missing comment capability
degrades to a recovery handoff with no Gitea mutation. Unproven
ancestry blocks every mutation regardless of capability.
*capabilities* maps ``close_pr``/``comment_pr``/``close_issue``/
``comment_issue`` to proven booleans; ``None`` or missing keys fail
closed.
"""
caps = capabilities or {}
if not ancestor_proven:
return {
"policy": PARTIAL_RECONCILIATION_POLICY,
"outcome": "GATE_NOT_PROVEN",
"allowed_mutations": (),
"required_comment_fields": (),
"report_requirements": (
"state that ancestry was not proven and no Gitea mutation "
"was performed",
),
"reasons": [
"ancestor proof missing; no reconciliation mutation may "
"run (#302)"
],
"safe_next_action": (
"run the already-landed ancestry check before any "
"reconciliation mutation"
),
}
if caps.get("close_pr") is True:
return {
"policy": PARTIAL_RECONCILIATION_POLICY,
"outcome": "FULL_RECONCILE_CLOSE_ALLOWED",
"allowed_mutations": ("close_pr", "comment_pr"),
"required_comment_fields": (),
"report_requirements": (
"report the PR close result",
),
"reasons": [],
"safe_next_action": (
"close the already-landed PR with the proven close_pr "
"capability and report the close result"
),
}
if caps.get("comment_pr") is True:
return {
"policy": PARTIAL_RECONCILIATION_POLICY,
"outcome": "PARTIAL_RECONCILE_COMMENT_THEN_STOP",
"allowed_mutations": ("comment_pr",),
"required_comment_fields": PARTIAL_RECONCILIATION_COMMENT_FIELDS,
"report_requirements": (
"report that the reconciliation comment was posted",
"report the missing close capability that prevented the "
"PR close",
),
"reasons": [
"close_pr capability missing; policy is comment-then-stop "
"(#302)"
],
"safe_next_action": (
"post one reconciliation comment carrying the ancestor "
"proof, then stop for a human or authorized close"
),
}
return {
"policy": PARTIAL_RECONCILIATION_POLICY,
"outcome": "RECOVERY_HANDOFF_ONLY",
"allowed_mutations": (),
"required_comment_fields": (),
"report_requirements": (
"report that no reconciliation comment was posted and name "
"the missing capability",
"report that no Gitea mutation was performed",
),
"reasons": [
"close_pr and comment_pr capabilities missing; recovery "
"handoff only (#302)"
],
"safe_next_action": (
"produce a recovery handoff recording the intended comment "
"and required capabilities; perform no Gitea mutation"
),
}
def assess_partial_reconciliation_report(
report_text: str | None,
*,
plan: dict,
) -> dict:
"""#302: final reports must explain why comments were or were not posted."""
text = (report_text or "").lower()
outcome = (plan or {}).get("outcome", "")
reasons: list[str] = []
if outcome == "FULL_RECONCILE_CLOSE_ALLOWED":
if "close" not in text or "result" not in text:
reasons.append(
"full reconciliation report must state the PR close "
"result (#302)"
)
elif outcome == "PARTIAL_RECONCILE_COMMENT_THEN_STOP":
if "comment" not in text:
reasons.append(
"partial reconciliation report must state that the "
"reconciliation comment was posted (#302)"
)
if "capability" not in text and "close_pr" not in text:
reasons.append(
"partial reconciliation report must name the missing "
"close capability (#302)"
)
elif outcome == "RECOVERY_HANDOFF_ONLY":
if "comment" not in text or "capability" not in text:
reasons.append(
"recovery handoff report must explain that no comment was "
"posted and name the missing capability (#302)"
)
if "no gitea mutation" not in text and "no mutation" not in text:
reasons.append(
"recovery handoff report must state that no Gitea "
"mutation was performed (#302)"
)
return {
"complete": not reasons,
"block": bool(reasons),
"reasons": reasons,
}
# ---------------------------------------------------------------------------
# Already-landed review gate (#292)
# ---------------------------------------------------------------------------
@@ -4252,6 +4543,9 @@ def assess_already_landed_report_state(
}
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Full-suite failure approval gate (#323)
# ---------------------------------------------------------------------------
@@ -4359,6 +4653,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)
# ---------------------------------------------------------------------------
@@ -4874,6 +5331,20 @@ def assess_validation_worktree_edit_report(report_text, **kwargs):
return _assess(report_text, **kwargs)
def assess_inventory_worktree_report(report_text, **kwargs):
"""#293: prove inventory pagination and consistent worktree reporting."""
from reviewer_inventory_worktree import assess_inventory_worktree_report as _assess
return _assess(report_text, **kwargs)
def assess_infra_stop_handoff_report(report_text, **kwargs):
"""#289: repair handoff purity when infra_stop blocks reviewer capability."""
from reviewer_infra_stop_handoff import assess_infra_stop_handoff_report as _assess
return _assess(report_text, **kwargs)
def assess_mutation_categories_report(report_text, **kwargs):
"""#319: require precise mutation categories in reviewer controller handoffs."""
from reviewer_mutation_categories import assess_mutation_categories_report as _assess