发现于 #3407 / PR #3795 的实施过程(该 PR 只在 record:highlights 一个 block 上加了这道门,
本单是把同一检查推到全仓)。未认领,交 PM triage。
机制
registry 的 inputs 就是对外发布的授权面:packages/sdui-parser/scripts/gen-manifest.ts 把它
序列化进 sdui.manifest.json(save-gate + parser 白名单)和 sdui-intrinsics.d.ts(JSX 授权
类型面)。但仓内没有任何东西把 inputs 与 @objectstack/spec 的 ComponentPropsMap 交叉
校验过(getPublicConfigs / manifestFromConfigs 的使用点里没有这类门),于是「registry 声明
了 spec 不接受的顶层键」这个方向是完全静默的:
- manifest 与生成的
.d.ts 宣告该 prop 合法;
packages/sdui-parser/src/validate.ts 的 manifest 门只遍历节点顶层 prop 并比对
comp.inputs,该键因此被判为「已知 prop」,零诊断;
- spec 侧多为普通
z.object,parse 时把未知顶层键无声剥掉,不报错;
- renderer 拿不到它。
净结果:平台自己的 manifest 指示 AI 作者写一个被静默丢弃的键,且任何一层都不产生诊断 ——
即 objectstack#5435「平台权威不得指向自己闸门会拒绝的键」的反向违例,也是 AGENTS.md #0.1
契约优先要防的那类第二方言。
实测(objectui origin/main,pin 版 @objectstack/spec@17.0.0-rc.5)
遍历 ComponentPropsMap 中本仓也注册且有 inputs 的 block,比对 input 名与 spec props 顶层键:
OFF-SPEC page:header: ["recordChrome","showStar","showCopyId"]
(spec 顶层: ["title","subtitle","icon","breadcrumb","actions","aria"])
OFF-SPEC page:tabs: ["tabStyle"]
(spec 顶层: ["type","position","items","aria"])
OFF-SPEC page:accordion: ["variant"]
(spec 顶层: ["items","allowMultiple","aria"])
OFF-SPEC element:record_picker: ["labelField","valueField","label"]
(spec 顶层: ["object","displayField","searchFields","filter","multiple",
"targetVariable","placeholder","aria"])
4 block(s) flagged
element:record_picker 的 labelField 尤其像 spec displayField 的别名漂移(同一语义两种拼写,
只有一种被 spec 接受),属经典 dialect 问题。
需要逐块判断,不是一律删
每条都有两个可能的正确方向,请 triage 时逐块定:
- spec 该声明它 —— 这是真实的授权键,spec 漏了 → 去 objectstack 补声明(协议联合先动);
- input 该撤 —— 它只是 renderer 内部/宿主注入的 prop,不该出现在授权面 → 从
inputs 移除
(record:activity 的 entries/loading/actions 就是照这个理由不声明的,
packages/plugin-detail/src/index.tsx 有现成注释)。
注意 page:header 的 inputs 在 #3226 / PR #3265 刚被收窄过,所以那三个可能是有意保留的
renderer-only prop —— 若如此,至少需要像 record:activity.showSubscriptionToggle 那样在
description 里写明,而不是让它看起来是普通可授权键。
建议的门
PR #3795 在 packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts
里已有单块版本可直接推广:运行时从 spec 推导期望值(而非复述键表),断言
「本 block 不得声明 ComponentPropsMap[type] 不接受的顶层 input」。推到全仓时需要一份
显式豁免名单 + 每条豁免的理由,这样新增的偏离必须显式登记,而不是静默通过。
复现脚本(临时,未提交)遍历 ComponentPropsMap × ComponentRegistry.getConfig(type),
逻辑与上面 PR 里的 specTopLevelKeys() 一致。
发现于 #3407 / PR #3795 的实施过程(该 PR 只在
record:highlights一个 block 上加了这道门,本单是把同一检查推到全仓)。未认领,交 PM triage。
机制
registry 的
inputs就是对外发布的授权面:packages/sdui-parser/scripts/gen-manifest.ts把它序列化进
sdui.manifest.json(save-gate + parser 白名单)和sdui-intrinsics.d.ts(JSX 授权类型面)。但仓内没有任何东西把
inputs与@objectstack/spec的ComponentPropsMap交叉校验过(
getPublicConfigs/manifestFromConfigs的使用点里没有这类门),于是「registry 声明了 spec 不接受的顶层键」这个方向是完全静默的:
.d.ts宣告该 prop 合法;packages/sdui-parser/src/validate.ts的 manifest 门只遍历节点顶层 prop 并比对comp.inputs,该键因此被判为「已知 prop」,零诊断;z.object,parse 时把未知顶层键无声剥掉,不报错;净结果:平台自己的 manifest 指示 AI 作者写一个被静默丢弃的键,且任何一层都不产生诊断 ——
即 objectstack#5435「平台权威不得指向自己闸门会拒绝的键」的反向违例,也是 AGENTS.md #0.1
契约优先要防的那类第二方言。
实测(objectui
origin/main,pin 版@objectstack/spec@17.0.0-rc.5)遍历
ComponentPropsMap中本仓也注册且有inputs的 block,比对 input 名与 spec props 顶层键:element:record_picker的labelField尤其像 specdisplayField的别名漂移(同一语义两种拼写,只有一种被 spec 接受),属经典 dialect 问题。
需要逐块判断,不是一律删
每条都有两个可能的正确方向,请 triage 时逐块定:
inputs移除(
record:activity的entries/loading/actions就是照这个理由不声明的,packages/plugin-detail/src/index.tsx有现成注释)。注意
page:header的 inputs 在 #3226 / PR #3265 刚被收窄过,所以那三个可能是有意保留的renderer-only prop —— 若如此,至少需要像
record:activity.showSubscriptionToggle那样在description 里写明,而不是让它看起来是普通可授权键。
建议的门
PR #3795 在
packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts里已有单块版本可直接推广:运行时从 spec 推导期望值(而非复述键表),断言
「本 block 不得声明
ComponentPropsMap[type]不接受的顶层 input」。推到全仓时需要一份显式豁免名单 + 每条豁免的理由,这样新增的偏离必须显式登记,而不是静默通过。
复现脚本(临时,未提交)遍历
ComponentPropsMap×ComponentRegistry.getConfig(type),逻辑与上面 PR 里的
specTopLevelKeys()一致。