Skip to content

service-automation: update_record reports success when written fields are silently stripped — no observability for dropped writes (split from #3356) #3407

Description

@os-zhuang

背景

#3356 的「次级问题(可拆单)」拆出。#3356主因(runAs:'user' 凭证空心 —— 触发上下文从不携带触发人的权限集/岗位)已由 #3389 修复,随 16.1.0 发布。本单只跟进剩下的可观测性缺口。

现象

update_record 节点无条件返回 success,即使请求写入的字段被服务端丢弃:

// packages/services/service-automation/src/builtin/crud-nodes.ts
const result = await data.update(objectName, fields, { where: filter, context: dataCtx });
return { success: true, output: { result, object: objectName } };

字段丢弃发生在数据层若干处,各自都是合法语义:

数据层确实打了 warn(packages/objectql/src/validation/rule-validator.ts:335),但那条日志落在服务端 logger,不进流程运行的步骤日志。作者看 run trace 只看到一条 3ms 的 success

这正是 #3356 里两条审批流的 stage 回写整链失效被掩盖的原因:sharingModel: 'public_read_write' 对象上对象级不拦,readonly 镜像字段被静默剥离,节点报 success 而 DB 真值恒空,极难察觉。

需要说明的是:#3356 的授权根因修好之后,「因为零权限集被剥离」这一类已经不再发生;但 readonly / readonlyWhen / FLS 这些语义上正确的剥离依旧静默,所以这条可观测性缺口独立存在,不随 #3389 消失。

期望

update_record(以及同理的 create_record)在「请求写 N 个字段、实际生效 < N」时,至少在步骤结果上带一条 warning,列出被丢弃的字段名。

success 与否可以维持不变(剥离本身是合法语义,不该升级成失败),关键是不要沉默

设计要点 / 待拍板

需要一个把「被剥离的键」从数据层回传到调用方的通道。今天 IDataEngine.update 的返回类型是 any,节点侧拿不到「哪些键没落」的结构化信息。可选方向:

  1. 引擎回传:update 结果里附带 droppedFields(或经 options 回填),节点直接读。最干净,但要动 data-engine 契约(packages/spec/src/contracts/data-engine.ts)。
  2. 节点侧 diff:比对请求的 fields 键集与返回行的实际值。脆 —— 返回行不一定回显全部字段,值相等也可能是巧合。不推荐。
  3. 日志路由:把数据层已有的 warn 通过 run-scoped logger 路由进流程步骤日志。侵入最小,但依赖 logger 上下文透传。

倾向 1 或 3,需要先拍板再动手。

边界:

  • runAs: 'system' 走系统上下文本就跳过 readonly 剥离,不受影响;
  • 批量 update(where 匹配多行)的措辞要留意 —— readonlyWhen 已经是「≥1 matched row」语义,不是逐行精确。

参考

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions