fix(runtime): callData 的 delete 兜底返回 spec 声明的 {object, id, success},与 protocol 路径同形 (#5581) - #5641
Merged
Merged
Conversation
…5581) `callData` 是 protocol 优先 + ObjectQL 兜底,两条路径对「删除成功」给的是两种 形状:protocol 回 `{object,id,success:true}`(合 `DeleteDataResponseSchema`), 兜底回 `{object,id,deleted:true}` —— `success` 缺失,`deleted` 从未被任何 schema 声明。按 spec 写的客户端在未注册 `protocol` 槽的精简装配上,会从一个 HTTP 200 里 读到 `success === undefined`,而调用方无从分辨自己走的是哪条路径。 规范只有一个,且 protocol 路径与公开 HTTP 文档都已站在 `success` 一侧,所以兜底 是唯一的偏离方。消费端兼容 `success ?? deleted` 两种拼写正是 contract-first 禁止 的形状,故修在生产方。 - `action-execution.ts` delete 兜底末行 `deleted: true` → `success: true` - `domains/data.ts` 那条把兜底形状写成规范的注释改正(同文件 get/update 两条 本来就是对的,只有 delete 这条对不上) - 测试钉住两条路径同形:schema safeParse + 跨路径逐字段相等 + 显式断言不带 未声明的 `deleted` 键(`z.object` 会剥掉未知键,单靠 parse 抓不到残留), 并覆盖 `/data` 与声明式端点执行器两个消费面 - `packages/mcp` 两处模拟 `callData('delete')` 的 test double 同步改口径 反向验证:把兜底改回 `deleted: true`,新增/更新的 6 条断言全红(fallback 侧), protocol 侧 parse 仍绿 —— 与动手前预判的方向一致。 Fixes #5581 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
上一提交的 `git add -A` 扫进了 `packages/spec/authorable-surface.base.json` —— 那是本地跑 `pnpm --filter ... build` 时 `gen:schema` 顺带重生成的产物 (把 `baseRev` 重锚到本工作树的基点,并带进 main 上此后新增的 `EmailServiceConfig` 三个键),与本单毫无关系。 该文件是 #4650/#5235 删除门禁的锚点,按其自述「只应由 gen:schema 从 git 解析 出的基线写入,绝不由被检查的这次构建写入」,PR 里不该出现它的改动。退回基点 版本。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 115 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 5, 2026 21:43
baozhoutao
pushed a commit
that referenced
this pull request
Aug 5, 2026
…5638) CI 抓到的漏网消费方 —— 我的读取方测绘按派单枚举的路径(client 自身测试、 client-react、examples、docs)扫,没有覆盖 `packages/cli`,而 `packages/cli/src/commands/data/delete.ts:65/68` 正是 `DeleteDataResult` 在 仓内唯一的具名字段读取方,`@objectstack/cli#build` 因此红。 该处 `deleted: result.deleted` 恒为 `undefined`,`JSON.stringify` 会把 undefined 丢掉 —— 也就是说这个命令声明了很久的 `deleted` 键,在 `os data delete --format json` 的任何一次运行里**都没有出现过**。现改读 `result.success`。 输出键名保持 `deleted` 不变:它是本命令自己的输出键,不是 protocol 的键; 而同一个 payload 顶层的 `success` 表达的是另一件事(CLI 信封的「命令完成」)。 把两者拼成同一个名字正是 #5641 点过的 `body.success` / `body.data.success` 混淆风险。改名属输出契约决定,已在 PR 里交维护者裁定。 与 #5644 无交集:那单整单落在 `packages/cli/src/commands/doctor.ts`。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 6, 2026
…ed` (objectstack-ai#5638) (objectstack-ai#5657) * fix(client)!: `DeleteDataResult` 声明它自称的 schema —— `success`,不是 `deleted` (objectstack-ai#5638) `DeleteDataResult` 顶着 `Spec: DeleteDataResponseSchema` 的注释,却把该 schema 的 `success` 声明成了 `deleted`。两个 delete 面(`client.data.delete` 与 project 作用域下的同名方法)都是 `unwrapResponse` / `_unwrap` 纯直通,这个接口 是对服务端响应体的一句声明、而非改写,所以声明必须是 schema 的那一句。 `deleted` 从未被任何 schema 声明、也从未被 `/data/:object/:id` 的任何服务端路径 返回:objectstack-ai#5581 / PR objectstack-ai#5641 之后 protocol 与 ObjectQL 兜底两条路径同形返回 `{object, id, success}`。因此 `r.deleted` 编译通过、运行时恒 `undefined` —— 改名是揭错,不是破坏在用行为。不留 deprecated 双键(消费端同时认两种拼写正是 contract-first 禁止的形状)。 - 新增 `data-delete-result-shape.test.ts`:类型层钉 `DeleteDataResult` 与 spec `DeleteDataResponse` 双向可赋值,加两个面的直通形状 + 「不合成 `success`」反钉。 - `client.hono.test.ts` 补上这套 live server 套件一直缺的 delete 用例:真实 HTTP DELETE,读 `deleted.success`,并逐字断言键集(`z.object` 会剥未知键, 单靠 parse 证不了没有残留的 `deleted`)。 - `data-service.mdx` 的 delete 返回值由「success marker / deleted ID payload」 写实为 `{ object, id, success }`。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh * fix(cli): `os data delete` 的 `deleted` 输出键读的是幻影键,改读 spec 的 `success` (objectstack-ai#5638) CI 抓到的漏网消费方 —— 我的读取方测绘按派单枚举的路径(client 自身测试、 client-react、examples、docs)扫,没有覆盖 `packages/cli`,而 `packages/cli/src/commands/data/delete.ts:65/68` 正是 `DeleteDataResult` 在 仓内唯一的具名字段读取方,`@objectstack/cli#build` 因此红。 该处 `deleted: result.deleted` 恒为 `undefined`,`JSON.stringify` 会把 undefined 丢掉 —— 也就是说这个命令声明了很久的 `deleted` 键,在 `os data delete --format json` 的任何一次运行里**都没有出现过**。现改读 `result.success`。 输出键名保持 `deleted` 不变:它是本命令自己的输出键,不是 protocol 的键; 而同一个 payload 顶层的 `success` 表达的是另一件事(CLI 信封的「命令完成」)。 把两者拼成同一个名字正是 objectstack-ai#5641 点过的 `body.success` / `body.data.success` 混淆风险。改名属输出契约决定,已在 PR 里交维护者裁定。 与 objectstack-ai#5644 无交集:那单整单落在 `packages/cli/src/commands/doctor.ts`。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5581
前提复核:成立
callData是 protocol 优先 + ObjectQL 兜底,两条路径对「删除成功」给的是两种形状:deleteData){ object, id, success: true }action-execution.tsdelete 分支末行){ object, id, deleted: true }success缺失,deleted未声明规范只有一个 ——
DeleteDataResponseSchema(packages/spec/src/api/protocol.zod.ts:472)声明{ object, id, success },deleted从未被任何 schema 声明。裁定方向(向 spec 收敛)在实现中额外得到一条独立佐证:公开 HTTP 文档一直就写的是
success。content/docs/protocol/kernel/http-protocol.mdx:649明文 “Delete returns aDeleteDataResponse: theobjectname, the recordid, and asuccessflag”,示例体也是{"object","id","success":true}。所以 spec、protocol 实现、公开文档三者一致,兜底是唯一的偏离方,收敛方向没有第二种读法。改动
packages/runtime/src/action-execution.ts—— 兜底 delete 分支末行deleted: true→success: true。删除行为本身(含 callData 的 ObjectQL 兜底路径对「记录不存在」给三种不同答案(get→200 null / update→500 / delete→200 deleted:true) #5138 落的「记录不存在 → 404RECORD_NOT_FOUND且不发出写」)一字未动,只改成功体这一个键。packages/runtime/src/domains/data.ts—— 那条// Spec: returns DeleteDataResponse = { object, id, deleted }改正为success。这是该文件 get/update/delete 三条路由注释里唯一与自己的 schema 对不上的一条(87/94 两条本来就对),它把兜底的off-spec形状当成了规范。action-execution-calldata-not-found.test.ts,fix(runtime): callData 的 ObjectQL 兜底对「记录不存在」统一答 404 RECORD_NOT_FOUND (#5138) #5584 落的同族) —— 新增#5581describe 与两个消费面的成功用例;engine double 的assertEngineDeleteDispatch接法(hotfix(runtime): route #5138 test engine doubles through assertEngineDeleteDispatch #5615)原样保留未回退。packages/mcp两处 test double —— 模拟callData('delete', …)返回值的remove()同步改口径。无断言读该键,纯粹是不给下一个读者留一份生产方已不再返回的形状。测试怎么钉的
z.object会剥掉未知键,所以单靠 schema parse 抓不到残留的deleted;反过来只比较两条路径也会在「两边一起漂」时保持绿。因此三种断言都下了:DeleteDataResponseSchema.safeParse绿;Object.keys(out).sort() === ['id','object','success']—— 未声明的deleted不得残留。消费面两个都覆盖:
/data走真实HttpDispatcher的 DELETE 200 体,以及声明式端点执行器(#5092/#5136)。后者顺带钉住了两个success是不同的事实:body.success是 HTTP 信封的 ok 标志(successAnswer),body.data.success才是DeleteDataResponse的「删除发生了」—— 这个形状最容易被读串,所以两个都不留作隐含。反向验证(方向先判后跑,结果与预判一致)
预判:兜底改回
deleted: true应当红,且只红 fallback 侧 6 条;此处无??链、不涉及 schema 具名拒绝,兜底是字面量构造,不存在 #5018 那类反转风险。实测:
deleted读取方测绘(本单最大回归风险,先测后动)全仓(
packages//examples//apps//packages/qa,含 dogfood 与 http-conformance)搜.deleted读取方,没有任何消费方从/datadelete 响应体里读这个键:domains/data.ts/domains/mcp.ts的remove/endpoint-executor.ts:434—— 三个callData('delete')调用点全是直通,不读键;packages/qa里 17 处DELETE /data/...只断言状态码与库内副作用,不读响应体的deleted;domains/automation.ts:472的{name, deleted:true}、http-dispatcher.test.ts:295、spec/api/automation-api.zod.test.ts—— 都是 automation flow 注销面,与本单无关,未动;protocol.delete-many.test.ts:18的{deleted:true}是 engine.delete 的返回(IDataEngine.delete→Promise< any >),不是callData的成功体,未动。分诊警示的结论:client 归一化与本单面无交集
packages/client五处if (res.status === 204) return { deleted: true }(index.ts3416 / 3467 / 3536 / 3590 / 3799)逐条核过路由,分别是 shares revoke、sharing rules delete、reports delete、report schedules unschedule、ai conversations delete —— 没有一条走/data/:object/:id。本单的 delete 面(client.data.delete,index.ts:4300/4851)走的是 200 + JSON 体,不经过任何 204 归一化。故收敛不牵动 client 公开返回形状,未扩面。测绘中另发现一条界外缺陷并已立单:
client的DeleteDataResult注释自称Spec: DeleteDataResponseSchema却声明deleted: boolean—— 该类型是纯直通声明,在装了 protocol 插件的常规部署上此前就已经是错的(TS 使用方写r.deleted编译通过、运行时恒undefined)。属@objectstack/client公开导出接口的破坏性重命名,blast radius 与 semver 判断都不同,故未搭车 → #5638。验证
pnpm --filter @objectstack/runtime test:97 files / 1428 tests 全绿pnpm --filter @objectstack/runtime typecheck:绿(tsc --noEmit无输出)pnpm --filter @objectstack/mcp test:8 files / 83 tests 绿;typecheck绿node scripts/check-nul-bytes.mjs:OK;另做grep -naP控制字符自扫,改动文件全净changeset:
@objectstack/runtimepatch,行为变化写了升级须知(仅影响未装MetadataPlugin的精简装配,DELETE /data/:object/:id、MCPdelete_record、声明式 delete 端点三个面的成功体键由deleted改为success)。未触碰content/docs/releases/、spec、packages/metadata-protocol、packages/client。🤖 Generated with Claude Code
https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Generated by Claude Code