diff --git a/.github/protection/main.required-checks.json b/.github/protection/main.required-checks.json index 319999f32da..7db419c94e4 100644 --- a/.github/protection/main.required-checks.json +++ b/.github/protection/main.required-checks.json @@ -2,13 +2,14 @@ "branch": "main", "required_status_checks": [ "meta-gate", - "CI Core Gate ✅", + "CI Core Gate \u2705", "Unit Tests", "gate", "Release Readiness Gate", "SOC Controls", "test (20.x)", - "Workflow Validity Check" + "Workflow Validity Check", + "merge-queue / merge-queue-critical" ], "strict": true, "require_pull_request": true, diff --git a/.github/workflows/merge-group-heavy.yml b/.github/workflows/merge-group-heavy.yml index 5552282c2d5..28f8aca71cb 100644 --- a/.github/workflows/merge-group-heavy.yml +++ b/.github/workflows/merge-group-heavy.yml @@ -1,11 +1,10 @@ name: merge-group-heavy on: - merge_group: + workflow_dispatch: jobs: heavy: - if: github.event_name == 'merge_group' runs-on: ubuntu-latest timeout-minutes: 20 steps: diff --git a/.github/workflows/merge-queue-feeder.yml b/.github/workflows/merge-queue-feeder.yml index 164a18e3a2f..40ac09330cc 100644 --- a/.github/workflows/merge-queue-feeder.yml +++ b/.github/workflows/merge-queue-feeder.yml @@ -1,13 +1,10 @@ name: Merge Queue Feeder on: - schedule: - - cron: "*/2 * * * *" workflow_dispatch: permissions: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - contents: write + contents: read pull-requests: write env: @@ -16,43 +13,8 @@ env: jobs: feed-queue: runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install GitHub CLI - uses: cli/cli-action@v2 - - - name: Fetch eligible PRs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Intentional no-op run: | - gh pr list \ - --label "queue:merge-now" \ - --state open \ - --json number \ - > mergeable.json - - - name: Determine queue depth - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr list \ - --search "is:queued" \ - --json number \ - > queue.json - - - name: Feed merge queue - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - TARGET=20 - CURRENT=$(jq length queue.json) - - if [ "$CURRENT" -lt "$TARGET" ]; then - NEEDED=$((TARGET-CURRENT)) - jq -r '.[].number' mergeable.json | head -n $NEEDED | while read pr; do - gh pr merge $pr --auto --squash - done - fi + echo "Native GitHub merge queue is authoritative." + echo "Manual feeder is retained for emergency one-off operation only." diff --git a/.github/workflows/merge-queue.yml b/.github/workflows/merge-queue.yml index e1b9c2656ea..5fe4ac865c1 100644 --- a/.github/workflows/merge-queue.yml +++ b/.github/workflows/merge-queue.yml @@ -1,58 +1,137 @@ -name: Merge Queue CI +name: merge-queue on: merge_group: types: [checks_requested] + pull_request: + types: [enqueued, dequeued, synchronize, reopened, ready_for_review] + branches: [main] + +permissions: + contents: read + pull-requests: write concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: merge-queue-main + cancel-in-progress: false jobs: - merge-heavy: - name: Heavy Checks + queue-visibility: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Publish queue state comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = context.payload.pull_request; + if (!pr) { + core.info('No pull_request payload, skipping.'); + return; + } + + const marker = ''; + const state = context.payload.action; + const branch = 'main'; + + let queueSize = 0; + let positionText = 'Not currently in queue'; + let reason = 'No dequeue reason reported.'; + + try { + const queue = await github.request('GET /repos/{owner}/{repo}/merge-queue', { + owner: context.repo.owner, + repo: context.repo.repo, + branch, + }); + + const entries = queue.data?.entries ?? []; + queueSize = entries.length; + const index = entries.findIndex((entry) => entry.number === pr.number); + if (index >= 0) { + positionText = `${index + 1} of ${queueSize}`; + } + } catch (error) { + core.warning(`Unable to read merge queue: ${error.message}`); + } + + if (state === 'dequeued') { + reason = context.payload.reason || 'Validation failed or PR no longer mergeable.'; + } + + const body = [ + marker, + `### Merge Queue Status`, + `- **PR:** #${pr.number}`, + `- **Event:** ${state}`, + `- **Queue position:** ${positionText}`, + `- **Queue depth:** ${queueSize}`, + `- **Removal reason:** ${reason}`, + '', + '_Queue is FIFO and each candidate is validated on latest `main` through `merge_group` before merge._', + ].join('\n'); + + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + per_page: 100, + }); + + const existing = comments.find((comment) => comment.body?.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body, + }); + } + + merge-queue-critical: + if: github.event_name == 'merge_group' runs-on: ubuntu-latest + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 -<<<<<<< HEAD + - name: Checkout merge candidate + uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - - uses: pnpm/action-setup@v4 -======= - - uses: pnpm/action-setup@v3 ->>>>>>> pr-21884 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 with: version: 9.15.4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - cache: 'pnpm' - - name: Check for PR Validation Artifact - id: validate - uses: actions/download-artifact@v4 + - name: Setup Node + uses: actions/setup-node@v4 with: - name: ci-validation-${{ github.event.merge_group.head_sha }} - path: . - continue-on-error: true - - - name: Install - if: steps.validate.outcome != 'success' - run: pnpm install - - - name: Build - if: steps.validate.outcome != 'success' - run: pnpm -r build || echo "Build skipped or no scripts" + node-version: 24 + cache: pnpm - - name: Test - if: steps.validate.outcome != 'success' - run: pnpm -r test || echo "Test skipped or no scripts" + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Audit - if: steps.validate.outcome != 'success' - run: pnpm audit || true + - name: Critical validation suite + run: | + pnpm run lint --if-present + pnpm run typecheck --if-present + pnpm run test --if-present - - name: Generate SBOM - if: steps.validate.outcome != 'success' - run: node scripts/generate_sbom.mjs || echo "SBOM generation failed" + - name: Publish merge-group summary + if: always() + run: | + echo "### Merge Queue Validation" >> "$GITHUB_STEP_SUMMARY" + echo "- Merge group SHA: ${{ github.event.merge_group.head_sha }}" >> "$GITHUB_STEP_SUMMARY" + echo "- Base ref: ${{ github.event.merge_group.base_ref }}" >> "$GITHUB_STEP_SUMMARY" + echo "- Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/merge-train-autopilot.yml b/.github/workflows/merge-train-autopilot.yml index 3c80f4a38b1..063e2863c0d 100644 --- a/.github/workflows/merge-train-autopilot.yml +++ b/.github/workflows/merge-train-autopilot.yml @@ -1,8 +1,6 @@ name: Merge-train Autopilot on: - schedule: - - cron: '*/30 * * * *' workflow_dispatch: inputs: batch_size: @@ -19,7 +17,6 @@ on: default: false permissions: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true contents: read pull-requests: write @@ -36,34 +33,6 @@ jobs: fetch-tags: true - name: Run Autopilot - id: drain - run: | - # Use inputs if provided, otherwise defaults - BATCH="${{ github.event.inputs.batch_size || '10' }}" - MAXQ="${{ github.event.inputs.max_queue || '50' }}" - DRY="${{ github.event.inputs.dry_run }}" - - ARGS="--batch-size $BATCH --max-queue $MAXQ" - if [ "$DRY" == "true" ]; then ARGS="$ARGS --dry-run"; fi - - # If triggered by schedule, use defaults explicitly - if [ "${{ github.event_name }}" == "schedule" ]; then - ARGS="--batch-size 10 --max-queue 50" - fi - - chmod +x .github/scripts/merge-train-autopilot.sh - ./.github/scripts/merge-train-autopilot.sh $ARGS | tee autopilot.log - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Step Summary - if: always() run: | - echo "### Merge-train Autopilot Report" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - if [ -f autopilot.log ]; then - cat autopilot.log >> $GITHUB_STEP_SUMMARY - else - echo "Error: autopilot.log not found." >> $GITHUB_STEP_SUMMARY - fi - echo '```' >> $GITHUB_STEP_SUMMARY + echo "Autopilot now manual-only to prevent unsupervised queue churn." + echo "Use GitHub native merge queue for routine sequencing."