Skip to content

[观察] autonumber builder 不注入 readonly: true —— readonly 契约的「表单不可编辑」那一半对单号字段不成立 #5628

Closed

Description

@os-zhuang

性质

观察类(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 座位判断)

  1. builder 直接注入 readonly: true(issue [17.0-rc2验收] autonumber 字段可被普通调用者改写:POST 提交显式值绕过序列、PATCH 直接改号落库 —— readonly 剥离不保护 type:'autonumber' #5503 建议方向的后一种)。需要确认注入后
    stripReadonlyForInsert 入口剥离、isPreservableUnderAudit(system !== true 分支)
    与既有的 authorable-surface / 表单基线不会被动出别的语义。
  2. 不动 builder,改为在表单/渲染契约侧把"运行时拥有的字段类型"与 readonly 并列处理,
    与 PR fix(objectql): autonumber 是运行时拥有的字段,写路径不再接受调用者提交的单号 (#5503) #5627 在引擎侧引入的 isRuntimeOwnedField 判定同源。

无论走哪条,都不替代引擎侧防线:入口剥离对 sys_ / managedBy 平台对象是豁免的,
而且绕过 DataProtocol 直接调用 engine.insert 的路径不经过入口。

发现于 #5503 的实现过程(PR #5627),按 Prime Directive #10 单独记录,未在该 PR 内扩大范围。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    发现分诊(#4949 纪律):持有,保留 finding + domain:spec。

    一行理由:影响低(表单侧「二等公民」交互;数据正确性已由 #5503 / PR #5627 的引擎侧防线兜住),且修向未决(builder 注入 readonly: true vs 表单契约侧引入 runtime-owned 判定)。重启条件:PR #5627 合入后,由 spec 车道对两条方向拍板再判级。

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


    Generated by Claude Code

  2. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    发现分诊(#4949 纪律):晋级——摘 finding 换 pm:queue,保留 domain:spec。

    一行理由:前次持有评论记录的重启条件已触发——PR #5627 于 2026-08-05 23:38Z 合入 main,引擎侧防线落地,数据正确性半边闭合。按该评论的重启计划,方向 1(builder 注入 readonly: true)vs 方向 2(表单/渲染契约侧并列处理 runtime-owned)由 spec 车道派发时拍板——两向均落 spec 面,且 PR #5627 的「范围之外」段已确认两向都不与引擎侧防线冲突、互不排斥。维护者可否决。

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


    Generated by Claude Code

  3. hotlong commented on Aug 7, 2026

    @hotlong
    Contributor

    Release-board audit (maintainer-directed re-audit of non-board domain:spec items, 2026-08-07): adding target:v17 — metadata-protocol change, size S, low-risk: the data path is already safe (#5627); the remaining fix injects readonly: true into the autonumber builder output (or a runtime-owned form rule) — bounded spec change, no migration. Maintainer directive: protocol changes land in v17 unless large/risky. Triage seat may veto.


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions