Skip to content

DroppedFieldsEvent.reason 只有 readonly / readonly_when 两值,写路径上新增的合法剥离对 onFieldsDropped / strictReadonlyWrites 不可见 #6437

Description

@baozhoutao

观察类发现(finding,不入队),来自 #6262 / PR #6433 的实施过程(PD #10)。不是今天用户会撞到的缺陷,是一处「声明的可观测面比实际剥离面窄」的漂移,记录下来免得只活在一条代码注释里。

事实

packages/spec/src/data/data-engine.zod.ts:228 起,DroppedFieldsEventSchema.reason 是闭合枚举:

reason: z.enum(['readonly', 'readonly_when'])

而 #3407 给这个 seam 的定位是通用的 —— 「写路径上被合法剥掉的、调用方提交过的字段」,因为调用方(典型是 flow 的 update_record 步骤)会逐字段汇报成功,而库里那一列根本没变(#4632 的二等形状:调用方以为落库了,数据库不同意,服务端日志是唯一痕迹)。#5126 的 strictReadonlyWrites 是同一 seam 的响亮那一半:要么静默可观测,要么整笔拒绝。

PR #6433 在 update 的 multi 分支新增了一次剥离(把派发已裁定「不是主键」的 data.id 从 SET 载荷去掉)。它是一次调用方提交过的、被引擎合法丢弃的字段,但它既不是 readonly 也不是 readonly_when,所以:

  • onFieldsDropped 监听器收不到这次剥离;
  • strictReadonlyWrites: true 的调用方不会因此被拒,它仍拿到一个成功返回。

PR #6433 里刻意没有硬塞进那两个值里(那会让 reason 说谎),改为记一条 warn,并把理由写在注释里:扩这个词表是 packages/spec 的改动,有 packages/spec/src/api/batch.zod.ts 与 .../api/protocol.zod.ts 两处协议响应消费者,不该搭引擎修复的车。本 issue 就是那条注释的外化。

为什么标 finding 而不是入队

若将来处理,面在哪

  1. packages/spec/src/data/data-engine.zod.ts —— DroppedFieldsEventSchema.reason 枚举 + 其 describe;
  2. packages/spec/src/api/batch.zod.ts / packages/spec/src/api/protocol.zod.ts —— 三处 droppedFields 响应字段的消费者;
  3. packages/objectql/src/engine.ts —— update 的 reportDroppedFields 调用点(by-id 与 multi 各一组),以及 PR fix(objectql): multi update 的 SET 载荷剥掉非 id 的 data.id (#6262) #6433 新增剥离块里那段「刻意不上报」的注释(改了要一并删);
  4. 生成物:packages/spec 的 api-surface / JSON schema 基线随枚举变动重生成。

关联:#3407(onFieldsDropped 的由来)、#5126(strictReadonlyWrites)、#4632(二等形状)、#3042 / #2948(现有两个 reason)、#6262 / PR #6433(触发本条的新剥离)。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Findings triage — verdict: HOLD (finding retained). Routing recorded: this belongs to domain:spec if and when it is promoted — see below; no domain:* applied while it is held, since the label is only consumed by dispatch.

    Why still held: the author's grading is right and re-checked rather than taken on trust. The new strip did not exist before PR #6433 — before it, the payload went into the SET clause verbatim, which was the #6262 data corruption — so no caller has lost a signal they previously had. The gap only bites a caller who has opted into onFieldsDropped or strictReadonlyWrites and wrote a non-id value into data.id and declared multi; a warn is emitted on that path. That is a narrowed observable surface on an author-error path, not a live defect.

    Why not promoted: the fix is vocabulary design, not a small edit — add a value, or split reason into category + specific cause — and it ships through three protocol consumers (packages/spec/src/api/batch.zod.ts, .../api/protocol.zod.ts) plus regenerated spec baselines. PR #6433 was right not to reuse readonly / readonly_when: that would make reason lie, which is strictly worse than the current silence.

    Routing when promoted: domain:spec, not domain:spec-surface. The enum at packages/spec/src/data/data-engine.zod.ts:228 is the accepted set — widening it changes what validates — so the reverse red line applies however small the diff (precedents #6245, #6235). Only the .describe() half would be surface, and it cannot travel without the enum.

    Restart condition: promote when a second non-readonly strip lands on the write path. One instance is a special case that a warn covers; two make the enum's closed shape a recurring lie, and at that point the design question ("new value vs. category + cause") has two data points instead of one to answer it. Also promote immediately if a caller reports relying on strictReadonlyWrites for total coverage — that would convert this from a narrowed surface into a broken promise.

    Stale-premise check: reason: z.enum(['readonly', 'readonly_when']) is verbatim on origin/main at data-engine.zod.ts:228, describe text unchanged. The deliberate "not reporting this one" comment in PR #6433's new strip block is the externalised half this card records, and item 3 of the body's own landing-site list correctly requires deleting it whenever the enum moves.

    No target:<major>: no shipped-surface defect — the strip itself is correct, only a machine-readable signal is absent, and the warn is present.

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


    Generated by Claude Code

  2. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue. The seam's declared observability is now narrower than the strips it reports on: yesterday's legal data.id strip (PR #6433) is invisible to onFieldsDropped and to strictReadonlyWrites — the loud/observable pair #3407/#5126 built loses coverage silently as new legal strips appear. Widen the enum (acceptance-surface change ⇒ spec seat). finding → pm:queue + domain:spec.


    Generated by Claude Code

  3. self-assigned this
    on Aug 9, 2026
  4. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    CLAIM — PM seat domain:spec (session session_01PiRUoQkTSBBmpyXBY3cVn2), dispatching to an os-dev subagent.


    Generated by Claude Code

  5. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    PM bookkeeping: pm:dispatched → pm:queue. The dispatched os-dev was stopped externally (mass session-level cancellation, 15:21Z patrol census) before pushing any code — no branch, zero work product. Claim released; card returns to the queue intact. Re-dispatch awaits the maintainer's word; the brief (ruling: widen the enum; strict-mode posture measured in-PR) remains valid.


    Generated by Claude Code

  6. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    RE-CLAIM — PM seat (session session_01PiRUoQkTSBBmpyXBY3cVn2), fresh os-dev dispatched under the maintainer's re-acceleration (2026-08-09 chat). Branch claude/issue-6437-dropped-fields-reason-vocabulary, worktree ../objectstack-issue-6437. All constraints in the 12:28Z claim comment remain binding.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions