Files
Gitea-Tools/tests/test_issue_757_bootstrap_guard_agreement.py
T
sysadminandClaude Opus 4.8 9d2c652ae8 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 at adc61255 and 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 on bde5c5fb and 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
2026-07-19 12:59:08 -04:00

810 lines
32 KiB
Python

"""Regression tests for #757: #274 and #604 must not disagree.
The sanctioned ``create_issue`` bootstrap (#749/#750) was unreachable in
production: the #274 branches-only guard consulted the bootstrap and permitted
a clean canonical control checkout, then the bootstrap-blind #604 anti-stomp
preflight rejected the same checkout as ``wrong_worktree``.
These tests pin the fix:
* one server-derived assessment, interpreted by one shared predicate;
* the waiver is narrow (only the wrong-worktree verdict, only create_issue,
only from the exact clean canonical control checkout);
* every other guard and rejection reason keeps its fail-closed behaviour;
* eligibility cannot be forged through a public tool signature.
"""
from __future__ import annotations
import inspect
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import anti_stomp_preflight as asp # noqa: E402
import create_issue_bootstrap as cib # noqa: E402
import gitea_mcp_server as srv # noqa: E402
FAKE_AUTH = {"Authorization": "token test-token"}
MASTER_SHA = "a" * 40
STALE_SHA = "b" * 40
current_file_path = Path(__file__).resolve()
if "branches" in current_file_path.parts:
CONTROL_CHECKOUT_ROOT = str(current_file_path.parents[3])
else:
CONTROL_CHECKOUT_ROOT = str(current_file_path.parents[1])
BRANCHES_WORKTREE = str(Path(CONTROL_CHECKOUT_ROOT) / "branches" / "issue-1-x")
def proven_bootstrap(
*,
workspace=CONTROL_CHECKOUT_ROOT,
root=CONTROL_CHECKOUT_ROOT,
task="create_issue",
branch="master",
porcelain="",
head=MASTER_SHA,
remote_sha=MASTER_SHA,
):
"""Build a real assessment via the production assessor (never hand-rolled)."""
return cib.assess_create_issue_bootstrap(
workspace_path=workspace,
canonical_repo_root=root,
current_branch=branch,
head_sha=head,
porcelain_status=porcelain,
remote_master_sha=remote_sha,
task=task,
)
class TestSharedPredicate(unittest.TestCase):
"""The one interpretation both guards consume (AC6, AC7)."""
def _permits(self, assessment, task="create_issue", workspace=None, root=None):
return cib.bootstrap_permits_control_checkout(
assessment,
task=task,
workspace_path=workspace or CONTROL_CHECKOUT_ROOT,
canonical_repo_root=root or CONTROL_CHECKOUT_ROOT,
)
def test_proven_bootstrap_permits(self):
self.assertTrue(self._permits(proven_bootstrap()))
def test_tool_alias_permits(self):
assessment = proven_bootstrap(task="gitea_create_issue")
self.assertTrue(self._permits(assessment, task="gitea_create_issue"))
def test_missing_assessment_fails_closed(self):
self.assertFalse(self._permits(None))
def test_malformed_assessment_fails_closed(self):
for bogus in ("allowed", 1, True, [], ["allowed"], object()):
with self.subTest(bogus=bogus):
self.assertFalse(self._permits(bogus))
def test_refused_assessment_fails_closed(self):
# Dirty control checkout -> assessor blocks -> predicate must refuse.
self.assertFalse(
self._permits(proven_bootstrap(porcelain=" M gitea_mcp_server.py\n"))
)
def test_not_applicable_assessment_fails_closed(self):
# branches/ worktree -> not_applicable -> no waiver.
self.assertFalse(
self._permits(proven_bootstrap(workspace=BRANCHES_WORKTREE))
)
def test_incomplete_assessment_fails_closed(self):
incomplete = dict(proven_bootstrap())
del incomplete["bootstrap_path"]
self.assertFalse(self._permits(incomplete))
def test_contradictory_block_and_allow_fails_closed(self):
contradictory = dict(proven_bootstrap(), block=True)
self.assertFalse(self._permits(contradictory))
def test_contradictory_dirty_but_allowed_fails_closed(self):
contradictory = dict(proven_bootstrap(), dirty_files=["gitea_mcp_server.py"])
self.assertFalse(self._permits(contradictory))
def test_contradictory_under_branches_but_allowed_fails_closed(self):
contradictory = dict(proven_bootstrap(), under_branches=True)
self.assertFalse(self._permits(contradictory))
def test_contradictory_reasons_present_fails_closed(self):
contradictory = dict(proven_bootstrap(), reasons=["something refused"])
self.assertFalse(self._permits(contradictory))
def test_truthy_non_true_values_fail_closed(self):
"""Strict identity: no truthy smuggling (1, 'yes') can assert eligibility."""
for value in (1, "yes", "true", [1]):
with self.subTest(value=value):
self.assertFalse(self._permits(dict(proven_bootstrap(), allowed=value)))
def test_wrong_task_fails_closed(self):
"""An otherwise-proven assessment cannot license a different task."""
self.assertFalse(self._permits(proven_bootstrap(), task="lock_issue"))
self.assertFalse(self._permits(proven_bootstrap(), task="create_pr"))
self.assertFalse(self._permits(proven_bootstrap(), task=None))
def test_foreign_workspace_binding_fails_closed(self):
"""Assessment must describe the workspace actually being guarded."""
other = proven_bootstrap(workspace="/other/clone", root="/other/clone")
self.assertFalse(self._permits(other))
def test_scope_and_path_tampering_fails_closed(self):
self.assertFalse(
self._permits(dict(proven_bootstrap(), task_scope="all_tasks"))
)
self.assertFalse(
self._permits(dict(proven_bootstrap(), bootstrap_path="anything_goes"))
)
class TestAntiStompHonorsBootstrap(unittest.TestCase):
"""#604 consumes the same decision, and waives only wrong_worktree."""
def _assess(self, *, task="create_issue", bootstrap=None, workspace=None, **kw):
params = dict(
task=task,
profile_role="author",
required_role="author",
workspace_path=workspace or CONTROL_CHECKOUT_ROOT,
project_root=CONTROL_CHECKOUT_ROOT,
current_branch="master",
root_head_sha=MASTER_SHA,
root_porcelain="",
remote_master_sha=MASTER_SHA,
check_repo=False,
create_issue_bootstrap_assessment=bootstrap,
)
params.update(kw)
return asp.assess_anti_stomp_preflight(**params)
def _blocker_kinds(self, result):
return {b["kind"] for b in result.get("blockers") or []}
def test_control_checkout_without_bootstrap_still_blocked(self):
"""Baseline: the exact production failure, unwaived."""
res = self._assess(bootstrap=None)
self.assertIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
def test_control_checkout_with_proven_bootstrap_permitted(self):
"""AC1/AC2: the #757 fix — same inputs, bootstrap honoured."""
res = self._assess(bootstrap=proven_bootstrap())
self.assertNotIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
self.assertTrue(res["checks"]["worktree"]["create_issue_bootstrap_waived"])
self.assertFalse(res["checks"]["worktree"]["block"])
def test_tool_alias_permitted(self):
res = self._assess(
task="gitea_create_issue",
bootstrap=proven_bootstrap(task="gitea_create_issue"),
)
self.assertNotIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
def test_non_create_issue_task_never_waived(self):
"""AC5: other author mutations keep the branches-only requirement."""
for task in ("lock_issue", "create_pr", "commit_files", "mark_issue"):
with self.subTest(task=task):
res = self._assess(task=task, bootstrap=proven_bootstrap())
self.assertIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
def test_dirty_control_checkout_still_blocked(self):
"""AC4: dirty root refuses the bootstrap, so no waiver."""
dirty = " M gitea_mcp_server.py\n"
res = self._assess(
bootstrap=proven_bootstrap(porcelain=dirty), root_porcelain=dirty
)
self.assertIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
def test_detached_control_checkout_still_blocked(self):
res = self._assess(
bootstrap=proven_bootstrap(branch=""), current_branch=""
)
self.assertIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
def test_base_divergence_still_blocked(self):
res = self._assess(
bootstrap=proven_bootstrap(head=STALE_SHA), root_head_sha=STALE_SHA
)
self.assertIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
def test_waiver_does_not_suppress_stale_runtime(self):
"""AC: waive only wrong_worktree — stale runtime still fails closed."""
res = self._assess(
bootstrap=proven_bootstrap(),
startup_head=STALE_SHA,
current_code_head=MASTER_SHA,
)
self.assertNotIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
self.assertIn(asp.BLOCKER_STALE_RUNTIME, self._blocker_kinds(res))
self.assertTrue(res["block"])
def test_waiver_does_not_suppress_wrong_repo(self):
res = self._assess(
bootstrap=proven_bootstrap(),
check_repo=True,
remote="prgs",
resolved_org="Scaled-Tech-Consulting",
resolved_repo="Timesheet",
# Not both-explicit, so the #530 repo guard actually evaluates the
# mismatch against the local remote instead of trusting the caller.
org_explicit=False,
repo_explicit=False,
local_remote_url=(
"https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git"
),
)
self.assertNotIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
self.assertIn(asp.BLOCKER_WRONG_REPO, self._blocker_kinds(res))
def test_waiver_does_not_suppress_wrong_role(self):
res = self._assess(
bootstrap=proven_bootstrap(),
profile_role="reviewer",
required_role="author",
)
self.assertIn(asp.BLOCKER_WRONG_ROLE, self._blocker_kinds(res))
def test_branches_worktree_unaffected(self):
"""AC: ordinary branches/ worktrees keep working, waived or not."""
for bootstrap in (None, proven_bootstrap(workspace=BRANCHES_WORKTREE)):
with self.subTest(bootstrap=bool(bootstrap)):
res = self._assess(
workspace=BRANCHES_WORKTREE,
bootstrap=bootstrap,
current_branch="fix/issue-1-x",
)
self.assertNotIn(
asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res)
)
def test_forged_assessment_rejected(self):
"""AC6: a hand-built 'allowed' dict cannot unlock the waiver."""
forged = {"allowed": True, "block": False}
res = self._assess(bootstrap=forged)
self.assertIn(asp.BLOCKER_WRONG_WORKTREE, self._blocker_kinds(res))
class TestGuardAgreement(unittest.TestCase):
"""AC7: the regression that would have caught the #757 defect.
For identical evidence, the #274 guard and the #604 guard must return the
same wrong-worktree verdict across the full workspace-state matrix.
"""
MATRIX = [
(
"clean control + create_issue",
CONTROL_CHECKOUT_ROOT, "create_issue", "master", "", MASTER_SHA,
),
(
"clean control + alias",
CONTROL_CHECKOUT_ROOT, "gitea_create_issue", "master", "", MASTER_SHA,
),
(
"clean control + lock_issue",
CONTROL_CHECKOUT_ROOT, "lock_issue", "master", "", MASTER_SHA,
),
(
"clean control + create_pr",
CONTROL_CHECKOUT_ROOT, "create_pr", "master", "", MASTER_SHA,
),
(
"dirty control + create_issue",
CONTROL_CHECKOUT_ROOT, "create_issue", "master",
" M gitea_mcp_server.py\n", MASTER_SHA,
),
(
"detached control + create_issue",
CONTROL_CHECKOUT_ROOT, "create_issue", "", "", MASTER_SHA,
),
(
"non-base control + create_issue",
CONTROL_CHECKOUT_ROOT, "create_issue", "feat/x", "", MASTER_SHA,
),
(
"diverged control + create_issue",
CONTROL_CHECKOUT_ROOT, "create_issue", "master", "", STALE_SHA,
),
(
"branches wt + create_issue",
BRANCHES_WORKTREE, "create_issue", "fix/issue-1-x", "", MASTER_SHA,
),
(
"branches wt + lock_issue",
BRANCHES_WORKTREE, "lock_issue", "fix/issue-1-x", "", MASTER_SHA,
),
]
def _guard_274_blocks(self, *, workspace, task, branch, porcelain, head, bootstrap):
git_state = {
"current_branch": branch,
"head_sha": head,
"porcelain_status": porcelain,
}
ctx = {
"workspace_path": workspace,
"canonical_repo_root": CONTROL_CHECKOUT_ROOT,
}
with patch.object(srv, "_effective_workspace_role", return_value="author"), \
patch.object(srv, "_actual_profile_role", return_value="author"), \
patch.object(
srv, "_resolve_namespace_mutation_context", return_value=ctx
), \
patch.object(
srv.issue_lock_worktree,
"read_worktree_git_state",
return_value=git_state,
):
try:
srv._enforce_branches_only_author_mutation(
workspace, task=task, bootstrap_assessment=bootstrap
)
return False
except RuntimeError:
return True
def _guard_604_blocks(self, *, workspace, task, branch, porcelain, head, bootstrap):
res = asp.assess_anti_stomp_preflight(
task=task,
profile_role="author",
required_role="author",
workspace_path=workspace,
project_root=CONTROL_CHECKOUT_ROOT,
current_branch=branch,
root_head_sha=head,
root_porcelain=porcelain,
remote_master_sha=MASTER_SHA,
check_repo=False,
create_issue_bootstrap_assessment=bootstrap,
)
return asp.BLOCKER_WRONG_WORKTREE in {
b["kind"] for b in res.get("blockers") or []
}
def test_guards_agree_across_matrix(self):
for label, workspace, task, branch, porcelain, head in self.MATRIX:
with self.subTest(case=label):
# ONE server-derived assessment, exactly as production computes it.
bootstrap = cib.assess_create_issue_bootstrap(
workspace_path=workspace,
canonical_repo_root=CONTROL_CHECKOUT_ROOT,
current_branch=branch,
head_sha=head,
porcelain_status=porcelain,
remote_master_sha=MASTER_SHA,
task=task,
)
kwargs = dict(
workspace=workspace,
task=task,
branch=branch,
porcelain=porcelain,
head=head,
bootstrap=bootstrap,
)
blocked_274 = self._guard_274_blocks(**kwargs)
blocked_604 = self._guard_604_blocks(**kwargs)
self.assertEqual(
blocked_274,
blocked_604,
f"{label}: #274 blocked={blocked_274} "
f"but #604 blocked={blocked_604}",
)
def test_clean_control_create_issue_permitted_by_both(self):
"""The specific case that was broken: both guards must permit."""
kwargs = dict(
workspace=CONTROL_CHECKOUT_ROOT,
task="create_issue",
branch="master",
porcelain="",
head=MASTER_SHA,
bootstrap=proven_bootstrap(),
)
self.assertFalse(self._guard_274_blocks(**kwargs))
self.assertFalse(self._guard_604_blocks(**kwargs))
class TestNoCallerForgeableEligibility(unittest.TestCase):
"""AC6: eligibility is never reachable through a public tool signature."""
def test_create_issue_tool_exposes_no_bootstrap_argument(self):
params = set(inspect.signature(srv.gitea_create_issue).parameters)
for forbidden in (
"bootstrap",
"bootstrap_assessment",
"create_issue_bootstrap",
"create_issue_bootstrap_assessment",
"allow_control_checkout",
"bootstrap_allowed",
):
self.assertNotIn(forbidden, params)
def test_no_mcp_tool_exposes_bootstrap_argument(self):
"""No public gitea_* tool may take bootstrap evidence from the caller."""
for name in dir(srv):
if not name.startswith("gitea_"):
continue
fn = getattr(srv, name)
if not callable(fn):
continue
try:
params = set(inspect.signature(fn).parameters)
except (TypeError, ValueError):
continue
leaked = {p for p in params if "bootstrap" in p.lower()}
self.assertFalse(leaked, f"{name} exposes bootstrap args: {leaked}")
def test_internal_helper_yields_nothing_for_other_tasks(self):
self.assertTrue(hasattr(srv, "_create_issue_bootstrap_assessment"))
self.assertIsNone(
srv._create_issue_bootstrap_assessment("lock_issue"),
"non-create_issue tasks must yield no bootstrap evidence",
)
class TestNativeCreateIssueEndToEnd(unittest.TestCase):
"""AC8: the production handler, with the #604 gate LIVE (not patched out).
The pre-existing #749 e2e test patched ``_run_anti_stomp_preflight`` to a
no-op, which is exactly why this defect reached production. These tests
leave it running.
"""
def setUp(self):
srv._preflight_whoami_called = True
srv._preflight_capability_called = True
srv._preflight_resolved_role = "author"
srv._preflight_resolved_task = "create_issue"
srv._preflight_whoami_violation = False
srv._preflight_capability_violation = False
self._orig_in_test = srv._preflight_in_test_mode
srv._preflight_in_test_mode = lambda: False
def tearDown(self):
srv._preflight_in_test_mode = self._orig_in_test
srv._preflight_resolved_task = None
def _git_state(self, branch="master", head=MASTER_SHA, porcelain=""):
return {
"current_branch": branch,
"head_sha": head,
"porcelain_status": porcelain,
}
def _run_create_issue(
self,
*,
git_state,
remote_sha=MASTER_SHA,
parity=None,
title="Bootstrap issue from clean control",
):
"""Invoke the native handler with the anti-stomp gate live."""
parity = parity or {
"startup_head": MASTER_SHA,
"current_head": MASTER_SHA,
}
with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT), \
patch.object(srv, "_auth", return_value=FAKE_AUTH), \
patch.object(srv, "_profile_permission_block", return_value=None), \
patch.object(srv, "_namespace_mutation_block", return_value=None), \
patch.object(
srv.role_session_router,
"check_author_mutation_after_reviewer_stop",
return_value=(True, []),
), \
patch.object(srv, "api_get_all", return_value=[]), \
patch.object(srv, "api_request") as mock_api, \
patch.object(
srv.root_checkout_guard,
"resolve_remote_master_sha",
return_value=remote_sha,
), \
patch.object(srv, "_current_master_parity", return_value=parity), \
patch.object(
srv,
"get_profile",
return_value={
"profile_name": "prgs-author",
"allowed_operations": [
"gitea.issue.create",
"gitea.issue.comment",
"gitea.pr.create",
"gitea.read",
],
},
), \
patch.object(srv, "_actual_profile_role", return_value="author"), \
patch.object(srv, "_effective_workspace_role", return_value="author"), \
patch.object(
srv.issue_lock_worktree,
"read_worktree_git_state",
return_value=git_state,
), \
patch.object(
srv,
"_get_workspace_porcelain",
return_value=git_state["porcelain_status"],
), \
patch.object(srv, "_enforce_root_checkout_guard"):
mock_api.return_value = {
"number": 99,
"html_url": "https://gitea.example.com/issues/99",
}
try:
result = srv.gitea_create_issue(
title=title,
body="Body text for content gate.",
)
except RuntimeError as exc:
return {"raised": str(exc)}, mock_api
return result, mock_api
def test_clean_control_checkout_reaches_api(self):
"""The exact production failure that blocked filing #757 and #758."""
res, mock_api = self._run_create_issue(git_state=self._git_state())
self.assertNotIn("raised", res, f"guard still blocks: {res.get('raised')}")
self.assertEqual(res.get("number"), 99)
mock_api.assert_called_once()
def test_dirty_control_checkout_blocked(self):
dirty = " M gitea_mcp_server.py\n"
res, mock_api = self._run_create_issue(
git_state=self._git_state(porcelain=dirty)
)
self.assertFalse(isinstance(res, dict) and res.get("number") == 99)
mock_api.assert_not_called()
def test_detached_control_checkout_blocked(self):
res, mock_api = self._run_create_issue(git_state=self._git_state(branch=""))
self.assertFalse(isinstance(res, dict) and res.get("number") == 99)
mock_api.assert_not_called()
def test_base_mismatch_blocked(self):
res, mock_api = self._run_create_issue(
git_state=self._git_state(head=STALE_SHA)
)
self.assertFalse(isinstance(res, dict) and res.get("number") == 99)
mock_api.assert_not_called()
def test_stale_runtime_blocked(self):
res, mock_api = self._run_create_issue(
git_state=self._git_state(),
parity={"startup_head": STALE_SHA, "current_head": MASTER_SHA},
)
self.assertFalse(isinstance(res, dict) and res.get("number") == 99)
mock_api.assert_not_called()
def test_non_create_issue_mutation_still_blocked_from_control(self):
"""AC5 through the real preflight, with anti-stomp live."""
srv._preflight_resolved_task = "lock_issue"
with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT), \
patch.object(srv, "_actual_profile_role", return_value="author"), \
patch.object(srv, "_effective_workspace_role", return_value="author"), \
patch.object(
srv.issue_lock_worktree,
"read_worktree_git_state",
return_value=self._git_state(),
), \
patch.object(srv, "_get_workspace_porcelain", return_value=""), \
patch.object(
srv.root_checkout_guard,
"resolve_remote_master_sha",
return_value=MASTER_SHA,
), \
patch.object(srv, "_enforce_root_checkout_guard"):
with self.assertRaises(RuntimeError) as ctx:
srv.verify_preflight_purity(remote="prgs", task="lock_issue")
self.assertIn("control checkout", str(ctx.exception))
class TestBaseEquivalenceProofRequired(unittest.TestCase):
"""#757 AC3/AC4: an unknown tip is not evidence of agreement.
The original fix compared SHAs only inside
``if remote_tip and local_tip and remote_tip != local_tip``, so a missing
local HEAD, an unresolvable live master, or a resolver failure all fell
through and *granted* the bootstrap exemption. Base equivalence must be
proven, and anything less must fail closed.
"""
def _permits(self, assessment):
return cib.bootstrap_permits_control_checkout(
assessment,
task="create_issue",
workspace_path=CONTROL_CHECKOUT_ROOT,
canonical_repo_root=CONTROL_CHECKOUT_ROOT,
)
def _assert_fails_closed(self, assessment, *, expect_reason):
self.assertTrue(assessment["block"], "assessor must block")
self.assertFalse(assessment["allowed"])
self.assertFalse(assessment["proven"])
self.assertFalse(assessment["base_tips_verified"])
self.assertFalse(self._permits(assessment), "predicate must refuse")
joined = " ".join(assessment["reasons"]).lower()
self.assertIn(expect_reason, joined)
def test_missing_local_head_fails_closed(self):
self._assert_fails_closed(
proven_bootstrap(head=None),
expect_reason="control checkout head sha is unknown",
)
def test_missing_remote_master_fails_closed(self):
self._assert_fails_closed(
proven_bootstrap(remote_sha=None),
expect_reason="live master tip is unknown",
)
def test_both_tips_missing_fails_closed(self):
assessment = proven_bootstrap(head=None, remote_sha=None)
self._assert_fails_closed(
assessment, expect_reason="control checkout head sha is unknown"
)
self.assertIn("live master tip is unknown", " ".join(assessment["reasons"]))
def test_empty_and_whitespace_tips_fail_closed(self):
for head, remote in (("", MASTER_SHA), (MASTER_SHA, ""), (" ", " ")):
with self.subTest(head=repr(head), remote=repr(remote)):
assessment = proven_bootstrap(head=head, remote_sha=remote)
self.assertTrue(assessment["block"])
self.assertFalse(self._permits(assessment))
def test_mismatched_tips_fail_closed(self):
self._assert_fails_closed(
proven_bootstrap(head=STALE_SHA),
expect_reason="does not match",
)
def test_resolver_failure_fails_closed(self):
assessment = cib.assess_create_issue_bootstrap(
workspace_path=CONTROL_CHECKOUT_ROOT,
canonical_repo_root=CONTROL_CHECKOUT_ROOT,
current_branch="master",
head_sha=MASTER_SHA,
porcelain_status="",
remote_master_sha=None,
remote_master_sha_error="TimeoutError: remote unreachable",
task="create_issue",
)
self._assert_fails_closed(
assessment, expect_reason="could not be resolved"
)
# The operator must be able to see *why*, not just that it blocked.
self.assertIn("remote unreachable", " ".join(assessment["reasons"]))
def test_proven_bootstrap_records_both_normalized_shas(self):
assessment = proven_bootstrap()
self.assertEqual(assessment["local_head_sha"], MASTER_SHA)
self.assertEqual(assessment["remote_master_sha"], MASTER_SHA)
self.assertTrue(assessment["base_tips_verified"])
self.assertTrue(self._permits(assessment))
def test_tips_are_normalized_before_comparison(self):
"""Case and surrounding whitespace are not a different commit."""
assessment = proven_bootstrap(
head=f" {MASTER_SHA.upper()} ", remote_sha=MASTER_SHA
)
self.assertTrue(assessment["allowed"])
self.assertEqual(assessment["local_head_sha"], MASTER_SHA)
self.assertTrue(self._permits(assessment))
def test_predicate_rejects_assessment_with_tips_stripped(self):
"""A recorded proof that is later removed cannot still permit."""
for field in ("local_head_sha", "remote_master_sha"):
with self.subTest(field=field):
assessment = dict(proven_bootstrap())
assessment[field] = None
self.assertFalse(self._permits(assessment))
def test_predicate_rejects_forged_verified_flag(self):
"""base_tips_verified is re-derived, never trusted on its own."""
assessment = dict(proven_bootstrap())
assessment["local_head_sha"] = MASTER_SHA
assessment["remote_master_sha"] = STALE_SHA
assessment["base_tips_verified"] = True
self.assertFalse(self._permits(assessment))
def test_predicate_rejects_missing_verified_flag(self):
assessment = dict(proven_bootstrap())
assessment.pop("base_tips_verified")
self.assertFalse(self._permits(assessment))
class TestServerAssessmentFailsClosedOnResolverError(unittest.TestCase):
"""AC3/AC4 at the single server-derived computation site."""
def setUp(self):
# verify_preflight_purity short-circuits under pytest; the production
# path only runs with test mode disabled (same setup the #757 e2e uses).
srv._preflight_whoami_called = True
srv._preflight_capability_called = True
srv._preflight_resolved_role = "author"
srv._preflight_whoami_violation = False
srv._preflight_capability_violation = False
self._orig_in_test = srv._preflight_in_test_mode
srv._preflight_in_test_mode = lambda: False
def tearDown(self):
srv._preflight_in_test_mode = self._orig_in_test
srv._preflight_resolved_task = None
def _git_state(self):
return {
"current_branch": "master",
"head_sha": MASTER_SHA,
"porcelain_status": "",
}
def test_resolver_exception_produces_blocking_assessment(self):
"""A raising resolver must not become a silent, permissive None."""
with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT), \
patch.object(
srv.issue_lock_worktree,
"read_worktree_git_state",
return_value=self._git_state(),
), \
patch.object(
srv.root_checkout_guard,
"resolve_remote_master_sha",
side_effect=TimeoutError("remote unreachable"),
):
assessment = srv._create_issue_bootstrap_assessment("create_issue")
self.assertIsNotNone(assessment)
self.assertTrue(assessment["block"])
self.assertFalse(assessment["allowed"])
self.assertFalse(assessment["base_tips_verified"])
self.assertIsNone(assessment["remote_master_sha"])
self.assertIn("could not be resolved", " ".join(assessment["reasons"]))
self.assertFalse(
cib.bootstrap_permits_control_checkout(
assessment,
task="create_issue",
workspace_path=CONTROL_CHECKOUT_ROOT,
canonical_repo_root=CONTROL_CHECKOUT_ROOT,
)
)
def test_resolver_exception_blocks_real_preflight(self):
"""Production path: verify_preflight_purity must fail closed."""
srv._preflight_resolved_task = "create_issue"
try:
with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT), \
patch.object(srv, "_actual_profile_role", return_value="author"), \
patch.object(
srv, "_effective_workspace_role", return_value="author"
), \
patch.object(
srv.issue_lock_worktree,
"read_worktree_git_state",
return_value=self._git_state(),
), \
patch.object(srv, "_get_workspace_porcelain", return_value=""), \
patch.object(
srv.root_checkout_guard,
"resolve_remote_master_sha",
side_effect=TimeoutError("remote unreachable"),
), \
patch.object(srv, "_enforce_root_checkout_guard"):
with self.assertRaises(RuntimeError):
srv.verify_preflight_purity(remote="prgs", task="create_issue")
finally:
srv._preflight_resolved_task = None
if __name__ == "__main__":
unittest.main()