Skip to content

fix(seed-loader): roll-up summary 重算耗尽重试后改记 error 并计入结果对象 (#4998) - #5062

Merged
xuyushun441-sys merged 3 commits into
mainfrom
claude/issue-4998-seed-summary-stale-loud
Aug 4, 2026
Merged

xuyushun441-sys merged 3 commits into
mainfrom
claude/issue-4998-seed-summary-stale-loud

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Collaborator

Fixes #4998

维护者裁决 A + B 全部落地。

行为没变的部分

ERR_SUMMARY_RECOMPUTE 的恢复逻辑一字未动(framework#3147):记录确实已经写入,重写会产生重复,所以照旧返回 e.written。这个 PR 只改后果的等级和可发现性。

A —— 提到 error,文案带后果与修复动作

roll-up summary 是落盘的派生列(挂在 parent 记录上)。重算耗尽重试之后,库里明细行和汇总它们的那一列互相矛盾,而且不会自愈——要等到后续某次写入恰好碰到同一个 parent,seed 之后未必再有。这正是 AGENTS.md「Degradation log levels」#4632 说的那一类:持久化状态与运行时状态不一致,而外表一切正常。

原来整件事只有一行 warn:不点名对象、不计数、success 仍是 true。现在按 #4632 的约定记 error,一行里同时给出:

  • 后果:点名被 seed 的对象和具体陈旧的列(roll_account.total_billed),说明明细与汇总不一致、无法自愈、而且这次 seed 依然报 success;
  • 修复动作:修掉下面附着的 recompute 原始错误后重跑 seed,或对受影响的 parent 记录触发任意一次写入以强制重算;
  • 原始 cause 通过 logger 的 error 参数结构化附上,每条 failure 的 parentId/field/error 进 meta。

B —— 计入结果对象;success 保持 true

新增 SeedLoadResult.summariesStale 与 SeedLoaderResult.summary.totalSummariesStale,与 referencesDropped / totalReferencesDropped 逐字对齐(z.number().int().min(0).default(0))。后者正是为低一层的同一形状而设的:「行写进去了,由它派生的东西丢了」。日志不是调用方能 branch 的东西,计数才是。

success 判定为保持 true,依据是三个消费方的实测证据,不是偏好:

消费方 若翻转 success 会怎样
packages/metadata-protocol/src/protocol.ts seed-apply 对外返回 success: false 而 errors 为空——一个说「失败了」却结构上说不出原因的机器可读面(违反 AGENTS.md「Machine-readable surfaces must not lie」)
packages/runtime/src/app-plugin.ts 启动横幅 走失败分支打印 0 dropped record(s) and 0 error(s)——正是 #3932 注释里骂过的「true and useless」那一行
packages/runtime/src/domains/packages.ts、cloud-connection marketplace 安装 每一行都写成功的安装被判失败

success 回答的是「行落地了吗」,答案是落地了;信号由新计数承载,想把陈旧汇总当致命的调用方读 summary.totalSummariesStale > 0。

门禁那一段:原方案是假保护,已修

原计划是把 fn() 抽成具名私有方法 performSeedWrite 并登记进 DURABILITY_CRITICAL_CALLEES。抽取做了,但只做这一步登记进去也是不生效的,实测确认:

scripts/check-durability-degradation-log-level.mjs 对任何含 throw 的 catch 一律放行(if (rethrows || loud.length > 0) return;)。而本 seam 的形状恰恰是「命中 ERR_SUMMARY_RECOMPUTE 就恢复、其余一律 rethrow」——于是把日志改回 warn,门禁照样绿。那就是一条看着像保护、实际永远不会 fire 的账本条目。

所以同时收紧了规则:只有无条件 rethrow 才算豁免;一个分支恢复、另一个分支 rethrow 的 catch,其恢复分支和别的降级没有区别,必须响。

实测口径:

  • 全仓 11 个 durability seam,收紧前后判定无一改变(全部 loud 或 rethrow),不需要任何 baseline 条目;
  • 把本 seam 的 error 改回 warn:门禁 exit 1,并打印该 callee 登记的后果文案;改回来 exit 0;
  • 门禁自带 self-test 从 10 例加到 13 例,新增三例分别钉住「部分恢复 + warn 必须报」「同形状 + error 必须过」「条件分支里全部 rethrow 仍算豁免」。

测试

新增 packages/metadata-protocol/src/seed-loader-summary-stale.test.ts(7 例),按 #5001 在 seed-loader-deferred-failure.test.ts 立的写法:

变异钉子:把实现改回「warn + 不计数」,新测试 4 例转红,门禁同时 exit 1。

seed-loader-retry.test.ts 里那句 summary recompute failure is a warning, not an error 的 describe 标题已按事实改写(断言未变:陈旧汇总依旧不是写入错误,totalErrored 仍为 0)。

生成物落地纪律

推送前:git merge origin/main(未 rebase)→ 把 .gitattributes 里全部 merge=os-regen 路径 git checkout origin/main -- 重置 → 重建 spec → 整体重生成(check:generated --fix,从不文本合并)。核对结果:本分支在这些生成物上相对 origin/main 的 delta 恰好是自己的两行新增,#5043(chart/theme 未知键批次)落地的兄弟条目原样健在(ui/Chart 44 条、ui/Theme 12 条,两个 reference 页未动)。

未做的事(有意)

本地门禁

28 条 check:* 全跑:27 过。唯一失败 check:objectui-pin-fresh(.objectui-sha 相对 objectui main 已陈旧)与本改动无关——该文件未被触碰,属仓库既有状态。check:engine-double-contract 通过,无需新增基线条目。


🤖 Generated with Claude Code

https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX


Generated by Claude Code

os-sales and others added 2 commits August 4, 2026 01:21
…counted (#4998)

The ERR_SUMMARY_RECOMPUTE recovery is unchanged (framework#3147: the rows WERE
written, re-writing them would duplicate). What changes is the rank of the
consequence and its detectability.

- `error`, not `warn` (#4632): a roll-up summary is a persisted DERIVED column,
  so exhausting its recompute retries leaves the detail rows and the column
  summarizing them disagreeing in the database, with nothing to self-heal it.
  The line names the seeded object and the stale column, states the consequence
  (including that the seed still reports success) and the remedy, and carries
  the original cause.
- Counted: `SeedLoadResult.summariesStale` / `summary.totalSummariesStale`,
  mirroring `referencesDropped` / `totalReferencesDropped`. `success` stays
  `true` — it answers "did the rows land", and they did.
- The guarded write is extracted as `performSeedWrite` and registered in
  `DURABILITY_CRITICAL_CALLEES`, and the gate no longer excuses a catch that
  rethrows on one branch while recovering on another — without that, the ledger
  entry could never have fired.

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

vercel Bot commented Aug 4, 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 4, 2026 2:02am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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.

…d-summary-stale-loud

Conflict: scripts/check-durability-degradation-log-level.mjs — both sides
appended a DURABILITY_CRITICAL_CALLEES entry at the same list position.
Resolution keeps BOTH: this branch's `performSeedWrite` (#4998) and #5025's
`dropPromotedDraftRow` (#4981), with the seed-loader callees kept adjacent.

Verified on the merged state: #5025's seam is unaffected by this branch's
tightening of the rethrow rule. Its catch contains no `throw` at all, so
`rethrows` is false and the "only an unconditional rethrow excuses the seam"
change cannot apply to it — it is judged on log level exactly as before, and
reports loud via console.error in draftDrainVerdict(). Gate: 12 seams, all loud
or rethrowing, exit 0; self-test 13/13.

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

Copy link
Copy Markdown
Collaborator Author

与 #5025 的同文件串行化已完成(HEAD 79ff030)

#5025 合入 main 后同样改了 scripts/check-durability-degradation-log-level.mjs,已按串行化流程处理(git merge origin/main,未 rebase)。

冲突与解决:唯一冲突就在该脚本,双方在 DURABILITY_CRITICAL_CALLEES 的同一位置各追加了一条,属纯增量。解决保留两条——本分支的 performSeedWrite(#4998)与 #5025 的 dropPromotedDraftRow(#4981)——并把 seed-loader 系的三条 callee 排在一起。除该脚本外无其他冲突。

关键验证:#5025 的 seam 在收紧后的门禁下依然通过,未放宽门禁、未动 sys-metadata-repository.ts。

这在结构上是必然的:#5025 的 catch 是 catch (error) { draftDrainFailed = this.draftDrainVerdict(...); },整块没有 throw,所以 rethrows 恒为 false,而本分支的收紧是 propagatesAlways = rethrows && !catchRecovers(...)——对它恒等于 false,判定路径与收紧前完全一致,仍旧只看日志等级(经 draftDrainVerdict() 的 console.error 判为 loud)。收紧只可能改变「一个分支恢复、另一个分支 rethrow」这一种形状的判定。

Durability-critical catch seams found: 12
  …
  packages/metadata-protocol/src/seed-loader.ts:1294  guards performSeedWrite()@1293
      → recovers on one branch, loud (error@1323 via reportStaleSummaries())
  packages/metadata-protocol/src/sys-metadata-repository.ts:707  guards dropPromotedDraftRow()@706
      → loud (error@1320 via draftDrainVerdict())
  …
✓ durability-degradation log levels: 12 durability-critical catch seam(s), all loud or rethrowing.
GATE EXIT CODE = 0        self-test: 13 case(s) passed

生成物纪律(纪律 A)重跑:.gitattributes 全部 merge=os-regen 路径 git checkout origin/main -- 重置 → 重建 spec → 整体重生成。本分支在这些生成物上相对 origin/main 的 delta 仍恰好是自己的两行新增;兄弟条目全部健在——ui/Chart 44 条、ui/Theme 12 条(#5043)、drilldown 1 条(#5012)。重生成结果与已提交内容逐字节一致,故无新增提交。

合并态复验:@objectstack/metadata-protocol 36 files / 319 tests 全过(含 #5025 新增的 sys-metadata-repository.draft-drain.test.ts 与本 PR 的 seed-loader-summary-stale.test.ts);@objectstack/spec 301 files / 7689 tests 全过;spec + metadata-protocol + runtime typecheck 全过;28 条 check:* 27 过,唯一失败仍是 check:objectui-pin-fresh(.objectui-sha 本分支零触碰,属仓库既有状态)。


Generated by Claude Code

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/l tests tooling

Projects

None yet

3 participants