Skip to content

Commit 5a85e67

Browse files
docs(spec): ui/component.zod.ts 的 29 个站点判定为 no gate 而非收紧(#4001 批 17) (#5071)
* docs(spec): 把 ui/component.zod.ts 的 29 个站点判定为 no gate 而非收紧 (#4001 批 17) SDUI 组件 props 是 ui/ 目录最大的待收紧面,批 17 的测量结果是它根本没有 parse:.strict() 在这里不会强制任何东西,只会花掉一次 v17 破坏性变更换来 #4583 所说的「一个被精确校验的死槽位」。 三条独立测量,控制组同轮为真: - 承载键 PageComponentSchema.properties 是 z.record(z.string(), z.unknown()); PageComponentSchema 自 ADR-0089 D3a 起是 .strict(),但严格性不递归。 - 从 24 个 metadata-type root + ObjectStackSchema 做 BFS(复用 build-schemas.ts 的 #4650 闭包,6899 节点),52 个目标全部 UNREACHABLE;六个正控制组全部 root-graph;批 13 的 no-door 形状保持 unreachable。 - 三个仓库里对本文件的 parse/safeParse 全部落在本文件自己的单测内。 经验证据(definePage() 即 PageSchema.parse()):example 语料 10/10 页面上, 写进 components[].properties 的未声明键原样通过并被保留;同一个键放到 properties 的兄弟位上 10/10 被拒(负控制组)。 判定是 no gate 而非 no door —— 词汇是活的(objectui SchemaRenderer 把 properties 里每个键 spread 成 React prop),因此不能按 ADR-0049 退役。修法是 把 parse 接到承载键的闸门上,已立为 #5068。 判定写在三处:文件头、component.test.ts 的钉子(含一条 properties 一旦获得 类型化分派就变红的断言)、账本 ui/ 两张表。账本 authorable strip 76 → 47, no gate 2 → 31,均从存活行重算。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(spec): 把 批 17 判定从 JSDoc 改为行注释,避免泄漏进面向用户的生成文档 build-docs.ts 会把文件第一个 JSDoc 块当作模块描述,原样发布到 content/docs/references/ui/component.mdx 和 skills/objectstack-ui/references/ _index.md。内部战役判定不该出现在协议参考文档里(而且生成器会把段落打散, 渲染也是坏的)。 改为 imports 之后的 // 注释块,与 批 13 在 touch.zod.ts 的做法一致 —— touch.mdx 至今仍显示 "Touch Target Configuration Schema",证明这个位置对 生成器不可见。check:generated 8/8 恢复全绿。 WIP checkpoint:实现 + 测试钉子 + 账本 + changeset 已完成并已验证; 待办 = 全量 typecheck/test、三个 example app 的 validate、os-regen 四步、 开 draft PR。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(spec): 记录 #5056 桥接缺陷对 批 17 判定不适用的理由 批 16 发现 derived-clone 桥(共享 .describe() 克隆)会把死形状误报为 REACHABLE。其误差方向与本判定相反——只可能掩盖 no-gate 发现,不可能制造 一个。且本判定不依赖该桥:六个正控制组全部 root-graph(自身实例在闭包内), 52 个目标 root-graph 与 derived-clone 双双未命中。两条非 BFS 测量(开放 承载键 + 三仓零 parse 站点)独立成立。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(changeset): 把 批 17 changeset 的账本算术更新为合并后数值 合并 #5042(批 14)后总数 100 → 91,authorable 65 → 36,两个 no-parse 类 合计 55 of 91。changeset 正文会作为 CHANGELOG 发给使用者,不能停留在 合并前的分支局部数值。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(changeset): 把 批 17 changeset 算术更新为合并 批 16 后的数值 #5069(批 16)关掉 AriaProps 并把 widget/i18n 移出 authorable,总数 91 → 90。批 17 的 changeset 正文会作为 CHANGELOG 发给使用者,不能停留在 上一次合并时的数值:authorable 50 → 21 of 90,两个 no-parse 类合计 69 of 90(38 no door + 31 no gate)。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a70cd0a commit 5a85e67

4 files changed

Lines changed: 224 additions & 16 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': major
3+
---
4+
5+
**`ui/component.zod.ts` 的 29 个站点判定为 `no gate`,不收紧(#4001 批 17,ADR-0078)**
6+
7+
⚠️ **本条没有破坏性变更,没有迁移,没有任何键的行为改变。** 它记录的是一次测量结果:SDUI 组件 props 这块 `ui/` 目录最大的待收紧面,**根本没有 parse**,所以 `.strict()` 在这里不会强制任何东西。
8+
9+
## 为什么不收紧
10+
11+
`.strict()` 是 **parse 的属性**。三条独立测量(2026-08-04,控制组同轮为真):
12+
13+
1. **承载键活着,但它是个开放口袋。** `PageComponentSchema.properties` 是 `z.record(z.string(), z.unknown())`。`PageComponentSchema` 自 ADR-0089 D3a 起确实是 `.strict()`,但**严格性不递归**——它守住 component 节点自己的键,`properties` 里面完全不校验,也没有任何地方按 `type` 分派 `ComponentPropsMap`。
14+
2. **BFS 不可达。** 从 24 个 metadata-type root 加 `ObjectStackSchema` 出发(复用 `build-schemas.ts` 自己的 `zodChildSchemas`/`zodShapeOf`,即 #4650 闭包,6899 个节点),本文件 **52 个目标全部 UNREACHABLE**(21 个导出 schema + `ComponentPropsMap` 全部 31 个条目);同一轮里 `PageSchema` / `PageComponentSchema` / `PageRegionSchema` / `ThemeSchema` / `ChartConfigSchema` / `ResponsiveConfigSchema` 六个正控制组全部 `root-graph`,批 13 的 no-door 形状保持 unreachable。BFS 正好停在 `properties`。
15+
3. **三个仓库无生产 parse 点。** `objectstack` / `objectui` / `cloud` 中,对本文件任何 schema 的 `.parse()`/`.safeParse()` 全部落在本文件自己的单测里。objectui 手写平行的 React interface、只引用推断类型;cloud 引用为 0;`react-blocks.ts` 只用 `Object.keys(ComponentPropsMap)` 取类型名。
16+
17+
经验证据(`definePage()` 就是 `PageSchema.parse()`,活的授权门):example 语料 10/10 个页面上,写进 `components[].properties` 的未声明键**原样通过并被保留**;同一个键放到外面一层(`properties` 的兄弟位)10/10 被拒——这个负控制组才让前一个数字有意义。
18+
19+
## `no gate`,不是 `no door` —— 不要退役
20+
21+
这些词汇是**活的**,不能按 ADR-0049 退役:objectui 的 `SchemaRenderer` 把 `properties` 整个 hoist 到节点上,再把不在固定 deny-list 上的**每一个**作者键 spread 成 React prop。所以拼错的键既不被拒、也不被丢,而是安静地流到渲染器再被忽略——正是 ADR-0078 要消灭的形状,只是位置比本 ratchet 能触及的层更低一层。
22+
23+
这确实是 #4909 的 open-slot 形状,但在一个没人 parse 的 schema 上,`.passthrough()` 和 `.strict()` 一样空洞,所以**没有改任何 posture**。
24+
25+
contract-first 的修法是把 parse 接到承载键自己的闸门上,已单独立为 **#5068**;那个 issue 同时记录了两条使它不能顺手做的约束:`type` 是开放 union(`record:line_items` 这类未注册类型在现实中被使用),以及真实页面已经写了这些 schema 未声明的形状(`record:details` 的 `sections[].fields[]`/`hideFields[]`、record picker 的 `labelField`)。#5068 落地后本文件才重新变成 `authorable`,收紧才有意义。
26+
27+
判定写在三处(文件头、`component.test.ts` 的钉子——含一条 `properties` 一旦获得类型化分派就变红的断言、账本 `ui/` 两张表),改要一起改。
28+
29+
账本连带效果(与 #5042 批 14、#5069 批 16、#5070 批 18 合并后,从存活行重算):`ui/` 的 authorable strip 从 35 降到 **6 of 75**——重分类只是换类不是出列,总数不变,文件保留 29/29 行,`check:strictness-ledger` 的反向钉子仍然管着它;`no gate` 从 2 涨到 **31**。批 18 关掉 15 个真门、批 17 测出 29 个没有门,两件事叠加后 `ui/` 只剩 **6 个** authorable strip 站点。由此得到这个目录现在最大的一个事实:**`ui/` 剩余 75 个 strip 站点里有 69 个(92%)根本不是本 ratchet 的工作**(38 个 `no door` + 31 个 `no gate`)。排后续 `ui/` 收紧批次前请先读这个数——这个目录的 ratchet 已经接近完成,剩下的绝大多数是别的 issue 的工作。

0 commit comments

Comments
 (0)