fix(spec): 别名表内不得有两个 key 共用一个 probe —— 修 4 条 + 开第三条闸门断言 (#5481) - #5516
Merged
Conversation
…be (#5481) `strictUnknownKeyError` indexes the alias table by `aliasProbe(key)` — lowercased, `_`/`-`/space stripped — so two keys in one table that normalise identically share one index and the later entry silently overwrites the earlier. Nothing checked for it. Live defect: `these snap settings` carried `grid: 'gridSize'` and, at the end of the same table, `grid_: 'showGrid'`. Writing `grid: 24` was answered "Did you mean `grid` -> `showGrid`?", and `showGrid` is a boolean — a second rejection for taking the advice, ledger finding 7's shape. `grid_` is deleted; `visible` already carried that intent. Three dormant instances pointed both keys at the same target, so the overwrite changed nothing: `rollup`/`rollUp`, `object_name`/`objectName`, `strokeDasharray`/`strokeDashArray`. Since the probe already folds case and separators, the surviving entry accepts every spelling the deleted one did, so removing them preserves behaviour. `alias-integrity.test.ts` gains the third assertion: no two alias keys in one table may share a probe. It imports the real `aliasProbe` — extracted to `shared/alias-probe.ts`, a leaf module deliberately kept out of the barrel like `strict-object.ts` — rather than transcribing the expression, so widening the normalisation cannot leave the gate judging by an old rule. Census over the 235 runtime tables: exactly these four, now zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…as-probe-collision
This was referenced Aug 5, 2026
`gen:schema` rewrote the anchor when the merge base moved: `baseRev` cdfbee2 -> 168f60f, plus the two keys that commit itself added (`UpdateRecordConfig:multi`, `DeleteRecordConfig:multi`). Both facts come from main, not from this PR — the alias-table change adds and removes no authorable key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
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.
Fixes #5481
前置 #5486 已 MERGED,闸门文件
packages/spec/src/shared/alias-integrity.test.ts已在 main,本 PR 在其上加第三条断言。前提复核(改代码前)
issue 的行号是上午的,#5486 刚合并,所以先重新定位并实测复现,而不是照抄 issue。四处撞车在
origin/main(306ca50) 上全部属实。活缺陷的实测输出:第二行是关键:照着提示改就是第二次被拒 —— 账本 finding 7 的形状,由这场 campaign 自己的修复把作者指进去。
另外没有只信 issue 列的四条,而是跑了一次全量普查(遍历运行时 235 张表):撞车恰好 4 条,不多不少,与 issue 一致。
成因
strictUnknownKeyError用aliasProbe(key)(转小写 + 去掉_/-/空格)给别名表建索引。同一张表里两个 key 归一化后相同即共用一个下标,后写的静默覆盖先写的,且被覆盖的那条在任何作者能碰到它之前就已经消失。改动
1. 活缺陷
studio/flow-builder.zod.ts:删掉表尾的grid_: 'showGrid'。它把前面的grid: 'gridSize'覆盖了,于是想设网格像素间距的grid: 24被指向布尔的showGrid。visible: 'showGrid'本来就承载了显示/隐藏那个意图,所以删掉grid_不丢覆盖面。修后grid: 24得到grid → gridSize,而且照做能过(gridSize: 24解析成功)。2. 三条休眠冗余:
rollup/rollUp(field)、object_name/objectName(webhook)、strokeDasharray/strokeDashArray(chart series)。这三条两个 key 指向同一 target,覆盖后语义不变,所以今天没人踩到。各删一条。行为不变是可证的而非推测的:probe 已经吃掉大小写和分隔符,幸存的那条接受被删那条能接受的每一种拼法 —— 这条等价性现在由strict-object.test.ts里一条新测试钉住(用visible_when/VISIBLE-WHEN/Visible When/visiblewhen四种拼法验证单条 entry 全覆盖)。3. 第三条闸门断言:一张表内任意两个别名 key 的 probe 不得相同。失败信息点名文件、surface、两个 key、各自 target,以及幸存者是谁。
为什么多了一个
shared/alias-probe.ts断言必须用真的
aliasProbe判,不能在测试里抄一份正则。抄一份就正是 #5013 / #5481 这一类问题本身的形状 —— 哪天 probe 放宽(比如再吃掉.),抄了旧表达式的闸门会继续"通过",而它已经看不见新出现的撞车了。aliasProbe原本是suggestions.zod.ts的模块私有 const,而该文件被shared/index.ts以export *导出,直接导出它会扩大@objectstack/spec公共 API。所以抽到一个叶子模块,并刻意不进 barrel —— 与strict-object.ts的strictObjectDeclarations(同一类"给闸门用的内部接缝",测试按相对路径拿)完全一致的处理。放进strict-object.ts会形成 suggestions ↔ strict-object 循环导入,而该文件的注释记录过这个循环在OS_EAGER_SCHEMAS=1下真炸过,故未采用。check:api-surface绿,证实公共面没动。反向验证(方向是先定后跑)
预判:普通的红。 这条断言是对单张表 key 集合的纯谓词,既不喂下游计数闸门,也没有
??链的先后顺序问题,所以把删掉的肢体接回去应当增加一条 finding,并点名 flow-builder 那张表。实跑与预判一致 —— 临时把
grid_加回:只有新断言变红,其余 7 条不动;恢复后 8/8 绿。
消费半径走查
按"规则被谁消费"而不是"改了哪个包"来扫:全仓 grep 被删的三种拼法 +
grid_,packages/spec/src之外零命中(webhook 的objectName命中全部是 nav item 的合法声明键或plugin-webhooks内部字段,不走WebhookSchema的别名表);these snap settings/showGrid在 spec 之外也无断言。故无 fixture 需要改口径。验证
pnpm --filter @objectstack/spec test—— 312 files / 7965 tests 全绿(main 基线 7962,+3 为本 PR 新增断言)pnpm --filter @objectstack/spec typecheck——tsc --noEmit无输出pnpm --filter @objectstack/spec check:generated—— 9/9 up to date,无需重生成(别名表不进任何生成物)node scripts/check-nul-bytes.mjs—— OK;改动文件另做了grep -naP越界自扫,无裸控制字节changeset
按 PM 指示打了
@objectstack/specpatch:这是对作者可见的行为修复(grid: 24的处方从错的变成对的),不是纯内部改动,故走 changeset 而非skip-changeset。Generated by Claude Code