## 背景 #3003 排查过程中发现的同族残留:#2948/#2957 把**静态** `readonly` 的剥离补到了单条与多行两条 UPDATE 路径,但**条件只读 `readonlyWhen` 从一开始就只在单条路径强制**——多行路径完全不评估、不剥离。 ## 定位(以当前 main 为准) `packages/objectql/src/engine.ts` `update`: - **单条 id 路径**(`engine.ts:2405-2426`):fetch `priorRecord` → `stripReadonlyWhenFields` 基于合并的 `{...previous, ...data}` 求值并剥离(`:2416`)→ 强制 ✅ - **多行路径**(`engine.ts:2427-2445`,`options.multi && driver.updateMany`):不 fetch per-row prior(逐行查询无上界),只跑静态 `stripReadonlyFields`(`:2442`,#2948);`readonlyWhen` **不求值、不剥离** ❌ ## 暴露面 REST 直接暴露 `POST /data/:object/updateMany`(`packages/rest/src/rest-server.ts:6076` → `updateManyData` → 引擎多行路径)。 攻击示意(按代码路径判断成立,待复现坐实):`showcase_invoice.tax_rate` 声明 `readonlyWhen: record.status == 'paid'`(paid 后锁税率)。单条 `PATCH { tax_rate: 0 }` 会被剥离;换成 `POST /data/showcase_invoice/updateMany`、`where` 精确命中同一条已 paid 记录、载荷 `{ tax_rate: 0 }` → 写
Activity