Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# .github/CODEOWNERS — governance routing (#6785; ruling #6741).
#
# The ruling this file helps enforce (maintainer, verbatim):
# 「adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。」
#
# Routing docs/adr/ to the maintainer does two things: every PR touching an
# ADR requests his review automatically, and a ruleset / branch-protection
# rule "require review from Code Owners" has an entry to bind to (Half 2 of
# #6785 — a repo-settings change only the maintainer can apply; the
# button-by-button instructions are in the PR that added this file). The CI
# half of the same enforcement is .github/workflows/adr-merge-approval.yml +
# scripts/check-adr-merge-approval.mjs.
#
# `hotlong` is verified against the repo history, not assumed: 2,153 commits
# on main are authored as 50353452+hotlong@users.noreply.github.com — GitHub's
# {id}+{login} noreply address form ties the login to the account.
#
# The enforcement chain itself is routed the same way, deliberately: a PR
# that edits the gate script, its workflow, or this file is a governance
# change — without these entries the docs/adr/ rule could be lifted by
# editing the enforcers instead of the ADRs, and the CI check alone must stay
# scoped to docs/adr/** (its own PR has to pass the not-an-ADR-diff path).

/docs/adr/ @hotlong
/.github/CODEOWNERS @hotlong
/.github/workflows/adr-merge-approval.yml @hotlong
/scripts/check-adr-merge-approval.mjs @hotlong
83 changes: 83 additions & 0 deletions .github/workflows/adr-merge-approval.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: ADR Merge Approval

# Machine enforcement of the #6741 ruling (maintainer, verbatim):
# 「adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。」
#
# A PR whose diff touches docs/adr/** must carry an APPROVED review from the
# maintainer's OWN account before it is mergeable; approvals from the shared
# bot/agent identities deliberately do not count. Prose enforcement was
# measured insufficient the day the ruling 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). Drafting ADR PRs stays open to every seat; only the
# merge is reserved, and the maintainer's own approval + merge is the intended
# zero-extra-friction green path.
#
# 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]
# An approval does not fire `pull_request`, so without this trigger the
# failed check would sit red after the maintainer approves until someone
# re-ran it by hand. Subscribing to reviews makes the maintainer's approval
# itself re-run the gate — the zero-friction green path the card requires.
# (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 via the REST API.
pull-requests: read

jobs:
adr-merge-approval:
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); the clean path does zero lookups.
- name: Require the maintainer's own approval 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"check:skill-compatibility": "node scripts/check-skill-compatibility-version.mjs --self-test && node scripts/check-skill-compatibility-version.mjs",
"check:adr-anchors": "node scripts/check-adr-anchors.mjs --self-test && node scripts/check-adr-anchors.mjs",
"check:adr-links": "node scripts/check-adr-links.mjs --self-test && node scripts/check-adr-links.mjs",
"check:adr-merge-approval": "node scripts/check-adr-merge-approval.mjs --self-test && node scripts/check-adr-merge-approval.mjs",
"check:platform-checklist": "node scripts/checklist-select.mjs --self-test && node scripts/check-platform-checklist.mjs",
"check:org-identifier": "node scripts/check-org-identifier.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
Expand Down
Loading
Loading