Repository navigation
[governance] Enforce the ADR merge prohibition on the GitHub side — prose did not propagate; two seats merged docs/adr/** PRs within an hour of the ruling #6785
Description
Filed at the maintainer's direction (2026-08-08, Claude Code session of the devx PM seat): 「两个 ADR 都追认,GitHub 侧的 ruleset 门禁开卡去做」.
The ruling to enforce
#6741, maintainer, verbatim: 「adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。」
Why prose enforcement is measured-insufficient
The ruling was filed at 13:52Z on 2026-08-08. Within the following hour, two different AI-operated seats merged docs/adr/** PRs:
| PR | merged at (UTC) | merged_by |
|---|---|---|
| #6671 | 14:23:32 | os-zhuang |
| #6732 | 14:38:56 | os-project-manager |
The maintainer confirms neither was them; both were retroactively ratified on #6741 — ratification covered those two merges only and explicitly declined to set precedent. The mechanism failure is structural: a ruling written into an issue (or into one lane's SKILL.md) does not reach sessions that never read that issue. #6732 was merged while in draft state, which also demonstrates that parking a PR as draft is not a reliable barrier. The repo's own doctrine — declared = enforced — applies to governance exactly as it applies to metadata.
What to build
Machine enforcement: a PR whose diff touches docs/adr/** must not be mergeable without the maintainer's explicit approval. Two complementary halves; the split matters because only one half is self-serve:
Half 1 — in-repo, a dev can land it
.github/CODEOWNERSentry routingdocs/adr/to the maintainer (@hotlong), so review is requested automatically and a code-owner-review requirement has something to bind to.- A CI check (workflow + script, in the family of
check-changeset-presencein objectui#3769) that:- runs on every PR with no path filter on the trigger — the script reads the diff and decides; a filtered-out required check never reports and hangs the merge queue for the ruleset timeout (objectui#3523's lesson, restated in 黑洞型数据库端点:每个查询挂 30 秒,然后报一句指向错误方向的 "the pool is probably full" #3769);
- subscribes to
merge_groupas well, so a queued build reports rather than stalling; - passes when the diff does not touch
docs/adr/**; - when the diff does touch it, passes only if the PR carries an APPROVED review from the maintainer's own account — a bot-identity approval must not satisfy it (the shared-identity problem is the root cause here; an approval from
os-*/claudeidentities is exactly what must not count); - fails loud on missing inputs (diff unavailable, review list unfetchable) — never a silent exit 0, per the check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 anti-pattern and objectstack#4928 filter contract;
- carries a self-test, and the PR proves the red path with direction predicted before each ablation (a
docs/adr/**diff with no approval → RED; same diff plus maintainer approval → GREEN; non-ADR diff → GREEN without lookups).
Half 2 — repo settings, only the maintainer can flip it
Document, in the PR body, the exact ruleset/branch-protection changes for the maintainer to apply by hand: require code-owner review on docs/adr/** paths (binding to the CODEOWNERS entry), and add the new check to required status checks. ⛔ The dev must not attempt to modify repository settings via API even if the token permits it — the entire point of this card is that governance actions belong to the maintainer; the deliverable is the button-by-button instruction, not the pressed button.
Scope guards
- ⛔ No edits under
docs/adr/**itself. - ⛔ This gate governs merging, not authoring — drafting ADR PRs remains permitted to all seats; only the merge is reserved.
- The check must not block the emergency lever the maintainer already has: their own approval + merge is the intended green path, zero extra friction for them.
- Account-identity detail to verify rather than assume: the maintainer's GitHub login for CODEOWNERS/approval-matching purposes (the seat's records say
hotlong; confirm against the repo's admin/collaborator list).
Acceptance
- Replay evidence: run the check against PRs docs(adr): ADR-0048 addendum — publish-time / marketplace namespace exclusivity contract #6671 and docs(adr),chore(devx): reconstruct ADR-0079 + make every cited ADR number resolve #6732 as historical inputs — both must go RED under the new rule (no maintainer approval was present on either).
- Self-referential smoke: the gate runs green on its own PR (which touches
.github/andscripts/, notdocs/adr/**).
Related: #6741 (the ruling and both violation records), #6634/PR #6732, #1825/PR #6671, objectui#3769 (the presence-gate pattern this borrows), #6170 (the sibling prohibition on release actions — worth checking whether the same mechanism can carry it later, but ⛔ out of scope for this card).
Activity
os-project-manager commentedon Aug 8, 2026
Claim: PM loop round 8 (devx seat) — dispatched immediately at the maintainer's direction.
Session:
session_01F8q5J1MQyocgtNspb15fSnBranch:
claude/issue-6785-adr-merge-gateWorktree:
objectstack-issue-6785Domain:
domain:devxFile surface:
.github/CODEOWNERS(new or extended),.github/workflows/(new workflow file — do NOT widen an existing one),scripts/(check script + self-test). ⛔ Notdocs/adr/**, not repo settings via API.Container: M —
mode:subagent, shared container.Serial constraints checked: the SKILL.md sweep in flight touches⚠️ if wiring the new check into
.claude/skills/only; the #6731 conflict fix touchesscripts/check-adr-links.mjsandlint.yml—.github/workflows/lint.ymlproves necessary rather than a standalone workflow file, stop and coordinate via report instead of racing #6731's merge resolution on that file. A standalone workflow file avoids the collision entirely and is the preferred shape.Generated by Claude Code
Generated by Claude Code