fix(metadata-protocol): 保存回执按 isArtifactBacked 分句,不再一律自称 customization overlay (#5265) - #5926
Merged
Merged
Conversation
…tomization overlay" (#5265) `saveMetaItem` had two success sentences and both hardwired the noun "customization overlay". Seven `DEFAULT_METADATA_TYPE_REGISTRY` entries declare `supportsOverlay: false` and are still runtime-writable by design (`object`, `field`, `hook`, `seed`, `mapping`, `flow`, `action`), so a brand-new one of those — an overlay of nothing — was told it had saved a customization overlay. The discriminator is neither `supportsOverlay` nor `allowOrgOverride` (the spec's TSDoc keeps loader merge *capability* and runtime write *permission* apart on purpose). It is `isArtifactBacked`, which the write path already computed and already derives `intent` from, so the receipt now states an existing fact and adds no read. artifact-backed → unchanged, byte for byte runtime-only → Saved <type> '<name>' (env-wide|org=<id>, state=…) [seq=N] Deliberately not split into created-vs-updated: the only available fact, `parentVersion === null`, is scoped to (state, packageId), so the first draft of a live item reads as parentless and a `Created …` receipt would swap one false claim for another. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
…e-receipt-truthful-wording
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 6, 2026 12:22
This was referenced Aug 6, 2026
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5265
前提复核(以 origin/main 为准)
issue 的行号早已漂移(protocol.ts 今日已合 6 个 PR)。按内容定位,前提成立:
两句都写死了 "customization overlay",而
DEFAULT_METADATA_TYPE_REGISTRY里supportsOverlay: false且allowRuntimeCreate: true的类型(object/field/hook/seed/mapping/flow/action)按设计可以运行时写入 —— 它们的一次全新创建并没有覆盖任何 artifact,却也被回执成 "saved a customization overlay"。
判据:用写路径已经算出的事实,不新增读路径查询
判据既不是
supportsOverlay,也不是allowOrgOverride(spec 的 TSDoc 把这两件事分得很清楚:前者是 loader 的合并能力,后者是运行时写入的许可),而是同一个方法上面
几十行就已经算好的
isArtifactBacked—— 也就是intent: 'override-artifact' | 'runtime-only'的来源。回执现在跟着intent走,按构造不可能与授权层的判断相左。Saved customization overlay (org=…, state=…) — type=…, name=… [seq=N]Saved {type} '{name}' (env-wide | org=…, state=…) [seq=N]org 维度照旧在括号里;
state=与[seq=N]两个分支都保留,所以读seq(HMR 游标)或
state的消费方完全不受影响。message本身没有任何消费方解析(全仓 + objectuigrep 过:objectui 只在 CHANGELOG/注释里出现该词),它只作 toast 展示。
记录一个取舍:不分「新建 vs 更新既有 DB-only 行」
issue 建议无 artifact 的新建说
Created …。可用的事实只有parentVersion === null(
repo.put强制opts.parentVersion === existingHash,所以 put 成功即等价于"当时无行"),但它的作用域是
(state, packageId):一个已有 active 行的首个 draft 同样读成"无父版本",据此写
Created …只是把一句假话换成另一句假话。要诚实地分辨需要一次这条路径本来不做的读查询 —— 不为一句文案发明新的读路径。故采用中性动词
Saved,如实且不越界。反向验证(方向在跑之前先定)
预测:普通红,外加一半刻意保持绿。把无条件模板恢复回去后,
runtime-only用例应全红;
override-artifact用例应全绿,因为它们那句一个字节都没改。绿的那一半不是松懈,而是分句本身的证据:一个"干脆不再说 overlay"的改法能过红的一半,却会在这里失败。
实测(恢复缺陷后):
绿的 6 条 = 4 条 override-artifact + registry 前提 pin + 长度边界 pin(长度性质与措辞无关,
两种句式都远低于上限,所以它按设计两边都绿)。
断言翻面(全仓一轮,无一条靠删断言换绿)
git grep -rn "Saved customization overlay|customization overlay" -- 'packages/**' 'examples/**'逐条处置:
packages/objectql/src/protocol-meta.test.ts:316—— 翻面。该用例的 registry 是空的,test_app无 artifact,原断言toMatch(/Saved customization overlay/)正是被修掉的假话。新断言继续承重(整句锚定:类型、名称、env 维度、seq),并新增一条 artifact-backed
的对照用例(注册带
_packageId的同名 item → 断言原措辞逐字保留)。packages/spec/src/api/protocol.test.ts:628—— 保持原文。它自称 "a verbatim capture ofa real
saveMetaItemreturn",内容是view的 org overlay,即真 overlay 场景;guarded surface 不收缩。
packages/metadata-protocol/src/protocol.code-only-types.test.ts—— 断言本身匹配的是'[seq='(两个分支都有),无需翻面;只在叙述块补一句 保存成功的回执一律自称 "customization overlay",包括注册表声明 supportsOverlay:false 的类型(object / flow / action / seed / hook) #5265 说明,避免读者以为当前代码仍然永远说 overlay。
packages/metadata-protocol/src/protocol.lock-gate-fail-closed.test.ts:57—— 那是一次已完成的反向验证记录。不倒填历史:保留原始测量文本,另加一句注明今天重跑会读作
Saved view 'v1' (env-wide, …)(该文件 registry 无v1的 artifact),方向与 5/7 分布不变。packages/rest/src/rest-5xx-message-sanitization.test.ts等其余命中 —— 全部是Failed to persist/delete customization overlay的错误路径,不在本次改动面内。边界转发核验(收益穿过边界)
新增
packages/rest/src/rest-meta-save-receipt-envelope.test.ts:真 better-sqlite3:memory:引擎 + 真ObjectStackProtocolImplementation+ 真PUT /meta/:type/:name路由(handler 以
res.json(result)收尾),复现 issue 里 showcase 的那一发 PUT。证明两件事:message逐字到达信封 —— 路由不改写、不改名、不重新推导;toBeLessThan(200)且endsWith('[seq=N]')),所以 org 维度和[seq=…]尾巴不可能被截掉。sys_metadata/sys_metadata_history用@objectstack/platform-objects/metadata的真对象定义注册(该包已是 rest 的生产依赖),不手抄镜像 —— 免得重置 #5785 停掉的那个漂移时钟。
必答项
#5079(deleted overlay 仍在 /meta listing)—— 无影响。 本 PR 只碰
saveMetaItem的成功
message,deleteMetaItem的两句(Customization overlay deleted — …/No customization overlay found for …)一个字节没动,枚举/缓存路径更是没读没写。顺带发现 delete 侧有同族的措辞问题(runtime-only 项被删时并不存在"artifact default"
可回退),已按观察类单独立 issue,不搭车、不预判 #5079 的修法。
#4636(决策箱在途)—— 不依赖其走向。
isArtifactBacked经lookupArtifactItem→registry.getArtifactItem,而后者对 object 分支的判据是obj._packageId && obj._packageId !== 'sys_metadata' && !isTenantAuthored(obj),其中isTenantAuthored就是_provenance === 'org'(packages/objectql/src/registry.ts:810)。#4636 建议的修法是把
record.packageId改读record.package_id,同时保留_provenance: 'org'戳(其正文明确点出该戳是 cloud#970not_overridable陷阱的现行护栏)。所以即便 #4636 落地、boot 水合的 object 行拿到真实 packageId,
isTenantAuthored仍然把它挡在
getArtifactItem之外,isArtifactBacked不变,本 PR 的分句也不变。更根本地:本 PR读的是授权层同一个事实,所以它按构造随
intent移动,永远不会与之分歧 —— 既不让 #4636更难,也不让它更简单,是无影响。本 PR 不含任何触碰
loadMetaFromDb或getMetaItemLayered的 diff。
验证
已
git merge origin/main(未 rebase)后重跑上述三个包,全绿。新 fake engine 的写动词谓词从
@objectstack/metadata-coreimport(
assertEngineDeleteDispatch/assertEngineUpdateDispatch)——@objectstack/metadata-protocol不能 import
@objectstack/objectql(成环),同包已有 13 个接线范例。changeset:
@objectstack/metadata-protocolpatch,含 FROM→TO 两句式表。Generated by Claude Code