diff --git a/AGENTS.md b/AGENTS.md index b646a64b65..534a8ee514 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -349,7 +349,7 @@ AGENTS.md 的「只跑受影响的包」指的是**用上面的路径过滤缩 队列会把 PR **在当前 `main` 上重建**后再落地,重建不绿就把它踢出队列,而不是把红的落到共享 `main` 上。所以旧版那条「绝不 `gh pr merge --auto`」的前提已经反转:它防的正是队列现在替你防住的事,而在强制队列的仓库里 **enable auto-merge 就是入队的标准手段**,也是本仓实际走得通的唯一通路(仓内佐证:`.github/workflows/dependabot-auto-merge.yml` 对 Dependabot PR 用的就是 `gh pr merge --auto --squash`)。注意 path-filter 跳过的检查(显示 `skipping`)不是失败,配合 `mergeStateStatus: CLEAN` 即算全绿。 - **auto-merge 会在「合并冲突」和「draft」窗口里被静默丢弃 —— 事后必须复查并重挂。** 已两次踩实(先例 PR #3458):PR 一旦变成 conflicting、或被(重新)标记为 draft,已挂上的 auto-merge 就没了,**且不会有任何通知**。解完冲突或 `gh pr ready` 之后若不重新挂一次,PR 会一直停在那里 —— 看着"全绿待合",实际谁也没在等它。收工前复查一次:`gh pr view --json isDraft,mergeStateStatus,autoMergeRequest`,`autoMergeRequest` 为 `null` 就是掉了,重挂。 - **不必为了合并去 rebase 其他在途分支** —— 队列自己会在当前 `main` 上重建,旧版「串行合并、合下一个前先 rebase 在途分支」那套编排已是历史。**但队列只拦得住文本冲突和 CI 看得见的破坏**:两个各自全绿的 PR 仍可能**语义冲突**(改了同一约定的两端;一边删掉了另一边刚开始用的导出)。所以动**共享面**(barrel/注册表/公共类型/跨包约定)时,合并前扫一眼在途 PR(`gh pr list`),有交叠就在 PR 正文里写清交叠点与取并集的办法(先例:PR #3458 对 #3456 同文件交叠的说明)。 -- ruleset 的**具体配置**(谁可绕过、required checks 清单)本文不写 —— 从仓内读不到,别照抄任何推断。上面几条写的都是实测到的可观测行为。 +- ruleset 的**具体配置**(谁可绕过、required checks 清单)本文不写,两半的理由不同(objectui#9520 实测):**谁可绕过**从仓内读不到(ruleset 端点对席位 token 只答「我能不能绕过」,不给名单;这一半没有仪器复核),别照抄任何推断;**required checks 清单**读得到,要看就跑 `node scripts/check-required-check-set.mjs`(`.github/workflows/required-check-set-patrol.yml` 定时跑它;`pnpm check:required-check-set` 只是它的离线 self-test,不读线上),⛔ 别把它的答案抄进本文。上面几条写的都是实测到的可观测行为。 ### ⚠️ Actions workflow 注册表:`list_workflows` 回答不了「本仓到底跑不跑 X」 @@ -547,7 +547,7 @@ sha pin **退休**之后这条**更重、不是更轻**(维护者 2026-09-04 裁 - **CI 全绿、已 review 都不构成例外。** 这类文件是后续每一次 dispatch 读的操作规程,绿灯说明不了它该不该成为规程。 - **发现自己已经挂上了怎么办**:把 PR 转回 **draft** 是唯一能可靠退出合并队列的动作 —— 只调 `disable_pr_auto_merge` 会摘掉 auto-merge 但**不取消队列成员资格**,两个都要做。⚠️ 只回收**你自己**挂上的:本仓多 agent 共用同一 GitHub 身份,不是你设置的状态就属于别的 actor —— 去问、去报告,别替他回退。 -**本仓的机械兜底只有一件,而且它现在只报告、不拦截 —— 别读成一道拦得住的门,也别再读成「什么都没有」。** 本仓仍然没有 CODEOWNERS(核实:仓内不存在该文件),受管面上也没有钩子;但 `.github/workflows/governed-surface-guard.yml`(check 名 `Governed Surface Queue Guard`,判定逻辑在 `scripts/check-governed-queue-guard.mjs`)**是活的**:`pull_request` 腿是早期告警、**故意 exit 0**(受管 PR 停在 draft 正是健康终态,所以**绿不等于不受管**),`merge_group` 腿才是会拒绝的那条 —— 它要求 `GOVERNED_APPROVERS`(`os-zhuang` / `hotlong`)里某个账号的一条 latest-decisive APPROVED review,**留在哪个 commit 上都算**;DISMISSED 与被顶掉的批准(同一 reviewer 后续给了 CHANGES_REQUESTED)不算,该集合之外账号的 APPROVED 不算,review 列表为空或读不到则 fail closed —— 判定读的是**有没有一条人工批准记录**,不问它是对哪些字节给的(维护者 2026-09-04 裁,逐字未译:「你的门禁有问题,只需要有人工批准记录就行,不需要卡最新的提交。」;sha pin 是**退休**不是放宽,守卫里已没有任何判定读 `commit_id`)。⚠️ 已接受的代价:批准之后的 push 不再被这道门重审,一个已批准的受管 PR 可以带着批准者没读过的字节落地 —— 维护者接受这一点,而这道拒绝先印的补救仍是转回 draft、交人类合并。⚠️ 而这条拒绝腿上的判据**不止上面那一条** —— #9018(PR #9212)给 `merge_group` 腿加了**第二条、与受管面完全无关的**判据:它枚举该 merge group 要落地的**每一个** PR(逐 commit 分解,不是只读 `merge_group.head_ref`),读每个 PR 的 label,其中任何一个仍挂着 `needs:contract-review` 就拒绝(**exit 6**);label 读不到、或该 group 指不出任何 PR,同样拒绝(**exit 7**)。⚠️ 这条判据读的是**挂在 PR 上的 label**,不读任何路径、不问 diff 命中了什么 —— ⇒ **一个 diff 完全没碰受管面的 merge group,现在也可能被这道 check 拒绝**;`pull_request` 腿不受影响,不读 label。⚠️ 但它**尚未**是 required context:ruleset 开关只有维护者能翻(#6596,`pm:awaiting-maintainer`),**在翻转之前,那条拒绝腿只报告、不阻止队列**。事后一侧:`../objectstack` 的 report-only 合并后审计(`scripts/pm/check-governed-merges.mjs`)自 objectstack#9619 起**已覆盖本仓**(四个受管仓一次扫完),它把受管面的合并列出来,但同样不阻止任何事。⇒ 违规不再完全静默,但**仍然没有任何东西会替你拦下它**,这条规则的效力主要还是在于你读到了它并照做。**⛔ 别再把本段当成兜底工具的完整清单** —— 覆盖面以脚本自己的 `GOVERNED_SURFACES` 为准(它随树变化,本段不会);⚠️ 该清单曾与上面的受管面清单**并不一致**(脚本的集合含已发布 `skills/**`),这一分歧**已裁**:维护者第 5 场决裁批 #7 采 **Option A**(#6866 评论 5469339478)—— 已发布 `skills/**` **受管**,守卫的读法才是裁定的那个;该裁决**已随本段上方的清单落地**(#6866):上面五项已含 `skills/**`,与脚本的 `GOVERNED_SURFACES` 一致,曾经那条「仓根 `skills/**` 不受管、自行入队」的豁免**已作废**,⛔ 别再照它行事。 +**本仓的机械兜底只有一件,它的两条腿意思不同:`merge_group` 腿拦得住合并队列(2026-09-27 起,见下),`pull_request` 腿只告警 —— 别把 PR 页上的绿读成不受管,也别再读成「什么都没有」。** 本仓仍然没有 CODEOWNERS(核实:仓内不存在该文件),受管面上也没有钩子;但 `.github/workflows/governed-surface-guard.yml`(check 名 `Governed Surface Queue Guard`,判定逻辑在 `scripts/check-governed-queue-guard.mjs`)**是活的**:`pull_request` 腿是早期告警、**故意 exit 0**(受管 PR 停在 draft 正是健康终态,所以**绿不等于不受管**),`merge_group` 腿才是会拒绝的那条 —— 它要求 `GOVERNED_APPROVERS`(`os-zhuang` / `hotlong`)里某个账号的一条 latest-decisive APPROVED review,**留在哪个 commit 上都算**;DISMISSED 与被顶掉的批准(同一 reviewer 后续给了 CHANGES_REQUESTED)不算,该集合之外账号的 APPROVED 不算,review 列表为空或读不到则 fail closed —— 判定读的是**有没有一条人工批准记录**,不问它是对哪些字节给的(维护者 2026-09-04 裁,逐字未译:「你的门禁有问题,只需要有人工批准记录就行,不需要卡最新的提交。」;sha pin 是**退休**不是放宽,守卫里已没有任何判定读 `commit_id`)。⚠️ 已接受的代价:批准之后的 push 不再被这道门重审,一个已批准的受管 PR 可以带着批准者没读过的字节落地 —— 维护者接受这一点,而这道拒绝先印的补救仍是转回 draft、交人类合并。⚠️ 而这条拒绝腿上的判据**不止上面那一条** —— #9018(PR #9212)给 `merge_group` 腿加了**第二条、与受管面完全无关的**判据:它枚举该 merge group 要落地的**每一个** PR(逐 commit 分解,不是只读 `merge_group.head_ref`),读每个 PR 的 label,其中任何一个仍挂着 `needs:contract-review` 就拒绝(**exit 6**);label 读不到、或该 group 指不出任何 PR,同样拒绝(**exit 7**)。⚠️ 这条判据读的是**挂在 PR 上的 label**,不读任何路径、不问 diff 命中了什么 —— ⇒ **一个 diff 完全没碰受管面的 merge group,现在也可能被这道 check 拒绝**;`pull_request` 腿不受影响,不读 label。⚠️ 它拦得住,是因为这个 check 名进了 ruleset 的 required 集合 —— 下面是一段有日期的历史,不是本段替你复核的现状:ruleset 开关只有维护者能翻;2026-09-14 的读数里它还不在集合中,那时这条拒绝腿只报告、不阻止队列;ruleset 的最后一次编辑在 2026-09-27,三分钟后 objectui#6596 以 completed 关闭,2026-09-28 实测它已在集合里。**在集合里,它的拒绝(上面的 exit 6 / exit 7 同样)就会把 PR 踢出合并队列。** 它现在还在不在,跑 `node scripts/check-required-check-set.mjs` 读,⛔ 别把它的答案抄进本段。⚠️ 这是按配置推出的,不是观察到的:登记后到 2026-09-28 的 `merge_group` 运行全部通过,拒绝路径还没在队列里真正触发过一次。事后一侧:`../objectstack` 的 report-only 合并后审计(`scripts/pm/check-governed-merges.mjs`)自 objectstack#9619 起**已覆盖本仓**(四个受管仓一次扫完),它把受管面的合并列出来,但它不阻止任何事。⇒ 一个没有获授权批准记录的受管 PR 经合并队列落不了地,这一半队列替你拦;**拦不住的是批准本身**:席位共用一个 GitHub 身份,守卫分不出那条 APPROVED 是不是人留的,绕过 ruleset 的人也不经这道门(谁能绕过,见上面 ruleset 那一条)—— 所以第五条禁令的效力仍然全在于你读到了它并照做。**⛔ 别再把本段当成兜底工具的完整清单** —— 覆盖面以脚本自己的 `GOVERNED_SURFACES` 为准(它随树变化,本段不会);⚠️ 该清单曾与上面的受管面清单**并不一致**(脚本的集合含已发布 `skills/**`),这一分歧**已裁**:维护者第 5 场决裁批 #7 采 **Option A**(#6866 评论 5469339478)—— 已发布 `skills/**` **受管**,守卫的读法才是裁定的那个;该裁决**已随本段上方的清单落地**(#6866):上面五项已含 `skills/**`,与脚本的 `GOVERNED_SURFACES` 一致,曾经那条「仓根 `skills/**` 不受管、自行入队」的豁免**已作废**,⛔ 别再照它行事。 ### 服务纪律(本仓库与 `../objectstack` 多 agent 并行开发) diff --git a/scripts/__tests__/check-required-check-set.test.ts b/scripts/__tests__/check-required-check-set.test.ts index d825a4d760..5632176b1f 100644 --- a/scripts/__tests__/check-required-check-set.test.ts +++ b/scripts/__tests__/check-required-check-set.test.ts @@ -358,11 +358,17 @@ describe('check-required-check-set — the wiring', () => { * stale the next time a maintainer edits the ruleset. * * ⚠️ The population is named in words: the three files objectui#9502 repaired. - * `AGENTS.md` carries a fourth instance and is deliberately NOT in that set — - * it is governed surface, and its parenthetical also covers who may bypass the - * ruleset, which the endpoint this gate reads does not carry. That exclusion is - * what makes the control below real: the same detector, run over `AGENTS.md`, - * must FIRE. A zero from a detector never observed firing is decoration. + * `AGENTS.md` carried a fourth instance and is deliberately NOT in that set. + * objectui#9502 left it standing — it is governed surface, and its + * parenthetical also covered who may bypass the ruleset, which the endpoint + * this gate reads does not carry — and objectui#9520 then split it along that + * line: its required-checks half points at this gate, and "从仓内读不到" stays + * on the bypass-actors half alone, where it was measured true. So it still + * cannot join `REPAIRED` (the "makes no cannot-be-read claim" case would go red + * on that true half, which is exactly what Leg 2 below asserts it carries), and + * it is still what makes the control below real: the same detector, run over + * `AGENTS.md`, must FIRE. A zero from a detector never observed firing is + * decoration. */ describe('check-required-check-set — the prose points here instead of answering (#9502)', () => { /** The three files objectui#9502 repaired. */ @@ -372,8 +378,12 @@ describe('check-required-check-set — the prose points here instead of answerin 'scripts/dependabot-merge-gate.mjs', ]; - /** The carrier left standing on purpose — and this block's positive control. */ - const LEFT_STANDING = 'AGENTS.md'; + /** + * The carrier objectui#9502 left standing and objectui#9520 split: the one + * file that still writes "从仓内读不到", on its bypass-actors half, where it is + * true — and this block's positive control. + */ + const SPLIT_CARRIER = 'AGENTS.md'; /** * The claim being hunted: the ruleset cannot be READ from this repository. @@ -393,15 +403,20 @@ describe('check-required-check-set — the prose points here instead of answerin expect(CANNOT_READ.test('that set is a surface nothing here can change')).toBe(false); // Leg 2, the same detector over real content: `AGENTS.md` still carries the - // claim, deliberately. If this leg ever goes red the governed carrier was - // ruled on and repaired — which is a legitimate change, not a bug here. The - // remedy is to move the inventory docblock in the gate with it and retire - // this leg, NOT to weaken the detector. + // phrase, deliberately, on the bypass-actors half of its ruleset bullet — + // the one place in the tree it is true, which is where objectui#9520 kept it + // when it split that bullet. ⚠️ Said per AGENTS.md #9 rather than left to be + // assumed: this detector cannot tell which half a phrase sits on, so this + // leg does NOT hold the bullet's required-checks half to its pointer, and + // nothing else in this file does either. If this leg ever goes red the bypass + // half was re-worded or ruled on again — a legitimate change, not a bug + // here. The remedy is to move the inventory docblock in the gate with it and + // retire this leg, NOT to weaken the detector. expect( - CANNOT_READ.test(flatten(LEFT_STANDING)), - `${LEFT_STANDING} no longer carries the claim this detector hunts. If that carrier was ` + - `repaired, update the inventory docblock in ${GATE} to match and drop this leg. ` + - 'Leg 1 above keeps the detector honest either way.', + CANNOT_READ.test(flatten(SPLIT_CARRIER)), + `${SPLIT_CARRIER} no longer carries the phrase this detector hunts (its bypass-actors half did, ` + + `deliberately). If that half was re-worded, update the inventory docblock in ${GATE} to match ` + + 'and drop this leg. Leg 1 above keeps the detector honest either way.', ).toBe(true); }); @@ -440,9 +455,9 @@ describe('check-required-check-set — the prose points here instead of answerin } }); - it("the gate's own docblock inventories every repaired carrier, and the one left standing", () => { + it("the gate's own docblock inventories every repaired carrier, and the one objectui#9520 split", () => { const docblock = fs.readFileSync(path.join(ROOT, GATE), 'utf8').slice(0, 4000); - for (const rel of [...REPAIRED, LEFT_STANDING]) { + for (const rel of [...REPAIRED, SPLIT_CARRIER]) { expect(docblock, `${GATE} no longer names ${rel} in its inventory`).toContain(rel); } // The write half is the reason the sentences were not simply deleted. diff --git a/scripts/check-required-check-set.mjs b/scripts/check-required-check-set.mjs index c809136f3d..a9078b64a3 100644 --- a/scripts/check-required-check-set.mjs +++ b/scripts/check-required-check-set.mjs @@ -45,16 +45,21 @@ * .github/workflows/dependabot-auto-merge.yml the header's "does NOT do" list * scripts/dependabot-merge-gate.mjs "declared = enforced" * - * ⛔ One carrier is deliberately left standing, and it is not an oversight: + * ⛔ One carrier was deliberately left standing by objectui#9502, and it was + * not an oversight: * * AGENTS.md "从仓内读不到" * * AGENTS.md is GOVERNED surface -- an agent drafts it, an authorised approver - * lands it -- so objectui#9502 did not touch it. It may also be true on a leg - * the repaired sentences never had: its parenthetical covers who may BYPASS the - * ruleset as well as the required-context list, and bypass actors are not - * carried by `GET /rules/branches/{branch}`, the endpoint this file reads. - * ⛔ Ruling on it is a separate, governed decision and is not made here. + * lands it -- so objectui#9502 did not touch it. Its parenthetical also covered + * who may BYPASS the ruleset, and bypass actors are not carried by + * `GET /rules/branches/{branch}`, the endpoint this file reads. objectui#9520 + * ruled on it as a governed change of its own and split it along that line: + * the required-checks half now points at this file, and "从仓内读不到" stays on + * the bypass-actors half alone, where it was measured true -- the ruleset + * endpoint answers a seat's token with `current_user_can_bypass` and no + * `bypass_actors` key. ⛔ That kept half is not a missed repair. Nothing in this + * tree re-derives it, either. * * The WRITE half of every one of them is still true and this file does not * touch it. ⛔ This script never writes: no enrolment, no removal, no ruleset