事实
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/hotcrm 的 os lint 是整个系统里唯一看见形状分歧的东西(component-props-invalid 按 spec schema 校验,含形状,16 条)。但那是唯一没有权威改渲染器的一侧,于是 16 条正确报警被当成噪声,写进了 KNOWN_UNCONFORMING 豁免,并附了一段论证「渲染器才是权威」的散文。那段散文随后误导了一个 PR 和两轮 agent 会话,直到 2026-09-06 维护者裁决 「元数据项目不应该依赖 @object-ui/components」 才推翻。
要做的事
给 ComponentInput 一个机器可读的成员形状表达,并让本闸门用上它。
⛔ 本卡不预设字段名或形状(of / element / items 皆可)——设计是这张卡的主体,因为 ComponentInput 有四个下游消费者,任何新增都要同时对它们成立:
packages/sdui-parser/scripts/gen-manifest.ts → sdui.manifest.json(save gate + parser 白名单)
- 同上 →
sdui-intrinsics.d.ts(JSX 授权类型面)
packages/sdui-parser/src/validate.ts(走 comp.inputs 判 unknown-prop)
packages/components/src/renderers/layout/page.tsx:462(JSX-page prop 白名单)
⛔ 硬约束
⚠️ 新槽位必须从第一天起就有 reader。 #5905(已关闭)记录的正是反面教材:ComponentInput 的 inputType/min/max/step/placeholder 五个键没有任何读者,manifest 序列化器转发六个键、一个都不读。⛔ 再加一个没人读的键,等于把本卡变成 #5905 的续集。
⇒ 验收要求:新槽位落地的同一个 PR 里,本闸门必须真的比较成员形状,并且有一条消融证明它能红——把某个 array key 的成员形状改成与 spec 不符,闸门必须失败。
⚠️ 配对正向对照:消融的同一轮里,必须展示未改动的树上该闸门通过。一个不会红的闸门和一个不存在的闸门无法区分。
⛔ 不在本卡内
Refs: #3797(建立本闸门的卡)· #4971(ARM DIRECTION,第二条 LIMIT)· #5905(无 reader 的 ComponentInput 键,反面教材)· #6252 / #7182(page:header.actions 的修复与裁决)· objectstack#11592 · objectstack-ai/hotcrm#1279 / #1653。
事实
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts自述,逐字:本卡是这两条 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/specactions: 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)。
packages/components/src/renderers/layout/containers.tsx:2032:⇒ 机器读不到。这正是本仓反复在打的 declared-in-prose / unenforced 模式。
下游代价(说明它为什么值得修,⛔ 不是本卡要做的事)
消费方
objectstack-ai/hotcrm的os lint是整个系统里唯一看见形状分歧的东西(component-props-invalid按 spec schema 校验,含形状,16 条)。但那是唯一没有权威改渲染器的一侧,于是 16 条正确报警被当成噪声,写进了KNOWN_UNCONFORMING豁免,并附了一段论证「渲染器才是权威」的散文。那段散文随后误导了一个 PR 和两轮 agent 会话,直到 2026-09-06 维护者裁决 「元数据项目不应该依赖 @object-ui/components」 才推翻。要做的事
给
ComponentInput一个机器可读的成员形状表达,并让本闸门用上它。⛔ 本卡不预设字段名或形状(
of/element/items皆可)——设计是这张卡的主体,因为ComponentInput有四个下游消费者,任何新增都要同时对它们成立:packages/sdui-parser/scripts/gen-manifest.ts→sdui.manifest.json(save gate + parser 白名单)sdui-intrinsics.d.ts(JSX 授权类型面)packages/sdui-parser/src/validate.ts(走comp.inputs判unknown-prop)packages/components/src/renderers/layout/page.tsx:462(JSX-page prop 白名单)⛔ 硬约束
ComponentInput的inputType/min/max/step/placeholder五个键没有任何读者,manifest 序列化器转发六个键、一个都不读。⛔ 再加一个没人读的键,等于把本卡变成 #5905 的续集。⇒ 验收要求:新槽位落地的同一个 PR 里,本闸门必须真的比较成员形状,并且有一条消融证明它能红——把某个 array key 的成员形状改成与 spec 不符,闸门必须失败。
⛔ 不在本卡内
@objectstack/spec的改动。协议侧是对的:z.array(z.string())完整且机器可读,缺口全部在本仓。Refs: #3797(建立本闸门的卡)· #4971(ARM DIRECTION,第二条 LIMIT)· #5905(无 reader 的
ComponentInput键,反面教材)· #6252 / #7182(page:header.actions的修复与裁决)· objectstack#11592 · objectstack-ai/hotcrm#1279 / #1653。