Skip to content

update 路径的 stripReadonlyFields 删除的是 hook 已覆写的当前值而非调用方提交的值 —— 调用方回传 readonly 键会连坐抹掉 beforeUpdate hook 的写入,可落出 status=published 而 published_at=null 的行(17.0.0-rc.2) #5591

Description

@yinlianghui

Part of objectstack-ai/hotcrm#788

现象

suppliedKeys 在 hook 运行之前从调用方 payload 快照,而 stripReadonlyFieldsbeforeUpdate hook 之后执行、且对每个「readonly 且 ∈ suppliedKeys」的 key 做 delete result[name] —— 删的是当前值,而当前值可能已被 hook 覆写。于是「调用方回传了该 readonly 字段」这一件事,会连带把 hook 在同一次写里打的戳一起抹掉。

const suppliedKeys = new Set(Object.keys(opCtx.data ?? {}));
…
await this.triggerHooks("beforeUpdate", hookContext);
…
hookContext.input.data = stripReadonlyFields(updateSchema, preRo, suppliedKeys, …);

实测(pinned 17.0.0-rc.2,真 ObjectQL + InMemoryDriver + 真 crm_knowledge_article schema + 真 hook + sys_fetch_previous_update 复刻,非系统上下文)

draft -> published,非系统 update,payload 整记录回传(含 readonly 的 published_at):
=> 落库 status = "published", published_at = null, last_reviewed_at = 2026-08-05T…

hook(knowledge_article_publish_timestamps)在首发转换上写入了 published_at = nowIso,随后剥离因「调用方 supplied 了这个 key」把 hook 刚写的值一起删掉。结果是一篇 status 为 published、published_at 为 null 的文章——按 published_at 排序/过滤的每个面(列表、报表)对这行都无定义。

可达性:REST/集成调用方「读出整记录 → 改一个字段 → 整记录写回」是常见写法;console 表单不回传只读字段所以 UI 路径不触发。

与在案记录的边界(先搜后立,均已读过)

期望

剥离作用于调用方提交的值而非执行时的当前值。实现方向任选:在 hook 之前剥离(caller 输入在进 hook 前就干净);或剥离时跳过「hook 已改写过的 key」(比较剥离时值与 supplied 快照值);或把 supplied 快照与 hook 写入分账。任一方向下,上面的实测都应落出 published_at = nowIso 而非 null。

发现过程:objectstack-ai/hotcrm#780 / PR objectstack-ai/hotcrm#789 的实测(探针脚本未入库,复现步骤在 hotcrm#788)。hotcrm 侧对照实测已证明该行为在 #780 修复前后完全一致(非新引入)。

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions