Skip to content

fix(metadata-protocol): 保存回执按 isArtifactBacked 分句,不再一律自称 customization overlay (#5265) - #5926

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5265-save-receipt-truthful-wording
Aug 6, 2026
Merged

fix(metadata-protocol): 保存回执按 isArtifactBacked 分句,不再一律自称 customization overlay (#5265)#5926
baozhoutao merged 2 commits into
mainfrom
claude/issue-5265-save-receipt-truthful-wording

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5265

前提复核(以 origin/main 为准)

issue 的行号早已漂移(protocol.ts 今日已合 6 个 PR)。按内容定位,前提成立:

$ git grep -n "Saved customization overlay" origin/main -- packages/metadata-protocol/src/protocol.ts
7948:  ? `Saved customization overlay (org=${orgId}, state=…) — type=…, name=… [seq=…]`
7949:  : `Saved customization overlay (env-wide, state=…) — type=…, name=… [seq=…]`

两句都写死了 "customization overlay",而 DEFAULT_METADATA_TYPE_REGISTRY
supportsOverlay: falseallowRuntimeCreate: 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 走,按构造不可能与授权层的判断相左。

情形 FROM TO
artifact-backed(真覆盖) Saved customization overlay (org=…, state=…) — type=…, name=… [seq=N] 逐字不变
无 artifact(runtime-only) 同上(假话) Saved {type} '{name}' (env-wide | org=…, state=…) [seq=N]

org 维度照旧在括号里;state=[seq=N] 两个分支都保留,所以读 seq(HMR 游标)
state 的消费方完全不受影响。message 本身没有任何消费方解析(全仓 + objectui
grep 过: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"的改法能过红的一半,却会在这里失败。

实测(恢复缺陷后):

protocol.save-receipt-wording.test.ts   Tests  9 failed | 6 passed (15)   # 预测 9,实测 9
rest-meta-save-receipt-envelope.test.ts Tests  2 failed | 1 passed (3)
objectql/protocol-meta.test.ts          Tests  1 failed | 82 passed (83)

绿的 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 of
    a real saveMetaItem return",内容是 vieworg 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。证明两件事:

  1. message 逐字到达信封 —— 路由不改写、不改名、不重新推导;
  2. 长度远低于 rest-server 的 4xx 直通把 ≥500 字符的 message 整条换成 "Request failed" —— #5368 刚写好的过滤器拒收措辞,客户端一个字也收不到(实测) #5423 的 500 字符替换线(实测两种句式均 < 200,断言 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
lookupArtifactItemregistry.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#970 not_overridable 陷阱的现行护栏)。
所以即便 #4636 落地、boot 水合的 object 行拿到真实 packageId,isTenantAuthored 仍然把它
挡在 getArtifactItem 之外,isArtifactBacked 不变,本 PR 的分句也不变。更根本地:本 PR
读的是授权层同一个事实,所以它按构造随 intent 移动,永远不会与之分歧 —— 既不让 #4636
更难,也不让它更简单,是无影响。本 PR 不含任何触碰 loadMetaFromDbgetMetaItemLayered
的 diff。

验证

pnpm --filter @objectstack/metadata-protocol test   Test Files 49 passed (49)   Tests  486 passed (486)
pnpm --filter @objectstack/objectql            test Test Files 128 passed (128) Tests 2116 passed (2116)
pnpm --filter @objectstack/rest                test Test Files 59 passed (59)   Tests  827 passed (827)
pnpm --filter @objectstack/objectql       typecheck tsc --noEmit → Done
pnpm check:engine-double-contract   OK — 66 pinned, 139 DEBT, 2 exempt
                                    (新 fake engine 两个动词均落在 pinned:
                                     protocol.save-receipt-wording.test.ts [delete] / [update])
pnpm check:nul-bytes                OK (5717 files, no raw ASCII control bytes)
pnpm check:type-check-coverage      OK — 62/77
pnpm check:durability-log-level     OK — 24 seams
pnpm --filter @objectstack/spec check:generated   ✓ All 10 up to date

git merge origin/main(未 rebase)后重跑上述三个包,全绿。

新 fake engine 的写动词谓词从 @objectstack/metadata-core import
(assertEngineDeleteDispatch / assertEngineUpdateDispatch)—— @objectstack/metadata-protocol
不能 import @objectstack/objectql(成环),同包已有 13 个接线范例。

changeset:@objectstack/metadata-protocol patch,含 FROM→TO 两句式表。


Generated by Claude Code

claude added 2 commits August 6, 2026 11:58
…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
@vercel

vercel Bot commented Aug 6, 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 6, 2026 12:12pm

Request Review

@github-actions github-actions Bot added the size/l label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 12:22
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 72bd873 Aug 6, 2026
24 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5265-save-receipt-truthful-wording branch August 6, 2026 12:31
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

Development

Successfully merging this pull request may close these issues.

保存成功的回执一律自称 "customization overlay",包括注册表声明 supportsOverlay:false 的类型(object / flow / action / seed / hook)

2 participants