Skip to content

fix(spec): 别名表内不得有两个 key 共用一个 probe —— 修 4 条 + 开第三条闸门断言 (#5481) - #5516

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-5481-alias-probe-collision
Aug 5, 2026
Merged

fix(spec): 别名表内不得有两个 key 共用一个 probe —— 修 4 条 + 开第三条闸门断言 (#5481)#5516
os-zhuang merged 3 commits into
mainfrom
claude/issue-5481-alias-probe-collision

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5481

前置 #5486 已 MERGED,闸门文件 packages/spec/src/shared/alias-integrity.test.ts 已在 main,本 PR 在其上加第三条断言。

前提复核(改代码前)

issue 的行号是上午的,#5486 刚合并,所以先重新定位并实测复现,而不是照抄 issue。四处撞车在 origin/main (306ca50) 上全部属实。活缺陷的实测输出:

MESSAGE >>> Unrecognized key(s) on these snap settings: `grid`. …
            Did you mean `grid` → `showGrid`?
FOLLOW-THE-ADVICE >>> Invalid input: expected boolean, received number

第二行是关键:照着提示改就是第二次被拒 —— 账本 finding 7 的形状,由这场 campaign 自己的修复把作者指进去。

另外没有只信 issue 列的四条,而是跑了一次全量普查(遍历运行时 235 张表):撞车恰好 4 条,不多不少,与 issue 一致。

成因

strictUnknownKeyErroraliasProbe(key)(转小写 + 去掉 _/-/空格)给别名表建索引。同一张表里两个 key 归一化后相同即共用一个下标,后写的静默覆盖先写的,且被覆盖的那条在任何作者能碰到它之前就已经消失。

改动

1. 活缺陷 studio/flow-builder.zod.ts:删掉表尾的 grid_: 'showGrid'。它把前面的 grid: 'gridSize' 覆盖了,于是想设网格像素间距的 grid: 24 被指向布尔的 showGridvisible: '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.tsexport * 导出,直接导出它会扩大 @objectstack/spec 公共 API。所以抽到一个叶子模块,并刻意不进 barrel —— 与 strict-object.tsstrictObjectDeclarations(同一类"给闸门用的内部接缝",测试按相对路径拿)完全一致的处理。放进 strict-object.ts 会形成 suggestions ↔ strict-object 循环导入,而该文件的注释记录过这个循环在 OS_EAGER_SCHEMAS=1 下真炸过,故未采用。check:api-surface 绿,证实公共面没动。

反向验证(方向是先定后跑)

预判:普通的红。 这条断言是对单张表 key 集合的纯谓词,既不喂下游计数闸门,也没有 ?? 链的先后顺序问题,所以把删掉的肢体接回去应当增加一条 finding,并点名 flow-builder 那张表。

实跑与预判一致 —— 临时把 grid_ 加回:

FAIL … no two alias keys in one table collapse onto the same probe (#5481)
+ [
+   "studio/flow-builder.zod.ts:241 — \"these snap settings\": `grid` -> `gridSize` — 2 keys share the probe `grid`: `grid` -> `gridSize`, `grid_` -> `showGrid` — only `showGrid` survives",
+ ]
 Tests  1 failed | 7 passed (8)

只有新断言变红,其余 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/spec patch:这是对作者可见的行为修复(grid: 24 的处方从错的变成对的),不是纯内部改动,故走 changeset 而非 skip-changeset


Generated by Claude Code

…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
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 3:08pm

Request Review

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

`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
@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 15:48
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit a9f32df Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5481-alias-probe-collision branch August 5, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

别名表内部撞车:aliasProbe 归一化后两个 key 相同,后者静默覆盖前者(snap.grid 被指向 showGrid)

2 participants