feat(spec,objectql): hook 注册面新增 excludeObjects,可表达「全局但排除这些对象」 (#5928) - #6575
Merged
Conversation
注册契约此前只有一种表达能力:`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
…k-exclude-objects
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 115 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
qq9340100
marked this pull request as ready for review
August 8, 2026 06:09
This was referenced Aug 8, 2026
This was referenced Aug 8, 2026
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.
Fixes #5928
按裁决评论(issue 首条,claude[bot] 5205548295)采纳 A 形状:
registerHook与HookEntry新增声明式排除面excludeObjects,匹配语义matches = allowMatches && !excludeMatches。前提复核(origin/main)
裁决的两处论据修正已贯彻。原单行号均已漂移,内容逐处核实成立:
HookEntry.objectengine.ts:698registerHookoptionsengine.ts:1147triggerHooks按对象匹配engine.ts:1415hasHooksForengine.ts:1442(镜像在 1448):187-191excludeObjects也确非新造名词:api/rest-server.zod.ts:378与system/disaster-recovery.zod.ts:230已用它表达「全部减去这些」。为什么允许列表不够
允许列表与拒绝列表只在封闭全集上可互换,而对象全集在运行期是开放的:
/metaPUT 成功后applyObjectRegistryMutation直接把新对象注册进引擎,而SchemaRegistry.registerObject不发任何事件,插件侧没有可订阅的通道去维护一份枚举出来的名单。于是「全局但排除这几张平台表」只剩两条错路 —— 知识留在 handler 里早退(注册面仍是全局,#5284/#5038 的按对象门对这些对象判真、白读一遍行集),或把补集枚举进object(名单在启动时冻结,此后新建的对象静默不被覆盖,对审计插件是无声的合规倒退)。探针 D 的两个方向都已进测试。红线映射
hookMatchesObject();triggerHooks(engine.ts:1560)与hasHooksFor(:1593)两处手写实现同时收敛到它hook-exclude-objects.test.ts以 allow×exclude 全矩阵断言蕴含方向dispatched ⇒ gate open(非等价:hasHooksFor故意不看skipAutomations,更松合法)''、['']、任一空白成员 → throw;排除面出现'*'→ throw(减掉全集 = 永不触发)。三种拒绝均有用例,消息给出改法objectFilterpackages/spec/src/data/hook.zod.ts零改动;check:authorable-surface绿即为证logger.debug('Registered hook', …)增加excludeObjects,有用例钉住.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。验证读数
pnpm --filter @objectstack/objectql test→ 148 files / 2486 tests 全绿(含合入 main 后复跑)hook-exclude-objects.test.ts→ 19/19 绿pnpm --filter @objectstack/spec test→ 343 files / 8764 tests 全绿pnpm --filter @objectstack/spec check:generated→ 10/10 up to date(build 后跑;合入 main 后复跑一次)typecheck绿。新增的 spec 契约用例零类型错误 —— 未进test-typecheck-debt.json(该账本只减不增),编译期断言改用带类型标注的值断言,避开旧写法的noUnusedLocals报错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全绿origin/main(11 个提交,其中 fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468) #6553/fix(rest,objectql): the import dry run asks the engine for its verdict instead of predicting it (#4633) #6532 触 objectql、refactor(spec)!: ADR-0049 退役 sweep —— server 运行期词表 / ViewProtocol / L2 ETL(#5295 #6239 #6414) #6526 触 spec),重装依赖 + 重建后复跑上述全部逆向验证(方向先判后跑)
R1 —— 删掉共享匹配函数的减法半边(声明、日志、拒绝保留)。预判:语义用例红,性质 pin 绿。实测 6 红 13 绿 —— 两条真正的性质断言如预判保持绿,因为它守的是门与派发的关系,不是排除面的存在;多出的 1 红是
skipAutomations那条同时携带了一句排除断言,按它自己的理由红,不是 pin 抓到的。R2 —— 让门比派发更紧(派发只读允许面,门读完整匹配)。预判:性质 pin 红。实测正是它红,并打印出违例作用域:
一条诚实读数:多注册组合那条用例在 R2 下仍绿 —— 集合里只要有一个全局 entry,门对任何对象都是开的,violation 无从浮现。检出力在单 entry 矩阵上,已在测试内注明,不要把它读成有牙的断言。
落地连锁
合入后 #5860 解锁,收敛为「5 个注册带
excludeObjects: [...SKIP_OBJECTS]」。本 PR 不含任何 plugin-audit 改动。范围外发现
finding,未打pm:queue,未认领):registerHook代码注册面缺少 fix(spec,objectql)!: hook 的空目标不再被静默放大成通配符 #4281 那道空目标校验(object: ''静默变全局);并记录了 allow 与 exclude 完全抵消时静默永不触发的同类形状 —— 裁决只点名三种拒绝,该形状按字面不在范围内,刻意未做。全仓调用方传的都是字面量常量,今天无人能触发,故判观察级。Generated by Claude Code