-
Notifications
You must be signed in to change notification settings - Fork 0
fix(sdlc): native merge queue core drain — arm-only autoqueue + all-green + governance-gate #3849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2a6d63a
fb62b23
a56989d
588a25c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||
| name: governance-gate | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Server-side governance required check for the native merge queue | ||||||||||||||||||||
| # (task reform-native-merge-queue, part 3). | ||||||||||||||||||||
| # | ||||||||||||||||||||
| # A PR may enter the merge queue only when its linked cc-task is | ||||||||||||||||||||
| # governance-releasable: stage >= S7_RELEASE, release_authorized == true, and an | ||||||||||||||||||||
| # authority_case present. That decision needs the local Obsidian cc-task vault, | ||||||||||||||||||||
| # which GitHub-hosted runners CANNOT read. So this gate verifies the server-side | ||||||||||||||||||||
| # PROOF instead: the `hapax/autoqueue-admission` commit status that the | ||||||||||||||||||||
| # vault-reading local autoqueue posts on the PR head SHA after it validates the | ||||||||||||||||||||
| # task. scripts/queue-admission-proof-check.py asserts that proof is fresh and | ||||||||||||||||||||
| # successful for queued / auto-merge PRs. | ||||||||||||||||||||
| # | ||||||||||||||||||||
| # Scope: the proof is enforced only for enqueued / auto_merge_enabled PRs (and | ||||||||||||||||||||
| # merge_group), so ordinary PR CI is never wedged by this gate. It is the | ||||||||||||||||||||
| # governance counterpart to pr-admission.yml's freeze governor — kept as a | ||||||||||||||||||||
| # distinct, clearly-named context so it can be made a required status check in | ||||||||||||||||||||
| # the main-merge-queue ruleset ("enqueueing itself is governed"). | ||||||||||||||||||||
|
|
||||||||||||||||||||
| on: | ||||||||||||||||||||
| pull_request: | ||||||||||||||||||||
| types: | ||||||||||||||||||||
| - opened | ||||||||||||||||||||
| - synchronize | ||||||||||||||||||||
| - reopened | ||||||||||||||||||||
| - ready_for_review | ||||||||||||||||||||
| - enqueued | ||||||||||||||||||||
| - auto_merge_enabled | ||||||||||||||||||||
| - auto_merge_disabled | ||||||||||||||||||||
| merge_group: | ||||||||||||||||||||
| types: [checks_requested] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| permissions: | ||||||||||||||||||||
| contents: read | ||||||||||||||||||||
| pull-requests: read | ||||||||||||||||||||
| statuses: read | ||||||||||||||||||||
|
|
||||||||||||||||||||
| jobs: | ||||||||||||||||||||
| governance-gate: | ||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||
| steps: | ||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| fetch-depth: 1 | ||||||||||||||||||||
|
Comment on lines
+43
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pin action to commit SHA and disable credential persistence. The checkout action should be pinned to a specific commit SHA per repository policy and should set 🔒 Proposed security fix - name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 1
+ persist-credentials: falseAs per coding guidelines and static analysis (zizmor): actions must be pinned to commit hashes per blanket policy, and credentials should not persist to prevent exposure through artifacts. 📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.25.2)[warning] 43-46: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 44-44: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Verify queue admission governance proof | ||||||||||||||||||||
| env: | ||||||||||||||||||||
| GH_TOKEN: ${{ github.token }} | ||||||||||||||||||||
| REPO: ${{ github.repository }} | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||
| python3 scripts/queue-admission-proof-check.py \ | ||||||||||||||||||||
| --repo "$REPO" \ | ||||||||||||||||||||
| --event-path "$GITHUB_EVENT_PATH" | ||||||||||||||||||||
|
Comment on lines
+54
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This required gate checks out the candidate ref and then executes Useful? React with 👍 / 👎. |
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,12 +3,14 @@ | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The merge queue should not depend on a human/session remembering to run | ||||||||||||||||||||||||||||||||||||||||||
| ``gh pr merge`` after a governed PR is ready. This reconciler scans open PRs, | ||||||||||||||||||||||||||||||||||||||||||
| matches each PR to a cc-task in the local Obsidian vault, and queues or arms | ||||||||||||||||||||||||||||||||||||||||||
| auto-merge only when Hapax governance and GitHub protection state both pass. | ||||||||||||||||||||||||||||||||||||||||||
| matches each PR to a cc-task in the local Obsidian vault, and ARMS auto-merge | ||||||||||||||||||||||||||||||||||||||||||
| only when Hapax governance and GitHub protection state both pass. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| GitHub's current CLI behavior for branches that require a merge queue is the | ||||||||||||||||||||||||||||||||||||||||||
| primitive this script uses: ``gh pr merge`` adds a ready PR to the queue, and | ||||||||||||||||||||||||||||||||||||||||||
| ``gh pr merge --auto`` arms auto-merge until required checks/reviews pass. | ||||||||||||||||||||||||||||||||||||||||||
| Arm-only (task reform-native-merge-queue): the sole positive GitHub mutation is | ||||||||||||||||||||||||||||||||||||||||||
| one idempotent ``gh pr merge --auto --squash``. GitHub's native merge queue then | ||||||||||||||||||||||||||||||||||||||||||
| owns batching, speculative ``gh-readonly-queue`` branches, auto-rebase, and | ||||||||||||||||||||||||||||||||||||||||||
| bisect-on-failure — this script no longer issues a direct ``--merge`` or manages | ||||||||||||||||||||||||||||||||||||||||||
| the queue itself, which previously raced GitHub's own batching and stranded PRs. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Usage:: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -114,9 +116,15 @@ | |||||||||||||||||||||||||||||||||||||||||
| # concurrently in flight in a DIFFERENT lane (the actual hazard); same-lane serial | ||||||||||||||||||||||||||||||||||||||||||
| # work is never held, and a deterministic lowest-PR tiebreak keeps two | ||||||||||||||||||||||||||||||||||||||||||
| # different-lane epic PRs from dead-holding each other. | ||||||||||||||||||||||||||||||||||||||||||
| SHARED_FILE_EPIC_PARENT_SPECS = { | ||||||||||||||||||||||||||||||||||||||||||
| # parent_spec basename -> serialized-epic id (the shared file it contends on) | ||||||||||||||||||||||||||||||||||||||||||
| "clog-frontend-elevation-design-2026-06-01.md": "clog-dashboard-lisp", | ||||||||||||||||||||||||||||||||||||||||||
| SHARED_FILE_EPIC_PARENT_SPECS: dict[str, str] = { | ||||||||||||||||||||||||||||||||||||||||||
| # parent_spec basename -> serialized-epic id (the shared file it contends on). | ||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||
| # Emptied 2026-06-01 (task reform-native-merge-queue): the native GitHub merge | ||||||||||||||||||||||||||||||||||||||||||
| # queue serializes shared-file contention through its speculative | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+119
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This registry is now empty, so existing CLOG tasks that only carry the documented Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||
| # gh-readonly-queue branches (auto-rebase + bisect-on-failure), so a | ||||||||||||||||||||||||||||||||||||||||||
| # pre-admission affinity hold is no longer needed to keep two different-lane | ||||||||||||||||||||||||||||||||||||||||||
| # epic PRs from merge-conflicting. Re-add an entry here only to re-enable the | ||||||||||||||||||||||||||||||||||||||||||
| # local pre-queue hold for a specific shared-file epic. | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| EPIC_INFLIGHT_STATUSES = frozenset( | ||||||||||||||||||||||||||||||||||||||||||
| {"claimed", "in_progress", "pr_open", "in_review", "merge_queue", "ready_for_merge"} | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -899,10 +907,15 @@ def merge_pr( | |||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| cmd = ["gh", "pr", "merge", str(decision.pr.number), "--repo", repo] | ||||||||||||||||||||||||||||||||||||||||||
| if decision.action == "enable_auto_merge": | ||||||||||||||||||||||||||||||||||||||||||
| cmd.extend(["--auto", "--merge"]) | ||||||||||||||||||||||||||||||||||||||||||
| elif decision.action == "queue": | ||||||||||||||||||||||||||||||||||||||||||
| cmd.append("--merge") | ||||||||||||||||||||||||||||||||||||||||||
| if decision.action in ("enable_auto_merge", "queue"): | ||||||||||||||||||||||||||||||||||||||||||
| # Arm-only (task reform-native-merge-queue): the local autoqueue's sole | ||||||||||||||||||||||||||||||||||||||||||
| # positive mutation is to ARM auto-merge with one idempotent command. | ||||||||||||||||||||||||||||||||||||||||||
| # GitHub's native merge queue then owns batching, speculative | ||||||||||||||||||||||||||||||||||||||||||
| # gh-readonly-queue branches, auto-rebase, and bisect-on-failure — we no | ||||||||||||||||||||||||||||||||||||||||||
| # longer issue a direct `--merge` (which raced the queue's own management). | ||||||||||||||||||||||||||||||||||||||||||
| # Re-arming an already-armed PR is a no-op; `--squash` matches the queue's | ||||||||||||||||||||||||||||||||||||||||||
| # configured merge method. | ||||||||||||||||||||||||||||||||||||||||||
| cmd.extend(["--auto", "--squash"]) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+910
to
+918
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, the --match-head-commit flag is supported in the official GitHub CLI documentation for the gh pr merge command [1][2][3]. This flag requires a commit SHA as an argument, which serves as a safety check: the pull request's head must match the provided SHA to allow the merge to proceed [1][4][2]. If the pull request head SHA has changed (i.e., it does not match the SHA provided), the command will prevent the merge [4][5]. While the documentation notes that the flag ensures the branch has not changed out from under the user [4], its primary function is to block the operation if the specified HEAD condition is not met. It is not explicitly stated in the manual that this flag disables the enabling of auto-merge, but rather that it serves as a constraint to allow or block the merge process based on the current state of the pull request head [1][2][3]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show context around the cited lines
sed -n '860,960p' scripts/cc-pr-autoqueue.py | nl -ba | sed -n '1,140p'
# Find and inspect set_autoqueue_admission_status()
rg -n "def set_autoqueue_admission_status|set_autoqueue_admission_status\(" -S scripts/cc-pr-autoqueue.py
python3 - <<'PY'
import re, itertools, pathlib
path = pathlib.Path("scripts/cc-pr-autoqueue.py")
txt = path.read_text(encoding="utf-8", errors="replace").splitlines()
# locate function start
start = None
for i,line in enumerate(txt):
if re.match(r"def set_autoqueue_admission_status\s*\(", line):
start = i
break
if start is None:
raise SystemExit("set_autoqueue_admission_status not found")
# print ~200 lines from start
for j in range(start, min(len(txt), start+220)):
print(f"{j+1:5d}:{txt[j]}")
PY
# Search for gh pr merge invocation / cmd construction near that block
rg -n "gh pr merge|pr merge|--auto|--squash|merge queue" scripts/cc-pr-autoqueue.pyRepository: hapax-systems/hapax-council Length of output: 114 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show context around the cited region without relying on `nl`
echo "=== scripts/cc-pr-autoqueue.py lines 880-960 (approx) ==="
sed -n '880,960p' scripts/cc-pr-autoqueue.py | sed -n '1,120p'
echo
echo "=== Locate set_autoqueue_admission_status definition ==="
rg -n "def set_autoqueue_admission_status\s*\(" scripts/cc-pr-autoqueue.py
echo
echo "=== Print set_autoqueue_admission_status body (first ~240 lines) ==="
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("scripts/cc-pr-autoqueue.py")
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
start = None
for i,l in enumerate(lines):
if re.match(r"def set_autoqueue_admission_status\s*\(", l):
start = i
break
assert start is not None
end = min(len(lines), start+240)
for idx in range(start, end):
print(f"{idx+1:5d}:{lines[idx]}")
PY
echo
echo "=== Search for gh pr merge command construction/execution ==="
rg -n "gh pr merge|--auto|--squash|--match-head-commit|subprocess\.run|check_call|Popen" scripts/cc-pr-autoqueue.py
echo
echo "=== Extract the specific if block around cmd.extend([\"--auto\", \"--squash\"]) ==="
rg -n "cmd\.extend\(\[\s*\"--auto\"\s*,\s*\"--squash\"\s*\]\)" -n scripts/cc-pr-autoqueue.py || true
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("scripts/cc-pr-autoqueue.py")
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
for i,l in enumerate(lines):
if 'cmd.extend(["--auto", "--squash"])' in l.replace(" ", "") or 'cmd.extend(["--auto", "--squash"])' in l:
# print ~25 lines around
s=max(0,i-20); e=min(len(lines), i+25)
print(f"Match near line {i+1}")
for j in range(s,e):
print(f"{j+1:5d}:{lines[j]}")
PYRepository: hapax-systems/hapax-council Length of output: 16441 🌐 Web query:
💡 Result: Behavior summary for Citations:
🌐 Web query:
💡 Result: If the PR head commit changes after you captured the SHA and you run gh pr merge with --match-head-commit , the merge will not proceed because that flag specifies “Commit SHA that the pull request head must match to allow merge” (i.e., it acts as a precondition). The official gh pr merge manual documents the flag semantics but does not describe the exact wording of the error message you’ll see, only that the merge is allowed only when the head matches the provided SHA [1]. References: [1] https://cli.github.com/manual/gh_pr_merge Citations: Bind the auto-merge/merge-queue arming command to the classified head SHA.
Suggested change if decision.action in ("enable_auto_merge", "queue"):
# Arm-only (task reform-native-merge-queue): the local autoqueue's sole
# positive mutation is to ARM auto-merge with one idempotent command.
# GitHub's native merge queue then owns batching, speculative
# gh-readonly-queue branches, auto-rebase, and bisect-on-failure — we no
# longer issue a direct `--merge` (which raced the queue's own management).
# Re-arming an already-armed PR is a no-op; `--squash` matches the queue's
# configured merge method.
cmd.extend(["--auto", "--squash"])
+ if decision.pr.head_sha:
+ cmd.extend(["--match-head-commit", decision.pr.head_sha])
elif decision.action == "disable_auto_merge":
cmd.append("--disable-auto")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| elif decision.action == "disable_auto_merge": | ||||||||||||||||||||||||||||||||||||||||||
| cmd.append("--disable-auto") | ||||||||||||||||||||||||||||||||||||||||||
| elif decision.action != "dequeue": | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because
all-greendoes not depend ondocs_only_filterorpost_merge_duplicate_filter, a failure in either setup job causes downstream jobs likelint,typecheck,web-build, andvscode-buildto be skipped; this loop then treats everyskippedresult as acceptable and reports the aggregate check green. Once branch protection is switched to require onlyall-green, a broken filter/duplicate-detection step can therefore let a PR or merge-group pass without running the required CI jobs. Please either include the setup jobs in the aggregate or only allowskippedfor the explicitly expected cases.Useful? React with 👍 / 👎.