问题
BatchOptionsSchema(packages/spec/src/api/batch.zod.ts:64)声明了:
validateOnly: z.boolean().optional().default(false).describe('If true, validate records without persisting changes (dry-run mode)'),该字段挂在 updateManyData / deleteManyData 的 options 上(protocol.zod.ts:526,541)以及 /batch 请求体里。但运行时没有任何一处读它 —— 全仓 grep validateOnly 的消费者为零(只有 schema 声明和一个断言默认值能解析的测试)。protocol.ts 的 updateManyData / deleteManyData / batchData 都无条件落库。
为什么这比无害的 no-op 更糟
validateOnly 的语义是数据安全承诺 —— 调用者以为在跑一次不落库的预检(导入前校验一批记录、CI 里验证 payload),实际行为是照常写库。这个字段把"预演"骗成了"真删/真改"。这是 PD #10「声明 ≠ 强制」里危害最高的一类:不是能力缺失,而是契约主动误导。
对比同组三个兄弟,全都真实生效,唯独它悬空:
| 选项 | 运行时消费点 |
|---|
atomic | protocol.ts:3482 + rest-server.ts:6607(atomic===false → 400) |
returnRecords | protocol.ts:3501 |
continueOnError | protocol.ts:3486,3610 |
validateOnly | 无 |
决定:墓碑退役(而非仓促实现)
一个真正的 dry-run 有独立的设计空间(no-commit 下的 cascade / 唯一约束语义、响应契约该如何表达每行"若执行是否会通过"),值得在有真实需求时单独立项做对,而不是为了填坑仓促实现。因此:
batch.zod.ts:validateOnly → retiredKey(...) 墓碑(写它 → 解析报错并给出处方,而非静默剥除,ADR-0104)。step18.semantic[] 注册一条 semantic 迁移记录(与 analytics-query-request-format-retired 同型:声明但从未实现、无存储可改写,故为 semantic TODO 而非 stack conversion),使 spec-changes.json / upgrade-guide / spec_changes MCP 工具如实记录本次退役。- major changeset。
validateOnly 是 optional + 有 default,退役不影响任何现有正确调用者。日后要做 dry-run 随时可重新引入一个设计完整的版本。
顺带(同一 PR)
packages/spec/src/api/plugin-rest-api.zod.ts:918 的 requestSchema: 'CreateManyRequestSchema' 是一处悬空引用 —— 从未有 schema 导出此名(真实契约是 protocol.zod.ts 的 CreateManyDataRequestSchema)。该字符串运行时和 OpenAPI 生成都不消费,是文档字符串失真,危害低于 validateOnly。改指向真实存在的 CreateManyDataRequestSchema。
问题
BatchOptionsSchema(packages/spec/src/api/batch.zod.ts:64)声明了:该字段挂在
updateManyData/deleteManyData的options上(protocol.zod.ts:526,541)以及/batch请求体里。但运行时没有任何一处读它 —— 全仓 grepvalidateOnly的消费者为零(只有 schema 声明和一个断言默认值能解析的测试)。protocol.ts的updateManyData/deleteManyData/batchData都无条件落库。为什么这比无害的 no-op 更糟
validateOnly的语义是数据安全承诺 —— 调用者以为在跑一次不落库的预检(导入前校验一批记录、CI 里验证 payload),实际行为是照常写库。这个字段把"预演"骗成了"真删/真改"。这是 PD #10「声明 ≠ 强制」里危害最高的一类:不是能力缺失,而是契约主动误导。对比同组三个兄弟,全都真实生效,唯独它悬空:
atomicprotocol.ts:3482+rest-server.ts:6607(atomic===false → 400)returnRecordsprotocol.ts:3501continueOnErrorprotocol.ts:3486,3610validateOnly决定:墓碑退役(而非仓促实现)
一个真正的 dry-run 有独立的设计空间(no-commit 下的 cascade / 唯一约束语义、响应契约该如何表达每行"若执行是否会通过"),值得在有真实需求时单独立项做对,而不是为了填坑仓促实现。因此:
batch.zod.ts:validateOnly→retiredKey(...)墓碑(写它 → 解析报错并给出处方,而非静默剥除,ADR-0104)。step18.semantic[]注册一条 semantic 迁移记录(与analytics-query-request-format-retired同型:声明但从未实现、无存储可改写,故为 semantic TODO 而非 stack conversion),使spec-changes.json/ upgrade-guide /spec_changesMCP 工具如实记录本次退役。validateOnly是 optional + 有 default,退役不影响任何现有正确调用者。日后要做 dry-run 随时可重新引入一个设计完整的版本。顺带(同一 PR)
packages/spec/src/api/plugin-rest-api.zod.ts:918的requestSchema: 'CreateManyRequestSchema'是一处悬空引用 —— 从未有 schema 导出此名(真实契约是protocol.zod.ts的CreateManyDataRequestSchema)。该字符串运行时和 OpenAPI 生成都不消费,是文档字符串失真,危害低于validateOnly。改指向真实存在的CreateManyDataRequestSchema。