feat(spec)!: object 内层块拒绝未知键 —— 13 站点收紧,2 个站点依据实测保留 (#4001 批 19/20) - #5250
Merged
Conversation
…19/20) `data/object.zod.ts` 的顶层自 #1535/#4519/#4522 起就会拒绝未知键,而它下面 的内层块一直是 zod 默认的 `.strip`。这个不对称正是本批的价值所在:一个已经 见过顶层拒绝拼写错误的作者,完全有理由把 `lifecycle: { maxAge: '30d' }` 的 干净解析读作"接受"。object 是本仓作者写入量最大的元数据类型。 批 20 —— 14 个站点关掉 13 个: - `access`(ADR-0066 D2)、`lifecycle` 及其四个子块(retention/ttl/storage/ archive)、`fieldGroups[]`(ADR-0085)、`external`(ADR-0015)、 `userActions`、`systemFields`、`activityMilestones[]`、`publicSharing`、 `objectExtensions[]`。 - 可达性是实测而非假定:从 24 个元数据类型根加 `ObjectStackSchema` 做 BFS (4810 节点 / 25 根),每一个都按**身份**判定 `direct`,没有一个依赖 #5056 发现的 `derived-clone` 桥;同一次运行里 `ObjectSchema`/`PageSchema` 为正控制,批 14 的 `EmbedConfigSchema` 为负控制(unreachable,重叠 0.00)。 - parse 门单独探针、逐路径验证:今天顶层 REJECT、13 条嵌套路径全部静默 STRIP,所以每一次收紧转化的都是实测到的静默丢弃,而不是猜测。⚠️ `IndexSchema` 有意保留,这个"不动"才是本批的发现(#5114 类,这次是在 发布**之前**抓到而不是之后)。console 为这个形状自带了一份手抄 JSON-Schema (objectui `metadata-admin/EmbeddedItemEditor.tsx`)—— 因为 `index` 是框架 不发布 schema 的内嵌子类型 —— 而这份抄件已经漂移:它用 `where` 表示部分索引 谓词,spec 声明的是 `partial`。编辑器把表单结果拼回 `object.indexes[]` 后 PUT 整个对象,`saveMetaItem` 逐字保留 body 并做校验,所以单独关掉这个形状会 让 console 自己渲染的控件 422。该能力两个方向上其实都已经死了 —— `driver-sql` 的 `syncDeclaredIndexes` 只读 `name`/`fields`/`unique` —— 这恰恰是必须先修生产者、并给 `type`/`partial` 一个 ADR-0049 结论的原因: 今天把作者指向 `partial`,是一条声称超出平台实际交付的 guidance(finding 18)。 批 19 —— `ui/app.zod.ts` 的 `BaseNavItemSchema`:`verify` 检查结论为否, 不改姿态。台账假定成员 `.extend()` 该基底(finding 16 的陷阱),实际它们 spread `...shape`;两者机制不同,并且双向实测过。九个分支各自已经 `.strict()`, 基底是模块私有且从不 parse,关掉它是保证的 no-op —— 而 #4583 明确指出 no-op 收紧并非中性。`Class` 单元格未改动:它是机读的,而八个既有判定词 没有一个对"既不是门也不是死物"的形状是诚实的,加第九个属于改机读契约。 每条 alias/guidance 都是对 schema 的断言(finding 18),因此逐条锚定到具名 的兄弟契约并在测试中断言;`fieldGroups[]` 三个已弃用的 collapse 别名保持 **接受** —— 收紧一个形状不该把有文档的弃用变成拒绝。 已知并如实钉住的一处:`systemFields` 是 `false | {…}` 联合,拒绝信息是 `invalid_union` 的裸 "Invalid input"(#5014 摊平);批 18 的 `discriminatedUnion` 解法在这里不可用(一个分支是字面量,没有判别键)。 三处记录齐备:schema JSDoc + `object-strictness-batch20.test.ts` / `app-strictness-batch19.test.ts` + 台账散文行;计数由 os-regen 重新生成。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
merge 带入 #5221 的 chart 站点(482 → 483),计数文件按 merge=os-regen 的 约定从合并后的树重新生成,而不是任何一边的算术。authorable 仍为 16。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
IndexSchema 的保留(生产者漂移 + ADR-0049)与 BaseNavItemSchema 的 Class 词汇缺口都已单独归档,台账行与 schema JSDoc 现在指向 issue,而不是只说 '交给维护者'——一条指不出去的判定,下一个人只能重新测一遍。 Co-Authored-By: Claude Fable 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
|
Contributor
📓 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:
|
xuyushun441-sys
marked this pull request as ready for review
August 4, 2026 12:24
xuyushun441-sys
enabled auto-merge
August 4, 2026 12:24
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Aug 4, 2026
…-1) → 15 合并队列以 MERGE_CONFLICT 踢出后重新同步 origin/main(merge,非 rebase)。 #5237(#5073 allowAddTab)与本批从两个不同目录改这份台账,散文行不重叠, git 干净合并;数字零手改,counts.md 按 merge=os-regen 从合并后的树整体重生成。 重算结果 15 = `ui/` 7 → 6(#5237)+ `data/` 22 → 9(批 20),两边分支都没写过 这个数——正是 #5107 拆分要达到的效果。这一条也补进 data/ 段落:它是第十二次 实例,也是第一次没让任何人付出代价。 #5237 对 `ui/app.zod.ts` 的改动(`apis` 墓碑文案)与本批的 BaseNavItemSchema 判定互不相干,两侧都在;它对 scripts/strictness-ledger.test.ts 的 view 计数 5 也是它自己的领地。 门:spec 308 文件 / 7929 测试通过;typecheck 通过;check:generated 9/9; check:strictness-ledger 通过(484 站点,authorable 剩 15)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#4001 战役的最后两个 authorable strip 组。引用 #4001,不关闭它。
逐站点判定
data/object.zod.tsObjectAccessConfigSchemaaccess;BFSdirect;探针:今天静默 stripdata/object.zod.tsLifecycleSchemalifecycle;direct;静默 stripdata/object.zod.tsLifecycleSchema.retentiondata/object.zod.tsLifecycleSchema.ttldata/object.zod.tsLifecycleSchema.storagedata/object.zod.tsLifecycleSchema.archivedata/object.zod.tsObjectFieldGroupSchemafieldGroups[];direct;静默 stripdata/object.zod.tsObjectExternalBindingSchemaexternal;direct;静默 stripdata/object.zod.tsObjectSchemaBase.userActionsdata/object.zod.tsObjectSchemaBase.systemFieldsdata/object.zod.tsObjectSchemaBase.activityMilestones[]data/object.zod.tsObjectSchemaBase.publicSharingdata/object.zod.tsObjectExtensionSchemaobjectExtensions[]+defineObjectExtension;静默 stripdata/object.zod.tsIndexSchemawhere,spec 声明partial—— 收紧会 422 console 自己渲染的控件(#5114 类)。#5247 / #5248ui/app.zod.tsBaseNavItemSchema...shape而非.extend(),九个分支各自已.strict(),基底模块私有且零 parse —— 关掉是保证的 no-op(#4583)。Class 词汇缺口:#5249data/object.zod.ts14 → 1 strip;ui/app.zod.ts保持 1。全局 authorable strip 29 → 16。测量方法(不是读代码读出来的)
可达性:从 24 个元数据类型根加
ObjectStackSchema做 BFS(4810 节点 / 25 根),六个具名 schema 全部按身份判定direct(重叠 1.00),没有一个依赖 #5056 发现的derived-clone桥。同一次运行里正负控制都在:ObjectSchema/PageSchemadirect,批 14 的EmbedConfigSchemaunreachable(重叠 0.00)。parse 门:单独探针、逐路径验证。今天
ObjectSchema.safeParse在顶层 REJECT,而 13 条嵌套路径全部静默 STRIP —— 所以每一次收紧转化的都是实测到的静默丢弃。门本身也断言了:saveMetaItem的 422(getMetadataTypeSchema('object'))、ObjectSchema.create()、defineObjectExtension()。语料网:仓内 102 个
*.object.ts里 101 个导入通过(唯一失败的是create-objectstack模板,依赖解析问题,非校验失败)。ObjectSchema.create()在模块加载时就跑完整.parse(),所以这是一次真实语料的全量验证。批 20 的发现:一个在发布之前抓到的 #5114 类缺陷
IndexSchema有意保留,这个"不动"是本批的主要产出。console 为这个形状自带一份手抄 JSON-Schema(objectui
metadata-admin/EmbeddedItemEditor.tsx→FALLBACK_SCHEMAS.index)——理由正当:index是内嵌专用子类型,框架没有槽位发布它的 schema。但这份抄件已经漂移:它用where表示部分索引谓词,spec 声明的是partial(枚举里还多了brin)。编辑器把表单结果拼回
object.indexes[]再 PUT 整个对象,saveMetaItem逐字保留 body 并校验 —— 所以收紧这一个形状会让 console 自己渲染的控件 422。与 #5073 的
allowAddTab不同,这个能力两个方向上都已经死了:driver-sql的syncDeclaredIndexes只读name/fields/unique(ADR-0120 合并后的DeclaredIndexInput也是这四个字段),where和partial都到不了任何 DDL。这正是"先修生产者"还不够的原因:今天把作者指向partial,是一条声称超出平台实际交付的 guidance —— 台账 finding 18 记录的正是本战役已经发过的四条假 guidance。所以收紧被门在两件事上:objectui 改名(#5247)+type/partial的 ADR-0049 结论(#5248)。批 19:
verify检查结论为否台账假定成员
.extend()基底(finding 16 的陷阱)。实际它们 spread...BaseNavItemSchema.shape,而这是不同的机制 —— 双向实测,因为"关基底会连带关成员"和"关基底是 no-op"互斥:九个分支各自已
.strict()(逐分支通过真实的门AppSchema.navigation断言,正控制 = 基底贡献的每个键都被接受,含没有任何分支自己声明的requiresService;负控制 = 未声明键被拒),基底模块私有且零 parse。关掉它是保证的 no-op,而 #4583 明确指出 no-op 收紧并非中性。Class单元格未改动,这是刻意的:它是机读的,而八个既有判定词对"既不是门也不是死物"的形状没有一个诚实 ——no door规定的 ADR-0049 退役在这里是破坏性的(九个分支共享这套键)。加第九个词属于改机读契约,已归档为 #5249 请维护者决策。verify是唯一对结论不发表主张的既有值,小计因此停在原处。curation
每条 alias/guidance 都是对 schema 的断言(finding 18),因此逐条锚定到具名的兄弟契约并在测试中断言其正确性(包括"被指向的写法确实能 parse")。
本文件最主要的真实错误是摊平,所以
lifecycle向下指进拥有该键的子块(maxAge→retention、expireAfter/field→ttl、strategy/shards/unit→storage、after/to/keep→archive)。这条不只是整洁问题:摊平的maxAge让retention缺席,于是 ADR-0057 §3.5 转而以"无界"拒绝该对象 —— 报的是完全不相干的键。其余 wrong-layer:
userActions.sort/search/filter/editInline指向ui/view.zod.ts同名但词汇完全不相交的块;userActions.clone指向enable;systemFields.owner指向ownership(该块自己的字段文档提到这个键,而 shape 从未声明);external.allowWrites说明 ADR-0015 的双重 opt-in,并与datasource.zod.ts自己的writable → allowWrites别名互为镜像;fieldGroups[].fields说明成员关系的方向(声明在 FIELD 上);objectExtensions[].actions/hooks/listViews直说 merge 没有槽位。fieldGroups[]三个已弃用的 collapse 别名保持接受 —— 收紧一个形状不该把有文档的弃用变成拒绝。已知并如实钉住的一处
systemFields是false | {…}联合,拒绝信息是invalid_union的裸"Invalid input"(#5014 摊平),curated 的处方在issue.errors[1][0].message里。批 18 的discriminatedUnion解法在这里不可用(一个分支是字面量,没有判别键)。没有在收紧批里现发明机制,而是按批 18 的先例如实钉住。本文件其余站点都是普通对象,处方直接出现在顶层信息里。顺带修复
metadata-authoring-lint.test.ts的三个 fixture 用userActions作为"静默 strip"的样本,收紧后失去主体 —— 这是该文件自己记录过两次的模式。按其惯例更新:两处改断言交接(键不是没被报告,而是被 REJECT),并恢复了那条按位置索引的断言 —— 该测试在 6d 失去主体时留下过指令"若再出现带嵌套数组的 strip 面,恢复这里,别让它第二次失测",object.indexes[]正好是它。验证
pnpm --filter @objectstack/spec test— 308 文件 / 7919 测试通过pnpm --filter @objectstack/spec typecheck— 通过pnpm --filter @objectstack/spec check:generated— 9/9 生成物 up to datepnpm --filter @objectstack/spec check:strictness-ledger— 通过(483 站点,authorable strip 剩 16)pnpm --filter @objectstack/objectql test— 115 文件 / 1839 测试通过pnpm --filter @objectstack/metadata-protocol test— 39 文件 / 348 测试通过*.object.ts导入通过已合并 origin/main(未 rebase),计数按
merge=os-regen从合并后的树重算(merge 带入 #5221 的 chart 站点,482 → 483)。三处记录
每个判定都写在三个地方:schema 自身的 JSDoc、pin 测试(
object-strictness-batch20.test.ts/app-strictness-batch19.test.ts)、以及台账散文行。两个保留站点的 pin 尤其重要 —— 一个只在散文里解释的"有意打开",和一个没人做完的站点,reverse pin 分辨不出。Refs #4001. 衍生 issue:#5247 #5248 #5249
Generated by Claude Code