feat(spec)!: field 收紧,复用那张早就知道哪条建议是错的表(#4001 批 6b) - #4531
Merged
Conversation
…w which advice would be wrong (#4001) `FieldSchema` carries more silently-stripped keys than any shape in the spec, and it said so about itself for two releases. Two notes on the object — one on `accept`/`maxSize`, one on the five pruned governance keys — both state that a write "parsed clean and the key was silently stripped", and both name it the ADR-0104 failure class. Neither could act, because the object was not strict. The guidance is derived rather than hand-written, and the reason is a bug the first pass shipped. `FIELD_KEY_GUIDANCE` is twenty-odd curated entries for this exact surface, already held honest by a test. A hand-written table beside it is a second copy of the truth, and it proved that immediately: the lint suppresses the suggestion for `pii` BECAUSE `pii` is three edits from `min`, so a bare suggester answers a personally-identifiable-information key with "did you mean `min`?" — confident, wrong, unrelated. The hand-written pass did exactly that. `FieldSchema` now reads the table (`to` → alias, `why` → guidance). The table is unchanged; its consumer moved, which is the intended end state for every entry. The two entries that matter most are the ones that read as protection and were not: `encryptionConfig` and `maskingRule` "implied at-rest protection that never happened". A field declaring either was stored in plaintext exactly as if it had not, silently. The rejection now points at `type: 'secret'` and `requiredPermissions`. A cycle the whole test suite passed through. `suggestions.zod` imports `FieldType` from `field.zod`, so adopting `strictObject` here closed a loop. Under OS_EAGER_SCHEMAS=1 every lazySchema body runs at module init, so the loader hit a half-initialized module and threw before any schema was built. 284 files and 7,239 cases went green over it — tests import lazily, so the cycle never resolved in the order that breaks. Only the eager build caught it. `strictObject` now defers its error map to first use: free, and cycle-proof for every schema after this one instead of making each conversion prove it is not in a loop. Pinned via an alias-table getter that fires exactly when the map is built, and verified to go red when the map is hoisted back to construction. `field` gains its ADR-0010 envelope — it was the ONE type the original probe actually checked, so it was the only gap visible while that probe was green, and it outlasted every gap the probe was hiding. Debt list down to one, from eight. Registered types closed: 22 of 25. Still open: action, dashboard, view. Verified: 284 files / 7240 tests, tsc clean, 8 generated artifacts current, all 10 spec gates green, and CRM/Todo/showcase/platform-objects build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 1, 2026 18:46
os-zhuang
enabled auto-merge
August 1, 2026 18:46
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:
|
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 批 6b(清单)。批 1–6a = #4514 / #4519 / #4522 / #4527 / #4528 / #4529 / #4530,均已合并。
这个对象为自己写了两个 release 的检讨
FieldSchema携带的「被静默剥离的键」比 spec 里任何一个形状都多,而且它自己一直知道。对象上有两处注释——一处讲accept/maxSize,一处讲被砍掉的五个治理键——都写着「写下去会 parse 干净、键被静默剥离」,都点名这是 ADR-0104 那一类失败。两次都只能写注释,因为这个对象不是
.strict()。这个 PR 就是那两段注释想要的修复。guidance 是推导的,不是手写的——因为手写那版当场犯了错
FIELD_KEY_GUIDANCE(在data/authoring-key-lint.ts)是专为这个表面curated 的二十多条,每一条都是野外抓到的,而且已经被测试守着(每个to必须真的是FieldSchema声明过的键)。我第一版在旁边手写了一张 guidance 表。那是真相的第二份拷贝,而它立刻证明了为什么这不行:
于是一个纯编辑距离的建议器,会对一个「个人可识别信息」的键回答「你是不是想写
min?」——自信、错误、而且是完全无关的概念。手写那版不知道这件事,拒绝信息就是这么说的。现在
FieldSchema直接读那张表(to→ alias,why→ guidance)。注意搬动的是什么:表没变,也没废弃——变的是它的消费者。
field关闭后 lint 不再走到它,于是同一份 curation 从「驱动一条告警」变成「驱动一条拒绝」。那正是这张表里每一条的目标终点。其中最要紧的两条,是读起来像保护、实际不是的那两个:
encryptionConfigmaskingRule一个声明了它们的字段,存的就是明文,和没声明一模一样,而且一声不吭。拒绝信息现在指向
type: 'secret'和requiredPermissions(ADR-0066 D3,由 FieldMasker 真正执行)。一个整个测试套件都通过了的循环依赖
shared/suggestions.zod从data/field.zod导入FieldType。所以field.zod一用上strictObject,图就闭环了:在
OS_EAGER_SCHEMAS=1(build-schemas.ts的运行方式)下,每个lazySchema体在模块初始化时就执行,于是加载器撞上一个半初始化的模块,在任何一个 schema 建出来之前就抛了Cannot read properties of undefined (reading 'strictUnknownKeyError')。284 个测试文件、7239 个用例,全绿地从它上面开过去。
测试是懒加载的,那个会炸的解析顺序根本不会出现。只有 eager 构建能抓到。 这是第 9 条发现的另一面——那次是仪器报告了它没有的覆盖率,这次是仪器根本不对,而绿色对这个失败模式什么都不意味着。
修法:
strictObject把 error map 推迟到首次使用。代价为零(只有拒绝时才需要它),而且让 helper 对后面每一个 schema 都免疫循环,而不是让每次转换自己去证明「我不在环里」。property 用一个可观测量钉住了——alias 表上的一个 getter,正好在 map 被构建时触发——并且验证过:把 map 提回构造期,它会报红。
闸门
field补上 ADR-0010 信封。它是当初那个探针唯一真正检查过的类型(另外 24 个都走了 early return),所以在探针报绿的那段时间里,它是唯一看得见的缺口——结果它比探针藏起来的每一个缺口都活得更久。信封欠债从 8 降到 1(只剩
action)。SelectOptionSchema、CurrencyConfigSchema以及FieldSchema下的嵌套形状(lookup 列、lookup 过滤、dependsOn、roll-up)一并关闭。刻意不动的:
AddressSchema/LocationCoordinatesSchema/CurrencyValueSchema是运行时值形状,全仓没有任何消费者,其中两个已标记随下个 major 移除——它们不是授权面,strictness 不是它们该回答的问题。进度
注册类型顶层已关闭:22 / 25。仍剥离:
action·dashboard·view验证
tsc --noEmit干净check:*全绿授权影响:
FieldSchema没声明的键从「静默丢弃」变成「拒绝」——本来就已经被忽略。参考
🤖 Generated with Claude Code
https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
Generated by Claude Code