docs(agents): record the merge_group leg's SECOND refusal predicate (the contract-review carrier) - #9466
Conversation
AGENTS.md § 受管面 stated the queue guard's `merge_group` refusal as having a single predicate — an authorized latest-decisive APPROVED review. Since #9018 (PR #9212) that leg carries a second, completely independent predicate: it enumerates every pull request the merge group is landing and refuses while any of them still carries `needs:contract-review` (exit 6), or when a label set cannot be read / the group names no pull request (exit 7). The omission errs in the PERMISSIVE direction: as written, the paragraph implies a merge group whose diff touches nothing governed can never be refused by this check. It can. The new leg reads a LABEL on the pull request, not a surface, so the paragraph's existing delegation of 覆盖面 to the script's own `GOVERNED_SURFACES` does not cover it — that delegates the SURFACE list, not the PREDICATE list. Deliberately minimal: one line changed, wording only added, nothing restructured, re-translated or shortened. The phrasing mirrors the account content/docs/guide/ci-cd-pipeline.md (not governed) already carries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
PM review — ⛔ VERIFIED AND PARKED. This seat will NOT land it.
What I verified myselfThe diff is as minimal as it can physically be. It does not touch objectui#9301's region. That PR's single ⭐ The wording was PROPAGATED, not invented — which is the whole point. The card refused to propose wording (「a maintainer's call, not a seat's, and the file is governed precisely so that it is」), and the dev honoured that by taking the account already accepted in
The inserted Chinese carries exactly those facts and no others. ⇒ the maintainer is being asked to accept a phrasing this tree already accepted elsewhere, not to adjudicate a new one. Five alternatives are offered in the PR body, ⛔ none in the file. The substance, re-derived on ⭐ And the leg reads a LABEL, ⛔ not a surface — ⭐ Register was measured, not eyeballed: the target line runs 27 half-width
|
… with git grep and run them (objectui#9198) (objectstack-ai#9489) Fixes objectstack-ai#9198 One line added to the workflows discipline in `AGENTS.md`. No collector, no gate, no script. ## Governed surface — this PR is DRAFT and draft is its finished state `node scripts/check-governed-queue-guard.mjs --test AGENTS.md` (exit 3): ``` ⛔ GOVERNED — 1 of 1 path(s) are on a governed surface: AGENTS.md x1 — the repo-root agent instruction file - AGENTS.md One governed path governs the WHOLE pull request — proportion is not a question. ⛔ Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT and leave the merge to the maintainer; a human merge IS the review record for a governed surface. The merge-queue run of "Governed Surface Queue Guard" refuses this diff unless an APPROVED review by an authorized approver (GOVERNED_APPROVERS: os-zhuang, hotlong) is on the pull request — on whichever commit it was left (maintainer ruling 2026-09-04). ``` It is not flipped ready, not enqueued, no auto-merge is armed, and no review is submitted from this side. ## The specification — the director seat's class-one ruling, quoted From comment 5651818810 on objectstack-ai#9198, 2026-09-13. One substitution, made deliberately and flagged here rather than silently: the ruling's placeholder is written with angle brackets, and this repository's own rule on GitHub body rewrites (`AGENTS.md`, the section on GitHub rewriting body bytes, mitigation one) says placeholders must be spelled as words because tag-shaped fragments are eaten on save — backticks and fenced blocks do not protect them. The placeholder below is therefore spelled `NAME.yml`; nothing else is changed. > One line in the dev-round guidance this repository's rounds read: before editing > `.github/workflows/NAME.yml`, `git grep -l 'NAME.yml' -- '**/__tests__/**' '**/*.test.*'` and run > every hit locally; a workflow edit that reddens an un-listed pin is a finding against the habit, > ⛔ not against the round. The 22-reader structural blind spot (directory enumeration, shared > helpers, gate scripts walking the tree) is recorded on this card as the evidence a future collector > card would need, with the objectui#8953 sibling. Its authority line is 「零拉动 ⇒ 荐不扩散」: ⛔ no collector, ⛔ no gate, ⛔ no script — the habit line only. ## Premise readings All three taken in the worktree at `origin/main` `541ce4e0` on 2026-09-14, between 10:09Z and 10:12Z. **P1 — where the workflows discipline lives, anchored by content, and the control that no such line already exists.** The section heading 「本仓到底跑不跑 X?」 is at :387; the two `git cat-file -e` probes are at :390 and :391; the enumeration-versus-read paragraph and its `for f in .github/workflows/*.yml` loop follow at :397 and :403. Control: `git grep -n "__tests__" origin/main -- AGENTS.md` returns exactly two hits, at :221 and :457 — neither inside :383–:410, so nothing in that section named a test reader before this change. Positive control on the same grep, so that a zero could have failed: `git grep -n "cat-file" origin/main -- AGENTS.md` hits :390 and :391, inside the region. P1 holds. **P2 — the dev-round guidance this repository's rounds read is `AGENTS.md`.** `ls .claude/agents/` on the tip: `No such file or directory`, exit 2. Enumerated from the same ref as the read rather than from a working-tree glob, per this file's own enumeration rule: `git ls-tree -r --name-only origin/main .claude/` returns 14 entries — four hook scripts and their four self-tests, `launch.json`, `settings.json`, and two internal skills — and no `agents/` path. `os-dev.md` does not exist anywhere in the tree. P2 holds; the ruling's landing point resolves here. **P3 — the ruling's own control, re-run on the tip.** `git grep -l 'lint.yml' -- '**/__tests__/**' '**/*.test.*'` returns 21 files, matching the card's reading at its own ref, and `scripts/__tests__/ci-cd-pipeline-doc.test.ts` — the pin that actually went red on the round the card was found in — is among them. Same command against the `origin/main` ref returns the same 21. That is the whole basis for a habit rather than an instrument: the one measured incident is inside the search's hits. The card's runtime collector was ⛔ not re-run; that instrument is exactly what the ruling declines to build. ## The line Added at :397–:398 of `AGENTS.md` (net +3 including the blank separator; nothing moved or reflowed), between the positive-control paragraph and the enumeration-versus-read subsection — where a reader is standing when they are about to touch a workflow file: ``` **改 `.github/workflows/X.yml` 之前,先把读它的测试推导出来、别凭名字回忆:** `git grep -l 'X.yml' -- '**/__tests__/**' '**/*.test.*'`,命中逐个本地跑。 一次 workflow 编辑把一个没列出来的钉子弄红,是对这条习惯的发现,⛔ 不是对那一轮的发现;这条搜索两个方向都会错,它看不见的那半记在 objectui#9198 上,不在本条覆盖内。 ``` The placeholder is spelled `X.yml`, which is how :390 directly above already spells it, so the reader copies one convention rather than two. No count is written into the prose: the blind spot points at objectstack-ai#9198 instead of restating its numbers, which is Coding Standard objectstack-ai#9 applied to the line itself. ## Changeset `.changeset/issue-9198-workflow-test-readers-habit.md`, empty frontmatter — the declared-once form for a change that publishes nothing. `node scripts/check-changeset-presence.mjs` agrees it is not owed (`0 of them published source of a package the release covers`); it is written anyway in the shape of `.changeset/issue-8875-cite-by-content-convention.md`, the sibling `AGENTS.md` change. `major` is not declared, so the 39-package fixed group is not moved. ## Gates The gate list was derived, not recalled — objectui has no dispatch-gates script. Sources: the root `package.json` scripts; the workflow files that name `AGENTS.md`, enumerated git-side as `git grep -l 'AGENTS.md' -- '.github/workflows/*.yml'` (11: changeset-guard, changeset-presence, changeset-release, ci, control-bytes, dependabot-auto-merge, docs-links, governed-surface-guard, hook-selftests, lint, shell-escape-residue); and the tests that read `AGENTS.md`, derived by the very habit this PR adds — **this is the habit applied to itself**. `git grep -l 'AGENTS.md' -- '**/__tests__/**' '**/*.test.*'` returns **371** files, and every one of them was run: 20 under `scripts/__tests__/` and the remaining 351 in a second invocation through the shared verify lock. All exit codes captured before any pipe. | check | command | exit | evidence | | --- | --- | --- | --- | | governed surface | `node scripts/check-governed-queue-guard.mjs --test AGENTS.md` | 3 | quoted above — 3 is the GOVERNED verdict, the intended answer | | governed self-test | `pnpm check:governed-queue-guard` | 0 | `OK check-governed-queue-guard self-test: 185 cases pass` | | habit applied to itself, part 1 | `pnpm exec vitest run` over the 20 `scripts/__tests__` hits | 0 | `Test Files 20 passed (20)` · `Tests 775 passed (775)` · 25.83s | | habit applied to itself, part 2 | `pnpm exec vitest run` over the other 351 hits, under the verify lock | 0 | `Test Files 351 passed (351)` · `Tests 6296 passed, 1 skipped (6297)` · `VERDICT command-exit 0 · held the lock 442s` | | control bytes | `pnpm check:control-bytes` | 0 | `✅ check-control-bytes: OK (scanned 7658 tracked text file(s); skipped 85 binary)` | | lint | `pnpm lint --concurrency=2`, under the verify lock | 0 | `Tasks: 47 successful, 47 total` · `VERDICT command-exit 0 · held the lock 182s`; the 830 warnings are pre-existing `no-explicit-any` and 0 errors | | type-check | `pnpm type-check --concurrency=2`, under the verify lock | 0 | `Tasks: 81 successful, 81 total` · `VERDICT command-exit 0 · held the lock 435s` | | changeset presence | `node scripts/check-changeset-presence.mjs` | 0 | `✅ No source or published contract of a released package changed in this range` | | changeset no-major | `node scripts/check-changeset-no-major.mjs` | 0 | `✅ No changeset declares a major bump.` | | changeset claims | `pnpm check:changeset-claims` | 0 | report-only; `1 file(s) changed outside .changeset/` read against 1070 pending bodies, no finding raised against this diff | | line citations | `pnpm check:new-line-citations` | 0 | `VERDICT new-cross-file-line-citations: 0 new citation(s)` | | shell escape residue | `pnpm check:shell-escape-residue` | 0 | `✅ OK (5/5 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s) ...)` | | doc links | `node scripts/check-doc-links.mjs` | 0 | `Links are valid across 17 scan roots.` | Beyond the gate, the control-character scan was also run by hand over both changed files — `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` returns exit 1 on each. ## Acceptance notes - **Region.** The diff is a pure 3-line insertion at :396–:398 of `AGENTS.md`; `git diff --stat` reports 3 insertions and 0 deletions in that file. The two sibling region claims on the same file — objectstack-ai#9191 at :232–:239 and PR objectstack-ai#9466 at :519 — are untouched. If this lands second, `origin/main` gets merged in first. - **The habit applied to itself is a weaker instrument on a doc than on a workflow, and the numbers say so.** For `lint.yml` the command returns 21 files. For `AGENTS.md` it returns 371, of which 342 mention the filename only on comment lines (`// AGENTS.md §测试纪律` and the like) and 9 more carry it inside a test name or an assertion message; 63 of the 351 non-`scripts` hits reference any `node:fs` read surface at all. That is the text-search false-positive direction the card measured, at a different magnitude, and it is why the line is scoped to `.github/workflows` files, whose names are distinctive, rather than to any path. Noted, not filed — this is an observation about the line's reach, not a defect in anything. - **Nothing in the diff can move an un-listed pin.** Both changed files are Markdown; eslint's config in this repo matches only `**/*.{ts,tsx}` and `**/*.tsx` and registers no Markdown processor, and `.md` is not a tsc input — so `lint` and `type-check` were run in full anyway rather than relied on for coverage of this diff. - **No collector was built and no gate was added**, per the ruling's authority line. The 22 readers the text search misses stay recorded on objectstack-ai#9198 as the evidence a future card would need; they are ⛔ not addressed here. Clause-②: no --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #9213
⛔ This PR is GOVERNED and opens as a DRAFT — the draft state is the FINISHED state
AGENTS.mdis governed surface. Verbatim verdict from this branch:are green. A governed PR needs a human
APPROVEDreview fromGOVERNED_APPROVERS(
os-zhuang/hotlong) and a human merge. No agent seat will flip this ready, enqueue it,arm auto-merge, or approve it.
What is wrong
AGENTS.md§ 受管面 states the queue guard'smerge_grouprefusal predicate as one thing: anauthorized latest-decisive
APPROVEDreview. Since #9018 / PR #9212 that leg carries twoindependent predicates:
APPROVEDreview recordneeds:contract-review⇒ a merge group whose diff touches nothing governed at all can now be refused. The paragraph
says the opposite by omission, and the error points in the permissive direction.
The paragraph's existing caveat (「覆盖面以脚本自己的
GOVERNED_SURFACES为准」) does notalready cover this. It delegates the surface list. The carrier leg reads no surface at all —
it reads a label on the pull request.
Measurements, re-derived on this branch's base (
f5b9c17973), not inherited from the card1. The exits exist and mean what the card said.
6 = a queued pull request carries the label (
refusalKind: 'gated'). 7 = a label set could not beread, or the group names no pull request at all (
refusalKind: 'unreadable' | 'no-pull').2. The carrier leg reads a LABEL, not a surface. Code path, all in
scripts/check-governed-queue-guard.mjs:runCarrierGuard({ event, rows, namedPull, fetchLabels })→carrierPullsInGroup(rows, namedPull)enumerates the queued PRs per commit → for each,
await fetchLabels(pr)→ the whole predicate isnames.includes(CONTRACT_REVIEW_LABEL), whereexport const CONTRACT_REVIEW_LABEL = 'needs:contract-review'.No path, no diff, no surface set is consulted anywhere on this leg. The file says so itself:
3.⚠️ Read as TEXT with whitespace collapsed, not with a
AGENTS.mdcarried no mention of it.line-oriented grep — the target is one enormous wrapping sentence on a single physical line (1434
chars), and a line grep cannot be trusted to answer a question about a sentence.
Every zero is carried by three controls that HIT in the same run, in the same file, with the same
reader.
4.
content/docs/guide/ci-cd-pipeline.md(NOT governed) already carries the corrected account —PR #9212 updated it. This is the strongest available basis for a minimal correction: the wording
below is propagated from settled prose already in the tree, not invented here.
Its row in the same file's check table already says it too:
⭐ The wording is the maintainer's to amend — that is why the diff is this small
#9213 declines to propose wording, verbatim: 「Not proposing the wording. What the paragraph
should say is a maintainer's call, not a seat's, and the file is governed precisely so that it is.」
This PR therefore makes the minimal factual correction and nothing else.
diff --numstat→1 1 AGENTS.md. One line changed, because the paragraph is one physicalline. Text was added; nothing was removed.
What I deliberately did NOT touch:
GOVERNED_SURFACES为准」 caveat, even though it is thesentence a reader is most likely to over-read (see the alternatives below)
content/docs/guide/ci-cd-pipeline.md522. Verified non-overlapping.
Register: the insertion is Chinese prose with inline code spans, half-width punctuation⚠️ / ⇒ /
(
,;:()— measured on the target line: 27,, 13(), 9:, 5;; the only twofull-width
,in that line are inside the verbatim untranslated maintainer quote), and theparagraph's existing
**bold**markers. No English sentence was introduced.⭐ Alternative phrasings I considered — offered here, NOT put in the file
The maintainer should feel free to take any of these instead; amending one line is cheap by
construction.
required context」, opening 「
while 「
merge_group腿才是会拒绝的那条 —— 它要求 …」 is still in the reader's eye, and because thefollowing 「但它尚未是 required context ⇒ 那条拒绝腿只报告、不阻止队列」 is true of both
predicates, so it now correctly scopes over both.
scratch. Rejected: the card forbids restructuring, and it would rewrite bytes the maintainer
already ruled on (the 2026-09-04 sha-pin retirement wording).
已接受的代价 caveat. Tightest possible placement. Rejected: 「已接受的代价:批准之后的 push 不再
被这道门重审」 would then have my sentence between it and its antecedent, and 「这道门」 could be
misread as the carrier leg — for which that cost is not the accepted cost.
GOVERNED_SURFACEScaveat, so it could say「上面那句托付的是受管面清单,⛔ 不是判据清单」. Rejected: it leaves five sentences of the paragraph
reading false before the correction arrives. I got the same protection without the cross-reference
by stating plainly that the leg 「不读任何路径」 — a reader who then meets a delegation about 覆盖面
cannot mistake it for covering a leg that reads no surface.
--testrenderer's own advisory text, which describes the merge-queuerefusal with the approval predicate alone (quoted at the top of this PR). Rejected: out of scope
for finding(docs): AGENTS.md describes the queue guard's
merge_grouprefusal as the approval predicate alone — objectui#9018 added a second, independent one and that paragraph is governed surface #9213, and it is a different file. Reported to the PM rather than fixed here.Gates — PRE / POST, exit codes captured by redirect-then-capture, never across a pipe
PRE = base bytes restored from
HEADby explicit ref; POST = this commit. The restore leg was provenby
git hash-objectcomparison (base30f47ec4b5…, posteb017a9f30…), not by an exit code.check:control-bytescheck:new-line-citationsdocs:check-linkscheck:shell-escape-residuecheck:governed-queue-guard(self-test)check:required-check-set(self-test)check-changeset-presence.mjsBoth sides agree, so each instrument is shown to be non-empty and to have actually read the file:
check-control-bytes: OK (scanned 7644 tracked text file(s); skipped 85 binary)check-shell-escape-residue: OK (5/5 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s); …)Links are valid across 17 scan roots.VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0check-governed-queue-guard self-test: 185 cases pass (… plus the SECOND queue predicate objectui#9018 added on the merge_group leg — the contract-review carrier …)— the guard's own self-test corroborates the premise on this base.Changeset: none owed.⚠️ The
check-changeset-presence.mjs→✅ No source or published contract of a released package changed in this range, so no changeset is owed.skip-changesetlabel wasdeliberately not applied: in this repository that label object exists but no workflow or script
reads it, and a pin test holds that in place.
I also self-scanned the file for control bytes outside the gate
(
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'→ no match, with a positive control that HIT in thesame run on a fixture containing
0x0c).维护者速读(草稿)
改了什么 ——
AGENTS.md受管面那段里,关于merge_group拒绝腿的描述补了一句:那条腿现在有两条相互独立的判据,原文只写了一条(获授权的 APPROVED review),漏掉了 #9018 加的 contract-review carrier
(读 PR 上的
needs:contract-reviewlabel,exit 6 / 7)。一行 diff,只加不删。为什么改 —— 漏写的方向是放宽:照原文读,一个 diff 完全没碰受管面的 merge group 不可能被这道
check 拒绝;实际上可以。每一次 dispatch 都读这段。
风险与代价(含回滚) —— 风险极低:纯文字,不动脚本、不动 workflow、不动任何判定逻辑,六道门禁
PRE/POST 全绿。回滚 = revert 这一个 commit,恢复一行。措辞是从
content/docs/guide/ci-cd-pipeline.md里已经被接受的说法搬过来的,不是新造的。
席位意见 ——
你要做的 —— ① 读上面「Alternative phrasings」五条,措辞归你定,改一行很便宜;② 这是受管面 PR,
需要你一条
APPROVED并由你合并 —— 席位不会 ready、不会入队、不会自合、不会批准。🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code