fix(guard): derive cross-repository target base ref

Cross-repository mutation gating assumed the tracking base ref was
prgs/master, and parity reporting independently assumed origin/master.
A namespace bound to any other repository -- for example remote MDCPS on
integration branch dev -- could not prove base equivalence, so every
gated mutation failed closed with no reachable remedy. The two modules
also disagreed with each other, so at most one could be right for any
given repository.

Derive the target instead of assuming it. canonical_repository_root
already discovered the correct remote while resolving repository
identity and then discarded its name; it now returns that name with its
exact configured case preserved, and resolve_target_base_ref() builds
refs/remotes/<remote>/<branch> from it. The integration branch comes
from refs/remotes/<remote>/HEAD -- git's own record of the remote's
default branch -- so no new configuration field is required. Only when
a remote publishes no such default does it fall back to exactly one
present integration-branch candidate.

Resolution fails closed with a machine-checkable reason_code when
identity is unprovable, when distinct remotes claim different
repositories, when several candidate branches exist with no recorded
default, or when no candidate exists. It never invents a branch, writes
a ref, or falls back to another repository's base.

Both the mutation guard and the parity report now consume that one
resolved target, so they cannot disagree again. Root-checkout
contamination names the ref it actually compared rather than a literal
prgs/master the target repository may not have.

Fixes an observable defect in this repository: refs/remotes/origin/master
survives as an orphan ref from a removed remote, so parity reported the
target stale against a dead commit while reporting its identity as
underivable.

PRGS behaviour is unchanged -- prgs/master still resolves via the
recorded remote HEAD to the same SHA, and an explicit remote_refs
override keeps the historical probe path verbatim.

Tests: 24 new hermetic regression tests covering PRGS prgs/master,
MDCPS/dev, no origin remote, exact remote-name case, equal/behind/
divergent targets, missing remote or ref, ambiguous remote and branch
resolution, gate/report agreement, and every affected production
caller. Full suite 6191 passed / 28 failed, byte-identical failure set
to the baseline at 108cbfa (zero introduced failures).

Refs #983

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-30 22:31:15 -04:00
co-authored by Claude Opus 4.8
parent 108cbfa173
commit 2d5d5c9d17
7 changed files with 945 additions and 38 deletions
+482
View File
@@ -0,0 +1,482 @@
"""Regression tests for Issue #983: derived target base ref for cross-repository checkouts.
The mutation guard previously assumed ``prgs/master`` and the parity report
assumed ``origin/master``. Any repository using neither — for example remote
``MDCPS`` on integration branch ``dev`` — could not prove base equivalence, so
every gated mutation failed closed with no reachable remedy.
These tests build hermetic git repositories on disk (no network, no fetch) and
assert the derived target end to end: identity remote, integration branch,
tracking ref, fail-closed refusals, and agreement between the mutation guard and
the parity report.
"""
from __future__ import annotations
import inspect
import os
import subprocess
import tempfile
import unittest
import anti_stomp_preflight
import canonical_repository_root as crr
import master_parity_gate
import root_checkout_guard
def _git(root: str, *args: str) -> str:
res = subprocess.run(
["git", "-C", root, *args],
capture_output=True,
text=True,
check=True,
)
return (res.stdout or "").strip()
def _make_repo(root: str, *, remote: str | None, url: str | None) -> str:
"""Initialise a repository with one commit and an optional named remote."""
os.makedirs(root, exist_ok=True)
_git(root, "init", "--quiet")
_git(root, "config", "user.email", "[email protected]")
_git(root, "config", "user.name", "Issue983 Test")
_git(root, "config", "commit.gpgsign", "false")
with open(os.path.join(root, "seed.txt"), "w", encoding="utf-8") as fh:
fh.write("seed\n")
_git(root, "add", "seed.txt")
_git(root, "commit", "--quiet", "-m", "seed")
if remote and url:
_git(root, "remote", "add", remote, url)
return _git(root, "rev-parse", "HEAD")
def _set_remote_branch(root: str, remote: str, branch: str, sha: str) -> None:
"""Create refs/remotes/<remote>/<branch> without contacting a network."""
_git(root, "update-ref", f"refs/remotes/{remote}/{branch}", sha)
def _set_remote_head(root: str, remote: str, branch: str) -> None:
_git(
root,
"symbolic-ref",
f"refs/remotes/{remote}/HEAD",
f"refs/remotes/{remote}/{branch}",
)
def _advance(root: str, message: str) -> str:
with open(os.path.join(root, "seed.txt"), "a", encoding="utf-8") as fh:
fh.write(message + "\n")
_git(root, "add", "seed.txt")
_git(root, "commit", "--quiet", "-m", message)
return _git(root, "rev-parse", "HEAD")
class _RepoCase(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
self.root = os.path.join(self._tmp.name, "repo")
class TestPrgsBehaviourPreserved(_RepoCase):
"""AC1: existing PRGS behaviour using prgs/master is unchanged."""
def test_prgs_master_resolves_unchanged(self):
head = _make_repo(
self.root,
remote="prgs",
url="https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git",
)
_set_remote_branch(self.root, "prgs", "master", head)
_set_remote_head(self.root, "prgs", "master")
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["remote"], "prgs")
self.assertEqual(got["branch"], "master")
self.assertEqual(got["tracking_ref"], "refs/remotes/prgs/master")
self.assertEqual(got["repository_slug"], "Scaled-Tech-Consulting/Gitea-Tools")
self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head)
def test_legacy_explicit_remote_refs_path_is_untouched(self):
"""An explicit remote_refs override still short-circuits derivation."""
head = _make_repo(
self.root,
remote="prgs",
url="https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git",
)
_set_remote_branch(self.root, "prgs", "master", head)
state = root_checkout_guard.resolve_remote_master_ref_state(
self.root, remote_refs=root_checkout_guard.REMOTE_MASTER_REFS
)
self.assertEqual(state["sha"], head)
self.assertEqual(state["source"], "explicit_remote_refs")
class TestCrossRepositoryTarget(_RepoCase):
"""AC2/AC3/AC4: MDCPS/dev, no origin remote, exact remote-name case."""
def _mdcps(self) -> str:
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_head(self.root, "MDCPS", "dev")
return head
def test_mdcps_dev_resolves(self):
head = self._mdcps()
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["remote"], "MDCPS")
self.assertEqual(got["branch"], "dev")
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
self.assertEqual(got["repository_slug"], "MDCPS/WeeklyBriefings-Meta")
self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head)
def test_no_remote_named_origin(self):
self._mdcps()
self.assertEqual(_git(self.root, "remote"), "MDCPS")
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"], got["reasons"])
self.assertNotIn("origin", got["tracking_ref"])
def test_remote_name_case_is_preserved_exactly(self):
self._mdcps()
got = crr.resolve_target_base_ref(self.root)
self.assertEqual(got["remote"], "MDCPS")
self.assertNotEqual(got["remote"], "mdcps")
# The tracking ref must address the real ref, which is case-sensitive.
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
self.assertTrue(
_git(self.root, "rev-parse", "--verify", got["tracking_ref"]),
"case-preserved tracking ref must resolve",
)
def test_lowercase_candidate_never_supplies_the_remote_name(self):
"""Guards against silently case-folding MDCPS to the candidate 'mdcps'.
``_IDENTITY_REMOTE_CANDIDATES`` contains a lowercase ``mdcps`` entry and
is probed *before* the repository's own remote listing. Git remote names
live in case-sensitive config subsections on every platform, so the
lowercase probe cannot resolve and the exact-case name must arrive from
``git remote``. Asserted through config rather than ref lookup because a
case-insensitive filesystem (macOS) resolves loose refs either way, which
would make a ref-based assertion test the filesystem instead of the code.
"""
self._mdcps()
res = subprocess.run(
["git", "-C", self.root, "remote", "get-url", "mdcps"],
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(res.returncode, 0, "git remote names are case-sensitive")
# Even when the caller *hints* the wrong case, the resolved name is exact.
got = crr.resolve_target_base_ref(self.root, remote="mdcps")
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["remote"], "MDCPS")
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
name, slug = crr.resolve_identity_remote(self.root)
self.assertEqual(name, "MDCPS")
self.assertEqual(slug, "MDCPS/WeeklyBriefings-Meta")
class TestTargetStaleness(_RepoCase):
"""AC5/AC6: local equal to, behind, or divergent from the resolved tip."""
def _repo_with_tip(self) -> tuple[str, str]:
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_head(self.root, "MDCPS", "dev")
return head, self.root
def test_local_equal_to_resolved_tip_is_not_stale(self):
self._repo_with_tip()
got = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertTrue(got["determinable"])
self.assertFalse(got["stale"])
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
self.assertEqual(got["base_remote"], "MDCPS")
self.assertEqual(got["base_branch"], "dev")
def test_local_behind_resolved_tip_is_stale(self):
head, _ = self._repo_with_tip()
advanced = _advance(self.root, "remote moved on")
_set_remote_branch(self.root, "MDCPS", "dev", advanced)
_git(self.root, "reset", "--hard", "--quiet", head)
got = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertTrue(got["determinable"])
self.assertTrue(got["stale"])
self.assertEqual(got["checkout_head"], head)
self.assertEqual(got["remote_tracking_head"], advanced)
def test_local_divergent_from_resolved_tip_is_stale(self):
head, _ = self._repo_with_tip()
remote_side = _advance(self.root, "remote side")
_set_remote_branch(self.root, "MDCPS", "dev", remote_side)
_git(self.root, "reset", "--hard", "--quiet", head)
local_side = _advance(self.root, "local side")
got = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertTrue(got["stale"])
self.assertEqual(got["checkout_head"], local_side)
self.assertNotEqual(local_side, remote_side)
class TestFailClosed(_RepoCase):
"""AC7/AC8: missing remote/ref and ambiguous resolution never guess."""
def test_missing_remote_fails_closed(self):
_make_repo(self.root, remote=None, url=None)
got = crr.resolve_target_base_ref(self.root)
self.assertFalse(got["proven"])
self.assertEqual(got["reason_code"], crr.DENY_NO_IDENTITY_REMOTE)
self.assertEqual(got["tracking_refs"], ())
self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root))
def test_missing_tracking_ref_fails_closed(self):
_make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
# Remote configured, but nothing has ever been fetched.
got = crr.resolve_target_base_ref(self.root)
self.assertFalse(got["proven"])
self.assertEqual(got["reason_code"], crr.DENY_NO_BASE_BRANCH)
self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root))
def test_ambiguous_integration_branch_fails_closed(self):
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
# Two candidate integration branches and no recorded remote default.
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "MDCPS", "main", head)
got = crr.resolve_target_base_ref(self.root)
self.assertFalse(got["proven"])
self.assertEqual(got["reason_code"], crr.DENY_AMBIGUOUS_BASE_BRANCH)
self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root))
def test_recorded_remote_head_resolves_otherwise_ambiguous_branches(self):
"""Ambiguity is refused only when git records no default."""
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "MDCPS", "main", head)
_set_remote_head(self.root, "MDCPS", "dev")
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["branch"], "dev")
self.assertEqual(got["source"], crr.BASE_REF_SOURCE_REMOTE_HEAD)
def test_ambiguous_identity_remote_fails_closed(self):
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_git(
self.root,
"remote",
"add",
"prgs",
"https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "prgs", "master", head)
got = crr.resolve_target_base_ref(self.root)
self.assertFalse(got["proven"])
self.assertEqual(got["reason_code"], crr.DENY_AMBIGUOUS_REMOTE)
self.assertEqual(got["tracking_refs"], ())
def test_named_remote_disambiguates(self):
"""An explicitly named remote is authoritative and not ambiguous."""
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_git(
self.root,
"remote",
"add",
"prgs",
"https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "prgs", "master", head)
got = crr.resolve_target_base_ref(self.root, remote="MDCPS")
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["remote"], "MDCPS")
self.assertEqual(got["branch"], "dev")
def test_orphan_tracking_ref_from_removed_remote_is_ignored(self):
"""The live Gitea-Tools symptom: refs/remotes/origin/* outlives its remote."""
head = _make_repo(
self.root,
remote="prgs",
url="https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git",
)
_set_remote_branch(self.root, "prgs", "master", head)
_set_remote_head(self.root, "prgs", "master")
# An abandoned ref left behind by a remote that no longer exists.
_set_remote_branch(self.root, "origin", "master", head)
_advance(self.root, "orphan must not be consulted")
got = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertEqual(got["tracking_ref"], "refs/remotes/prgs/master")
self.assertEqual(got["repository_slug"], "Scaled-Tech-Consulting/Gitea-Tools")
self.assertNotIn(
"target repository identity could not be derived from its git remote",
got["reasons"],
)
class TestGatingAndReportingAgree(_RepoCase):
"""AC9: mutation gating and parity reporting resolve the same target."""
def test_same_resolved_target_for_gate_and_report(self):
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_head(self.root, "MDCPS", "dev")
gate = root_checkout_guard.resolve_remote_master_ref_state(self.root)
report = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertEqual(gate["remote"], report["base_remote"])
self.assertEqual(gate["branch"], report["base_branch"])
self.assertEqual(gate["sha"], report["remote_tracking_head"])
self.assertIn(
gate["ref"],
(report["tracking_ref"], f"{gate['remote']}/{gate['branch']}"),
)
def test_both_sides_refuse_the_same_unresolvable_target(self):
_make_repo(self.root, remote=None, url=None)
self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root))
report = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertIsNone(report["remote_tracking_head"])
self.assertFalse(report["stale"])
self.assertTrue(report["reasons"])
class TestProductionCallers(unittest.TestCase):
"""AC10: every affected production caller supplies/consumes the resolved target."""
def test_guard_reports_the_ref_it_actually_compared(self):
assessment = root_checkout_guard.assess_root_checkout_guard(
workspace_path="/tmp/nonexistent-workspace-983",
canonical_repo_root="/tmp/nonexistent-root-983",
current_branch="dev",
head_sha="a" * 40,
porcelain_status="",
remote_master_sha="b" * 40,
remote_master_ref="refs/remotes/MDCPS/dev",
)
self.assertTrue(assessment["block"])
joined = " ".join(assessment["reasons"])
self.assertIn("refs/remotes/MDCPS/dev", joined)
self.assertNotIn("prgs/master", joined)
def test_guard_message_without_a_ref_stays_generic(self):
assessment = root_checkout_guard.assess_root_checkout_guard(
workspace_path="/tmp/nonexistent-workspace-983",
canonical_repo_root="/tmp/nonexistent-root-983",
current_branch="master",
head_sha="a" * 40,
porcelain_status="",
remote_master_sha="b" * 40,
)
joined = " ".join(assessment["reasons"])
self.assertIn("the tracking integration ref", joined)
self.assertNotIn("prgs/master", joined)
def test_anti_stomp_preflight_forwards_the_resolved_ref(self):
sig = inspect.signature(anti_stomp_preflight.assess_anti_stomp_preflight)
self.assertIn("remote_master_ref", sig.parameters)
src = inspect.getsource(anti_stomp_preflight.assess_anti_stomp_preflight)
self.assertIn("remote_master_ref=remote_master_ref", src)
def test_no_production_caller_inherits_the_prgs_default(self):
"""Every resolve site must derive, or pass remote_refs explicitly."""
import gitea_mcp_server
src = inspect.getsource(gitea_mcp_server)
# The four historical call sites now consume the resolved-target state.
self.assertGreaterEqual(src.count("resolve_remote_master_ref_state("), 4)
self.assertNotIn("resolve_remote_master_sha(canonical_root)", src)
def test_resolver_signature_supports_explicit_remote(self):
sig = inspect.signature(root_checkout_guard.resolve_remote_master_sha)
self.assertIn("remote", sig.parameters)
self.assertIn("remote_refs", sig.parameters)
class TestRepositoryStructureUntouched(_RepoCase):
"""Derivation is strictly read-only: it never writes refs or branches."""
def test_resolution_creates_no_refs_or_branches(self):
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_head(self.root, "MDCPS", "dev")
fmt = "--format=%(refname) %(objectname)"
before = _git(self.root, "for-each-ref", fmt)
before_remotes = _git(self.root, "remote")
crr.resolve_target_base_ref(self.root)
root_checkout_guard.resolve_remote_master_sha(self.root)
master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertEqual(_git(self.root, "for-each-ref", fmt), before)
self.assertEqual(_git(self.root, "remote"), before_remotes)
if __name__ == "__main__":
unittest.main()
+16 -2
View File
@@ -87,13 +87,27 @@ class TestAssessRootCheckoutGuard(unittest.TestCase):
self.assertTrue(result["block"])
self.assertIn("tracked local edits", result["reasons"][0])
def test_head_behind_prgs_master_blocked(self):
def test_head_behind_tracking_base_ref_blocked(self):
"""#983: the base ref is derived, so the message no longer hardcodes PRGS."""
result = self._assess(
head_sha=OTHER_SHA,
remote_master_sha=MASTER_SHA,
)
self.assertTrue(result["block"])
self.assertIn("does not match prgs/master", result["reasons"][0])
self.assertIn(
"does not match the tracking integration ref", result["reasons"][0]
)
def test_head_behind_named_base_ref_reports_that_ref(self):
"""The resolved ref is named, so a non-PRGS target is reported accurately."""
result = self._assess(
head_sha=OTHER_SHA,
remote_master_sha=MASTER_SHA,
remote_master_ref="refs/remotes/MDCPS/dev",
)
self.assertTrue(result["block"])
self.assertIn("does not match refs/remotes/MDCPS/dev", result["reasons"][0])
self.assertNotIn("prgs/master", result["reasons"][0])
def test_merger_requires_clean_control_checkout(self):
result = self._assess(