发现于 #5186(为「读接缝把故障答成空值」这一族新增闸门)实施期,由新规则在 packages/objectql 扫出;不在该单文件面内(#5186 是纯 scripts/ 闸门单,不改被扫包),故单独立卡并已作为 baseline 条目记录在 scripts/durability-read-invention.baseline.json。
现象
packages/objectql/src/engine.ts 的 seedAutonumber()(约 2051–2087 行,以 origin/main 为准):
private async seedAutonumber(object, field, prefix, execCtx?): Promise< number > {
try {
const rows = await this.find(object, { fields: ['id', field], limit: 5000, context: execCtx } as any);
let max = 0;
// … 从已有值里取出最大计数 …
return max;
} catch {
return 0;
}
}
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)) return 0;
throw error; // 其余一律上抛:调用方不得用没读到的数据推算号段
}
⚠️ 需要确认的落点问题(实现时判断,不在本卡预判):@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」。