Skip to content

fix(metadata-protocol): seed-loader 中「计为错误」的失败一律记 error,不再自相矛盾地记 warn (#4729) - #5001

Merged
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4729-seed-loader-loud-failure
Aug 3, 2026
Merged

fix(metadata-protocol): seed-loader 中「计为错误」的失败一律记 error,不再自相矛盾地记 warn (#4729)#5001
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4729-seed-loader-loud-failure

Conversation

@xuyushun441-sys

@xuyushun441-sys xuyushun441-sys commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #4729

问题

packages/metadata-protocol/src/seed-loader.ts 的 pass-2 延迟引用回填,catch 上方的注释白纸黑字写着这次失败 "must be a reported, counted error, never a silent warning",紧跟着的调用却是 this.logger.warn

公允地说,它并不完全静默:recordDeferredError(...) 把它计入 allErrors,success 会变 false。但日志行本身是一次 seed 在宿主控制台上留下的唯一痕迹,而 warn 正是 #4420 证明没人读的那一级 —— 后果(引用停在 NULL、循环关系半写入)则一个字都没说。计数与日志级别应当一致,这是本单的判据。

issue 正文里的「本轮因包冻结未修」已失效:#4556 的冻结今晨关闭,同包 #4980、同文件 #4836 今天都已合入,本 PR 基于当前 origin/main

改了什么

1. 主接缝提到 error,并补齐一条 error 该有的两件东西(AGENTS.md → "Degradation log levels",参照 #4823 / #4460 的文案形态):

  • 后果:对象.字段 在具名记录上停在 NULL;行本身已经种下,所以每一个行计数都是干净的;循环关系处于 HALF-WRITTEN 状态,并点名它本该指向的 目标对象.目标字段 = '自然键';
  • 修复动作:没有任何东西会重试 —— 修掉下面那个写入错误(超出重试预算的瞬时故障,或某条 validation 规则否决了这次 update)后重跑 seed;
  • 原因(err.message)并入同一行,读者不必再翻第二处。

实际打出来长这样(测试里逐段断言):

[SeedLoader] Deferred reference back-fill FAILED — audit_department.head_id stays NULL on record
'Engineering'. The row itself was seeded, so every row counter looks healthy while the circular
relationship is HALF-WRITTEN: nothing links it to audit_worker.name = 'Alice'. Nothing retries
this — fix the write error below (a transient failure that outlasted the retry budget, or a
validation rule vetoing the update) and re-run the seed to complete the link.
Cause: UPDATE rejected by validation rule

2. 按同一判据盘完本文件其余 logger.warn 判据取「这次失败是否进 errors/allErrors(即是否让 success 变 false)」——buildResultsuccess = !(errors.length 大于 0 || totalErrored 大于 0),所以这条线是客观的。另有五处「计为错误、日志 warn」,一并提到 error:

位置 后果
批量插入的失败行 该记录没落盘
cel 表达式解析失败 整条记录被丢弃
单值字段收到数组(引用被 DROP) 行落了、关联没落,且行计数干净(framework#3932)
引用值是对象(引用被 DROP) 同上
自引用数据集的顺序写 catch 该记录没落盘
upsert/update 路径的 catch 该行声明的值没落盘,原有内容原样留着

两处 DROP 引用的行额外补上后果与修复动作 —— 那正是 #3932 注释自己描述的形态:「行计数一切正常,损失只在别处看得见」,也就是 #4632 第二类的教科书样子。

3. 三处维持 warn,并把审计结论写进注释(免得下一个人再翻一遍):

4. 让门禁钉住,而不只是测试。 pnpm check:durability-log-level 按 callee 名匹配被 try 守护的接缝,且不下潜进嵌套函数体 —— 而原来的写入藏在 withTransientRetry(() => ...) 闭包里,AST 扫描根本看不见。于是把回填写入抽成 writeDeferredReference,与 writeRecord 一同登记进 DURABILITY_CRITICAL_CALLEES(各带一句「失败意味着什么」)。现在这两处 catch 若被降回 warn,CI 直接红:

packages/metadata-protocol/src/seed-loader.ts:813   guards writeRecord()@800              → loud (error@829)
packages/metadata-protocol/src/seed-loader.ts:1085  guards writeDeferredReference()@1077  → loud (error@1098)
✓ durability-degradation log levels: 10 durability-critical catch seam(s), all loud or rethrowing.

durability-degradation.baseline.json 保持空表(本 PR 不新增豁免)。

测试

packages/metadata-protocol/src/seed-loader-deferred-failure.test.ts 新增两条:

  • 回填写入失败时:断言级别是 error、消息点名 audit_department.head_id、含 stays NULL / HALF-WRITTEN / 目标 audit_worker.name / re-run the seed / 原始 cause,第二/三个实参分别是 Error{ object, field },并断言该失败不再出现在 warn 上;同时确认它仍被计数(success: falsetotalErrored 大于 0、errors 里有 head_id);
  • 回填成功时:引用确实落盘、success: true,且 logger.error / logger.warn 一次都没被调用 —— 别把读者训练成跳过 error

两处既有断言随之更新(runtime/src/seed-loader.test.ts 的写入失败、seed-loader-multi-value-reference.test.ts 的 DROP 引用),后者顺带断言新的后果措辞。

顺带记录(未在本 PR 修)

两条都已在代码注释里留下指针。

验证

  • pnpm exec turbo run test --filter=@objectstack/metadata-protocol → 34 files / 301 passed(合入 main 后重跑一次,仍 301)
  • packages/runtime:seed-loader.test.ts + app-plugin.seed.test.ts + seed-datasets.test.ts53 passed;packages/objectql 两个 seed 用例 → 8 passed
  • tsc --noEmit:metadata-protocol 改动前后同为 156 行输出(全是 ledger 记录的既有 TS2835/TS7006 噪声),seed-loader.ts 零错误;runtime 无本文件相关错误
  • 门禁全绿:lintdurability-log-levelstartup-registry-verdictinit-service-contractroute-envelopewildcard-fallthrougherror-code-casingengine-double-contracttype-check-coveragepublished-filesadr-anchorsslot-lookupnul-bytesdoc-authoringdocs-audit-scoperole-wordorg-identifierauthz-resolverservice-providersobjectui-changesetrelease-notesnode-versionmerge-driver
  • check:i18n / check:i18n-coverage 本地未跑通:两者都要求先做整仓 build(CI 里就排在 build 步骤之后,报错是 os CLI 未构建),本 PR 不触碰任何 translation / 声明标签面

changeset:.changeset/seed-loader-loud-failure-log-level.md(@objectstack/metadata-protocol patch)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX

claude added 2 commits August 3, 2026 19:41
…#4729)

`SeedLoaderService` pass-2 延迟引用回填的 catch 上方写着「this must be a
reported, counted error, never a silent warning」,紧跟着的调用却是
`this.logger.warn`。计数是对的(`recordDeferredError` 进 `allErrors` ⇒
`success: false`),但日志级别与它矛盾 —— 而这一行是一次 seed 在宿主控制台上
留下的唯一痕迹,`warn` 正是 #4420 证明没人读的那一级。

- 该行提到 `error`,并按 AGENTS.md「Degradation log levels」补齐一条 error
  该有的两件东西:**后果**(`<object>.<field>` 停在 NULL、行本身已种下所以
  行计数一切正常、循环关系半写入)与**修复动作**(没有任何东西会重试,修掉
  写入错误 —— 超出重试预算的瞬时故障,或某条 validation 规则否决了这次
  update —— 之后重跑 seed)。
- 按同一判据(这次失败是否计入 `errors` / 是否让 `success` 变 false)盘完本
  文件其余 `logger.warn`:另有五处「计为错误、日志 warn」一并提到 `error` ——
  批量插入失败行、`cel` 表达式解析失败被丢弃的记录、两处 DROP 引用字段的非法
  引用路径(行落了、关联没落,而行计数干净 —— framework#3932),以及顺序写
  与 update 两处 catch。两处 DROP 路径的日志行另补后果与修复动作。
- 三处**维持 warn**并把审计结论写进注释:`Halting on first error`(控制流通知,
  它所halt 的错误各自已在 error 级别报过)、`NODE_ENV` 无法判定(功能性、
  fail-open 降级)、roll-up summary 重算失败(记录确实写入了;陈旧汇总列是否
  属于 #4632 第二类另开 #4998 由维护者定夺)。
- 让门禁而不只是测试钉住这个接缝:回填写入抽成 `writeDeferredReference`
  (原写入在 `withTransientRetry` 闭包里,AST 扫描进不去),与 `writeRecord`
  一同登记进 `scripts/check-durability-degradation-log-level.mjs` 的
  `DURABILITY_CRITICAL_CALLEES`,这两处 catch 再被降级即 CI 红。

结果对象、API、schema 均无变化,变的只是级别与措辞。

另记录两处不在本单判据内的同文件发现:#4997(无 pass-2 时整条记录被丢弃却
一行日志都不打)、#4998(roll-up summary 陈旧值不计数、只记 warn)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 3, 2026 7:49pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 19:57
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 1d29e6d Aug 3, 2026
25 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4729-seed-loader-loud-failure branch August 3, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[metadata-protocol] seed-loader 的「LOUD FAILURE」注释要求 error,代码却记 warn —— 第二类降级(#4632 规则),本轮因包冻结未修

2 participants