Skip to content

merge-guard: cure branch-delete cross-leg over-block via per-leg literal arms (v4.4.54)#1102

Merged
michael-wojcik merged 5 commits into
mainfrom
fix/1094-branch-delete-over-block
Jul 4, 2026
Merged

merge-guard: cure branch-delete cross-leg over-block via per-leg literal arms (v4.4.54)#1102
michael-wojcik merged 5 commits into
mainfrom
fix/1094-branch-delete-over-block

Conversation

@michael-wojcik

Copy link
Copy Markdown
Collaborator

Summary

Cures the merge-guard branch-delete cross-leg over-block — the 4th and final cross-leg twin (after force-push, API-merge, and PR-close legs). Pre-fix, the three whole-command branch-delete arms in DANGEROUS_PATTERNS cross-matched a stray -D/--delete token in a benign continuation leg (git branch new-feature && echo -D gated — cardinal-sin direction). Post-fix, the arms live in a per-leg _BRANCH_DELETE_LITERAL_ARMS SSOT consumed by both the read floor and detect_command_operation_type.

What landed

  • Per-leg read floor: 3 arm bodies moved verbatim into _BRANCH_DELETE_LITERAL_ARMS; 4th per-leg loop reusing the hoisted legs substrate; arms removed from DANGEROUS_PATTERNS with relocation note
  • Detect conversion (read==mint parity by construction): detect_command_operation_type branch-delete arms converted per-leg over the same tuple — closes the mint-scan multiplicity over-block vector a read-floor-only fix would have UNMASKED, and structurally closes the ambiguous-compound laundering substrate (no gate → no mint)
  • Certification: new TestBranchDeleteLiteralArmCrossLegSweep (cured rows incl. 3 probe-discovered separator families, 14-row same-leg preservation matrix, classification-parity rows p1–p6, reversed-order forward-only pin, per-row two-stage counter-mutations) + a two-direction laundering-closure canary beside the close-twin canaries
  • PATCH bump 4.4.53 → 4.4.54 (canonical 4 files)

Evidence

  • FULL suite: 10595 passed / 11 skipped / 0 failed / 0 errors (plain python -m pytest, errors token grepped explicitly; Python 3.12.7, pytest 9.0.2)
  • Anti-tautology source-revert: against pre-fix source the fix-coupled test set fails at exactly the pre-derived cardinality (20 rows); preservation/parity/pin rows stay green
  • 32 adversarial probes beyond the design rows, both directions, zero findings; extract_command_context on the cured compound returns {} (multiplicity vector empirically closed)
  • Standing pins untouched and green, including the pre-adjudicated clustered-flag non-first-leg limitation (unchanged in both directions by design)
  • Exactly one characterized classification delta on a gated command: raw line-continuation OP None → branch-delete at the raw retirement seam only (coherence-improving, force-push-precedented, pinned as p6)

Notes

…ral arms (#1094)

- Move the 3 branch-delete arms verbatim from DANGEROUS_PATTERNS into
  _BRANCH_DELETE_LITERAL_ARMS; add 4th per-leg read-floor loop reusing the
  hoisted legs (4th and final cross-leg twin)
- Convert detect_command_operation_type branch-delete arms to per-leg over
  the same SSOT tuple — read==mint classification parity by construction;
  closes the mint-scan multiplicity over-block vector
- New TestBranchDeleteLiteralArmCrossLegSweep: cured rows c1-c5 (paired
  D-False/OP-None), 14-row same-leg preservation matrix, classification-
  parity rows p1-p6 (p6 pins the single raw-retirement-seam delta),
  reversed-order forward-only pin, per-row two-stage counter-mutations
- Laundering-closure canary beside the close-twin canaries (auth_symmetry)
- Module threat-model + test docstring syncs per the consumers-in-sync table
…s-leg sweep

Probe-discovered during certification: the per-leg fix also cures these
three separator families (pre-fix DENY, post-fix ALLOW — intended over-block-
cure direction). Pinned in the cured matrix + counter-mutation matrix in
lockstep so a regression cannot silently re-over-block them.
Copilot AI review requested due to automatic review settings July 4, 2026 04:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the merge-guard’s branch-delete detection to avoid cross-leg false positives by moving the git branch -D/--delete --force/--force --delete regexes into a per-leg literal-arm SSOT and consuming it consistently in both the read-floor gate and detect_command_operation_type.

Changes:

  • Introduce _BRANCH_DELETE_LITERAL_ARMS and apply it per-leg in is_dangerous_command, removing the prior whole-command branch-delete patterns from DANGEROUS_PATTERNS.
  • Convert detect_command_operation_type branch-delete recognition to the same per-leg substrate to preserve read/mint parity.
  • Add comprehensive regression/certification tests and bump documented/plugin versions to 4.4.54.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pact-plugin/hooks/shared/merge_guard_common.py Moves branch-delete literal regexes to a per-leg SSOT and updates both gate + classifier to use it.
pact-plugin/tests/test_merge_guard_op_recognition_completeness.py Adds a cross-leg sweep ensuring the over-block is cured while same-leg gating/classification remains correct.
pact-plugin/tests/test_merge_guard_auth_symmetry.py Adds a non-vacuity canary ensuring the branch-delete laundering channel stays closed post-fix.
README.md Updates displayed plugin cache version to 4.4.54.
pact-plugin/README.md Bumps plugin README version to 4.4.54.
pact-plugin/.claude-plugin/plugin.json Bumps plugin version to 4.4.54.
.claude-plugin/marketplace.json Bumps marketplace version to 4.4.54.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +556 to +570
for _leg in _split_into_legs(command):
if _api_merge_leg_endpoint(_leg) is not None:
return "merge"
# branch-delete: git branch -D, git branch --delete --force,
# or git branch --force --delete (matches DANGEROUS_PATTERNS).
if re.search(_GIT_PREFIX + r"branch\s+.*-D\b", command):
return "branch-delete"
if re.search(_GIT_PREFIX + r"branch\s+.*--delete\s+--force\b", command):
return "branch-delete"
if re.search(_GIT_PREFIX + r"branch\s+--force\s+--delete\b", command):
return "branch-delete"
# branch-delete: git branch -D / --delete --force / --force --delete. Matched
# PER-LEG over the shared _BRANCH_DELETE_LITERAL_ARMS SSOT (#1094) — the same
# arm tuple the read floor consumes, over the same leg substrate
# (_split_into_legs), so a stray force-delete token in a benign continuation
# leg no longer classifies the compound as branch-delete, and read==mint
# holds by construction on this class (a command is gated via these arms iff
# some stripped leg matches iff detect classifies branch-delete here).
# Clustered spellings (-Df / -fD / --delete -f) fall through to the union-arm
# fallback below, same as the read floor.
for _leg in _split_into_legs(command):
if any(arm.search(_leg) for arm in _BRANCH_DELETE_LITERAL_ARMS):
return "branch-delete"
…-2 non-first-leg row, clustered-parity row

- Pin the |& separator cure in cured + counter-mutation matrices in lockstep;
  counter-mutation docstring now states the expected RED case-count against
  pre-fix source (22) and what it means
- Replace duplicated preservation row with cd-prefixed --delete --force form
  (first arm-2 non-first-leg pin; matrix now 14 unique forms)
- Add git branch -f --delete temp to the clustered-parity matrix
- Correct arm-3 tuple comment: whole-command \s+ DID span raw-newline leg
  boundaries; per-leg scoping is what bounds it now (cure direction)
- Fix stale hoist comment: four literal-arm loops, not three
- Sync module docstring close-example mechanism wording to per-leg phrasing
- Hoist a single _split_into_legs computation in detect_command_operation_type,
  reused by the three per-leg loops (behavior-neutral; verified by 26-command
  pre/post capture)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants