molecule-core/.github/workflows/check-merge-group-trigger.yml
devops-engineer aa6458b42d
Some checks failed
Check merge_group trigger on required workflows / Required workflows have merge_group trigger (pull_request) Successful in 9s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Successful in 8s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Successful in 10s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 19s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 21s
CI / Detect changes (pull_request) Successful in 31s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 20s
Retarget main PRs to staging / Retarget to staging (pull_request) Has been skipped
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 16s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 19s
Harness Replays / detect-changes (pull_request) Successful in 21s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 23s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 24s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 26s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 30s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 24s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 37s
Harness Replays / Harness Replays (pull_request) Successful in 23s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 15s
CI / Python Lint & Test (pull_request) Successful in 8m27s
CI / Canvas (Next.js) (pull_request) Failing after 9m59s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Successful in 12m57s
fix(ci): replace gh api REST passthroughs with Gitea-compatible shapes (#75 class D)
Part of the post-#66 sweep to remove `gh` CLI dependencies that fail
silently against Gitea (which exposes /api/v1 only — no GraphQL → 405,
no /api/v3 → 404). Class D covers `gh api` REST passthroughs that
either have a Gitea v1 equivalent at a different path/shape or no
equivalent at all.

Three files in this class, each with a different fix shape because
each underlying Gitea capability is different:

`auto-promote-on-e2e.yml` (compute SHA ancestry):
  Old: `gh api repos/.../compare/A...B` returning `.status`
       (ahead|behind|identical|diverged).
  Gitea: `/api/v1/repos/.../compare/A...B` accepts only branch / tag
       refs — full commit SHAs return `BaseNotExist`. So even a
       "translate the URL" rewrite would fail. Verified empirically
       2026-05-07: branches/tags work, SHAs don't.
  Fix:  Add `actions/checkout@v6 fetch-depth=200` + use `git merge-
       base --is-ancestor` locally. Exact same four-bucket semantics
       (ahead | behind | diverged | error), zero cross-host API
       dependency. Same pattern PR #66 used for auto-sync. The 200-
       commit depth comfortably covers any realistic divergence
       between :latest and a candidate retag (promotes are minutes
       apart, not hundreds of commits).

`ci.yml` (canvas-deploy-reminder commit comment):
  Old: `gh api -X POST repos/.../commits/{sha}/comments` posting a
       deploy-reminder body for the operator.
  Gitea: NO commit-comments endpoint exists — `/repos/.../commits/
       {sha}/comments` returns 404 (verified 2026-05-07). Gitea only
       exposes `/commits/{sha}/statuses` for commit-level surface,
       which is the wrong shape for a free-form reminder.
  Fix:  Drop the API call. Write the reminder body to
       `$GITHUB_STEP_SUMMARY` instead. The reminder is entirely
       operator-facing and is just as discoverable on the run summary
       page (which an operator naturally lands on when they need to
       action a deploy). Commit comments were a stale UI artefact of
       the GitHub era, not a load-bearing automation surface.
  Permission: drop `contents: write` (no longer needed) → `read`,
       smallest scope per least-privilege.

`check-merge-group-trigger.yml` (merge_group: trigger linter):
  Old: `gh api .../branches/staging/protection/required_status_checks`
       reading the contexts list, then walking workflow files.
  Gitea: branch-protection API is at /api/v1/repos/.../branch_
       protections/{name} (different path) with `status_check_
       contexts` (different field name) — but the entire workflow
       only existed to lint that workflows producing a required
       check declare a `merge_group:` trigger, which is needed
       because GitHub's merge queue dead-locks at AWAITING_CHECKS
       when the trigger is missing. Gitea has NO merge queue, NO
       gh-readonly-queue/... ref shape, NO merge_group event
       semantics. The dead-lock pattern this linter catches cannot
       occur on Gitea by construction.
  Fix:  Convert to no-op stub (same pattern as the CodeQL stub
       landed in PR #51). Workflow name + trigger surface preserved
       so any external referrer (none confirmed via the 2026-05-07
       branch-protection audit) keeps resolving. Re-enable path
       documented in the file header for if/when Gitea grows a
       merge queue.

curl invocation pattern: `curl --fail-with-body -sS` (NOT `-fsS` —
the two short-fail flags are mutually exclusive in modern curl).

Token model: workflows continue to use act_runner's GITHUB_TOKEN
where they still need API access (`auto-promote-on-e2e.yml`'s
checkout uses the runner's default token; `ci.yml` no longer needs
any API auth for the deploy-reminder step; `check-merge-group-
trigger.yml` no longer makes any API calls).

Verification:
- YAML syntax validates for all three files.
- Live curl against Gitea confirms `/compare/A...B` accepts branch
  refs (200, total_commits=N) and refuses full SHAs (404,
  BaseNotExist) — justifying the local-git approach.
- `/repos/.../commits/{sha}/comments` confirmed 404 on Gitea.
- `git merge-base --is-ancestor` exit-code semantics match the
  GitHub compare API status semantics exactly: ahead = current is
  ancestor of target; behind = target is ancestor of current;
  diverged = neither.

Closes part of #75. Class A landed in #80; class F (gh run list →
no Gitea workflow-runs API at all) lands in a separate PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:35:59 -07:00

107 lines
4.5 KiB
YAML

name: Check merge_group trigger on required workflows
# Stub workflow — Gitea Actions has no merge queue (post-2026-05-06
# SCM migration off GitHub).
#
# Why this is a stub, not a real linter:
#
# 1. The original workflow existed exclusively to lint that any
# workflow producing a required status check on the staging branch
# also declares a `merge_group:` trigger. Without that trigger,
# GitHub's merge queue dead-locks at AWAITING_CHECKS forever
# because the required check can't fire on `gh-readonly-queue/...`
# refs (a GitHub-merge-queue-specific ref shape).
#
# 2. Gitea has no merge queue. There is no `gh-readonly-queue/...`
# ref shape on Gitea. There is no `merge_group` event type in the
# Gitea Actions trigger surface. The deadlock pattern this linter
# catches CANNOT occur on the new SCM, by construction.
#
# 3. The original linter additionally called
# `gh api repos/.../branches/staging/protection/required_status_checks`,
# which on Gitea returns 404 — Gitea's branch-protection API lives
# at `/repos/{owner}/{repo}/branch_protections/{name}` (different
# path), and the response shape uses `status_check_contexts`
# instead of `contexts`. So the linter would have failed to fetch
# the required-check list even if Gitea had a merge queue.
#
# What this stub preserves:
#
# - Workflow name `Check merge_group trigger on required workflows`
# is unchanged so any external surface that referenced it (none
# confirmed via branch-protection audit 2026-05-07) keeps resolving.
# - Trigger surface (pull_request, push, merge_group). The
# merge_group: trigger is a no-op on Gitea (the event never fires)
# but kept declared so the workflow file itself stays a faithful
# subscriber to anything that DID fire it on the GitHub side, in
# case we ever migrate back.
#
# Re-enabling real linting (future work):
#
# - If/when Gitea grows a merge queue (none on the roadmap as of
# 2026-05-07), reinstate the linter using
# `/api/v1/repos/.../branch_protections/{branch}` to read the
# required-check list (.status_check_contexts), then walk
# workflow files for the appropriate Gitea-shape trigger.
# - Alternative invariant worth checking on Gitea today: that any
# workflow producing a status_check_context declared in branch
# protection has at least one non-skipped path through to a
# terminal step (the "required check name needs a job that always
# runs" invariant from saved memory
# feedback_branch_protection_check_name_parity). That linter
# exists separately as `branch-protection-drift.yml` so this stub
# doesn't need to also cover it.
#
# Until merge-queue semantics return to this fleet, the stub keeps
# the workflow surface visible in Gitea's Actions UI so the next
# operator notices it's a stub instead of a missing surface, and
# emits success so the surrounding chain isn't artificially red.
#
# Issue tracking: post-#66 sweep tracked in #75 (class D — `gh api`
# REST passthroughs that have no Gitea v1 equivalent).
on:
pull_request:
paths:
- '.github/workflows/**.yml'
- '.github/workflows/**.yaml'
push:
branches: [staging, main]
paths:
- '.github/workflows/**.yml'
- '.github/workflows/**.yaml'
# Self-listen on merge_group is structurally a no-op on Gitea; kept
# declared for parity with the GitHub-era contract.
merge_group:
types: [checks_requested]
jobs:
check:
name: Required workflows have merge_group trigger
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Stub — Gitea has no merge queue
shell: bash
run: |
set -euo pipefail
cat <<'EOF'
merge_group-trigger linter is a no-op on Gitea Actions.
Reason: Gitea has no merge queue. The dead-lock pattern this
linter catches (required check missing merge_group: trigger
→ AWAITING_CHECKS forever on gh-readonly-queue refs) cannot
occur on this SCM by construction.
Adjacent live linters that DO catch real Gitea-shape failure
modes:
- branch-protection-drift.yml — guards the protection rules
- check-name parity gate (PR #56, issue #144) — guards
that path-filtered workflows still emit required checks
Issue: #75 (post-#66 gh-CLI sweep, class D — gh api REST
calls that have no Gitea v1 equivalent).
EOF
echo "::notice::merge_group-trigger linter is a stub on Gitea — see workflow file header for context."