fix(client)!: DeleteDataResult 声明它自称的 schema —— success,不是 deleted (#5638) - #5657
Conversation
…ed` (#5638) `DeleteDataResult` 顶着 `Spec: DeleteDataResponseSchema` 的注释,却把该 schema 的 `success` 声明成了 `deleted`。两个 delete 面(`client.data.delete` 与 project 作用域下的同名方法)都是 `unwrapResponse` / `_unwrap` 纯直通,这个接口 是对服务端响应体的一句声明、而非改写,所以声明必须是 schema 的那一句。 `deleted` 从未被任何 schema 声明、也从未被 `/data/:object/:id` 的任何服务端路径 返回:#5581 / PR #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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…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
首轮 CI 红,原因是我漏扫了一个消费方 —— 已修,如实记录8 个失败 job 同一个根因: Test Core (3/3)、Build Core、Dogfood 各腿都是在这一步倒的,没有第二个失败点(逐个 job 拉日志核过)。 我错在哪读取方测绘我是按派单枚举的路径扫的( 修法,以及为什么键名不动
输出键名保持 与 #5644 的关系:无交集#5644 整单落在 复验
另:本轮再次确认 Generated by Claude Code |
Fixes #5638
前提复核:两半都成立
基线
origin/main=30b784363,git merge-base --is-ancestor 7bf3d1ce2 origin/main确认已含 PR #5641。deleted?packages/client/src/index.ts:235-239,注释Spec: DeleteDataResponseSchema之下第三个键是deleted: booleansuccess?packages/spec/src/api/protocol.zod.ts:472的DeleteDataResponseSchema={ object, id, success }deleteData一直如此;ObjectQL 兜底packages/runtime/src/action-execution.ts:260现为return { object: params.object, id: params.id, success: true }(#5641 落的那一行)deleted的路径?DELETE /data/:object/:id(packages/rest/src/rest-server.ts:5046)直接res.json(await p.deleteData(...)),不经过任何改写第四行是本单的停手条件(「若某处真的在运行时拿到过
deleted,前提可能有变」)。它没有触发 —— 相反,测绘找到的两个下游读取方拿到的正是undefined,反过来加固了前提。改动
packages/client/src/index.ts——DeleteDataResult.deleted→success,注释保持指向 schema 并写清「这是对响应体的一句声明、不是改写」。⛔ 不留 deprecated 双键、不加deleted?: boolean过渡:消费端同时认两种拼写正是 contract-first 禁止的形状,而且旧键在任何部署上运行时都恒undefined,改名是揭错而非破坏在用行为。packages/client/src/data-delete-result-shape.test.ts(新) —— 见下。packages/client/src/client.hono.test.ts—— 这套 live server 套件此前有 create / get / find,唯独没有 delete;补上真实 HTTP DELETE 用例。packages/cli/src/commands/data/delete.ts——deleted: result.deleted→result.success。该处恒undefined,而JSON.stringify丢弃 undefined,所以这个命令声明已久的deleted键在任何一次os data delete --format json里都没出现过。输出键名保持deleted不变(理由见上方评论:同 payload 顶层的success是 CLI 信封的另一件事,撞名会把两个不同事实揉成一个;改键名属输出契约决定,留给维护者)。与os doctor把「cloud-connection 没装」和「装了但加载不了」当成同一件事 —— ledger 目录在场也照打 clean bill(#5412 假 PASS 的上一层) #5644 无交集 —— 那单整单落在commands/doctor.ts。content/docs/kernel/runtime-services/data-service.mdx——delete的返回值由含糊的「success marker / deleted ID payload」写实为{ object, id, success }。这一句正是 AI 作者会照着猜键名的地方。未触碰content/docs/releases/。changeset:
@objectstack/clientmajor(公开导出接口的破坏性重命名)+@objectstack/clipatch,升级须知点明「旧键从未在运行时有值,迁移就是把r.deleted改成r.success,服务端无需升级」,并写清 CLI 输出的可观察变化。测试怎么钉的:三层,且必须说清哪一层会动
照 #5641 的三层先例,但本单的层次分工与它相反,这一点不能含糊:#5641 改的是运行时构造的字面量,vitest 抓得住;本单改的是类型声明,而类型在 vitest 跑起来之前就被擦掉了。所以:
Exact< DeleteDataResult, DeleteDataResponse >双向可赋值断言。任一侧改名、多一个键、把某个键改成可选(包括未来有人想加回success: boolean; deleted?: boolean这种「过渡形状」),它就不再是true,check:test-typecheck(幽灵@ts-expect-error不止 spec:@objectstack/client也有 1 处落在 tsconfig 排除区内(全仓横扫结果) #5449/fix(client): 让 client 测试层真的进 tsc,那处@ts-expect-error不再是幽灵检查 #5546)报错。新文件不在test-typecheck-debt.json里,按该配置的口径必须零错误。r.success、safeParse绿、逐字断言键集['id','object','success'](z.object会剥未知键,单靠 parse 证不了没有残留的deleted—— fix(runtime): callData 的 delete 兜底返回 spec 声明的{object, id, success},与 protocol 路径同形 (#5581) #5641 记过的那条注意事项)。success」:喂一个 off-spec 的{object, id, deleted: true},断言r.success仍是undefined、r.deleted原样透出。这条钉的是我们没有在消费端加body.success ?? body.deleted。将来谁加了容错读,红在这里。client.hono.test.ts里 create 一条再client.data.delete,读deleted.success、断言键集、并回查find确认记录真的没了(一个没人交叉验证的 success 标志是世上最容易保持绿的东西)。这条不是自证:该路由走的是protocol.deleteData,不是该套件顶部那个 broker shim,所以断言的是服务端自己的响应体。CLI 侧未新增用例:改名之后,
data/delete.ts已经不可能再拼错这个键 —— 拼错就是@objectstack/cli#build编译错误(首轮 CI 演示的正是这一点)。结构性阻断比再加一条断言更强。反向验证:方向先判后跑,而预判的方向不是模板里的那一种
预判:把
success改回deleted后 ——pnpm --filter @objectstack/client typecheck→ 红(新 pin 文件 2 处 + hono 用例 1 处);vitest run→ 仍然全绿。因为类型被擦除,而 client 对响应体不做任何改写:mock 与真实服务端给什么,断言就读到什么,与声明成哪个键无关。实测,与预判一致:
这一层要如实说:本单没有「回退后测试变红」这种常见方向可写 —— 单元测试在原理上看不见这个缺陷,这恰恰是它能在三个面上活到今天的原因(client 自己没有 delete 响应体的用例;CLI 那两行没有任何用例;objectui 的用例喂的是自己编的键)。会动的只有类型门。把 vitest 的绿写成「验证通过」会是一份形状对、内容假的证据,所以这里写明它不动、以及为什么不动。
.deleted读取方测绘(跨仓;首轮漏过一处,已补全)DeleteDataResult全仓 5 处引用(类型定义 + 两个面各 2 处)+ 文档一处引用其名。具名字段读取方仓内只有一个:packages/cli/src/commands/data/delete.ts—— 首轮被我漏掉,CI 抓出,已修(见上)。补扫全仓确认再无第三处(meta/delete.ts:61/63的result.deleted属meta.deleteItem,另一个类型、不受影响)。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,未动。client-react的data-hooks.tsx:291以as any直通、不读键。cloud仓零命中。objectui 命中一个真实受害者,已另立单(见下),本 PR 不跨仓改:它的修复依赖本包发版后类型才对得上,今天改会因已发布的 client 类型仍声明
deleted而 TS 红,而绕过它只剩as any—— 正是本单禁止的形状。界外发现
result.deleted—— 服务端从不返回该键,delete 的 MutationEvent 从未发出、返回值恒 undefined;测试绿是因为 fixture 自己编了这个键 objectui#3412(新立,未标签、未指派) ——ObjectStackDataSource.delete()(packages/data-objectstack/src/index.ts:1438/1441)拿result.deleted当守卫发MutationEvent、并把它作为Promise< boolean >的返回值。对真实服务端它恒undefined,所以单条删除的 mutation 事件从未发出过(订阅方的列表/计数删除后不刷新),方法也恒返回undefined。它的套件之所以绿,是因为onMutation.test.ts:83-84的 fixture 自己 mock 了{ deleted: true }/{ deleted: false }—— 一个没有服务端会产生的响应体;validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 那一族(fixture 拼写了不存在的键,于是守卫已死而断言照绿)的又一个标本。已在单里写明该 fixture 属「整条替换」而非「改拼写」,并挂Blocked-by: #5638。立单前按关键词 + 文件路径搜过 objectui 开/闭 issue,零重复(docs(adr): ADR-0087 — metadata protocol upgrades for AI consumers (conversion over notification, replayable migration chain) #2582 同族不同因,已在正文区分)。pnpm --filter ... build会改写受版本控制的packages/spec/authorable-surface.base.json(重锚baseRev+ 3 个键)。已确认是既有已知问题check:authorable-surface在--check模式下也会重写authorable-surface.base.json—— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358 / os-regen 驱动指示的gen:schema在 merge 未 commit 时运行,会把 authorable-surface 锚点倒退回旧 merge-base —— 生成器写入、门全绿、静默撤销 main 的锚点推进 #5370 的同一现象,故未重复立单;两轮都已git checkout --丢弃该副作用,提交里不含它。验证(第二轮,CI 24 项 0 失败)
pnpm --filter @objectstack/client test:18 files / 229 tests 全绿,exit 0(新文件 6 条 + hono delete 1 条)pnpm --filter @objectstack/client typecheck:绿 ——tsc --noEmit无输出,check:test-typecheck: OK(债务台账仍是既有的 3 files / 6 errors,未增)pnpm --filter @objectstack/cli test:82 files / 812 tests 全绿,exit 0;typecheck绿pnpm --filter '...@objectstack/client' build:client + 全部 6 个下游(cli、client-react、app-todo、app-crm、app-showcase、qa/dogfood)全绿 —— 这次是按类型的消费半径扫的,不是按被改的包node scripts/check-nul-bytes.mjs:OK;另对全部改动文件做grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'自扫,全净packages/runtime、packages/spec、packages/cli/src/commands/doctor.ts(os doctor把「cloud-connection 没装」和「装了但加载不了」当成同一件事 —— ledger 目录在场也照打 clean bill(#5412 假 PASS 的上一层) #5644 在飞)、content/docs/releases/🤖 Generated with Claude Code
https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh