Skip to content

chore: version packages (rc) #2352

chore: version packages (rc)

chore: version packages (rc) #2352

name: ADR Merge Approval
# Machine enforcement of the 2026-08-12 ruling (maintainer, verbatim):
# 「门禁改成只要求「APPROVED review 存在」」/「不要指定具体的人」
#
# A PR whose diff touches docs/adr/** must carry an APPROVED review before it
# is mergeable. The gate does NOT check WHO approved: any account with review
# rights on this repo — including an AI seat — satisfies it, which is the
# accepted cost of the ruling and is stated in full in the guarantee table at
# the head of scripts/check-adr-merge-approval.mjs. The approval must be
# current: a later CHANGES_REQUESTED or DISMISSED revokes it.
#
# Second clause (#8012): the PR must ALSO not be sitting on an armed
# auto-merge. Arming is not merging — it is a standing instruction to merge
# later — so no gate used to fire on it, and an AI seat armed one on a live
# docs/adr/** PR at 11:15Z on 2026-08-12. The next approving review would have
# turned this check green and GitHub would have merged the PR unattended, every
# check reporting success. Armed is now RED whether or not the PR is approved:
# the approved case is the only one in which the unattended merge actually
# happens, so a rule that fired only on the unapproved case would change no
# verdict at all. This is how 「人工合并」 becomes machine-enforced — the gate is
# green only while the merge still requires a person to press the button.
# It is not a deadlock: anyone can disable auto-merge, and doing so re-runs
# this workflow (see the trigger below) and clears the check by itself.
#
# This supersedes the account-identity rule this workflow used to describe
# (#6741 「adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。」, enforced by
# matching the maintainer's numeric account id). That proxy became
# unsatisfiable once cloud sessions began authoring PRs under the maintainer's
# own account, since GitHub forbids self-approval (#8161). #6741's two halves
# survive as convention, not as anything this workflow can measure.
#
# Prose enforcement was measured insufficient the day #6741 landed — two
# different AI-operated seats merged docs/adr/** PRs within the following hour
# (#6671, #6732; the full record and both replays live in
# scripts/check-adr-merge-approval.mjs and its --self-test). Both had ZERO
# reviews of any kind, so both stay red under the widened rule too. Drafting
# ADR PRs stays open to every seat; only the merge is gated.
#
# Deliberately NO `paths` filter, on either trigger — the same choice
# changeset-presence.yml made in objectui (#3769) and for the same reason
# (objectui#3523): a path filter skips the WHOLE workflow, so the check
# context is never CREATED on a non-matching PR, and a required context that
# never reports leaves the PR pending in the merge queue until the ruleset's
# 60-minute timeout. This gate reports on every PR instead: the script reads
# the diff and decides, and a diff that does not touch docs/adr/** passes
# with zero API lookups.
#
# This file is one of the enforcement surfaces .github/CODEOWNERS routes to
# the maintainer: weakening or removing the gate is itself a governance
# change and carries the same review requirement the gate enforces.
on:
pull_request:
branches: [main]
# `opened`/`synchronize`/`reopened` are GitHub's default set and are
# RESTATED here because naming any `types:` replaces that default — dropping
# one would mean no check run on that activity, and a required context that
# never reports is permanent pending, not a skip (the same failure mode the
# `paths:` note below describes).
#
# The two auto-merge activities are the #8012 addition, and they are what
# make the arming clause observable at all:
# auto_merge_enabled — arming is otherwise a silent state change that
# fires no event this workflow listens to, so the gate would keep
# reporting its last green while the PR sat armed.
# auto_merge_disabled — the zero-friction exit, and the reason the arming
# clause is not a deadlock: disarming re-runs the gate, which then goes
# green on its own with no push, no re-review and no admin action.
types: [opened, synchronize, reopened, auto_merge_enabled, auto_merge_disabled]
# An approval does not fire `pull_request`, so without this trigger the
# failed check would sit red after the approval lands until someone re-ran
# it by hand. Subscribing to reviews makes the approval itself re-run the
# gate — the zero-friction green path the card requires. `pull-requests:
# read` below covers listing reviews from ANY account, so the widened rule
# needs no extra permission or token scope.
# (On non-ADR PRs a review re-runs the cheap clean path; harmless.)
pull_request_review:
types: [submitted, edited, dismissed]
# Merge queue (objectui#3523; see ci.yml's trigger block): a required
# context must report on queue builds or the queue stalls. On this event
# the script resolves the PR from the gh-readonly-queue ref (falling back
# to the head commit subject, then the commit's associated PRs) and fails
# loud if it cannot — never a silent skip.
merge_group:
types: [checks_requested]
concurrency:
group: adr-merge-approval-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
# The gated path lists the PR's reviews and reads the PR itself (for
# `auto_merge`) via the REST API. `read` covers both — the #8012 clause needs
# no new permission and no new token scope. The PR object is read LIVE rather
# than taken from the event payload: `auto_merge` is not documented as a
# member of the pull_request object carried by `pull_request_review`, and a
# projection that merely lacks the key would read as "not armed". The script
# refuses such a payload instead; see its header.
pull-requests: read
jobs:
adr-merge-approval:
# ⛔ Do NOT rename this job. Its name IS the required status-context string
# in the `main` ruleset (#7022), and it is registered under that exact
# spelling in scripts/check-required-contexts.mjs — renaming it here alone
# leaves the ruleset waiting for a context that never reports, which hangs
# the merge queue until the 60-minute timeout. The word "maintainer" now
# over-claims (see this file's header: any approver counts); correcting it
# is a settings action nobody in CI can perform, so it is tracked as
# follow-up work rather than done here.
name: ADR maintainer approval
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
# The gate diffs against the merge base with the target branch (or
# the merge group's base); a depth-1 clone has no merge base, and an
# unresolvable base is a hard failure in the script, never a skip.
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22'
# A checkout, setup-node, and one `node` call over `git diff` — no
# install, no build. The self-test runs first (repo convention), then
# the gate. GITHUB_TOKEN is only read on the gated path (a docs/adr/**
# diff needs the PR's review list and arming state); the clean path does
# zero lookups.
- name: Require an APPROVED review and no armed auto-merge on docs/adr/** diffs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/check-adr-merge-approval.mjs --self-test && node scripts/check-adr-merge-approval.mjs