Files
Gitea-Tools/tests/test_issue_709_decision_lock_cross_profile.py
T
sysadminandGrok 4.5 9cb12ee0f4 fix(workflow): non-forgeable irrecoverable auth, merger consumer, exact-scope cleanup (#709)
Address formal review 434 REQUEST_CHANGES on PR #710:
- F1: replace caller operator_authorized with server-side HMAC auth artifacts
- F2: implement fail-closed merger consumption for prior-provenance only
- F3: enforce remote/org/repo/head on cross-profile load and clear

Co-Authored-By: Grok 4.5 (xAI) <[email protected]>
2026-07-14 01:36:39 -04:00

1144 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""#709: cross-profile decision-lock cleanup, overwrite protection, recovery.
Covers AC1AC8 plus review-434 F1/F2/F3 remediations without fabricating
historical PR provenance or special-casing live PR numbers in production code.
"""
from __future__ import annotations
import os
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
import irrecoverable_provenance as irp
import mcp_session_state as ss
import stale_review_decision_lock as srdl
def _lock(
mutations=None,
*,
profile="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
head=None,
):
muts = []
for m in mutations or []:
row = dict(m)
if head and "head_sha" not in row:
row["head_sha"] = head
muts.append(row)
return {
"task": "review_pr",
"remote": remote,
"org": org,
"repo": repo,
"session_pid": os.getpid(),
"session_profile": profile,
"session_profile_lock": profile,
"profile_identity": profile,
"final_review_decision_ready": False,
"ready_pr_number": None,
"ready_action": None,
"ready_expected_head_sha": None,
"live_mutations": muts,
"correction_authorized": False,
"correction_reason": None,
"kind": ss.KIND_DECISION_LOCK,
}
APPROVE = {"pr_number": 100, "action": "approve", "review_id": 9}
APPROVE_OTHER = {"pr_number": 200, "action": "approve", "review_id": 10}
HEAD_A = "a" * 40
HEAD_B = "b" * 40
RECONCILER_OPS = [
"gitea.read",
"gitea.pr.close",
"gitea.pr.comment",
"gitea.issue.comment",
]
def _mint_auth(
*,
pr_number=42,
head=HEAD_A,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
incident_issue=700,
incident_comment_id=11489,
issuer="sysadmin",
profile="prgs-reconciler",
destroyed_subject=None,
):
return irp.build_authorization_artifact(
remote=remote,
org=org,
repo=repo,
pr_number=pr_number,
expected_head_sha=head,
incident_issue=incident_issue,
incident_comment_id=incident_comment_id,
destroyed_subject=destroyed_subject,
issuer_username=issuer,
issuer_profile=profile,
native_provenance={
"native_mcp_transport": True,
"production_native_mcp_transport": False,
"pytest": True,
"token_fingerprint": "testfp",
"entrypoint": "pytest",
"pid": os.getpid(),
},
)
class TestAC2InitOverwrite(unittest.TestCase):
def test_empty_lock_allows_reinit(self):
a = srdl.assess_init_overwrite(_lock([]), force=True)
self.assertTrue(a["overwrite_allowed"])
def test_terminal_lock_blocks_force_reinit(self):
a = srdl.assess_init_overwrite(_lock([APPROVE]), force=True)
self.assertFalse(a["overwrite_allowed"])
self.assertTrue(a["has_unresolved_terminal"])
self.assertEqual(a["last_terminal_pr"], 100)
def test_none_lock_allows_init(self):
a = srdl.assess_init_overwrite(None)
self.assertTrue(a["overwrite_allowed"])
class TestAC1TargetApproval(unittest.TestCase):
def test_targets_matching_approve(self):
self.assertTrue(
srdl.lock_targets_merged_pr_approval(
_lock([APPROVE], head=HEAD_A),
pr_number=100,
expected_head_sha=HEAD_A,
)
)
def test_rejects_other_pr(self):
self.assertFalse(
srdl.lock_targets_merged_pr_approval(
_lock([APPROVE_OTHER]), pr_number=100
)
)
def test_rejects_head_mismatch(self):
self.assertFalse(
srdl.lock_targets_merged_pr_approval(
_lock([APPROVE], head=HEAD_A),
pr_number=100,
expected_head_sha=HEAD_B,
)
)
class TestF1AuthorizationNotSelfAssertable(unittest.TestCase):
def test_operator_authorized_true_cannot_authorize_via_build(self):
rec = srdl.build_irrecoverable_provenance_record(
pr_number=42,
head_sha=HEAD_A,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
actor_username="sysadmin",
profile_name="prgs-reconciler",
reason="evidence destroyed",
incident_ref="anything",
operator_authorized=True,
)
self.assertFalse(rec["applied"])
self.assertFalse(rec["historical_cleanup_proven"])
self.assertFalse(rec["merger_may_accept"])
def test_confirmation_string_not_authorization(self):
# Confirmation is only intent text; capability assess ignores it.
conf = irp.expected_confirmation(99)
self.assertEqual(conf, "IRRECOVERABLE DECISION PROVENANCE PR 99")
# Without auth artifact, merger cannot accept.
rec = srdl.build_irrecoverable_provenance_record(
pr_number=99,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="x",
profile_name="y",
reason="r",
operator_authorized=False,
)
self.assertFalse(rec["merger_may_accept"])
def test_gitea_read_alone_insufficient(self):
a = irp.assess_capability_for_irrecoverable_recovery(
allowed_operations=["gitea.read"],
forbidden_operations=[],
role_kind="author",
profile_name="prgs-author",
)
self.assertFalse(a["allowed"])
def test_expected_head_missing_fails(self):
g = irp.assess_live_head_binding(
expected_head_sha=None,
live_head_sha=HEAD_A,
)
self.assertFalse(g["valid"])
def test_expected_head_differs_fails(self):
g = irp.assess_live_head_binding(
expected_head_sha=HEAD_A,
live_head_sha=HEAD_B,
)
self.assertFalse(g["valid"])
def test_missing_incident_fails(self):
g = irp.assess_incident_evidence(
incident_issue=None,
incident_comment_id=None,
comment_payload=None,
)
self.assertFalse(g["valid"])
def test_nonexistent_incident_fails(self):
g = irp.assess_incident_evidence(
incident_issue=1,
incident_comment_id=2,
comment_payload=None,
comment_lookup_error="404",
)
self.assertFalse(g["valid"])
def test_valid_auth_succeeds_exact_scope(self):
auth = _mint_auth()
v = irp.verify_authorization_artifact(
auth,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
pr_number=42,
expected_head_sha=HEAD_A,
incident_issue=700,
incident_comment_id=11489,
)
self.assertTrue(v["valid"], v)
rec = irp.build_irrecoverable_provenance_record(
pr_number=42,
head_sha=HEAD_A,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
actor_username="sysadmin",
profile_name="prgs-reconciler",
reason="evidence destroyed",
incident_issue=700,
incident_comment_id=11489,
authorization=auth,
)
self.assertTrue(rec["merger_may_accept"])
self.assertFalse(rec["applied"])
body = irp.format_irrecoverable_audit_comment(rec)
self.assertIn("applied: `False`", body)
def test_wrong_repo_auth_fails(self):
auth = _mint_auth(repo="Other-Repo")
v = irp.verify_authorization_artifact(
auth,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
pr_number=42,
expected_head_sha=HEAD_A,
incident_issue=700,
incident_comment_id=11489,
)
self.assertFalse(v["valid"])
def test_wrong_pr_auth_fails(self):
auth = _mint_auth(pr_number=1)
v = irp.verify_authorization_artifact(
auth,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
pr_number=42,
expected_head_sha=HEAD_A,
)
self.assertFalse(v["valid"])
def test_wrong_head_auth_fails(self):
auth = _mint_auth(head=HEAD_B)
v = irp.verify_authorization_artifact(
auth,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
pr_number=42,
expected_head_sha=HEAD_A,
)
self.assertFalse(v["valid"])
def test_altered_signature_fails(self):
auth = _mint_auth()
auth["server_signature"] = "0" * 64
v = irp.verify_authorization_artifact(
auth,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
pr_number=42,
expected_head_sha=HEAD_A,
incident_issue=700,
incident_comment_id=11489,
)
self.assertFalse(v["valid"])
def test_fresh_non_pytest_process_cannot_mint_accepted_record(self):
"""Ordinary Python process: merger_may_accept stays False without server auth."""
script = (
"import stale_review_decision_lock as s\n"
"r=s.build_irrecoverable_provenance_record(\n"
" pr_number=1, head_sha=None, remote='prgs', org=None, repo=None,\n"
" actor_username='x', profile_name='y', reason='r',\n"
" incident_ref=None, operator_authorized=True)\n"
"print(r.get('merger_may_accept'), r.get('head_sha'))\n"
)
env = {k: v for k, v in os.environ.items() if not k.startswith("PYTEST")}
env.pop("PYTEST_CURRENT_TEST", None)
proc = subprocess.run(
[sys.executable, "-c", script],
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
capture_output=True,
text=True,
env=env,
timeout=30,
)
self.assertEqual(proc.returncode, 0, proc.stderr)
out = (proc.stdout or "").strip()
self.assertTrue(out.startswith("False"), msg=out)
def test_unauthorized_profile_capability(self):
a = irp.assess_capability_for_irrecoverable_recovery(
allowed_operations=["gitea.read", "gitea.pr.comment"],
forbidden_operations=["gitea.pr.close"],
role_kind="author",
profile_name="prgs-author",
)
self.assertFalse(a["allowed"])
def test_reconciler_capability_allowed(self):
a = irp.assess_capability_for_irrecoverable_recovery(
allowed_operations=RECONCILER_OPS,
forbidden_operations=[
"gitea.pr.approve",
"gitea.pr.merge",
"gitea.pr.review",
],
role_kind="reconciler",
profile_name="prgs-reconciler",
)
self.assertTrue(a["allowed"], a)
class TestF2MergerConsumer(unittest.TestCase):
def test_merger_rejects_without_valid_auth(self):
rec = srdl.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
operator_authorized=True,
)
a = irp.assess_merger_consumption(
rec,
None,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_A,
approval_at_current_head=True,
has_blocking_change_requests=False,
)
self.assertFalse(a["allowed"])
def test_merger_rejects_wrong_head(self):
auth = _mint_auth(pr_number=10, head=HEAD_A, org="o", repo="r")
rec = irp.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
incident_issue=700,
incident_comment_id=1,
authorization=auth,
)
a = irp.assess_merger_consumption(
rec,
auth,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_B,
approval_at_current_head=True,
has_blocking_change_requests=False,
)
self.assertFalse(a["allowed"])
def test_merger_rejects_replayed_auth(self):
auth = _mint_auth(pr_number=10, head=HEAD_A, org="o", repo="r", incident_comment_id=1)
consumed = irp.mark_consumed(auth, consumer_username="m", consumer_profile="merger")
rec = irp.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
incident_issue=700,
incident_comment_id=1,
authorization=auth, # original unconsumed for record build
)
a = irp.assess_merger_consumption(
rec,
consumed,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_A,
approval_at_current_head=True,
has_blocking_change_requests=False,
)
self.assertFalse(a["allowed"])
def test_recovery_cannot_bypass_missing_approval(self):
auth = _mint_auth(pr_number=10, head=HEAD_A, org="o", repo="r", incident_comment_id=1)
rec = irp.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
incident_issue=700,
incident_comment_id=1,
authorization=auth,
)
a = irp.assess_merger_consumption(
rec,
auth,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_A,
approval_at_current_head=False,
has_blocking_change_requests=False,
)
self.assertFalse(a["allowed"])
self.assertTrue(any("approval" in r for r in a["reasons"]))
def test_recovery_cannot_bypass_blocking_crs(self):
auth = _mint_auth(pr_number=10, head=HEAD_A, org="o", repo="r", incident_comment_id=1)
rec = irp.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
incident_issue=700,
incident_comment_id=1,
authorization=auth,
)
a = irp.assess_merger_consumption(
rec,
auth,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_A,
approval_at_current_head=True,
has_blocking_change_requests=True,
)
self.assertFalse(a["allowed"])
def test_valid_recovery_resolves_only_prior_provenance(self):
auth = _mint_auth(pr_number=10, head=HEAD_A, org="o", repo="r", incident_comment_id=1)
rec = irp.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
incident_issue=700,
incident_comment_id=1,
authorization=auth,
)
a = irp.assess_merger_consumption(
rec,
auth,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_A,
approval_at_current_head=True,
has_blocking_change_requests=False,
mergeable=True,
lease_ok=True,
runtime_ok=True,
workspace_ok=True,
anti_stomp_ok=True,
)
self.assertTrue(a["allowed"], a)
self.assertTrue(a["resolves_prior_provenance_blocker"])
self.assertFalse(a["historical_cleanup_proven"])
def test_duplicate_consume_idempotent(self):
auth = _mint_auth(pr_number=10, head=HEAD_A, org="o", repo="r", incident_comment_id=1)
rec = irp.build_irrecoverable_provenance_record(
pr_number=10,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="x",
incident_issue=700,
incident_comment_id=1,
authorization=auth,
)
consumed_auth = irp.mark_consumed(auth, consumer_username="m", consumer_profile="mer")
consumed_rec = irp.mark_consumed(rec, consumer_username="m", consumer_profile="mer")
a = irp.assess_merger_consumption(
consumed_rec,
consumed_auth,
remote="prgs",
org="o",
repo="r",
pr_number=10,
live_head_sha=HEAD_A,
approval_at_current_head=True,
has_blocking_change_requests=False,
)
self.assertTrue(a["allowed"], a)
self.assertTrue(a["recovery_record_consumed"])
class TestF3ExactScopeEnforcement(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.state_dir = self._tmp.name
os.chmod(self.state_dir, 0o700)
def tearDown(self):
self._tmp.cleanup()
def test_same_pr_other_remote_not_loaded(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([APPROVE], profile="prgs-reviewer", remote="other"),
remote="other",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-reviewer",
state_dir=self.state_dir,
)
loaded = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
state_dir=self.state_dir,
skip_identity_match=True,
)
self.assertIsNone(loaded)
def test_same_pr_other_org_not_loaded(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock(
[APPROVE],
profile="prgs-reviewer",
org="Other-Org",
),
remote="prgs",
org="Other-Org",
repo="Gitea-Tools",
profile_identity="prgs-reviewer",
state_dir=self.state_dir,
)
loaded = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
state_dir=self.state_dir,
skip_identity_match=True,
)
self.assertIsNone(loaded)
def test_same_pr_other_repo_not_loaded(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock(
[APPROVE],
profile="prgs-reviewer",
repo="Other-Repo",
),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Other-Repo",
profile_identity="prgs-reviewer",
state_dir=self.state_dir,
)
loaded = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
state_dir=self.state_dir,
skip_identity_match=True,
)
self.assertIsNone(loaded)
def test_path_traversal_profile_fails(self):
g = irp.assess_profile_path_identity("../evil")
self.assertFalse(g["valid"])
loaded = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="../evil",
remote="prgs",
org="o",
repo="r",
state_dir=self.state_dir,
skip_identity_match=True,
)
self.assertIsNone(loaded)
def test_malformed_legacy_missing_repo_fails_closed(self):
# Record without repo identity when caller requires repo.
payload = _lock([APPROVE], profile="prgs-reviewer")
del payload["repo"]
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=payload,
remote="prgs",
org="Scaled-Tech-Consulting",
repo=None,
profile_identity="prgs-reviewer",
state_dir=self.state_dir,
)
loaded = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
state_dir=self.state_dir,
skip_identity_match=True,
)
self.assertIsNone(loaded)
def test_list_and_load_foreign_profile_lock(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([APPROVE], profile="prgs-reviewer"),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-reviewer",
state_dir=self.state_dir,
)
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([], profile="prgs-merger"),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-merger",
state_dir=self.state_dir,
)
foreign = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
state_dir=self.state_dir,
skip_identity_match=True,
)
self.assertIsNotNone(foreign)
self.assertTrue(
srdl.lock_targets_merged_pr_approval(foreign, pr_number=100)
)
class TestAC3PostMergeRecoveryRecord(unittest.TestCase):
def test_recovery_record_is_not_applied_cleanup(self):
rec = srdl.build_post_merge_recovery_record(
pr_number=10,
head_sha=HEAD_A,
merge_commit_sha="m" * 40,
target_profile_identity="prgs-reviewer",
failed_step="audit_comment_publish",
error="timeout",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
actor_username="sysadmin",
profile_name="prgs-merger",
)
self.assertEqual(rec["status"], "recovery_required")
self.assertFalse(rec["applied"])
self.assertTrue(rec["recovery_critical"])
class TestSessionStateTTL(unittest.TestCase):
def test_recovery_critical_kinds_ttl_exempt(self):
auth = _mint_auth(pr_number=1)
rec = irp.build_irrecoverable_provenance_record(
pr_number=1,
head_sha=HEAD_A,
remote="prgs",
org="o",
repo="r",
actor_username="a",
profile_name="p",
reason="gone",
incident_issue=700,
incident_comment_id=1,
authorization=auth,
)
rec["kind"] = ss.KIND_IRRECOVERABLE_DECISION_PROVENANCE
rec["recorded_at"] = "2000-01-01T00:00:00Z"
rec["updated_at"] = rec["recorded_at"]
rec["profile_identity"] = "prgs-reconciler"
rec["session_profile_lock"] = "prgs-reconciler"
reasons = ss.identity_match_reasons(
rec, profile_identity="prgs-reconciler"
)
self.assertFalse(any("expired" in r for r in reasons), msg=reasons)
class TestInitReviewDecisionLockIntegration(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.env = patch.dict(
os.environ,
{
"GITEA_MCP_SESSION_STATE_DIR": self._tmp.name,
"GITEA_SESSION_PROFILE_LOCK": "prgs-reviewer",
"GITEA_PROFILE_NAME": "prgs-reviewer",
},
clear=False,
)
self.env.start()
import mcp_server
self.mcp = mcp_server
self.mcp._REVIEW_DECISION_LOCK = None
def tearDown(self):
self.mcp._REVIEW_DECISION_LOCK = None
self.env.stop()
self._tmp.cleanup()
def test_init_does_not_wipe_terminal_ledger(self):
self.mcp._save_review_decision_lock(
_lock([APPROVE], profile="prgs-reviewer")
)
self.mcp.init_review_decision_lock("prgs", "review_pr", force=True)
loaded = self.mcp._load_review_decision_lock()
self.assertIsNotNone(loaded)
last = srdl.last_terminal_mutation(loaded)
self.assertIsNotNone(last)
self.assertEqual(last.get("pr_number"), 100)
def test_init_creates_empty_when_no_terminal(self):
self.mcp._save_review_decision_lock(None)
self.mcp.init_review_decision_lock("prgs", "review_pr", force=True)
loaded = self.mcp._load_review_decision_lock()
self.assertIsNotNone(loaded)
self.assertEqual(loaded.get("live_mutations"), [])
class TestIrrecoverableToolF1(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.env = patch.dict(
os.environ,
{
"GITEA_MCP_SESSION_STATE_DIR": self._tmp.name,
"GITEA_SESSION_PROFILE_LOCK": "prgs-reconciler",
"GITEA_PROFILE_NAME": "prgs-reconciler",
"GITEA_ALLOWED_OPERATIONS": ",".join(RECONCILER_OPS),
},
clear=False,
)
self.env.start()
import mcp_server
self.mcp = mcp_server
def tearDown(self):
self.env.stop()
self._tmp.cleanup()
def _profile(self):
return {
"profile_name": "prgs-reconciler",
"role": "reconciler",
"allowed_operations": RECONCILER_OPS,
"forbidden_operations": [
"gitea.pr.approve",
"gitea.pr.merge",
"gitea.pr.review",
],
}
def test_operator_authorized_true_rejected(self):
with patch.object(self.mcp, "get_profile", return_value=self._profile()):
r = self.mcp.gitea_record_irrecoverable_decision_lock_provenance(
pr_number=50,
reason="lost",
confirmation=irp.expected_confirmation(50),
operator_authorized=True,
expected_head_sha=HEAD_A,
incident_issue=700,
incident_comment_id=11489,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
post_audit_comment=False,
)
self.assertFalse(r["success"])
self.assertTrue(any("operator_authorized" in x for x in r["reasons"]))
def test_missing_expected_head_fails(self):
with patch.object(self.mcp, "get_profile", return_value=self._profile()), patch.object(
self.mcp, "_authenticated_username", return_value="sysadmin"
), patch.object(
self.mcp, "_irrecoverable_capability_gate", return_value=None
), patch.object(
irp, "assess_transport_for_auth_mint", return_value={"allowed": True, "reasons": []}
), patch.object(
self.mcp, "_resolve", return_value=("h", "Scaled-Tech-Consulting", "Gitea-Tools")
):
r = self.mcp.gitea_record_irrecoverable_decision_lock_provenance(
pr_number=50,
reason="lost",
confirmation=irp.expected_confirmation(50),
expected_head_sha=None,
incident_issue=700,
incident_comment_id=11489,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
post_audit_comment=False,
)
self.assertFalse(r["success"])
def test_wrong_confirmation_fails(self):
with patch.object(self.mcp, "get_profile", return_value=self._profile()), patch.object(
self.mcp, "_irrecoverable_capability_gate", return_value=None
), patch.object(
irp, "assess_transport_for_auth_mint", return_value={"allowed": True, "reasons": []}
), patch.object(
self.mcp, "_resolve", return_value=("h", "o", "r")
):
r = self.mcp.gitea_record_irrecoverable_decision_lock_provenance(
pr_number=50,
reason="x",
confirmation=irp.expected_confirmation(51),
expected_head_sha=HEAD_A,
incident_issue=1,
incident_comment_id=2,
remote="prgs",
post_audit_comment=False,
)
self.assertFalse(r["success"])
def test_records_with_server_auth(self):
auth = _mint_auth(
pr_number=50,
head=HEAD_A,
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
incident_issue=700,
incident_comment_id=11489,
)
auth_profile = irp.auth_state_profile_identity(
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
pr_number=50,
expected_head_sha=HEAD_A,
)
ss.save_state(
kind=ss.KIND_IRRECOVERABLE_PROVENANCE_AUTH,
payload=auth,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity=auth_profile,
state_dir=self._tmp.name,
)
def _api(method, url, auth=None, data=None, **kwargs):
if "/pulls/" in str(url):
return {"head": {"sha": HEAD_A}, "state": "open"}
if "/issues/comments/" in str(url):
return {
"id": 11489,
"body": "forensic diagnosis",
"user": {"login": "sysadmin"},
"created_at": "2026-07-13T00:00:00Z",
}
raise AssertionError(f"unexpected API {method} {url}")
common = dict(
get_profile=self._profile(),
)
with patch.object(self.mcp, "get_profile", return_value=self._profile()), patch.object(
self.mcp, "_authenticated_username", return_value="sysadmin"
), patch.object(
self.mcp, "_irrecoverable_capability_gate", return_value=None
), patch.object(
irp, "assess_transport_for_auth_mint", return_value={"allowed": True, "reasons": []}
), patch.object(
self.mcp, "_resolve", return_value=("h", "Scaled-Tech-Consulting", "Gitea-Tools")
), patch.object(
self.mcp, "_auth", return_value={"Authorization": "token test"}
), patch.object(
self.mcp, "api_request", side_effect=_api
), patch.object(
self.mcp, "repo_api_url", return_value="https://example.test/api/v1/repos/o/r"
):
r = self.mcp.gitea_record_irrecoverable_decision_lock_provenance(
pr_number=50,
reason="terminal evidence overwritten",
confirmation=irp.expected_confirmation(50),
expected_head_sha=HEAD_A,
incident_issue=700,
incident_comment_id=11489,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
post_audit_comment=False,
)
self.assertTrue(r["success"], r)
self.assertFalse(r["applied"])
self.assertFalse(r["historical_cleanup_proven"])
self.assertTrue(r["merger_may_accept"])
self.assertEqual(r["record"]["status"], "provenance_irrecoverable")
# Idempotent
with patch.object(self.mcp, "get_profile", return_value=self._profile()), patch.object(
self.mcp, "_authenticated_username", return_value="sysadmin"
), patch.object(
self.mcp, "_irrecoverable_capability_gate", return_value=None
), patch.object(
irp, "assess_transport_for_auth_mint", return_value={"allowed": True, "reasons": []}
), patch.object(
self.mcp, "_resolve", return_value=("h", "Scaled-Tech-Consulting", "Gitea-Tools")
), patch.object(
self.mcp, "_auth", return_value={"Authorization": "token test"}
), patch.object(
self.mcp, "api_request", side_effect=_api
), patch.object(
self.mcp, "repo_api_url", return_value="https://example.test/api/v1/repos/o/r"
):
r2 = self.mcp.gitea_record_irrecoverable_decision_lock_provenance(
pr_number=50,
reason="terminal evidence overwritten",
confirmation=irp.expected_confirmation(50),
expected_head_sha=HEAD_A,
incident_issue=700,
incident_comment_id=11489,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
post_audit_comment=False,
)
self.assertTrue(r2["success"], r2)
self.assertFalse(r2["performed"])
class TestClearProfileHelperF3(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.env = patch.dict(
os.environ,
{
"GITEA_MCP_SESSION_STATE_DIR": self._tmp.name,
"GITEA_SESSION_PROFILE_LOCK": "prgs-merger",
},
clear=False,
)
self.env.start()
import mcp_server
self.mcp = mcp_server
self.mcp._REVIEW_DECISION_LOCK = None
def tearDown(self):
self.mcp._REVIEW_DECISION_LOCK = None
self.env.stop()
self._tmp.cleanup()
def test_clear_only_matching_reviewer_approve(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([APPROVE], profile="prgs-reviewer", head=HEAD_A),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-reviewer",
state_dir=self._tmp.name,
)
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([], profile="prgs-merger"),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-merger",
state_dir=self._tmp.name,
)
out = self.mcp._clear_decision_lock_for_profile(
profile_identity="prgs-reviewer",
pr_number=100,
expected_head_sha=HEAD_A,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
)
self.assertTrue(out["cleared"], out)
skip = self.mcp._clear_decision_lock_for_profile(
profile_identity="prgs-merger",
pr_number=100,
expected_head_sha=HEAD_A,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
)
self.assertFalse(skip["cleared"])
def test_pr_number_only_fallback_impossible(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([APPROVE], profile="prgs-reviewer", head=HEAD_A),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-reviewer",
state_dir=self._tmp.name,
)
# Missing expected_head_sha
out = self.mcp._clear_decision_lock_for_profile(
profile_identity="prgs-reviewer",
pr_number=100,
expected_head_sha=None,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
)
self.assertFalse(out["cleared"])
self.assertIn("PR-number-only", out["reason"])
def test_wrong_head_not_cleared(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock([APPROVE], profile="prgs-reviewer", head=HEAD_A),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
profile_identity="prgs-reviewer",
state_dir=self._tmp.name,
)
out = self.mcp._clear_decision_lock_for_profile(
profile_identity="prgs-reviewer",
pr_number=100,
expected_head_sha=HEAD_B,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
)
self.assertFalse(out["cleared"])
def test_cross_repo_same_pr_number_not_cleared(self):
ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload=_lock(
[APPROVE],
profile="prgs-reviewer",
head=HEAD_A,
repo="Other-Repo",
),
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Other-Repo",
profile_identity="prgs-reviewer",
state_dir=self._tmp.name,
)
out = self.mcp._clear_decision_lock_for_profile(
profile_identity="prgs-reviewer",
pr_number=100,
expected_head_sha=HEAD_A,
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Gitea-Tools",
)
self.assertFalse(out["cleared"])
# Original lock still present under Other-Repo scope
still = ss.load_state_for_profile(
kind=ss.KIND_DECISION_LOCK,
profile_identity="prgs-reviewer",
remote="prgs",
org="Scaled-Tech-Consulting",
repo="Other-Repo",
state_dir=self._tmp.name,
skip_identity_match=True,
)
self.assertIsNotNone(still)
if __name__ == "__main__":
unittest.main()