feat(spec)!: 收紧 action 选项/公开分享/报表排序/数据集语义层/仪表盘小组件的嵌套未知键,并把两个站点按实测改判(#4001 批 14) - #5042
Conversation
…as no-door (#4001 批 14) Eleven strip sites measured per the #4852 remeasure; the counts held exactly (1+1+2+2+3+2, confirmed against the ledger gate's own AST counter). Per-schema door measurement then split them nine / two. Closed with strictObject: ui/action.zod.ts ActionParamSchema.options[] ui/sharing.zod.ts SharingConfigSchema ui/report.zod.ts ReportSortSchema, JoinedReportBlockSchema ui/dataset.zod.ts DatasetDimensionSchema, DatasetMeasureSchema, .derived ui/dashboard.zod.ts DashboardWidgetSchema.compareTo (object arm), .layout Four of those are strict shells over strip children: the container was already strict and strictness does not recurse. An action param option carrying color/visibleWhen/icon/disabled parsed clean through getMetadataTypeSchema('action') and came back {label, value}. Reclassified as the fourth class (no door), NOT tightened: ui/notification.zod.ts NotificationActionSchema ui/sharing.zod.ts EmbedConfigSchema Both measured three ways with positive controls in the same run: no carrier key, unreachable in a 6860-node BFS from the 24 metadata-type roots + defineStack, zero parse outside their own tests. Injecting a synthetic carrier flipped both, so the verdict is a fact about the graph rather than a broken walker. sharing.zod.ts is the ledger's first file that splits across the class. action's option entry goes strict rather than following bulk-action's .passthrough() (#4909): measured, not inherited. That def reaches the grid verbatim with no spec door and objectui's BulkActionParam declares an explicit index signature; this path has a door that already strips and lands in the CLOSED SelectOptionMetadata. Curation anchored on named sibling contracts, not edit distance: dataset on the Cube layer this module's header names (a Cube metric's `type` IS its aggregation), report's order key on SortNodeSchema and the widget's flat sortBy/sortOrder, layout on React-Grid-Layout. Known reach limit, measured: compareTo is a union, so its prescription is produced but not delivered (zodIssuesToFields maps only top-level issues; #5014). The rejection is unaffected. Tests pin both halves separately. Out-of-scope findings filed unassigned: #5013 (ReportSchema's `filter` alias prescribes `filters`, which it also rejects, plus 5 dead entries), #5014, #5015 (ADR-0049 verdict for the two no-door shapes), #5016 (should action options carry the field-level per-option vocabulary). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Brings in #5003 (批 13), #5006, #4983, #4991, #4855, #4729, #4250. Ledger `ui/` section conflicted, as planned — 批 13 and 批 14 both edit it. Every row from both sides kept; nothing resolved in favour of a side: - classification table: ONE merged `no door` row (批 13 authored the class; 批 14 adds the positive-control requirement and the first file that SPLITS across it, ui/sharing.zod.ts). - triage table: structure from 批 13 (it split `responsive` out and grouped the five no-door files); 批 14's four measured verdicts overlaid. - remaining-strip map: `responsive` row deleted by 批 13, `action`/`report`/ `dataset`/`dashboard` rows deleted by 批 14 — all five by the reverse pin. Header and subtotal RECOMPUTED FROM THE SURVIVING ROWS, not decremented: 29+20+14+9+7+7+6+4+4+4+3+1+1+1 = 110. 批 13 wrote 119 (against a tree where 批 14's four rows still existed), 批 14 wrote 114 (against one where `responsive` still did); both were right against their own branch and both are wrong against the merge. The fifth and sixth instances of the failure the automation/ section documents, and the first where the two wrong numbers were both this line. Subtotal 88 of 110 authorable, 22 in the fourth class. check:strictness-ledger arbitrates and is green. os-regen four-step: merged (never rebased), refreshed install + spec build, reset all seven generator-owned paths to origin/main, regenerated wholesale. Delta vs origin/main is exactly one file — content/docs/references/ui/sharing.mdx — so the regeneration reproduced main's artifacts byte-identically and no sibling entry was dropped (#5006's RateLimitConfig/RateLimitConfigSchema and 批 13's responsive entries confirmed present). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
修正一处被 body sanitizer 吃掉的字符正文第六节里 ( 合并 origin/main 之后的完整验证输出所有数字都是在合并后的树(
|
Ledger `ui/` section conflicted in four regions, as planned — 批 14 and 批 15
both edit it. Every row and both sides' prose kept; nothing resolved in favour
of a side.
- classification table: kept 批 14's fuller `no door` wording (it adds the
call-site half of the measurement and names #4988/#5015) AND 批 15's new
fifth class `no gate`. The two are NOT the same verdict: `no door` has no
carrier key at all and points at ADR-0049 removal; `no gate` has a LIVE
carrier (`<ObjectChart aggregate>`) with no parse between author and
runtime and points at wiring the gate. Opposite follow-ups — collapsing
them would prescribe the destructive action on one of the two.
- class prose: both blocks kept (批 14's positive-control requirement and
per-SCHEMA split finding; 批 15's no-door/no-gate table).
- rows: auto-merged clean — 批 15 deleted `theme` and took `chart` 7 -> 2,
批 14 had deleted `action`/`report`/`dataset`/`dashboard` and taken
`sharing` 2 -> 1.
Header and subtotal RECOMPUTED FROM THE SURVIVING ROWS, not decremented:
29+20+9+2+7+6+4+4+4+3+1+1+1 = 91. Cross-checked from both directions (110 - 14
theme - 5 chart = 91; 100 - 3 dataset - 2 report - 2 dashboard - 1 action - 1
sharing = 91). 批 13 wrote 119, 批 14 wrote 110, 批 15 wrote 100 — each correct
against its own branch, all three wrong against the merge, and 91 is a number no
side ever wrote down. Seventh instance of the failure the automation/ section
documents. Subtotal 65 of 91 authorable; 26 in the two no-parse classes (24
`no door`, 2 `no gate`). check:strictness-ledger arbitrates and is green.
os-regen four-step: merged (never rebased), reinstalled + rebuilt spec, reset all
seven generator-owned paths to origin/main, regenerated wholesale. Delta vs
origin/main is exactly one file — content/docs/references/ui/sharing.mdx — so the
regeneration reproduced main's artifacts byte-identically. Sibling coexistence
asserted: 批 15's ui/theme.mdx and ui/chart.mdx both present and 0 changed lines
against main, beside 批 14's sharing.mdx.
Re-verified on the merged tree: spec 299 files / 7639 tests passed, tsc --noEmit
clean, all sixteen gates green (8 generated + 8 source audits).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…#5043 landed) Ledger ui/ section was the only conflict, as expected for a three-way contended table. Every side's rows kept; header and authorable subtotal RECOMPUTED from the surviving rows rather than resolved in favour of a side: rows: 29+20+9+2+7+5+4+4+4+3+1+1+1 = 90 strip of 198 authorable = 90 - 40 no-parse (38 no door + 2 no gate) = component 29 + view 20 + app 1 = 50 Neither side's number survives — 批 16 computed 118 against a tree where 批 14's and 批 15's rows still existed, main carried 91. Eighth instance of the pattern this table documents; recorded as such. Merge resolution detail: main split the combined `chart / i18n` triage row into two, so 批 16's i18n verdict was rehomed onto main's standalone i18n row, and main's 批 15 chart row was kept verbatim. `theme.zod.ts` left the remaining-strip map on main's reverse pin (0 strip) and stays gone. os-regen four-step done: merge → restore the seven driver-managed artifact paths from origin/main → rebuild spec → `check:generated --fix` (all 8 already current; strictness changes no generated snapshot, same as 批 14/15) → sibling entries asserted surviving (批 13/14/15/16 changesets all present, theme/chart/dataset/ sharing strictObject counts intact). Post-merge verification (§10, overlapping packages): @objectstack/spec 301 files / 7709 tests pass, tsc --noEmit clean, check:strictness-ledger green.
冲突两处,都在账本的 ui/ 节,与 #5042(批 14)预期内的重叠: 1. 分类定义行 —— 保留 main 对 `no door` 的措辞改进(补上「除自身单测外 无人 .parse()」),并把 批 17 的 `no gate` 补充语句嫁接到 main 的行上。 两边内容都保留,不偏袒任一侧。 2. authorable 小计段 —— 采用 main 的叙述段(它记录了第五/六/七次 「两边都算错」),然后从存活行重算。 重算(批 14 关掉 dataset/dashboard/report/action 四行,并把 sharing/ notification 重分类为 no door,总数 100 → 91): authorable = view 20 + widget 9 + i18n 6 + app 1 = 36 no door = touch 7 + animation 4 + dnd 4 + keyboard 4 + offline 3 + sharing 1 + notification 1 = 24 no gate = component 29 + chart 2 = 31 合计 = 91 ✓ 批 17 自己就是第八次实例:它在自己分支上算的 47 of 100 对本分支正确、对 合并结果错误(正确值 36 of 91)。表格干净合并、散文两边都错——正是本节 反复记录的那种失败,已按此如实写下。 os-regen 四步已执行:merge(未 rebase)→ 从 origin/main 取回生成物 → 重装 + 重建 spec + 整体重生成 → check:generated 8/8 全绿,并断言兄弟批次 条目存活(批 13 responsive 8 / 批 14 sharing 13 + notification 3 / 批 15 theme 12 + chart 44 键均在)。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
合并 #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
Part of #4001 ——
ui/方向第二波(批 14),六个文件,账本重测记的 11 个 strip 站点。结论先说:11 个站点,9 个收紧、2 个改判
批 13 连着五个文件的
(p)解出「没有门」,留下一个新的隐含预期:验证 = 找不到门。批 14 用同一套程序、同一次运行里带阳性对照,十一个站点里九个有真门 —— 而且其中四个正是批 13 在page.components[]上找到的嵌套洞形状。两批合起来才说出各自说不出的那句:(p)是真问题,两种答案都常见。(p)解出来是action.zod.tsstrictObjectsharing.zod.tsSharingConfig收紧;EmbedConfig改判report.zod.tsstrictObjectdataset.zod.tsstrictObjectdashboard.zod.tsstrictObjectnotification.zod.ts站点数完全对上(1+1+2+2+3+2 = 11,动手前先用闸门自己的 AST 计数器复核)。批 13 证明了
authorable(p)会在测量下溶解;计数自 finding 19 改读 AST 之后没有再错过。一、四个嵌套洞 —— 严格性不递归
四个容器早就 strict,而承载真正契约的子块不是:
ActionParamSchema自 #3746、DashboardWidgetSchema自 ADR-0021 cutover、ReportSchema、DatasetSchema。最能说明问题的一条,走的是真门(
getMetadataTypeSchema('action')——MetadataManager.validate/GET /api/v1/meta/ Studio 表单都过它):parse 成功,出来是
{"label":"Overload","value":"overload"}。四个键在任何 renderer 看到之前就没了,报告有效。一个 strict 的壳套着 strip 的子块,不是一个关上的面,是关上的面的剪影。action的选项为什么走 strict,而兄弟bulk-action走.passthrough()测出来的,不是照搬的。 #4909 给 bulk-action 选项条目
.passthrough()的两条理由在这里都不成立:.passthrough()在这里不是保住一条活的流,而是打开一条。BulkActionParam声明了显式[key: string]: unknown兜底;这边的落点SelectOptionMetadata是封闭接口。目标词汇封闭,正是「声明」胜过「容忍」的场合。留下的是一个独立的能力问题(action 选项该不该讲字段级逐选项词汇)—— 归档 #5016,不在收紧批次里猜。
二、
ui/sharing.zod.ts—— 账本第一个一行两判的文件同一个文件、两个相反的判决,这也是「per SCHEMA, not per file」第一次真正生效:
SharingConfigSchema是活门:FormViewSchema.sharing承载,rest-server.ts靠sharing.allowAnonymous+sharing.publicLink挂匿名表单路由,两个示例应用都在写。→ 收紧。EmbedConfigSchema没有门:全仓没有任何模块提到这个符号,BFS 不可达,零 parse。→ 改判。按文件下判断,无论落哪边都会错一半。
三、两个
no door站点(三条测量,均带阳性对照)NotificationActionSchema与EmbedConfigSchema:sharing.zod,子串法会把stack.zod.ts误记进来。这条是先证红发现的。)defineStack做 BFS,6860 节点,两个都走不到。四个阳性对照Page/Action/DashboardWidget/Webhook同一次运行全部root-graph;注入合成承载键后两个都翻成root-graph。.parse()。objectui 读NotificationActionSchema.shape.variant当词汇表,从不 parse 授权 payload —— 这恰好说明收紧它一分钱也买不到。ADR-0049 定去留:#5015。三处留痕(JSDoc + 测试 pin + 账本行),pin 是会红的。
四、策展锚在同仓的兄弟契约上
data/analytics.zod.tsCube 层。Cube metric 的type就是聚合函数,所以{ name: 'revenue', type: 'sum', field: 'amount' }(LLM 见了 Cube/LookML 就会这么写)过去 parse 干净、算出来是count。sql只给 guidance 不给别名 —— 把SUM(amount)指向吃字段路径的field,正是 finding 7 的陷阱。SortNodeSchema的{field, order}、小组件的扁平sortBy/sortOrder、这里的{by, direction}),映射方向相反,推不出来。layout锚在 React-Grid-Layout:minW/static/i给逐键 guidance,而不是改名到无关的位置键上。allowAnonymous拼错的后果是表单保持私有而作者以为公开。五、仪器自证(每一类新断言都先证红,再信绿)
ActionParamSchema.options退回z.objectEmbedConfig接上真实承载键ui/dashboard.zod.tscompareTo的 union guidanceicon活在SelectOptionSchema上check:strictness-ledger算术第三条破坏抓到了我自己的仪器欠缺,而且是本战役反复记录的那一种。 「处方完整性」测试第一版把被处方的键手抄在断言旁边 —— 真相的第二份拷贝 —— 所以改别名表根本不改它检查的东西,破坏保持全绿。重写成 AST 读别名表 + 运行时
.shape判目标(运行时,才看得见...MetadataProtectionFields这类展开)。重写后同一次破坏立刻红。strictObject存在的意义就是把两份拷贝并成一份;测它的测试不该把拷贝再造回来。重写过程中 walker 的硬失败护栏也真的救了一次:
compareTo是ZodOptional(ZodUnion),第一版取不到 union,测试红在「could not resolve the shape behind this comparison window」而不是静默跳过这个面。六、ADR-0087 示例应用验证(按承载路径溯源,over BUILT 产物)
validate从不按PageSchema解析页面,所以「三个应用 validate 全过」对 page 面是恒真的空证。这里走真门直接 parse 构建产物,并且只沿真实承载路径下降(批 12 警告:这些形状近乎全 optional,{}能过,按形状匹配会「找到」成千上万个无关对象、给出毫无意义的绿)。阴性对照三个应用全红(往真实槽位注入
aggregation):探针自己也先红过一次,而且是有意义的一次:
views[].sharing第一版报 0 slot,而两个示例应用明明在写 —— 我猜的是views[].form.sharing,产物里的真实路径是views[].formViews.<key>.sharing。一个说不出自己没覆盖什么的探针,就是 finding 9 的形状;这个静默的零本来会以「已验证」落地。三个应用
validate全过(exit 0)。七、账本
no door行合并成一条(批 13 立的类 + 批 14 补的阳性对照要求和「一行两判」发现)。responsive、合并了五个 no-door 文件),批 14 的四条实测判决叠加其上。responsive行由批 13 删、action/report/dataset/dashboard四行由批 14 删,都是反向 pin 先红后删。表头与小计由存活行重算 = 110(29+20+14+9+7+7+6+4+4+4+3+1+1+1)。批 13 写的是 119、批 14 写的是 114 —— 各自对着自己的分支都对,对着合并都错,而正确答案 110 两边都没写过。这是
automation/段记录的那个失败的第 5、6 例,也是头一次两个错数字都落在这一行。git 只报了一个冲突区,两批要是碰的是不同段落就一个都不会报。check:strictness-ledger仲裁,绿。小计 88 of 110 authorable,22 属第四类。验证(全部在合并 origin/main 之后的树上重跑)
check:generated)+ 8 个源审计(含check:strictness-ledger/check:authorable-surface/check:api-surface,build 在前)pnpm build全仓 71/71 successful@objectstack/spec全量测试 +tsc --noEmit(见下方评论贴的真实输出)git merge origin/main(未 rebase、未 force-push)→ 重装 + 重建 → 七个生成物路径checkout origin/main --后整体重生成 → 断言兄弟条目存活。与 origin/main 的差异恰好只有一个文件(content/docs/references/ui/sharing.mdx),即整体重生成逐字节复现了 main 的产物,feat(spec)!: 收紧 responsive/SDUI 样式形状的未知键,并把另外五个文件按实测改判(#4001 批 13) #5003 / feat(spec,runtime,hono)!: 入站 rateLimit 从零建 seam —— 授权预算真正产生 429 (#4910, #4937) #5006 / spec:DashboardWidgetSchema.responsive按断点建模 —— objectui#3173 维护者已裁决;目标 17.0.0 正式版,带回退条款 #4876 的条目一条没丢归档的范围外发现(均未指派)
filter别名指向filters—— 一个 ReportSchema 同样拒绝的键(#4001 战役自己的假处方,第 5 例) #5013 ——ReportSchema的filter别名指向filters,而ReportSchema同样拒绝filters(真正的键是runtimeFilter)。作者照做会被拒第二次,而且第二次连建议都没有 —— 本战役自己的假处方,第 5 例。另附 5 条死条目zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input#5014 —— union 分支里的 unknown-key 处方永远到不了作者:zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input。影响本战役放进 union 分支的每一条策展文案NotificationActionSchema/EmbedConfigSchema实测没有授权门(#4001 批 14 改判的两个站点) #5015 —— 上述两个no door站点的 ADR-0049 去留判定options[]该不该讲字段级的逐选项词汇(color/visibleWhen)? —— 三处形状,三种拼法 #5016 —— action param 选项该不该讲字段级逐选项词汇(三处形状,三种拼法)已知触及面限制(实测记录,非推断)
compareTo是 union,所以本 PR 的收紧拒绝可靠、处方送不到(#5014)。拒绝仍是 #4001 的收益:静默半丢弃变成了compareTo上的硬失败。这条限制写进了dashboard.zod.ts,测试对两半分开 pin —— 作者今天真正看到的裸 message,和等着被传递的处方文案 —— 免得一个绿测试冒充一条没人打印的消息。变更集:
.changeset/unknown-key-strictness-ui-batch14.md(major,v17 rc 窗口)。🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Generated by Claude Code