Adds author_proofs.py, the author-side counterpart of review_proofs.py (#173), turning the branch-drift incident from PR #176 into fail-closed gates: - verify_branch_for_commit: the current branch must equal the intended feature branch and may never be a protected branch (master, main, develop, development, dev); missing state fails closed. - detect_branch_drift: any branch or HEAD change between validation time and commit time — including an external branch switch in a shared worktree, which is treated as an expected event to detect — blocks the commit until reconciled. - verify_push_target: a push requires the local branch, remote target branch, and intended issue branch to all match, none protected. - assess_protected_branch_commit: an accidental protected-branch commit must never be pushed, requires repair, and the repair must be reported; pushing the accident or silently continuing is a violation. - build_commit_push_report: final-report block carrying the branch proof before commit and before push; any failed proof, drift, or violation makes the status blocked. tests/test_author_proofs.py (27 tests) covers the issue's harness assertions: commit on master blocked; drift between validation and commit blocked; push target mismatch blocked; shared-worktree branch switch detected; repair path cannot silently continue without reporting. SKILL.md section E and templates/start-issue.md now require recording the validation-time branch/HEAD, the branch proof before commit, the branch proof before push, and non-silent accident repair. No review/merge/ permission gate is weakened. Closes #177 Co-Authored-By: Claude Fable 5 <[email protected]>
235 lines
9.3 KiB
Python
235 lines
9.3 KiB
Python
"""Tests for author-side branch-identity proofs (Issue #177).
|
|
|
|
Issue #177 (author-side counterpart of the #173 reviewer proofs) requires
|
|
author workflows to *prove* local git state before staging, committing, or
|
|
pushing, instead of discovering drift after the fact:
|
|
|
|
1. The current branch equals the intended feature branch and is never a
|
|
protected branch (master/main/develop/development/dev).
|
|
2. Branch or HEAD drift between validation and commit — including external
|
|
branch switches in a shared worktree — stops the workflow.
|
|
3. A push requires local branch, remote target branch, and intended issue
|
|
branch to all match.
|
|
4. An accidental commit on a protected branch must not be pushed and its
|
|
repair must be reported, never silently continued.
|
|
|
|
These are the harness assertions from the issue's Required behavior 5.
|
|
"""
|
|
import sys
|
|
import unittest
|
|
|
|
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
|
|
|
|
from author_proofs import ( # noqa: E402
|
|
PROTECTED_BRANCHES,
|
|
assess_protected_branch_commit,
|
|
build_commit_push_report,
|
|
detect_branch_drift,
|
|
verify_branch_for_commit,
|
|
verify_push_target,
|
|
)
|
|
|
|
FEATURE = "feat/issue-177-branch-drift-proofs"
|
|
HEAD_1 = "64dc334a92685b7b6a1fdb7ffe363f02a69f5dbd"
|
|
HEAD_2 = "ccc5ef79dfe629853e144763238593bd808d57e0"
|
|
|
|
|
|
class TestProtectedBranches(unittest.TestCase):
|
|
def test_known_protected_names(self):
|
|
for name in ("master", "main", "develop", "development", "dev"):
|
|
self.assertIn(name, PROTECTED_BRANCHES)
|
|
|
|
|
|
class TestVerifyBranchForCommit(unittest.TestCase):
|
|
"""Required behavior 1: prove the branch before staging/committing."""
|
|
|
|
def test_on_intended_feature_branch_is_proven(self):
|
|
proof = verify_branch_for_commit(FEATURE, FEATURE)
|
|
self.assertTrue(proof["proven"])
|
|
self.assertFalse(proof["block"])
|
|
|
|
def test_commit_attempted_while_on_master_is_blocked(self):
|
|
# Harness assertion (behavior 5, bullet 1).
|
|
proof = verify_branch_for_commit("master", FEATURE)
|
|
self.assertFalse(proof["proven"])
|
|
self.assertTrue(proof["block"])
|
|
self.assertTrue(any("master" in r for r in proof["reasons"]))
|
|
|
|
def test_every_protected_branch_is_blocked_as_current(self):
|
|
for name in PROTECTED_BRANCHES:
|
|
proof = verify_branch_for_commit(name, FEATURE)
|
|
self.assertTrue(proof["block"], name)
|
|
|
|
def test_intended_branch_may_not_be_protected(self):
|
|
proof = verify_branch_for_commit("master", "master")
|
|
self.assertFalse(proof["proven"])
|
|
self.assertTrue(proof["block"])
|
|
|
|
def test_wrong_feature_branch_is_blocked(self):
|
|
proof = verify_branch_for_commit("feat/issue-178-other-work", FEATURE)
|
|
self.assertFalse(proof["proven"])
|
|
self.assertTrue(proof["block"])
|
|
|
|
def test_missing_current_branch_fails_closed(self):
|
|
proof = verify_branch_for_commit("", FEATURE)
|
|
self.assertTrue(proof["block"])
|
|
|
|
def test_missing_intended_branch_fails_closed(self):
|
|
proof = verify_branch_for_commit(FEATURE, None)
|
|
self.assertTrue(proof["block"])
|
|
|
|
|
|
class TestBranchDrift(unittest.TestCase):
|
|
"""Required behaviors 2 + 3: drift between validation and commit stops
|
|
the workflow."""
|
|
|
|
def test_no_drift_when_branch_and_head_unchanged(self):
|
|
drift = detect_branch_drift(FEATURE, HEAD_1, FEATURE, HEAD_1)
|
|
self.assertFalse(drift["drifted"])
|
|
self.assertFalse(drift["block"])
|
|
|
|
def test_branch_drift_between_validation_and_commit_is_blocked(self):
|
|
# Harness assertion (behavior 5, bullet 2).
|
|
drift = detect_branch_drift(FEATURE, HEAD_1, "feat/other", HEAD_1)
|
|
self.assertTrue(drift["drifted"])
|
|
self.assertTrue(drift["block"])
|
|
|
|
def test_shared_worktree_branch_switch_is_detected(self):
|
|
# Harness assertion (behavior 5, bullet 4): an external session
|
|
# switching the shared checkout to another branch (e.g. master)
|
|
# must be detected as drift, not treated as exceptional noise.
|
|
drift = detect_branch_drift(FEATURE, HEAD_1, "master", HEAD_1)
|
|
self.assertTrue(drift["drifted"])
|
|
self.assertTrue(drift["block"])
|
|
self.assertTrue(any("switch" in r.lower() for r in drift["reasons"]))
|
|
|
|
def test_head_moved_since_validation_is_blocked(self):
|
|
drift = detect_branch_drift(FEATURE, HEAD_1, FEATURE, HEAD_2)
|
|
self.assertTrue(drift["drifted"])
|
|
self.assertTrue(drift["block"])
|
|
self.assertTrue(any("HEAD" in r for r in drift["reasons"]))
|
|
|
|
def test_missing_state_fails_closed(self):
|
|
drift = detect_branch_drift(FEATURE, HEAD_1, FEATURE, None)
|
|
self.assertTrue(drift["drifted"])
|
|
self.assertTrue(drift["block"])
|
|
|
|
|
|
class TestVerifyPushTarget(unittest.TestCase):
|
|
"""Required behavior 1 (push leg) + acceptance: push needs proof that
|
|
local, remote, and intended branches all match."""
|
|
|
|
def test_matching_local_remote_and_intended_is_proven(self):
|
|
proof = verify_push_target(FEATURE, FEATURE, FEATURE)
|
|
self.assertTrue(proof["proven"])
|
|
self.assertFalse(proof["block"])
|
|
|
|
def test_push_target_mismatch_is_blocked(self):
|
|
# Harness assertion (behavior 5, bullet 3).
|
|
proof = verify_push_target(FEATURE, "feat/issue-178-other-work", FEATURE)
|
|
self.assertFalse(proof["proven"])
|
|
self.assertTrue(proof["block"])
|
|
|
|
def test_local_branch_differs_from_intended_is_blocked(self):
|
|
proof = verify_push_target("feat/other", FEATURE, FEATURE)
|
|
self.assertTrue(proof["block"])
|
|
|
|
def test_pushing_a_protected_branch_is_blocked(self):
|
|
proof = verify_push_target("master", "master", "master")
|
|
self.assertFalse(proof["proven"])
|
|
self.assertTrue(proof["block"])
|
|
|
|
def test_missing_remote_target_fails_closed(self):
|
|
proof = verify_push_target(FEATURE, "", FEATURE)
|
|
self.assertTrue(proof["block"])
|
|
|
|
|
|
class TestProtectedBranchAccident(unittest.TestCase):
|
|
"""Required behavior 4: accidental protected-branch commits must not be
|
|
pushed and their repair must be reported."""
|
|
|
|
def test_feature_branch_commit_is_not_an_accident(self):
|
|
result = assess_protected_branch_commit(FEATURE)
|
|
self.assertFalse(result["accident"])
|
|
self.assertEqual(result["violations"], [])
|
|
|
|
def test_commit_on_master_is_an_accident_and_must_not_push(self):
|
|
result = assess_protected_branch_commit(
|
|
"master", pushed=False, repair_reported=True
|
|
)
|
|
self.assertTrue(result["accident"])
|
|
self.assertTrue(result["must_not_push"])
|
|
self.assertEqual(result["violations"], [])
|
|
self.assertTrue(result["repair_required"])
|
|
|
|
def test_pushing_the_accident_is_a_violation(self):
|
|
result = assess_protected_branch_commit(
|
|
"master", pushed=True, repair_reported=True
|
|
)
|
|
self.assertTrue(any("push" in v.lower() for v in result["violations"]))
|
|
|
|
def test_silent_repair_is_a_violation(self):
|
|
# Harness assertion (behavior 5, bullet 5): the repair path must not
|
|
# silently continue without reporting.
|
|
result = assess_protected_branch_commit(
|
|
"master", pushed=False, repair_reported=False
|
|
)
|
|
self.assertTrue(any("report" in v.lower() for v in result["violations"]))
|
|
|
|
|
|
class TestCommitPushReport(unittest.TestCase):
|
|
"""Acceptance criteria: the final report includes branch proof before
|
|
commit and before push, and blocks instead of continuing."""
|
|
|
|
def _report(self, **overrides):
|
|
kwargs = {
|
|
"commit_proof": verify_branch_for_commit(FEATURE, FEATURE),
|
|
"drift": detect_branch_drift(FEATURE, HEAD_1, FEATURE, HEAD_1),
|
|
"push_proof": verify_push_target(FEATURE, FEATURE, FEATURE),
|
|
"accident": assess_protected_branch_commit(FEATURE),
|
|
}
|
|
kwargs.update(overrides)
|
|
return build_commit_push_report(**kwargs)
|
|
|
|
def test_fully_proven_report_is_ok(self):
|
|
report = self._report()
|
|
self.assertEqual(report["status"], "ok")
|
|
self.assertTrue(report["branch_proof_before_commit"])
|
|
self.assertTrue(report["branch_proof_before_push"])
|
|
self.assertFalse(report["drift_detected"])
|
|
self.assertEqual(report["violations"], [])
|
|
|
|
def test_commit_proof_failure_blocks(self):
|
|
report = self._report(
|
|
commit_proof=verify_branch_for_commit("master", FEATURE)
|
|
)
|
|
self.assertEqual(report["status"], "blocked")
|
|
self.assertFalse(report["branch_proof_before_commit"])
|
|
|
|
def test_drift_blocks(self):
|
|
report = self._report(
|
|
drift=detect_branch_drift(FEATURE, HEAD_1, "master", HEAD_1)
|
|
)
|
|
self.assertEqual(report["status"], "blocked")
|
|
self.assertTrue(report["drift_detected"])
|
|
|
|
def test_push_proof_failure_blocks(self):
|
|
report = self._report(
|
|
push_proof=verify_push_target(FEATURE, "feat/other", FEATURE)
|
|
)
|
|
self.assertEqual(report["status"], "blocked")
|
|
self.assertFalse(report["branch_proof_before_push"])
|
|
|
|
def test_accident_violations_block(self):
|
|
report = self._report(
|
|
accident=assess_protected_branch_commit(
|
|
"master", pushed=False, repair_reported=False
|
|
)
|
|
)
|
|
self.assertEqual(report["status"], "blocked")
|
|
self.assertTrue(report["violations"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|