性质
观察类(finding),不是今天用户会踩到的数据缺陷 。#5503 / PR #5627 已在引擎侧把
type: 'autonumber' 视为隐含 readonly,非 system 调用者提交的单号在派发给任何驱动之前
就被剥离,因此数据不会再被伪造 。这里记录的是同一契约的另一半没有对齐。
事实
packages/spec/src/data/field.zod.ts:779,FieldSchema.readonly 的 .describe() 把
这个键定义为两件事 :
Read-only — never editable in forms , AND server-enforced on BOTH write paths…
packages/spec/src/data/field.zod.ts:898 的 builder:
autonumber : ( config : FieldInput = { } ) => ( { type : 'autonumber' , ...config } as const ) ,
不注入 readonly: true。于是任何按 field.readonly 决定是否渲染成可编辑输入的
authoring / 渲染层,都会把 autonumber 字段当作可编辑字段对待 —— 而它的值现在必然被
服务端丢弃。
反查过的对照:仓内确实存在若干"计算字段"集合,但没有一个被表单层消费成 readonly ——
packages/verify/src/derive.ts:23 的 COMPUTED、
packages/plugins/plugin-audit/src/audit-writers.ts:188 的 COMPUTED_FIELD_TYPES
(审计跳过计算列)、packages/metadata-protocol/src/protocol.ts 的 clone 剔除。
packages/spec/src/data/object.form.ts 里 autonumber 只出现在类型下拉与
autonumberFormat 的 visibleWhen 上,没有可编辑性的分支。
后果(低)
一个用户在表单里看到可以输入的"单号"框,填了值、提交成功,回包里的单号却是序列发的另一个
号。写入路径会在 droppedFields 里如实上报、服务端也会留下一条带补救办法的 warn
(PR #5627 ),但渲染器未必把 droppedFields 呈现给用户。属于 #4632 那类"第二等公民"的
交互,不是数据正确性问题。
可能的方向(未决,交由 spec 座位判断)
builder 直接注入 readonly: true(issue [17.0-rc2验收] autonumber 字段可被普通调用者改写:POST 提交显式值绕过序列、PATCH 直接改号落库 —— readonly 剥离不保护 type:'autonumber' #5503 建议方向的后一种)。需要确认注入后
stripReadonlyForInsert 入口剥离、isPreservableUnderAudit(system !== true 分支)
与既有的 authorable-surface / 表单基线不会被动出别的语义。
不动 builder,改为在表单/渲染契约侧把"运行时拥有的字段类型"与 readonly 并列处理,
与 PR fix(objectql): autonumber 是运行时拥有的字段,写路径不再接受调用者提交的单号 (#5503) #5627 在引擎侧引入的 isRuntimeOwnedField 判定同源。
无论走哪条,都不替代 引擎侧防线:入口剥离对 sys_ / managedBy 平台对象是豁免的,
而且绕过 DataProtocol 直接调用 engine.insert 的路径不经过入口。
发现于 #5503 的实现过程(PR #5627 ),按 Prime Directive #10 单独记录,未在该 PR 内扩大范围。
性质
观察类(
finding),不是今天用户会踩到的数据缺陷。#5503 / PR #5627 已在引擎侧把type: 'autonumber'视为隐含 readonly,非 system 调用者提交的单号在派发给任何驱动之前就被剥离,因此数据不会再被伪造。这里记录的是同一契约的另一半没有对齐。
事实
packages/spec/src/data/field.zod.ts:779,FieldSchema.readonly的.describe()把这个键定义为两件事:
packages/spec/src/data/field.zod.ts:898的 builder:不注入
readonly: true。于是任何按field.readonly决定是否渲染成可编辑输入的authoring / 渲染层,都会把 autonumber 字段当作可编辑字段对待 —— 而它的值现在必然被
服务端丢弃。
反查过的对照:仓内确实存在若干"计算字段"集合,但没有一个被表单层消费成 readonly ——
packages/verify/src/derive.ts:23的COMPUTED、packages/plugins/plugin-audit/src/audit-writers.ts:188的COMPUTED_FIELD_TYPES(审计跳过计算列)、
packages/metadata-protocol/src/protocol.ts的 clone 剔除。packages/spec/src/data/object.form.ts里 autonumber 只出现在类型下拉与autonumberFormat的visibleWhen上,没有可编辑性的分支。后果(低)
一个用户在表单里看到可以输入的"单号"框,填了值、提交成功,回包里的单号却是序列发的另一个
号。写入路径会在
droppedFields里如实上报、服务端也会留下一条带补救办法的warn(PR #5627),但渲染器未必把
droppedFields呈现给用户。属于 #4632 那类"第二等公民"的交互,不是数据正确性问题。
可能的方向(未决,交由 spec 座位判断)
readonly: true(issue [17.0-rc2验收] autonumber 字段可被普通调用者改写:POST 提交显式值绕过序列、PATCH 直接改号落库 —— readonly 剥离不保护 type:'autonumber' #5503 建议方向的后一种)。需要确认注入后stripReadonlyForInsert入口剥离、isPreservableUnderAudit(system !== true分支)与既有的
authorable-surface/ 表单基线不会被动出别的语义。readonly并列处理,与 PR fix(objectql): autonumber 是运行时拥有的字段,写路径不再接受调用者提交的单号 (#5503) #5627 在引擎侧引入的
isRuntimeOwnedField判定同源。无论走哪条,都不替代引擎侧防线:入口剥离对
sys_/managedBy平台对象是豁免的,而且绕过 DataProtocol 直接调用
engine.insert的路径不经过入口。发现于 #5503 的实现过程(PR #5627),按 Prime Directive #10 单独记录,未在该 PR 内扩大范围。