发现于 #5186(为「读接缝把故障答成空值」这一族新增闸门)实施期,由新规则在 packages/objectql 扫出;不在该单文件面内(#5186 是纯 scripts/ 闸门单,不改被扫包),故单独立卡并已作为 baseline 条目记录在 scripts/durability-read-invention.baseline.json。
现象
packages/objectql/src/engine.ts 的 seedAutonumber()(约 2051–2087 行,以 origin/main 为准):
privateasyncseedAutonumber(object,field,prefix,execCtx?): Promise<number>{try{constrows=awaitthis.find(object,{fields: ['id',field],limit: 5000,context: execCtx}asany);letmax=0;// … 从已有值里取出最大计数 …returnmax;}catch{return0;}}catch 里:没有任何日志、没有 rethrow、没有按错误类型区分,直接编了一个 0 出来。
为什么这是 #4825 那一半(写进去的数是错的)
这是「读接缝把故障答成空值」家族里最贵的形状,和 #4825 的 nextEventSeq 的 catch { return 1 } 完全同构:
- 表里已经有 N 行、自增号已经发到
PRE-000123; - 一次连接抖动 / 超时 / 权限失败让这次
find 抛出; catch 答 0,计数器从头开始,下一批自增号与已存在的行相撞;- 插入成功,一行日志都没有,
success 与行计数全部读起来正常。
和「字节没落盘」不同,这是落盘的值是错的——重试不修,重启不修。
值得一提的是:这个风险 issue 代码里已经写着。同一个 try 块上方的注释(#4371 的修复注释)原文就说:
the catch below would have swallowed the guard's rejection into "seed from 0", i.e. duplicate autonumbers.
即:读那半边在 #4371 修好了,catch 这半边留在原地。
按错误类型区分,只在良性分支返回零值:
import{isMissingTableError}from'@objectstack/metadata/errors';}catch(error){// 良性且仅良性:表尚未 provision,确实没有行,从 0 起号不会撞任何东西if(isMissingTableError(error))return0;throwerror;// 其余一律上抛:调用方不得用没读到的数据推算号段}⚠️ 需要确认的落点问题(实现时判断,不在本卡预判):@objectstack/objectql 依赖 @objectstack/metadata 是否成立/是否会成环。若不成立,packages/metadata/src/errors.ts 的头注释已经把三条路线写清楚了(其中方案 2「沉到公共依赖 @objectstack/types / @objectstack/spec/shared」当时只是被判为「那一轮 out of scope」,并未被否决)——这正是需要该方案的那个调用方。
验收
seedAutonumber 的 catch 只在 isMissingTableError 为真时返回 0,其余上抛;- 有测试钉住:表未建 → 从 0 起号且不抛;驱动报连接错误 → 上抛,不发号;
- 修好后删除
scripts/durability-read-invention.baseline.json 里的 packages/objectql/src/engine.ts::seedAutonumber 条目(该 baseline 是 shrink-only,条目失效即红)。
关联
#5186(新增本族闸门,本卡由它扫出)、#4825(同族、同形状,history event_seq)、#5108(同族,DatabaseLoader 五处读)、#4728(同族起点,DDL 侧)、#4632(规则)、#4371(同一个 try 里读那半边的修复,其注释预言了本卡)、AGENTS.md「Degradation log levels」「Absence must be loud」。
发现于 #5186(为「读接缝把故障答成空值」这一族新增闸门)实施期,由新规则在
packages/objectql扫出;不在该单文件面内(#5186 是纯scripts/闸门单,不改被扫包),故单独立卡并已作为 baseline 条目记录在scripts/durability-read-invention.baseline.json。现象
packages/objectql/src/engine.ts的seedAutonumber()(约 2051–2087 行,以 origin/main 为准):catch里:没有任何日志、没有 rethrow、没有按错误类型区分,直接编了一个0出来。为什么这是 #4825 那一半(写进去的数是错的)
这是「读接缝把故障答成空值」家族里最贵的形状,和 #4825 的
nextEventSeq的catch { return 1 }完全同构:PRE-000123;find抛出;catch答0,计数器从头开始,下一批自增号与已存在的行相撞;success与行计数全部读起来正常。和「字节没落盘」不同,这是落盘的值是错的——重试不修,重启不修。
值得一提的是:这个风险 issue 代码里已经写着。同一个
try块上方的注释(#4371 的修复注释)原文就说:即:读那半边在 #4371 修好了,
catch这半边留在原地。修法(与 #4825 / #5108 同一处方)
按错误类型区分,只在良性分支返回零值:
@objectstack/objectql依赖@objectstack/metadata是否成立/是否会成环。若不成立,packages/metadata/src/errors.ts的头注释已经把三条路线写清楚了(其中方案 2「沉到公共依赖@objectstack/types/@objectstack/spec/shared」当时只是被判为「那一轮 out of scope」,并未被否决)——这正是需要该方案的那个调用方。验收
seedAutonumber的catch只在isMissingTableError为真时返回 0,其余上抛;scripts/durability-read-invention.baseline.json里的packages/objectql/src/engine.ts::seedAutonumber条目(该 baseline 是 shrink-only,条目失效即红)。关联
#5186(新增本族闸门,本卡由它扫出)、#4825(同族、同形状,history
event_seq)、#5108(同族,DatabaseLoader五处读)、#4728(同族起点,DDL 侧)、#4632(规则)、#4371(同一个try里读那半边的修复,其注释预言了本卡)、AGENTS.md「Degradation log levels」「Absence must be loud」。