Skip to content

registry-inputs-spec-parity 对 array/object 类 key 降级成「只比名字」——ComponentInput 没有承载成员形状的槽位,page:header.actions 因此漂移了整整一个契约周期 #8067

Description

@os-steve

事实

apps/console/src/__tests__/registry-inputs-spec-parity.test.ts 自述,逐字:

LIMIT — worth knowing before trusting a pass. This gate compares TOP-LEVEL KEY NAMES and nothing else. Two things it therefore cannot see, both real and both filed:

  • member shapes. An inputs entry of type array/object declares no member shape (ComponentInput has no slot for one), so a drifted key INSIDE an array element or nested object is invisible here …

本卡是这两条 LIMIT 中第一条的卡。(第二条「types NARROWER than the contract」是刻意不 gate 的,见闸门的 ARM DIRECTION 一节与 #4971,⛔ 不在本卡范围。)

ComponentInput 定义在 packages/types/src/base.ts:528,经 @object-ui/core 导出。它有 type: 'array',但没有任何字段说「数组里装的是什么」。

⇒ 闸门虽然从 ComponentPropsMap[type]运行时 shape 取期望(spec 侧是知道元素类型的,zod 携带它),但因为 inputs 侧无法表达,比较只能退化到顶层 key 名字。

已发生的代价:page:header.actions

声明
@objectstack/spec actions: z.array(z.string()).describe("Action IDs to show in header")
渲染器实际读取 把成员当 ActionDef 对象:rawHeaderActions.filter(a => actionRendersAt(a, 'record_header') …)
本闸门 两侧都有 actions 这个 key ⇒ 绿

漂移从这个 key 存在起一直到 objectstack#11592(2026-08-24 立卡)才被人注意到,而且settle 它的是维护者裁决,不是任何测试(2026-08-25「全部同意」推荐 B;#6252 / #7182 option C)。

⚠️ 今天修好之后,「这是 id」这个事实仍然只活在散文里 —— packages/components/src/renderers/layout/containers.tsx:2032

{ name: 'actions', type: 'array',
  description: "Action IDS — the names of actions declared on the object's own metadata — …" }

⇒ 机器读不到。这正是本仓反复在打的 declared-in-prose / unenforced 模式。

下游代价(说明它为什么值得修,⛔ 不是本卡要做的事)

消费方 objectstack-ai/hotcrmos lint 是整个系统里唯一看见形状分歧的东西component-props-invalid 按 spec schema 校验,含形状,16 条)。但那是唯一没有权威改渲染器的一侧,于是 16 条正确报警被当成噪声,写进了 KNOWN_UNCONFORMING 豁免,并附了一段论证「渲染器才是权威」的散文。那段散文随后误导了一个 PR 和两轮 agent 会话,直到 2026-09-06 维护者裁决 「元数据项目不应该依赖 @object-ui/components」 才推翻。

要做的事

ComponentInput 一个机器可读的成员形状表达,并让本闸门用上它。

⛔ 本卡不预设字段名或形状(of / element / items 皆可)——设计是这张卡的主体,因为 ComponentInput 有四个下游消费者,任何新增都要同时对它们成立:

  1. packages/sdui-parser/scripts/gen-manifest.tssdui.manifest.json(save gate + parser 白名单)
  2. 同上 → sdui-intrinsics.d.ts(JSX 授权类型面)
  3. packages/sdui-parser/src/validate.ts(走 comp.inputsunknown-prop
  4. packages/components/src/renderers/layout/page.tsx:462(JSX-page prop 白名单)

⛔ 硬约束

⚠️ 新槽位必须从第一天起就有 reader。 #5905(已关闭)记录的正是反面教材:ComponentInputinputType/min/max/step/placeholder 五个键没有任何读者,manifest 序列化器转发六个键、一个都不读。⛔ 再加一个没人读的键,等于把本卡变成 #5905 的续集。

⇒ 验收要求:新槽位落地的同一个 PR 里,本闸门必须真的比较成员形状,并且有一条消融证明它能红——把某个 array key 的成员形状改成与 spec 不符,闸门必须失败。

⚠️ 配对正向对照:消融的同一轮里,必须展示未改动的树上该闸门通过。一个不会红的闸门和一个不存在的闸门无法区分。

⛔ 不在本卡内

Refs: #3797(建立本闸门的卡)· #4971(ARM DIRECTION,第二条 LIMIT)· #5905(无 reader 的 ComponentInput 键,反面教材)· #6252 / #7182page:header.actions 的修复与裁决)· objectstack#11592 · objectstack-ai/hotcrm#1279 / #1653

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions