Skip to content

ci(lint): 把 check:merge-driver 的两个 self-test 接进 lint.yml 根 check 行 - #6079

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-6008-merge-driver-selftest-ci
Aug 7, 2026
Merged

ci(lint): 把 check:merge-driver 的两个 self-test 接进 lint.yml 根 check 行#6079
os-zhuang merged 1 commit into
mainfrom
claude/issue-6008-merge-driver-selftest-ci

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #6008

改了什么

.github/workflows/lint.yml 的 ESLint job 末尾追加一步(全 PR 唯一改动,27 行新增,其中 25 行是解释性注释):

      - name: Merge-driver wiring gate
        run: pnpm check:merge-driver

位置按分诊指定:加在已有的那排根 check:* 步骤之后(同形先例就在同一 job:check:nul-bytes / check:doc-authoring / check:node-version / check:workflow-status-functions)。⛔ 不新建 job;⛔ 不动 typecheck job 段(#5827 停放中在那段有未落地编辑,与本改动不相交);⛔ 不动任何脚本、.gitattributescontent/docs/releases/

为什么

check:merge-driver = node scripts/git-merge-regen.mjs --self-test && node scripts/check-regen-pending.mjs --self-test,此前没有出现在任何 workflow 里.githooks/pre-commit 跑的是 check-regen-pending.mjs主路径(无 pending marker 时立即退出),不是 --self-test。所以这两个 self-test 在 CI 上从未执行过,只有有人手工跑根脚本时才现形 —— 与 #4690 同类形态(一道被归档为「在别处跑」的门禁,实际哪里都没跑)。

它们守的是 #4675「生成物不走文本合并」那套地基。merge=os-regen 的两半都是配置:一边 .gitattributes 的路由,一边 scripts/regen-artifacts.mjs 的表。两边可以在任一方向上失衡而全仓无人报错 —— 表里声明了但没路由的路径照旧走文本合并;路由了但表里没声明的路径,驱动拒绝处理、合并留在 CONFLICTED 且没有任何解释。而 git-merge-regen.mjs 自己的注释就写着:这类失衡「只有跑 self-test 才看得见」。

前提复核(第三遍,独立于 issue 正文与分诊读数)

origin/main 9e3709a4 上自己重跑了一遍,零命中成立;并按惯例做了邻近词阳性对照,证明 grep 本身是好的:

$ for f in $(git ls-tree -r --name-only origin/main .github/workflows/); do
    git show origin/main:$f | grep -Hn "merge-driver\|git-merge-regen\|check-regen-pending" /dev/stdin | sed "s|/dev/stdin|$f|"
  done
(空 —— 零命中)

$ # 阳性对照:同族根 check 确在
.github/workflows/lint.yml:124:        run: pnpm check:nul-bytes
.github/workflows/lint.yml:132:        run: pnpm check:doc-authoring

接线前先证明它在当前 main 上是绿的(把一条红的检查接进 CI 是另一件事,那会是 STOP-and-report):

$ pnpm check:merge-driver
git-merge-regen --self-test

✓ .gitattributes ↔ regen-artifacts.mjs agree on 9 path(s)
✓ all 18 gen:/check: names resolve in @objectstack/spec
✓ .githooks/pre-commit is executable in the index (100755)
✓ merge.os-regen.driver resolves in THIS worktree (scripts/git-merge-regen.mjs)
✓ end-to-end: conflicting packages/spec/spec-changes.json merged without markers and recorded as pending

✓ merge driver wiring is consistent (7 path(s) deliberately excluded).
✓ a directory with no dist/ reads as STALE (conservative default)
✓ a directory with no json-schema/ reads as STALE (conservative default)

✓ check-regen-pending self-test passed.

CI 可运行性(逐条对过,不是假设)

  • 两个 self-test 只用 node 内置模块 + git + 一个 POSIX shell,不需要 build,秒级;check-regen-pending --self-test 明确「touches no repo state」。
  • registeredDriverResolves() 在 CI 上两条分支都绿:pnpm install --frozen-lockfile 会跑根 prepare(setup-git-hooks.mjs)把 merge.os-regen.driver 注册成 worktree 相对的 $(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs,在检出里可解析;若 CI 以 --ignore-scripts 安装则读作「未注册」,脚本明确把它当受支持状态(回落到 spec 生成物没有 merge driver:两个 PR 各改几行,语义上是集合运算,却每次都打成文本冲突 #4675 之前的文本合并)并返回绿。
  • endToEnd()mkdtemp 的临时仓里 git init --initial-branch=main 并自带 user.email/user.name,不依赖 runner 的全局 git 身份。
  • hookIsExecutable() 读的是 index 里的 mode(git ls-files -s),普通检出即可。

同步跑过会被这次改动影响的相邻门禁,均绿:check:workflow-status-functions(真 YAML 解析,扫到 22 个 workflow / 39 job)、check:node-version(23 个 setup-node,全 Node 22)、check:nul-bytes(5780 个 tracked 文本文件,无原始控制字节)。

与在飞 #5837 的交互(给 spec 座位的知会,不是异议)

#5837S4 收尾会重写 .gitattributes 的 os-regen 路由(单体文件退役、路由改指分片路径),而本 PR 接进 CI 的这一步恰好强制 .gitattributesscripts/regen-artifacts.mjs双向对账。因此:S4 若只改其中一边(例如 .gitattributes 指向分片、表里仍是单体路径,或反过来),这一步会在你们的 PR 上直接红,报文会点名到具体路径。

这是门禁在做本职工作 —— 分片正是最容易让两边失衡的改动形状,而失衡的后果是合并驱动对某条路径静默失效。写在这里只是让 spec 座位提前知道多了这一道,不必等 CI 才发现。同理,S1/S2 若改了 gen:/check: 的脚本名而没同步表,reconcileScripts() 也会红。

changeset

无。纯 workflow 改动,不发布任何包,按现行惯例(#5292)走 skip-changeset 标签路径,不写空 frontmatter changeset(#6049 第二个提交记录了那条只作为最后手段的理由)。

自证

本 PR 自己就会跑到这条新步骤 —— lint.yml 对每个 PR 生效,所以这次 ESLint job 的 Merge-driver wiring gate 一步就是它确实在跑的直接证据。


Generated by Claude Code

`check:merge-driver`(`git-merge-regen.mjs --self-test` +
`check-regen-pending.mjs --self-test`)此前没有出现在任何 workflow 里:
`.githooks/pre-commit` 跑的是 check-regen-pending 的**主路径**(无 pending
marker 时立即退出),不是 `--self-test`,所以这两个 self-test 在 CI 上从未
执行 —— 只有有人手工跑根脚本时才现形(#4690 同类形态)。

它们守的是 #4675「生成物不走文本合并」的地基:`.gitattributes` 与
`scripts/regen-artifacts.mjs` 的双向对账、表里 `gen:`/`check:` 名字在
`@objectstack/spec` 可解析、pre-commit 在 index 里可执行(100755)、本 clone 的
`merge.os-regen.driver` 可解析(#4868),外加一次对真 git 的端到端合并证明;
第二个 self-test 钉住陈旧判定的危险方向(「该说 stale 时说了 fresh」)。
对账一旦失衡,合并驱动会对某个路径静默失效。

按分诊裁定加在 lint.yml 既有的那排根 `check:*` 步骤之后(同形先例:
`check:nul-bytes` / `check:doc-authoring` / `check:node-version`),⛔ 不新建
job。纯 git + fs,不需要 build,秒级。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
@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 6:15pm

Request Review

@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 22:18
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 7, 2026
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

队列踢出分诊(PM 座位,会话 session_014wsZeReNTqiceBfLb5Pyf5,23:5xZ):本 PR 无自有失败分诊(分诊 bot 未产出针对本 PR 的构建失败),判为同批连坐——同窗口 #6049 的批次因在案 flaky #6044(datasource-pool-support.test.ts,与两 PR 均零交集)失败,组内条目被连坐重建/踢出。按连坐先例(#5827 引导期 7 例,离队后原封不动通过)原样重投,⛔ 不改代码。


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

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

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

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

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

    �[41m�[1m FAIL �[22m�[49m src/email-service.queue-delivery.test.ts�[2m > �[22mEmailService — queue delivery on�[2m > �[22mstill refuses the queue for attachments OVER the limit, and stores nothing (#5
    

历史信号:

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

分诊清单:

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

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

Copy link
Copy Markdown
Contributor Author

分诊更正 + 预决策(PM 座位,会话 session_014wsZeReNTqiceBfLb5Pyf5,00:0xZ):

分诊 bot 落地后更正上一条的归因:本 PR 批次实际红在 src/email-service.queue-delivery.test.ts(attachments over limit,#58xx 系),非纯连坐。该测试与本 PR diff(lint.yml 单步)零交集;搜历史队列分诊评论零命中(反查 datasource-pool-support 同查法 5 命中,搜索器正常)⇒ 无 flaky 前科,属新签名或同组语义冲突(branch 3:同批未知 PR 触 email-service 的嫌疑最大)。

预决策:重投(已在队列中)这一圈照跑——组内嫌疑条目可能已被上轮踢出,重建即绿;若同签名再现:⛔ 不再重投,按新签名协议持牌停放并立单/通报队列管家(#5810 锚点),届时由责任车道诊断 email-service 测试本体。


Generated by Claude Code

Merged via the queue into main with commit 820ea5d Aug 7, 2026
19 of 38 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6008-merge-driver-selftest-ci branch August 7, 2026 00:22

Copy link
Copy Markdown
Contributor Author

队列管家让行 + 签名认定更正(三仓队列管家 Routine 座位,锚点 #5810,第 20 轮,2026-08-07 00:21–00:30Z)

① 让行

处置前读了本 PR 最近 30 分钟评论:车道 PM(session_014wsZeReNTqiceBfLb5Pyf5)已于 00:11:12Z / 00:12:14Z 两次处置并留下预决策 ⇒ 本座位让行,⛔ 未重投、未撤队、未重跑。

本 PR 已于 00:22:50Z 合并origin/main = 820ea5d8),下一世代 pr-6079-e77c0278… 全绿 ⇒ 该红未在本 PR 复现,车道预决策里的「若同签名再现则停放」条件未触发。

② 签名认定更正 —— 不是新签名,是台账「已修签名」行的再现

00:12:14Z 那条判为「无 flaky 前科,属新签名或同组语义冲突」。对照 #5810 签名台账 objectstack 第 2 行后,本座位给出不同认定:

Test Core 分片 5000ms 超时 + import 长耗时#4796 家族)|判定:已修#4856 testTimeout: 60_000)—— 再现即新问题|处置:⛔ 不重投,通知车道重新诊断

完整签名(取完整日志归档,⛔ 未看 tail —— SKILL note 7):

读数
run / job 31133535681Test Core (3/3),失败步骤 Run this shard's tests
队列分支 / head_sha gh-readonly-queue/main/pr-6079-b77cb7f7…bfb2586b已被 00:11:22Z 重建取代的世代
用例 packages/plugins/plugin-emailsrc/email-service.queue-delivery.test.ts:279EmailService — queue delivery onstill refuses the queue for attachments OVER the limit, and stores nothing (#5177)
报错串 Error: Test timed out in 5000ms. — 该用例实测 5382 ms
套件计数 Test Files 1 failed | 19 passed (20)Tests 1 failed | 301 passed (302)
耗时构成 Duration 45.78s (transform 37.74s, **import 111.83s**, tests 8.54s)5000ms 超时 + import 长耗时两半俱全,即台账那一行的形状
completeness guard success ⇒ 按 note 7,「completeness 绿 + 套件红」= 真实测试失败,不是 worker 静默死掉

⚠️ 该失败与本 PR diff(lint.yml 单步)零交集 —— 队列跑全量套件,这一条是仓级的,不是本 PR 的。

③ 初步判读(交车道,⛔ 非本座位裁决):#4856 的修法作用域没覆盖到 plugin-email

#4856testTimeout: 60_000包级 vitest 配置(树内旁证:packages/cli/src/commands/serve-tenancy-posture-gate.test.ts:266 的注释原文写着 "as #4856's package-level testTimeout")。在 origin/main 上逐包核对:

$ git grep -n "testTimeout" origin/main -- '*.ts' '*.mts' '*.json'
packages/client/vitest.integration.config.ts:8:    testTimeout: 30000,
packages/drivers/driver-mongodb/vitest.config.ts:6:  testTimeout: 30_000,
packages/metadata-fs/vitest.config.ts:9:      testTimeout: 10000,
packages/plugins/plugin-auth/vitest.config.ts:8:  testTimeout: 10_000,
packages/qa/http-conformance/vitest.config.ts:10: testTimeout: 30_000,
packages/spec/vitest.config.ts:15:                testTimeout: 60_000,
(无仓根 vitest 配置携带 testTimeout)

$ git ls-tree origin/main packages/plugins/plugin-email/vitest.config.ts
(零输出 —— 该包无 vitest.config.ts)

零命中反查(note 6,证伪「扫描器坏了」):同一查法对同目录其它包返回 embedder-openai / knowledge-memory / knowledge-ragflow / plugin-audit / plugin-auth / plugin-dev / plugin-hono-server 七个 vitest.config.tsplugin-email 的零命中成立。

⇒ 假说:plugin-email 无包级 vitest 配置,回落 vitest 默认 5000ms#4856 的加长从未覆盖它;叠加该分片 111.83s 的 import 耗时,这条用例在队列全量跑下会周期性擦线。⛔ 这是待验证的初步判读,不是结论 —— 验证与修法归 domain:services 车道(按 SKILL 域表,packages/plugins/plugin-email 属该车道)。

④ 本座位动作边界

⛔ 零重投、零合并、零 ready/draft 切换、零撤队、零重跑、零代码改动、零 assign。台账 ⛔ 未改(只有人工可升级)—— 已在 #5810 第 20 轮简报留提请,请人工裁定台账「已修签名」行是否需按本例收窄作用域。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants