fix(guard): derive base ref from configured upstream, not the remote-HEAD cache

Remediates the REQUEST_CHANGES verdict (review 658) at head 2d5d5c9d.

B1 — refs/remotes/<remote>/HEAD is a stale local cache, not authority.

The previous derivation read that symref as "git's own record of the remote
default branch". It is a cache written once at clone time; an ordinary fetch
never refreshes it, and only an explicit `git remote set-head` updates it. On
the real target this issue exists to unblock, the cache still named `main`
while the checkout tracked and sat exactly on `dev`, so the guard compared
HEAD a37ac427c18b against MDCPS/main 9a84325a1b68 and blocked a checkout that
was not behind anything.

The resolver now derives, in order:

  1. the identity remote, exact case preserved;
  2. the checkout's own configured upstream — branch.<current>.remote plus
     branch.<current>.merge — accepted only when it names that remote and its
     remote-tracking ref actually exists;
  3. otherwise exactly one present master/main/dev remote-tracking ref.

The cached symref is demoted to an observation. It is still read and reported
as cached_remote_head_branch / cached_remote_head_conflicts, and it is named in
refusal text so an operator can see the misleading signal, but it never decides
the branch and never breaks a tie between ambiguous candidates. Requiring the
tracking ref to exist also makes `proven` honest: every proven target now names
a ref that resolves.

Verified read-only against /Users/jasonwalker/Development/weekly-briefings:
MDCPS/dev, source configured_branch_upstream, cached_remote_head_conflicts
true, checkout not stale. PRGS is unchanged — prgs/master, identical SHA.

B2 — an inferred remote must not be laundered into explicit caller intent.

assess_target_repository_parity resolved the identity remote itself and passed
it back into resolve_target_base_ref, which reads a caller-supplied remote as
"the caller already disambiguated" and skips its ambiguity gate. On a target
whose remotes claim different repositories the gate refused while the report
named a different repository with stale=false and no reasons.

The parameter is renamed `explicit_remote` through the resolver and both
root_checkout_guard entry points so the two meanings cannot be confused, and
the reporting path no longer supplies one. Identity resolution for reporting
moves to the new ambiguity-aware assess_identity_remote, so an ambiguous target
now yields a null slug, no tracking ref, and the same reason_code the gate
emits. resolve_identity_remote / repository_identity_slug keep their first-wins
behaviour for the #706/#973 canonical-root validation path, which compares
against an independently trusted expected slug and needs no ambiguity verdict.

Nothing fetches, sets a remote HEAD, writes a ref, adds a remote, invents a
branch, or changes any repository's default branch. A test snapshots refs,
remotes, local config, HEAD, branch, working-tree status, and the cached symref
across every resolver entry point and asserts all are unchanged.

Tests: tests/test_issue_983_cross_repo_base_ref.py rewritten to 37 tests. The
principal MDCPS/dev fixture now reproduces the real defect — upstream dev,
cached refs/remotes/MDCPS/HEAD -> main, both refs present at different commits
— rather than manufacturing the cache state the real checkout does not have.
Added: cache-alone never proves a target, cache never breaks a tie, gate and
report agree on ambiguous remote and ambiguous branch, explicit disambiguation
stays distinct from inferred identity, no production caller passes
explicit_remote, and the read-only proof above.

Focused suite 37 passed. Nineteen affected suites 441 passed, 167 subtests
passed. Full suite 28 failed, 6204 passed, 6 skipped, 1106 subtests passed;
clean baseline at the same base commit 108cbfa 28 failed, 6166 passed, 6
skipped, 1106 subtests passed. Sorted FAILED lists are byte-identical
(sha1 092dae4bc8c4e77d14504d90690d50e0fcd2f637) — zero introduced failures.

Refs #983

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-31 00:00:14 -04:00
co-authored by Claude Opus 4.8
parent 2d5d5c9d17
commit 03b434a0b6
4 changed files with 685 additions and 230 deletions
+346 -136
View File
@@ -5,6 +5,22 @@ 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.
Two further defects were found by review at head ``2d5d5c9d`` and are covered
here:
* **B1** — the first fix derived the integration branch from
``refs/remotes/<remote>/HEAD``. That symref is a *local cache* written at clone
time and never refreshed by fetch, so on the real Weekly Briefings target it
still named ``main`` while the checkout tracked and sat exactly on ``dev``. The
authoritative signal is the checkout's own configured upstream. The fixtures
below therefore reproduce the **disagreement**: the cache says ``main``, the
configured upstream says ``dev``, and ``dev`` must win.
* **B2** — the parity report passed its internally inferred identity remote back
into the resolver, which reads a caller-supplied remote as "the caller already
disambiguated" and skips its ambiguity gate. Gating and reporting could then
evaluate different repositories. An inferred remote is now never laundered into
explicit caller intent, and ambiguity fails closed on both sides.
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
@@ -24,6 +40,11 @@ import canonical_repository_root as crr
import master_parity_gate
import root_checkout_guard
MDCPS_URL = "https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git"
MDCPS_SLUG = "MDCPS/WeeklyBriefings-Meta"
PRGS_URL = "https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git"
PRGS_SLUG = "Scaled-Tech-Consulting/Gitea-Tools"
def _git(root: str, *args: str) -> str:
res = subprocess.run(
@@ -57,6 +78,11 @@ def _set_remote_branch(root: str, remote: str, branch: str, sha: str) -> None:
def _set_remote_head(root: str, remote: str, branch: str) -> None:
"""Write the *cached* refs/remotes/<remote>/HEAD symref.
This is the signal B1 proved untrustworthy. Fixtures use it to reproduce a
stale cache, never to manufacture the answer under test.
"""
_git(
root,
"symbolic-ref",
@@ -65,6 +91,22 @@ def _set_remote_head(root: str, remote: str, branch: str) -> None:
)
def _set_upstream(root: str, remote: str, branch: str) -> None:
"""Configure the current branch's upstream, exactly as git tracking does.
Writes ``branch.<current>.remote`` / ``branch.<current>.merge`` directly
rather than via ``--set-upstream-to`` so no ref is required to pre-exist and
no network is touched.
"""
current = _git(root, "symbolic-ref", "--short", "HEAD")
_git(root, "config", f"branch.{current}.remote", remote)
_git(root, "config", f"branch.{current}.merge", f"refs/heads/{branch}")
def _checkout_new_branch(root: str, branch: str) -> None:
_git(root, "checkout", "--quiet", "-b", 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")
@@ -79,34 +121,61 @@ class _RepoCase(unittest.TestCase):
self.addCleanup(self._tmp.cleanup)
self.root = os.path.join(self._tmp.name, "repo")
def _weekly_briefings_shape(self) -> str:
"""The real Weekly Briefings target, including its stale cache.
Remote ``MDCPS``; checked out on ``dev``; upstream configured to
``MDCPS/dev``; both ``dev`` and ``main`` present as tracking refs; and
``refs/remotes/MDCPS/HEAD`` still cached at ``main`` from clone time.
"""
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_checkout_new_branch(self.root, "dev")
_set_remote_branch(self.root, "MDCPS", "dev", head)
stale = _advance(self.root, "main diverged long ago")
_set_remote_branch(self.root, "MDCPS", "main", stale)
_git(self.root, "reset", "--hard", "--quiet", head)
_set_remote_head(self.root, "MDCPS", "main") # stale clone-time cache
_set_upstream(self.root, "MDCPS", "dev") # authoritative
return head
class TestPrgsBehaviourPreserved(_RepoCase):
"""AC1: existing PRGS behaviour using prgs/master is unchanged."""
"""Required coverage 1: PRGS prgs/master compatibility."""
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",
)
def _prgs(self) -> str:
head = _make_repo(self.root, remote="prgs", url=PRGS_URL)
_set_remote_branch(self.root, "prgs", "master", head)
_set_remote_head(self.root, "prgs", "master")
_set_upstream(self.root, "prgs", "master")
return head
def test_prgs_master_resolves_unchanged(self):
head = self._prgs()
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(got["repository_slug"], PRGS_SLUG)
self.assertEqual(got["source"], crr.BASE_REF_SOURCE_CONFIGURED_UPSTREAM)
# Cache and upstream agree here, which is the ordinary PRGS state.
self.assertFalse(got["cached_remote_head_conflicts"])
self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head)
def test_prgs_resolves_without_a_configured_upstream(self):
"""A PRGS checkout with no tracking config still resolves master."""
head = _make_repo(self.root, remote="prgs", url=PRGS_URL)
_set_remote_branch(self.root, "prgs", "master", head)
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["tracking_ref"], "refs/remotes/prgs/master")
self.assertEqual(got["source"], crr.BASE_REF_SOURCE_UNIQUE_CANDIDATE)
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",
)
head = _make_repo(self.root, remote="prgs", url=PRGS_URL)
_set_remote_branch(self.root, "prgs", "master", head)
state = root_checkout_guard.resolve_remote_master_ref_state(
@@ -116,38 +185,124 @@ class TestPrgsBehaviourPreserved(_RepoCase):
self.assertEqual(state["source"], "explicit_remote_refs")
class TestCrossRepositoryTarget(_RepoCase):
"""AC2/AC3/AC4: MDCPS/dev, no origin remote, exact remote-name case."""
class TestStaleCachedRemoteHead(_RepoCase):
"""Required coverage 3: configured upstream MDCPS/dev vs stale cache -> main.
def _mdcps(self) -> str:
head = _make_repo(
self.root,
remote="MDCPS",
url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git",
This is B1. The fixture deliberately does **not** point
``refs/remotes/MDCPS/HEAD`` at ``dev``; it reproduces the disagreement that
was live on ``/Users/jasonwalker/Development/weekly-briefings``.
"""
def test_configured_upstream_beats_stale_cached_remote_head(self):
head = self._weekly_briefings_shape()
# Precondition: the fixture really is in the defective state.
self.assertEqual(
_git(self.root, "symbolic-ref", "refs/remotes/MDCPS/HEAD"),
"refs/remotes/MDCPS/main",
)
self.assertEqual(_git(self.root, "config", "--get", "branch.dev.merge"), "refs/heads/dev")
self.assertNotEqual(
_git(self.root, "rev-parse", "refs/remotes/MDCPS/main"),
_git(self.root, "rev-parse", "refs/remotes/MDCPS/dev"),
)
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["branch"], "dev")
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
self.assertEqual(got["source"], crr.BASE_REF_SOURCE_CONFIGURED_UPSTREAM)
# The stale cache is reported, never obeyed.
self.assertEqual(got["cached_remote_head_branch"], "main")
self.assertTrue(got["cached_remote_head_conflicts"])
self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head)
def test_checkout_on_its_integration_tip_is_not_blocked(self):
"""The live symptom: a checkout exactly on its tip was reported stale."""
head = self._weekly_briefings_shape()
state = root_checkout_guard.resolve_remote_master_ref_state(self.root)
self.assertEqual(state["sha"], head)
self.assertTrue(state["cached_remote_head_conflicts"])
assessment = root_checkout_guard.assess_root_checkout_guard(
workspace_path=self.root,
canonical_repo_root=self.root,
current_branch="dev",
head_sha=head,
porcelain_status="",
remote_master_sha=state["sha"],
remote_master_ref=state["ref"],
)
self.assertTrue(assessment["proven"], assessment["reasons"])
report = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertFalse(report["stale"])
self.assertEqual(report["base_branch"], "dev")
self.assertEqual(report["reasons"], [])
def test_cached_remote_head_alone_never_proves_a_target(self):
"""With no configured upstream, the cache cannot supply the branch."""
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
# Only a non-candidate branch exists, and only the cache names it.
_set_remote_branch(self.root, "MDCPS", "trunk", head)
_set_remote_head(self.root, "MDCPS", "trunk")
got = crr.resolve_target_base_ref(self.root)
self.assertFalse(got["proven"])
self.assertEqual(got["reason_code"], crr.DENY_NO_BASE_BRANCH)
self.assertEqual(got["tracking_refs"], ())
self.assertEqual(got["cached_remote_head_branch"], "trunk")
# The refusal names the misleading signal so an operator is not sent
# chasing a ref that looks authoritative.
self.assertIn("trunk", " ".join(got["reasons"]))
def test_cached_remote_head_never_breaks_a_tie(self):
"""Two candidates, no upstream: the cache must not decide."""
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "MDCPS", "main", head)
_set_remote_head(self.root, "MDCPS", "dev")
return 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_no_proven_source_is_the_remote_head_cache(self):
"""Structural guard: the cache is not in the set of proving sources."""
sources = {
crr.BASE_REF_SOURCE_CONFIGURED_UPSTREAM,
crr.BASE_REF_SOURCE_UNIQUE_CANDIDATE,
}
self.assertNotIn("remote_head_symref", sources)
self.assertFalse(hasattr(crr, "BASE_REF_SOURCE_REMOTE_HEAD"))
class TestCrossRepositoryTarget(_RepoCase):
"""Required coverage 2/4/5: MDCPS/dev, no origin remote, exact case."""
def test_mdcps_dev_resolves(self):
head = self._mdcps()
head = self._weekly_briefings_shape()
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(got["repository_slug"], MDCPS_SLUG)
self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head)
def test_no_remote_named_origin(self):
self._mdcps()
self._weekly_briefings_shape()
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()
self._weekly_briefings_shape()
got = crr.resolve_target_base_ref(self.root)
self.assertEqual(got["remote"], "MDCPS")
self.assertNotEqual(got["remote"], "mdcps")
@@ -169,7 +324,7 @@ class TestCrossRepositoryTarget(_RepoCase):
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()
self._weekly_briefings_shape()
res = subprocess.run(
["git", "-C", self.root, "remote", "get-url", "mdcps"],
capture_output=True,
@@ -178,32 +333,24 @@ class TestCrossRepositoryTarget(_RepoCase):
)
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")
# A caller naming the wrong case cannot disambiguate, but the target is
# unambiguous anyway, so the exact-case name is still resolved.
got = crr.resolve_target_base_ref(self.root, explicit_remote="mdcps")
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["remote"], "MDCPS")
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
self.assertFalse(got["identity_explicit"], "a non-matching name is not explicit intent")
name, slug = crr.resolve_identity_remote(self.root)
self.assertEqual(name, "MDCPS")
self.assertEqual(slug, "MDCPS/WeeklyBriefings-Meta")
self.assertEqual(slug, MDCPS_SLUG)
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
"""Required coverage 6/7: matching tip, and behind or divergent checkout."""
def test_local_equal_to_resolved_tip_is_not_stale(self):
self._repo_with_tip()
self._weekly_briefings_shape()
got = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
@@ -212,9 +359,10 @@ class TestTargetStaleness(_RepoCase):
self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev")
self.assertEqual(got["base_remote"], "MDCPS")
self.assertEqual(got["base_branch"], "dev")
self.assertIsNone(got["reason_code"])
def test_local_behind_resolved_tip_is_stale(self):
head, _ = self._repo_with_tip()
head = self._weekly_briefings_shape()
advanced = _advance(self.root, "remote moved on")
_set_remote_branch(self.root, "MDCPS", "dev", advanced)
_git(self.root, "reset", "--hard", "--quiet", head)
@@ -228,7 +376,7 @@ class TestTargetStaleness(_RepoCase):
self.assertEqual(got["remote_tracking_head"], advanced)
def test_local_divergent_from_resolved_tip_is_stale(self):
head, _ = self._repo_with_tip()
head = self._weekly_briefings_shape()
remote_side = _advance(self.root, "remote side")
_set_remote_branch(self.root, "MDCPS", "dev", remote_side)
_git(self.root, "reset", "--hard", "--quiet", head)
@@ -243,7 +391,7 @@ class TestTargetStaleness(_RepoCase):
class TestFailClosed(_RepoCase):
"""AC7/AC8: missing remote/ref and ambiguous resolution never guess."""
"""Required coverage 8/9: missing remote or ref, and ambiguous targets."""
def test_missing_remote_fails_closed(self):
_make_repo(self.root, remote=None, url=None)
@@ -254,24 +402,33 @@ class TestFailClosed(_RepoCase):
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",
)
_make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
# 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_configured_upstream_without_a_tracking_ref_fails_closed(self):
"""A proven target always names a ref that resolves."""
_make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_set_upstream(self.root, "MDCPS", "dev") # declared, never 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(got["tracking_ref"])
def test_every_proven_target_resolves(self):
"""No 'proven' result may name an unresolvable tracking ref."""
head = self._weekly_briefings_shape()
got = crr.resolve_target_base_ref(self.root)
self.assertTrue(got["proven"])
self.assertEqual(_git(self.root, "rev-parse", got["tracking_ref"]), head)
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.
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
# Two candidate integration branches and no configured upstream.
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "MDCPS", "main", head)
@@ -280,35 +437,9 @@ class TestFailClosed(_RepoCase):
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",
)
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_git(self.root, "remote", "add", "prgs", PRGS_URL)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "prgs", "master", head)
@@ -317,37 +448,11 @@ class TestFailClosed(_RepoCase):
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",
)
head = _make_repo(self.root, remote="prgs", url=PRGS_URL)
_set_remote_branch(self.root, "prgs", "master", head)
_set_remote_head(self.root, "prgs", "master")
_set_upstream(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")
@@ -356,24 +461,82 @@ class TestFailClosed(_RepoCase):
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.assertEqual(got["repository_slug"], PRGS_SLUG)
self.assertNotIn(
"target repository identity could not be derived from its git remote",
got["reasons"],
)
class TestExplicitVersusInferredRemote(_RepoCase):
"""Required coverage 11: explicit disambiguation stays distinct from inference.
This is B2. A remote the module inferred while probing must never re-enter
the resolver as though an operator had named it.
"""
def _two_remotes(self) -> str:
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_git(self.root, "remote", "add", "prgs", PRGS_URL)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "prgs", "master", head)
return head
def test_explicit_remote_disambiguates(self):
self._two_remotes()
got = crr.resolve_target_base_ref(self.root, explicit_remote="MDCPS")
self.assertTrue(got["proven"], got["reasons"])
self.assertEqual(got["remote"], "MDCPS")
self.assertEqual(got["branch"], "dev")
self.assertTrue(got["identity_explicit"])
def test_inferred_remote_does_not_disambiguate(self):
"""Feeding the inferred remote back in must not unlock the target."""
self._two_remotes()
inferred, _ = crr.resolve_identity_remote(self.root)
self.assertIsNotNone(inferred, "the first-wins probe still returns a name")
# The report infers internally and must still refuse.
report = master_parity_gate.assess_target_repository_parity(
canonical_root=self.root, source="test"
)
self.assertEqual(report["reason_code"], crr.DENY_AMBIGUOUS_REMOTE)
self.assertIsNone(report["repository_slug"])
self.assertIsNone(report["tracking_ref"])
self.assertFalse(report["stale"])
self.assertTrue(report["reasons"])
def test_report_never_passes_a_remote_into_the_resolver(self):
"""Structural guard against the exact B2 regression."""
src = inspect.getsource(master_parity_gate.assess_target_repository_parity)
self.assertIn("resolve_target_base_ref(canonical_root)", src)
self.assertNotIn("resolve_target_base_ref(canonical_root, remote=", src)
self.assertNotIn("explicit_remote=identity", src)
# Reporting must use the ambiguity-aware identity resolver.
self.assertIn("assess_identity_remote(canonical_root)", src)
def test_explicit_parameter_is_named_for_its_meaning(self):
for fn in (
crr.resolve_target_base_ref,
root_checkout_guard.resolve_remote_master_sha,
root_checkout_guard.resolve_remote_master_ref_state,
):
params = inspect.signature(fn).parameters
self.assertIn("explicit_remote", params, fn.__name__)
self.assertNotIn("remote", params, fn.__name__)
def test_unmatched_explicit_remote_cannot_unlock_an_ambiguous_target(self):
self._two_remotes()
got = crr.resolve_target_base_ref(self.root, explicit_remote="nonexistent")
self.assertFalse(got["proven"])
self.assertEqual(got["reason_code"], crr.DENY_AMBIGUOUS_REMOTE)
class TestGatingAndReportingAgree(_RepoCase):
"""AC9: mutation gating and parity reporting resolve the same target."""
"""Required coverage 10: guard and parity report make identical decisions."""
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")
self._weekly_briefings_shape()
gate = root_checkout_guard.resolve_remote_master_ref_state(self.root)
report = master_parity_gate.assess_target_repository_parity(
@@ -391,17 +554,52 @@ class TestGatingAndReportingAgree(_RepoCase):
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))
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.assertIsNone(gate["sha"])
self.assertIsNone(report["remote_tracking_head"])
self.assertFalse(report["stale"])
self.assertTrue(report["reasons"])
self.assertEqual(gate["reason_code"], report["reason_code"])
def test_both_sides_refuse_the_same_ambiguous_target(self):
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_git(self.root, "remote", "add", "prgs", PRGS_URL)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "prgs", "master", head)
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.assertIsNone(gate["sha"])
self.assertEqual(gate["reason_code"], crr.DENY_AMBIGUOUS_REMOTE)
# The report must not name a repository the gate refuses to act on.
self.assertEqual(report["reason_code"], gate["reason_code"])
self.assertIsNone(report["repository_slug"])
self.assertIsNone(report["base_remote"])
self.assertIsNone(report["base_branch"])
self.assertFalse(report["stale"])
def test_both_sides_refuse_the_same_ambiguous_branch(self):
head = _make_repo(self.root, remote="MDCPS", url=MDCPS_URL)
_set_remote_branch(self.root, "MDCPS", "dev", head)
_set_remote_branch(self.root, "MDCPS", "main", head)
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["reason_code"], crr.DENY_AMBIGUOUS_BASE_BRANCH)
self.assertEqual(report["reason_code"], gate["reason_code"])
self.assertIsNone(report["tracking_ref"])
class TestProductionCallers(unittest.TestCase):
"""AC10: every affected production caller supplies/consumes the resolved target."""
"""Required coverage 13: every production caller consumes the same target."""
def test_guard_reports_the_ref_it_actually_compared(self):
assessment = root_checkout_guard.assess_root_checkout_guard(
@@ -446,36 +644,48 @@ class TestProductionCallers(unittest.TestCase):
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)
def test_no_production_caller_supplies_an_explicit_remote(self):
"""Nothing in production may suppress the ambiguity gate (#983 B2)."""
import gitea_mcp_server
for module in (gitea_mcp_server, anti_stomp_preflight, master_parity_gate):
src = inspect.getsource(module)
self.assertNotIn("explicit_remote=", src, module.__name__)
class TestRepositoryStructureUntouched(_RepoCase):
"""Derivation is strictly read-only: it never writes refs or branches."""
"""Required coverage 12: resolution mutates no ref, remote, config, or checkout."""
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")
self._weekly_briefings_shape()
fmt = "--format=%(refname) %(objectname)"
before = _git(self.root, "for-each-ref", fmt)
before_refs = _git(self.root, "for-each-ref", fmt)
before_remotes = _git(self.root, "remote")
before_config = _git(self.root, "config", "--local", "--list")
before_head = _git(self.root, "rev-parse", "HEAD")
before_branch = _git(self.root, "symbolic-ref", "--short", "HEAD")
before_status = _git(self.root, "status", "--porcelain", "--untracked-files=all")
before_symref = _git(self.root, "symbolic-ref", "refs/remotes/MDCPS/HEAD")
crr.resolve_target_base_ref(self.root)
crr.assess_identity_remote(self.root)
root_checkout_guard.resolve_remote_master_sha(self.root)
root_checkout_guard.resolve_remote_master_ref_state(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, "for-each-ref", fmt), before_refs)
self.assertEqual(_git(self.root, "remote"), before_remotes)
self.assertEqual(_git(self.root, "config", "--local", "--list"), before_config)
self.assertEqual(_git(self.root, "rev-parse", "HEAD"), before_head)
self.assertEqual(_git(self.root, "symbolic-ref", "--short", "HEAD"), before_branch)
self.assertEqual(
_git(self.root, "status", "--porcelain", "--untracked-files=all"), before_status
)
# The stale cache is specifically NOT repaired: that would be a mutation.
self.assertEqual(_git(self.root, "symbolic-ref", "refs/remotes/MDCPS/HEAD"), before_symref)
if __name__ == "__main__":