Merge remote-tracking branch 'prgs/master' into feat/issue-514-branch-delete-guard

# Conflicts:
#	final_report_validator.py
This commit is contained in:
2026-07-09 09:12:33 -04:00
53 changed files with 4880 additions and 196 deletions
+140
View File
@@ -14,6 +14,7 @@ from typing import Any, Callable
import branch_cleanup_guard
import issue_acceptance_gate
import issue_lock_provenance
import reviewer_handoff_consistency
from mcp_native_cleanup_proof import assess_mcp_native_cleanup_proof
from post_merge_cleanup_proof import assess_post_merge_cleanup_proof
from review_proofs import (
@@ -30,6 +31,7 @@ from validation_status_vocabulary import assess_validation_status_vocabulary
FINAL_REPORT_TASK_KINDS = frozenset({
"review_pr",
"merge_pr",
"reconcile_already_landed",
"author_issue",
"work_issue",
@@ -41,6 +43,8 @@ FINAL_REPORT_TASK_KINDS = frozenset({
_TASK_KIND_ALIASES = {
"review": "review_pr",
"review-merge-pr": "review_pr",
"merge": "merge_pr",
"merge_pr": "merge_pr",
"reconcile-landed-pr": "reconcile_already_landed",
"reconcile_already_landed": "reconcile_already_landed",
"work-issue": "work_issue",
@@ -49,6 +53,7 @@ _TASK_KIND_ALIASES = {
_HANDOFF_ROLE_BY_TASK = {
"review_pr": "review",
"merge_pr": "merger",
"reconcile_already_landed": None,
"author_issue": "author",
"work_issue": "author",
@@ -196,6 +201,23 @@ _PAGINATION_PROOF_RE = re.compile(
re.IGNORECASE,
)
_GIT_FETCH_RE = re.compile(r"\bgit\s+fetch\b", re.IGNORECASE)
_CANONICAL_VALIDATION_REJECTED_RE = re.compile(
r"canonical comment validation failed|"
r"canonical_comment_validation|"
r'"allowed"\s*:\s*false',
re.IGNORECASE,
)
_COMMENT_POSTED_CLAIM_RE = re.compile(
r"(?:issue comments posted\s*:\s+(?!none\b)\S|"
r"pr comments posted\s*:\s+(?!none\b)\S|"
r"comment_id\s*[:=]\s*\d+|"
r"gitea comment (?:was )?posted|"
r"posted (?:issue|pr|canonical) (?:state )?comment|"
r"comment posted successfully|"
r"mcp/gitea mutations\s*:\s*[^;\n]*comment posted|"
r"reconciliation mutations\s*:\s*[^;\n]*comment posted)",
re.IGNORECASE,
)
_READONLY_DIAG_RE = re.compile(
r"read[- ]only diagnostics\s*:\s*(.+)$",
re.IGNORECASE | re.MULTILINE,
@@ -382,6 +404,43 @@ def _rule_shared_email_disclosure(report_text: str) -> list[dict[str, str]]:
)
def _rule_shared_canonical_comment_post_claim(
report_text: str,
*,
action_log: list[dict] | None = None,
) -> list[dict[str, str]]:
"""#496: reports must not claim comment posted when validator rejected."""
text = report_text or ""
rejected_in_report = bool(_CANONICAL_VALIDATION_REJECTED_RE.search(text))
rejected_in_log = False
if action_log:
for entry in action_log:
validation = entry.get("canonical_comment_validation") or {}
if validation.get("allowed") is False:
rejected_in_log = True
break
result = entry.get("result") or {}
nested = result.get("canonical_comment_validation") or {}
if nested.get("allowed") is False:
rejected_in_log = True
break
if not (rejected_in_report or rejected_in_log):
return []
if not _COMMENT_POSTED_CLAIM_RE.search(text):
return []
return [
validator_finding(
"shared.canonical_comment_post_claim",
"block",
"MCP/Gitea mutations",
"report claims a Gitea comment was posted while canonical comment "
"validation rejected the workflow comment",
"do not claim comment posted when validator fail-closed; repair "
"the canonical comment body and retry posting",
)
]
def _rule_reviewer_legacy_workspace_mutations(
report_text: str,
*,
@@ -599,6 +658,27 @@ def _rule_reviewer_stale_head_proof(report_text: str) -> list[dict[str, str]]:
)
def _rule_conflict_fix_classification_proof(report_text: str) -> list[dict[str, str]]:
from conflict_fix_classification import (
assess_conflict_fix_classification_final_report,
)
text = report_text or ""
result = assess_conflict_fix_classification_final_report(text)
if result.get("proven"):
return []
return _findings_from_reasons(
"author.conflict_fix_classification_proof",
result.get("reasons") or [],
field="Conflict-fix classification",
severity="block",
safe_next_action=(
"call gitea_assess_conflict_fix_classification, state the live head "
"SHA, and state the classification before creating a conflict-fix worktree"
),
)
def _rule_conflict_fix_push_proof(report_text: str) -> list[dict[str, str]]:
from pr_work_lease import assess_conflict_fix_final_report
@@ -1112,6 +1192,25 @@ def _rule_reviewer_validation_structured(
)
def _rule_reviewer_handoff_consistency(report_text: str) -> list[dict[str, str]]:
result = reviewer_handoff_consistency.assess_reviewer_handoff_consistency(
report_text
)
if result.get("proven"):
return []
return _findings_from_reasons(
"reviewer.handoff_consistency",
result.get("reasons") or [],
field="Review mutations",
severity="block",
safe_next_action=(
"rewrite reviewer handoff so narrative claims match the mutation "
"ledger; use the blocked-review handoff template when submission "
"was rejected"
),
)
def _rule_reviewer_mutation_ledger(
report_text: str,
*,
@@ -1289,6 +1388,22 @@ def _rule_reviewer_review_mutation(
)
def _rule_shared_canonical_state_update(report_text: str) -> list[dict[str, str]]:
from canonical_state_comments import validate_final_report_state_update
result = validate_final_report_state_update(report_text)
if not result.get("applicable") or result.get("valid"):
return []
return [
validator_finding(
"shared.canonical_state_update",
"block",
"Canonical state update",
reason,
"include a canonical state block with STATE, WHO_IS_NEXT, NEXT_ACTION, and NEXT_PROMPT",
)
for reason in (result.get("reasons") or ["invalid canonical state update"])
]
def _rule_reviewer_mutation_capability_proof(report_text: str) -> list[dict[str, str]]:
from reviewer_mutation_capability_proof import assess_mutation_capability_proof
@@ -1338,17 +1453,23 @@ _SHARED_ISSUE_LOCK_RULES = (
_rule_shared_manual_lock_pr_override,
_rule_shared_author_reviewer_same_run,
_rule_shared_raw_branch_delete_bypass,
_rule_shared_canonical_state_update,
)
_SHARED_CLEANUP_PROOF_RULES = (
_rule_shared_mcp_native_cleanup_proof,
)
_SHARED_CANONICAL_COMMENT_RULES = (
_rule_shared_canonical_comment_post_claim,
)
_RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
"review_pr": [
_rule_shared_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
_rule_reviewer_legacy_workspace_mutations,
_rule_reviewer_vague_mutations_none,
@@ -1367,6 +1488,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
_rule_reviewer_already_landed_eligible,
_rule_reviewer_already_landed_state,
_rule_reviewer_target_branch_freshness,
_rule_reviewer_handoff_consistency,
_rule_reviewer_workflow_load_boundary,
_rule_reviewer_mutation_ledger,
_rule_reviewer_review_mutation,
@@ -1375,10 +1497,22 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
*_SHARED_CLEANUP_PROOF_RULES,
_rule_reviewer_stale_head_proof,
],
"merge_pr": [
_rule_shared_controller_handoff,
_rule_shared_email_disclosure,
*_SHARED_ISSUE_LOCK_RULES,
_rule_reviewer_legacy_workspace_mutations,
_rule_reviewer_vague_mutations_none,
_rule_reviewer_mutation_categories,
_rule_reviewer_git_fetch_readonly,
_rule_reviewer_linked_issue,
_rule_reviewer_stale_head_proof,
],
"reconcile_already_landed": [
_rule_reconcile_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
*_SHARED_CLEANUP_PROOF_RULES,
_rule_reconcile_stale_author_fields,
@@ -1396,6 +1530,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
_rule_shared_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
_rule_reviewer_vague_mutations_none,
],
@@ -1403,9 +1538,11 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
_rule_shared_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
_rule_shared_issue_acceptance_gate,
_rule_reviewer_vague_mutations_none,
_rule_conflict_fix_classification_proof,
_rule_conflict_fix_push_proof,
_rule_worktree_cleanup_audit_proof,
],
@@ -1413,12 +1550,14 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
_rule_shared_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
],
"inventory": [
_rule_shared_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
_rule_reconcile_pagination_proof,
],
@@ -1426,6 +1565,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
_rule_shared_controller_handoff,
_rule_shared_state_handoff_next_action,
_rule_shared_email_disclosure,
*_SHARED_CANONICAL_COMMENT_RULES,
*_SHARED_ISSUE_LOCK_RULES,
],
}