发现于 #4728 (修 database-loader.ts 的 ensureSchema() 静默吞 DDL 失败)。仅记录,未在该 PR 中修改 —— 与本单无关,按 Prime Directive #10 单开。
现象
packages/metadata/src/loaders/database-loader.ts 的 nextEventSeq():
private async nextEventSeq ( ) : Promise < number > {
const where : Record < string , unknown > = this . organizationId
? { organization_id : this . organizationId }
: { } ;
try {
const rows = await this . _find ( this . historyTableName , { where } ) ;
let max = 0 ;
for ( const row of rows as Array < { event_seq ?: number | null } > ) {
const v = typeof row . event_seq === 'number' ? row . event_seq : 0 ;
if ( v > max ) max = v ;
}
return max + 1 ;
} catch {
// Table not provisioned yet or driver error — start at 1.
return 1 ;
}
}
为什么值得单开
这是 #4728 刚修掉的同一种形状 ,只是在下一层,而且 #4632 的机械检查看不到它 (_find 不在 DURABILITY_CRITICAL_CALLEES 词表里):
注释同时点名了两种原因 —— 「表还没建」(良性:此时确实该从 1 开始)和「驱动错误」(不良性 ),然后用同一个 return 1 对待两者;
历史表里已经有 N 行时,一次瞬时读失败(连接抖动、超时、权限)会让下一条历史记录拿到 event_seq = 1,与既有行直接撞号 ;
event_seq 是历史排序/回滚定位的依据,撞号之后版本顺序就是错的,而写入本身成功 、日志一行没有 —— 系统对外完全正常。
nextEventSeq 的 TSDoc 已经承认它是 legacy、非事务、并发会撞号(canonical 是 SysMetadataRepository),所以这里不是「必须马上修」,但「并发撞号」和「读失败静默重置到 1」是两回事:前者被记录过,后者没有。
建议
按 #4632 的判定问句处理,任选其一:
只在确实是表不存在 时返回 1(按错误类型判别 —— [metadata] database-loader 吞掉 sys_metadata 的 DDL 失败后仍置 schemaReady=true —— 第二类降级(#4632 规则),本轮因包冻结未修 #4728 落地的 packages/metadata/src/utils/schema-sync-errors.ts 是同类判别的现成范式),其余读失败以 error 上报后果(历史序号将从 1 重新发号,与既有行撞号,版本顺序不可信)并抛出 ,让调用方决定;
或者确认该 legacy 路径已无生产调用方,直接删除,把历史写入统一收敛到 SysMetadataRepository。
顺带:如果要动这个词表,_find 这类「读出来决定后续写什么」的调用是否该进 DURABILITY_CRITICAL_CALLEES,值得一并想清楚 —— 它不是「字节没落盘」,而是「落盘的字节是错的」,可能需要词表之外的第二条规则。
参考
发现于 #4728(修
database-loader.ts的ensureSchema()静默吞 DDL 失败)。仅记录,未在该 PR 中修改 —— 与本单无关,按 Prime Directive #10 单开。现象
packages/metadata/src/loaders/database-loader.ts的nextEventSeq():为什么值得单开
这是 #4728 刚修掉的同一种形状,只是在下一层,而且 #4632 的机械检查看不到它(
_find不在DURABILITY_CRITICAL_CALLEES词表里):return 1对待两者;event_seq = 1,与既有行直接撞号;event_seq是历史排序/回滚定位的依据,撞号之后版本顺序就是错的,而写入本身成功、日志一行没有 —— 系统对外完全正常。nextEventSeq的 TSDoc 已经承认它是 legacy、非事务、并发会撞号(canonical 是SysMetadataRepository),所以这里不是「必须马上修」,但「并发撞号」和「读失败静默重置到 1」是两回事:前者被记录过,后者没有。建议
按 #4632 的判定问句处理,任选其一:
packages/metadata/src/utils/schema-sync-errors.ts是同类判别的现成范式),其余读失败以error上报后果(历史序号将从 1 重新发号,与既有行撞号,版本顺序不可信)并抛出,让调用方决定;SysMetadataRepository。顺带:如果要动这个词表,
_find这类「读出来决定后续写什么」的调用是否该进DURABILITY_CRITICAL_CALLEES,值得一并想清楚 —— 它不是「字节没落盘」,而是「落盘的字节是错的」,可能需要词表之外的第二条规则。参考
ensureSchema()的同形缺陷,已修)、[automation/approvals] 进程重启后审批决策静默失效:挂起 flow run 仍只存内存(#1518 标记 COMPLETED 但 17.0.0-rc.1 未生效),approve 落库却永不推进且零报错 #4420(事故)、[core/ADR-0116] init 阶段取服务的顺序契约是自愿声明的——不声明就没人拦,#4085 与 #4420 都是这么发生的 #4471(根因模式)