Skip to content

fix(spec,objectql)!: hook 的空目标不再被静默放大成通配符 - #4281

Merged
os-zhuang merged 1 commit into
mainfrom
claude/unknown-key-stripping-strictness-h3a3zt
Jul 31, 2026
Merged

fix(spec,objectql)!: hook 的空目标不再被静默放大成通配符#4281
os-zhuang merged 1 commit into
mainfrom
claude/unknown-key-stripping-strictness-h3a3zt

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

起因

原计划是给 object: '*' 的通配 hook 加 review 提示(#4001 campaign 的收尾项之一)。摸实现时发现了更硬的东西:通配符不需要作者主动写就会发生。

HookSchema.objectz.union([z.string(), z.array(z.string())]),没有任何非空约束;hook-binder.tsnormalizeObjects 则是:

if (Array.isArray(target)) return target.length > 0 ? target : ['*'];
if (typeof target === 'string' && target.length > 0) return [target];
return ['*'];

'*' 是引擎派发的 match-everything 哨兵(engine.ts:targets.includes('*'))。合起来:

作者写的 解析 实际注册到
object: '' ✅ 通过 每一个对象
object: [] ✅ 通过 每一个对象
object: [''] ✅ 通过 名为 '' 的对象 → 永不触发

实测确认(改动前逐个 safeParse + binder 行为核对)。这是 #4001 的失效模式反向发作:平常的静默剥离是把写下的东西变窄,这一条把「没填」放大成系统里最大的影响面 —— 全对象 × 全事件,且无任何诊断。[''] 则是另一头,ADR-0078 的「静默失效元数据」:注册在一个谁也匹配不上的对象名上,永远不会跑。

两种都不是任何人的本意 —— 这个兜底还把错误记录了两次(见下),本身就说明没人有意为之。

改动

1. HookSchema.object 加 refinement(packages/spec/src/data/hook.zod.ts)
一条 .refine() 覆盖两个分支,'' / ' ' / [] / [''] / ['account',''] 全部拒绝。按 #4001 要求,错误必须可修而非只是「大声」:

A hook object target must name at least one object. An empty target is not "no target": until #4001 '' and [] were widened to the wildcard '*', registering the hook on EVERY object … Name the object(s) — object: 'account' or object: ['account', 'contact'] — or, if firing on every object really is the intent, write the wildcard explicitly: object: '*'.

2. binder 独立堵一遍(packages/objectql/src/hook-binder.ts)
bindHooksToEngine 吃的是 Hook[](z.input)且不 parse —— 所以光改 schema 覆盖不到这条路径。normalizeObjects 不再放大,改为过滤空白后返回 [];空结果走已有的 skipped + errors 通道并 warn,而不是静默不注册。

3. 顺带修掉 strict 从来没生效过
BindHooksOptions.strict 文档写的是「treat unresolved hooks as fatal errors … fail fast on misconfiguration」,但每个 hook 的 try/catch 把它自己 strict 分支抛出的异常吞了 —— 结果是同一个失败被记录两次然后继续绑定。探针实测确认(THREW: false | errors: [<skip 记录>, <被吞的 throw>])后才改。现在 strict 下绑定失败真的致命。之所以在本 PR 一并修:我的新分支也用 opts.strict,照抄一个已知失效的模式比修掉它更糟。

4. 文档(content/docs/automation/hooks.mdx)新增 Targeting objects 一节:三种写法、空目标被拒的 Callout、以及 Wildcard hooks earn a higher review bar —— 影响面含未来才创建的对象、成本随对象数×写入量放大(该用 condition 而非 body 里 early return)、通配 + api.write L2 body 是系统里最难验证的形状(链到写集 gap)。

验证

  • @objectstack/spec 276 文件 / 7160 用例全过;packages/spec 全部 12 个 check 闸门 OK(docs / skill-refs / skill-docs / api-surface / spec-changes / upgrade-guide / liveness / react-blocks / react-conformance / skill-examples / variant-docs / strictness-ledger),build 后跑的 api-surface 也在内
  • hook-binder.test.ts 24 过(新增 9 个:四种空目标形状各自不注册且不触发任何对象、混合列表只丢空成员、显式 '*' 照常绑定、strict 致命、非 strict 仍记录并继续)
  • hook.test.ts 56 过(新增 5 种空目标拒绝 + 数组内通配仍通过,并断言错误信息里同时出现「must name at least one object」和 object: '*')
  • tsc --noEmit spec / objectql 均干净
  • 仓库级 check:role-word / check:doc-authoring / check:nul-bytes OK
  • objectql 全量有 75 failed / 4 files(protocol-data、protocol-unknown-query-param、query-expression-conformance、protocol-discovery)—— 已证预存在:stash 掉本 PR 全部改动后重跑,同样 75 failed / 同样 4 个文件(通过数 1245 → 1254,差值正是我新增的 9 个用例)。与 hook 无关,属 REST 列表:显式 filter 在场时,同时传的字段级参数被静默丢弃(#4134 的邻居) #4164/REST 列表:未知查询参数被静默当作字段过滤器 —— ?pageSize=5 返回 200 + 空列表 #4134 一族
  • 第一方资产扫描:处使用空目标;plugin.ts / 默认权限集里的 '*' 是代码注册路径,不经 schema,不受影响

关于原计划里的 lint

原本要加一条「通配 hook → advisory warning」。看过 validate-security-posture.ts 的纪律说明后放弃了 —— 那里明写「Per ADR-0049 discipline these are NOT advisory security: every error rule mirrors a runtime enforcement point」。对故意写的通配 hook 发一条无动作可做的告警,不镜像任何运行时执行点,只会变成每次 os validate 的噪音。意外的通配现在在 parse 期就是硬错误(严格优于 lint 告警),故意的通配交给文档里的 review bar。

破坏性

!:此前能解析并绑定的元数据现在被拒。但被拒的只有两类 —— 作者不知情的意外通配,和拼错的通配。没有合法用法受影响,第一方扫描零命中。

关联:#4001(母 issue)、ADR-0078、#4271(写集 lint)。


Generated by Claude Code

…the wildcard

HookSchema.object had no emptiness constraint, so '', [] and [''] all
parsed. normalizeObjects then mapped the first two to ['*'] — the
engine's match-everything sentinel — so a hook whose target was left
blank registered on EVERY object, on every event it listed, silently.
That is #4001 pointed the wrong way: the usual silent strip narrows what
was written, this one widened blank intent into the broadest possible
blast radius. [''] failed the other way, registering on an object name
nothing matches (ADR-0078 silently-inert).

Both are refused now, in two places: HookSchema gains a refinement whose
error names the spellings that work, and the binder — which accepts
unparsed Hook input, so the schema alone would not cover it — skips and
records an untargetable hook instead of widening it. A wildcard hook
stays legitimate; it has to be spelled '*' so a reviewer sees it.

Also fixes bindHooksToEngine's strict option: documented as fail-fast,
it never threw, because the per-hook try/catch swallowed the throw its
own strict branch raised — the failure was recorded twice and binding
carried on. Proven by probe before changing it.

Docs: hooks.mdx gains a Targeting objects section covering the three
forms, the refusal, and the review bar a wildcard earns.

First-party scan: zero assets use an empty target; the wildcards in
plugin.ts / permission sets are code-registered and unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
@vercel

vercel Bot commented Jul 31, 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 Jul 31, 2026 2:28am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @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 packages/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 @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/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 packages/objectql, @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/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • 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/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • 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/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @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/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.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 packages/objectql, @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/objectql, @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/objectql, @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/objectql, @objectstack/spec)
  • content/docs/ui/actions.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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 02:51
@os-zhuang
os-zhuang merged commit ab9fb5c into main Jul 31, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/unknown-key-stripping-strictness-h3a3zt branch July 31, 2026 02:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants