Merge remote-tracking branch 'prgs/master' into feat/issue-507-controller-thread-ledger
# Conflicts: # gitea_mcp_server.py
This commit is contained in:
@@ -11,6 +11,7 @@ import inspect
|
||||
import re
|
||||
from typing import Any, Callable
|
||||
|
||||
import issue_acceptance_gate
|
||||
import issue_lock_provenance
|
||||
import thread_state_ledger_validator
|
||||
from mcp_native_cleanup_proof import assess_mcp_native_cleanup_proof
|
||||
@@ -24,6 +25,7 @@ from review_proofs import (
|
||||
assess_review_mutation_final_report,
|
||||
assess_validation_report,
|
||||
)
|
||||
from state_handoff_ledger import assess_state_handoff_ledger_report
|
||||
from validation_status_vocabulary import assess_validation_status_vocabulary
|
||||
|
||||
FINAL_REPORT_TASK_KINDS = frozenset({
|
||||
@@ -335,6 +337,38 @@ def _rule_shared_controller_handoff(
|
||||
)
|
||||
|
||||
|
||||
def _rule_shared_state_handoff_next_action(report_text: str) -> list[dict[str, str]]:
|
||||
result = assess_state_handoff_ledger_report(report_text)
|
||||
if result.get("complete"):
|
||||
return []
|
||||
findings: list[dict[str, str]] = []
|
||||
next_action = result.get("next_action") or {}
|
||||
contradictions = result.get("contradictions") or {}
|
||||
if next_action.get("block"):
|
||||
findings.extend(
|
||||
_findings_from_reasons(
|
||||
"shared.state_handoff_next_action",
|
||||
next_action.get("reasons") or [],
|
||||
field="Next action handoff",
|
||||
severity="block",
|
||||
safe_next_action=next_action.get("safe_next_action")
|
||||
or "add next-action handoff fields to Controller Handoff",
|
||||
)
|
||||
)
|
||||
if contradictions.get("block"):
|
||||
findings.extend(
|
||||
_findings_from_reasons(
|
||||
"shared.state_handoff_contradiction",
|
||||
contradictions.get("reasons") or [],
|
||||
field="State handoff",
|
||||
severity="block",
|
||||
safe_next_action=contradictions.get("safe_next_action")
|
||||
or "resolve contradictory state claims before submission",
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def _rule_shared_email_disclosure(report_text: str) -> list[dict[str, str]]:
|
||||
result = assess_email_disclosure(report_text)
|
||||
if result.get("proven"):
|
||||
@@ -715,6 +749,25 @@ def _rule_reviewer_main_checkout_baseline(report_text: str) -> list[dict[str, st
|
||||
]
|
||||
|
||||
|
||||
def _rule_reviewer_premerge_baseline_proof(report_text: str) -> list[dict[str, str]]:
|
||||
from premerge_baseline_proof import assess_premerge_baseline_proof
|
||||
|
||||
result = assess_premerge_baseline_proof(report_text)
|
||||
if not result.get("block"):
|
||||
return []
|
||||
return _findings_from_reasons(
|
||||
"reviewer.premerge_baseline_proof",
|
||||
result.get("reasons") or [],
|
||||
field="Pre-merge baseline proof",
|
||||
severity="block",
|
||||
safe_next_action=result.get("safe_next_action")
|
||||
or (
|
||||
"prove the failure on the PR pre-merge base commit or a known-failure "
|
||||
"record predating the PR; current-master reproduction is not baseline proof"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _rule_reviewer_validation_status_vocabulary(
|
||||
report_text: str,
|
||||
*,
|
||||
@@ -961,6 +1014,69 @@ def _rule_reconcile_linked_issue_live(
|
||||
return []
|
||||
|
||||
|
||||
_PR_CLOSE_NEGATIVE = frozenset({"", "none", "n/a", "na", "0", "no", "not closed", "not performed"})
|
||||
_ANCESTOR_AFFIRMATIVE_RE = re.compile(
|
||||
r"ancestor|passed|true|verified|confirmed", re.IGNORECASE
|
||||
)
|
||||
|
||||
|
||||
def _reconciler_pr_close_performed(fields: dict[str, str], lock: dict) -> bool:
|
||||
"""True when the report/session indicates a reconciler PR close happened."""
|
||||
if lock.get("pr_closed") is True:
|
||||
return True
|
||||
value = (fields.get("prs closed", "") or "").strip().lower()
|
||||
if value in _PR_CLOSE_NEGATIVE:
|
||||
return False
|
||||
# A closed PR is reported by number (e.g. "#99") or an affirmative result.
|
||||
return bool(re.search(r"#\s*\d+|\b(?:closed|success|done)\b", value))
|
||||
|
||||
|
||||
def _rule_reconcile_close_proof(
|
||||
report_text: str,
|
||||
*,
|
||||
reconciler_close_lock: dict | None = None,
|
||||
) -> list[dict[str, str]]:
|
||||
"""#306: a reconciler PR close must carry exact proof fields.
|
||||
|
||||
Read-only/comment-only reconciliations are untouched. Once a PR close is
|
||||
reported (via the ``PRs closed`` field or a session close lock), the
|
||||
handoff must prove the close capability, ancestor landing, PR close
|
||||
result, and the linked-issue result — narrative alone fails closed.
|
||||
"""
|
||||
fields = _handoff_fields(report_text)
|
||||
lock = reconciler_close_lock or {}
|
||||
if not _reconciler_pr_close_performed(fields, lock):
|
||||
return []
|
||||
|
||||
missing: list[str] = []
|
||||
capabilities = fields.get("capabilities proven", "")
|
||||
if "gitea.pr.close" not in capabilities.lower():
|
||||
missing.append("close capability proof (gitea.pr.close)")
|
||||
ancestor = fields.get("ancestor proof", "")
|
||||
if not _ANCESTOR_AFFIRMATIVE_RE.search(ancestor):
|
||||
missing.append("ancestor proof")
|
||||
prs_closed = (fields.get("prs closed", "") or "").strip().lower()
|
||||
if prs_closed in _PR_CLOSE_NEGATIVE and lock.get("pr_closed") is True:
|
||||
missing.append("PR close result")
|
||||
linked = fields.get("linked issue live status", "") or fields.get("issues closed", "")
|
||||
if not linked.strip():
|
||||
missing.append("linked issue result")
|
||||
|
||||
if not missing:
|
||||
return []
|
||||
return [
|
||||
validator_finding(
|
||||
"reconcile.close_proof_fields",
|
||||
"block",
|
||||
"Reconciler close proof",
|
||||
"reconciler PR close reported without required proof field(s): "
|
||||
+ ", ".join(missing),
|
||||
"include close capability proof, ancestor proof, PR close result, "
|
||||
"and linked issue result in the handoff",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def _rule_reconcile_pagination_proof(report_text: str) -> list[dict[str, str]]:
|
||||
text = report_text or ""
|
||||
if not _INVENTORY_COMPLETE_RE.search(text):
|
||||
@@ -1047,6 +1163,22 @@ def _rule_shared_manual_lock_pr_override(report_text: str) -> list[dict[str, str
|
||||
)
|
||||
|
||||
|
||||
def _rule_shared_issue_acceptance_gate(report_text: str) -> list[dict[str, str]]:
|
||||
result = issue_acceptance_gate.validate_final_report_issue_acceptance(report_text)
|
||||
if not result.get("applicable") or result.get("valid"):
|
||||
return []
|
||||
return _findings_from_reasons(
|
||||
"shared.issue_acceptance_gate",
|
||||
result.get("reasons") or [],
|
||||
field="Controller acceptance",
|
||||
severity="block",
|
||||
safe_next_action=(
|
||||
"add Controller Issue Acceptance proof or state that controller "
|
||||
"acceptance is pending; do not claim issue complete from PR merge alone"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _rule_shared_author_reviewer_same_run(report_text: str) -> list[dict[str, str]]:
|
||||
result = issue_lock_provenance.assess_author_reviewer_same_run_report(report_text)
|
||||
if result.get("proven"):
|
||||
@@ -1209,6 +1341,7 @@ _SHARED_CLEANUP_PROOF_RULES = (
|
||||
_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_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
@@ -1222,6 +1355,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
|
||||
_rule_reviewer_validation_structured,
|
||||
_rule_reviewer_linked_issue,
|
||||
_rule_reviewer_baseline_on_failure,
|
||||
_rule_reviewer_premerge_baseline_proof,
|
||||
_rule_reviewer_validation_status_vocabulary,
|
||||
_rule_reviewer_main_checkout_baseline,
|
||||
_rule_reviewer_main_checkout_path,
|
||||
@@ -1238,6 +1372,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
|
||||
],
|
||||
"reconcile_already_landed": [
|
||||
_rule_reconcile_controller_handoff,
|
||||
_rule_shared_state_handoff_next_action,
|
||||
_rule_shared_email_disclosure,
|
||||
*_SHARED_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
@@ -1245,7 +1380,9 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
|
||||
_rule_reconcile_stale_author_fields,
|
||||
_rule_reconcile_eligible_reviewed,
|
||||
_rule_reconcile_linked_issue_live,
|
||||
_rule_reconcile_close_proof,
|
||||
_rule_reconcile_pagination_proof,
|
||||
_rule_reviewer_premerge_baseline_proof,
|
||||
_rule_reviewer_git_fetch_readonly,
|
||||
_rule_reviewer_legacy_workspace_mutations,
|
||||
_rule_reviewer_vague_mutations_none,
|
||||
@@ -1253,6 +1390,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
|
||||
],
|
||||
"author_issue": [
|
||||
_rule_shared_controller_handoff,
|
||||
_rule_shared_state_handoff_next_action,
|
||||
_rule_shared_email_disclosure,
|
||||
*_SHARED_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
@@ -1260,21 +1398,25 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
|
||||
],
|
||||
"work_issue": [
|
||||
_rule_shared_controller_handoff,
|
||||
_rule_shared_state_handoff_next_action,
|
||||
_rule_shared_email_disclosure,
|
||||
*_SHARED_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
_rule_shared_issue_acceptance_gate,
|
||||
_rule_reviewer_vague_mutations_none,
|
||||
_rule_conflict_fix_push_proof,
|
||||
_rule_worktree_cleanup_audit_proof,
|
||||
],
|
||||
"issue_filing": [
|
||||
_rule_shared_controller_handoff,
|
||||
_rule_shared_state_handoff_next_action,
|
||||
_rule_shared_email_disclosure,
|
||||
*_SHARED_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
],
|
||||
"inventory": [
|
||||
_rule_shared_controller_handoff,
|
||||
_rule_shared_state_handoff_next_action,
|
||||
_rule_shared_email_disclosure,
|
||||
*_SHARED_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
@@ -1282,6 +1424,7 @@ _RULES_BY_TASK: dict[str, list[Callable[..., list[dict[str, str]]]]] = {
|
||||
],
|
||||
"issue_selection": [
|
||||
_rule_shared_controller_handoff,
|
||||
_rule_shared_state_handoff_next_action,
|
||||
_rule_shared_email_disclosure,
|
||||
*_SHARED_TWO_COMMENT_RULES,
|
||||
*_SHARED_ISSUE_LOCK_RULES,
|
||||
@@ -1348,6 +1491,7 @@ def assess_final_report_validator(
|
||||
issue_filing_lock: dict | None = None,
|
||||
session_pr_opened: bool = False,
|
||||
validation_session: dict | None = None,
|
||||
reconciler_close_lock: dict | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate final-report text against task-specific proof rules (#327).
|
||||
|
||||
@@ -1404,6 +1548,7 @@ def assess_final_report_validator(
|
||||
"local_edits": local_edits,
|
||||
"session_pr_opened": session_pr_opened,
|
||||
"validation_session": validation_session,
|
||||
"reconciler_close_lock": reconciler_close_lock,
|
||||
}
|
||||
|
||||
for rule in _RULES_BY_TASK.get(normalized_kind, ()):
|
||||
|
||||
Reference in New Issue
Block a user