Skip to content

hotfix(runtime): route #5138 test engine doubles through assertEngineDeleteDispatch - #5615

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5138-engine-double-gate-fix
Aug 5, 2026
Merged

hotfix(runtime): route #5138 test engine doubles through assertEngineDeleteDispatch#5615
baozhoutao merged 1 commit into
mainfrom
claude/issue-5138-engine-double-gate-fix

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Hotfix / 解堵单。跟进 #5138 与已合并的 PR #5584

packages/runtime/src/action-execution-calldata-not-found.test.ts(PR #5584#5138 新增)里两个 fake ObjectQL 引擎的 delete() 没有走 assertEngineDeleteDispatch,合入 main 后 check:engine-double-contract(挂在 ESLint job)转红,每个后续 PR 的 ESLint 都跟着红(实例:PR #5601 job 92432801994)。

基于最新 origin/main @ a7b854f19 的新分支/新工作树,未复用已合并的旧分支。

修法:按门禁处方第一条,无例外

两处 fake 的 deleteassertEngineDeleteDispatch(opts) 开头,并用它返回的 by-id dispatch 里的 id 作为 store 键,而不是自己再从 opts.where.id 取一次 —— 这样 fake 的键来源也是生产者的标量提取逻辑,而不是第二份手抄。

@objectstack/objectql 已经是 @objectstack/runtimedependencies 条目(action-execution.ts 本来就从它导入 resolveActionHandlerKeys 等),所以处方括号里的「若包没有则加 devDependency」不适用,manifest 与 lockfile 均无改动。

没有走 MEASURED baseline 例外,因为没有理由走:两处 fake 都只被 { where: { id }, context? } 形状的调用命中(兜底侧是 callData 的 delete 分支,protocol 侧是 deleteData 自己的 by-id 删除),都是 by-id,加了断言后 25 条用例全绿 —— 不存在「加上就红」的情况需要申报。

这与 #5138 的取舍一致且互补,不矛盾:那个 PR 刻意不读 ql.delete返回值(因为 IDataEngine.delete 只声明 Promise< any >,读它是读契约没承诺的信号);本 PR 收紧的是 fake 的入口谓词。断言还顺带把一件事变成被证明的:callData 兜底发出的是标量 by-id 删除,即真引擎会执行的形状,而不是 #4434 那种真引擎会 500、fake 却照单全收的谓词形删除。

验证

pnpm check:engine-double-contract
  check-engine-double-contract: OK — 26 pinned, 31 in the DEBT ledger, 1 exempt
  gate exit: 0
  (本文件现在列在 pinned 中;--self-test 是该脚本第一段,已随之跑过)

pnpm --filter @objectstack/runtime exec vitest run src/action-execution-calldata-not-found.test.ts
  Test Files  1 passed (1)
       Tests  25 passed (25)

反向验证(方向先判后跑):预判为「还原两处 fake 的 delete → 门禁红回同一条,而 25 条用例保持全绿(断言对 by-id 调用是行为中性的)」。实跑两条都成立:

x PINNED: packages/runtime/src/action-execution-calldata-not-found.test.ts declares 2 engine
  double(s) whose delete() does not route through assertEngineDeleteDispatch (lines 76, 115). …
check-engine-double-contract: 1 problem(s).
gate exit: 1

同一条规则、同样 2 个 double(行号因新增的注释与 import 而从 69/102 变为 76/115)。用例侧行为中性也已被证实:同一批 25 条在加断言前(merge 后的 main)与加断言后都是 25/25 绿。

机制记录:为什么会漏 —— 与派单假设不同,我的 PR 的 ESLint 当时不是绿的,它是红的

派单里问的是「为什么你 PR 自己的 ESLint job 当时是绿的(base 时序?merge_group 检查集差异?)」。查了实际的 check runs,这个前提不成立,如实更正:

PR #5584 的 ESLint job(id 92425566733)结论是 failure,19:49:00Z 开始、19:53:08Z 就已经红了,而 PR 是 19:48:54Z 开的 —— 门禁在 PR 打开后 4 分钟就报了警,报的就是这条,连行号都一样:

x PINNED: packages/runtime/src/action-execution-calldata-not-found.test.ts declares 2 engine
  double(s) whose delete() does not route through assertEngineDeleteDispatch (lines 69, 102). …
check-engine-double-contract: 1 problem(s).
##[error]Process completed with exit code 1.

(job log 原文,与 PR #5601 上看到的是同一条。)

所以没有 base 时序缺口,也没有 merge_group 检查集差异要解释 —— 门禁没漏,它按时抓到了。真正的机制问题在别处,而且是两条:

  1. 这个 PR 是在 ESLint 已红约 19 分钟的情况下被合入的(ESLint 19:53:08Z 红;合并后才跑的 Close issues referenced in other repositories 在 20:12:31Z)。同一 PR 上 Test Core / Build Core / TypeScript Type Check 等 23 个检查全绿,只有 ESLint 一个红。也就是说,red-ESLint 没有挡住合入 —— 要么 ESLint 不在分支保护的必需检查集里,要么这次合并绕过了它。车道要防再发,该查的是这一条,我没有权限读分支保护配置,所以只陈述证据、不臆断是哪一种。
  2. 我自己的流程缺口(认领):我 push 完开了 draft PR 就直接返回了结构化报告,没有回看自己 PR 的 CI 结论。哪怕分支保护是松的,只要我等那 4 分钟,这条红就会在我手上被发现并在合入前修掉,不会变成解堵单。这一条我记下:今后 PR 开完要等 CI 收敛再交报告,不能把「本地绿」当成「CI 绿」。

范围

只改这一个测试文件(import + 两处 fake 的 delete)。无 changeset —— 纯测试双的契约收紧,不改任何产品行为(#5138 的行为变更 changeset 已随 PR #5584 合入)。未触碰 content/docs/releases/。构建期 gen:schema 重新生成的 packages/spec/authorable-surface.base.json 已还原,不在本 PR 内。


🤖 Generated with Claude Code

https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh


Generated by Claude Code

…eteDispatch

PR #5584(#5138)新增的 packages/runtime/src/action-execution-calldata-not-found.test.ts
里两个 fake ObjectQL 引擎的 delete() 没有走 @objectstack/objectql 的
assertEngineDeleteDispatch,合入 main 后 check:engine-double-contract
(挂在 ESLint job)转红,阻塞后续所有 PR 的 ESLint。

按门禁处方第一条修:两处 delete 以 assertEngineDeleteDispatch(opts) 开头,
并用它返回的 by-id dispatch 里的 id 作为 store 键,而不是自己再从
opts.where.id 取一次。@objectstack/objectql 已是 @objectstack/runtime 的
dependencies 条目,无需新增 devDependency,也不需要 MEASURED 例外。

这与 #5138 的取舍一致且互补:那个 PR 刻意不读 ql.delete 的返回值(引擎侧
IDataEngine.delete 只声明 Promise<any>),而这里收紧的是 fake 的入口谓词 ——
断言顺带证明了 callData 兜底发出的是标量 by-id 删除,即真引擎会执行的形状。

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

vercel Bot commented Aug 5, 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 5, 2026 8:30pm

Request Review

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

Copy link
Copy Markdown
Contributor Author

CI 已确认解堵(不只本地绿)

按上面机制记录里我给自己定的那条(「PR 开完要等 CI 收敛再交报告」),这次等了,结果是这条纪律当场又抓到一个:

检查 结论
ESLint(承载 check:engine-double-contract) success
Build Core / Dogfood Verify CLI / Console Pin Freshness / Check PR Size / filter 等 ✅ success
Check Changeset ⏭️ skipped(见下)
Test Core (1-3/3) / TypeScript Type Check / Temporal Conformance / Dogfood Regression Gate (1-2/3) 仍在跑,无一失败

FAILING: noneESLint 由红转绿,main 上的 ESLint 阻塞已解除 —— 这是本 PR 的验收点。

顺带被抓到的第二条:Check Changeset 一开始是红的

我在 PR body 里判断「纯测试双契约收紧,无 changeset」,这个判断本身对,但漏了门禁要求的表达方式:

##[error]This PR adds no changeset. If it releases nothing, apply the 'skip-changeset'
label (preferred); otherwise run 'pnpm changeset' and name the packages. An empty-frontmatter
changeset also passes this gate, but it is NOT equivalent to the label -- it is a real input
to changesets/action, and an all-empty set stalls the release silently and greenly (#4898).

已按处方首选项处理:打 skip-changeset 标签,Check Changeset 随即转为 skipped没有用空 frontmatter changeset 糊过去 —— 门禁自己写明那不等价,且正是 #4898 让 17.0.0-rc.2 静默停摆的原因。

这恰好是同一条教训的第二个实例:本地全绿 ≠ CI 全绿,而这次因为等了 CI,它在合入前就被修掉,而不是变成下一张解堵单。


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review August 5, 2026 20:34
@baozhoutao
baozhoutao enabled auto-merge August 5, 2026 20:34
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 2614aef Aug 5, 2026
32 of 33 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5138-engine-double-gate-fix branch August 5, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants