fix(hooks): merge-guard mint-vs-read auth-token symmetry (#1031 + #1032)#1040
Merged
michael-wojcik merged 17 commits intoJun 26, 2026
Merged
Conversation
…merge_guard_common (shared SSOT)
…ection-signal + command-driven gate
…erate closure (KD-10)
…ERED_L2 partition
…block (per-positional count, mirrors force-push)
…direct over-block
…tic-Labs-AI#1032) The post-hook minted a token from any command in the AskUserQuestion bundle, including a command padded into question prose alongside a benign clicked option — letting a padded question mint authorization the operator never selected (Synaptic-Labs-AI#1032). Anchor the mint to the operator's action surface: the minted (op,target) pair must be carried by a CLICKED option, never question prose alone. Prose stays a divergence signal (>1 distinct pair refuses) but is no longer a sole mint source; a bundle with no options can never mint (fail-closed); free-text answers no longer mint. Confined to _mint_context_from_bundle; zero token-lifecycle change.
…contract (Synaptic-Labs-AI#1032) Repoint the approval-flow tests broken by the option-anchoring fix (Synaptic-Labs-AI#1032): the authorizing command moves from question-prose into the clicked option's description (the conforming convention), so every original regression — FD-redirect over-block, bare-push authorize, two-uses-then-third-denied, branch-delete, force-push, session-scoped, merge and close-pr flows — stays guarded under the new contract. Add option-anchoring counter-tests: a command padded into question prose with a benign clicked option refuses; a no-options/free-text bundle refuses. Each is mutation-proven non-vacuous.
…undle (do-not-strip)
This was referenced Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the merge-guard mint-vs-read auth-token asymmetry — the single root cause behind #1031 (false-REJECT / usability) and #1032 (false-AUTHORIZE / security) — as one PR. The mint side (
merge_guard_post) parsed AskUserQuestion prose while the read side (merge_guard_pre) validated the actual command; they drifted. The fix routes BOTH arms through one shared command-anchored extractor (so they can't drift again), makes the read side fail-closed, and fixes the selection-signal so a selected option carrying the command counts as approval.What landed (10 commits)
refactor(hooks)— shared command-context extractor SSOT inmerge_guard_common.py; both arms call it on a command string.fix(hooks)—merge_guard_prefails closed: positive op + target per op-class, no terminal allow (closes the untyped-token, malformed-context, and pr-axis fall-through holes).fix(hooks)—merge_guard_postmints from the command literal + bimodal selection-signal + command-driven gate. SACROSANCT: the multiplicity gate counts distinct (op, target) pairs == 1, not raw command-regions (a region-count rule would refuse every template-conforming approval).feat(hooks)+docs(commands)— command-in-approval convention template inpeer-review.md+ guard refusal-message guidance (never teaches the bare-command/reduce-the-question workaround).test(hooks)—merge_guard_pre/postadded toSEAM_DEPENDENT_HOOKS; AST-derived closure regenerated same-commit.COVERED_L2partition, and the 145-residual retargeted to the fail-closed command-anchored contract. merge_guard: branch-delete auth token captures the delete-flag as the branch name, so an approved branch force-delete is permanently rejected #933 non-vacuity proven (per-mechanism source-only reverts + CLASS-II add-mutations).Why
Empirically at HEAD: all 4 #1032 bypasses + A-DEFER were live and #1031 R1-R7 false-rejected. The asymmetry required fixing both arms together; the shared extractor makes future drift structurally impossible (the #720 dual-hook class). Confine-the-fix (#797) honored — zero token-lifecycle changes.
Testing
Full suite GREEN: 9684 passed / 11 skipped / 0 failed / 0 errors (rtk proxy + explicit 0-errors scan, pyenv 3.12.7). Non-vacuity: C2-revert → 4 RED, C3-revert → 17 RED, CLASS-II add-mutations → 3, seam → 3 — all RESTORE_CLEAN. Coverage ≥ 90% on changed mint/read paths.
Closure
Completes the acceptance criteria of #1031 and #1032; closure pending the post-install logged live-probe PASS (do not auto-close — runtime-hook gate per the established discipline).
Follow-ups (NOT in this PR)
2>&1redirect case is an over-block, the safe merge_guard_post mints token for the WRONG PR when approval text contains an unrelated #NNNN (false 'token does not match' rejections) #1031 direction).,).gh issue commentbody quoting a dangerous command is rejected (substring-scan brittleness) #1037 (is_dangerous_commandsubstring brittleness) — already filed.Caveat
R7/#1034 is PROVISIONAL — asserted from the documented option-command structure (no verbatim captured payload exists); the load-bearing property (command-only-in-option + descriptive label mints) is exercised.