发现于 #4867(修同文件两个计数器的 catch { return 1 })。仅记录,未在该 PR 中修改 —— 不属于同一个编号家族,按 Prime Directive #10 单开,未指派。
现象
packages/metadata-protocol/src/sys-metadata-repository.ts,publishDraft() 里(origin/main 约 665–676 行,#4867 的 PR #4980 之后行号会下移):
// Drop the draft row — it has been promoted. Tolerate races where
// a second publisher already drained it.
try {
await this.delete(ref, {
parentVersion: draft.hash,
actor: opts.actor,
source: opts.source ?? 'sys-metadata-repo.publish',
intent: opts.intent ?? 'override-artifact',
state: 'draft',
});
} catch {
// best-effort: a concurrent publisher may have already drained
// the draft; the active row's authoritative content is intact.
}
注释只点名了一个原因(并发发布者已经抽走了 draft —— 这时 delete 抛 ConflictError,确实良性),catch 却吞掉全部失败:连接抖动、超时、权限不足、驱动错误,以及 draft 在此期间被改写导致的 parentVersion 不匹配。这与 #4728 / #4825 / #4867 是同一族形状 —— 一个良性原因赦免了所有原因,只是这里的数字换成了一行残留数据。
后果
publishDraft() 返回成功,active 行也确实是对的(这一点注释说得没错),但 sys_metadata 里那条 state='draft' 的行还在:
- Studio / Setup 会继续把这个 artifact 显示为「有未发布的改动」,而它其实已经发布了 —— 用户看到一个点了没用的按钮;
- 下一次
publishDraft() 会拿这条陈旧 draft 当新内容再发布一次,写出一条内容与 active 完全相同的历史事件(甚至可能把已经被覆盖的旧 body 重新推成 active);
- 没有任何日志,重试不修、重启也不修。
按 AGENTS.md「Degradation log levels」的那一问:降级之后系统对外看起来完全正常,而它声称已经清理的东西并没有清理 —— 属于 error 那一类,不是 warn。
按错误类型判别,只赦免真正良性的那一个:
参考
发现于 #4867(修同文件两个计数器的
catch { return 1 })。仅记录,未在该 PR 中修改 —— 不属于同一个编号家族,按 Prime Directive #10 单开,未指派。现象
packages/metadata-protocol/src/sys-metadata-repository.ts,publishDraft()里(origin/main 约 665–676 行,#4867 的 PR #4980 之后行号会下移):注释只点名了一个原因(并发发布者已经抽走了 draft —— 这时
delete抛ConflictError,确实良性),catch却吞掉全部失败:连接抖动、超时、权限不足、驱动错误,以及 draft 在此期间被改写导致的parentVersion不匹配。这与 #4728 / #4825 / #4867 是同一族形状 —— 一个良性原因赦免了所有原因,只是这里的数字换成了一行残留数据。后果
publishDraft()返回成功,active 行也确实是对的(这一点注释说得没错),但sys_metadata里那条state='draft'的行还在:publishDraft()会拿这条陈旧 draft 当新内容再发布一次,写出一条内容与 active 完全相同的历史事件(甚至可能把已经被覆盖的旧 body 重新推成 active);按 AGENTS.md「Degradation log levels」的那一问:降级之后系统对外看起来完全正常,而它声称已经清理的东西并没有清理 —— 属于
error那一类,不是warn。建议(与 #4728/#4825/#4867 一致的形状)
按错误类型判别,只赦免真正良性的那一个:
ConflictError(以及「行已不存在」)—— 并发发布者已抽走,静默,这正是注释里写的那个情况;error上报后果(draft 行残留、UI 会继续显示未发布改动、下一次 publish 会重复发布)与修复动作,然后决定是抛出还是仅上报。这里与 [metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867 不同,delete在put提交之后,抛出会把一次已经成功的发布报成失败,所以「抛 vs 只报」需要判断,不能照抄 [metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867 的结论 —— 这也是本条单开而不是塞进 [metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867 的原因之一。参考
nextEventSeq()/nextItemVersion(),PR fix(metadata-protocol): never invent event_seq/version from a failed history read (#4867) #4980)DatabaseLoader.nextEventSeq(),PR fix(metadata): 历史序号 event_seq 不再从一次失败的读里凭空发号 —— 只有「表还没建」可以从 1 开始 (#4825) #4872)、[metadata] database-loader 吞掉 sys_metadata 的 DDL 失败后仍置 schemaReady=true —— 第二类降级(#4632 规则),本轮因包冻结未修 #4728(ensureSchema())、[convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632(降级日志级别规则 + gate)