diff --git a/docs/automated-checks.md b/docs/automated-checks.md index d84b1be..1e0b1ad 100644 --- a/docs/automated-checks.md +++ b/docs/automated-checks.md @@ -45,12 +45,58 @@ Automated checks do not determine factual correctness, public product status, De Auto-merge must remain disabled. Only a Maintainer with write access may manually add a pull request to the queue, and green checks alone are never sufficient authorization. Capture the PR's `headRefOid` before reviewing both outputs in full: ```bash -TASK_REVIEWED_SHA="$(gh pr view --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" -gh pr diff --name-only -gh pr diff -TASK_CURRENT_SHA="$(gh pr view --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" -test "$TASK_CURRENT_SHA" = "$TASK_REVIEWED_SHA" -gh pr merge --repo QoderAI/cloud-agents-cookbook --match-head-commit "$TASK_REVIEWED_SHA" --squash +( + set -euo pipefail + TASK_PR_NUMBER=123 + TASK_PR_NODE_ID="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json id --jq .id)" + TASK_REVIEWED_SHA="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + gh pr diff "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --name-only + gh pr diff "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook + TASK_CURRENT_SHA="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + test "$TASK_CURRENT_SHA" = "$TASK_REVIEWED_SHA" + TASK_PRE_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$TASK_PR_NODE_ID")" + printf '%s\n' "$TASK_PRE_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null + TASK_ENQUEUE_UTC="$(node -e 'process.stdout.write(new Date().toISOString())')" + set +e + TASK_ENQUEUE_JSON="$(gh api graphql \ + -f query='mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: {pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid}) { mergeQueueEntry { id position } } }' \ + -F pullRequestId="$TASK_PR_NODE_ID" \ + -F expectedHeadOid="$TASK_REVIEWED_SHA")" + TASK_MUTATION_STATUS=$? + set -e + TASK_MUTATION_ENTRY_ID="$(printf '%s\n' "$TASK_ENQUEUE_JSON" | jq -er '.data.enqueuePullRequest.mergeQueueEntry.id | select(type == "string" and length > 0)' 2>/dev/null)" || TASK_MUTATION_ENTRY_ID="" + if ! TASK_POST_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$TASK_PR_NODE_ID")"; then + printf 'post-readback failed; admission state is indeterminate; do not retry blindly\n' >&2 + exit 1 + fi + printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -c '.data.node | {state, headRefOid, mergeQueueEntry}' + if TASK_QUEUE_ENTRY_ID="$(printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -er --arg sha "$TASK_REVIEWED_SHA" \ + 'select(.data.node.state == "OPEN" and .data.node.headRefOid == $sha) | .data.node.mergeQueueEntry.id | select(type == "string" and length > 0)')"; then + if [ "$TASK_MUTATION_STATUS" -eq 0 ]; then + test -n "$TASK_MUTATION_ENTRY_ID" + fi + TASK_EXPECTED_ENTRY_ID="$TASK_QUEUE_ENTRY_ID" + if [ -n "$TASK_MUTATION_ENTRY_ID" ]; then + TASK_EXPECTED_ENTRY_ID="$TASK_MUTATION_ENTRY_ID" + fi + printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" --arg entry "$TASK_EXPECTED_ENTRY_ID" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry.id == $entry' >/dev/null + test "$TASK_QUEUE_ENTRY_ID" = "$TASK_EXPECTED_ENTRY_ID" + printf 'mutation_status=%s\nenqueue_time=%s\nqueue_entry=%s\n' "$TASK_MUTATION_STATUS" "$TASK_ENQUEUE_UTC" "$TASK_QUEUE_ENTRY_ID" + elif printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null; then + printf 'confirmed not queued; stop and review before another admission attempt\n' >&2 + exit 1 + else + printf 'post-readback is indeterminate or the head changed; stop and dequeue if necessary\n' >&2 + exit 1 + fi +) ``` -Replace the `TASK_` prefix with a name unique to the operation. Read `headRefOid` again immediately before enqueueing and require strict equality with the reviewed SHA. If the head changes, stop and repeat the complete review; `--match-head-commit` is mandatory. Do not queue an external pull request that touches `.github/**`, `scripts/**`, `tests/**`, root `package*.json`, `config/**`, `schema/**`, `docs/**`, or other Maintainer-owned automation/security infrastructure. Recreate and review that work as a Maintainer-owned infrastructure pull request. The Ruleset keeps zero required approvals only because the repository currently has a single Maintainer; it compensates with an empty bypass list and this explicit manual admission boundary. When a second Maintainer is available, require approval, Code Owner review, and latest-push approval. +Replace the `TASK_` prefix with a name unique to the operation. Before mutation, readback must prove `state=OPEN`, the reviewed head, and no existing queue entry. A mutation transport failure is indeterminate, so the script always performs post-readback and never retries blindly. The PR is admitted only when post-readback returns the same head and a non-empty entry ID; when the mutation response also contains an ID, both IDs must match. The same head with `mergeQueueEntry=null` confirms no admission and stops the workflow. A head mismatch or any other state is indeterminate: stop and dequeue first if necessary. Never set `jump`. Do not queue an external pull request that touches `.github/**`, `scripts/**`, `tests/**`, root `package*.json`, `config/**`, `schema/**`, `docs/**`, or other Maintainer-owned automation/security infrastructure. Recreate and review that work as a Maintainer-owned infrastructure pull request. The Ruleset keeps zero required approvals only because the repository currently has a single Maintainer; it compensates with an empty bypass list and this explicit manual admission boundary. When a second Maintainer is available, require approval, Code Owner review, and latest-push approval. diff --git a/docs/maintainers/repository-design.md b/docs/maintainers/repository-design.md index d5827a2..66a30f8 100644 --- a/docs/maintainers/repository-design.md +++ b/docs/maintainers/repository-design.md @@ -61,7 +61,7 @@ For public content pull requests, the intended validation path checks out truste Automated checks cover metadata schema, global slug uniqueness, path consistency, taxonomy, article structure, required sections, images, links, Markdown fences, footnotes, Mermaid syntax and safety, unsupported elements, common secret patterns, Demo binding and static safety, configuration references, and deterministic catalog generation. Submitted Demo commands and source are never executed. -Automated checks do not decide factual accuracy, Demo runtime correctness or operational safety, publication value, copyright ownership, customer authorization, or whether a statement describes a public product capability. Maintainers review these areas and Demo source manually. In the current single-Maintainer configuration, Auto-merge is disabled and only a write-access Maintainer may manually queue a change after binding the full file-list and diff review to the PR's immutable `headRefOid`. The head SHA is read again immediately before `gh pr merge --match-head-commit`; any change requires a complete re-review. Green checks are evidence, not queue authorization. After a second Maintainer is available, require approval, Code Owner review, and latest-push approval while retaining the SHA-bound manual infrastructure review. +Automated checks do not decide factual accuracy, Demo runtime correctness or operational safety, publication value, copyright ownership, customer authorization, or whether a statement describes a public product capability. Maintainers review these areas and Demo source manually. In the current single-Maintainer configuration, Auto-merge is disabled and only a write-access Maintainer may manually queue a change after binding the full file-list and diff review to the PR's immutable `headRefOid`. Pre-readback proves the PR is open and unqueued; GraphQL `enqueuePullRequest` then uses the reviewed SHA as `expectedHeadOid` and never sets `jump`. Post-readback always runs even if mutation transport fails. Success requires the reviewed head and a non-empty readback entry ID, equal to the mutation entry ID when the response supplies one. A same-head null entry confirms no admission; any other result is indeterminate and may require dequeue. Green checks are evidence, not queue authorization. After a second Maintainer is available, require approval, Code Owner review, and latest-push approval while retaining the SHA-bound manual infrastructure review. ## Preview and publication diff --git a/docs/maintainers/repository-settings.md b/docs/maintainers/repository-settings.md index 802f49f..4fba9dd 100644 --- a/docs/maintainers/repository-settings.md +++ b/docs/maintainers/repository-settings.md @@ -31,15 +31,61 @@ The initial CODEOWNER is `@anchenqlw`, but CODEOWNERS is currently routing infor Green checks show that the candidate produced the expected contexts; they do not authorize a merge. A pull request can propose changes to the workflows, scripts, and tests that produce those contexts. Every admission review is bound to one immutable pull-request head SHA. Use a task-specific variable name when operating on a real PR: ```bash -TASK_REVIEWED_SHA="$(gh pr view --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" -gh pr diff --name-only -gh pr diff -TASK_CURRENT_SHA="$(gh pr view --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" -test "$TASK_CURRENT_SHA" = "$TASK_REVIEWED_SHA" -gh pr merge --repo QoderAI/cloud-agents-cookbook --match-head-commit "$TASK_REVIEWED_SHA" --squash +( + set -euo pipefail + TASK_PR_NUMBER=123 + TASK_PR_NODE_ID="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json id --jq .id)" + TASK_REVIEWED_SHA="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + gh pr diff "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --name-only + gh pr diff "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook + TASK_CURRENT_SHA="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + test "$TASK_CURRENT_SHA" = "$TASK_REVIEWED_SHA" + TASK_PRE_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$TASK_PR_NODE_ID")" + printf '%s\n' "$TASK_PRE_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null + TASK_ENQUEUE_UTC="$(node -e 'process.stdout.write(new Date().toISOString())')" + set +e + TASK_ENQUEUE_JSON="$(gh api graphql \ + -f query='mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: {pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid}) { mergeQueueEntry { id position } } }' \ + -F pullRequestId="$TASK_PR_NODE_ID" \ + -F expectedHeadOid="$TASK_REVIEWED_SHA")" + TASK_MUTATION_STATUS=$? + set -e + TASK_MUTATION_ENTRY_ID="$(printf '%s\n' "$TASK_ENQUEUE_JSON" | jq -er '.data.enqueuePullRequest.mergeQueueEntry.id | select(type == "string" and length > 0)' 2>/dev/null)" || TASK_MUTATION_ENTRY_ID="" + if ! TASK_POST_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$TASK_PR_NODE_ID")"; then + printf 'post-readback failed; admission state is indeterminate; do not retry blindly\n' >&2 + exit 1 + fi + printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -c '.data.node | {state, headRefOid, mergeQueueEntry}' + if TASK_QUEUE_ENTRY_ID="$(printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -er --arg sha "$TASK_REVIEWED_SHA" \ + 'select(.data.node.state == "OPEN" and .data.node.headRefOid == $sha) | .data.node.mergeQueueEntry.id | select(type == "string" and length > 0)')"; then + if [ "$TASK_MUTATION_STATUS" -eq 0 ]; then + test -n "$TASK_MUTATION_ENTRY_ID" + fi + TASK_EXPECTED_ENTRY_ID="$TASK_QUEUE_ENTRY_ID" + if [ -n "$TASK_MUTATION_ENTRY_ID" ]; then + TASK_EXPECTED_ENTRY_ID="$TASK_MUTATION_ENTRY_ID" + fi + printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" --arg entry "$TASK_EXPECTED_ENTRY_ID" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry.id == $entry' >/dev/null + test "$TASK_QUEUE_ENTRY_ID" = "$TASK_EXPECTED_ENTRY_ID" + printf 'mutation_status=%s\nenqueue_time=%s\nqueue_entry=%s\n' "$TASK_MUTATION_STATUS" "$TASK_ENQUEUE_UTC" "$TASK_QUEUE_ENTRY_ID" + elif printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null; then + printf 'confirmed not queued; stop and review before another admission attempt\n' >&2 + exit 1 + else + printf 'post-readback is indeterminate or the head changed; stop and dequeue if necessary\n' >&2 + exit 1 + fi +) ``` -Replace the `TASK_` prefix with a name unique to the operation, such as `INFRA_` or `PR11_`. Capture the reviewed SHA before inspecting the complete file list and full diff. Immediately before the queue command, read `headRefOid` again and require strict equality. If it changed for any reason, stop and restart the review against the new SHA. `--match-head-commit` is mandatory and prevents the enqueue operation from racing with a later push. +Replace the `TASK_` prefix with a name unique to the operation. Before mutation, readback must prove `state=OPEN`, the reviewed head, and no existing queue entry. A mutation transport failure is indeterminate, so the script always performs post-readback and never retries blindly. The PR is admitted only when post-readback returns the same head and a non-empty entry ID; when the mutation response also contains an ID, both IDs must match. The same head with `mergeQueueEntry=null` confirms no admission and stops the workflow. A head mismatch or any other state is indeterminate: stop and dequeue first if necessary. Do not set `jump`. Do not queue an external pull request that changes `.github/**`, `scripts/**`, `tests/**`, root `package*.json`, `config/**`, `schema/**`, `docs/**`, or other Maintainer-owned repository automation/security infrastructure. Recreate such work on a Maintainer-owned branch and submit it as a separate infrastructure pull request. diff --git a/docs/repository-governance.md b/docs/repository-governance.md index f272c9c..f0b87cf 100644 --- a/docs/repository-governance.md +++ b/docs/repository-governance.md @@ -28,7 +28,7 @@ Introducing or removing a Demo requires changing its owner article in the same p Required checks, resolved review conversations, and a successful preview are required before merge. While the repository has only one Maintainer, the Ruleset requires zero approvals, no Code Owner review, and no last-push approval because GitHub does not allow an author to approve their own pull request. Auto-merge is disabled and the bypass list is empty. -Only a Maintainer with write access may manually add a PR to Merge Queue. Before doing so, the Maintainer captures `headRefOid` in a task-specific variable, reviews `gh pr diff --name-only` and the complete `gh pr diff `, reads `headRefOid` again and requires exact equality, then uses `gh pr merge --match-head-commit "$TASK_REVIEWED_SHA" --squash`. Any head change invalidates the review and requires a complete re-review. Green checks alone never authorize queue admission. External infrastructure changes are rebuilt as separate Maintainer-owned pull requests. +Only a Maintainer with write access may manually add a PR to Merge Queue. Before doing so, the Maintainer captures the PR node ID and `headRefOid` in task-specific variables, reviews `gh pr diff --name-only` and the complete `gh pr diff `, then reads `headRefOid` again and requires exact equality. Pre-readback must prove the reviewed PR is open and unqueued. Queue admission uses GraphQL `enqueuePullRequest` with `pullRequestId` and the reviewed SHA as `expectedHeadOid`; it never sets `jump`. Mutation transport failure is indeterminate, so post-readback always runs and the mutation is never retried blindly. Successful admission requires the same head SHA and a non-empty post-readback entry ID; if the mutation response contains an entry ID, it must be the same ID. A same-head null entry confirms no admission, while any other state requires stopping and potentially dequeueing. Green checks alone never authorize queue admission. External infrastructure changes are rebuilt as separate Maintainer-owned pull requests. When a second Maintainer or Maintainer team has write access, upgrade the Ruleset to require at least one approval, Code Owner review, and approval of the latest push. The SHA-bound infrastructure diff review remains required. A merge to `main` is the publication event; there is no later content-import step. diff --git a/docs/superpowers/plans/2026-08-21-merge-queue-enqueue-correction.md b/docs/superpowers/plans/2026-08-21-merge-queue-enqueue-correction.md new file mode 100644 index 0000000..026cd11 --- /dev/null +++ b/docs/superpowers/plans/2026-08-21-merge-queue-enqueue-correction.md @@ -0,0 +1,303 @@ +# Merge Queue Enqueue Correction Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the queue-admission command that incorrectly depends on repository Auto-merge with the live-verified GitHub GraphQL `enqueuePullRequest` mutation while preserving the single-Maintainer SHA-bound admission gate. + +**Architecture:** Keep Ruleset `20582196`, `.github/workflows/merge-queue.yml`, and repository settings unchanged. Operational queue admission resolves the pull request node ID, binds the reviewed `headRefOid` through GraphQL `expectedHeadOid`, and proves the PR is open and unqueued before recording the enqueue time. Mutation transport failure is treated as indeterminate, so post-readback always determines whether the reviewed head acquired a queue entry and compares its ID with any ID returned by the mutation. The pre-queue infrastructure merge continues using `gh pr merge --match-head-commit` because it occurs before the queue rule is enabled. + +**Tech Stack:** Markdown, GitHub CLI, GitHub GraphQL API, npm repository checks. + +## Global Constraints + +- Repository Auto-merge remains disabled. +- Ruleset `20582196` and all Merge Queue parameters remain unchanged. +- Queue admission remains a write-access Maintainer-only operation after complete file-list and full-diff review. +- Every queue mutation must send the reviewed head as `expectedHeadOid`, must not set `jump`, and must be surrounded by precondition and postcondition readbacks. Mutation transport failure is never blindly retried. +- External infrastructure changes remain ineligible for queue admission. +- The correction is documentation-only and every commit must carry `Signed-off-by: 安陈 `. + +--- + +### Task 1: Correct the documented queue-admission interface + +**Files:** +- Modify: `docs/automated-checks.md` +- Modify: `docs/repository-governance.md` +- Modify: `docs/maintainers/repository-design.md` +- Modify: `docs/maintainers/repository-settings.md` +- Modify: `docs/superpowers/specs/2026-08-21-merge-queue-design.md` +- Modify: `docs/superpowers/plans/2026-08-21-merge-queue.md` + +**Interfaces:** +- Consumes: reviewed pull request number, GraphQL pull request node ID, and reviewed `headRefOid`. +- Produces: a live-verified queue command using `enqueuePullRequest(input: {pullRequestId, expectedHeadOid})`, plus readback evidence tying the reviewed head and mutation response to the same queue-entry ID. + +- [ ] **Step 1: Prove the stale command is present only where expected** + +Run: + +```bash +rg -n "gh pr merge|match-head-commit|enqueuePullRequest|expectedHeadOid" \ + docs/automated-checks.md \ + docs/repository-governance.md \ + docs/maintainers/repository-design.md \ + docs/maintainers/repository-settings.md \ + docs/superpowers/specs/2026-08-21-merge-queue-design.md \ + docs/superpowers/plans/2026-08-21-merge-queue.md +``` + +Expected: generic and PR #11 queue-admission instructions still use `gh pr merge --match-head-commit`; `enqueuePullRequest` and `expectedHeadOid` are absent. The infrastructure PR merge in Task 3 of the historical rollout plan also uses `gh pr merge --match-head-commit` and must remain unchanged. + +- [ ] **Step 2: Replace generic queue-admission examples** + +Use this exact sequence in `docs/automated-checks.md` and `docs/maintainers/repository-settings.md`, adapting only the surrounding prose: + +```bash +( + set -euo pipefail + TASK_PR_NUMBER=123 + TASK_PR_NODE_ID="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json id --jq .id)" + TASK_REVIEWED_SHA="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + gh pr diff "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --name-only + gh pr diff "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook + TASK_CURRENT_SHA="$(gh pr view "$TASK_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + test "$TASK_CURRENT_SHA" = "$TASK_REVIEWED_SHA" + TASK_PRE_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$TASK_PR_NODE_ID")" + printf '%s\n' "$TASK_PRE_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null + TASK_ENQUEUE_UTC="$(node -e 'process.stdout.write(new Date().toISOString())')" + set +e + TASK_ENQUEUE_JSON="$(gh api graphql \ + -f query='mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: {pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid}) { mergeQueueEntry { id position } } }' \ + -F pullRequestId="$TASK_PR_NODE_ID" \ + -F expectedHeadOid="$TASK_REVIEWED_SHA")" + TASK_MUTATION_STATUS=$? + set -e + TASK_MUTATION_ENTRY_ID="$(printf '%s\n' "$TASK_ENQUEUE_JSON" | jq -er '.data.enqueuePullRequest.mergeQueueEntry.id | select(type == "string" and length > 0)' 2>/dev/null)" || TASK_MUTATION_ENTRY_ID="" + if ! TASK_POST_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$TASK_PR_NODE_ID")"; then + printf 'post-readback failed; admission state is indeterminate; do not retry blindly\n' >&2 + exit 1 + fi + printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -c '.data.node | {state, headRefOid, mergeQueueEntry}' + if TASK_QUEUE_ENTRY_ID="$(printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -er --arg sha "$TASK_REVIEWED_SHA" \ + 'select(.data.node.state == "OPEN" and .data.node.headRefOid == $sha) | .data.node.mergeQueueEntry.id | select(type == "string" and length > 0)')"; then + if [ "$TASK_MUTATION_STATUS" -eq 0 ]; then + test -n "$TASK_MUTATION_ENTRY_ID" + fi + TASK_EXPECTED_ENTRY_ID="$TASK_QUEUE_ENTRY_ID" + if [ -n "$TASK_MUTATION_ENTRY_ID" ]; then + TASK_EXPECTED_ENTRY_ID="$TASK_MUTATION_ENTRY_ID" + fi + printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" --arg entry "$TASK_EXPECTED_ENTRY_ID" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry.id == $entry' >/dev/null + test "$TASK_QUEUE_ENTRY_ID" = "$TASK_EXPECTED_ENTRY_ID" + printf 'mutation_status=%s\nenqueue_time=%s\nqueue_entry=%s\n' "$TASK_MUTATION_STATUS" "$TASK_ENQUEUE_UTC" "$TASK_QUEUE_ENTRY_ID" + elif printf '%s\n' "$TASK_POST_READBACK_JSON" | jq -e --arg sha "$TASK_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null; then + printf 'confirmed not queued; stop and review before another admission attempt\n' >&2 + exit 1 + else + printf 'post-readback is indeterminate or the head changed; stop and dequeue if necessary\n' >&2 + exit 1 + fi +) +``` + +Document the precondition readback and the indeterminate mutation boundary. Before mutation, prove `state=OPEN`, the reviewed SHA, and `mergeQueueEntry=null`. Always perform post-readback even when the mutation command fails, and never retry blindly. A same-head non-null entry confirms admission; if the mutation response contains an entry ID, it must equal the readback ID. A same-head null entry confirms no admission and stops. A mismatched head or any other state is indeterminate and may require dequeue. Do not set `jump`. + +- [ ] **Step 3: Align governance, design, and the historical acceptance record** + +Update `docs/repository-governance.md`, `docs/maintainers/repository-design.md`, and `docs/superpowers/specs/2026-08-21-merge-queue-design.md` to name `enqueuePullRequest` with `expectedHeadOid` as the queue interface. In `docs/superpowers/plans/2026-08-21-merge-queue.md`, preserve the Task 3 infrastructure merge command and replace only the global queue-admission claims and Task 5 PR #11 command. Record the live acceptance evidence: + +```text +enqueue time: 2026-08-21T08:26:25.114Z +queue entry: MQE_lQDOTx8ed88AAAABAYr3Ss4AA9MRzgKZUfg +run: 32463212422 +queue head: c5855748f6d11b1be2e8222e3198e7fba98de378 +result: PR #11 MERGED +``` + +- [ ] **Step 4: Scan for contradictory queue instructions** + +Run: + +```bash +rg -n "gh pr merge|match-head-commit|enqueuePullRequest|expectedHeadOid|allow_auto_merge" \ + docs/automated-checks.md \ + docs/repository-governance.md \ + docs/maintainers/repository-design.md \ + docs/maintainers/repository-settings.md \ + docs/superpowers/specs/2026-08-21-merge-queue-design.md \ + docs/superpowers/plans/2026-08-21-merge-queue.md +``` + +Expected: every generic and content-PR queue instruction uses `enqueuePullRequest` plus `expectedHeadOid`; the only remaining `gh pr merge --match-head-commit` command is the Task 3 infrastructure merge that occurred before the queue rule was enabled. Auto-merge remains documented as disabled. + +- [ ] **Step 5: Run repository validation** + +Run: + +```bash +git diff --check +npm run check +``` + +Expected: no whitespace errors; 68 tests pass; content, Demo, link, Catalog, and Preview checks all pass. + +- [ ] **Step 6: Commit with DCO sign-off** + +```bash +git add \ + docs/automated-checks.md \ + docs/repository-governance.md \ + docs/maintainers/repository-design.md \ + docs/maintainers/repository-settings.md \ + docs/superpowers/specs/2026-08-21-merge-queue-design.md \ + docs/superpowers/plans/2026-08-21-merge-queue.md \ + docs/superpowers/plans/2026-08-21-merge-queue-enqueue-correction.md +git commit -s -m "docs: harden merge queue verification" +``` + +Expected: the commit author and `Signed-off-by` trailer are both `安陈 `. + +### Task 2: Publish the correction through the active queue + +**Files:** +- Read only: the Task 1 diff and GitHub PR state. + +**Interfaces:** +- Consumes: the signed Task 1 commit and active Ruleset `20582196`. +- Produces: a merged documentation correction validated by a real `merge_group` run. + +- [ ] **Step 1: Push and open a ready-for-review PR** + +```bash +git push -u origin codex/fix-merge-queue-enqueue +gh pr create --repo QoderAI/cloud-agents-cookbook --base main --head codex/fix-merge-queue-enqueue --title "docs: correct merge queue admission command" --body-file /private/tmp/qca-merge-queue-20260821/enqueue-correction-pr-body.md +``` + +The body file contains: + +```markdown +## Summary + +- replace queue admission through Auto-merge-dependent `gh pr merge` with GraphQL `enqueuePullRequest` +- bind every queue mutation to the reviewed head through `expectedHeadOid` +- record the live PR #11 acceptance evidence and preserve disabled Auto-merge + +## Validation + +- `git diff --check` +- `npm run check` +- live GraphQL schema and PR #11 queue acceptance + +Signed-off-by: 安陈 +``` + +Expected: a non-draft PR whose changed files are exactly the seven Task 1 documentation files. + +- [ ] **Step 2: Bind, review, and queue the correction** + +Run: + +```bash +{ + set -euo pipefail + CORRECTION_PR_NUMBER="$(gh pr view codex/fix-merge-queue-enqueue --repo QoderAI/cloud-agents-cookbook --json number --jq .number)" + CORRECTION_PR_NODE_ID="$(gh pr view "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json id --jq .id)" + CORRECTION_REVIEWED_SHA="$(gh pr view "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + gh pr view "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json state,isDraft,mergeable,mergeStateStatus,headRefOid,files,statusCheckRollup,url + gh pr diff "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --name-only + gh pr diff "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook + gh pr checks "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook + CORRECTION_CURRENT_SHA="$(gh pr view "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + test "$CORRECTION_CURRENT_SHA" = "$CORRECTION_REVIEWED_SHA" + CORRECTION_PRE_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$CORRECTION_PR_NODE_ID")" + printf '%s\n' "$CORRECTION_PRE_READBACK_JSON" | jq -e --arg sha "$CORRECTION_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null + CORRECTION_ENQUEUE_UTC="$(node -e 'process.stdout.write(new Date().toISOString())')" + set +e + CORRECTION_ENQUEUE_JSON="$(gh api graphql \ + -f query='mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: {pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid}) { mergeQueueEntry { id position } } }' \ + -F pullRequestId="$CORRECTION_PR_NODE_ID" \ + -F expectedHeadOid="$CORRECTION_REVIEWED_SHA")" + CORRECTION_MUTATION_STATUS=$? + set -e + CORRECTION_MUTATION_ENTRY_ID="$(printf '%s\n' "$CORRECTION_ENQUEUE_JSON" | jq -er '.data.enqueuePullRequest.mergeQueueEntry.id | select(type == "string" and length > 0)' 2>/dev/null)" || CORRECTION_MUTATION_ENTRY_ID="" + if ! CORRECTION_POST_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$CORRECTION_PR_NODE_ID")"; then + printf 'post-readback failed; admission state is indeterminate; do not retry blindly\n' >&2 + return 1 2>/dev/null || exit 1 + fi + printf '%s\n' "$CORRECTION_POST_READBACK_JSON" | jq -c '.data.node | {state, headRefOid, mergeQueueEntry}' + if CORRECTION_QUEUE_ENTRY_ID="$(printf '%s\n' "$CORRECTION_POST_READBACK_JSON" | jq -er --arg sha "$CORRECTION_REVIEWED_SHA" \ + 'select(.data.node.state == "OPEN" and .data.node.headRefOid == $sha) | .data.node.mergeQueueEntry.id | select(type == "string" and length > 0)')"; then + if [ "$CORRECTION_MUTATION_STATUS" -eq 0 ]; then + test -n "$CORRECTION_MUTATION_ENTRY_ID" + fi + CORRECTION_EXPECTED_ENTRY_ID="$CORRECTION_QUEUE_ENTRY_ID" + if [ -n "$CORRECTION_MUTATION_ENTRY_ID" ]; then + CORRECTION_EXPECTED_ENTRY_ID="$CORRECTION_MUTATION_ENTRY_ID" + fi + printf '%s\n' "$CORRECTION_POST_READBACK_JSON" | jq -e --arg sha "$CORRECTION_REVIEWED_SHA" --arg entry "$CORRECTION_EXPECTED_ENTRY_ID" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry.id == $entry' >/dev/null + test "$CORRECTION_QUEUE_ENTRY_ID" = "$CORRECTION_EXPECTED_ENTRY_ID" + printf 'mutation_status=%s\nenqueue_time=%s\nqueue_entry=%s\n' "$CORRECTION_MUTATION_STATUS" "$CORRECTION_ENQUEUE_UTC" "$CORRECTION_QUEUE_ENTRY_ID" + elif printf '%s\n' "$CORRECTION_POST_READBACK_JSON" | jq -e --arg sha "$CORRECTION_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null; then + printf 'confirmed not queued; stop and review before another admission attempt\n' >&2 + return 1 2>/dev/null || exit 1 + else + printf 'post-readback is indeterminate or the head changed; stop and dequeue if necessary\n' >&2 + return 1 2>/dev/null || exit 1 + fi +} +``` + +Expected: `dco`, `preview`, and `validate` pass; pre-readback proves the reviewed PR is open and unqueued. Mutation failure is indeterminate, so post-readback always runs and the mutation is never retried blindly. Admission succeeds only with the reviewed head and a non-empty readback entry ID; when the mutation response also has an ID, both IDs match. The same-head null case confirms no admission and stops; any other state stops and may require dequeue. Do not set `jump` and do not use `gh pr merge`. + +- [ ] **Step 3: Verify the merge-group run and final state** + +Run no more frequently than every 15 seconds. Bind acceptance to exactly one run created after `CORRECTION_ENQUEUE_UTC` whose queue branch belongs to this PR, then inspect that fixed run ID: + +```bash +{ + set -euo pipefail + : "${CORRECTION_PR_NUMBER:?Run Task 2 Step 2 in this shell first}" + : "${CORRECTION_ENQUEUE_UTC:?Run Task 2 Step 2 in this shell first}" + CORRECTION_QUEUE_PREFIX="gh-readonly-queue/main/pr-${CORRECTION_PR_NUMBER}-" + CORRECTION_RUN_LIST_JSON="$(gh run list --repo QoderAI/cloud-agents-cookbook --workflow merge-queue.yml --event merge_group --limit 10 --json databaseId,status,conclusion,headBranch,headSha,event,createdAt,url)" + CORRECTION_QUEUE_RUN_ID="$(printf '%s\n' "$CORRECTION_RUN_LIST_JSON" | jq -er --arg time "$CORRECTION_ENQUEUE_UTC" --arg prefix "$CORRECTION_QUEUE_PREFIX" \ + '[.[] | select(.createdAt > $time and ((.headBranch // "") | startswith($prefix)))] | select(length == 1) | .[0].databaseId | tostring | select(length > 0 and . != "null")')" + CORRECTION_QUEUE_RUN_JSON="$(gh run view "$CORRECTION_QUEUE_RUN_ID" --repo QoderAI/cloud-agents-cookbook --json databaseId,status,conclusion,jobs,headBranch,headSha,createdAt,event,url)" + printf '%s\n' "$CORRECTION_QUEUE_RUN_JSON" | jq -e --arg time "$CORRECTION_ENQUEUE_UTC" --arg prefix "$CORRECTION_QUEUE_PREFIX" ' + .event == "merge_group" + and .createdAt > $time + and ((.headBranch // "") | startswith($prefix)) + and ((.jobs | map(.name) | sort) == ["dco", "preview", "validate"]) + and (.jobs | map(.conclusion) | all(. == "success")) + ' >/dev/null + printf '%s\n' "$CORRECTION_QUEUE_RUN_JSON" | jq -c \ + '{databaseId, headBranch, headSha, createdAt, event, url, jobs: [.jobs[] | {name, conclusion}]}' +} +``` + +The non-empty/non-null run-ID extraction fails unless there is exactly one time-and-prefix match. The fixed-ID `gh run view` assertion requires event `merge_group`, a later timestamp, the PR-specific queue prefix, job names exactly `dco`, `preview`, and `validate`, and all three conclusions `success`. Only then run: + +```bash +gh pr view "$CORRECTION_PR_NUMBER" --repo QoderAI/cloud-agents-cookbook --json state,mergedAt,mergeCommit,url +git fetch origin main +git log origin/main --oneline --max-count=3 +gh api repos/QoderAI/cloud-agents-cookbook/rulesets/20582196 +gh api repos/QoderAI/cloud-agents-cookbook --jq .allow_auto_merge +``` + +Expected: the correction PR is `MERGED`, `origin/main` contains its squash commit, Ruleset `20582196` is unchanged, and repository Auto-merge remains disabled. diff --git a/docs/superpowers/plans/2026-08-21-merge-queue.md b/docs/superpowers/plans/2026-08-21-merge-queue.md index afbac52..8529622 100644 --- a/docs/superpowers/plans/2026-08-21-merge-queue.md +++ b/docs/superpowers/plans/2026-08-21-merge-queue.md @@ -17,7 +17,7 @@ - Workflows use only SHA-pinned official GitHub Actions, `permissions: contents: read`, bounded timeouts, no Secrets, no write tokens, and no Demo source execution. - Existing `pull_request` DCO behavior and `scripts/check-dco.mjs` remain unchanged and continue checking every contributor commit. - The pull-request `dco` context must remain required before and after queue enablement; the merge-group job named `dco` only attests that this admission gate passed. -- Auto-merge remains disabled. Green checks are not authorization; only a Maintainer with write access may manually queue a PR after capturing its `headRefOid`, reviewing `gh pr diff --name-only` and `gh pr diff ` in full, re-reading the head with strict equality, and using `--match-head-commit` for that reviewed SHA. Any head change requires a complete re-review. +- Auto-merge remains disabled. Green checks are not authorization; only a Maintainer with write access may manually queue a PR after capturing its node ID and `headRefOid`, reviewing `gh pr diff --name-only` and `gh pr diff ` in full, and proving by pre-readback that the reviewed head is open and unqueued. GraphQL `enqueuePullRequest` uses that SHA as `expectedHeadOid` and never sets `jump`. Post-readback always runs after the mutation attempt; success requires the reviewed head and a non-empty entry ID, equal to the mutation entry ID when one is returned. Never blindly retry an indeterminate mutation. - Never queue an external PR that changes `.github/**`, `scripts/**`, `tests/**`, root `package*.json`, `config/**`, `schema/**`, `docs/**`, or other Maintainer-owned automation/security infrastructure. Recreate it as a Maintainer-owned infrastructure PR. - Preserve the approved single-maintainer Ruleset review parameters: zero required approvals, no required Code Owner review, and no last-push approval. Preserve the empty bypass list. - The root test command is exactly `node scripts/run-tests.mjs`; the runner enumerates sorted, top-level `tests/*.test.mjs` paths without a shell glob. Demo and nested sentinels must remain unexecuted. @@ -379,7 +379,7 @@ Create a temporary PR body containing: - `npm run check` - authoritative PR-DCO and exact merge-group `head_sha` checkout contract tests - temporary-repository test-runner fixture -- infrastructure and content queue admission bound to reviewed `headRefOid` with `--match-head-commit` +- queue admission bound to reviewed `headRefOid` through GraphQL `expectedHeadOid` - real Merge Queue acceptance will run after this PR lands and Ruleset `20582196` is updated Signed-off-by: 安陈 @@ -635,6 +635,7 @@ Stop and restore immediately if any field differs. ```bash PR11_REVIEWED_SHA="$(gh pr view 11 --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" +PR11_NODE_ID="$(gh pr view 11 --repo QoderAI/cloud-agents-cookbook --json id --jq .id)" gh pr view 11 --repo QoderAI/cloud-agents-cookbook --json number,state,isDraft,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,headRefName,headRefOid,baseRefName,url gh pr diff 11 --repo QoderAI/cloud-agents-cookbook --name-only gh pr diff 11 --repo QoderAI/cloud-agents-cookbook @@ -646,13 +647,58 @@ Expected: `state=OPEN`, `isDraft=false`, `mergeable=MERGEABLE`, base `main`, no - [ ] **Step 2: Submit PR #11 to the queue** ```bash -PR11_CURRENT_SHA="$(gh pr view 11 --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" -test "$PR11_CURRENT_SHA" = "$PR11_REVIEWED_SHA" -PR11_ENQUEUE_UTC="$(node -e 'process.stdout.write(new Date().toISOString())')" -gh pr merge 11 --repo QoderAI/cloud-agents-cookbook --match-head-commit "$PR11_REVIEWED_SHA" --squash +{ + set -euo pipefail + : "${PR11_NODE_ID:?Run Step 1 in this shell first}" + : "${PR11_REVIEWED_SHA:?Run Step 1 in this shell first}" + PR11_CURRENT_SHA="$(gh pr view 11 --repo QoderAI/cloud-agents-cookbook --json headRefOid --jq .headRefOid)" + test "$PR11_CURRENT_SHA" = "$PR11_REVIEWED_SHA" + PR11_PRE_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$PR11_NODE_ID")" + printf '%s\n' "$PR11_PRE_READBACK_JSON" | jq -e --arg sha "$PR11_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null + PR11_ENQUEUE_UTC="$(node -e 'process.stdout.write(new Date().toISOString())')" + set +e + PR11_ENQUEUE_JSON="$(gh api graphql \ + -f query='mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: {pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid}) { mergeQueueEntry { id position } } }' \ + -F pullRequestId="$PR11_NODE_ID" \ + -F expectedHeadOid="$PR11_REVIEWED_SHA")" + PR11_MUTATION_STATUS=$? + set -e + PR11_MUTATION_ENTRY_ID="$(printf '%s\n' "$PR11_ENQUEUE_JSON" | jq -er '.data.enqueuePullRequest.mergeQueueEntry.id | select(type == "string" and length > 0)' 2>/dev/null)" || PR11_MUTATION_ENTRY_ID="" + if ! PR11_POST_READBACK_JSON="$(gh api graphql \ + -f query='query($id: ID!) { node(id: $id) { ... on PullRequest { state headRefOid mergeQueueEntry { id position } } } }' \ + -F id="$PR11_NODE_ID")"; then + printf 'post-readback failed; admission state is indeterminate; do not retry blindly\n' >&2 + exit 1 + fi + printf '%s\n' "$PR11_POST_READBACK_JSON" | jq -c '.data.node | {state, headRefOid, mergeQueueEntry}' + if PR11_QUEUE_ENTRY_ID="$(printf '%s\n' "$PR11_POST_READBACK_JSON" | jq -er --arg sha "$PR11_REVIEWED_SHA" \ + 'select(.data.node.state == "OPEN" and .data.node.headRefOid == $sha) | .data.node.mergeQueueEntry.id | select(type == "string" and length > 0)')"; then + if [ "$PR11_MUTATION_STATUS" -eq 0 ]; then + test -n "$PR11_MUTATION_ENTRY_ID" + fi + PR11_EXPECTED_ENTRY_ID="$PR11_QUEUE_ENTRY_ID" + if [ -n "$PR11_MUTATION_ENTRY_ID" ]; then + PR11_EXPECTED_ENTRY_ID="$PR11_MUTATION_ENTRY_ID" + fi + printf '%s\n' "$PR11_POST_READBACK_JSON" | jq -e --arg sha "$PR11_REVIEWED_SHA" --arg entry "$PR11_EXPECTED_ENTRY_ID" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry.id == $entry' >/dev/null + test "$PR11_QUEUE_ENTRY_ID" = "$PR11_EXPECTED_ENTRY_ID" + printf 'mutation_status=%s\nenqueue_time=%s\nqueue_entry=%s\n' "$PR11_MUTATION_STATUS" "$PR11_ENQUEUE_UTC" "$PR11_QUEUE_ENTRY_ID" + elif printf '%s\n' "$PR11_POST_READBACK_JSON" | jq -e --arg sha "$PR11_REVIEWED_SHA" \ + '.data.node.state == "OPEN" and .data.node.headRefOid == $sha and .data.node.mergeQueueEntry == null' >/dev/null; then + printf 'confirmed not queued; stop and review before another admission attempt\n' >&2 + exit 1 + else + printf 'post-readback is indeterminate or the head changed; stop and dequeue if necessary\n' >&2 + exit 1 + fi +} ``` -This command must be run by the write-access Maintainer only after completing Step 1. The head read occurs immediately before enqueueing and must match exactly; if it differs or `--match-head-commit` rejects it, stop and repeat Step 1 against the new head. Expected: GitHub queues the reviewed PR rather than directly merging it. Green checks alone do not authorize the command. +This controlled admission must be run by the write-access Maintainer only after completing Step 1 in the same shell. Pre-readback proves the reviewed PR is open and unqueued. Mutation failure is indeterminate, so post-readback always runs and the mutation must never be retried blindly. Admission succeeds only when post-readback has `state=OPEN`, `headRefOid=PR11_REVIEWED_SHA`, and a non-empty queue-entry ID; when the mutation response also has an ID, both IDs must match. The same head with a null entry confirms no admission and stops; any other state stops and may require dequeue. Never set `jump`. - [ ] **Step 3: Locate and monitor the real merge-group run** @@ -674,6 +720,8 @@ printf '%s\n' "$PR11_QUEUE_RUN_JSON" Expected within the configured 10-minute response window: event `merge_group`; `createdAt > PR11_ENQUEUE_UTC`; `headBranch` matches `gh-readonly-queue/main/pr-11-...`; jobs `dco`, `preview`, and `validate`; all three conclude `success`. Record `PR11_QUEUE_RUN_ID`, `headBranch`, and `headSha` as acceptance evidence. A run for another queue entry must never satisfy PR #11 acceptance. +The live acceptance recorded `PR11_ENQUEUE_UTC=2026-08-21T08:26:25.114Z`, queue entry `MQE_lQDOTx8ed88AAAABAYr3Ss4AA9MRzgKZUfg`, run `32463212422`, queue head `c5855748f6d11b1be2e8222e3198e7fba98de378`, and final PR #11 state `MERGED`. + - [ ] **Step 4: Verify automatic squash merge and final state** ```bash diff --git a/docs/superpowers/specs/2026-08-21-merge-queue-design.md b/docs/superpowers/specs/2026-08-21-merge-queue-design.md index 848cf6b..80a49ea 100644 --- a/docs/superpowers/specs/2026-08-21-merge-queue-design.md +++ b/docs/superpowers/specs/2026-08-21-merge-queue-design.md @@ -42,7 +42,7 @@ on: It grants only `contents: read`, uses no repository Secrets, never writes repository state, and never executes Demo source. A PR may be marked "Merge when ready" while its pull-request checks are still running, but it becomes an active, buildable queue entry only after satisfying the existing branch requirements. -Green checks are not authorization to publish or enqueue a change. A pull request can propose changes to the workflows and tests that produce those checks, so this single-maintainer configuration relies on a separate human admission boundary: Auto-merge remains disabled, and only a Maintainer with write access may manually add a PR to the queue. The Maintainer captures `headRefOid` in a task-specific reviewed-SHA variable before reviewing the complete `gh pr diff --name-only` output and full `gh pr diff `, reads the head again immediately before enqueueing, and requires strict equality. The queue command includes `--match-head-commit "$TASK_REVIEWED_SHA"`; any head change invalidates the review and requires a complete re-review. An external PR that touches `.github/**`, `scripts/**`, `tests/**`, root `package*.json`, `config/**`, `schema/**`, `docs/**`, or other Maintainer-owned repository automation/security infrastructure is not admitted to the queue. Such a change must be rebuilt as a Maintainer-owned infrastructure PR and reviewed separately. +Green checks are not authorization to publish or enqueue a change. A pull request can propose changes to the workflows and tests that produce those checks, so this single-maintainer configuration relies on a separate human admission boundary: Auto-merge remains disabled, and only a Maintainer with write access may manually add a PR to the queue. The Maintainer captures the PR node ID and `headRefOid` in task-specific variables before reviewing the complete `gh pr diff --name-only` output and full `gh pr diff `, reads the head again immediately before enqueueing, and requires strict equality. Pre-readback proves the reviewed PR is open and unqueued. Queue admission uses GraphQL `enqueuePullRequest(input: {pullRequestId, expectedHeadOid})` with the reviewed SHA and never sets `jump`. Mutation transport failure is indeterminate, so post-readback always runs and the operation is never retried blindly. Success requires the reviewed head and a non-empty post-readback entry ID; when the mutation response contains an entry ID, both IDs must match. A same-head null entry confirms no admission, while any other result requires stopping and potentially dequeueing. An external PR that touches `.github/**`, `scripts/**`, `tests/**`, root `package*.json`, `config/**`, `schema/**`, `docs/**`, or other Maintainer-owned repository automation/security infrastructure is not admitted to the queue. Such a change must be rebuilt as a Maintainer-owned infrastructure PR and reviewed separately. ### `dco` job @@ -104,7 +104,7 @@ Immediately before mutation, also read back repository settings and stop unless Use the already-open PR #11 as the first queue entry after confirming it remains open, mergeable, has no unresolved review requirement, and has passing PR checks. Before reviewing, capture its `headRefOid` as `PR11_REVIEWED_SHA`. Inspect `gh pr diff 11 --name-only` and `gh pr diff 11` and confirm the PR contains only the expected content-translation scope under `content/**`, with no workflow, script, test, package, configuration, Schema, or documentation changes. Re-read the head and stop unless it still equals `PR11_REVIEWED_SHA`. -Record a UTC enqueue timestamp, then add #11 through `gh pr merge 11 --match-head-commit "$PR11_REVIEWED_SHA" --squash`. Because the Ruleset requires Merge Queue, this command must enqueue the PR instead of directly merging it. Acceptance requires: +Record a UTC enqueue timestamp, then add #11 through GraphQL `enqueuePullRequest` with its node ID and `expectedHeadOid=PR11_REVIEWED_SHA`; do not set `jump`. The live acceptance returned queue entry `MQE_lQDOTx8ed88AAAABAYr3Ss4AA9MRzgKZUfg`. Acceptance requires: - a `merge_group` workflow run is created after the recorded enqueue time and its `headBranch` matches `gh-readonly-queue/main/pr-11-...`; - `dco`, `preview`, and `validate` report for the merge-group run and pass; `preview` and `validate` operate on the merge-group SHA, while `dco` records the admission invariant; @@ -117,6 +117,8 @@ No bypass option or direct push to `main` is allowed during acceptance. Only a Maintainer with write access performs the queue command. Auto-merge remains disabled; green status checks alone never authorize adding PR #11 or any future PR to the queue. +The live acceptance evidence is: enqueue time `2026-08-21T08:26:25.114Z`, queue entry `MQE_lQDOTx8ed88AAAABAYr3Ss4AA9MRzgKZUfg`, merge-group run `32463212422`, queue head `c5855748f6d11b1be2e8222e3198e7fba98de378`, and final PR #11 state `MERGED`. + ## Failure handling and rollback Before changing the Ruleset, write its complete API response and the exact update payload to a temporary local validation directory outside the repository. Do not include tokens or response headers.