fix(mcp): require proven base equivalence for the create-issue bootstrap
Remediates review #473 (REQUEST_CHANGES) on PR #759 / issue #757 AC3/AC4. The bootstrap compared SHAs only inside: if remote_tip and local_tip and remote_tip != local_tip: so agreement was assumed whenever either tip was unknown. A missing local HEAD, an unresolvable live master, or a resolver exception silently granted the control-checkout exemption instead of blocking it. An unknown tip is missing evidence, not proof of equivalence. Changes: * assess_create_issue_bootstrap now blocks unless BOTH tips are known and equal, with distinct reasons for missing local HEAD, unknown live master, and resolver failure. Adds a remote_master_sha_error parameter so a failed resolution is reported as missing evidence rather than absence of a constraint. * Both normalized SHAs and a derived base_tips_verified flag are recorded on the assessment. normalize_sha() treats only case and surrounding whitespace as equivalent spellings of a commit. * bootstrap_permits_control_checkout re-derives the comparison from the recorded tips instead of trusting base_tips_verified, so a hand-built or truncated assessment cannot assert agreement it never proved. * _create_issue_bootstrap_assessment captures the resolver exception and forwards it, replacing the silent except -> None. One shared assessment still serves both the #274 and #604 guards, and _BOOTSTRAP_UNSET is preserved. No MCP tool signature gains a bootstrap argument (AC6). No issue or PR number is special-cased (AC10). Tests: 16 new assertions across two classes covering missing local, missing remote, both missing, empty/whitespace tips, mismatch, resolver exception at the assessment site, and resolver exception through the real verify_preflight_purity path; plus predicate rejection of stripped tips, a forged base_tips_verified flag, and a missing flag. All 16 fail against the sources atadc61255and pass after this change. The valid non-create_issue lock_issue test is retained unchanged. Validation: focused #757 suite 51 passed (31 subtests); affected guard and preflight suites 217 passed (67 subtests); full suite 3637 passed, 2 failed, 6 skipped, 431 subtests. Both failures (test_issue_702 F1 worktree recovery; test_reconciler_supersession_close org/repo forwarding) are the documented pre-existing baseline failures onbde5c5fband are not caused by this change. Refs #757 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_015KRvvrFtaM5FEvQ6LvDJMH
This commit is contained in:
+7
-1
@@ -845,12 +845,17 @@ def _create_issue_bootstrap_assessment(
|
||||
ctx = _resolve_namespace_mutation_context(worktree_path)
|
||||
workspace = ctx["workspace_path"]
|
||||
git_state = issue_lock_worktree.read_worktree_git_state(workspace)
|
||||
# #757 AC3/AC4: a resolver failure is not "no constraint" — it is missing
|
||||
# evidence, and must reach the assessor as such so the bootstrap fails
|
||||
# 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(
|
||||
ctx["canonical_repo_root"]
|
||||
)
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
remote_master_sha = None
|
||||
remote_master_sha_error = f"{type(exc).__name__}: {exc}".strip() or "resolver failed"
|
||||
return _cib.assess_create_issue_bootstrap(
|
||||
workspace_path=workspace,
|
||||
canonical_repo_root=ctx["canonical_repo_root"],
|
||||
@@ -858,6 +863,7 @@ def _create_issue_bootstrap_assessment(
|
||||
head_sha=git_state.get("head_sha"),
|
||||
porcelain_status=git_state.get("porcelain_status") or "",
|
||||
remote_master_sha=remote_master_sha,
|
||||
remote_master_sha_error=remote_master_sha_error,
|
||||
task=task,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user