Skip to content

registry inputs 与 spec ComponentPropsMap 之间没有 parity 门,4 个 block 发布了 pin 版 spec 不接受的顶层 input #3797

Description

@yinlianghui

发现于 #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 不接受的顶层键」这个方向是完全静默的:

  1. manifest 与生成的 .d.ts 宣告该 prop 合法;
  2. packages/sdui-parser/src/validate.ts 的 manifest 门只遍历节点顶层 prop 并比对
    comp.inputs,该键因此被判为「已知 prop」,零诊断;
  3. spec 侧多为普通 z.object,parse 时把未知顶层键无声剥掉,不报错;
  4. 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() 一致。

Activity

  1. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    分诊判级(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt):bug + pm:queue,可派。

    判据:4 个 block 发布了 pin 版 spec 不接受的顶层 input,manifest/生成的 .d.ts 正在向作者(含 AI)宣告会被平台静默丢弃的键 —— 与 #3407/PR #3795 实测坐实的失败类同族,且 PR #3795 已交付可直接推广的单块版门(recordHighlightsInputs.spec-parity.test.ts 的「declares no top-level input the spec does not accept」方向)。

    派发时的边界:每个漂移条目有两个可能的正确方向(spec 该补声明 vs input 该撤),须逐块以代码为准判定 —— 渲染器真实读该键且行为对用户可达的,方向是「spec 补声明」(转 objectstack 车道立单,本仓不动 spec);渲染器不读或读了无效的,方向是「input 撤下」(本仓落地)。两向都拿不准的块停在 needs_decision,不许猜。labelField 疑为 displayField 别名漂移的那条,先查 objectstack 侧 conversion/alias 登记再定。门本身(推广 parity 测试到全 block)无条件成立,可与逐块处置同 PR 或先行。

    长期方向存档(来自 PR #3795 open question,不进决策箱):门只能拦顶层;数组对象型 input 的成员键要机器可读需扩展 ComponentInput 成员形状声明位(方案 C,跨 types/core/sdui-parser),那是独立的一单,本单不做。


    Generated by Claude Code

  2. self-assigned this
    on Aug 8, 2026
  3. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    🔒 认领(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)

    即将派发 os-dev 实施。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions