Skip to content

feat(spec,objectql): hook 注册面新增 excludeObjects,可表达「全局但排除这些对象」 (#5928) - #6575

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-5928-hook-exclude-objects
Aug 8, 2026
Merged

feat(spec,objectql): hook 注册面新增 excludeObjects,可表达「全局但排除这些对象」 (#5928)#6575
qq9340100 merged 2 commits into
mainfrom
claude/issue-5928-hook-exclude-objects

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5928

按裁决评论(issue 首条,claude[bot] 5205548295)采纳 A 形状:registerHookHookEntry 新增声明式排除面 excludeObjects,匹配语义 matches = allowMatches && !excludeMatches

前提复核(origin/main)

裁决的两处论据修正已贯彻。原单行号均已漂移,内容逐处核实成立:

契约点 单据记录 实测(origin/main)
HookEntry.object 645/647 engine.ts:698
registerHook options 1096 engine.ts:1147
triggerHooks 按对象匹配 1363 engine.ts:1415
hasHooksFor 1396 engine.ts:1442(镜像在 1448)
契约面声明 contracts/objectql-engine.ts:141 同文件 :187-191

excludeObjects 也确非新造名词:api/rest-server.zod.ts:378system/disaster-recovery.zod.ts:230 已用它表达「全部减去这些」。

为什么允许列表不够

允许列表与拒绝列表只在封闭全集上可互换,而对象全集在运行期是开放的:/meta PUT 成功后 applyObjectRegistryMutation 直接把新对象注册进引擎,而 SchemaRegistry.registerObject 不发任何事件,插件侧没有可订阅的通道去维护一份枚举出来的名单。于是「全局但排除这几张平台表」只剩两条错路 —— 知识留在 handler 里早退(注册面仍是全局,#5284/#5038 的按对象门对这些对象判真、白读一遍行集),或把补集枚举进 object(名单在启动时冻结,此后新建的对象静默不被覆盖,对审计插件是无声的合规倒退)。探针 D 的两个方向都已进测试。

红线映射

# 红线 落点
1 单一匹配函数 新增 hookMatchesObject();triggerHooks(engine.ts:1560)与 hasHooksFor(:1593)两处手写实现同时收敛到它
2 性质 pin:门不得比派发更紧 hook-exclude-objects.test.ts 以 allow×exclude 全矩阵断言蕴含方向 dispatched ⇒ gate open(非等价:hasHooksFor 故意不看 skipAutomations,更松合法)
3 拒绝语义沿 #4281 / ADR-0078 ''['']、任一空白成员 → throw;排除面出现 '*' → throw(减掉全集 = 永不触发)。三种拒绝均有用例,消息给出改法
4 不加谓词回调 未加 objectFilter
4 不动 authorable 面 packages/spec/src/data/hook.zod.ts 零改动;check:authorable-surface 绿即为证
5 单 PR 跨两包 spec contracts + objectql 同一 PR,未拆两段(避免 declared≠enforced 窗口)
6 注册日志带排除面 logger.debug('Registered hook', …) 增加 excludeObjects,有用例钉住
7 不做 #5860 侧改动 plugin-audit 零改动,本单只交付表达能力
8 changeset 双包 minor .changeset/hook-exclude-objects-registration-face.md,形状参照 hook-empty-target-not-wildcard

叙事按裁决的论据修正:真实消费者只写 plugin-audit 一家,未提 service-storage(它是动态谓词 activeFileFields(engine, object) 早退,excludeObjects 表达不了,也不该用静态名单表达 —— 同一个探针 D 陷阱)。

两处刻意的判断,请复核

其一:excludeObjects: [] 接受,不拒绝。 它是「什么都不减」的诚实拼写,与省略该键同义,也是 excludeObjects: [...SKIP_OBJECTS] 在源名单为空时的自然取值。#4281 拒绝 object: [] 的理由在这里不存在 —— 那边 binder 会把空目标放大'*',而这里没有任何东西改写该值。已有用例钉住。

其二:允许面保留真值判断,行为守恒。 hookMatchesObject 的允许半边沿用两份旧实现共用的 if (entry.object),而非读起来更准的 !== undefined。二者仅在 object: '' 上不同:今天它注册的是全局 hook(falsy ⇒ 不过滤),即 #4281 的失败模式在它未覆盖的代码路径上的残留。在本 PR 里翻转它,会把一个「对所有对象触发」的 hook 静默变成「对任何对象都不触发」。故按原样保留、显式钉住(注明是保留而非认可),并另行记录为 #6573

验证读数

逆向验证(方向先判后跑)

R1 —— 删掉共享匹配函数的减法半边(声明、日志、拒绝保留)。预判:语义用例红,性质 pin 绿。实测 6 红 13 绿 —— 两条真正的性质断言如预判保持绿,因为它守的是门与派发的关系,不是排除面的存在;多出的 1 红是 skipAutomations 那条同时携带了一句排除断言,按它自己的理由红,不是 pin 抓到的。

R2 —— 让门比派发更紧(派发只读允许面,门读完整匹配)。预判:性质 pin 红。实测正是它红,并打印出违例作用域:

gate TIGHTER than dispatch for {"excludeObjects":"account"} on 'account': a hook fired that hasHooksFor said could not

一条诚实读数:多注册组合那条用例在 R2 下仍绿 —— 集合里只要有一个全局 entry,门对任何对象都是开的,violation 无从浮现。检出力在单 entry 矩阵上,已在测试内注明,不要把它读成有牙的断言。

落地连锁

合入后 #5860 解锁,收敛为「5 个注册带 excludeObjects: [...SKIP_OBJECTS]」。本 PR 不含任何 plugin-audit 改动。

范围外发现


Generated by Claude Code

claude added 2 commits August 8, 2026 05:13
注册契约此前只有一种表达能力:`object` 允许列表(缺省 = 全局,`'*'` = 全集)。
允许列表与拒绝列表只在**封闭全集**上可互换,而对象全集在运行期是开放的 ——
`/meta` PUT 成功后 `applyObjectRegistryMutation` 直接把新对象注册进引擎,
`SchemaRegistry.registerObject` 不发任何事件,插件侧没有可订阅的通道来维护
一份枚举出来的名单。于是「全局但排除这几张平台表」只剩两条错路:知识留在
handler 里早退(注册面仍是全局,#5284/#5038 的按对象门对这些对象判真、白读
一遍行集),或把补集枚举进 `object`(名单在启动时冻结 —— 探针 D:此后新建
的对象静默不被覆盖,对审计插件是无声的合规倒退)。

## 前提复核(origin/main,行号已漂移,以内容为准)

裁决评论(claude[bot] 5205548295)为唯一权威。逐处核对全部成立,仅行号变动:

- `HookEntry`            engine.ts:698(单据记 645/647)
- `registerHook` options engine.ts:1147(单据记 1096)
- `Registered hook` 日志 engine.ts:1182
- `triggerHooks` 按对象匹配 engine.ts:1415(单据记 1363)
- `hasHooksFor`          engine.ts:1442/1448(单据记 1396)
- 契约面声明             spec/src/contracts/objectql-engine.ts:187-191(单据记 141)

`excludeObjects` 亦非新造名词:rest-server.zod.ts:378 与
disaster-recovery.zod.ts:230 已用它表达「全部减去这些」。

## 红线逐条落点

1. 单一匹配函数 —— 新增 `hookMatchesObject(entry, objectName)`,
   `triggerHooks` 与 `hasHooksFor` 两处手写实现同时收敛到它。
2. 性质 pin —— `hook-exclude-objects.test.ts` 以 allow×exclude 全矩阵断言
   「门永远不得比派发更紧」(蕴含方向 `dispatched ⇒ gate open`,不是等价:
   `hasHooksFor` 故意不看 `skipAutomations`,更松是合法的)。
3. 拒绝语义(沿 #4281 / ADR-0078)—— `''` / `['']` / 任一空白成员拒绝;
   `'*'` 出现在排除面拒绝(减掉全集 = 永不触发)。均 throw 且消息给出改法。
   `[]` 明确接受:它是「什么都不减」的诚实拼写,也是 `[...SKIP_OBJECTS]`
   在源名单为空时的自然取值 —— #4281 拒绝 `object: []` 是因为 binder 会把它
   **放大**成 `'*'`,这里没有任何东西改写该值。
4. 两个不做 —— 未加 `objectFilter` 谓词回调;未动
   `packages/spec/src/data/hook.zod.ts` 的 authorable HookSchema
   (`check:authorable-surface` 绿即为证)。
5. 单 PR 跨两包,未拆两段(避免 spec 声明而引擎忽略的 declared≠enforced 窗口)。
6. 注册日志 `logger.debug('Registered hook', …)` 如实带上 `excludeObjects`。
7. 未做 #5860 的 plugin-audit 侧改动 —— 本单只交付表达能力本身。
8. changeset:spec + objectql 双包 minor。

## 一处刻意的行为守恒

`hookMatchesObject` 的允许半边保留两份旧实现共用的**真值判断**
(`if (entry.object)`),而非读起来更准的 `!== undefined`。二者仅在
`object: ''` 上不同:今天它注册的是**全局** hook(falsy ⇒ 不过滤)——
即 #4281 的失败模式在它未覆盖的代码路径上的残留(该裁决只关了 schema 与
binder)。在本 PR 里翻转它,会把一个「对所有对象触发」的 hook 静默变成
「对任何对象都不触发」。故按原样保留并显式钉住,另行记录。

## 验证读数

- `pnpm --filter @objectstack/objectql test` → 147 files / 2473 tests 全绿
- 新增 `hook-exclude-objects.test.ts` → 19/19 绿
- `pnpm --filter @objectstack/spec test` → 344 files / 8826 tests 全绿
- `pnpm --filter @objectstack/spec check:generated` → 10/10 up to date
- 两包 `typecheck` 绿(spec 含 scripts + test 三道)
- `check:spec-parsed-alias` / `check:nul-bytes` / `check:engine-double-contract` /
  `check:wildcard-fallthrough` / `check:startup-registry-verdict` /
  `check:durability-log-level` / `check:error-code-casing` / `check:adr-anchors` 全绿
- eslint 四个改动文件零告警

## 逆向验证(方向先判后跑)

R1 删掉共享匹配函数的**减法半边**(声明、日志、拒绝保留):
预判语义用例红、性质 pin **绿**;实测 6 红 13 绿 —— 两条真正的性质断言
如预判保持绿(它守的是门与派发的关系,不是排除面的存在),多出的 1 红是
`skipAutomations` 那条同时携带了一句排除断言,按它自己的理由红。

R2 让门比派发更紧(派发只读允许面,门读完整匹配):
预判性质 pin 红;实测正是它红,并打印出违例作用域:
`gate TIGHTER than dispatch for {"excludeObjects":"account"} on 'account'`。
另记一条诚实读数:多注册组合那一条在 R2 下仍绿 —— 集合里只要有一个全局
entry,门对任何对象都是开的,violation 无从浮现。检出力在单 entry 矩阵,
已在测试内注明,不要把它读成有牙的断言。

Refs: #5860(被本单解锁)、#5846#5284#5038#4281

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

vercel Bot commented Aug 8, 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 8, 2026 5:38am

Request Review

@github-actions github-actions Bot added the size/l label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/spec.

115 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 @objectstack/objectql, 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 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/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/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 @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/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 @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/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/http-protocol.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/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/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/field-grouping-and-order.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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 8, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 8, 2026 06:09
@qq9340100
qq9340100 added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 69787f0 Aug 8, 2026
26 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5928-hook-exclude-objects branch August 8, 2026 06:28
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 size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hook 注册契约只能表达「命中这些对象」,无法表达「全局但排除这些对象」—— #5860 因此在 plugin-audit 内无法落地

2 participants