docs(objectql): RUNTIME_OWNED_FIELD_TYPES 的 summary 排除理由改为与实现相符 (#6014) - #6119
Conversation
`RUNTIME_OWNED_FIELD_TYPES` 上方的 TSDoc 把 `formula` 和 `summary` 并列成
「computed on read from a plan, never stored from the write payload」。formula
那半句对(`applyFormulaPlan` 读时按 plan 求值);summary 那半句是错的:roll-up
summary 是实打实的物理存储列 —— `recomputeSummaries()` 用
`update(parent, { [summaryField]: value })` 写入,#5749 / PR #6013 之后
`initializeSummaryFields` 在 insert 时也落初值,读路径直读该列(#5749 的
「筛选静默漏行」正因为它是库内列)。
危险方向具体:下一个作者按注释字面「修正代码以匹配注释」,把 summary 加进集合,
带汇总初值的历史导入/种子写入就会被静默 strip。而且 strip 站点(engine.insert
:5276)跑在 seed 之后、且只认原始 caller payload,所以 caller 送了
`task_count: 42` 时,42 被 strip 掉、0 也不会补(seed 正因为 caller 供了值而跳过),
列落回 null —— 恰是 #6013 要消灭的状态,且写入仍报成功。
改注释,不改行为:formula / summary 拆成两段,写明 summary 是 persisted +
runtime-maintained 但故意不 strip,真实判据是第三条「没有合法的 caller 供值」——
autonumber 满足(伪造业务标识且无人纠正),summary 不满足(派生缓存,下次子表写入
自愈,且显式写初值是 #6013 明确支持的路径)。成员判据补成 (a)/(b)/(c) 三条。
未触及 `RUNTIME_OWNED_FIELD_TYPES` 集合本身,未触及任何行为代码。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
…mary-comment-truth
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
ACCEPT(执行席 PM 验收) 核过:① diff 单文件纯注释(+38/-4), 翻 ready + auto-merge,进队列。 Generated by Claude Code |
Fixes #6014
纯注释订正,零行为变化:未触及
RUNTIME_OWNED_FIELD_TYPES集合本身,未触及任何一行代码。前提复核(在
origin/main重验,单据是 lead 不是 spec)单据前提成立,四条逐条实读:
packages/objectql/src/validation/rule-validator.ts:568-571(改前),原文 "Deliberately NOTformula/summary: those are computed on read from a plan, never stored from the write payload, so there is no caller value to strip."engine.tsrecomputeSummaries()用await this.update(desc.parentObject, { id: parentId, [desc.summaryField]: value }, ...)写入d56bcdb45在 main;initializeSummaryFields(engine.ts:4329)在 insert 路径:5162/:5167调用applyFormulaPlan(engine.ts:570)只按FormulaPlanEntry[]求值,summary 不在 plan 里 —— 所以 formula/summary 并列才是错的补充一条单据没写、但让危险方向更具体的实测:strip 站点跑在 seed 之后。
initializeSummaryFields在:5162,stripRuntimeOwnedFields在:5276,而suppliedPerRow(:5248)是 原始 caller payload。所以若把 summary 加进集合,caller 送task_count: 42时:42 被 strip 掉,0 也补不上(seed 正因为 caller 供了值而continue),列落回null—— 恰是 #6013 要消灭的状态,且写入仍报成功。新旧措辞对照
旧(4 行,一段):
新(拆成四段,要点):
applyFormulaPlan;recomputeSummaries写入 + Field.summary 的 count 汇总:从未有过子记录的父行停在 NULL,删光子记录才变 0 —— 同一个「零」两种值,筛选 = 0 静默漏行 #5749 / PR fix(objectql): 新建父行时把 count/sum 型 summary 汇总初始化为 0 (#5749) #6013 的 insert seed),读路径直读该列,所以["task_count","=",0]是库内比对、never-seeded 的null才会静默漏行;并明确点出 summary 同时满足 (a) persisted 和 (b) runtime-issued 却仍被排除 —— 即旧判据根本判不出它;(c) 没有合法的 caller 供值:autonumber 满足(伪造业务标识、无人纠正),summary 不满足(派生缓存、下次子表写入自愈,且显式写初值是 fix(objectql): 新建父行时把 count/sum 型 summary 汇总初始化为 0 (#5749) #6013 明确支持的路径);DO NOT "fix the code to match this comment",连同上面那条 seed/strip 顺序的后果;验证(如实)
这是注释级变更,没有反向验证肢可做 —— 删掉/还原注释不会让任何测试变色,任何"反向验证"输出都只能是编造,故不提供。实际证据是"零行为变化"+"文本已换":
diff 面自证只动注释:
grep 证明旧误述已不在、新措辞在:
必答项
1. #6013 已落 —— 新措辞是否把「insert 也写初值」算进去了?
是。 新注释原文:"
ObjectQL.recomputeSummarieswrites it with an ordinaryupdate(parent, { [summaryField]: value })after any child write, and since #5749 / PR #6013initializeSummaryFieldsalso seeds it at parent INSERT." 且第三段进一步用它作论据(显式写初值是受支持的授权路径,initializeSummaryFields特意保留 caller 供的值),第四段用 seed 与 strip 的先后顺序推出具体回归后果。#6013 =d56bcdb45,已在 main。2. record-validator.ts 核对结论 —— 含不含同一误解?
不含,原文成立,未改。
packages/objectql/src/validation/record-validator.ts:550-551原文:它讲的是 ADR-0104 的 value-shape 开放性(哪些值类别没有严格形状门禁),不是持久化/写路径归属 —— 整段上下文是
valueShapeStrictEffective/mediaStrictEffective的 warn-first 判定。它对 summary 没有任何 "computed on read" 或 "never stored" 的主张。反向佐证:它把 summary 与 autonumber outputs 并列,而 autonumber 恰恰是 persisted + runtime-owned 的那一个 —— 这个分组与"summary 是存储列"是一致的,而非矛盾。故不动。3. #5503 / preserveAudit —— 写下的理由与实现是否逐字相符?
逐字相符,四处代码位置(行号为本 PR 落地后):
rule-validator.ts:750if (!suppliedKeys.has(name)) continue; // hook/middleware stamp — keep(insert 侧stripRuntimeOwnedFields,:736);update 侧同形于:700(stripReadonlyFields,:682)。preserveAudit豁免::751if (preserveAudit && isPreservableUnderAudit(name, def)) continue;。isPreservableUnderAudit(:828)=if (AUDIT_TIMELINE_FIELDS.has(name)) return true; return def.system !== true;—— 所以未标system: true的 summary 字段在preserveAudit下确实会被保留。新注释就是这么写的("kept byisPreservableUnderAudit, since a summary field is notsystem: true"),没有夸大成"preserveAudit 也救不了"。isSystem整趟跳过:engine.ts:5271if (!opCtx.context?.isSystem) {包住整个 strip 循环。即:受损的是既非
isSystem也非preserveAudit的普通导入/种子写入 —— 新注释正是这样限定的。无 changeset
零生成物、零行为变化、无任何用户可见 surface(不动键、类型、
.describe()、导出),本 PR 不发布任何东西,故不加 changeset,改用skip-changeset标签。依据:.github/workflows/pr-automation.yml的 changeset gate 自身把"releases nothing"一类明列为 PREFERRED 走标签;AGENTS.md:763 只要求 feature / functional improvement 出 changeset。同形先例:PR #5964(docs(spec): HookEvent 枚举注释不再声称批量写把行级谓词放在 input,纯 TSDoc 订正)已合并,标签skip-changeset,无 changeset。范围
未越界:未改
content/docs/releases/,未 rebase,未 force-push,未动集合与任何行为代码。本轮未发现需另行开单的范围外缺陷。Generated by Claude Code