fix(mcp): mirror abandoned terminal phase into pr_work_lease (#742 review 460)
Addresses REQUEST_CHANGES review 460 on PR #743 @22d0fdd. reviewer_pr_lease._TERMINAL_PHASES gained "abandoned" for owner-session merger finalization, but pr_work_lease._TERMINAL_REVIEWER_PHASES did not. The two modules parse the same append-only lease markers, so the same comment read as terminal through reviewer_pr_lease and active through pr_work_lease, leaving the conflict-fix acquire and PR-sync inventory readers with a stale active lease after an abandoned finalization. Fix: add "abandoned" to pr_work_lease._TERMINAL_REVIEWER_PHASES, with a comment recording that the two sets must stay mirrored. Reproduced before the fix (single abandoned marker): reviewer_pr_lease active=False, pr_work_lease active=True After: both False; released/blocked/done unchanged in both modules. Tests: new TestCrossModuleTerminalPhaseAgreement in tests/test_merger_lease_finalization.py proves the abandoned marker is inactive in pr_work_lease, that both modules agree for released/blocked/done/abandoned, that claimed/validating stay active in both, that the two phase sets are mirrored, that the default 'released' finalization outcome and reason are unchanged, and that finalization appends without rewriting or deleting the prior marker. Separately pinned, NOT fixed here: on a claim-then-terminal ledger, pr_work_lease.find_active_reviewer_lease skips the terminal marker and walks back to the older claim, so it still reports an active lease. That newest-wins gap is pre-existing on clean baselinea8d2087for released, blocked, and done alike — the #577 fix landed in reviewer_pr_lease only — and is out of scope for this bounded remediation. test_abandoned_matches_preexisting_terminal_phases_on_full_ledger pins that "abandoned" introduces no behavior of its own, so all four phases can be reconciled in one separate change. Validation: focused TestCrossModuleTerminalPhaseAgreement 7 passed / 9 subtests; tests/test_merger_lease_finalization.py 45 passed / 20 subtests; related pr_work_lease + reviewer/merger lease + adoption + provenance + capability-map + anti-stomp + report-validator suites 261 passed / 41 subtests; full suite 3312 passed, 6 skipped, 2 failed. Both failures (test_issue_702_review_findings_f1_f6 F1 recovery, reconciler supersession close) reproduce identically on clean baseline mastera8d2087and are pre-existing #737 org/repo-forwarding drift. git diff --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
+5
-1
@@ -20,7 +20,11 @@ _FIELD_RE = re.compile(
|
||||
re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
|
||||
_TERMINAL_REVIEWER_PHASES = frozenset({"done", "released", "blocked"})
|
||||
# Must mirror reviewer_pr_lease._TERMINAL_PHASES: both modules read the same
|
||||
# append-only lease markers, so a phase that is terminal in one and active in
|
||||
# the other yields two conflicting truths for the same comment (#742 review
|
||||
# 460). "abandoned" is the owner-session merger finalization outcome.
|
||||
_TERMINAL_REVIEWER_PHASES = frozenset({"done", "released", "blocked", "abandoned"})
|
||||
_ACTIVE_REVIEWER_PHASES = frozenset({
|
||||
"claimed",
|
||||
"validating",
|
||||
|
||||
@@ -24,6 +24,7 @@ sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.par
|
||||
|
||||
import gitea_mcp_server as mcp_server # noqa: E402
|
||||
import merger_lease_adoption as mla # noqa: E402
|
||||
import pr_work_lease as pwl # noqa: E402
|
||||
import reviewer_pr_lease as leases # noqa: E402
|
||||
import task_capability_map as tcm # noqa: E402
|
||||
|
||||
@@ -467,6 +468,115 @@ class TestFinalizationAssessment(unittest.TestCase):
|
||||
self.assertEqual(result["lease_comment_id"], 12354)
|
||||
|
||||
|
||||
class TestCrossModuleTerminalPhaseAgreement(unittest.TestCase):
|
||||
"""#742 review 460 — reviewer_pr_lease and pr_work_lease must agree.
|
||||
|
||||
Both modules parse the same append-only lease markers. A phase that is
|
||||
terminal in one and active in the other produces two conflicting truths for
|
||||
the same comment, so an abandoned finalization would still read as an active
|
||||
lease to the conflict-fix acquire and PR-sync inventory paths.
|
||||
"""
|
||||
|
||||
TERMINAL_PHASES = ("released", "blocked", "done", "abandoned")
|
||||
NONTERMINAL_PHASES = ("claimed", "validating")
|
||||
|
||||
def _both_active(self, phase: str) -> tuple[bool, bool]:
|
||||
comments = [_lease_comment(phase=phase)]
|
||||
return (
|
||||
leases.find_active_reviewer_lease(comments, pr_number=PR) is not None,
|
||||
pwl.find_active_reviewer_lease(comments, pr_number=PR) is not None,
|
||||
)
|
||||
|
||||
def test_abandoned_marker_is_not_active_in_pr_work_lease(self):
|
||||
_, pwl_active = self._both_active("abandoned")
|
||||
self.assertFalse(pwl_active)
|
||||
|
||||
def test_terminal_phases_agree_across_modules(self):
|
||||
for phase in self.TERMINAL_PHASES:
|
||||
with self.subTest(phase=phase):
|
||||
rpl_active, pwl_active = self._both_active(phase)
|
||||
self.assertFalse(rpl_active)
|
||||
self.assertFalse(pwl_active)
|
||||
self.assertEqual(rpl_active, pwl_active)
|
||||
|
||||
def test_nonterminal_phases_remain_active_in_both_modules(self):
|
||||
for phase in self.NONTERMINAL_PHASES:
|
||||
with self.subTest(phase=phase):
|
||||
rpl_active, pwl_active = self._both_active(phase)
|
||||
self.assertTrue(rpl_active)
|
||||
self.assertTrue(pwl_active)
|
||||
|
||||
def test_terminal_phase_sets_are_mirrored(self):
|
||||
self.assertEqual(
|
||||
set(leases._TERMINAL_PHASES), set(pwl._TERMINAL_REVIEWER_PHASES)
|
||||
)
|
||||
|
||||
def test_default_released_finalization_behavior_unchanged(self):
|
||||
# The default outcome is still 'released', and a released marker ends
|
||||
# the lease for both modules exactly as before this change.
|
||||
leases.clear_session_lease()
|
||||
try:
|
||||
session = _record_acquired()
|
||||
result = mla.assess_merger_lease_finalization(
|
||||
[_lease_comment()],
|
||||
pr_number=PR,
|
||||
session=session,
|
||||
actor_identity="merger-user",
|
||||
actor_profile="prgs-merger",
|
||||
actor_session_id="merger-session",
|
||||
repo=REPO,
|
||||
worktree="branches/merger-pr740",
|
||||
candidate_head=HEAD,
|
||||
live_head_sha=HEAD,
|
||||
)
|
||||
self.assertTrue(result["finalize_allowed"], result["reasons"])
|
||||
self.assertEqual(result["outcome"], mla.OUTCOME_RELEASED)
|
||||
self.assertIn("phase: released", result["finalization_body"])
|
||||
self.assertEqual(
|
||||
result["finalization_reason"],
|
||||
mla.DEFAULT_MERGER_FINALIZATION_REASON,
|
||||
)
|
||||
finally:
|
||||
leases.clear_session_lease()
|
||||
|
||||
def test_finalization_appends_and_never_rewrites_prior_markers(self):
|
||||
claimed = _lease_comment()
|
||||
original_body = claimed["body"]
|
||||
ledger = [claimed]
|
||||
# A terminal marker is appended alongside the original claim; the
|
||||
# earlier marker is left byte-for-byte intact and is never removed.
|
||||
terminal = _lease_comment(phase="abandoned", comment_id=12361)
|
||||
ledger.append(terminal)
|
||||
self.assertEqual(len(ledger), 2)
|
||||
self.assertEqual(ledger[0]["body"], original_body)
|
||||
self.assertEqual(ledger[0]["id"], 12354)
|
||||
# Newest-wins (#577): the appended terminal marker ends the lease.
|
||||
self.assertIsNone(leases.find_active_reviewer_lease(ledger, pr_number=PR))
|
||||
|
||||
def test_abandoned_matches_preexisting_terminal_phases_on_full_ledger(self):
|
||||
"""'abandoned' must behave exactly like the other terminal phases.
|
||||
|
||||
On a claim-then-terminal ledger, pr_work_lease.find_active_reviewer_lease
|
||||
skips the terminal marker and walks back to the older claim, so it still
|
||||
reports an active lease. That newest-wins gap is pre-existing on clean
|
||||
baseline a8d2087 for 'released', 'blocked', and 'done' alike (the #577
|
||||
fix landed in reviewer_pr_lease only) and is out of scope for #742; this
|
||||
test pins that 'abandoned' introduces no behavior of its own, so the two
|
||||
modules can be reconciled for all four phases in one separate change.
|
||||
"""
|
||||
def pwl_active(phase):
|
||||
ledger = [
|
||||
_lease_comment(),
|
||||
_lease_comment(phase=phase, comment_id=12361),
|
||||
]
|
||||
return pwl.find_active_reviewer_lease(ledger, pr_number=PR) is not None
|
||||
|
||||
baseline = pwl_active("released")
|
||||
for phase in ("blocked", "done", "abandoned"):
|
||||
with self.subTest(phase=phase):
|
||||
self.assertEqual(pwl_active(phase), baseline)
|
||||
|
||||
|
||||
class TestReleaseMergerLeaseTool(unittest.TestCase):
|
||||
"""AC7/AC9/AC10/AC11 — the native tool contract end to end."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user