Skip to content

feat(devtools): enforce merge-gate at the actual merge boundary - #3607

Merged
Sinity merged 1 commit into
masterfrom
feature/devtools/merge-boundary-enforcement
Aug 3, 2026
Merged

feat(devtools): enforce merge-gate at the actual merge boundary#3607
Sinity merged 1 commit into
masterfrom
feature/devtools/merge-boundary-enforcement

Conversation

@Sinity

@Sinity Sinity commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Adds devtools workspace merge <PR>, a wrapper around gh pr merge --squash that structurally enforces the existing merge-gate record/check safety net and the merge-train terminal full-suite-verify rule, instead of leaving both as steps a coordinator has to remember to invoke.

Problem

devtools workspace merge-gate record/check (merge_gate.py) and the CLAUDE.md rule "run one full-suite devtools verify --all per merge-train session" are both real, already-shipped fixes for real 2026-08-01 incidents:

Both fixes work, but only if a coordinator remembers to invoke them at the right moment mid-session. The fanout-operations report's incident-ledger cross-check (referenced by beads polylogue-ct3r2 and polylogue-t6iga, which are duplicate filings of the same finding) found the exact pattern: fixes that became a command stuck; fixes that stayed a memory-triggered rule recurred at least once.

There is no GitHub Actions hook available to attach enforcement to here -- this repo's CI is CircleCI-only, GHA is intentionally dark. The actual merge boundary in practice is a human/agent coordinator invoking gh pr merge --squash, so that is the wrapping point.

Solution

devtools/merge_boundary.py, registered as devtools workspace merge in devtools/command_catalog.py:

  • merge <PR>: refuses unless the PR is OPEN; if no fresh merge-gate receipt exists for the current head sha, auto-records one (running --command, default devtools verify) instead of just erroring; runs merge-gate check and refuses to merge on any BLOCK (stale/missing receipt, nonzero exit, unacked late review comment); applies title hygiene by stripping a doubled (#N) (#N) squash-subject suffix (the 2026-07-12/13 incident where a manual gh pr edit --title step was skipped); then runs the actual gh pr merge --squash. --dry-run runs every check without merging. --with-verify immediately runs and records the merge-train's terminal full-suite verify after merging.
  • train-status: reports (exit 1) any PRs merged since the last recorded full-suite verify, reading a ledger at .cache/verify/merge-gate/merge-train-ledger.json -- the structural stand-in for "a merge-train records the full-suite verify as its terminal ledger step."
  • record-full-verify --command "devtools verify --all": runs and records that terminal step directly, clearing the ledger's pending list.

CLAUDE.md's merge-checklist bullet ("Before squash-merging any PR") now names workspace merge as the command to actually use, rather than describing the two-step merge-gate record/check sequence as something the coordinator must remember to run in order.

The lower-level merge-gate record/check/ack commands are untouched and still usable directly for ad hoc receipt inspection; merge is a thin, tested composition on top of them (cmd_record/cmd_check are called directly, not re-implemented).

Verification

  • devtools test tests/unit/devtools/test_merge_boundary.py tests/unit/devtools/test_merge_gate.py -> 35 passed (decision-logic tests mock subprocess.run/gh boundaries per the existing test_merge_gate.py pattern: receipt fresh/stale/absent, unacked newer review comment, title-hygiene collapse/append/idempotence, dry-run never calling gh pr merge, ledger append/pending-PR detection, record-full-verify clearing pending state).
  • python -m mypy devtools/merge_boundary.py tests/unit/devtools/test_merge_boundary.py -> no issues.
  • devtools verify --quick -> exit_code 0 (ruff format/check, mypy --strict, render all --check, topology/layering/manifests/lab-policy gates all ok).
  • devtools render topology-projection / devtools render devtools-reference were run and their outputs (docs/plans/topology-target.yaml, docs/devtools.md) are included in this diff.

Not run: devtools verify --all (full non-integration suite) -- left for the coordinator's merge-train terminal step, consistent with what this PR itself builds.

Ref polylogue-ct3r2, polylogue-t6iga -- both bead filings describe the same recommendation; this PR implements it once. The coordinator should dedupe the two beads (close one as duplicate-of-the-other, or close both against this PR) rather than tracking the same follow-up twice.

Co-Authored-By: Claude noreply@anthropic.com

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a9d242c-c73d-4327-b15b-69c8a783af9a

📥 Commits

Reviewing files that changed from the base of the PR and between f157068 and 5f966d9.

📒 Files selected for processing (5)
  • CLAUDE.md
  • devtools/command_catalog.py
  • devtools/merge_boundary.py
  • docs/devtools.md
  • tests/unit/devtools/test_merge_boundary.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Problem: `devtools workspace merge-gate record/check` and the one-full-
verify-per-merge-train rule are both real fixes for real 2026-08-01
incidents (late CodeRabbit findings after a squash-merge; a 43-test
regression no CI check or review comment ever flagged), but both fire
only if a coordinator remembers to invoke them at the right moment. The
fanout-operations incident-ledger cross-check found the pattern: fixes
that became a command stuck, fixes that stayed a memory-triggered rule
recurred. Duplicate bead filings polylogue-ct3r2/polylogue-t6iga name
this same gap; both are addressed by this one change.

Solution: `devtools/merge_boundary.py` adds `devtools workspace merge
<PR>`, a wrapper around the actual merge action (`gh pr merge --squash`
via `gh`, since this repo's CI is CircleCI-only with no GHA hook to
attach to). It auto-records a merge-gate receipt when none is fresh for
the current head sha, refuses to merge on any `merge-gate check` BLOCK,
strips a doubled `(#N) (#N)` squash-subject suffix (the 2026-07-12/13
title-hygiene incident), then merges. `--dry-run` runs every check
without merging; `--with-verify` runs and records the merge-train's
terminal full-suite verify immediately after merging into a ledger
(`.cache/verify/merge-gate/merge-train-ledger.json`). `train-status`
reports (exit 1) any PRs merged since the last recorded full-suite
verify -- the structural stand-in for "a merge-train records the
full-suite verify as its terminal ledger step" -- and
`record-full-verify` records that step directly.

Registered in devtools/command_catalog.py; CLAUDE.md's merge-checklist
sentence now points at `workspace merge` instead of the two-step
memory-triggered `merge-gate record`/`check` sequence.

Verification: devtools test tests/unit/devtools/test_merge_boundary.py
tests/unit/devtools/test_merge_gate.py -> 35 passed. devtools verify
--quick -> all steps ok, exit_code 0 (ruff format/check, mypy --strict,
render all --check, topology/layering/manifests/lab-policy gates).

Ref polylogue-ct3r2, polylogue-t6iga (duplicate filings of the same
recommendation -- implemented once here; coordinator should dedupe the
beads).

Co-Authored-By: Claude <noreply@anthropic.com>
@Sinity
Sinity force-pushed the feature/devtools/merge-boundary-enforcement branch from 78a81b1 to 5f966d9 Compare August 3, 2026 09:06
@Sinity
Sinity merged commit 44541b9 into master Aug 3, 2026
3 checks passed
@Sinity
Sinity deleted the feature/devtools/merge-boundary-enforcement branch August 3, 2026 09:38
Sinity added a commit that referenced this pull request Aug 3, 2026
6753s (byte-dup supersession, PR #3624), c831 (message_type restamp,
PR #3629, live backfill verified 0 remaining), qhk8z (duplicate-chain
baseline tie-break, PR #3616), ct3r2 + t6iga (merge-gate enforcement,
PR #3607, duplicate beads for the same fix).

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant