Skip to content

ci(changeset): PR 层闸门禁止新增空 frontmatter changeset,并把 skills/** 的正解写进处方 (#5471, #5947) - #6059

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-5471-ban-empty-changeset-gate
Aug 7, 2026
Merged

ci(changeset): PR 层闸门禁止新增空 frontmatter changeset,并把 skills/** 的正解写进处方 (#5471, #5947)#6059
hotlong merged 1 commit into
mainfrom
claude/issue-5471-ban-empty-changeset-gate

Conversation

@hotlong

@hotlong hotlong commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5471
Fixes #5947

#5471 的 10:44Z 已批裁决落地:PR 层闸门,新增的空 frontmatter changeset 直接红并指向 skip-changeset 标签路径;存量全部豁免、不清理、不碰发布机器。 同时并轨 #5947 —— 把 skills/** 的正解写进处方文本,让 git log 考古者与处方读者拿到同一个答案。

前提复核(先证后写)

三条前提都对 origin/main 实测过,均成立:

前提 实测 结论
处方现状:路线 2 PREFERRED / 路线 3 LAST RESORT pr-automation.yml 原文即是 成立
存量空 changeset 实数 efedd289f / 9e3709a 两点各测:1230 份 non-README,182 份空 frontmatter 成立(issue 正文的 172 是两天前的旧值,#5947 的 182 与我实测一致)
skills/** 先例链走路线 3 77adf297f(PR #5799,2026-08-06 06:57 UTC)新增 .changeset/published-pm-dispatch-three-axis-decision-frame.md,内容确为 --- / --- 空 frontmatter 成立,且确实晚于处方改口一天
#5947 的「没有包可署名」推理 skills 不在 pnpm-workspace.yaml 的 packages 列表;根包 private: true;全仓无任何 package 的 files 收录 skills 前提为真,但结论应是路线 2

182 这个数字只写在注释里并锚定 sha(182 at efedd289f),不进代码逻辑 —— 它是会漂的量,不该有第二个消费方。

落点选择:新增 scripts/check-empty-changeset.mjs,而非在步骤里加 shell

PM 给的两个选项里选了脚本,三个理由:

  1. 两种失败是两件事,该有两条错误信息。 现有步骤答的是「一份 changeset 都没有」,新闸答的是「你加的这份什么都没声明」。塞进同一个 shell 块就得共用一段文案,而这两句话给作者的下一步动作完全不同。
  2. shell 块自测不了。 本仓 check-*.mjs--self-test 惯例正是为此;红/绿双向 fixture 只有落在脚本里才能被 CI 每次执行。--self-test 已接进命令链(在真跑之前),不是 phantom check。
  3. YAML 里解析 frontmatter 是脆的。脚本复用了 check-changeset-no-major.mjs 同一形状的 entry 正则 —— 两道闸读同一个 frontmatter 块,对「什么算一条声明」必须同口径,否则其中一道在 judge 另一个文件。

package.json 只加一行 check:empty-changeset 接线(本地可跑 + 对齐仓内惯例)。已确认 check:generated 元闸只核对 packages/spec/package.json,根包这行不在它的辖区(实跑 --reconcile-only 绿)。

豁免机制:按 diff 判,并且比「是否新增文件」更稳一档

裁决给的判据是「该空 changeset 是否已在 base 上存在」。我实现成一条关于空声明集合的规则,而不是关于新增文件的规则:

一份 changeset 违规,当且仅当它在 HEAD 上是空声明,(它在 base 上不存在 它在 base 上是非空的)。

四行判据表:

diff 状态 HEAD base 判定
A 新增 (新空文件)
M 修改 非空 (就地掏空)
M 修改 豁免(存量,本 PR 没造出新的空声明)
任意 非空 绿

第 2 行是「更稳的判法」那部分,值得单独论证:把一份存量非空 changeset 的 frontmatter 条目删掉,产生的是一份全新的空声明 —— 危害与新建一个文件一字不差,而 --diff-filter=A 对它完全看不见。多花约十行换掉这个显而易见的绕过口。第 3 行则保证存量不会因为「顺手改了正文里一个错别字」被误伤,这是 --diff-filter=AM 下豁免仍然成立的那一行。

没有任何清单。 182 个文件名的硬编码清单是会腐烂的高水位(下一次合并就不准),而「base 上不存在或非空」是同一句话的免维护写法。存量因此天然全部豁免,本 PR 一个存量文件都没动。

自验:红/绿双向,fixture + 真仓两遍

--self-test真的临时 git 仓库(两个真 commit)跑同一个导出的 scan() —— 本闸的主题就是两个 commit 之间的 diff,用非真 commit 的 fixture 等于在测一段不会上线的代码路径。21 条断言,覆盖 PM 点名的四种情形加三种边界:

✓ check-empty-changeset --self-test: 21 assertions over real temp git repos (real scan() path)
fixture 方向 结果
新增空 changeset(skills/** 变更,#5799 原样形状) 红,且点名文件 通过
存量空 changeset 不动 绿 通过
新增非空 changeset 绿 通过
skills/**-only PR 完全不带 changeset(路线 2) 绿(本闸无话可说) 通过
存量非空 changeset 被掏空 红,kind=emptied 通过
存量空 changeset 只改正文 绿,记为 exempt 通过
新增无 frontmatter 围栏的文件 红,kind=added-unfenced 通过
.changeset/README.md 永不判定 通过
base 解析不出来 退出 1(缺输入即失败,#4690) 通过

真仓反向验证(方向在跑之前先声明,结果与预测一致):

预测:在本分支真加一份空 changeset 并提交 → 闸变红并点名;撤掉 → 变绿。实测:

# 加一份 .changeset/reverse-verify-empty.md(空 frontmatter)后:
This PR adds an empty-frontmatter changeset:
   .changeset/reverse-verify-empty.md
     new file, empty frontmatter -- declares no package
EXIT=1

# git reset 撤掉后:
✓ No empty-frontmatter changeset introduced by this diff (0 declaring changeset(s) added).
EXIT=0

同一轮还在真仓上验了第 2/3/4 行:掏空真实的 .changeset/action-alias-conflict-warning.md → 红且 kind=emptied;同一个 commit 里新增的非空 changeset 与被改了正文的存量空文件 .changeset/adr-0044-revise-service-owned-note.md 都没有进 violations。所有反向验证提交均已 git reset --hard 撤回,不在本分支。

存量豁免的真数据证明:对 origin/main 自身跑闸(HEAD=base=origin/main,即 182 份空文件全在场)→ 绿。豁免不是靠清单成立的,是靠闸根本不读目录成立的。

解析器交叉校验:--list1230 changeset(s): 182 empty-frontmatter, 1048 declaring,与我用完全独立的一段 awk 数出来的 1230/182 逐位相同。

#5947:处方文本改口

  • 路线 2 显式列入 skills/**,并把那句会自我复制的推理正面驳掉:「skills/ 不随任何 npm 包发布,没有包可署名」前提为真(已实测),但结论是标签,因为空 changeset 同样名不到任何包,还额外背上 空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898
  • 路线 3 标为 (CLOSED),并说明存量豁免。这一段是必须改的 —— 闸落地后再留着「路线 3 也满足本闸且仍然合法」就是一句假话,会把作者领进第二次红。
  • 计数步骤上方的注释同步改口:计数行为一字未变,变的是「计数不再是最后一句话」。

不碰的东西

量级 S,单 commit 可秒回滚。

标签读现状不读载荷(#5580 / #5625 的学费)

新步骤挂的是与上面每一步同一个 steps.labels.outputs.skip 守卫,也就是第一步那次实时标签复读,而不是事件载荷 —— rerun 能翻绿。

这留下一格,注释里写明而非默认:同时带 skip-changeset 标签一份新空 changeset 的 PR 不会被拦(整个 job 被豁免)。补上它意味着让本步骤跳出 job 级豁免,而那样它只在「标签晚于事件到达」时才触发 —— 一道会红掉这个 PR、绿掉另一个一模一样 PR 的闸。一致的豁免优于不确定的闸;何况这一格没有动机:已经拿到标签的作者再加这个文件什么都换不到。

本 PR 自己走哪条路

workflow + scripts,不发布任何包 → 路线 2,skip-changeset 标签,不带 changeset。本单要禁的正是那份空文件,不能让这个 PR 自己成为反例。

本地验证

node scripts/check-empty-changeset.mjs --self-test   → 21 assertions,绿
pnpm check:empty-changeset                           → self-test + 真跑,绿
npx eslint scripts/check-empty-changeset.mjs --no-inline-config → 0
pnpm check:workflow-status-functions                 → 34 assertions + 22 workflow 扫描,绿
pnpm check:type-check-coverage                       → 绿
pnpm --filter @objectstack/spec check:generated --reconcile-only → 绿
node scripts/check-nul-bytes.mjs                     → 5781 文件,零控制字节
python3 yaml.safe_load(pr-automation.yml)            → 解析通过,changeset-check 共 8 步

🤖 Generated with Claude Code

https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3


Generated by Claude Code

…#5471, #5947)

新增 scripts/check-empty-changeset.mjs,由 pr-automation.yml 的 Check Changeset
作业调用:PR 新引入的空 frontmatter changeset 直接红并点名文件,报错指向
`skip-changeset` 标签(路线 2)。豁免按 diff 计算 —— 只判本 PR 引入的声明,
base 上的 182 份存量因此天然豁免,无需任何会腐烂的清单。

同时把处方文本改口收口(#5947):路线 3 标记为 CLOSED,路线 2 显式列入
`skills/**` 并写明「没有包可署名 ⇒ 标签,不是空文件」,让 git log 考古者与
处方读者拿到同一个答案。

不碰发布机器、changesets 配置与 Release 行为;#5620 的 allow-major 步骤逐字未动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 3:57pm

Request Review

@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@github-actions github-actions Bot added the size/l label Aug 6, 2026
@hotlong
hotlong marked this pull request as ready for review August 6, 2026 16:51
@hotlong
hotlong enabled auto-merge August 6, 2026 16:51
@github-actions github-actions Bot added ci/cd dependencies Pull requests that update a dependency file labels Aug 7, 2026
@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31136158512 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/__tests__/datasource-pool-support.test.ts�[2m > �[22m#5714 — the driver factory rejects a pool it cannot honour�[2m > �[22msqlite WITHOUT a pool still builds exactly as b
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 76 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

队列失败归因(分诊清单第 2 条):失败用例 datasource-pool-support.test.ts > #5714,为已知 flaky #6044,包面(engine-core 测试)与本 PR 改动面(pr-automation.yml + scripts)无交集;同组 Lint & Type Check、Spec Liveness 均绿。不属本 PR 回归,按「重排一次、不连排」重挂 auto-merge。若再次同名失败,升级到 #6044 处置,不再重排。


Generated by Claude Code

@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
@claude

claude Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

队列管家让行 —— 车道 PM 已于 01:04:15Z 自行处置本 PR(认签名 + 重挂 auto-merge),本座位读到后不重复诊断、不重复重投(SKILL「双向让行」)。

判读与车道一致:踢出(01:03:02Z)的失败用例为 datasource-pool-support.test.ts > sqlite WITHOUT a pool still builds exactly as before(Test timed out in 5000ms.,:122:3),与本 PR 改动面无交集。

两点补充信息(让行 ≠ 不提供读数):

  1. 该签名已进台账:维护者 2026-08-07 授权,队列管家 Routine(三仓总管):合并队列健康专责化 —— flaky 签名分诊 / 原样重投 / 新签名拦截 / 跨仓 pin 链停滞观测(座位 Routine 化第二例,维护者 2026-08-06 拍板) #5810 正文 objectstack 表已新增该行(「已知 flaky,原样重投」,flaky(service-datasource): datasource-pool-support.test.ts 的「sqlite WITHOUT a pool still builds」在合并队列全量跑间歇红 —— 今日已踢 2 个不相关 PR(#5999、#5973) #6044 在案)。⇒ 后续同签名可直接引台账,不必每次重新论证。台账行同时写明了与「已修 5000ms 行」(flaky: spec/src/cloud/tenant.test.ts 的 #4739 导出面用例贴着 5s 超时 —— 今晚已两次把不相干的 PR 踢出合并队列 #4796/fix(spec): 给 packages/spec 的 vitest 设 testTimeout 60s —— 止血,不再把无关 PR 踢出合并队列 (#4850) #4856)的区分判据:根因是 service-datasource 无 vitest 配置走默认 5000ms,而 fix(spec): 给 packages/spec 的 vitest 设 testTimeout 60s —— 止血,不再把无关 PR 踢出合并队列 (#4850) #4856testTimeout: 60_000 逐包落在各自 vitest.config.ts,结构上覆盖不到该包
  2. ⚠️ 本 PR 自身另有一条红,与队列踢出无关:Validate Package Dependencies completed/failure(job 92732123172)—— OSV scanner 扫出 7 个既有漏洞(js-yaml 3.15.0/4.3.0 ×2、mermaid 11.16.0 ×5),Exit code: 1红因是仓库既有依赖状态,不是本 PR 引入;本 PR 只是因为改了 package.json触发了这条路径过滤的 workflow(今晚已落地的 fix(objectql): 新建父行时把 count/sum 型 summary 汇总初始化为 0 (#5749) #6013/fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) #6047/docs(spec): FieldWidgetPropsSchema 的 required/error JSDoc 不再教 objectui#3222 裁掉的双份显示 #6062 都没跑它)。是否阻塞由车道判断,本座位仅报读数。

Generated by Claude Code

Merged via the queue into main with commit b030c9a Aug 7, 2026
27 of 46 checks passed
@hotlong
hotlong deleted the claude/issue-5471-ban-empty-changeset-gate branch August 7, 2026 01:25
os-zhuang pushed a commit that referenced this pull request Aug 7, 2026
… 在飞行途中落地

新门禁 `scripts/check-empty-changeset.mjs` 明确判定:PR 新增的空 frontmatter
changeset 是违规。本 PR 的 `.changeset/type-check-debt-ledger-ratchet.md` 正是
「A added, empty at head」这一行,门禁在合并树上逐字点名了它。

按门禁给的两条路选:本 PR 只动 dev scripts / CI(`scripts/`、`.github/workflows/`、
`package.json`、`AGENTS.md`),不发布任何包 —— 走 route 2:删掉 changeset,改用
`skip-changeset` 标签。空 changeset 名不到任何包,正文到不了任何 CHANGELOG,却是
changesets/action 的真实输入(全空集会让 Release 静默且绿地空跑,即 #4898);标签
不产生输入,因此严格更优。

原 PR body 的「## changeset」一节因此过期,更正写在正文「裁决落地」一节里,原节
按接手协议不改写。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants