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.

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