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:
+27
-4
@@ -1052,9 +1052,16 @@ def _create_issue_bootstrap_assessment(
|
||||
git_state = issue_lock_worktree.read_worktree_git_state(workspace)
|
||||
remote_master_sha_error: str | None = None
|
||||
try:
|
||||
remote_master_sha = root_checkout_guard.resolve_remote_master_sha(
|
||||
# #983: consume the resolved-target state so an *underivable* base ref
|
||||
# reaches the assessor as named missing evidence rather than a bare
|
||||
# None, which the bootstrap would otherwise report only as "live
|
||||
# master tip is unknown" with no cause.
|
||||
_base_state = root_checkout_guard.resolve_remote_master_ref_state(
|
||||
ctx["canonical_repo_root"]
|
||||
)
|
||||
remote_master_sha = _base_state["sha"]
|
||||
if not remote_master_sha and _base_state.get("reasons"):
|
||||
remote_master_sha_error = "; ".join(_base_state["reasons"])
|
||||
except Exception as exc:
|
||||
remote_master_sha = None
|
||||
remote_master_sha_error = (
|
||||
@@ -1082,9 +1089,14 @@ def _create_issue_bootstrap_assessment(
|
||||
# closed instead of proceeding without base-equivalence proof.
|
||||
remote_master_sha_error: str | None = None
|
||||
try:
|
||||
remote_master_sha = root_checkout_guard.resolve_remote_master_sha(
|
||||
# #983: same resolved-target consumption as the author bootstrap above —
|
||||
# a target whose base ref cannot be derived must say why.
|
||||
_base_state = root_checkout_guard.resolve_remote_master_ref_state(
|
||||
ctx["canonical_repo_root"]
|
||||
)
|
||||
remote_master_sha = _base_state["sha"]
|
||||
if not remote_master_sha and _base_state.get("reasons"):
|
||||
remote_master_sha_error = "; ".join(_base_state["reasons"])
|
||||
except Exception as exc:
|
||||
remote_master_sha = None
|
||||
remote_master_sha_error = f"{type(exc).__name__}: {exc}".strip() or "resolver failed"
|
||||
@@ -1303,7 +1315,12 @@ def _run_anti_stomp_preflight(
|
||||
workspace = ctx["workspace_path"]
|
||||
canonical_root = ctx["canonical_repo_root"]
|
||||
git_state = issue_lock_worktree.read_worktree_git_state(canonical_root)
|
||||
remote_master_sha = root_checkout_guard.resolve_remote_master_sha(canonical_root)
|
||||
# #983: derive the target base ref for this repository and carry the ref
|
||||
# itself alongside the SHA, so the anti-stomp root-checkout check reports the
|
||||
# ref it actually compared.
|
||||
_root_base_state = root_checkout_guard.resolve_remote_master_ref_state(canonical_root)
|
||||
remote_master_sha = _root_base_state["sha"]
|
||||
remote_master_ref = _root_base_state.get("ref")
|
||||
|
||||
# Repo/org facts (best-effort; explicit org/repo when provided).
|
||||
resolved_org = org
|
||||
@@ -1432,6 +1449,7 @@ def _run_anti_stomp_preflight(
|
||||
root_head_sha=git_state.get("head_sha"),
|
||||
root_porcelain=git_state.get("porcelain_status") or "",
|
||||
remote_master_sha=remote_master_sha,
|
||||
remote_master_ref=remote_master_ref,
|
||||
startup_head=startup_head,
|
||||
current_code_head=current_code_head,
|
||||
lease_required=lease_required,
|
||||
@@ -1987,7 +2005,11 @@ def _enforce_root_checkout_guard(worktree_path: str | None = None) -> None:
|
||||
canonical_root = ctx["canonical_repo_root"]
|
||||
workspace = ctx["workspace_path"]
|
||||
git_state = issue_lock_worktree.read_worktree_git_state(canonical_root)
|
||||
remote_master_sha = root_checkout_guard.resolve_remote_master_sha(canonical_root)
|
||||
# #983: consume the resolved target so the contamination message names the
|
||||
# ref that was actually compared (refs/remotes/<remote>/<branch>) instead of
|
||||
# a hardcoded 'prgs/master' the target repository may not have.
|
||||
base_state = root_checkout_guard.resolve_remote_master_ref_state(canonical_root)
|
||||
remote_master_sha = base_state["sha"]
|
||||
assessment = root_checkout_guard.assess_root_checkout_guard(
|
||||
workspace_path=workspace,
|
||||
canonical_repo_root=canonical_root,
|
||||
@@ -1997,6 +2019,7 @@ def _enforce_root_checkout_guard(worktree_path: str | None = None) -> None:
|
||||
remote_master_sha=remote_master_sha,
|
||||
resolved_role=_preflight_resolved_role,
|
||||
actual_role=_actual_profile_role(),
|
||||
remote_master_ref=base_state.get("ref"),
|
||||
)
|
||||
if assessment["block"]:
|
||||
raise RuntimeError(root_checkout_guard.format_root_checkout_guard_error(assessment))
|
||||
|
||||
Reference in New Issue
Block a user