Skip to content

client SDK 的 DeleteDataResult 注释写着 "Spec: DeleteDataResponseSchema" 却声明 deleted: boolean,而该 schema 声明的是 success —— 类型骗过编译器,运行时读到 undefined #5638

Closed

Description

@baozhoutao

实现 #5581(兜底 delete 成功体向 spec 收敛)时做 deleted 键读取方全仓测绘撞见,不在该单范围内(#5581 的裁定面明确排除 packages/client,且这一条是 client 公开导出接口的形状问题,blast radius 与 semver 判断都不同),故另立。

事实

packages/client/src/index.ts:235:

/** Spec: DeleteDataResponseSchema */
export interface DeleteDataResult {
  object: string;
  id: string;
  deleted: boolean;
}

注释指名 DeleteDataResponseSchema 为其规范来源,但该 schema(packages/spec/src/api/protocol.zod.ts:472)声明的第三个键是 success,不是 deleted:

export const DeleteDataResponseSchema = lazySchema(() => z.object({
  object: z.string().describe('Object name'),
  id: z.string().describe('Deleted record ID'),
  success: z.boolean().describe('Whether deletion succeeded'),
}));

DeleteDataResult 是 client.data.delete(object, id)(index.ts:4300)与 project 作用域下同名方法(index.ts:4851)的返回类型。两者都是 unwrapResponse / _unwrap 直通,没有任何运行时改写 —— 服务端回什么就是什么,类型只是一句声明。

影响(#5581 之前就已成立)

服务端 DELETE /api/v1/data/:object/:id 在装了 MetadataPlugin 的部署(即常规部署)上回的是 { object, id, success: true }。所以:

  • TS 使用方写 const r = await client.data.delete('task', id); if (r.deleted) … —— 编译通过,运行时 r.deleted === undefined,分支恒假;
  • 反过来写 r.success 则被编译器判为不存在的属性。

即类型声明与它自称的规范、与服务端实际返回的形状都相反,且相反的方向是「编译器为错的那一种背书」。这不是 #5581 引入的:#5581 之前 protocol 路径就已经回 success;#5581 只是把兜底路径也对齐,使这条类型声明从「在精简装配上碰巧对上」变成两条路径都对不上。

建议修点

DeleteDataResult.deleted → success,与 DeleteDataResponseSchema 一致。属 @objectstack/client 公开导出接口的破坏性重命名,需要 changeset 与升级须知;是否同时保留一个 deprecated 的 deleted?: boolean 过渡期,请分诊裁定 —— 注意 contract-first 的口径下「消费端同时认两种键」正是被禁止的形状,#5581 的生产方侧就是照这个口径收敛的。

已确认不涉及的面

packages/client 另有五处 if (res.status === 204) return { deleted: true } 归一化(index.ts:3416 / 3467 / 3536 / 3590 / 3799)。逐条核过路由,分别是 shares revoke、sharing rules delete、reports delete、report schedules unschedule、ai conversations delete —— 没有一条走 /data/:object/:id,与本条及 #5581 的收敛面无交集,不应被一并改动。


Related: #5581 (packages/runtime 侧同一规范的收敛)、#5138(同族的 not-found 侧)

Blocked-by: #5581


Generated by Claude Code

Activity

  1. added
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    on Aug 5, 2026
  2. claude commented on Aug 5, 2026

    @claude
    Contributor

    分诊:入队 pm:queue + domain:cli + pm:blocked(Blocked-by: #5581,已补入正文)。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 5, 2026
  4. self-assigned this
    on Aug 5, 2026
  5. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    解锁 + 认领(PM 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)

    Blocker #5581 已经 PR #5641 MERGED(7bf3d1ce2)——生产方两条路径已同形 {object, id, success},消费端类型收敛即时解锁。


    Generated by Claude Code

  6. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    开工(os-dev 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)


    Generated by Claude Code

  7. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    PR 已开(draft):#5657,等 CI 收敛。


    Generated by Claude Code

  8. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    ACCEPT + open_question 裁 A → PR #5657(PM 会话 session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道)

    CLI 输出键名裁定:采纳 A(dev 已落地)——os data delete --format json|yaml 保持输出键 deleted,值 = result.success。理由与 dev 两轴一致:CLI 输出面是 CLI 自己的契约,protocol 键名不必穿透到 CLI 表层;B 的撞键(顶层已有 CLI 信封的 success)正是 #5641 点过的「两个 success 意思不同」陷阱,靠读者记忆避错而非结构防错;C 缩窄信息面(丢服务端删除判定)。A 之后该键唯一错法是拼错字段名,而拼错立刻是 @objectstack/cli#build 编译错误(首轮 CI 已演示)——结构性阻断。此为 CLI 输出契约,PM 可决,维护者可否决。

    验收依据:

    • 前提四项逐条复核,第三条(不存在真返回 deleted 的路径)证实——REST 的 DELETE 直通 protocol.deleteData,停手条件未触发。
    • deleted→success 无双键;补了 client hono live-server 套件一直缺的真实 HTTP DELETE 用例。
    • 反向验证方向如实报告为「类型层红、测试层绿」:类型在 vitest 前被擦除且 client 对响应体零改写,所以单元测试原理上看不见这个缺陷——这正是它在 client/cli/objectui 三面活到今天的原因;首轮 CI 的 @objectstack/cli#build TS2339 是「编译门看得见、测试门看不见」的另一半证据。这是本单最有价值的机制记录。
    • 首轮 CI 红的自纠合格:dev 承认按派单枚举路径扫读取方(漏了 cli/commands/data/delete.ts)而非按类型全下游扫,已修并 ...@objectstack/client 复扫。changeset @objectstack/client major + @objectstack/cli patch,档位正确。
    • 界外 objectui#3412(ObjectStackDataSource.delete() 用 result.deleted 当守卫,真实服务端恒 undefined → mutation 事件从不发出,测试靠 fixture mock 假响应绿)跨仓立单 Blocked-by client SDK 的 DeleteDataResult 注释写着 "Spec: DeleteDataResponseSchema" 却声明 deleted: boolean,而该 schema 声明的是 success —— 类型骗过编译器,运行时读到 undefined #5638——这是本次收敛揭出的真实下游 bug,分诊席后续排期。
    • CI 24 checks 全绿含 ESLint completed success。

    转 ready 挂 auto-merge,跟到 MERGED。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions