fix(spec,objectql)!: hook 的空目标不再被静默放大成通配符 - #4281
Merged
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 31, 2026 02:51
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.
起因
原计划是给
object: '*'的通配 hook 加 review 提示(#4001 campaign 的收尾项之一)。摸实现时发现了更硬的东西:通配符不需要作者主动写就会发生。HookSchema.object是z.union([z.string(), z.array(z.string())]),没有任何非空约束;hook-binder.ts的normalizeObjects则是:而
'*'是引擎派发的 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 要求,错误必须可修而非只是「大声」: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.writeL2 body 是系统里最难验证的形状(链到写集 gap)。验证
@objectstack/spec276 文件 / 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.ts24 过(新增 9 个:四种空目标形状各自不注册且不触发任何对象、混合列表只丢空成员、显式'*'照常绑定、strict 致命、非 strict 仍记录并继续)hook.test.ts56 过(新增 5 种空目标拒绝 + 数组内通配仍通过,并断言错误信息里同时出现「must name at least one object」和object: '*')tsc --noEmitspec / objectql 均干净check:role-word/check:doc-authoring/check:nul-bytesOKfilter在场时,同时传的字段级参数被静默丢弃(#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: everyerrorrule mirrors a runtime enforcement point」。对故意写的通配 hook 发一条无动作可做的告警,不镜像任何运行时执行点,只会变成每次os validate的噪音。意外的通配现在在 parse 期就是硬错误(严格优于 lint 告警),故意的通配交给文档里的 review bar。破坏性
标
!:此前能解析并绑定的元数据现在被拒。但被拒的只有两类 —— 作者不知情的意外通配,和拼错的通配。没有合法用法受影响,第一方扫描零命中。关联:#4001(母 issue)、ADR-0078、#4271(写集 lint)。
Generated by Claude Code